mcp-scraper 0.40.2 → 0.40.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/api-server.cjs +1517 -236
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +155 -87
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +6 -5
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/bin/paa-harvest.cjs +31 -9
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +4 -4
- package/dist/{chunk-XORPNO3Z.js → chunk-27DUCRAZ.js} +2 -2
- package/dist/{chunk-44HZLHDV.js → chunk-5UN33CGU.js} +38 -1
- package/dist/chunk-5UN33CGU.js.map +1 -0
- package/dist/{chunk-X2LKCX6H.js → chunk-6J57U6HA.js} +3 -3
- package/dist/{chunk-LP6E462I.js → chunk-6SZ52BQ5.js} +49 -101
- package/dist/chunk-6SZ52BQ5.js.map +1 -0
- package/dist/chunk-BBI7RGOT.js +99 -0
- package/dist/chunk-BBI7RGOT.js.map +1 -0
- package/dist/{chunk-NVXNEOUQ.js → chunk-CS4HE6QY.js} +47 -11
- package/dist/chunk-CS4HE6QY.js.map +1 -0
- package/dist/{chunk-3PIWJS6Y.js → chunk-EHES33KB.js} +2 -2
- package/dist/{chunk-GUVKHCKE.js → chunk-IQTT7CAB.js} +94 -3
- package/dist/chunk-IQTT7CAB.js.map +1 -0
- package/dist/{chunk-7XBBFBYY.js → chunk-JVIW4GK2.js} +44 -12
- package/dist/chunk-JVIW4GK2.js.map +1 -0
- package/dist/chunk-XU4ZLIB2.js +7 -0
- package/dist/chunk-XU4ZLIB2.js.map +1 -0
- package/dist/{db-W3CP562I.js → db-LVVU6NOK.js} +16 -2
- package/dist/{extract-bundle-K4PG3RZJ.js → extract-bundle-SKXEDG4Z.js} +4 -4
- package/dist/index.cjs +31 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/{location-data-repository-RLQX6SNM.js → location-data-repository-NBTCOA7B.js} +3 -3
- package/dist/{server-KM3CFWCF.js → server-W76RUYD3.js} +1108 -66
- package/dist/server-W76RUYD3.js.map +1 -0
- package/dist/{site-extract-repository-2SMMFKKL.js → site-extract-repository-6GX72U4N.js} +4 -4
- package/dist/{worker-FXAGFYOE.js → worker-5RWLFSAJ.js} +27 -28
- package/dist/worker-5RWLFSAJ.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-44HZLHDV.js.map +0 -1
- package/dist/chunk-7XBBFBYY.js.map +0 -1
- package/dist/chunk-GUVKHCKE.js.map +0 -1
- package/dist/chunk-LP6E462I.js.map +0 -1
- package/dist/chunk-NVXNEOUQ.js.map +0 -1
- package/dist/chunk-ZUJLSICT.js +0 -7
- package/dist/chunk-ZUJLSICT.js.map +0 -1
- package/dist/server-KM3CFWCF.js.map +0 -1
- package/dist/worker-FXAGFYOE.js.map +0 -1
- /package/dist/{chunk-XORPNO3Z.js.map → chunk-27DUCRAZ.js.map} +0 -0
- /package/dist/{chunk-X2LKCX6H.js.map → chunk-6J57U6HA.js.map} +0 -0
- /package/dist/{chunk-3PIWJS6Y.js.map → chunk-EHES33KB.js.map} +0 -0
- /package/dist/{db-W3CP562I.js.map → db-LVVU6NOK.js.map} +0 -0
- /package/dist/{extract-bundle-K4PG3RZJ.js.map → extract-bundle-SKXEDG4Z.js.map} +0 -0
- /package/dist/{location-data-repository-RLQX6SNM.js.map → location-data-repository-NBTCOA7B.js.map} +0 -0
- /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-
|
|
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-
|
|
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-
|
|
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-
|
|
70
|
+
} from "./chunk-6SZ52BQ5.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-
|
|
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-
|
|
122
|
+
import "./chunk-XU4ZLIB2.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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
18367
|
-
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
20850
|
-
const keyDigest =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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, "");
|
|
@@ -29400,10 +30378,10 @@ async function dbUsage(identity, plan) {
|
|
|
29400
30378
|
}
|
|
29401
30379
|
|
|
29402
30380
|
// src/api/nango-control.ts
|
|
29403
|
-
import { createHash as
|
|
30381
|
+
import { createHash as createHash14, randomUUID as randomUUID17 } from "crypto";
|
|
29404
30382
|
|
|
29405
30383
|
// src/api/main-nango-transport.ts
|
|
29406
|
-
import { createHash as
|
|
30384
|
+
import { createHash as createHash13, randomUUID as randomUUID16 } from "crypto";
|
|
29407
30385
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
29408
30386
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
29409
30387
|
|
|
@@ -29995,7 +30973,7 @@ async function listNangoToolsDirect(identity, connectionId) {
|
|
|
29995
30973
|
});
|
|
29996
30974
|
const readTools = tools.filter((tool) => classifyTool(tool) === "read").map((tool) => tool.name);
|
|
29997
30975
|
const actionTools = tools.filter((tool) => classifyTool(tool) === "action").map((tool) => tool.name);
|
|
29998
|
-
const revision =
|
|
30976
|
+
const revision = createHash13("sha256").update(JSON.stringify(tools.map((tool) => ({ name: tool.name, inputSchema: tool.inputSchema })))).digest("hex");
|
|
29999
30977
|
await updateServiceConnectionTools(connection.id, readTools, actionTools, revision);
|
|
30000
30978
|
const refreshed = await getOwnedServiceConnection(identity, connection.id);
|
|
30001
30979
|
return { connection: refreshed ?? { ...connection, readTools, actionTools, toolRevision: revision }, tools };
|
|
@@ -30016,7 +30994,7 @@ async function callNangoToolDirect(args) {
|
|
|
30016
30994
|
ratePolicyVersion: CONNECTED_USAGE_RATE_POLICY_VERSION
|
|
30017
30995
|
});
|
|
30018
30996
|
const requestId = args.requestId?.trim() || randomUUID16();
|
|
30019
|
-
const idempotencyKey2 = `main-nango:${
|
|
30997
|
+
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
30998
|
const startedAt = /* @__PURE__ */ new Date();
|
|
30021
30999
|
const started = performance.now();
|
|
30022
31000
|
let result;
|
|
@@ -30041,7 +31019,7 @@ async function callNangoToolDirect(args) {
|
|
|
30041
31019
|
toolName: args.tool,
|
|
30042
31020
|
operationKind: args.operationKind ?? args.classification,
|
|
30043
31021
|
outcome: providerError ? "error" : "partial",
|
|
30044
|
-
requestId: requestId.length <= 200 ? requestId :
|
|
31022
|
+
requestId: requestId.length <= 200 ? requestId : createHash13("sha256").update(requestId).digest("hex"),
|
|
30045
31023
|
startedAt: startedAt.toISOString(),
|
|
30046
31024
|
completedAt: completedAt.toISOString()
|
|
30047
31025
|
}
|
|
@@ -30078,7 +31056,7 @@ async function describeNangoToolDirect(identity, connectionId, toolName) {
|
|
|
30078
31056
|
providerContractHash: MAIN_INTEGRATION_CONTRACT_HASH,
|
|
30079
31057
|
protocolVersion: null,
|
|
30080
31058
|
schemaSource: "live_tools_list",
|
|
30081
|
-
schemaHash:
|
|
31059
|
+
schemaHash: createHash13("sha256").update(JSON.stringify(projected)).digest("hex"),
|
|
30082
31060
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30083
31061
|
};
|
|
30084
31062
|
}
|
|
@@ -30802,7 +31780,7 @@ async function setScheduleConnectionActionsEnabled(identity, connectionId, enabl
|
|
|
30802
31780
|
return data.connection.actionsEnabled === true;
|
|
30803
31781
|
}
|
|
30804
31782
|
async function callScheduleConnectionAction(identity, connectionId, input, tool, idempotencyKey2) {
|
|
30805
|
-
const requestId = `main-connected-action:${
|
|
31783
|
+
const requestId = `main-connected-action:${createHash14("sha256").update(identity).update("\0").update(idempotencyKey2?.trim() || randomUUID17()).digest("hex")}`;
|
|
30806
31784
|
if (mainOwnsIntegrations()) {
|
|
30807
31785
|
const selectedTool = tool?.trim();
|
|
30808
31786
|
if (!selectedTool) throw new NangoControlError("An action tool is required.", 400, "invalid_request", false);
|
|
@@ -30944,7 +31922,7 @@ function canonicalJson2(value) {
|
|
|
30944
31922
|
return JSON.stringify(value);
|
|
30945
31923
|
}
|
|
30946
31924
|
function projectedToolSchemaHash(tool) {
|
|
30947
|
-
return
|
|
31925
|
+
return createHash14("sha256").update(canonicalJson2(tool)).digest("hex");
|
|
30948
31926
|
}
|
|
30949
31927
|
async function describeNangoTool(identity, connectionId, tool, fresh) {
|
|
30950
31928
|
if (mainOwnsIntegrations()) {
|
|
@@ -31530,7 +32508,7 @@ async function callResendExportPage(identity, input) {
|
|
|
31530
32508
|
}
|
|
31531
32509
|
|
|
31532
32510
|
// src/api/scheduler-integration-auth.ts
|
|
31533
|
-
import { createHash as
|
|
32511
|
+
import { createHash as createHash15, createHmac as createHmac4, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
31534
32512
|
var MAX_CLOCK_SKEW_MS = 5 * 60 * 1e3;
|
|
31535
32513
|
var SchedulerIntegrationAuthError = class extends Error {
|
|
31536
32514
|
code;
|
|
@@ -31546,7 +32524,7 @@ function signingSecret() {
|
|
|
31546
32524
|
return secret2;
|
|
31547
32525
|
}
|
|
31548
32526
|
function schedulerIntegrationSignature(args) {
|
|
31549
|
-
const bodyHash =
|
|
32527
|
+
const bodyHash = createHash15("sha256").update(args.body).digest("hex");
|
|
31550
32528
|
return createHmac4("sha256", args.secret).update(`${args.method.toUpperCase()}
|
|
31551
32529
|
${args.path}
|
|
31552
32530
|
${args.timestamp}
|
|
@@ -31713,6 +32691,18 @@ async function createSignupStripeCustomer(email) {
|
|
|
31713
32691
|
}
|
|
31714
32692
|
app.use("*", async (c, next) => {
|
|
31715
32693
|
await next();
|
|
32694
|
+
if (c.res.status >= 500 && c.res.headers.get("content-type")?.includes("application/json")) {
|
|
32695
|
+
try {
|
|
32696
|
+
const payload = await c.res.clone().json();
|
|
32697
|
+
const headers = new Headers(c.res.headers);
|
|
32698
|
+
c.res = new Response(JSON.stringify(sanitizeOutboundDiagnostics(payload)), {
|
|
32699
|
+
status: c.res.status,
|
|
32700
|
+
statusText: c.res.statusText,
|
|
32701
|
+
headers
|
|
32702
|
+
});
|
|
32703
|
+
} catch {
|
|
32704
|
+
}
|
|
32705
|
+
}
|
|
31716
32706
|
c.header("Cache-Control", "no-store");
|
|
31717
32707
|
c.header("X-Content-Type-Options", "nosniff");
|
|
31718
32708
|
c.header("Referrer-Policy", "same-origin");
|
|
@@ -33253,8 +34243,8 @@ function countPaaQuestions(result) {
|
|
|
33253
34243
|
if (typeof value.totalQuestions === "number") return value.totalQuestions;
|
|
33254
34244
|
return Array.isArray(value.flat) ? value.flat.length : 0;
|
|
33255
34245
|
}
|
|
33256
|
-
function paaCostForQuestionCount(
|
|
33257
|
-
return MC_COSTS.paa_base + Math.max(1,
|
|
34246
|
+
function paaCostForQuestionCount(questionCount2) {
|
|
34247
|
+
return MC_COSTS.paa_base + Math.max(1, questionCount2) * MC_COSTS.paa;
|
|
33258
34248
|
}
|
|
33259
34249
|
async function checkHarvestLimits(user, reuseLockId) {
|
|
33260
34250
|
if (reuseLockId && await reuseExistingConcurrencyGate(user.id, reuseLockId, 30 * 60)) return null;
|
|
@@ -33301,9 +34291,50 @@ app.post("/harvest", auth2, async (c) => {
|
|
|
33301
34291
|
pages: Math.min(2, Math.max(1, body.pages ?? 1))
|
|
33302
34292
|
};
|
|
33303
34293
|
const harvestCost = options.serpOnly ? MC_COSTS.serp_headful : paaCostForQuestionCount(options.maxQuestions);
|
|
33304
|
-
|
|
33305
|
-
if (
|
|
33306
|
-
|
|
34294
|
+
let jobId2;
|
|
34295
|
+
if (options.serpOnly) {
|
|
34296
|
+
const { ok: harvestOk, balance_mc: harvestBal } = await debitMc(
|
|
34297
|
+
user.id,
|
|
34298
|
+
harvestCost,
|
|
34299
|
+
LedgerOperation.SERP,
|
|
34300
|
+
options.query
|
|
34301
|
+
);
|
|
34302
|
+
if (!harvestOk) return c.json(insufficientBalanceResponse(harvestBal, harvestCost), 402);
|
|
34303
|
+
jobId2 = await createJob(user.id, options.query, { ...options, billingHoldMc: harvestCost }, body.callback_url);
|
|
34304
|
+
} else {
|
|
34305
|
+
jobId2 = randomUUID18();
|
|
34306
|
+
const billingDebitKey = `paa-harvest:${jobId2}:hold`;
|
|
34307
|
+
const description = `PAA harvest: ${options.query}`.slice(0, 500);
|
|
34308
|
+
const hold = await debitMcIdempotent(
|
|
34309
|
+
user.id,
|
|
34310
|
+
harvestCost,
|
|
34311
|
+
LedgerOperation.PAA,
|
|
34312
|
+
description,
|
|
34313
|
+
billingDebitKey
|
|
34314
|
+
);
|
|
34315
|
+
if (!hold.ok) return c.json(insufficientBalanceResponse(hold.balance_mc, harvestCost), 402);
|
|
34316
|
+
try {
|
|
34317
|
+
await createJobWithId(jobId2, user.id, options.query, {
|
|
34318
|
+
...options,
|
|
34319
|
+
executionOwner: "inngest",
|
|
34320
|
+
billingHoldMc: harvestCost,
|
|
34321
|
+
billingDebitKey
|
|
34322
|
+
}, body.callback_url);
|
|
34323
|
+
} catch (error) {
|
|
34324
|
+
await settleDebitMcIdempotent(
|
|
34325
|
+
user.id,
|
|
34326
|
+
billingDebitKey,
|
|
34327
|
+
0,
|
|
34328
|
+
LedgerOperation.PAA_REFUND,
|
|
34329
|
+
"PAA job creation failed",
|
|
34330
|
+
"paa_harvest"
|
|
34331
|
+
).catch(() => void 0);
|
|
34332
|
+
throw error;
|
|
34333
|
+
}
|
|
34334
|
+
await dispatchPaaHarvest(jobId2).catch((error) => {
|
|
34335
|
+
console.error("[harvest] durable PAA dispatch pending reconciliation:", error instanceof Error ? error.message : String(error));
|
|
34336
|
+
});
|
|
34337
|
+
}
|
|
33307
34338
|
if (process.env.CRON_SECRET) {
|
|
33308
34339
|
const url = new URL(c.req.url);
|
|
33309
34340
|
void fetch(`${url.origin}/cron/tick`, {
|
|
@@ -33378,6 +34409,10 @@ app.post("/harvest/sync", auth2, async (c) => {
|
|
|
33378
34409
|
}
|
|
33379
34410
|
return c.json({ job_id: jobId2, status: "done", result: sanitizeHarvestResult(result), attempts: sanitizeAttempts(attempts) });
|
|
33380
34411
|
} catch (err) {
|
|
34412
|
+
console.error("[harvest/sync] failed", {
|
|
34413
|
+
jobId: jobId2,
|
|
34414
|
+
error: err instanceof Error ? err.message : String(err)
|
|
34415
|
+
});
|
|
33381
34416
|
const problem = classifyHarvestProblem(err);
|
|
33382
34417
|
const response = harvestProblemResponse(problem);
|
|
33383
34418
|
const attempts = await listHarvestAttempts(jobId2, user.id);
|
|
@@ -34421,10 +35456,10 @@ app.get("/cron/tick", async (c) => {
|
|
|
34421
35456
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
34422
35457
|
return c.json({ error: "Unauthorized" }, 401);
|
|
34423
35458
|
}
|
|
34424
|
-
const { drainQueue } = await import("./worker-
|
|
35459
|
+
const { drainQueue } = await import("./worker-5RWLFSAJ.js");
|
|
34425
35460
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
34426
35461
|
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([
|
|
35462
|
+
const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup, connectedAccountBilling, directoryWorkflowDispatch, directoryWorkflowReconciliation, directoryArtifactCleanup, siteExtractArtifactCleanup, siteExtractRedispatch, siteExtractReconciliation, paaHarvestRedispatch, paaHarvestReconciliation] = await Promise.all([
|
|
34428
35463
|
drainQueue(budget),
|
|
34429
35464
|
runMonthlyRefreshSweep(),
|
|
34430
35465
|
reapIdleBrowserSessions(120).catch(() => ({ reaped: 0 })),
|
|
@@ -34437,7 +35472,9 @@ app.get("/cron/tick", async (c) => {
|
|
|
34437
35472
|
cleanupExpiredDirectoryArtifacts().catch(() => ({ deleted: 0, store: "none" })),
|
|
34438
35473
|
cleanupExpiredSiteExtractArtifacts().catch(() => ({ deleted: 0, store: "none" })),
|
|
34439
35474
|
redispatchFundedSiteExtractJobs(25).catch(() => ({ checked: 0, dispatched: 0, failed: 0 })),
|
|
34440
|
-
reconcileSiteExtractSettlements(25).catch(() => ({ checked: 0, settled: 0, failed: 0 }))
|
|
35475
|
+
reconcileSiteExtractSettlements(25).catch(() => ({ checked: 0, settled: 0, failed: 0 })),
|
|
35476
|
+
redispatchPendingPaaHarvests(25).catch(() => ({ checked: 0, dispatched: 0, failed: 0 })),
|
|
35477
|
+
reconcilePaaHarvestSettlements(25).catch(() => ({ checked: 0, staleFailed: 0, settled: 0, failed: 0 }))
|
|
34441
35478
|
]);
|
|
34442
35479
|
return c.json({
|
|
34443
35480
|
drained: results.length,
|
|
@@ -34454,6 +35491,8 @@ app.get("/cron/tick", async (c) => {
|
|
|
34454
35491
|
siteExtractArtifactCleanup,
|
|
34455
35492
|
siteExtractRedispatch,
|
|
34456
35493
|
siteExtractReconciliation,
|
|
35494
|
+
paaHarvestRedispatch,
|
|
35495
|
+
paaHarvestReconciliation,
|
|
34457
35496
|
workflowDispatch: workflowDispatchResult
|
|
34458
35497
|
});
|
|
34459
35498
|
});
|
|
@@ -34470,8 +35509,8 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
34470
35509
|
finishExtractJob: finishExtractJob2,
|
|
34471
35510
|
terminalExtractJobStatus: terminalExtractJobStatus2,
|
|
34472
35511
|
extractJobLimitInfo: extractJobLimitInfo2
|
|
34473
|
-
} = await import("./site-extract-repository-
|
|
34474
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
35512
|
+
} = await import("./site-extract-repository-6GX72U4N.js");
|
|
35513
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-SKXEDG4Z.js");
|
|
34475
35514
|
const existing = await getExtractJob2(jobId2);
|
|
34476
35515
|
if (!existing) return c.json({ error: "job not found" }, 404);
|
|
34477
35516
|
if (existing.status !== "failed") {
|
|
@@ -34498,7 +35537,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
34498
35537
|
if (!updated) return c.json({ error: "job state changed during re-finalization" }, 409);
|
|
34499
35538
|
let settlement = "already_settled_or_refunded";
|
|
34500
35539
|
if (job.billedMc == null && job.userId != null) {
|
|
34501
|
-
const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-
|
|
35540
|
+
const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-6GX72U4N.js");
|
|
34502
35541
|
const heldMc = Number(job.options.heldMc ?? 0);
|
|
34503
35542
|
const successful = await countSuccessfulPages2(jobId2);
|
|
34504
35543
|
const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
|
|
@@ -34513,7 +35552,10 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
34513
35552
|
const bundle = stored.find((a) => a.contentType === "application/zip" || a.filename?.endsWith(".zip"));
|
|
34514
35553
|
return c.json({ ok: true, jobId: jobId2, artifacts: stored.length, settlement, bundleUrl: bundle?.url ?? null, bundleBytes: bundle?.bytes ?? null });
|
|
34515
35554
|
});
|
|
34516
|
-
app.on(["GET", "POST", "PUT"], "/api/inngest", serveInngest({
|
|
35555
|
+
app.on(["GET", "POST", "PUT"], "/api/inngest", serveInngest({
|
|
35556
|
+
client: inngest,
|
|
35557
|
+
functions: [siteAuditFn, siteExtractFn, directoryWorkflowFn, paaHarvestFn]
|
|
35558
|
+
}));
|
|
34517
35559
|
app.route("/admin/credits", adminCreditsApp);
|
|
34518
35560
|
app.route("/api/internal/site-architecture-auditor", siteAuditApp);
|
|
34519
35561
|
app.route("/api/internal/memory", internalMemoryApp);
|
|
@@ -34671,4 +35713,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
34671
35713
|
export {
|
|
34672
35714
|
app
|
|
34673
35715
|
};
|
|
34674
|
-
//# sourceMappingURL=server-
|
|
35716
|
+
//# sourceMappingURL=server-W76RUYD3.js.map
|