mcp-scraper 0.40.2 → 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.
Files changed (62) hide show
  1. package/README.md +2 -2
  2. package/dist/bin/api-server.cjs +2264 -337
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +1 -1
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +1 -1
  11. package/dist/bin/mcp-stdio-server.cjs +180 -92
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +6 -5
  14. package/dist/bin/mcp-stdio-server.js.map +1 -1
  15. package/dist/bin/paa-harvest.cjs +31 -9
  16. package/dist/bin/paa-harvest.cjs.map +1 -1
  17. package/dist/bin/paa-harvest.js +4 -4
  18. package/dist/{chunk-XORPNO3Z.js → chunk-27DUCRAZ.js} +2 -2
  19. package/dist/{chunk-44HZLHDV.js → chunk-5UN33CGU.js} +38 -1
  20. package/dist/chunk-5UN33CGU.js.map +1 -0
  21. package/dist/{chunk-X2LKCX6H.js → chunk-6J57U6HA.js} +3 -3
  22. package/dist/chunk-BBI7RGOT.js +99 -0
  23. package/dist/chunk-BBI7RGOT.js.map +1 -0
  24. package/dist/{chunk-NVXNEOUQ.js → chunk-CS4HE6QY.js} +47 -11
  25. package/dist/chunk-CS4HE6QY.js.map +1 -0
  26. package/dist/{chunk-3PIWJS6Y.js → chunk-EHES33KB.js} +2 -2
  27. package/dist/{chunk-GUVKHCKE.js → chunk-IQTT7CAB.js} +94 -3
  28. package/dist/chunk-IQTT7CAB.js.map +1 -0
  29. package/dist/{chunk-7XBBFBYY.js → chunk-JVIW4GK2.js} +44 -12
  30. package/dist/chunk-JVIW4GK2.js.map +1 -0
  31. package/dist/chunk-OOB35KFT.js +7 -0
  32. package/dist/chunk-OOB35KFT.js.map +1 -0
  33. package/dist/{chunk-LP6E462I.js → chunk-SIE5LZ2V.js} +74 -106
  34. package/dist/chunk-SIE5LZ2V.js.map +1 -0
  35. package/dist/{db-W3CP562I.js → db-LVVU6NOK.js} +16 -2
  36. package/dist/{extract-bundle-K4PG3RZJ.js → extract-bundle-SKXEDG4Z.js} +4 -4
  37. package/dist/index.cjs +31 -9
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.js +4 -4
  40. package/dist/{location-data-repository-RLQX6SNM.js → location-data-repository-NBTCOA7B.js} +3 -3
  41. package/dist/{server-KM3CFWCF.js → server-2JJPCZH4.js} +1812 -162
  42. package/dist/server-2JJPCZH4.js.map +1 -0
  43. package/dist/{site-extract-repository-2SMMFKKL.js → site-extract-repository-6GX72U4N.js} +4 -4
  44. package/dist/{worker-FXAGFYOE.js → worker-5RWLFSAJ.js} +27 -28
  45. package/dist/worker-5RWLFSAJ.js.map +1 -0
  46. package/package.json +1 -1
  47. package/dist/chunk-44HZLHDV.js.map +0 -1
  48. package/dist/chunk-7XBBFBYY.js.map +0 -1
  49. package/dist/chunk-GUVKHCKE.js.map +0 -1
  50. package/dist/chunk-LP6E462I.js.map +0 -1
  51. package/dist/chunk-NVXNEOUQ.js.map +0 -1
  52. package/dist/chunk-ZUJLSICT.js +0 -7
  53. package/dist/chunk-ZUJLSICT.js.map +0 -1
  54. package/dist/server-KM3CFWCF.js.map +0 -1
  55. package/dist/worker-FXAGFYOE.js.map +0 -1
  56. /package/dist/{chunk-XORPNO3Z.js.map → chunk-27DUCRAZ.js.map} +0 -0
  57. /package/dist/{chunk-X2LKCX6H.js.map → chunk-6J57U6HA.js.map} +0 -0
  58. /package/dist/{chunk-3PIWJS6Y.js.map → chunk-EHES33KB.js.map} +0 -0
  59. /package/dist/{db-W3CP562I.js.map → db-LVVU6NOK.js.map} +0 -0
  60. /package/dist/{extract-bundle-K4PG3RZJ.js.map → extract-bundle-SKXEDG4Z.js.map} +0 -0
  61. /package/dist/{location-data-repository-RLQX6SNM.js.map → location-data-repository-NBTCOA7B.js.map} +0 -0
  62. /package/dist/{site-extract-repository-2SMMFKKL.js.map → site-extract-repository-6GX72U4N.js.map} +0 -0
@@ -9,27 +9,38 @@ import {
9
9
  importHostedCensusPlacesCsv,
10
10
  importHostedZipGroupsCsv,
11
11
  listActiveHostedLocationDatasets
12
- } from "./chunk-XORPNO3Z.js";
12
+ } from "./chunk-27DUCRAZ.js";
13
13
  import {
14
14
  classifyHarvestProblem,
15
15
  createHarvestAttemptRecorder,
16
+ deliverWebhook,
16
17
  harvestProblemResponse,
17
18
  serializeHarvestProblem
18
- } from "./chunk-NVXNEOUQ.js";
19
+ } from "./chunk-CS4HE6QY.js";
19
20
  import {
21
+ AIModeSelectors,
22
+ AIOverviewSelectors,
20
23
  BACKUP_LAST_RESORT_ATTEMPTS,
21
24
  BrowserDriver,
25
+ ForumSelectors,
26
+ LocalPackSelectors,
22
27
  MapsSelectors,
28
+ OrganicSelectors,
29
+ PAASelectors,
30
+ VideoSelectors,
31
+ WhatPeopleSayingSelectors,
23
32
  backupProxyAvailable,
24
33
  buildYouTubeChannelVideosUrl,
25
34
  cleanupBackupProxyId,
26
35
  createBackupProxyIdSafe,
27
36
  deleteKernelProxyId,
37
+ encodeUule,
28
38
  harvest,
39
+ normalizeLocation,
29
40
  resolveBackupKernelProxyId,
30
41
  resolveKernelProxyId,
31
42
  serpArrivalUrl
32
- } from "./chunk-7XBBFBYY.js";
43
+ } from "./chunk-JVIW4GK2.js";
33
44
  import {
34
45
  CreateEditorialReadingRoomInputSchema,
35
46
  HttpMcpToolExecutor,
@@ -55,10 +66,8 @@ import {
55
66
  registerSerpIntelligenceCaptureTools,
56
67
  renewConnectedDataArtifactDownload,
57
68
  renewDirectoryArtifactDownload,
58
- sanitizeAttempts,
59
- sanitizeHarvestResult,
60
69
  transcribeMediaUrl
61
- } from "./chunk-LP6E462I.js";
70
+ } from "./chunk-SIE5LZ2V.js";
62
71
  import {
63
72
  auditImageUrls,
64
73
  auditImages,
@@ -69,6 +78,11 @@ import {
69
78
  renewPrivateArtifactDownload,
70
79
  validatePublicHttpUrl
71
80
  } from "./chunk-345BQXZH.js";
81
+ import {
82
+ sanitizeAttempts,
83
+ sanitizeHarvestResult,
84
+ sanitizeOutboundDiagnostics
85
+ } from "./chunk-BBI7RGOT.js";
72
86
  import {
73
87
  browserServiceApiKey,
74
88
  browserServiceProfileName,
@@ -78,7 +92,7 @@ import {
78
92
  recordKernelSession,
79
93
  recordVendorUsage,
80
94
  runWithCostContext
81
- } from "./chunk-3PIWJS6Y.js";
95
+ } from "./chunk-EHES33KB.js";
82
96
  import {
83
97
  listWorkflowDefinitions,
84
98
  runWorkflow,
@@ -105,7 +119,7 @@ import {
105
119
  RawMapsOverviewSchema,
106
120
  RawMapsReviewStatsSchema
107
121
  } from "./chunk-CB5C3BPB.js";
108
- import "./chunk-ZUJLSICT.js";
122
+ import "./chunk-OOB35KFT.js";
109
123
  import {
110
124
  abandonExtractSettlement,
111
125
  countSuccessfulPages,
@@ -128,7 +142,7 @@ import {
128
142
  setExtractJobTotal,
129
143
  settleExtractJob,
130
144
  terminalExtractJobStatus
131
- } from "./chunk-X2LKCX6H.js";
145
+ } from "./chunk-6J57U6HA.js";
132
146
  import {
133
147
  BROWSER_OPEN_MIN_BALANCE_MC,
134
148
  CONCURRENCY_PRICE_ID,
@@ -181,14 +195,18 @@ import {
181
195
  CaptchaError,
182
196
  MapsSearchIncompletePageError,
183
197
  RECAPTCHA_INSTRUCTIONS,
198
+ normalizePublicErrorCode,
199
+ publicErrorMessage,
184
200
  sanitizeVendorName
185
- } from "./chunk-44HZLHDV.js";
201
+ } from "./chunk-5UN33CGU.js";
186
202
  import {
187
203
  SiteAuditJobRowSchema,
188
204
  advanceWorkflowRunStep,
189
205
  appendWorkflowArtifacts,
190
206
  cancelJob,
191
207
  checkRateLimit,
208
+ checkpointJob,
209
+ claimInngestPaaJob,
192
210
  claimStripeEvent,
193
211
  completeJob,
194
212
  completeStripeEvent,
@@ -197,6 +215,7 @@ import {
197
215
  consumePasswordResetToken,
198
216
  countActiveUsers,
199
217
  createJob,
218
+ createJobWithId,
200
219
  createPasswordResetToken,
201
220
  createRunningJob,
202
221
  createUser,
@@ -212,6 +231,7 @@ import {
212
231
  failJob,
213
232
  failStripeEvent,
214
233
  failWorkflowRunRecord,
234
+ finishHarvestAttempt,
215
235
  getBillingLedgerSummary,
216
236
  getClient,
217
237
  getDb,
@@ -240,7 +260,10 @@ import {
240
260
  listHarvestAttempts,
241
261
  listInboxMessages,
242
262
  listJobs,
263
+ listPendingInngestPaaJobs,
264
+ listRecentTerminalInngestPaaJobs,
243
265
  listRequestEvents,
266
+ listStaleRunningInngestPaaJobs,
244
267
  listUsers,
245
268
  listUsersDueForConnectedAccountBillingReconciliation,
246
269
  listWorkflowArtifacts,
@@ -248,6 +271,7 @@ import {
248
271
  listWorkflowSchedules,
249
272
  logRequestEvent,
250
273
  markInboxMessageOpened,
274
+ markJobBillingSettled,
251
275
  markWorkflowRunRunning,
252
276
  markWorkflowScheduleRan,
253
277
  patchWorkflowSchedule,
@@ -271,9 +295,10 @@ import {
271
295
  setUserMemoryCreds,
272
296
  settleDebitMcIdempotent,
273
297
  softDeleteInboxMessage,
298
+ startHarvestAttempt,
274
299
  upsertPageSnapshot,
275
300
  verifyPassword
276
- } from "./chunk-GUVKHCKE.js";
301
+ } from "./chunk-IQTT7CAB.js";
277
302
 
278
303
  // src/blog/registry.ts
279
304
  var posts = [
@@ -3590,6 +3615,7 @@ async function generateOgImage(post) {
3590
3615
 
3591
3616
  // src/api/server.ts
3592
3617
  import { Resend as Resend3 } from "resend";
3618
+ import { randomUUID as randomUUID18 } from "crypto";
3593
3619
 
3594
3620
  // src/api/kpo-extractor.ts
3595
3621
  import TurndownService from "turndown";
@@ -8618,7 +8644,7 @@ var siteExtractFn = inngest.createFunction(
8618
8644
  });
8619
8645
  }) : null;
8620
8646
  const finalized = await step.run("finalize", async () => {
8621
- const { assembleExtractArtifacts } = await import("./extract-bundle-K4PG3RZJ.js");
8647
+ const { assembleExtractArtifacts } = await import("./extract-bundle-SKXEDG4Z.js");
8622
8648
  const current = await getExtractJob(jobId2);
8623
8649
  if (!current) throw new Error("extract job disappeared before finalization");
8624
8650
  const stored = await assembleExtractArtifacts(current, { branding, imageAudit });
@@ -9560,7 +9586,7 @@ async function resolveUsZipsPath(requestedPath) {
9560
9586
  async function loadZipGroups(stateAbbr, requestedPath, warnings, sourceMode) {
9561
9587
  const mode = resolvedLocationDataMode({ zipSource: sourceMode });
9562
9588
  if (mode === "hosted") {
9563
- const { getHostedZipGroups } = await import("./location-data-repository-RLQX6SNM.js");
9589
+ const { getHostedZipGroups } = await import("./location-data-repository-NBTCOA7B.js");
9564
9590
  const hosted = await getHostedZipGroups(stateAbbr);
9565
9591
  if (!hosted.dataset) {
9566
9592
  throw new DirectoryLocationError(
@@ -9623,7 +9649,7 @@ async function resolveDirectoryMarkets(options, config = {}) {
9623
9649
  const sourceUrl = censusStateUrl(state.fips);
9624
9650
  const warnings = [];
9625
9651
  if (resolvedLocationDataMode(config) === "hosted") {
9626
- const { queryHostedLocationMarkets } = await import("./location-data-repository-RLQX6SNM.js");
9652
+ const { queryHostedLocationMarkets } = await import("./location-data-repository-NBTCOA7B.js");
9627
9653
  const hosted = await queryHostedLocationMarkets({
9628
9654
  state: state.abbr,
9629
9655
  minPopulation: options.minPopulation,
@@ -10957,6 +10983,908 @@ var directoryWorkflowFn = inngest.createFunction(
10957
10983
  }
10958
10984
  );
10959
10985
 
10986
+ // src/api/paa-capture-artifacts.ts
10987
+ var DAY_MS = 24 * 60 * 60 * 1e3;
10988
+ var PAA_CAPTURE_ARTIFACT_PREFIX = "paa-captures";
10989
+ function paaCaptureArtifactPolicy() {
10990
+ return {
10991
+ prefix: PAA_CAPTURE_ARTIFACT_PREFIX,
10992
+ artifactTtlMs: 7 * DAY_MS,
10993
+ downloadTtlMs: 15 * 60 * 1e3,
10994
+ token: process.env.PAA_CAPTURE_BLOB_READ_WRITE_TOKEN ?? process.env.BLOB_READ_WRITE_TOKEN ?? null
10995
+ };
10996
+ }
10997
+ async function createPaaRawDomArtifact(input) {
10998
+ return createPrivateArtifact({
10999
+ policy: paaCaptureArtifactPolicy(),
11000
+ ownerId: String(input.userId),
11001
+ artifactKey: `${input.jobId}-attempt-${input.attempt}.html.gz`,
11002
+ createdAt: input.createdAt ?? /* @__PURE__ */ new Date(),
11003
+ filename: `${input.jobId}-serp.html.gz`,
11004
+ contentType: "application/gzip",
11005
+ content: input.gzip
11006
+ });
11007
+ }
11008
+ async function tryCreatePaaRawDomArtifact(input) {
11009
+ try {
11010
+ return {
11011
+ artifact: await createPaaRawDomArtifact(input),
11012
+ status: "stored"
11013
+ };
11014
+ } catch (error) {
11015
+ console.error("[paa-capture-artifact] private DOM preservation unavailable:", error instanceof Error ? error.message : String(error));
11016
+ return { artifact: null, status: "unavailable" };
11017
+ }
11018
+ }
11019
+
11020
+ // src/api/paa-harvest-settlement.ts
11021
+ function capturedQuestionCount(result) {
11022
+ if (!result || typeof result !== "object") return 0;
11023
+ const value = result;
11024
+ if (typeof value.totalQuestions === "number") return value.totalQuestions;
11025
+ return Array.isArray(value.progress?.records) ? value.progress.records.length : 0;
11026
+ }
11027
+ function finalCost(result, heldMc) {
11028
+ const questions = capturedQuestionCount(result);
11029
+ if (questions <= 0) return 0;
11030
+ return Math.min(heldMc, MC_COSTS.paa_base + questions * MC_COSTS.paa);
11031
+ }
11032
+ async function settlePaaHarvestJob(jobOrId) {
11033
+ const job = typeof jobOrId === "string" ? await getJob(jobOrId) : jobOrId;
11034
+ if (!job) return false;
11035
+ const options = job.options;
11036
+ const debitKey2 = options.billingDebitKey;
11037
+ const heldMc = Number(options.billingHoldMc ?? 0);
11038
+ if (!debitKey2 || !Number.isSafeInteger(heldMc) || heldMc <= 0) return false;
11039
+ const settlement = await settleDebitMcIdempotent(
11040
+ job.user_id,
11041
+ debitKey2,
11042
+ finalCost(job.result, heldMc),
11043
+ LedgerOperation.PAA_REFUND,
11044
+ "durable PAA harvest settlement",
11045
+ "paa_harvest"
11046
+ );
11047
+ await markJobBillingSettled(job.id, settlement.final_amount_mc);
11048
+ return true;
11049
+ }
11050
+
11051
+ // src/paa/durable-capture.ts
11052
+ import Kernel4 from "@onkernel/sdk";
11053
+ import { createHash as createHash2 } from "crypto";
11054
+ var PAA_INVOCATION_BUDGET_MS = 28e4;
11055
+ var PAA_BROWSER_WORK_BUDGET_MS = 25e4;
11056
+ var PAA_SOLVER_BUDGET_MS = 6e4;
11057
+ function remainingSeconds(deadlineMs, ceiling) {
11058
+ return Math.max(1, Math.min(ceiling, Math.floor((deadlineMs - Date.now()) / 1e3)));
11059
+ }
11060
+ async function execute(kernel, sessionId, code, deadlineMs, ceilingSeconds) {
11061
+ if (Date.now() >= deadlineMs) throw new Error("paa_work_deadline_exhausted");
11062
+ const response = await kernel.browsers.playwright.execute(sessionId, {
11063
+ code,
11064
+ timeout_sec: remainingSeconds(deadlineMs, ceilingSeconds)
11065
+ });
11066
+ if (!response.success || response.result === null || response.result === void 0) {
11067
+ throw new Error(response.error || response.stderr || "co-located browser execution failed");
11068
+ }
11069
+ return response.result;
11070
+ }
11071
+ function navigationCode(searchUrl) {
11072
+ return `
11073
+ const wait = ms => page.waitForTimeout(ms);
11074
+ const block = async () => {
11075
+ if (/google\\.[^/]+\\/sorry\\//i.test(page.url())) return 'captcha';
11076
+ if (await page.locator(${JSON.stringify(PAASelectors.captchaMarker)}).count().catch(() => 0)) return 'captcha';
11077
+ const title = await page.title().catch(() => '');
11078
+ const text = await page.locator('body').innerText({ timeout: 2000 }).catch(() => '');
11079
+ if (/recaptcha|unusual traffic|are you a robot|about this page|detected unusual traffic/i.test(text)) return 'captcha';
11080
+ if (/\\b403\\b.{0,120}forbidden|access denied|permission to access/i.test(title + '\\n' + text.slice(0, 3000))) return 'soft_block';
11081
+ return null;
11082
+ };
11083
+ await page.goto(${JSON.stringify(searchUrl)}, { waitUntil: 'domcontentloaded', timeout: 45000 });
11084
+ let captchaObserved = false;
11085
+ let solverCleared = false;
11086
+ let blocked = await block();
11087
+ const solverDeadline = Date.now() + ${PAA_SOLVER_BUDGET_MS};
11088
+ while (blocked === 'captcha' && Date.now() < solverDeadline) {
11089
+ captchaObserved = true;
11090
+ await wait(4000 + Math.floor(Math.random() * 1500));
11091
+ blocked = await block();
11092
+ if (!blocked) solverCleared = true;
11093
+ }
11094
+ let count = await page.locator(${JSON.stringify(PAASelectors.item)}).count().catch(() => 0);
11095
+ for (let scan = 0; !blocked && count === 0 && scan < 7; scan += 1) {
11096
+ await page.mouse.wheel(0, 520 + Math.floor(Math.random() * 420));
11097
+ await wait(350 + Math.floor(Math.random() * 500));
11098
+ count = await page.locator(${JSON.stringify(PAASelectors.item)}).count().catch(() => 0);
11099
+ }
11100
+ await page.evaluate((itemSelector) => {
11101
+ const state = window;
11102
+ if (state.__mcpTrustedListenerInstalled) return;
11103
+ document.addEventListener('click', event => {
11104
+ if (event.target instanceof Element && event.target.closest(itemSelector)) {
11105
+ state.__mcpLastPaaClickTrusted = event.isTrusted;
11106
+ }
11107
+ }, { capture: true });
11108
+ state.__mcpTrustedListenerInstalled = true;
11109
+ }, ${JSON.stringify(PAASelectors.item)}).catch(() => {});
11110
+ return {
11111
+ outcome: blocked || (count > 0 ? 'ready' : 'no_paa'),
11112
+ finalUrl: page.url(),
11113
+ title: await page.title().catch(() => ''),
11114
+ captchaObserved,
11115
+ solverCleared,
11116
+ environment: await page.evaluate(() => ({
11117
+ userAgent: navigator.userAgent,
11118
+ language: navigator.language,
11119
+ platform: navigator.platform,
11120
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
11121
+ innerWidth: window.innerWidth,
11122
+ innerHeight: window.innerHeight,
11123
+ outerWidth: window.outerWidth,
11124
+ outerHeight: window.outerHeight,
11125
+ screenWidth: window.screen.width,
11126
+ screenHeight: window.screen.height,
11127
+ devicePixelRatio: window.devicePixelRatio,
11128
+ })).catch(() => null),
11129
+ };
11130
+ `;
11131
+ }
11132
+ function recordReaderSource() {
11133
+ return `
11134
+ const clean = value => (value || '').replace(/\\u00a0/g, ' ').replace(/[ \\t]+/g, ' ').replace(/\\n{3,}/g, '\\n\\n').trim();
11135
+ const externalHref = anchor => {
11136
+ const href = anchor && anchor.href || '';
11137
+ if (!/^https?:\\/\\//i.test(href)) return '';
11138
+ try {
11139
+ const url = new URL(href);
11140
+ if (/(^|\\.)google(usercontent)?\\.[a-z.]+$/i.test(url.hostname)) {
11141
+ const redirected = url.searchParams.get('q') || url.searchParams.get('url');
11142
+ return redirected && redirected.startsWith('http') ? redirected : '';
11143
+ }
11144
+ } catch {}
11145
+ return href;
11146
+ };
11147
+ const sourceLabel = value => clean(value)
11148
+ .replace(/\\s*\\(\\+\\d+\\)\\s*-\\s*View related links.*$/i, '')
11149
+ .replace(/\\s*-\\s*Opens in new tab.*$/i, '').trim();
11150
+ const readRecords = () => Array.from(document.querySelectorAll(SEL.item)).map(item => {
11151
+ const question = clean(item.getAttribute(SEL.itemDataQ)
11152
+ || item.getAttribute(SEL.itemDataInitQ)
11153
+ || item.querySelector(SEL.itemQuestionEl)?.innerText);
11154
+ const expanded = item.classList.contains(SEL.expandedClass)
11155
+ || item.querySelector(SEL.clickTarget)?.getAttribute('aria-expanded') === 'true';
11156
+ const container = expanded ? item.querySelector(SEL.answerContainer) : null;
11157
+ const answerElement = container?.querySelector(SEL.aiAnswerBody) || container;
11158
+ const anchors = expanded ? Array.from(answerElement?.querySelectorAll(
11159
+ SEL.aiCitation + ',' + SEL.aiSourceCard + ',a[href]'
11160
+ ) || []) : [];
11161
+ const byUrl = new Map();
11162
+ for (const anchor of anchors) {
11163
+ const url = externalHref(anchor);
11164
+ if (!url || byUrl.has(url)) continue;
11165
+ let site = '';
11166
+ try { site = new URL(url).hostname.replace(/^www\\./, ''); } catch {}
11167
+ byUrl.set(url, { title: sourceLabel(anchor.getAttribute('aria-label')) || clean(anchor.textContent), site, url });
11168
+ }
11169
+ const sources = Array.from(byUrl.values());
11170
+ let sourceTitle = expanded ? clean(item.querySelector(SEL.sourceTitle)?.textContent) : '';
11171
+ let sourceSite = expanded ? clean(item.querySelector(SEL.sourceSite)?.textContent) : '';
11172
+ let sourceCite = expanded ? externalHref(item.querySelector(SEL.sourceCite)) : '';
11173
+ const primary = sources.find(source => source.title.length > 2) || sources[0];
11174
+ if (!sourceCite && primary) {
11175
+ sourceTitle ||= primary.title;
11176
+ sourceSite ||= primary.site;
11177
+ sourceCite = primary.url;
11178
+ }
11179
+ return {
11180
+ question,
11181
+ answer: clean(answerElement?.innerText || answerElement?.textContent),
11182
+ sourceTitle,
11183
+ sourceSite,
11184
+ sourceCite,
11185
+ sources,
11186
+ depth: 1,
11187
+ parentQuestion: null,
11188
+ };
11189
+ }).filter(record => record.question);
11190
+ `;
11191
+ }
11192
+ function expansionCode(clickedQuestions) {
11193
+ return `
11194
+ const SEL = ${JSON.stringify(PAASelectors)};
11195
+ ${recordReaderSource()}
11196
+ const deliveryBlock = async () => {
11197
+ if (/google\\.[^/]+\\/sorry\\//i.test(page.url())) return 'captcha';
11198
+ if (await page.locator(SEL.captchaMarker).count().catch(() => 0)) return 'captcha';
11199
+ const text = await page.locator('body').innerText({ timeout: 2000 }).catch(() => '');
11200
+ if (/recaptcha|unusual traffic|are you a robot|about this page/i.test(text)) return 'captcha';
11201
+ if (/\\b403\\b.{0,120}forbidden|access denied|permission to access/i.test(text.slice(0, 3000))) return 'soft_block';
11202
+ return null;
11203
+ };
11204
+ const blocked = await deliveryBlock();
11205
+ const records = await page.evaluate(({ SEL }) => {
11206
+ ${recordReaderSource()}
11207
+ return readRecords();
11208
+ }, { SEL });
11209
+ if (blocked) return {
11210
+ outcome: blocked, targetQuestion: null, records, newQuestions: [],
11211
+ finalUrl: page.url(), title: await page.title().catch(() => ''),
11212
+ interactionTrusted: null, clickError: null,
11213
+ };
11214
+ const clicked = new Set(${JSON.stringify(clickedQuestions)});
11215
+ const targetQuestion = records.map(record => record.question).find(question => !clicked.has(question)) || null;
11216
+ if (!targetQuestion) return {
11217
+ outcome: 'exhausted', targetQuestion: null, records, newQuestions: [],
11218
+ finalUrl: page.url(), title: await page.title().catch(() => ''),
11219
+ interactionTrusted: null, clickError: null,
11220
+ };
11221
+ const items = page.locator(SEL.item);
11222
+ let targetIndex = -1;
11223
+ for (let index = 0, count = await items.count().catch(() => 0); index < count; index += 1) {
11224
+ const question = await items.nth(index).evaluate((item, SEL) =>
11225
+ item.getAttribute(SEL.itemDataQ)
11226
+ || item.getAttribute(SEL.itemDataInitQ)
11227
+ || item.querySelector(SEL.itemQuestionEl)?.innerText?.trim()
11228
+ || '', SEL).catch(() => '');
11229
+ if (question.replace(/\\s+/g, ' ').trim() === targetQuestion) { targetIndex = index; break; }
11230
+ }
11231
+ if (targetIndex < 0) return {
11232
+ outcome: 'exhausted', targetQuestion, records, newQuestions: [],
11233
+ finalUrl: page.url(), title: await page.title().catch(() => ''),
11234
+ interactionTrusted: null, clickError: 'question disappeared before click',
11235
+ };
11236
+ const beforeQuestions = new Set(records.map(record => record.question));
11237
+ const item = items.nth(targetIndex);
11238
+ await item.scrollIntoViewIfNeeded({ timeout: 5000 }).catch(() => {});
11239
+ await page.waitForTimeout(180 + Math.floor(Math.random() * 420));
11240
+ await page.evaluate(() => { window.__mcpLastPaaClickTrusted = null; }).catch(() => {});
11241
+ let clickError = null;
11242
+ try {
11243
+ await item.locator(SEL.clickTarget).first().click({
11244
+ timeout: 6000,
11245
+ delay: 55 + Math.floor(Math.random() * 120),
11246
+ });
11247
+ } catch (error) {
11248
+ clickError = String(error && error.message || error).slice(0, 240);
11249
+ }
11250
+ const interactionTrusted = await page.evaluate(() => window.__mcpLastPaaClickTrusted ?? null).catch(() => null);
11251
+ await page.waitForTimeout(650 + Math.floor(Math.random() * 750));
11252
+ const afterRecords = await page.evaluate(({ SEL }) => {
11253
+ ${recordReaderSource()}
11254
+ return readRecords();
11255
+ }, { SEL });
11256
+ return {
11257
+ outcome: clickError ? 'exhausted' : 'clicked',
11258
+ targetQuestion,
11259
+ records: afterRecords,
11260
+ newQuestions: afterRecords.map(record => record.question).filter(question => !beforeQuestions.has(question)),
11261
+ finalUrl: page.url(),
11262
+ title: await page.title().catch(() => ''),
11263
+ interactionTrusted,
11264
+ clickError,
11265
+ };
11266
+ `;
11267
+ }
11268
+ function finalCaptureCode(maxQuestions, parentByQuestion) {
11269
+ const surfaces = {
11270
+ aiOverview: AIOverviewSelectors,
11271
+ aiMode: AIModeSelectors,
11272
+ organic: OrganicSelectors,
11273
+ localPack: LocalPackSelectors,
11274
+ video: VideoSelectors,
11275
+ forum: ForumSelectors,
11276
+ whatPeopleSaying: WhatPeopleSayingSelectors
11277
+ };
11278
+ return `
11279
+ const SEL = ${JSON.stringify(PAASelectors)};
11280
+ const SURF = ${JSON.stringify(surfaces)};
11281
+ const PARENTS = ${JSON.stringify(parentByQuestion)};
11282
+ ${recordReaderSource()}
11283
+ const capture = await page.evaluate(({ SEL, SURF, PARENTS, maxQuestions }) => {
11284
+ ${recordReaderSource()}
11285
+ const unique = (items, key) => {
11286
+ const seen = new Set();
11287
+ return items.filter(item => { const value = key(item); if (!value || seen.has(value)) return false; seen.add(value); return true; });
11288
+ };
11289
+ const paa = readRecords().map(record => {
11290
+ const parentQuestion = PARENTS[record.question] || null;
11291
+ let depth = 1, parent = parentQuestion;
11292
+ const seen = new Set();
11293
+ while (parent && !seen.has(parent)) { seen.add(parent); depth += 1; parent = PARENTS[parent] || null; }
11294
+ return { ...record, depth, parentQuestion };
11295
+ }).slice(0, maxQuestions);
11296
+ const organicResults = [];
11297
+ document.querySelectorAll(SURF.organic.result).forEach(card => {
11298
+ const titleElement = card.querySelector(SURF.organic.title);
11299
+ const title = clean(titleElement?.textContent);
11300
+ const link = titleElement?.closest('a');
11301
+ const url = externalHref(link);
11302
+ if (!title || !url) return;
11303
+ let domain = '';
11304
+ try { domain = new URL(url).hostname.replace(/^www\\./, ''); } catch {}
11305
+ const rating = card.querySelector(SURF.organic.ratingWrap);
11306
+ organicResults.push({
11307
+ position: organicResults.length + 1, title, url, domain,
11308
+ cite: clean(card.querySelector(SURF.organic.cite)?.textContent) || null,
11309
+ snippet: clean(card.querySelector(SURF.organic.snippet)?.textContent) || null,
11310
+ isRedditStyle: Boolean(card.querySelector(SURF.organic.redditCite)),
11311
+ inlineRating: rating ? {
11312
+ value: clean(rating.querySelector(SURF.organic.ratingValue)?.textContent),
11313
+ count: clean(rating.querySelector(SURF.organic.reviewCount)?.textContent),
11314
+ } : null,
11315
+ });
11316
+ });
11317
+ const localPack = [];
11318
+ document.querySelectorAll(SURF.localPack.card).forEach(card => {
11319
+ const name = clean(card.querySelector(SURF.localPack.name)?.textContent);
11320
+ if (!name) return;
11321
+ const links = Array.from(card.querySelectorAll('a[href]'));
11322
+ let cid = card.querySelector('a[data-cid]')?.getAttribute('data-cid') || null;
11323
+ for (const link of links) {
11324
+ if (cid) break;
11325
+ const match = link.href.match(/[?&]cid=(\\d+)/);
11326
+ if (match) cid = match[1];
11327
+ }
11328
+ localPack.push({
11329
+ position: localPack.length + 1, name, cid,
11330
+ rating: clean(card.querySelector(SURF.localPack.ratingValue)?.textContent) || null,
11331
+ reviewCount: clean(card.querySelector(SURF.localPack.reviewCount)?.textContent).replace(/[()]/g, '') || null,
11332
+ metadata: [], websiteUrl: links.map(externalHref).find(Boolean) || null,
11333
+ directionsUrl: links.find(link => link.href.includes('google.com/maps'))?.href || null,
11334
+ });
11335
+ });
11336
+ const videos = unique(Array.from(document.querySelectorAll(SURF.video.item)).map(link => {
11337
+ const url = externalHref(link);
11338
+ const raw = clean(link.textContent);
11339
+ const platform = /youtu/i.test(url) ? 'YouTube' : /tiktok/i.test(url) ? 'TikTok' : /instagram/i.test(url) ? 'Instagram' : /facebook/i.test(url) ? 'Facebook' : '';
11340
+ return { type: 'video', title: raw, channel: '', platform, duration: raw.match(/\\b\\d{1,2}:\\d{2}\\b/)?.[0] || '', url };
11341
+ }).filter(item => item.title && item.url), item => item.url);
11342
+ const forums = unique(Array.from(document.querySelectorAll(SURF.forum.item)).map(link => ({
11343
+ title: clean(link.querySelector(SURF.forum.title)?.textContent),
11344
+ source: clean(link.querySelector(SURF.forum.source)?.textContent),
11345
+ url: externalHref(link),
11346
+ })).filter(item => item.title && item.url), item => item.url);
11347
+ const socialCards = Array.from(document.querySelectorAll(SURF.whatPeopleSaying.card));
11348
+ const whatPeopleSaying = unique(socialCards.map(card => {
11349
+ const link = card.querySelector(SURF.whatPeopleSaying.cardLink);
11350
+ const url = externalHref(link);
11351
+ const title = clean(card.querySelector(SURF.whatPeopleSaying.titleH1)?.textContent || card.querySelector(SURF.whatPeopleSaying.titleDiv)?.textContent);
11352
+ const source = clean(card.querySelector(SURF.whatPeopleSaying.ytChannel)?.textContent || card.querySelector(SURF.whatPeopleSaying.source)?.textContent);
11353
+ const platform = clean(card.querySelector(SURF.whatPeopleSaying.platformBadge)?.textContent) || (/youtube/i.test(url) ? 'YouTube' : '');
11354
+ const identity = (platform + source + url).toLowerCase();
11355
+ const type = identity.includes('reddit') ? 'reddit' : identity.includes('facebook') ? 'facebook' : identity.includes('instagram') ? 'instagram' : identity.includes('tiktok') ? 'tiktok' : identity.includes('youtube') ? 'youtube' : 'unknown';
11356
+ return { type, title, url, source, platform, popularComment: null, engagement: '', date: '', duration: null, authorNote: null };
11357
+ }).filter(item => item.title || item.url), item => item.url || item.type + ':' + item.title);
11358
+ const aioRoot = document.querySelector(SURF.aiOverview.root) || document.querySelector(SURF.aiOverview.legacyRoot);
11359
+ const aimRoot = document.querySelector(SURF.aiMode.root);
11360
+ const citations = root => unique(Array.from(root?.querySelectorAll('a[href]') || []).map(link => ({
11361
+ text: clean(link.textContent || link.getAttribute('aria-label')), href: externalHref(link),
11362
+ })).filter(item => item.href), item => item.href);
11363
+ const aiOverview = { detected: Boolean(aioRoot), text: aioRoot ? clean(aioRoot.innerText || aioRoot.textContent) : null, citations: citations(aioRoot), expanded: true, fullyExpanded: true, sections: [] };
11364
+ const aiMode = { detected: Boolean(aimRoot), text: aimRoot ? clean(aimRoot.innerText || aimRoot.textContent) : null, citations: citations(aimRoot) };
11365
+ const html = document.documentElement.outerHTML;
11366
+ const kgIds = unique(Array.from(html.matchAll(/\\/g\\/[a-zA-Z0-9_-]{5,20}/g)).map(match => match[0]), value => value);
11367
+ const cids = unique([
11368
+ ...localPack.map(item => item.cid).filter(Boolean),
11369
+ ...Array.from(html.matchAll(/[?&]cid=(\\d+)/g)).map(match => match[1]),
11370
+ ], value => value);
11371
+ const gcids = unique(Array.from(html.matchAll(/gcid:[a-zA-Z0-9_]+/g)).map(match => match[0]), value => value);
11372
+ const entities = localPack.filter(item => item.cid).map(item => ({ name: item.name, kgId: null, cid: item.cid, gcid: null }));
11373
+ const headings = unique(Array.from(document.querySelectorAll('h1,h2,h3,[role="heading"]')).map(node => clean(node.textContent)).filter(text => text.length > 1 && text.length < 160), value => value);
11374
+ const relatedSearches = unique(Array.from(document.querySelectorAll('a[href*="/search?"]')).map(link => ({
11375
+ text: clean(link.textContent), url: link.href,
11376
+ })).filter(item => item.text && /related/i.test(item.url + ' ' + item.text)), item => item.url);
11377
+ const counts = {
11378
+ peopleAlsoAsk: paa.length, organic: organicResults.length, localPack: localPack.length,
11379
+ videos: videos.length, discussions: forums.length, whatPeopleSaying: whatPeopleSaying.length,
11380
+ aiOverview: aiOverview.detected ? 1 : 0, aiMode: aiMode.detected ? 1 : 0,
11381
+ relatedSearches: relatedSearches.length,
11382
+ };
11383
+ return {
11384
+ paa, organicResults: unique(organicResults, item => item.url), localPack: unique(localPack, item => item.cid || item.name),
11385
+ videos, forums, whatPeopleSaying, aiOverview, aiMode,
11386
+ surface: window.google?.sn === 'aim' ? 'aim' : window.google?.sn === 'web' ? 'web' : 'unknown',
11387
+ entityIds: { entities, kgIds, cids, gcids },
11388
+ serpFeatures: { sectionHeadings: headings, detected: Object.fromEntries(Object.entries(counts).map(([key, count]) => [key, count > 0])), counts },
11389
+ relatedSearches,
11390
+ };
11391
+ }, { SEL, SURF, PARENTS, maxQuestions: ${maxQuestions} });
11392
+ return capture;
11393
+ `;
11394
+ }
11395
+ var RAW_PREPARE_CODE = `
11396
+ const result = await page.evaluate(async () => {
11397
+ const source = new TextEncoder().encode(document.documentElement.outerHTML);
11398
+ const compressed = typeof CompressionStream === 'undefined'
11399
+ ? source
11400
+ : new Uint8Array(await new Response(new Blob([source]).stream().pipeThrough(new CompressionStream('gzip'))).arrayBuffer());
11401
+ const pieces = [];
11402
+ for (let offset = 0; offset < compressed.length; offset += 32768) {
11403
+ pieces.push(String.fromCharCode(...compressed.subarray(offset, offset + 32768)));
11404
+ }
11405
+ window.__mcpPaaRawDom = btoa(pieces.join(''));
11406
+ return { encoding: typeof CompressionStream === 'undefined' ? 'plain' : 'gzip', chars: window.__mcpPaaRawDom.length };
11407
+ });
11408
+ return result;
11409
+ `;
11410
+ function rawChunkCode(offset, length) {
11411
+ return `return await page.evaluate(({ offset, length }) => ({ offset, chunk: window.__mcpPaaRawDom.slice(offset, offset + length) }), { offset: ${offset}, length: ${length} });`;
11412
+ }
11413
+ function buildPublicResult(input) {
11414
+ const extractedAt = (/* @__PURE__ */ new Date()).toISOString();
11415
+ const selected = input.records;
11416
+ const flat = selected.map((record) => ({
11417
+ seed_query: input.query,
11418
+ question: record.question,
11419
+ answer: record.answer,
11420
+ source_title: record.sourceTitle,
11421
+ source_site: record.sourceSite,
11422
+ source_cite: record.sourceCite,
11423
+ depth: record.depth,
11424
+ parent_question: record.parentQuestion ?? "",
11425
+ extracted_at: extractedAt
11426
+ }));
11427
+ const tree = selected.map((record) => ({
11428
+ question: record.question,
11429
+ answer: record.answer || null,
11430
+ sourceTitle: record.sourceTitle || null,
11431
+ sourceSite: record.sourceSite || null,
11432
+ sourceCite: record.sourceCite || null,
11433
+ depth: record.depth,
11434
+ parentQuestion: record.parentQuestion,
11435
+ children: []
11436
+ }));
11437
+ return {
11438
+ seed: input.query,
11439
+ location: input.location,
11440
+ extractedAt,
11441
+ diagnostics: {
11442
+ completionStatus: selected.length > 0 ? "paa_found" : "no_paa",
11443
+ problem: null,
11444
+ resultQuality: input.partial ? "partial" : "complete",
11445
+ degradedResult: input.partial,
11446
+ retryRecommended: false,
11447
+ completeness: {
11448
+ paaWithoutAnswer: selected.filter((record) => !record.answer).length,
11449
+ paaWithoutSource: selected.filter((record) => !record.sourceCite).length,
11450
+ paaAnswersRecovered: 0,
11451
+ aioShareCaptured: null
11452
+ }
11453
+ },
11454
+ totalQuestions: selected.length,
11455
+ surface: input.capture.surface,
11456
+ aiOverview: input.capture.aiOverview,
11457
+ aiMode: input.capture.aiMode,
11458
+ whatPeopleSaying: input.capture.whatPeopleSaying,
11459
+ tree,
11460
+ flat,
11461
+ videos: input.capture.videos,
11462
+ forums: input.capture.forums,
11463
+ organicResults: input.capture.organicResults,
11464
+ localPack: input.capture.localPack,
11465
+ entityIds: input.capture.entityIds,
11466
+ serpFeatures: input.capture.serpFeatures,
11467
+ relatedSearches: input.capture.relatedSearches,
11468
+ stats: {
11469
+ seed: input.query,
11470
+ totalQuestions: selected.length,
11471
+ maxDepthReached: selected.reduce((max, item) => Math.max(max, item.depth), 0),
11472
+ durationMs: input.durationMs,
11473
+ errorCount: input.partial ? 1 : 0
11474
+ },
11475
+ capture: {
11476
+ execution: "kernel_colocated_playwright",
11477
+ stealth: true,
11478
+ managedProxy: "kernel_default",
11479
+ metadataOverrides: false,
11480
+ locationSignal: input.location ? "uule_only" : "none",
11481
+ attempt: input.attempt,
11482
+ finalUrl: input.navigation.finalUrl,
11483
+ captchaObserved: input.navigation.captchaObserved,
11484
+ solverCleared: input.navigation.solverCleared,
11485
+ environment: input.navigation.environment,
11486
+ rawDomSha256: input.rawDomSha256
11487
+ }
11488
+ };
11489
+ }
11490
+ async function runDurablePaaCapture(input) {
11491
+ const startedAt = input.startedAtMs ?? Date.now();
11492
+ const workDeadline = startedAt + PAA_BROWSER_WORK_BUDGET_MS;
11493
+ const location2 = input.location?.trim() || null;
11494
+ const params = new URLSearchParams({
11495
+ q: input.query,
11496
+ gl: input.gl ?? "us",
11497
+ hl: input.hl ?? "en",
11498
+ pws: "0"
11499
+ });
11500
+ if (location2) params.set("uule", encodeUule(normalizeLocation(location2)));
11501
+ const searchUrl = `https://www.google.com/search?${params.toString()}`;
11502
+ const kernel = new Kernel4({ apiKey: input.apiKey });
11503
+ let lastCheckpoint = {
11504
+ phase: "navigating",
11505
+ attempt: 1,
11506
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
11507
+ records: [],
11508
+ clickedQuestions: [],
11509
+ finalUrl: null,
11510
+ captchaObserved: false,
11511
+ solverCleared: false,
11512
+ error: null
11513
+ };
11514
+ const persist = async (patch) => {
11515
+ lastCheckpoint = { ...lastCheckpoint, ...patch, observedAt: (/* @__PURE__ */ new Date()).toISOString() };
11516
+ await input.onCheckpoint?.(lastCheckpoint);
11517
+ };
11518
+ for (let attempt = 1; attempt <= 2; attempt += 1) {
11519
+ if (Date.now() >= workDeadline) break;
11520
+ let sessionId = null;
11521
+ const records = /* @__PURE__ */ new Map();
11522
+ const clicked = /* @__PURE__ */ new Set();
11523
+ const parents = {};
11524
+ let navigation = null;
11525
+ try {
11526
+ await persist({ phase: "navigating", attempt, records: [], clickedQuestions: [], error: null });
11527
+ const browser = await kernel.browsers.create({
11528
+ stealth: true,
11529
+ timeout_seconds: Math.max(60, Math.min(280, Math.ceil((workDeadline - Date.now()) / 1e3) + 20))
11530
+ });
11531
+ sessionId = browser.session_id;
11532
+ navigation = await execute(kernel, sessionId, navigationCode(searchUrl), workDeadline, 110);
11533
+ await persist({
11534
+ phase: navigation.outcome === "ready" ? "expanding" : "blocked",
11535
+ finalUrl: navigation.finalUrl,
11536
+ captchaObserved: navigation.captchaObserved,
11537
+ solverCleared: navigation.solverCleared
11538
+ });
11539
+ if (navigation.outcome === "captcha" || navigation.outcome === "soft_block") {
11540
+ if (attempt === 1 && records.size === 0) continue;
11541
+ throw new Error(navigation.outcome);
11542
+ }
11543
+ let stagnant = 0;
11544
+ for (let step = 0; step < input.maxQuestions * 2 && Date.now() < workDeadline - 35e3; step += 1) {
11545
+ const observed = await execute(
11546
+ kernel,
11547
+ sessionId,
11548
+ expansionCode(Array.from(clicked)),
11549
+ workDeadline,
11550
+ 25
11551
+ );
11552
+ const beforeSize = records.size;
11553
+ for (const record of observed.records) {
11554
+ const existing = records.get(record.question);
11555
+ records.set(record.question, {
11556
+ ...record,
11557
+ answer: record.answer || existing?.answer || "",
11558
+ sourceTitle: record.sourceTitle || existing?.sourceTitle || "",
11559
+ sourceSite: record.sourceSite || existing?.sourceSite || "",
11560
+ sourceCite: record.sourceCite || existing?.sourceCite || "",
11561
+ sources: Array.from(new Map([...existing?.sources ?? [], ...record.sources].map((source) => [source.url, source])).values()),
11562
+ parentQuestion: parents[record.question] ?? null,
11563
+ depth: parents[record.question] ? 2 : 1
11564
+ });
11565
+ }
11566
+ if (observed.outcome === "clicked" && observed.targetQuestion) {
11567
+ clicked.add(observed.targetQuestion);
11568
+ for (const question of observed.newQuestions) {
11569
+ if (!parents[question]) parents[question] = observed.targetQuestion;
11570
+ }
11571
+ }
11572
+ const selected2 = Array.from(records.values()).slice(0, input.maxQuestions);
11573
+ await persist({
11574
+ phase: "expanding",
11575
+ records: selected2,
11576
+ clickedQuestions: Array.from(clicked),
11577
+ finalUrl: observed.finalUrl,
11578
+ captchaObserved: lastCheckpoint.captchaObserved || observed.outcome === "captcha"
11579
+ });
11580
+ if (observed.outcome === "captcha" || observed.outcome === "soft_block") break;
11581
+ if (selected2.length >= input.maxQuestions && selected2.every((record) => clicked.has(record.question))) break;
11582
+ stagnant = records.size === beforeSize || observed.outcome === "exhausted" ? stagnant + 1 : 0;
11583
+ if (stagnant >= 4) break;
11584
+ }
11585
+ const finalCapture = await execute(
11586
+ kernel,
11587
+ sessionId,
11588
+ finalCaptureCode(input.maxQuestions, parents),
11589
+ workDeadline,
11590
+ 45
11591
+ );
11592
+ for (const record of finalCapture.paa) {
11593
+ const existing = records.get(record.question);
11594
+ records.set(record.question, {
11595
+ ...record,
11596
+ answer: record.answer || existing?.answer || "",
11597
+ sourceTitle: record.sourceTitle || existing?.sourceTitle || "",
11598
+ sourceSite: record.sourceSite || existing?.sourceSite || "",
11599
+ sourceCite: record.sourceCite || existing?.sourceCite || "",
11600
+ sources: Array.from(new Map([...existing?.sources ?? [], ...record.sources].map((source) => [source.url, source])).values()),
11601
+ parentQuestion: parents[record.question] ?? record.parentQuestion,
11602
+ depth: record.depth
11603
+ });
11604
+ }
11605
+ let rawDomGzip = null;
11606
+ let rawDomSha256 = null;
11607
+ if (Date.now() < workDeadline - 12e3) {
11608
+ const prepared = await execute(
11609
+ kernel,
11610
+ sessionId,
11611
+ RAW_PREPARE_CODE,
11612
+ workDeadline,
11613
+ 35
11614
+ );
11615
+ const chunks = [];
11616
+ for (let offset = 0; offset < prepared.chars && Date.now() < workDeadline - 5e3; offset += 18e4) {
11617
+ const part = await execute(
11618
+ kernel,
11619
+ sessionId,
11620
+ rawChunkCode(offset, 18e4),
11621
+ workDeadline,
11622
+ 15
11623
+ );
11624
+ if (part.offset !== offset) throw new Error("raw_dom_chunk_offset_mismatch");
11625
+ chunks.push(part.chunk);
11626
+ }
11627
+ if (chunks.join("").length === prepared.chars) {
11628
+ const transported = Buffer.from(chunks.join(""), "base64");
11629
+ if (prepared.encoding === "gzip") rawDomGzip = transported;
11630
+ else {
11631
+ const { gzipSync } = await import("zlib");
11632
+ rawDomGzip = gzipSync(transported);
11633
+ }
11634
+ rawDomSha256 = createHash2("sha256").update(rawDomGzip).digest("hex");
11635
+ }
11636
+ }
11637
+ const selected = Array.from(records.values()).slice(0, input.maxQuestions);
11638
+ const partial = selected.length < input.maxQuestions || selected.some((record) => !record.answer || !record.sourceCite);
11639
+ await persist({ phase: "captured", records: selected, clickedQuestions: Array.from(clicked) });
11640
+ return {
11641
+ result: buildPublicResult({
11642
+ query: input.query,
11643
+ location: location2,
11644
+ capture: finalCapture,
11645
+ records: selected,
11646
+ navigation,
11647
+ durationMs: Date.now() - startedAt,
11648
+ partial,
11649
+ attempt,
11650
+ rawDomSha256
11651
+ }),
11652
+ rawDomGzip,
11653
+ rawDomSha256,
11654
+ attempt,
11655
+ partial,
11656
+ checkpoint: lastCheckpoint
11657
+ };
11658
+ } catch (error) {
11659
+ const message = error instanceof Error ? error.message : String(error);
11660
+ await persist({
11661
+ phase: records.size > 0 ? "captured" : "failed",
11662
+ records: Array.from(records.values()).slice(0, input.maxQuestions),
11663
+ clickedQuestions: Array.from(clicked),
11664
+ error: message.slice(0, 500)
11665
+ });
11666
+ if (records.size > 0) {
11667
+ const emptyCapture = {
11668
+ paa: [],
11669
+ organicResults: [],
11670
+ localPack: [],
11671
+ videos: [],
11672
+ forums: [],
11673
+ whatPeopleSaying: [],
11674
+ aiOverview: { detected: false, text: null, citations: [] },
11675
+ aiMode: { detected: false, text: null, citations: [] },
11676
+ surface: "unknown",
11677
+ entityIds: { entities: [], kgIds: [], cids: [], gcids: [] },
11678
+ serpFeatures: { sectionHeadings: [], detected: {}, counts: {} },
11679
+ relatedSearches: []
11680
+ };
11681
+ return {
11682
+ result: buildPublicResult({
11683
+ query: input.query,
11684
+ location: location2,
11685
+ capture: emptyCapture,
11686
+ records: Array.from(records.values()).slice(0, input.maxQuestions),
11687
+ navigation: navigation ?? {
11688
+ outcome: "soft_block",
11689
+ finalUrl: lastCheckpoint.finalUrl ?? "",
11690
+ title: "",
11691
+ captchaObserved: lastCheckpoint.captchaObserved,
11692
+ solverCleared: lastCheckpoint.solverCleared,
11693
+ environment: null
11694
+ },
11695
+ durationMs: Date.now() - startedAt,
11696
+ partial: true,
11697
+ attempt,
11698
+ rawDomSha256: null
11699
+ }),
11700
+ rawDomGzip: null,
11701
+ rawDomSha256: null,
11702
+ attempt,
11703
+ partial: true,
11704
+ checkpoint: lastCheckpoint
11705
+ };
11706
+ }
11707
+ if (attempt === 2 || Date.now() >= workDeadline - 35e3) throw error;
11708
+ } finally {
11709
+ if (sessionId) {
11710
+ await kernel.browsers.deleteByID(sessionId).catch(() => void 0);
11711
+ }
11712
+ }
11713
+ }
11714
+ throw new Error("paa_work_deadline_exhausted_before_capture");
11715
+ }
11716
+
11717
+ // src/inngest/functions/paa-harvest.ts
11718
+ function questionCount(result) {
11719
+ if (!result || typeof result !== "object") return 0;
11720
+ const value = result;
11721
+ if (typeof value.totalQuestions === "number") return value.totalQuestions;
11722
+ return Array.isArray(value.progress?.records) ? value.progress.records.length : 0;
11723
+ }
11724
+ var paaHarvestFn = inngest.createFunction(
11725
+ {
11726
+ id: "paa-harvest",
11727
+ retries: 0,
11728
+ triggers: [{ event: "mcp-scraper/paa.requested" }],
11729
+ onFailure: async ({ event }) => {
11730
+ const jobId2 = event?.data?.event?.data?.jobId;
11731
+ if (!jobId2) return;
11732
+ const current = await getJob(jobId2);
11733
+ if (!current) return;
11734
+ if (current.status === "done" || current.status === "failed" || current.status === "cancelled") {
11735
+ await settlePaaHarvestJob(jobId2).catch((error) => {
11736
+ console.error("[paa-harvest/onFailure] terminal settlement pending:", error instanceof Error ? error.message : String(error));
11737
+ });
11738
+ return;
11739
+ }
11740
+ const message = String(event?.data?.error?.message ?? "durable PAA invocation failed").slice(0, 1e3);
11741
+ await failJob(jobId2, message);
11742
+ await settlePaaHarvestJob(jobId2).catch((error) => {
11743
+ console.error("[paa-harvest/onFailure] settlement pending:", error instanceof Error ? error.message : String(error));
11744
+ });
11745
+ }
11746
+ },
11747
+ async ({ event }) => {
11748
+ const invocationStartedAt = Date.now();
11749
+ const job = await claimInngestPaaJob(event.data.jobId);
11750
+ if (!job) return { jobId: event.data.jobId, status: "missing_or_not_paa" };
11751
+ if (job.status === "done" || job.status === "failed" || job.status === "cancelled") {
11752
+ await settlePaaHarvestJob(job.id);
11753
+ return { jobId: job.id, status: job.status };
11754
+ }
11755
+ const options = job.options;
11756
+ const maxQuestions = Math.max(1, Math.min(100, Number(options.maxQuestions ?? 30)));
11757
+ const apiKey = browserServiceApiKey();
11758
+ if (!apiKey) throw new Error("browser service is not configured");
11759
+ const attemptStartedAt = (/* @__PURE__ */ new Date()).toISOString();
11760
+ await startHarvestAttempt({
11761
+ jobId: job.id,
11762
+ userId: job.user_id,
11763
+ attemptNumber: 1,
11764
+ maxAttempts: 2,
11765
+ query: options.query ?? job.query,
11766
+ location: options.location ?? null,
11767
+ maxQuestions,
11768
+ startedAt: attemptStartedAt
11769
+ });
11770
+ const checkpointState = { value: null };
11771
+ try {
11772
+ const capture = await runDurablePaaCapture({
11773
+ apiKey,
11774
+ query: options.query ?? job.query,
11775
+ location: options.location ?? null,
11776
+ gl: options.gl ?? "us",
11777
+ hl: options.hl ?? "en",
11778
+ maxQuestions,
11779
+ startedAtMs: invocationStartedAt,
11780
+ onCheckpoint: async (checkpoint) => {
11781
+ checkpointState.value = checkpoint;
11782
+ const persisted = await checkpointJob(job.id, {
11783
+ progress: checkpoint,
11784
+ durable: {
11785
+ invocationBudgetMs: PAA_INVOCATION_BUDGET_MS,
11786
+ workBudgetMs: 25e4,
11787
+ cleanupReserveMs: 3e4
11788
+ }
11789
+ });
11790
+ if (!persisted) throw new Error("job_left_running_state_during_capture");
11791
+ }
11792
+ });
11793
+ const artifactResult = capture.rawDomGzip ? await tryCreatePaaRawDomArtifact({
11794
+ userId: job.user_id,
11795
+ jobId: job.id,
11796
+ attempt: capture.attempt,
11797
+ gzip: capture.rawDomGzip
11798
+ }) : { artifact: null, status: "unavailable" };
11799
+ const artifact = artifactResult.artifact;
11800
+ const artifactMissing = capture.rawDomGzip !== null && artifactResult.status !== "stored";
11801
+ const result = {
11802
+ ...capture.result,
11803
+ diagnostics: {
11804
+ ...capture.result.diagnostics,
11805
+ ...artifactMissing ? {
11806
+ resultQuality: "partial",
11807
+ degradedResult: true,
11808
+ retryRecommended: false
11809
+ } : {}
11810
+ },
11811
+ capture: {
11812
+ ...capture.result.capture,
11813
+ rawDomArtifact: artifact,
11814
+ rawDomArtifactStatus: artifactResult.status
11815
+ }
11816
+ };
11817
+ await completeJob(job.id, result);
11818
+ await finishHarvestAttempt({
11819
+ jobId: job.id,
11820
+ attemptNumber: 1,
11821
+ outcome: capture.partial ? "partial" : "paa_found",
11822
+ kernelSessionId: null,
11823
+ questionCount: questionCount(result),
11824
+ durationMs: Date.now() - invocationStartedAt,
11825
+ error: capture.checkpoint.error,
11826
+ willRetry: false,
11827
+ kernelDeleteStarted: true,
11828
+ kernelDeleteSucceeded: true,
11829
+ kernelDeleteError: null,
11830
+ browserCloseSucceeded: true,
11831
+ browserCloseError: null,
11832
+ debug: {
11833
+ execution: "kernel_colocated_playwright",
11834
+ stealth: true,
11835
+ attemptUsed: capture.attempt,
11836
+ rawDomStored: Boolean(artifact)
11837
+ },
11838
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
11839
+ });
11840
+ await settlePaaHarvestJob(job.id);
11841
+ if (job.callback_url) {
11842
+ await deliverWebhook(job.callback_url, { job_id: job.id, status: "done", result });
11843
+ }
11844
+ return {
11845
+ jobId: job.id,
11846
+ status: "done",
11847
+ totalQuestions: questionCount(result),
11848
+ partial: capture.partial,
11849
+ artifactStored: Boolean(artifact)
11850
+ };
11851
+ } catch (error) {
11852
+ const message = error instanceof Error ? error.message : String(error);
11853
+ const latestCheckpoint = checkpointState.value;
11854
+ await finishHarvestAttempt({
11855
+ jobId: job.id,
11856
+ attemptNumber: 1,
11857
+ outcome: latestCheckpoint?.records.length ? "partial_session_lost" : "failed_before_capture",
11858
+ kernelSessionId: null,
11859
+ questionCount: latestCheckpoint?.records.length ?? 0,
11860
+ durationMs: Date.now() - invocationStartedAt,
11861
+ error: message.slice(0, 1e3),
11862
+ willRetry: false,
11863
+ kernelDeleteStarted: true,
11864
+ kernelDeleteSucceeded: null,
11865
+ kernelDeleteError: null,
11866
+ browserCloseSucceeded: null,
11867
+ browserCloseError: null,
11868
+ debug: latestCheckpoint,
11869
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
11870
+ });
11871
+ await failJob(job.id, message.slice(0, 1e3));
11872
+ await settlePaaHarvestJob(job.id).catch((settlementError) => {
11873
+ console.error("[paa-harvest] settlement pending:", settlementError instanceof Error ? settlementError.message : String(settlementError));
11874
+ });
11875
+ if (job.callback_url) {
11876
+ await deliverWebhook(job.callback_url, {
11877
+ job_id: job.id,
11878
+ status: "failed",
11879
+ error: message,
11880
+ progress: latestCheckpoint
11881
+ });
11882
+ }
11883
+ throw error;
11884
+ }
11885
+ }
11886
+ );
11887
+
10960
11888
  // src/api/site-extract-start.ts
10961
11889
  async function prepareSiteExtractStart(input) {
10962
11890
  const existing = await getExtractJobByIdempotencyKey(input.userId, input.idempotencyKey);
@@ -11607,7 +12535,7 @@ import { Hono as Hono3 } from "hono";
11607
12535
  import { z as z12 } from "zod";
11608
12536
 
11609
12537
  // src/api/unified-billing.ts
11610
- import { createHash as createHash2, createHmac, randomUUID as randomUUID3, timingSafeEqual } from "crypto";
12538
+ import { createHash as createHash3, createHmac, randomUUID as randomUUID3, timingSafeEqual } from "crypto";
11611
12539
 
11612
12540
  // src/api/scheduling-access.ts
11613
12541
  var PAID_PLAN_REQUIRED_CODE = "paid_plan_required";
@@ -11708,7 +12636,7 @@ function tokenFor(id, idempotencyKey2) {
11708
12636
  return createHmac("sha256", billingSecret()).update(id).update(":").update(idempotencyKey2).digest("base64url");
11709
12637
  }
11710
12638
  function tokenHash(token2) {
11711
- return createHash2("sha256").update(token2).digest("hex");
12639
+ return createHash3("sha256").update(token2).digest("hex");
11712
12640
  }
11713
12641
  function verifyToken(row, token2) {
11714
12642
  const expected = Buffer.from(row.token_hash, "hex");
@@ -12274,7 +13202,7 @@ async function syncScheduledActionCredentials(user) {
12274
13202
  }
12275
13203
 
12276
13204
  // src/api/connected-usage-billing.ts
12277
- import { createHash as createHash3, randomUUID as randomUUID4 } from "crypto";
13205
+ import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
12278
13206
  import { z as z11 } from "zod";
12279
13207
 
12280
13208
  // src/api/connected-cost-telemetry.ts
@@ -12349,7 +13277,7 @@ var ConnectedUsageBillingError = class extends Error {
12349
13277
  details;
12350
13278
  };
12351
13279
  function hash(value) {
12352
- return createHash3("sha256").update(value).digest("hex");
13280
+ return createHash4("sha256").update(value).digest("hex");
12353
13281
  }
12354
13282
  function eventKey(idempotencyKey2) {
12355
13283
  return `connected-usage:${hash(idempotencyKey2)}`;
@@ -18363,12 +19291,15 @@ function mapsErrorResponse(c, err, errorCode) {
18363
19291
  const msg = err instanceof Error ? err.message : String(err);
18364
19292
  const rotationError = err instanceof MapsSearchRotationError ? err : null;
18365
19293
  const blocked = msg.includes("CAPTCHA") || msg.includes("blocked");
18366
- const retryable = rotationError?.retryable ?? blocked;
18367
- const message = sanitizeVendorName(msg);
19294
+ const publicCode = normalizePublicErrorCode(
19295
+ rotationError?.errorCode ?? (blocked ? "captcha_or_blocked" : "service_unavailable")
19296
+ );
19297
+ const retryable = publicCode === "service_unavailable" || rotationError?.retryable === true || blocked;
19298
+ const message = publicErrorMessage(publicCode);
18368
19299
  return c.json({
18369
19300
  error: message,
18370
19301
  message,
18371
- error_code: rotationError?.errorCode ?? (blocked ? "captcha_or_blocked" : errorCode),
19302
+ error_code: publicCode,
18372
19303
  retryable
18373
19304
  }, retryable ? 503 : 500);
18374
19305
  }
@@ -18879,7 +19810,7 @@ g2App.post("/reviews", createApiKeyAuth(), async (c) => {
18879
19810
  });
18880
19811
 
18881
19812
  // src/api/directory-routes.ts
18882
- import { createHash as createHash4, randomUUID as randomUUID7 } from "crypto";
19813
+ import { createHash as createHash5, randomUUID as randomUUID7 } from "crypto";
18883
19814
  import { Hono as Hono16 } from "hono";
18884
19815
 
18885
19816
  // src/api/directory-workflow-dispatch.ts
@@ -18919,7 +19850,7 @@ function safeOptions(options) {
18919
19850
  return safe;
18920
19851
  }
18921
19852
  function requestFingerprint(options) {
18922
- return createHash4("sha256").update(JSON.stringify(safeOptions(options))).digest("hex");
19853
+ return createHash5("sha256").update(JSON.stringify(safeOptions(options))).digest("hex");
18923
19854
  }
18924
19855
  function idempotencyKey(raw) {
18925
19856
  if (raw !== void 0) {
@@ -18931,7 +19862,7 @@ function idempotencyKey(raw) {
18931
19862
  return { ok: true, key: `directory-${randomUUID7()}` };
18932
19863
  }
18933
19864
  function debitKeyFor(userId, responseKey) {
18934
- const digest = createHash4("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
19865
+ const digest = createHash5("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
18935
19866
  return `directory-workflow:${userId}:${digest}`;
18936
19867
  }
18937
19868
  function jobId() {
@@ -19044,7 +19975,7 @@ async function runSynchronously(c, user, options, plan, responseKey) {
19044
19975
  const csv = renderDirectoryWorkflowCsv(result);
19045
19976
  const artifact = await createDirectoryCsvArtifact({
19046
19977
  ownerId: String(user.id),
19047
- jobId: createHash4("sha256").update(debitKey2).digest("hex").slice(0, 32),
19978
+ jobId: createHash5("sha256").update(debitKey2).digest("hex").slice(0, 32),
19048
19979
  createdAt: result.extractedAt,
19049
19980
  filename: `${options.state}-${options.query}-directory.csv`,
19050
19981
  csv,
@@ -19460,6 +20391,53 @@ locationDataApp.post("/admin/sync-census", async (c) => {
19460
20391
  }
19461
20392
  });
19462
20393
 
20394
+ // src/api/paa-harvest-dispatch.ts
20395
+ async function dispatchPaaHarvest(jobId2) {
20396
+ await inngest.send({
20397
+ id: `paa-harvest:${jobId2}`,
20398
+ name: "mcp-scraper/paa.requested",
20399
+ data: { jobId: jobId2 }
20400
+ });
20401
+ }
20402
+ async function redispatchPendingPaaHarvests(limit = 25) {
20403
+ const jobs = await listPendingInngestPaaJobs(limit);
20404
+ let dispatched = 0;
20405
+ let failed = 0;
20406
+ for (const job of jobs) {
20407
+ try {
20408
+ await dispatchPaaHarvest(job.id);
20409
+ dispatched += 1;
20410
+ } catch {
20411
+ failed += 1;
20412
+ }
20413
+ }
20414
+ return { checked: jobs.length, dispatched, failed };
20415
+ }
20416
+
20417
+ // src/api/paa-harvest-reconciliation.ts
20418
+ async function reconcilePaaHarvestSettlements(limit = 25) {
20419
+ const stale = await listStaleRunningInngestPaaJobs(limit);
20420
+ let staleFailed = 0;
20421
+ for (const job of stale) {
20422
+ try {
20423
+ await failJob(job.id, "durable PAA invocation exceeded its recovery window; preserved progress remains in result");
20424
+ staleFailed += 1;
20425
+ } catch {
20426
+ }
20427
+ }
20428
+ const jobs = await listRecentTerminalInngestPaaJobs(limit);
20429
+ let settled = 0;
20430
+ let failed = 0;
20431
+ for (const job of jobs) {
20432
+ try {
20433
+ if (await settlePaaHarvestJob(job)) settled += 1;
20434
+ } catch {
20435
+ failed += 1;
20436
+ }
20437
+ }
20438
+ return { checked: jobs.length, staleFailed, settled, failed };
20439
+ }
20440
+
19463
20441
  // src/api/directory-workflow-reconciliation.ts
19464
20442
  async function reconcileDirectoryWorkflowSettlements(limit = 25) {
19465
20443
  const expiredStarts = await listExpiredDirectoryWorkflowStarts(limit);
@@ -20060,17 +21038,17 @@ workflowApp.post("/cron/dispatch", async (c) => {
20060
21038
 
20061
21039
  // src/api/serp-intelligence-routes.ts
20062
21040
  import { Hono as Hono19 } from "hono";
20063
- import { createHash as createHash6, randomUUID as randomUUID8 } from "crypto";
21041
+ import { createHash as createHash7, randomUUID as randomUUID8 } from "crypto";
20064
21042
 
20065
21043
  // src/serp-intelligence/page-snapshot-extractor.ts
20066
- import { createHash as createHash5 } from "crypto";
21044
+ import { createHash as createHash6 } from "crypto";
20067
21045
  import pLimit4 from "p-limit";
20068
21046
  var DEFAULT_TIMEOUT_MS = 15e3;
20069
21047
  var DEFAULT_MAX_CONCURRENCY = 2;
20070
21048
  var DEFAULT_MAX_CONTENT_CHARS = 25e4;
20071
21049
  function sha256(value) {
20072
21050
  if (!value) return null;
20073
- return createHash5("sha256").update(value).digest("hex");
21051
+ return createHash6("sha256").update(value).digest("hex");
20074
21052
  }
20075
21053
  function countWords(markdown) {
20076
21054
  const matches = markdown.trim().match(/\b[\p{L}\p{N}][\p{L}\p{N}'-]*\b/gu);
@@ -20846,8 +21824,8 @@ async function logRequestEventBestEffort(input) {
20846
21824
  }
20847
21825
  function captureBillingKeys(userId, suppliedKey, body) {
20848
21826
  const responseKey = suppliedKey?.trim() || randomUUID8();
20849
- const requestFingerprint2 = createHash6("sha256").update(JSON.stringify(body)).digest("hex");
20850
- const keyDigest = createHash6("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
21827
+ const requestFingerprint2 = createHash7("sha256").update(JSON.stringify(body)).digest("hex");
21828
+ const keyDigest = createHash7("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
20851
21829
  return {
20852
21830
  responseKey,
20853
21831
  debitKey: `serp-capture:${userId}:${keyDigest}`,
@@ -22116,7 +23094,7 @@ async function listReplayRows(sessionId) {
22116
23094
  }
22117
23095
 
22118
23096
  // src/services/browser-agent/browser-agent-service.ts
22119
- import Kernel4 from "@onkernel/sdk";
23097
+ import Kernel5 from "@onkernel/sdk";
22120
23098
  import { chromium as playwrightChromium } from "playwright";
22121
23099
 
22122
23100
  // src/services/fanout/cdp-capture.ts
@@ -22819,7 +23797,7 @@ var DEFAULT_TIMEOUT_SECONDS = 600;
22819
23797
  function client() {
22820
23798
  const apiKey = browserServiceApiKey();
22821
23799
  if (!apiKey) throw new Error("Browser backend API key is required");
22822
- return new Kernel4({ apiKey });
23800
+ return new Kernel5({ apiKey });
22823
23801
  }
22824
23802
  function isProfileConflict(err) {
22825
23803
  const message = err instanceof Error ? err.message : String(err);
@@ -24299,7 +25277,7 @@ async function resolveUser3(customerId, emailFallback) {
24299
25277
  // src/api/oauth-routes.ts
24300
25278
  import { Hono as Hono23 } from "hono";
24301
25279
  import { getCookie, setCookie } from "hono/cookie";
24302
- import { createHash as createHash7, randomBytes as randomBytes2, randomUUID as randomUUID11 } from "crypto";
25280
+ import { createHash as createHash8, randomBytes as randomBytes2, randomUUID as randomUUID11 } from "crypto";
24303
25281
  import { importPKCS8, exportJWK, SignJWT } from "jose";
24304
25282
  var ISSUER = "https://mcpscraper.dev";
24305
25283
  var RESOURCE = () => process.env.MCP_MEMORY_RESOURCE_URL ?? "https://memory.mcpscraper.dev/mcp";
@@ -24332,7 +25310,7 @@ async function getKeys() {
24332
25310
  const privateKey = await importPKCS8(pem, "RS256", { extractable: true });
24333
25311
  const full = await exportJWK(privateKey);
24334
25312
  const publicJwk = { kty: full.kty, n: full.n, e: full.e };
24335
- const kid = createHash7("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
25313
+ const kid = createHash8("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
24336
25314
  publicJwk.kid = kid;
24337
25315
  publicJwk.alg = "RS256";
24338
25316
  publicJwk.use = "sig";
@@ -24642,7 +25620,7 @@ oauthApp.post("/oauth/authorize", async (c) => {
24642
25620
  });
24643
25621
  function pkceMatches(verifier, challenge) {
24644
25622
  if (!verifier) return false;
24645
- const computed = createHash7("sha256").update(verifier).digest("base64url");
25623
+ const computed = createHash8("sha256").update(verifier).digest("base64url");
24646
25624
  return computed === challenge;
24647
25625
  }
24648
25626
  async function mintAccessToken(identity, scope, plan, audience) {
@@ -26095,7 +27073,7 @@ import { Hono as Hono27 } from "hono";
26095
27073
  import { Resend } from "resend";
26096
27074
 
26097
27075
  // src/api/resend-support-thread.ts
26098
- import { createHash as createHash8 } from "crypto";
27076
+ import { createHash as createHash9 } from "crypto";
26099
27077
  var SUPPORT_TAGS = [
26100
27078
  {
26101
27079
  tag: "support",
@@ -26135,7 +27113,7 @@ function quoteUntrusted(value) {
26135
27113
  return clean2.split("\n").map((line) => `> ${line}`).join("\n");
26136
27114
  }
26137
27115
  function shortHash(value, length = 24) {
26138
- return createHash8("sha256").update(value).digest("hex").slice(0, length);
27116
+ return createHash9("sha256").update(value).digest("hex").slice(0, length);
26139
27117
  }
26140
27118
  function safePathPart(value) {
26141
27119
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "contact";
@@ -27634,7 +28612,7 @@ import { Hono as Hono29 } from "hono";
27634
28612
  import { z as z29 } from "zod";
27635
28613
 
27636
28614
  // src/editorial-reading-room/render.ts
27637
- import { createHash as createHash9 } from "crypto";
28615
+ import { createHash as createHash10 } from "crypto";
27638
28616
  import { readFileSync as readFileSync3 } from "fs";
27639
28617
  import { dirname as dirname2, join as join5, resolve } from "path";
27640
28618
  import { Marked, Renderer } from "marked";
@@ -27767,7 +28745,7 @@ function renderEditorialReadingRoom(input, now = /* @__PURE__ */ new Date()) {
27767
28745
  articleCount: articles.length,
27768
28746
  wordCount: totalWordCount,
27769
28747
  bytes,
27770
- sha256: createHash9("sha256").update(html).digest("hex"),
28748
+ sha256: createHash10("sha256").update(html).digest("hex"),
27771
28749
  warnings
27772
28750
  };
27773
28751
  }
@@ -28000,13 +28978,13 @@ async function reconcileSiteExtractSettlements(limit = 25) {
28000
28978
  }
28001
28979
 
28002
28980
  // src/api/page-diff.ts
28003
- import { createHash as createHash10 } from "crypto";
28981
+ import { createHash as createHash11 } from "crypto";
28004
28982
  import { diffLines } from "diff";
28005
28983
  var MAX_SNAPSHOT_CONTENT_CHARS = 25e4;
28006
28984
  var MAX_DIFF_HUNKS = 200;
28007
28985
  var MAX_DIFF_LINES_PER_RESPONSE = 2e3;
28008
28986
  function sha256Hex(value) {
28009
- return createHash10("sha256").update(value).digest("hex");
28987
+ return createHash11("sha256").update(value).digest("hex");
28010
28988
  }
28011
28989
  function truncateForStorage(value, maxChars = MAX_SNAPSHOT_CONTENT_CHARS) {
28012
28990
  if (value.length <= maxChars) return { value, truncated: false };
@@ -28528,7 +29506,7 @@ function inferredWaybackCapturedAt(content) {
28528
29506
  }
28529
29507
 
28530
29508
  // src/api/connection-memory-import.ts
28531
- import { createHash as createHash11 } from "crypto";
29509
+ import { createHash as createHash12 } from "crypto";
28532
29510
  var CONNECTION_MEMORY_IMPORT_MAX_ARGS_BYTES = 64 * 1024;
28533
29511
  var CONNECTION_MEMORY_IMPORT_MAX_RESULT_BYTES = 1e6;
28534
29512
  var CONNECTION_MEMORY_IMPORT_MAX_STRING_CHARS = 256e3;
@@ -28558,7 +29536,7 @@ function canonicalJson(value) {
28558
29536
  return JSON.stringify(value);
28559
29537
  }
28560
29538
  function sha2562(value) {
28561
- return createHash11("sha256").update(value).digest("hex");
29539
+ return createHash12("sha256").update(value).digest("hex");
28562
29540
  }
28563
29541
  function sensitiveKey(key) {
28564
29542
  const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -28846,6 +29824,7 @@ var CONNECTED_DATA_DATASETS = [
28846
29824
  "calendar_events",
28847
29825
  "zoom_recordings",
28848
29826
  "zoom_transcripts",
29827
+ "slack_channel_messages",
28849
29828
  "meta_ads_insights",
28850
29829
  "search_console_performance",
28851
29830
  "resend_data",
@@ -28872,8 +29851,9 @@ function resolveConnectedDataRange(args) {
28872
29851
  const from = args.from ? new Date(args.from) : new Date(to.getTime() - (args.lastDays ?? 7) * 864e5);
28873
29852
  if (!Number.isFinite(from.getTime())) throw new ConnectedDataExportValidationError("from must be an RFC3339 timestamp.");
28874
29853
  if (from.getTime() >= to.getTime()) throw new ConnectedDataExportValidationError("from must be earlier than to.");
28875
- if (to.getTime() - from.getTime() > 90 * 864e5) {
28876
- 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.`);
28877
29857
  }
28878
29858
  if (to.getTime() > now.getTime() + 5 * 6e4) {
28879
29859
  throw new ConnectedDataExportValidationError("to cannot be in the future.");
@@ -28883,10 +29863,18 @@ function resolveConnectedDataRange(args) {
28883
29863
  function resolveConnectedDataExportRequest(args) {
28884
29864
  const continuation = args.continuation;
28885
29865
  if (!continuation) {
29866
+ const scope2 = resolveConnectedDataExportScope(args.requestedDataset, args.scope);
28886
29867
  return {
28887
29868
  dataset: args.requestedDataset,
28888
- range: resolveConnectedDataRange({ from: args.from, to: args.to, lastDays: args.lastDays, now: args.now }),
28889
- ...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 } : {}
28890
29878
  };
28891
29879
  }
28892
29880
  const supported = CONNECTED_DATA_DATASETS.filter((dataset2) => dataset2 !== "auto");
@@ -28909,10 +29897,38 @@ function resolveConnectedDataExportRequest(args) {
28909
29897
  throw new ConnectedDataExportValidationError("dataset conflicts with continuation.");
28910
29898
  }
28911
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
+ }
28912
29904
  return {
28913
29905
  dataset,
28914
- range: resolveConnectedDataRange({ from: continuation.from, to: continuation.to, now: args.now }),
28915
- 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
+ }
28916
29932
  };
28917
29933
  }
28918
29934
  function previewRecord(value) {
@@ -28941,7 +29957,14 @@ function previewRecord(value) {
28941
29957
  "createdAt",
28942
29958
  "updatedAt",
28943
29959
  "hasTranscript",
28944
- "contentTruncated"
29960
+ "contentTruncated",
29961
+ "channelId",
29962
+ "threadTs",
29963
+ "parentMessageTs",
29964
+ "occurredAt",
29965
+ "userId",
29966
+ "text",
29967
+ "replyCount"
28945
29968
  ]) {
28946
29969
  if (record[key] === void 0) continue;
28947
29970
  const item = record[key];
@@ -28984,7 +30007,8 @@ async function collectConnectedDataExport(args) {
28984
30007
  dataset: args.dataset,
28985
30008
  from: args.range.from,
28986
30009
  to: args.range.to,
28987
- 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 } : {},
28988
30012
  ...cursor !== void 0 && cursor !== null ? { cursor } : {}
28989
30013
  });
28990
30014
  pages++;
@@ -29029,6 +30053,7 @@ async function collectConnectedDataExport(args) {
29029
30053
  providerConfigKey,
29030
30054
  dataset,
29031
30055
  range: args.range,
30056
+ ...args.scope ? { scope: args.scope } : {},
29032
30057
  complete,
29033
30058
  counts: { pages, listed, exported: records.length, failed },
29034
30059
  untrustedContent: true,
@@ -29048,6 +30073,7 @@ ${lines.length ? `${lines.join("\n")}
29048
30073
  providerConfigKey,
29049
30074
  dataset,
29050
30075
  range: args.range,
30076
+ ...args.scope ? { scope: args.scope } : {},
29051
30077
  counts: { pages, listed, exported: records.length, failed, bytes },
29052
30078
  complete,
29053
30079
  ...!artifact ? { records } : {},
@@ -29057,7 +30083,8 @@ ${lines.length ? `${lines.join("\n")}
29057
30083
  cursor: continuationCursor,
29058
30084
  from: args.range.from,
29059
30085
  to: args.range.to,
29060
- dataset
30086
+ dataset,
30087
+ ...args.scope ? { scope: args.scope } : {}
29061
30088
  } : null,
29062
30089
  warnings: [...warnings],
29063
30090
  untrustedContent: true
@@ -29227,6 +30254,97 @@ async function getFreeCreditBreakdown(userId) {
29227
30254
 
29228
30255
  // src/api/memory-db.ts
29229
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
29230
30348
  var _sql = null;
29231
30349
  function sql() {
29232
30350
  if (_sql) return _sql;
@@ -29371,6 +30489,35 @@ async function dbNote(identity, vault, path5) {
29371
30489
  capturedAt: r.captured_at
29372
30490
  };
29373
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
+ }
29374
30521
  var FREE_COST_CAP_USD = 1;
29375
30522
  async function dbUsage(identity, plan) {
29376
30523
  const now = /* @__PURE__ */ new Date();
@@ -29400,10 +30547,379 @@ async function dbUsage(identity, plan) {
29400
30547
  }
29401
30548
 
29402
30549
  // src/api/nango-control.ts
29403
- import { createHash as createHash13, randomUUID as randomUUID17 } from "crypto";
30550
+ import { createHash as createHash14, randomUUID as randomUUID17 } from "crypto";
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
+ }
29404
30920
 
29405
30921
  // src/api/main-nango-transport.ts
29406
- import { createHash as createHash12, randomUUID as randomUUID16 } from "crypto";
30922
+ import { createHash as createHash13, randomUUID as randomUUID16 } from "crypto";
29407
30923
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
29408
30924
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
29409
30925
 
@@ -29678,12 +31194,14 @@ var MainNangoTransportError = class extends Error {
29678
31194
  status;
29679
31195
  code;
29680
31196
  retryable;
29681
- constructor(message, status, code, retryable = status === 429 || status >= 500) {
31197
+ retryAfterMs;
31198
+ constructor(message, status, code, retryable = status === 429 || status >= 500, retryAfterMs = null) {
29682
31199
  super(message);
29683
31200
  this.name = "MainNangoTransportError";
29684
31201
  this.status = status;
29685
31202
  this.code = code;
29686
31203
  this.retryable = retryable;
31204
+ this.retryAfterMs = retryAfterMs;
29687
31205
  }
29688
31206
  };
29689
31207
  function nangoClientErrorText(error) {
@@ -29695,6 +31213,13 @@ function nangoClientErrorText(error) {
29695
31213
  return "";
29696
31214
  }
29697
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
+ }
29698
31223
  function normalizeNangoClientError(error) {
29699
31224
  if (error instanceof MainNangoTransportError) return error;
29700
31225
  const detail = nangoClientErrorText(error);
@@ -29715,7 +31240,13 @@ function normalizeNangoClientError(error) {
29715
31240
  );
29716
31241
  }
29717
31242
  if (/RESOURCE_EXHAUSTED|rate.?limit|status["']?\s*:\s*429/i.test(detail)) {
29718
- 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
+ );
29719
31250
  }
29720
31251
  if (/UNAUTHENTICATED|invalid_grant|status["']?\s*:\s*401/i.test(detail)) {
29721
31252
  return new MainNangoTransportError("The service connection requires reconnection.", 409, "connection_inactive", false);
@@ -29780,7 +31311,9 @@ async function nangoRequest(path5, options = {}) {
29780
31311
  throw new MainNangoTransportError(
29781
31312
  status === 429 ? "The connected provider is temporarily rate limited." : "Nango request failed.",
29782
31313
  status,
29783
- 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
29784
31317
  );
29785
31318
  }
29786
31319
  return payload;
@@ -29995,7 +31528,7 @@ async function listNangoToolsDirect(identity, connectionId) {
29995
31528
  });
29996
31529
  const readTools = tools.filter((tool) => classifyTool(tool) === "read").map((tool) => tool.name);
29997
31530
  const actionTools = tools.filter((tool) => classifyTool(tool) === "action").map((tool) => tool.name);
29998
- const revision = createHash12("sha256").update(JSON.stringify(tools.map((tool) => ({ name: tool.name, inputSchema: tool.inputSchema })))).digest("hex");
31531
+ const revision = createHash13("sha256").update(JSON.stringify(tools.map((tool) => ({ name: tool.name, inputSchema: tool.inputSchema })))).digest("hex");
29999
31532
  await updateServiceConnectionTools(connection.id, readTools, actionTools, revision);
30000
31533
  const refreshed = await getOwnedServiceConnection(identity, connection.id);
30001
31534
  return { connection: refreshed ?? { ...connection, readTools, actionTools, toolRevision: revision }, tools };
@@ -30016,7 +31549,7 @@ async function callNangoToolDirect(args) {
30016
31549
  ratePolicyVersion: CONNECTED_USAGE_RATE_POLICY_VERSION
30017
31550
  });
30018
31551
  const requestId = args.requestId?.trim() || randomUUID16();
30019
- const idempotencyKey2 = `main-nango:${createHash12("sha256").update(args.identity.toLowerCase()).update("\0").update(connection.id).update("\0").update(args.tool).update("\0").update(requestId).digest("hex")}`;
31552
+ const idempotencyKey2 = `main-nango:${createHash13("sha256").update(args.identity.toLowerCase()).update("\0").update(connection.id).update("\0").update(args.tool).update("\0").update(requestId).digest("hex")}`;
30020
31553
  const startedAt = /* @__PURE__ */ new Date();
30021
31554
  const started = performance.now();
30022
31555
  let result;
@@ -30041,7 +31574,7 @@ async function callNangoToolDirect(args) {
30041
31574
  toolName: args.tool,
30042
31575
  operationKind: args.operationKind ?? args.classification,
30043
31576
  outcome: providerError ? "error" : "partial",
30044
- requestId: requestId.length <= 200 ? requestId : createHash12("sha256").update(requestId).digest("hex"),
31577
+ requestId: requestId.length <= 200 ? requestId : createHash13("sha256").update(requestId).digest("hex"),
30045
31578
  startedAt: startedAt.toISOString(),
30046
31579
  completedAt: completedAt.toISOString()
30047
31580
  }
@@ -30078,7 +31611,7 @@ async function describeNangoToolDirect(identity, connectionId, toolName) {
30078
31611
  providerContractHash: MAIN_INTEGRATION_CONTRACT_HASH,
30079
31612
  protocolVersion: null,
30080
31613
  schemaSource: "live_tools_list",
30081
- schemaHash: createHash12("sha256").update(JSON.stringify(projected)).digest("hex"),
31614
+ schemaHash: createHash13("sha256").update(JSON.stringify(projected)).digest("hex"),
30082
31615
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
30083
31616
  };
30084
31617
  }
@@ -30228,37 +31761,37 @@ function controlSecret() {
30228
31761
  if (!secret2) throw new NangoControlError("Scheduled service connections are not configured.", 503);
30229
31762
  return secret2;
30230
31763
  }
30231
- function isRecord2(value) {
31764
+ function isRecord4(value) {
30232
31765
  return !!value && typeof value === "object" && !Array.isArray(value);
30233
31766
  }
30234
31767
  function unwrapData(value) {
30235
- if (isRecord2(value) && isRecord2(value.data)) return value.data;
31768
+ if (isRecord4(value) && isRecord4(value.data)) return value.data;
30236
31769
  return value;
30237
31770
  }
30238
- function cleanString(value, max = 300) {
31771
+ function cleanString3(value, max = 300) {
30239
31772
  if (typeof value !== "string") return null;
30240
31773
  const trimmed = value.trim();
30241
31774
  return trimmed ? trimmed.slice(0, max) : null;
30242
31775
  }
30243
31776
  function firstString(record, keys, max = 300) {
30244
31777
  for (const key of keys) {
30245
- const value = cleanString(record[key], max);
31778
+ const value = cleanString3(record[key], max);
30246
31779
  if (value) return value;
30247
31780
  }
30248
31781
  return null;
30249
31782
  }
30250
31783
  function cleanTools(value) {
30251
31784
  if (!Array.isArray(value)) return [];
30252
- const tools = value.map((tool) => cleanString(tool, 200)).filter((tool) => !!tool);
31785
+ const tools = value.map((tool) => cleanString3(tool, 200)).filter((tool) => !!tool);
30253
31786
  return [...new Set(tools)].slice(0, 200);
30254
31787
  }
30255
31788
  function cleanStringArray(value, maxItems = 20, maxLength = 100) {
30256
31789
  if (!Array.isArray(value)) return [];
30257
- const items = value.map((item) => cleanString(item, maxLength)).filter((item) => !!item);
31790
+ const items = value.map((item) => cleanString3(item, maxLength)).filter((item) => !!item);
30258
31791
  return [...new Set(items)].slice(0, maxItems);
30259
31792
  }
30260
31793
  function cleanHttpsUrl(value) {
30261
- const candidate = cleanString(value, 2e3);
31794
+ const candidate = cleanString3(value, 2e3);
30262
31795
  if (!candidate) return null;
30263
31796
  try {
30264
31797
  const url = new URL(candidate);
@@ -30270,7 +31803,7 @@ function cleanHttpsUrl(value) {
30270
31803
  function arrayFromPayload(value, keys) {
30271
31804
  const unwrapped = unwrapData(value);
30272
31805
  if (Array.isArray(unwrapped)) return unwrapped;
30273
- if (!isRecord2(unwrapped)) return [];
31806
+ if (!isRecord4(unwrapped)) return [];
30274
31807
  for (const key of keys) {
30275
31808
  if (Array.isArray(unwrapped[key])) return unwrapped[key];
30276
31809
  }
@@ -30351,7 +31884,7 @@ function defaultControlErrorMessage(status) {
30351
31884
  function safeControlErrorPayload(body, responseStatus) {
30352
31885
  const status = controlErrorStatus(responseStatus);
30353
31886
  const unwrapped = unwrapData(body);
30354
- const record = isRecord2(unwrapped) ? unwrapped : isRecord2(body) ? body : null;
31887
+ const record = isRecord4(unwrapped) ? unwrapped : isRecord4(body) ? body : null;
30355
31888
  const candidateCode = record ? firstString(record, ["code", "errorCode", "error_code"], 100) : null;
30356
31889
  const normalizedCandidateCode = candidateCode ? CONTROL_ERROR_CODE_ALIASES.get(candidateCode) ?? candidateCode : null;
30357
31890
  const code = normalizedCandidateCode && SAFE_CONTROL_ERROR_CODES.has(normalizedCandidateCode) ? normalizedCandidateCode : defaultControlErrorCode(status);
@@ -30400,7 +31933,7 @@ function sanitizeScheduleConnectionSelections(value) {
30400
31933
  const selections = [];
30401
31934
  const seen = /* @__PURE__ */ new Set();
30402
31935
  for (const item of value) {
30403
- 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.");
30404
31937
  const connectionId = firstString(item, ["connectionId", "connection_id"]);
30405
31938
  const providerConfigKey = firstString(item, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id"]);
30406
31939
  const allowedTools = cleanTools(item.allowedTools ?? item.allowed_tools ?? item.allowedCapabilities ?? item.allowed_capabilities);
@@ -30420,7 +31953,7 @@ async function getNangoCatalog() {
30420
31953
  const rows = arrayFromPayload(body, ["providers", "catalog", "integrations", "services"]);
30421
31954
  const result = [];
30422
31955
  for (const row of rows) {
30423
- if (!isRecord2(row)) continue;
31956
+ if (!isRecord4(row)) continue;
30424
31957
  const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "id"]);
30425
31958
  if (!providerConfigKey) continue;
30426
31959
  const provider = firstString(row, ["provider"]);
@@ -30440,18 +31973,18 @@ async function getNangoCatalog() {
30440
31973
  ).filter((tool) => !disabledTools?.has(tool));
30441
31974
  const requiredPermissionsByTool = {};
30442
31975
  const rawRequiredPermissions = row.requiredPermissionsByTool ?? row.required_permissions_by_tool;
30443
- if (isRecord2(rawRequiredPermissions)) {
31976
+ if (isRecord4(rawRequiredPermissions)) {
30444
31977
  for (const [tool, permissions] of Object.entries(rawRequiredPermissions).slice(0, 500)) {
30445
- const cleanTool = cleanString(tool, 200);
31978
+ const cleanTool = cleanString3(tool, 200);
30446
31979
  if (!cleanTool || !safeDefaultAllowedTools.includes(cleanTool) && !actionTools.includes(cleanTool)) continue;
30447
31980
  requiredPermissionsByTool[cleanTool] = cleanStringArray(permissions, 32, 200);
30448
31981
  }
30449
31982
  }
30450
31983
  const requiredFeaturesByTool = {};
30451
31984
  const rawRequiredFeatures = row.requiredFeaturesByTool ?? row.required_features_by_tool;
30452
- if (isRecord2(rawRequiredFeatures)) {
31985
+ if (isRecord4(rawRequiredFeatures)) {
30453
31986
  for (const [tool, features] of Object.entries(rawRequiredFeatures).slice(0, 500)) {
30454
- const cleanTool = cleanString(tool, 200);
31987
+ const cleanTool = cleanString3(tool, 200);
30455
31988
  if (!cleanTool) continue;
30456
31989
  requiredFeaturesByTool[cleanTool] = cleanStringArray(features, 32, 200);
30457
31990
  }
@@ -30576,7 +32109,7 @@ async function getNangoConnections(identity, options = {}) {
30576
32109
  const rows = arrayFromPayload(body, ["connections"]);
30577
32110
  const result = [];
30578
32111
  for (const row of rows) {
30579
- if (!isRecord2(row)) continue;
32112
+ if (!isRecord4(row)) continue;
30580
32113
  const connectionId = firstString(row, ["connectionId", "connection_id", "id"]);
30581
32114
  const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
30582
32115
  if (!connectionId || !providerConfigKey) continue;
@@ -30595,8 +32128,8 @@ async function getNangoConnections(identity, options = {}) {
30595
32128
  const rawToolCapabilities = row.toolCapabilities ?? row.tool_capabilities;
30596
32129
  if (Array.isArray(rawToolCapabilities)) {
30597
32130
  for (const value of rawToolCapabilities.slice(0, 500)) {
30598
- if (!isRecord2(value)) continue;
30599
- const name = cleanString(value.name, 200);
32131
+ if (!isRecord4(value)) continue;
32132
+ const name = cleanString3(value.name, 200);
30600
32133
  const classification = value.classification;
30601
32134
  const blockedValue = Object.prototype.hasOwnProperty.call(value, "blockedReason") ? value.blockedReason : value.blocked_reason;
30602
32135
  const blockedReason = blockedValue === null ? null : blockedValue === "missing_permission" || blockedValue === "permission_policy_missing" || blockedValue === "permission_verification_unavailable" || blockedValue === "missing_app_feature" ? blockedValue : void 0;
@@ -30663,7 +32196,7 @@ async function deleteNangoConnection(identity, connectionId) {
30663
32196
  body: JSON.stringify({ identity, connectionId })
30664
32197
  });
30665
32198
  const data = unwrapData(body);
30666
- if (!isRecord2(data) || data.ok !== true) {
32199
+ if (!isRecord4(data) || data.ok !== true) {
30667
32200
  throw new NangoControlError("The connection service returned an invalid deletion response.");
30668
32201
  }
30669
32202
  const remaining = Number(data.remainingConnections);
@@ -30671,7 +32204,7 @@ async function deleteNangoConnection(identity, connectionId) {
30671
32204
  }
30672
32205
  function sanitizeConnectSession(body) {
30673
32206
  const data = unwrapData(body);
30674
- 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.");
30675
32208
  const connectLink = firstString(data, ["connectLink", "connect_link"], 2e3);
30676
32209
  if (!connectLink) throw new NangoControlError("The connection service did not return a connect link.");
30677
32210
  return {
@@ -30713,14 +32246,14 @@ async function createNangoReconnectSession(identity, connectionId) {
30713
32246
  function sanitizeBindingRows(body, defaultScheduleActionId) {
30714
32247
  const data = unwrapData(body);
30715
32248
  const flattened = [];
30716
- if (isRecord2(data) && isRecord2(data.bindings) && !Array.isArray(data.bindings)) {
32249
+ if (isRecord4(data) && isRecord4(data.bindings) && !Array.isArray(data.bindings)) {
30717
32250
  for (const [scheduleActionId, value] of Object.entries(data.bindings)) {
30718
32251
  if (Array.isArray(value)) value.forEach((row) => flattened.push({ row, scheduleActionId }));
30719
32252
  }
30720
32253
  } else {
30721
32254
  const rows = arrayFromPayload(data, ["bindings", "connections"]);
30722
32255
  for (const row of rows) {
30723
- if (isRecord2(row) && Array.isArray(row.connections)) {
32256
+ if (isRecord4(row) && Array.isArray(row.connections)) {
30724
32257
  const scheduleActionId = firstString(row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || defaultScheduleActionId;
30725
32258
  row.connections.forEach((connection) => flattened.push({ row: connection, scheduleActionId }));
30726
32259
  } else {
@@ -30730,7 +32263,7 @@ function sanitizeBindingRows(body, defaultScheduleActionId) {
30730
32263
  }
30731
32264
  const result = [];
30732
32265
  for (const item of flattened) {
30733
- if (!isRecord2(item.row)) continue;
32266
+ if (!isRecord4(item.row)) continue;
30734
32267
  const connectionId = firstString(item.row, ["connectionId", "connection_id"]);
30735
32268
  const providerConfigKey = firstString(item.row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
30736
32269
  if (!connectionId || !providerConfigKey) continue;
@@ -30798,11 +32331,11 @@ async function setScheduleConnectionActionsEnabled(identity, connectionId, enabl
30798
32331
  body: JSON.stringify({ identity, connectionId, enabled })
30799
32332
  });
30800
32333
  const data = unwrapData(body);
30801
- if (!isRecord2(data) || !isRecord2(data.connection)) return enabled;
32334
+ if (!isRecord4(data) || !isRecord4(data.connection)) return enabled;
30802
32335
  return data.connection.actionsEnabled === true;
30803
32336
  }
30804
32337
  async function callScheduleConnectionAction(identity, connectionId, input, tool, idempotencyKey2) {
30805
- const requestId = `main-connected-action:${createHash13("sha256").update(identity).update("\0").update(idempotencyKey2?.trim() || randomUUID17()).digest("hex")}`;
32338
+ const requestId = `main-connected-action:${createHash14("sha256").update(identity).update("\0").update(idempotencyKey2?.trim() || randomUUID17()).digest("hex")}`;
30806
32339
  if (mainOwnsIntegrations()) {
30807
32340
  const selectedTool = tool?.trim();
30808
32341
  if (!selectedTool) throw new NangoControlError("An action tool is required.", 400, "invalid_request", false);
@@ -30848,7 +32381,7 @@ async function callScheduleConnectionAction(identity, connectionId, input, tool,
30848
32381
  body: JSON.stringify({ identity, connectionId, ...tool ? { tool } : {}, input })
30849
32382
  });
30850
32383
  const data = unwrapData(body);
30851
- return isRecord2(data) ? data.result ?? data : data;
32384
+ return isRecord4(data) ? data.result ?? data : data;
30852
32385
  }
30853
32386
  async function callScheduleConnectionRead(identity, connectionId, tool, args, idempotencyKey2) {
30854
32387
  if (mainOwnsIntegrations()) {
@@ -30871,7 +32404,7 @@ async function callScheduleConnectionRead(identity, connectionId, tool, args, id
30871
32404
  body: JSON.stringify({ identity, connectionId, tool, args: args ?? {} })
30872
32405
  });
30873
32406
  const data = unwrapData(body);
30874
- return isRecord2(data) ? data.result ?? data : data;
32407
+ return isRecord4(data) ? data.result ?? data : data;
30875
32408
  }
30876
32409
  async function testNangoConnection(identity, connectionId) {
30877
32410
  if (!mainOwnsIntegrations()) {
@@ -30891,20 +32424,20 @@ async function testNangoConnection(identity, connectionId) {
30891
32424
  }
30892
32425
  }
30893
32426
  function sanitizeToolSchema(value) {
30894
- if (!isRecord2(value) || value.type !== "object") return null;
32427
+ if (!isRecord4(value) || value.type !== "object") return null;
30895
32428
  try {
30896
32429
  const serialized = JSON.stringify(value);
30897
32430
  if (Buffer.byteLength(serialized, "utf8") > 256 * 1024) return null;
30898
32431
  const cloned = JSON.parse(serialized);
30899
- return isRecord2(cloned) ? cloned : null;
32432
+ return isRecord4(cloned) ? cloned : null;
30900
32433
  } catch {
30901
32434
  return null;
30902
32435
  }
30903
32436
  }
30904
32437
  function sanitizeToolAnnotations(value) {
30905
- if (!isRecord2(value)) return void 0;
32438
+ if (!isRecord4(value)) return void 0;
30906
32439
  const annotations = {};
30907
- const title = cleanString(value.title, 200);
32440
+ const title = cleanString3(value.title, 200);
30908
32441
  if (title) annotations.title = title;
30909
32442
  for (const key of ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"]) {
30910
32443
  if (typeof value[key] === "boolean") annotations[key] = value[key];
@@ -30912,7 +32445,7 @@ function sanitizeToolAnnotations(value) {
30912
32445
  return Object.keys(annotations).length > 0 ? annotations : void 0;
30913
32446
  }
30914
32447
  function sanitizeToolIconSource(value) {
30915
- const candidate = cleanString(value, 1e4);
32448
+ const candidate = cleanString3(value, 1e4);
30916
32449
  if (!candidate) return null;
30917
32450
  if (/^data:image\/(?:png|jpeg|webp|gif|svg\+xml);base64,[a-z0-9+/=]+$/i.test(candidate)) return candidate;
30918
32451
  return cleanHttpsUrl(candidate);
@@ -30921,10 +32454,10 @@ function sanitizeToolIcons(value) {
30921
32454
  if (!Array.isArray(value)) return void 0;
30922
32455
  const icons = [];
30923
32456
  for (const item of value.slice(0, 16)) {
30924
- if (!isRecord2(item)) continue;
32457
+ if (!isRecord4(item)) continue;
30925
32458
  const src = sanitizeToolIconSource(item.src);
30926
32459
  if (!src) continue;
30927
- const mimeType = cleanString(item.mimeType ?? item.mime_type, 100);
32460
+ const mimeType = cleanString3(item.mimeType ?? item.mime_type, 100);
30928
32461
  const sizes = cleanStringArray(item.sizes, 16, 32);
30929
32462
  const theme = item.theme === "light" || item.theme === "dark" ? item.theme : void 0;
30930
32463
  icons.push({
@@ -30938,13 +32471,13 @@ function sanitizeToolIcons(value) {
30938
32471
  }
30939
32472
  function canonicalJson2(value) {
30940
32473
  if (Array.isArray(value)) return `[${value.map(canonicalJson2).join(",")}]`;
30941
- if (isRecord2(value)) {
32474
+ if (isRecord4(value)) {
30942
32475
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(value[key])}`).join(",")}}`;
30943
32476
  }
30944
32477
  return JSON.stringify(value);
30945
32478
  }
30946
32479
  function projectedToolSchemaHash(tool) {
30947
- return createHash13("sha256").update(canonicalJson2(tool)).digest("hex");
32480
+ return createHash14("sha256").update(canonicalJson2(tool)).digest("hex");
30948
32481
  }
30949
32482
  async function describeNangoTool(identity, connectionId, tool, fresh) {
30950
32483
  if (mainOwnsIntegrations()) {
@@ -30969,8 +32502,8 @@ async function describeNangoTool(identity, connectionId, tool, fresh) {
30969
32502
  }
30970
32503
  function sanitizeNangoToolDescription(body, tool) {
30971
32504
  const data = unwrapData(body);
30972
- const rawTool = isRecord2(data) && isRecord2(data.tool) ? data.tool : data;
30973
- if (!isRecord2(rawTool)) {
32505
+ const rawTool = isRecord4(data) && isRecord4(data.tool) ? data.tool : data;
32506
+ if (!isRecord4(rawTool)) {
30974
32507
  throw new NangoControlError(
30975
32508
  "The connection service returned an invalid live tool description.",
30976
32509
  502,
@@ -30983,13 +32516,13 @@ function sanitizeNangoToolDescription(body, tool) {
30983
32516
  serializedBytes = Buffer.byteLength(JSON.stringify(rawTool), "utf8");
30984
32517
  } catch {
30985
32518
  }
30986
- const name = cleanString(rawTool.name, 200);
30987
- const classification = cleanString(rawTool.classification, 20);
30988
- const transport = cleanString(rawTool.transport, 20);
30989
- const providerConfigKey = cleanString(rawTool.providerConfigKey ?? rawTool.provider_config_key, 200);
30990
- const schemaSource = cleanString(rawTool.schemaSource ?? rawTool.schema_source, 50);
30991
- const upstreamSchemaHash = cleanString(rawTool.schemaHash ?? rawTool.schema_hash, 200);
30992
- 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);
30993
32526
  const inputSchema = sanitizeToolSchema(rawTool.inputSchema ?? rawTool.input_schema);
30994
32527
  const outputSchemaValue = rawTool.outputSchema ?? rawTool.output_schema;
30995
32528
  const outputSchema = outputSchemaValue === void 0 ? void 0 : sanitizeToolSchema(outputSchemaValue);
@@ -31003,11 +32536,11 @@ function sanitizeNangoToolDescription(body, tool) {
31003
32536
  true
31004
32537
  );
31005
32538
  }
31006
- 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);
31007
32540
  const executionValue = rawTool.execution;
31008
- const taskSupport = isRecord2(executionValue) && (executionValue.taskSupport === "forbidden" || executionValue.taskSupport === "optional" || executionValue.taskSupport === "required") ? executionValue.taskSupport : null;
31009
- const title = cleanString(rawTool.title, 200);
31010
- 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);
31011
32544
  const annotations = sanitizeToolAnnotations(rawTool.annotations);
31012
32545
  const icons = sanitizeToolIcons(rawTool.icons);
31013
32546
  const execution = taskSupport ? { taskSupport } : void 0;
@@ -31050,10 +32583,13 @@ function sanitizeNangoToolDescription(body, tool) {
31050
32583
  };
31051
32584
  }
31052
32585
  async function callScheduleConnectionExportPage(identity, input) {
31053
- if (mainOwnsIntegrations() && input.dataset === "search_console_performance") {
32586
+ if (mainOwnsIntegrations() && (input.dataset === "search_console_performance" || input.dataset === "slack_channel_messages")) {
31054
32587
  try {
31055
32588
  return await callMainOwnedExportPage(identity, input);
31056
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
+ }
31057
32593
  throw asNangoControlError(error);
31058
32594
  }
31059
32595
  }
@@ -31062,11 +32598,11 @@ async function callScheduleConnectionExportPage(identity, input) {
31062
32598
  body: JSON.stringify({ identity, ...input })
31063
32599
  }, 9e4);
31064
32600
  const data = unwrapData(body);
31065
- if (!isRecord2(data) || data.ok !== true) {
32601
+ if (!isRecord4(data) || data.ok !== true) {
31066
32602
  throw new NangoControlError("Connected-data export returned an invalid page response.");
31067
32603
  }
31068
- const providerConfigKey = cleanString(data.providerConfigKey, 128);
31069
- const dataset = cleanString(data.dataset, 64);
32604
+ const providerConfigKey = cleanString3(data.providerConfigKey, 128);
32605
+ const dataset = cleanString3(data.dataset, 64);
31070
32606
  if (!providerConfigKey || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
31071
32607
  throw new NangoControlError("Connected-data export returned invalid provider metadata.");
31072
32608
  }
@@ -31076,7 +32612,7 @@ async function callScheduleConnectionExportPage(identity, input) {
31076
32612
  if (data.nextCursor !== void 0 && data.nextCursor !== null && typeof data.nextCursor !== "string") {
31077
32613
  throw new NangoControlError("Connected-data export returned an invalid continuation cursor.");
31078
32614
  }
31079
- const rawCounts = isRecord2(data.counts) ? data.counts : {};
32615
+ const rawCounts = isRecord4(data.counts) ? data.counts : {};
31080
32616
  const numberOrZero = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
31081
32617
  return {
31082
32618
  providerConfigKey,
@@ -31094,11 +32630,11 @@ async function callScheduleConnectionExportPage(identity, input) {
31094
32630
  };
31095
32631
  }
31096
32632
  function unwrapMcpToolJson(value) {
31097
- if (!isRecord2(value)) return value;
31098
- if (isRecord2(value.structuredContent)) return value.structuredContent;
32633
+ if (!isRecord4(value)) return value;
32634
+ if (isRecord4(value.structuredContent)) return value.structuredContent;
31099
32635
  if (Array.isArray(value.content)) {
31100
- const textPart = value.content.find((part) => isRecord2(part) && part.type === "text" && typeof part.text === "string");
31101
- 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") {
31102
32638
  try {
31103
32639
  return JSON.parse(textPart.text);
31104
32640
  } catch {
@@ -31111,13 +32647,26 @@ function unwrapMcpToolJson(value) {
31111
32647
  function rowsFromToolResult(value, keys) {
31112
32648
  const unwrapped = unwrapMcpToolJson(value);
31113
32649
  if (Array.isArray(unwrapped)) return unwrapped;
31114
- if (!isRecord2(unwrapped)) return [];
32650
+ if (!isRecord4(unwrapped)) return [];
31115
32651
  for (const key of keys) if (Array.isArray(unwrapped[key])) return unwrapped[key];
31116
32652
  return [];
31117
32653
  }
31118
32654
  async function callMainOwnedExportPage(identity, input) {
31119
32655
  const connection = await getOwnedServiceConnection(identity, input.connectionId);
31120
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
+ }
31121
32670
  if (connection.providerConfigKey !== "google-search-console" || input.dataset !== "search_console_performance") {
31122
32671
  throw new NangoControlError("This dataset has not moved to main-MCP export execution yet.", 400, "invalid_request", false);
31123
32672
  }
@@ -31135,8 +32684,8 @@ async function callMainOwnedExportPage(identity, input) {
31135
32684
  operationKind: "export"
31136
32685
  });
31137
32686
  const sites = rowsFromToolResult(listed, ["siteEntry", "sites", "items"]).flatMap((value) => {
31138
- const row = isRecord2(value) ? value : null;
31139
- 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;
31140
32689
  return siteUrl2 ? [siteUrl2] : [];
31141
32690
  }).sort().slice(0, 1e4);
31142
32691
  if (siteIndex >= sites.length) {
@@ -31173,7 +32722,7 @@ async function callMainOwnedExportPage(identity, input) {
31173
32722
  });
31174
32723
  const rows = rowsFromToolResult(performance2, ["rows"]).slice(0, input.pageSize);
31175
32724
  const records = rows.map((value, index) => {
31176
- const row = isRecord2(value) ? value : {};
32725
+ const row = isRecord4(value) ? value : {};
31177
32726
  const keys = Array.isArray(row.keys) ? row.keys.map((item) => String(item ?? "")) : [];
31178
32727
  const date = keys[0] || null;
31179
32728
  return {
@@ -31250,34 +32799,34 @@ function controlSecret2() {
31250
32799
  if (!secret2) throw new ResendControlError("Resend connections are not configured.", 503);
31251
32800
  return secret2;
31252
32801
  }
31253
- function isRecord3(value) {
32802
+ function isRecord5(value) {
31254
32803
  return !!value && typeof value === "object" && !Array.isArray(value);
31255
32804
  }
31256
32805
  function unwrapData2(value) {
31257
- return isRecord3(value) && isRecord3(value.data) ? value.data : value;
32806
+ return isRecord5(value) && isRecord5(value.data) ? value.data : value;
31258
32807
  }
31259
- function cleanString2(value, max = 300) {
32808
+ function cleanString4(value, max = 300) {
31260
32809
  if (typeof value !== "string") return null;
31261
32810
  const trimmed = value.trim();
31262
32811
  return trimmed ? trimmed.slice(0, max) : null;
31263
32812
  }
31264
32813
  function firstString2(record, keys, max = 300) {
31265
32814
  for (const key of keys) {
31266
- const value = cleanString2(record[key], max);
32815
+ const value = cleanString4(record[key], max);
31267
32816
  if (value) return value;
31268
32817
  }
31269
32818
  return null;
31270
32819
  }
31271
32820
  function cleanTools2(value) {
31272
32821
  if (!Array.isArray(value)) return [];
31273
- 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);
31274
32823
  }
31275
32824
  function cleanStringArray2(value, maxItems = 50, maxLength = 500) {
31276
32825
  if (!Array.isArray(value)) return [];
31277
- 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);
31278
32827
  }
31279
32828
  function cleanHttpsUrl2(value) {
31280
- const candidate = cleanString2(value, 2e3);
32829
+ const candidate = cleanString4(value, 2e3);
31281
32830
  if (!candidate) return null;
31282
32831
  try {
31283
32832
  const url = new URL(candidate);
@@ -31295,7 +32844,7 @@ function cleanAuthorizationUrl(value) {
31295
32844
  function arrayFromPayload2(value, keys) {
31296
32845
  const unwrapped = unwrapData2(value);
31297
32846
  if (Array.isArray(unwrapped)) return unwrapped;
31298
- if (!isRecord3(unwrapped)) return [];
32847
+ if (!isRecord5(unwrapped)) return [];
31299
32848
  for (const key of keys) {
31300
32849
  if (Array.isArray(unwrapped[key])) return unwrapped[key];
31301
32850
  }
@@ -31322,7 +32871,7 @@ async function controlRequest2(path5, init, timeoutMs = 2e4) {
31322
32871
  body = {};
31323
32872
  }
31324
32873
  if (!response.ok) {
31325
- const upstream = isRecord3(body) ? cleanString2(body.error, 300) : null;
32874
+ const upstream = isRecord5(body) ? cleanString4(body.error, 300) : null;
31326
32875
  throw new ResendControlError(upstream || `Resend connection control failed (${response.status}).`);
31327
32876
  }
31328
32877
  return body;
@@ -31332,7 +32881,7 @@ async function getResendCatalog() {
31332
32881
  const rows = arrayFromPayload2(body, ["providers", "catalog", "integrations", "services"]);
31333
32882
  const result = [];
31334
32883
  for (const row of rows) {
31335
- if (!isRecord3(row)) continue;
32884
+ if (!isRecord5(row)) continue;
31336
32885
  const id = firstString2(row, ["providerConfigKey", "provider_config_key", "id"]);
31337
32886
  if (id !== RESEND_PROVIDER_CONFIG_KEY) continue;
31338
32887
  const safeDefaultAllowedTools = cleanTools2(
@@ -31368,7 +32917,7 @@ async function getResendConnections(identity) {
31368
32917
  const rows = arrayFromPayload2(body, ["connections"]);
31369
32918
  const result = [];
31370
32919
  for (const row of rows) {
31371
- if (!isRecord3(row)) continue;
32920
+ if (!isRecord5(row)) continue;
31372
32921
  const connectionId = firstString2(row, ["connectionId", "connection_id", "id"]);
31373
32922
  const providerConfigKey = firstString2(row, ["providerConfigKey", "provider_config_key", "provider"]) || RESEND_PROVIDER_CONFIG_KEY;
31374
32923
  if (!connectionId || providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) continue;
@@ -31412,7 +32961,7 @@ async function getResendConnections(identity) {
31412
32961
  }
31413
32962
  function sanitizeConnectSession2(body) {
31414
32963
  const data = unwrapData2(body);
31415
- 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.");
31416
32965
  const connectLink = cleanAuthorizationUrl(data.authorizationUrl ?? data.authorization_url ?? data.connectLink ?? data.connect_link);
31417
32966
  if (!connectLink) throw new ResendControlError("The Resend connection service did not return a trusted authorization link.");
31418
32967
  return {
@@ -31453,7 +33002,7 @@ async function setResendActionsEnabled(identity, connectionId, enabled) {
31453
33002
  body: JSON.stringify({ identity, connectionId, enabled })
31454
33003
  });
31455
33004
  const data = unwrapData2(body);
31456
- if (!isRecord3(data) || !isRecord3(data.connection)) return enabled;
33005
+ if (!isRecord5(data) || !isRecord5(data.connection)) return enabled;
31457
33006
  return data.connection.actionsEnabled === true || data.connection.actions_enabled === true;
31458
33007
  }
31459
33008
  async function callResendRead(identity, connectionId, tool, args) {
@@ -31462,7 +33011,7 @@ async function callResendRead(identity, connectionId, tool, args) {
31462
33011
  body: JSON.stringify({ identity, connectionId, tool, input: args ?? {} })
31463
33012
  });
31464
33013
  const data = unwrapData2(body);
31465
- return isRecord3(data) ? data.result ?? data : data;
33014
+ return isRecord5(data) ? data.result ?? data : data;
31466
33015
  }
31467
33016
  async function callResendAction(identity, connectionId, tool, input) {
31468
33017
  const body = await controlRequest2("/api/internal/resend/actions/call", {
@@ -31470,7 +33019,7 @@ async function callResendAction(identity, connectionId, tool, input) {
31470
33019
  body: JSON.stringify({ identity, connectionId, tool, input })
31471
33020
  });
31472
33021
  const data = unwrapData2(body);
31473
- return isRecord3(data) ? data.result ?? data : data;
33022
+ return isRecord5(data) ? data.result ?? data : data;
31474
33023
  }
31475
33024
  async function describeResendTool(identity, connectionId, tool) {
31476
33025
  const body = await controlRequest2("/api/internal/resend/describe", {
@@ -31478,12 +33027,12 @@ async function describeResendTool(identity, connectionId, tool) {
31478
33027
  body: JSON.stringify({ identity, connectionId, tool })
31479
33028
  });
31480
33029
  const data = unwrapData2(body);
31481
- const rawTool = isRecord3(data) && isRecord3(data.tool) ? data.tool : data;
31482
- 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.");
31483
33032
  const name = firstString2(rawTool, ["name"], 200);
31484
33033
  const classification = firstString2(rawTool, ["classification", "kind"], 20);
31485
33034
  const inputSchema = rawTool.inputSchema ?? rawTool.input_schema;
31486
- if (!name || name !== tool || classification !== "read" && classification !== "action" || !isRecord3(inputSchema)) {
33035
+ if (!name || name !== tool || classification !== "read" && classification !== "action" || !isRecord5(inputSchema)) {
31487
33036
  throw new ResendControlError("Resend returned an invalid tool description.");
31488
33037
  }
31489
33038
  return {
@@ -31500,10 +33049,10 @@ async function callResendExportPage(identity, input) {
31500
33049
  body: JSON.stringify({ identity, ...input })
31501
33050
  }, 9e4);
31502
33051
  const data = unwrapData2(body);
31503
- 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) {
31504
33053
  throw new ResendControlError("Resend export returned an invalid page response.");
31505
33054
  }
31506
- const dataset = cleanString2(data.dataset, 64);
33055
+ const dataset = cleanString4(data.dataset, 64);
31507
33056
  if (!dataset || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
31508
33057
  throw new ResendControlError("Resend export returned invalid dataset metadata.");
31509
33058
  }
@@ -31511,7 +33060,7 @@ async function callResendExportPage(identity, input) {
31511
33060
  if (data.nextCursor !== void 0 && data.nextCursor !== null && typeof data.nextCursor !== "string") {
31512
33061
  throw new ResendControlError("Resend export returned an invalid continuation cursor.");
31513
33062
  }
31514
- const rawCounts = isRecord3(data.counts) ? data.counts : {};
33063
+ const rawCounts = isRecord5(data.counts) ? data.counts : {};
31515
33064
  const numberOrZero = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
31516
33065
  return {
31517
33066
  providerConfigKey: RESEND_PROVIDER_CONFIG_KEY,
@@ -31530,7 +33079,7 @@ async function callResendExportPage(identity, input) {
31530
33079
  }
31531
33080
 
31532
33081
  // src/api/scheduler-integration-auth.ts
31533
- import { createHash as createHash14, createHmac as createHmac4, timingSafeEqual as timingSafeEqual3 } from "crypto";
33082
+ import { createHash as createHash15, createHmac as createHmac4, timingSafeEqual as timingSafeEqual3 } from "crypto";
31534
33083
  var MAX_CLOCK_SKEW_MS = 5 * 60 * 1e3;
31535
33084
  var SchedulerIntegrationAuthError = class extends Error {
31536
33085
  code;
@@ -31546,7 +33095,7 @@ function signingSecret() {
31546
33095
  return secret2;
31547
33096
  }
31548
33097
  function schedulerIntegrationSignature(args) {
31549
- const bodyHash = createHash14("sha256").update(args.body).digest("hex");
33098
+ const bodyHash = createHash15("sha256").update(args.body).digest("hex");
31550
33099
  return createHmac4("sha256", args.secret).update(`${args.method.toUpperCase()}
31551
33100
  ${args.path}
31552
33101
  ${args.timestamp}
@@ -31713,6 +33262,18 @@ async function createSignupStripeCustomer(email) {
31713
33262
  }
31714
33263
  app.use("*", async (c, next) => {
31715
33264
  await next();
33265
+ if (c.res.status >= 500 && c.res.headers.get("content-type")?.includes("application/json")) {
33266
+ try {
33267
+ const payload = await c.res.clone().json();
33268
+ const headers = new Headers(c.res.headers);
33269
+ c.res = new Response(JSON.stringify(sanitizeOutboundDiagnostics(payload)), {
33270
+ status: c.res.status,
33271
+ statusText: c.res.statusText,
33272
+ headers
33273
+ });
33274
+ } catch {
33275
+ }
33276
+ }
31716
33277
  c.header("Cache-Control", "no-store");
31717
33278
  c.header("X-Content-Type-Options", "nosniff");
31718
33279
  c.header("Referrer-Policy", "same-origin");
@@ -32114,6 +33675,16 @@ app.get("/memory/note", auth2, async (c) => {
32114
33675
  return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load note" }, 502);
32115
33676
  }
32116
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
+ });
32117
33688
  app.put("/memory/note", auth2, async (c) => {
32118
33689
  const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
32119
33690
  if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
@@ -32855,7 +34426,18 @@ app.post("/schedule-connections/actions/export", auth2, requireIntegrationsTier,
32855
34426
  const body = await c.req.json().catch(() => ({}));
32856
34427
  const connectionId = providerConfigKeyFrom(body.connectionId);
32857
34428
  if (!connectionId) return c.json({ ok: false, error: "connectionId is required." }, 400);
32858
- 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;
32859
34441
  const datasets = [...CONNECTED_DATA_DATASETS];
32860
34442
  if (!datasets.includes(requestedDataset)) {
32861
34443
  return c.json({ ok: false, error: `dataset must be one of: ${datasets.join(", ")}.` }, 400);
@@ -32878,21 +34460,37 @@ app.post("/schedule-connections/actions/export", auth2, requireIntegrationsTier,
32878
34460
  if (body.continuation !== void 0 && !continuation) {
32879
34461
  return c.json({ ok: false, error: "continuation must be the complete object returned by a prior export." }, 400);
32880
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
+ }
32881
34472
  try {
32882
34473
  const useResend = await isResendConnection(user.email, connectionId);
32883
34474
  const resolved = resolveConnectedDataExportRequest({
32884
34475
  requestedDataset,
32885
- 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,
32886
34477
  to: typeof body.to === "string" ? body.to : void 0,
32887
34478
  lastDays,
32888
34479
  cursor: typeof body.cursor === "string" ? body.cursor : void 0,
32889
- continuation
34480
+ continuation,
34481
+ scope: channelId ? {
34482
+ slack: {
34483
+ channelId,
34484
+ includeThreads: body.includeThreads !== false
34485
+ }
34486
+ } : void 0
32890
34487
  });
32891
34488
  const result = await collectConnectedDataExport({
32892
34489
  ownerId: sha256Hex(user.api_key).slice(0, 24),
32893
34490
  connectionId,
32894
34491
  dataset: resolved.dataset,
32895
34492
  range: resolved.range,
34493
+ ...resolved.scope ? { scope: resolved.scope } : {},
32896
34494
  maxItems,
32897
34495
  forceArtifact: body.delivery === "artifact",
32898
34496
  ...resolved.cursor ? { cursor: resolved.cursor } : {},
@@ -33253,8 +34851,8 @@ function countPaaQuestions(result) {
33253
34851
  if (typeof value.totalQuestions === "number") return value.totalQuestions;
33254
34852
  return Array.isArray(value.flat) ? value.flat.length : 0;
33255
34853
  }
33256
- function paaCostForQuestionCount(questionCount) {
33257
- return MC_COSTS.paa_base + Math.max(1, questionCount) * MC_COSTS.paa;
34854
+ function paaCostForQuestionCount(questionCount2) {
34855
+ return MC_COSTS.paa_base + Math.max(1, questionCount2) * MC_COSTS.paa;
33258
34856
  }
33259
34857
  async function checkHarvestLimits(user, reuseLockId) {
33260
34858
  if (reuseLockId && await reuseExistingConcurrencyGate(user.id, reuseLockId, 30 * 60)) return null;
@@ -33301,9 +34899,50 @@ app.post("/harvest", auth2, async (c) => {
33301
34899
  pages: Math.min(2, Math.max(1, body.pages ?? 1))
33302
34900
  };
33303
34901
  const harvestCost = options.serpOnly ? MC_COSTS.serp_headful : paaCostForQuestionCount(options.maxQuestions);
33304
- const { ok: harvestOk, balance_mc: harvestBal } = await debitMc(user.id, harvestCost, options.serpOnly ? LedgerOperation.SERP : LedgerOperation.PAA, options.query);
33305
- if (!harvestOk) return c.json(insufficientBalanceResponse(harvestBal, harvestCost), 402);
33306
- const jobId2 = await createJob(user.id, options.query, { ...options, billingHoldMc: harvestCost }, body.callback_url);
34902
+ let jobId2;
34903
+ if (options.serpOnly) {
34904
+ const { ok: harvestOk, balance_mc: harvestBal } = await debitMc(
34905
+ user.id,
34906
+ harvestCost,
34907
+ LedgerOperation.SERP,
34908
+ options.query
34909
+ );
34910
+ if (!harvestOk) return c.json(insufficientBalanceResponse(harvestBal, harvestCost), 402);
34911
+ jobId2 = await createJob(user.id, options.query, { ...options, billingHoldMc: harvestCost }, body.callback_url);
34912
+ } else {
34913
+ jobId2 = randomUUID18();
34914
+ const billingDebitKey = `paa-harvest:${jobId2}:hold`;
34915
+ const description = `PAA harvest: ${options.query}`.slice(0, 500);
34916
+ const hold = await debitMcIdempotent(
34917
+ user.id,
34918
+ harvestCost,
34919
+ LedgerOperation.PAA,
34920
+ description,
34921
+ billingDebitKey
34922
+ );
34923
+ if (!hold.ok) return c.json(insufficientBalanceResponse(hold.balance_mc, harvestCost), 402);
34924
+ try {
34925
+ await createJobWithId(jobId2, user.id, options.query, {
34926
+ ...options,
34927
+ executionOwner: "inngest",
34928
+ billingHoldMc: harvestCost,
34929
+ billingDebitKey
34930
+ }, body.callback_url);
34931
+ } catch (error) {
34932
+ await settleDebitMcIdempotent(
34933
+ user.id,
34934
+ billingDebitKey,
34935
+ 0,
34936
+ LedgerOperation.PAA_REFUND,
34937
+ "PAA job creation failed",
34938
+ "paa_harvest"
34939
+ ).catch(() => void 0);
34940
+ throw error;
34941
+ }
34942
+ await dispatchPaaHarvest(jobId2).catch((error) => {
34943
+ console.error("[harvest] durable PAA dispatch pending reconciliation:", error instanceof Error ? error.message : String(error));
34944
+ });
34945
+ }
33307
34946
  if (process.env.CRON_SECRET) {
33308
34947
  const url = new URL(c.req.url);
33309
34948
  void fetch(`${url.origin}/cron/tick`, {
@@ -33378,6 +35017,10 @@ app.post("/harvest/sync", auth2, async (c) => {
33378
35017
  }
33379
35018
  return c.json({ job_id: jobId2, status: "done", result: sanitizeHarvestResult(result), attempts: sanitizeAttempts(attempts) });
33380
35019
  } catch (err) {
35020
+ console.error("[harvest/sync] failed", {
35021
+ jobId: jobId2,
35022
+ error: err instanceof Error ? err.message : String(err)
35023
+ });
33381
35024
  const problem = classifyHarvestProblem(err);
33382
35025
  const response = harvestProblemResponse(problem);
33383
35026
  const attempts = await listHarvestAttempts(jobId2, user.id);
@@ -34421,10 +36064,10 @@ app.get("/cron/tick", async (c) => {
34421
36064
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
34422
36065
  return c.json({ error: "Unauthorized" }, 401);
34423
36066
  }
34424
- const { drainQueue } = await import("./worker-FXAGFYOE.js");
36067
+ const { drainQueue } = await import("./worker-5RWLFSAJ.js");
34425
36068
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
34426
36069
  const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
34427
- const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup, connectedAccountBilling, directoryWorkflowDispatch, directoryWorkflowReconciliation, directoryArtifactCleanup, siteExtractArtifactCleanup, siteExtractRedispatch, siteExtractReconciliation] = await Promise.all([
36070
+ const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup, connectedAccountBilling, directoryWorkflowDispatch, directoryWorkflowReconciliation, directoryArtifactCleanup, siteExtractArtifactCleanup, siteExtractRedispatch, siteExtractReconciliation, paaHarvestRedispatch, paaHarvestReconciliation] = await Promise.all([
34428
36071
  drainQueue(budget),
34429
36072
  runMonthlyRefreshSweep(),
34430
36073
  reapIdleBrowserSessions(120).catch(() => ({ reaped: 0 })),
@@ -34437,7 +36080,9 @@ app.get("/cron/tick", async (c) => {
34437
36080
  cleanupExpiredDirectoryArtifacts().catch(() => ({ deleted: 0, store: "none" })),
34438
36081
  cleanupExpiredSiteExtractArtifacts().catch(() => ({ deleted: 0, store: "none" })),
34439
36082
  redispatchFundedSiteExtractJobs(25).catch(() => ({ checked: 0, dispatched: 0, failed: 0 })),
34440
- reconcileSiteExtractSettlements(25).catch(() => ({ checked: 0, settled: 0, failed: 0 }))
36083
+ reconcileSiteExtractSettlements(25).catch(() => ({ checked: 0, settled: 0, failed: 0 })),
36084
+ redispatchPendingPaaHarvests(25).catch(() => ({ checked: 0, dispatched: 0, failed: 0 })),
36085
+ reconcilePaaHarvestSettlements(25).catch(() => ({ checked: 0, staleFailed: 0, settled: 0, failed: 0 }))
34441
36086
  ]);
34442
36087
  return c.json({
34443
36088
  drained: results.length,
@@ -34454,6 +36099,8 @@ app.get("/cron/tick", async (c) => {
34454
36099
  siteExtractArtifactCleanup,
34455
36100
  siteExtractRedispatch,
34456
36101
  siteExtractReconciliation,
36102
+ paaHarvestRedispatch,
36103
+ paaHarvestReconciliation,
34457
36104
  workflowDispatch: workflowDispatchResult
34458
36105
  });
34459
36106
  });
@@ -34470,8 +36117,8 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
34470
36117
  finishExtractJob: finishExtractJob2,
34471
36118
  terminalExtractJobStatus: terminalExtractJobStatus2,
34472
36119
  extractJobLimitInfo: extractJobLimitInfo2
34473
- } = await import("./site-extract-repository-2SMMFKKL.js");
34474
- const { assembleExtractArtifacts } = await import("./extract-bundle-K4PG3RZJ.js");
36120
+ } = await import("./site-extract-repository-6GX72U4N.js");
36121
+ const { assembleExtractArtifacts } = await import("./extract-bundle-SKXEDG4Z.js");
34475
36122
  const existing = await getExtractJob2(jobId2);
34476
36123
  if (!existing) return c.json({ error: "job not found" }, 404);
34477
36124
  if (existing.status !== "failed") {
@@ -34498,7 +36145,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
34498
36145
  if (!updated) return c.json({ error: "job state changed during re-finalization" }, 409);
34499
36146
  let settlement = "already_settled_or_refunded";
34500
36147
  if (job.billedMc == null && job.userId != null) {
34501
- const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-2SMMFKKL.js");
36148
+ const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-6GX72U4N.js");
34502
36149
  const heldMc = Number(job.options.heldMc ?? 0);
34503
36150
  const successful = await countSuccessfulPages2(jobId2);
34504
36151
  const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
@@ -34513,7 +36160,10 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
34513
36160
  const bundle = stored.find((a) => a.contentType === "application/zip" || a.filename?.endsWith(".zip"));
34514
36161
  return c.json({ ok: true, jobId: jobId2, artifacts: stored.length, settlement, bundleUrl: bundle?.url ?? null, bundleBytes: bundle?.bytes ?? null });
34515
36162
  });
34516
- app.on(["GET", "POST", "PUT"], "/api/inngest", serveInngest({ client: inngest, functions: [siteAuditFn, siteExtractFn, directoryWorkflowFn] }));
36163
+ app.on(["GET", "POST", "PUT"], "/api/inngest", serveInngest({
36164
+ client: inngest,
36165
+ functions: [siteAuditFn, siteExtractFn, directoryWorkflowFn, paaHarvestFn]
36166
+ }));
34517
36167
  app.route("/admin/credits", adminCreditsApp);
34518
36168
  app.route("/api/internal/site-architecture-auditor", siteAuditApp);
34519
36169
  app.route("/api/internal/memory", internalMemoryApp);
@@ -34671,4 +36321,4 @@ app.get("/blog/:slug/", (c) => {
34671
36321
  export {
34672
36322
  app
34673
36323
  };
34674
- //# sourceMappingURL=server-KM3CFWCF.js.map
36324
+ //# sourceMappingURL=server-2JJPCZH4.js.map