mcp-scraper 0.77.0 → 0.78.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 (40) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +1 -1
  3. package/dist/{analytics-repository-AUTJK4HK.js → analytics-repository-D5LEGQG3.js} +3 -3
  4. package/dist/bin/api-server.js +4 -4
  5. package/dist/bin/mcp-scraper-cli.js +3 -3
  6. package/dist/bin/mcp-scraper-core.js +7 -7
  7. package/dist/bin/mcp-scraper-install.js +2 -2
  8. package/dist/bin/mcp-stdio-server.js +7 -7
  9. package/dist/bin/paa-harvest.js +4 -4
  10. package/dist/{chunk-DTJ56S5B.js → chunk-2P2ZVRPK.js} +189 -57
  11. package/dist/{chunk-E6Y3VCOJ.js → chunk-3PUMNEOC.js} +3 -3
  12. package/dist/{chunk-KIPIBPUB.js → chunk-47ZCUBM5.js} +1 -1
  13. package/dist/{chunk-IBQNOZ7E.js → chunk-74CCAY6T.js} +1 -1
  14. package/dist/{chunk-7RQULQF3.js → chunk-B6L73AO4.js} +1 -1
  15. package/dist/{chunk-WFQ2E4WE.js → chunk-BTQ23KCT.js} +1 -1
  16. package/dist/{chunk-IP7UDXMU.js → chunk-FODYEDZK.js} +3 -3
  17. package/dist/{chunk-PJEEKOUM.js → chunk-FUMG43EN.js} +1 -1
  18. package/dist/{chunk-GT6MP4SU.js → chunk-IYYRSVRQ.js} +1 -1
  19. package/dist/{chunk-A66DGFOU.js → chunk-KAGHJQFO.js} +1 -1
  20. package/dist/{chunk-CXY5WV45.js → chunk-NGLZ6UJR.js} +1 -1
  21. package/dist/{chunk-WEFPBAAG.js → chunk-OU4FV2RP.js} +140 -4
  22. package/dist/{chunk-F5TK4KMT.js → chunk-OUBH6XMY.js} +1 -1
  23. package/dist/{chunk-77LQV73B.js → chunk-OWF2JJKN.js} +13 -1
  24. package/dist/{chunk-F6MUGMRN.js → chunk-UFJJGXVO.js} +702 -165
  25. package/dist/{chunk-KBTUPRTF.js → chunk-VEQPA65D.js} +1 -1
  26. package/dist/{chunk-D2O7V2CT.js → chunk-VMWSXKUQ.js} +1 -1
  27. package/dist/{chunk-7HF3T7P2.js → chunk-ZUCU2A6V.js} +1 -1
  28. package/dist/{db-566MOHHD.js → db-TA567JLA.js} +17 -1
  29. package/dist/{extract-bundle-OO74L67T.js → extract-bundle-P2DTUVY3.js} +3 -3
  30. package/dist/{gmail-service-TIU4V5JA.js → gmail-service-HN57R2GS.js} +4 -4
  31. package/dist/index.cjs +714 -165
  32. package/dist/index.d.cts +83 -1
  33. package/dist/index.d.ts +83 -1
  34. package/dist/index.js +4 -4
  35. package/dist/{lead-list-enrichment-repository-WWEMJ7EN.js → lead-list-enrichment-repository-5LEWW4X7.js} +2 -2
  36. package/dist/{location-data-repository-H4B2BWGY.js → location-data-repository-AHKDXTEQ.js} +2 -2
  37. package/dist/{server-EKRNKKLU.js → server-6B2OG3NN.js} +312 -89
  38. package/dist/{site-extract-repository-B47JB62U.js → site-extract-repository-XKSVXSSY.js} +2 -2
  39. package/dist/{worker-DR6R7J46.js → worker-2BYACLNF.js} +6 -6
  40. package/package.json +1 -1
@@ -10,7 +10,7 @@ import {
10
10
  queuedCommonsDocumentCount,
11
11
  semanticCommonsEntityScores,
12
12
  typeFromMime
13
- } from "./chunk-WFQ2E4WE.js";
13
+ } from "./chunk-BTQ23KCT.js";
14
14
  import {
15
15
  SITE_EXTRACT_ARTIFACT_PREFIX,
16
16
  abandonExtractSettlement,
@@ -41,13 +41,13 @@ import {
41
41
  setExtractJobTotal,
42
42
  settleExtractJob,
43
43
  terminalExtractJobStatus
44
- } from "./chunk-D2O7V2CT.js";
44
+ } from "./chunk-VMWSXKUQ.js";
45
45
  import {
46
46
  REQUIRED_LOCATION_STATE_CODES,
47
47
  importHostedCensusPlacesCsv,
48
48
  importHostedZipGroupsCsv,
49
49
  listActiveHostedLocationDatasets
50
- } from "./chunk-F5TK4KMT.js";
50
+ } from "./chunk-OUBH6XMY.js";
51
51
  import {
52
52
  cancelPendingLeadListEnrichmentRows,
53
53
  claimLeadListEnrichmentOutbox,
@@ -74,7 +74,7 @@ import {
74
74
  saveLeadListEnrichmentRowResult,
75
75
  setInitializingLeadListEnrichmentConcurrencyLock,
76
76
  stableCanonicalJson
77
- } from "./chunk-A66DGFOU.js";
77
+ } from "./chunk-KAGHJQFO.js";
78
78
  import {
79
79
  ConnectedUsageBillingError,
80
80
  ConnectedUsagePreflightInputSchema,
@@ -101,7 +101,7 @@ import {
101
101
  probeNangoConnectionDirect,
102
102
  recordNangoConnectionCost,
103
103
  settleConnectedUsage
104
- } from "./chunk-E6Y3VCOJ.js";
104
+ } from "./chunk-3PUMNEOC.js";
105
105
  import {
106
106
  ANALYTICS_CONTENT_SORTS,
107
107
  AnalyticsIdentityProfileError,
@@ -185,7 +185,7 @@ import {
185
185
  upsertAnalyticsAdSpend,
186
186
  upsertAnalyticsHostGroup,
187
187
  verifyAnalyticsWebhookSignature
188
- } from "./chunk-IBQNOZ7E.js";
188
+ } from "./chunk-74CCAY6T.js";
189
189
  import {
190
190
  claimServiceConnectionAction,
191
191
  ensureServiceConnectionsSchema,
@@ -198,7 +198,7 @@ import {
198
198
  setServiceConnectionUserLabel,
199
199
  signSession,
200
200
  verifySession
201
- } from "./chunk-PJEEKOUM.js";
201
+ } from "./chunk-FUMG43EN.js";
202
202
  import {
203
203
  classifyHarvestProblem,
204
204
  createHarvestAttemptRecorder,
@@ -207,7 +207,7 @@ import {
207
207
  harvestProblemResponse,
208
208
  publicErrorBoundary,
209
209
  serializeHarvestProblem
210
- } from "./chunk-KIPIBPUB.js";
210
+ } from "./chunk-47ZCUBM5.js";
211
211
  import "./chunk-OZJMVCDK.js";
212
212
  import {
213
213
  BACKUP_LAST_RESORT_ATTEMPTS,
@@ -216,18 +216,20 @@ import {
216
216
  backupProxyAvailable,
217
217
  brightDataSerpEnabled,
218
218
  buildYouTubeChannelVideosUrl,
219
+ classifyGoogleOutboundLink,
219
220
  cleanupBackupProxyId,
220
221
  createBackupProxyIdSafe,
221
222
  deleteKernelProxyId,
222
223
  detectPageUserAgent,
223
224
  detectRuntimeUserAgent,
224
225
  harvest,
226
+ questionIdFor,
225
227
  resolveBackupKernelProxyId,
226
228
  resolveKernelProxyId,
227
229
  serpArrivalUrl,
228
230
  validateProviderSessionId,
229
231
  withRuntimeUserAgent
230
- } from "./chunk-F6MUGMRN.js";
232
+ } from "./chunk-UFJJGXVO.js";
231
233
  import {
232
234
  AnalyticsConfirmedPersonProjectionSchema,
233
235
  AnalyticsCrmFieldSchema,
@@ -314,7 +316,7 @@ import {
314
316
  resolveDeploymentProfile,
315
317
  resolveLocalSourcebookSchemaType,
316
318
  transcribeMediaUrl
317
- } from "./chunk-DTJ56S5B.js";
319
+ } from "./chunk-2P2ZVRPK.js";
318
320
  import {
319
321
  auditImageUrls,
320
322
  auditImages,
@@ -338,7 +340,7 @@ import {
338
340
  sanitizeAttempts,
339
341
  sanitizeHarvestResult,
340
342
  sanitizeOutboundDiagnostics
341
- } from "./chunk-7RQULQF3.js";
343
+ } from "./chunk-B6L73AO4.js";
342
344
  import {
343
345
  BROWSER_OPEN_MIN_BALANCE_MC,
344
346
  CONCURRENCY_PACK_INVOICE_DESCRIPTION,
@@ -397,7 +399,7 @@ import {
397
399
  recordKernelSession,
398
400
  recordVendorUsage,
399
401
  runWithCostContext
400
- } from "./chunk-CXY5WV45.js";
402
+ } from "./chunk-NGLZ6UJR.js";
401
403
  import {
402
404
  listWorkflowDefinitions,
403
405
  runWorkflow,
@@ -407,7 +409,7 @@ import {
407
409
  workflowOutputBaseDir,
408
410
  workflowStepCount,
409
411
  workflowSupportsSteps
410
- } from "./chunk-KBTUPRTF.js";
412
+ } from "./chunk-VEQPA65D.js";
411
413
  import {
412
414
  csvRecords,
413
415
  parseCsv,
@@ -422,7 +424,7 @@ import {
422
424
  RawMapsHoursRowSchema,
423
425
  RawMapsOverviewSchema,
424
426
  RawMapsReviewStatsSchema
425
- } from "./chunk-77LQV73B.js";
427
+ } from "./chunk-OWF2JJKN.js";
426
428
  import {
427
429
  extractContactEvidence
428
430
  } from "./chunk-VXLU74YZ.js";
@@ -433,7 +435,7 @@ import {
433
435
  } from "./chunk-GGZEC22A.js";
434
436
  import {
435
437
  PACKAGE_VERSION
436
- } from "./chunk-7HF3T7P2.js";
438
+ } from "./chunk-ZUCU2A6V.js";
437
439
  import {
438
440
  CaptchaError,
439
441
  MapsSearchIncompletePageError,
@@ -444,11 +446,12 @@ import {
444
446
  buildPublicErrorEnvelope,
445
447
  cancelJob,
446
448
  checkRateLimit,
447
- checkpointJob,
449
+ checkpointDurablePaaJob,
448
450
  claimDurablePaaJob,
449
451
  claimStripeEvent,
452
+ completeDurablePaaJob,
453
+ completeExpiredDurablePaaJob,
450
454
  completeJob,
451
- completeRunningJob,
452
455
  completeStripeEvent,
453
456
  completeWorkflowRunRecord,
454
457
  consumeCode,
@@ -468,8 +471,9 @@ import {
468
471
  debitMcIdempotent,
469
472
  deleteWorkflowSchedule,
470
473
  expireOldLots,
474
+ failDurablePaaJob,
475
+ failExpiredDurablePaaJob,
471
476
  failJob,
472
- failRunningJob,
473
477
  failStripeEvent,
474
478
  failWorkflowRunRecord,
475
479
  getBillingLedgerSummary,
@@ -503,8 +507,8 @@ import {
503
507
  listInboxMessages,
504
508
  listJobs,
505
509
  listRecentTerminalDurablePaaJobs,
510
+ listRecoverableDurablePaaJobs,
506
511
  listRequestEvents,
507
- listStaleRunningDurablePaaJobs,
508
512
  listUsers,
509
513
  listUsersDueForConnectedAccountBillingReconciliation,
510
514
  listWorkflowArtifacts,
@@ -521,7 +525,9 @@ import {
521
525
  reconcileBalanceMc,
522
526
  recordWorkflowWebhookDelivery,
523
527
  registerClient,
528
+ renewDurablePaaLease,
524
529
  replaceWorkflowArtifacts,
530
+ requeueExpiredDurablePaaJob,
525
531
  resolveUserByMemorySubscription,
526
532
  revokeApiKey,
527
533
  rotateApiKey,
@@ -540,7 +546,7 @@ import {
540
546
  updateHarvestAttemptProviderTelemetry,
541
547
  upsertPageSnapshot,
542
548
  verifyPassword
543
- } from "./chunk-WEFPBAAG.js";
549
+ } from "./chunk-OU4FV2RP.js";
544
550
 
545
551
  // src/blog/registry.ts
546
552
  var posts = [
@@ -9641,7 +9647,7 @@ var siteExtractFn = inngest.createFunction(
9641
9647
  });
9642
9648
  }) : null;
9643
9649
  const finalized = await step.run("finalize", async () => {
9644
- const { assembleExtractArtifacts } = await import("./extract-bundle-OO74L67T.js");
9650
+ const { assembleExtractArtifacts } = await import("./extract-bundle-P2DTUVY3.js");
9645
9651
  const current = await getExtractJob(jobId2);
9646
9652
  if (!current) throw new Error("extract job disappeared before finalization");
9647
9653
  const stored = await assembleExtractArtifacts(current, { branding, imageAudit });
@@ -10601,7 +10607,7 @@ async function resolveUsZipsPath(requestedPath) {
10601
10607
  async function loadZipGroups(stateAbbr, requestedPath, warnings, sourceMode) {
10602
10608
  const mode = resolvedLocationDataMode({ zipSource: sourceMode });
10603
10609
  if (mode === "hosted") {
10604
- const { getHostedZipGroups } = await import("./location-data-repository-H4B2BWGY.js");
10610
+ const { getHostedZipGroups } = await import("./location-data-repository-AHKDXTEQ.js");
10605
10611
  const hosted = await getHostedZipGroups(stateAbbr);
10606
10612
  if (!hosted.dataset) {
10607
10613
  throw new DirectoryLocationError(
@@ -10664,7 +10670,7 @@ async function resolveDirectoryMarkets(options, config = {}) {
10664
10670
  const sourceUrl = censusStateUrl(state.fips);
10665
10671
  const warnings = [];
10666
10672
  if (resolvedLocationDataMode(config) === "hosted") {
10667
- const { queryHostedLocationMarkets } = await import("./location-data-repository-H4B2BWGY.js");
10673
+ const { queryHostedLocationMarkets } = await import("./location-data-repository-AHKDXTEQ.js");
10668
10674
  const hosted = await queryHostedLocationMarkets({
10669
10675
  state: state.abbr,
10670
10676
  minPopulation: options.minPopulation,
@@ -12151,7 +12157,7 @@ function summarizePersistedLeadRows(rows, warnings = []) {
12151
12157
  async function settlePersistedLeadListEnrichment(jobId2) {
12152
12158
  const jobs = await listLeadListEnrichmentJobsNeedingSettlement(100);
12153
12159
  const job = jobs.find((candidate) => candidate.id === jobId2);
12154
- const { getLeadListEnrichmentJob: getLeadListEnrichmentJob2 } = await import("./lead-list-enrichment-repository-WWEMJ7EN.js");
12160
+ const { getLeadListEnrichmentJob: getLeadListEnrichmentJob2 } = await import("./lead-list-enrichment-repository-5LEWW4X7.js");
12155
12161
  const current = job ?? await getLeadListEnrichmentJob2(jobId2);
12156
12162
  if (!current) return null;
12157
12163
  const rows = await listLeadListEnrichmentRows(jobId2);
@@ -12207,7 +12213,7 @@ async function reconcileLeadListEnrichmentSettlements(limit = 25) {
12207
12213
  const persisted = job.result ?? summarizePersistedLeadRows(rows, job.warnings);
12208
12214
  const status = job.cancelRequestedAt ? "cancelled" : persisted.failedRows > 0 && persisted.completeRows > 0 ? "partial" : persisted.failedRows > 0 ? "cancelled" : persisted.rowsWithContacts > 0 ? "complete" : "empty";
12209
12215
  if (persisted.failedRows > 0 && persisted.completeRows === 0 && !job.cancelRequestedAt) {
12210
- const { failLeadListEnrichmentJob: failLeadListEnrichmentJob2 } = await import("./lead-list-enrichment-repository-WWEMJ7EN.js");
12216
+ const { failLeadListEnrichmentJob: failLeadListEnrichmentJob2 } = await import("./lead-list-enrichment-repository-5LEWW4X7.js");
12211
12217
  await failLeadListEnrichmentJob2({
12212
12218
  jobId: job.id,
12213
12219
  errorCode: "lead_list_all_rows_failed",
@@ -25939,6 +25945,7 @@ var CLEANUP_RESERVE_MS = 4e4;
25939
25945
  var DIRECT_PAA_MAX_ATTEMPTS = 3;
25940
25946
  var CHECKPOINT_INTERVAL_MS = 1e4;
25941
25947
  var CHECKPOINT_IMPROVEMENT_THRESHOLD = 5;
25948
+ var LEASE_HEARTBEAT_MS = 1e4;
25942
25949
  function normalizePaaQuestion(value) {
25943
25950
  if (typeof value !== "string") return "";
25944
25951
  return value.normalize("NFKC").toLocaleLowerCase("en-US").replace(/[^\p{L}\p{N}\s]/gu, "").replace(/\s+/g, " ").trim();
@@ -25946,45 +25953,87 @@ function normalizePaaQuestion(value) {
25946
25953
  function clean2(value) {
25947
25954
  return typeof value === "string" ? value.trim() : "";
25948
25955
  }
25956
+ function normalizeSource(value) {
25957
+ if (!value || typeof value !== "object") return null;
25958
+ const raw = value;
25959
+ const classified = classifyGoogleOutboundLink(clean2(raw.rawUrl) || clean2(raw.url), "https://www.google.com/");
25960
+ if (!classified) return null;
25961
+ return {
25962
+ ...classified,
25963
+ url: clean2(raw.url) || classified.url,
25964
+ rawUrl: clean2(raw.rawUrl) || classified.rawUrl,
25965
+ resolvedUrl: clean2(raw.resolvedUrl) || classified.resolvedUrl,
25966
+ linkType: raw.linkType ?? classified.linkType,
25967
+ resolutionStatus: raw.resolutionStatus ?? classified.resolutionStatus,
25968
+ title: clean2(raw.title) || null,
25969
+ site: clean2(raw.site) || null
25970
+ };
25971
+ }
25972
+ function mergeSources(...groups) {
25973
+ const byRawUrl = /* @__PURE__ */ new Map();
25974
+ for (const value of groups.flat()) {
25975
+ const source = normalizeSource(value);
25976
+ if (!source) continue;
25977
+ const existing = byRawUrl.get(source.rawUrl);
25978
+ if (!existing || source.resolutionStatus === "resolved" && existing.resolutionStatus !== "resolved") {
25979
+ byRawUrl.set(source.rawUrl, source);
25980
+ } else {
25981
+ byRawUrl.set(source.rawUrl, { ...existing, title: existing.title ?? source.title, site: existing.site ?? source.site });
25982
+ }
25983
+ }
25984
+ return [...byRawUrl.values()];
25985
+ }
25949
25986
  function durableRecordFromFlat(value) {
25950
25987
  if (!value || typeof value !== "object") return null;
25951
25988
  const row = value;
25952
25989
  const question = clean2(row.question);
25953
25990
  if (!normalizePaaQuestion(question)) return null;
25954
- const sourceCite = clean2(row.source_cite);
25955
- const sourceTitle = clean2(row.source_title);
25956
- const sourceSite = clean2(row.source_site);
25957
- return {
25991
+ const durable = value;
25992
+ const sourceCite = clean2(row.source_cite) || clean2(durable.sourceCite);
25993
+ const sourceTitle = clean2(row.source_title) || clean2(durable.sourceTitle);
25994
+ const sourceSite = clean2(row.source_site) || clean2(durable.sourceSite);
25995
+ const legacySource = sourceCite ? normalizeSource({ title: sourceTitle, site: sourceSite, url: sourceCite, rawUrl: sourceCite }) : null;
25996
+ const sources = mergeSources(Array.isArray(row.sources) ? row.sources : [], legacySource ? [legacySource] : []);
25997
+ const primary = sources[0];
25998
+ return {
25999
+ questionId: clean2(row.question_id) || clean2(durable.questionId) || questionIdFor(question),
26000
+ googleLinkId: clean2(row.google_link_id) || clean2(durable.googleLinkId) || null,
26001
+ googleEvidenceId: clean2(row.google_evidence_id) || clean2(durable.googleEvidenceId) || null,
25958
26002
  question,
25959
26003
  answer: clean2(row.answer),
25960
- sourceTitle,
25961
- sourceSite,
25962
- sourceCite,
25963
- sources: sourceCite ? [{ title: sourceTitle, site: sourceSite, url: sourceCite }] : [],
26004
+ sourceTitle: primary?.title ?? sourceTitle,
26005
+ sourceSite: primary?.site ?? sourceSite,
26006
+ sourceCite: primary?.url ?? sourceCite,
26007
+ sources: mergeSources(sources, Array.isArray(durable.sources) ? durable.sources : []),
25964
26008
  depth: typeof row.depth === "number" && Number.isFinite(row.depth) ? row.depth : 1,
25965
26009
  parentQuestion: clean2(row.parent_question) || null
25966
26010
  };
25967
26011
  }
25968
26012
  function completenessRank(record2) {
25969
26013
  const answer = Boolean(record2.answer.trim());
25970
- const source = Boolean(record2.sourceCite.trim());
26014
+ const source = record2.sources.length > 0 || Boolean(record2.sourceCite.trim());
25971
26015
  return answer && source ? 3 : answer ? 2 : source ? 1 : 0;
25972
26016
  }
25973
26017
  function sameRecord(left, right) {
25974
- return left.question === right.question && left.answer === right.answer && left.sourceTitle === right.sourceTitle && left.sourceSite === right.sourceSite && left.sourceCite === right.sourceCite && left.depth === right.depth && left.parentQuestion === right.parentQuestion;
26018
+ return left.questionId === right.questionId && left.googleLinkId === right.googleLinkId && left.googleEvidenceId === right.googleEvidenceId && left.question === right.question && left.answer === right.answer && left.sourceTitle === right.sourceTitle && left.sourceSite === right.sourceSite && left.sourceCite === right.sourceCite && left.depth === right.depth && left.parentQuestion === right.parentQuestion && JSON.stringify(left.sources) === JSON.stringify(right.sources);
25975
26019
  }
25976
26020
  function fillMissing(primary, fallback) {
25977
- const sourceCite = primary.sourceCite || fallback.sourceCite;
25978
- const sourceTitle = primary.sourceTitle || fallback.sourceTitle;
25979
- const sourceSite = primary.sourceSite || fallback.sourceSite;
26021
+ const sources = mergeSources(primary.sources, fallback.sources);
26022
+ const primarySource = sources[0];
26023
+ const sourceCite = primarySource?.url || primary.sourceCite || fallback.sourceCite;
26024
+ const sourceTitle = primarySource?.title || primary.sourceTitle || fallback.sourceTitle;
26025
+ const sourceSite = primarySource?.site || primary.sourceSite || fallback.sourceSite;
25980
26026
  return {
25981
26027
  ...primary,
26028
+ questionId: primary.questionId || fallback.questionId,
26029
+ googleLinkId: primary.googleLinkId || fallback.googleLinkId,
26030
+ googleEvidenceId: primary.googleEvidenceId || fallback.googleEvidenceId,
25982
26031
  question: primary.question || fallback.question,
25983
26032
  answer: primary.answer || fallback.answer,
25984
26033
  sourceTitle,
25985
26034
  sourceSite,
25986
26035
  sourceCite,
25987
- sources: sourceCite ? [{ title: sourceTitle, site: sourceSite, url: sourceCite }] : [],
26036
+ sources,
25988
26037
  depth: Number.isFinite(primary.depth) ? primary.depth : fallback.depth,
25989
26038
  parentQuestion: primary.parentQuestion || fallback.parentQuestion
25990
26039
  };
@@ -26003,23 +26052,28 @@ function mergePaaProgressRecord(existing, incoming, attempt, observedAt) {
26003
26052
  function flatFromRecord(record2, seedQuery) {
26004
26053
  return {
26005
26054
  seed_query: seedQuery,
26055
+ question_id: record2.questionId,
26056
+ google_link_id: record2.googleLinkId ?? "",
26057
+ google_evidence_id: record2.googleEvidenceId ?? "",
26006
26058
  question: record2.question,
26007
26059
  answer: record2.answer,
26008
26060
  source_title: record2.sourceTitle,
26009
26061
  source_site: record2.sourceSite,
26010
26062
  source_cite: record2.sourceCite,
26063
+ sources: record2.sources,
26011
26064
  depth: record2.depth,
26012
26065
  parent_question: record2.parentQuestion ?? "",
26013
26066
  extracted_at: record2.observedAt
26014
26067
  };
26015
26068
  }
26016
26069
  var ProgressivePaaCheckpoint = class {
26017
- constructor(jobId2, durableMetadata, persist = checkpointJob, now = Date.now) {
26070
+ constructor(jobId2, durableMetadata, persist, now = Date.now, initialResult) {
26018
26071
  this.jobId = jobId2;
26019
26072
  this.durableMetadata = durableMetadata;
26020
26073
  this.persist = persist;
26021
26074
  this.now = now;
26022
26075
  this.lastPersistedAt = now();
26076
+ this.hydrate(initialResult);
26023
26077
  }
26024
26078
  jobId;
26025
26079
  durableMetadata;
@@ -26028,10 +26082,34 @@ var ProgressivePaaCheckpoint = class {
26028
26082
  records = /* @__PURE__ */ new Map();
26029
26083
  clickedQuestions = /* @__PURE__ */ new Set();
26030
26084
  interruptions = [];
26085
+ lifecycle = null;
26086
+ material = null;
26031
26087
  sequence = 0;
26032
26088
  attempt = 1;
26033
26089
  improvementsSinceWrite = 0;
26034
26090
  lastPersistedAt;
26091
+ hydrate(initialResult) {
26092
+ if (!initialResult || typeof initialResult !== "object") return;
26093
+ const progress = initialResult.progress;
26094
+ if (!progress || typeof progress !== "object") return;
26095
+ const value = progress;
26096
+ this.sequence = Number.isFinite(value.sequence) ? Math.max(0, Number(value.sequence)) : 0;
26097
+ this.attempt = Number.isFinite(value.attempt) ? Math.max(1, Number(value.attempt)) : 1;
26098
+ for (const question of value.clickedQuestions ?? []) if (clean2(question)) this.clickedQuestions.add(clean2(question));
26099
+ for (const interruption of value.interruptions ?? []) this.interruptions.push(interruption);
26100
+ if (value.lifecycle) this.lifecycle = { ...value.lifecycle };
26101
+ if (value.material) this.material = structuredClone(value.material);
26102
+ for (const raw of value.records ?? []) {
26103
+ const incoming = durableRecordFromFlat(raw);
26104
+ if (!incoming) continue;
26105
+ const record2 = raw;
26106
+ this.records.set(normalizePaaQuestion(incoming.question), {
26107
+ ...incoming,
26108
+ attempts: Array.isArray(record2.attempts) ? record2.attempts.filter(Number.isFinite) : [this.attempt],
26109
+ observedAt: clean2(record2.observedAt) || new Date(this.now()).toISOString()
26110
+ });
26111
+ }
26112
+ }
26035
26113
  setAttempt(attempt) {
26036
26114
  this.attempt = Math.max(1, attempt);
26037
26115
  }
@@ -26050,6 +26128,11 @@ var ProgressivePaaCheckpoint = class {
26050
26128
  }
26051
26129
  accept(snapshot) {
26052
26130
  this.sequence += 1;
26131
+ if (snapshot.lifecycle) this.lifecycle = { ...snapshot.lifecycle };
26132
+ if (snapshot.material) {
26133
+ this.material = structuredClone(snapshot.material);
26134
+ this.improvementsSinceWrite += 1;
26135
+ }
26053
26136
  for (const question of snapshot.clickedQuestions) if (clean2(question)) this.clickedQuestions.add(clean2(question));
26054
26137
  for (const raw of snapshot.records) {
26055
26138
  const incoming = durableRecordFromFlat(raw);
@@ -26074,6 +26157,9 @@ var ProgressivePaaCheckpoint = class {
26074
26157
  hasRows() {
26075
26158
  return this.records.size > 0;
26076
26159
  }
26160
+ hasPreservedMaterial() {
26161
+ return this.records.size > 0 || this.material !== null;
26162
+ }
26077
26163
  rows() {
26078
26164
  return [...this.records.values()];
26079
26165
  }
@@ -26089,7 +26175,9 @@ var ProgressivePaaCheckpoint = class {
26089
26175
  captchaObserved: false,
26090
26176
  solverCleared: false,
26091
26177
  error,
26092
- interruptions: [...this.interruptions]
26178
+ interruptions: [...this.interruptions],
26179
+ lifecycle: this.lifecycle ? { ...this.lifecycle } : null,
26180
+ material: this.material ? structuredClone(this.material) : null
26093
26181
  };
26094
26182
  }
26095
26183
  async flush(phase, options = {}) {
@@ -26106,11 +26194,12 @@ var ProgressivePaaCheckpoint = class {
26106
26194
  return true;
26107
26195
  }
26108
26196
  };
26109
- function partialTerminalResult(job, options, maxQuestions, progress) {
26197
+ function partialTerminalResult(job, options, maxQuestions, progress, recoveryCount) {
26110
26198
  const flat = progress.records.map((record2) => flatFromRecord(record2, options.query ?? job.query));
26111
26199
  const withoutAnswer = flat.filter((row) => !row.answer).length;
26112
- const withoutSource = flat.filter((row) => !row.source_cite).length;
26200
+ const withoutSource = flat.filter((row) => row.sources.length === 0 && !row.source_cite).length;
26113
26201
  const latestInterruption = progress.interruptions.at(-1);
26202
+ const material = progress.material;
26114
26203
  return {
26115
26204
  seed: options.query ?? job.query,
26116
26205
  location: options.location ?? null,
@@ -26118,22 +26207,40 @@ function partialTerminalResult(job, options, maxQuestions, progress) {
26118
26207
  totalQuestions: flat.length,
26119
26208
  tree: [],
26120
26209
  flat,
26121
- videos: [],
26122
- forums: [],
26123
- organicResults: [],
26124
- localPack: [],
26125
- whatPeopleSaying: [],
26126
- aiOverview: { detected: false, text: null, citations: [] },
26127
- aiMode: { detected: false, text: null, citations: [] },
26128
- entityIds: { entities: [], kgIds: [], cids: [], gcids: [] },
26129
- surface: "unknown",
26210
+ videos: material?.videos ?? [],
26211
+ forums: material?.forums ?? [],
26212
+ organicResults: material?.organicResults ?? [],
26213
+ localPack: material?.localPack ?? [],
26214
+ whatPeopleSaying: material?.whatPeopleSaying ?? [],
26215
+ aiOverview: material?.aiOverview ?? { detected: false, text: null, citations: [] },
26216
+ aiMode: material?.aiMode ?? { detected: false, text: null, citations: [] },
26217
+ entityIds: material?.entityIds ?? { entities: [], kgIds: [], cids: [], gcids: [] },
26218
+ surface: material?.surface ?? "unknown",
26130
26219
  stats: { seed: options.query ?? job.query, totalQuestions: flat.length, maxDepthReached: 1, durationMs: 0, errorCount: 1 },
26131
26220
  diagnostics: {
26132
26221
  completionStatus: "paa_found",
26133
26222
  problem: null,
26134
26223
  resultQuality: "partial",
26135
26224
  degradedResult: false,
26136
- retryRecommended: false,
26225
+ retryRecommended: true,
26226
+ paaLifecycle: {
26227
+ ...progress.lifecycle ?? {
26228
+ requestedQuestions: maxQuestions,
26229
+ questionsFound: flat.length,
26230
+ targetStatus: flat.length >= maxQuestions ? "met" : "not_met",
26231
+ materialStatus: "partial",
26232
+ knownQuestions: flat.length,
26233
+ processedQuestions: progress.clickedQuestions.length,
26234
+ failedInteractions: 0,
26235
+ noGrowthConfirmations: 0
26236
+ },
26237
+ questionsFound: flat.length,
26238
+ targetStatus: flat.length >= maxQuestions ? "met" : "not_met",
26239
+ discoveryStatus: "recovery_exhausted",
26240
+ materialStatus: "partial",
26241
+ completionReason: "Automatic runtime recovery was exhausted; all preserved material is returned.",
26242
+ automaticRetries: recoveryCount + progress.interruptions.filter((value) => value.willRetry).length
26243
+ },
26137
26244
  completeness: {
26138
26245
  paaWithoutAnswer: withoutAnswer,
26139
26246
  paaWithoutSource: withoutSource,
@@ -26143,7 +26250,7 @@ function partialTerminalResult(job, options, maxQuestions, progress) {
26143
26250
  paaReturned: flat.length,
26144
26251
  paaUnique: flat.length
26145
26252
  },
26146
- warnings: [{ code: "paa_partial", surface: "paa", message: "Provider attempts ended before the requested PAA capture completed.", retryable: false }],
26253
+ warnings: [{ code: "paa_partial", surface: "paa", message: "Automatic recovery ended before frontier exhaustion could be proven; preserved material is returned.", retryable: true }],
26147
26254
  ...latestInterruption && ["browser_session_interrupted", "request_aborted", "timeout"].includes(latestInterruption.outcome) ? { interruption: { code: latestInterruption.outcome, reasonSource: latestInterruption.reasonSource, message: null } } : {}
26148
26255
  },
26149
26256
  progress,
@@ -26171,21 +26278,36 @@ async function settleCompletedJob(jobId2) {
26171
26278
  }
26172
26279
  async function runPaaHarvestJob(jobId2) {
26173
26280
  const invocationStartedAt = Date.now();
26174
- const job = await claimDurablePaaJob(jobId2);
26175
- if (!job) return { jobId: jobId2, status: "missing_or_not_paa" };
26281
+ const claim = await claimDurablePaaJob(jobId2);
26282
+ if (!claim) return { jobId: jobId2, status: "missing_or_not_paa" };
26283
+ const { job, leaseToken, recoveryCount } = claim;
26176
26284
  if (job.status === "done" || job.status === "failed" || job.status === "cancelled") {
26177
26285
  await settleCompletedJob(job.id);
26178
26286
  return { jobId: job.id, status: job.status };
26179
26287
  }
26288
+ if (!leaseToken) return { jobId: job.id, status: "missing_lease" };
26180
26289
  const options = job.options;
26181
26290
  const maxQuestions = Math.max(1, Math.min(100, Number(options.maxQuestions ?? 30)));
26182
26291
  const timeoutBudget = harvestTimeoutBudget(maxQuestions);
26183
26292
  const workBudgetMs = Math.max(1, timeoutBudget.serverMs - CLEANUP_RESERVE_MS);
26184
- const progress = new ProgressivePaaCheckpoint(job.id, {
26185
- invocationBudgetMs: timeoutBudget.serverMs,
26186
- workBudgetMs,
26187
- cleanupReserveMs: CLEANUP_RESERVE_MS
26188
- });
26293
+ const progress = new ProgressivePaaCheckpoint(
26294
+ job.id,
26295
+ {
26296
+ invocationBudgetMs: timeoutBudget.serverMs,
26297
+ workBudgetMs,
26298
+ cleanupReserveMs: CLEANUP_RESERVE_MS,
26299
+ recoveryCount
26300
+ },
26301
+ (_id, result2) => checkpointDurablePaaJob(job.id, leaseToken, result2),
26302
+ Date.now,
26303
+ job.result
26304
+ );
26305
+ const heartbeat = setInterval(() => {
26306
+ void renewDurablePaaLease(job.id, leaseToken).catch((error) => {
26307
+ console.error("[paa-harvest] lease heartbeat failed:", error instanceof Error ? error.message : String(error));
26308
+ });
26309
+ }, LEASE_HEARTBEAT_MS);
26310
+ heartbeat.unref?.();
26189
26311
  try {
26190
26312
  if (!brightDataSerpEnabled()) throw new Error("SERP browser service is not configured");
26191
26313
  await progress.flush("navigating", { force: true });
@@ -26238,8 +26360,12 @@ async function runPaaHarvestJob(jobId2) {
26238
26360
  const rawDiagnostics = harvestResult.diagnostics && typeof harvestResult.diagnostics === "object" ? harvestResult.diagnostics : {};
26239
26361
  const rawCompleteness = rawDiagnostics.completeness && typeof rawDiagnostics.completeness === "object" ? rawDiagnostics.completeness : {};
26240
26362
  const missingAnswers = mergedFlat.filter((row) => !row.answer).length;
26241
- const missingSources = mergedFlat.filter((row) => !row.source_cite).length;
26242
- const incomplete = mergedFlat.length > 0 && (mergedFlat.length < maxQuestions || missingAnswers > 0 || missingSources > 0);
26363
+ const missingSources = mergedFlat.filter((row) => row.sources.length === 0 && !row.source_cite).length;
26364
+ const rawLifecycle = rawDiagnostics.paaLifecycle && typeof rawDiagnostics.paaLifecycle === "object" ? rawDiagnostics.paaLifecycle : {};
26365
+ const discoveryStatus = typeof rawLifecycle.discoveryStatus === "string" ? rawLifecycle.discoveryStatus : "interrupted";
26366
+ const materialPartial = missingAnswers > 0 || missingSources > 0;
26367
+ const incomplete = discoveryStatus === "interrupted" || discoveryStatus === "recovery_exhausted" || materialPartial;
26368
+ const automaticRetries = recoveryCount + terminalProgress.interruptions.filter((value) => value.willRetry).length;
26243
26369
  const result2 = {
26244
26370
  ...harvestResult,
26245
26371
  totalQuestions: mergedFlat.length,
@@ -26247,7 +26373,17 @@ async function runPaaHarvestJob(jobId2) {
26247
26373
  stats: harvestResult.stats && typeof harvestResult.stats === "object" ? { ...harvestResult.stats, totalQuestions: mergedFlat.length } : harvestResult.stats,
26248
26374
  diagnostics: {
26249
26375
  ...rawDiagnostics,
26250
- ...incomplete ? { resultQuality: "partial", degradedResult: false, retryRecommended: true } : {},
26376
+ resultQuality: incomplete ? "partial" : "complete",
26377
+ degradedResult: false,
26378
+ retryRecommended: discoveryStatus === "interrupted" || discoveryStatus === "recovery_exhausted",
26379
+ paaLifecycle: {
26380
+ ...rawLifecycle,
26381
+ requestedQuestions: maxQuestions,
26382
+ questionsFound: mergedFlat.length,
26383
+ targetStatus: mergedFlat.length >= maxQuestions ? "met" : "not_met",
26384
+ materialStatus: materialPartial ? "partial" : "complete",
26385
+ automaticRetries
26386
+ },
26251
26387
  completeness: {
26252
26388
  ...rawCompleteness,
26253
26389
  paaWithoutAnswer: missingAnswers,
@@ -26267,7 +26403,7 @@ async function runPaaHarvestJob(jobId2) {
26267
26403
  rawDomArtifactStatus: "unavailable"
26268
26404
  }
26269
26405
  };
26270
- if (!await completeRunningJob(job.id, result2)) throw new Error("job_left_running_state_during_capture");
26406
+ if (!await completeDurablePaaJob(job.id, leaseToken, result2)) throw new Error("job_left_running_state_during_capture");
26271
26407
  await settleCompletedJob(job.id);
26272
26408
  if (job.callback_url) await deliverWebhook(job.callback_url, { job_id: job.id, status: "done", result: result2 });
26273
26409
  return { jobId: job.id, status: "done", totalQuestions: questionCount(result2), partial: incomplete, artifactStored: false };
@@ -26276,16 +26412,16 @@ async function runPaaHarvestJob(jobId2) {
26276
26412
  const privateError = serializeHarvestProblem(problem);
26277
26413
  await progress.flush("failed", { force: true, error: privateError }).catch(() => {
26278
26414
  });
26279
- if (progress.hasRows()) {
26415
+ if (progress.hasPreservedMaterial()) {
26280
26416
  const terminalProgress = progress.checkpoint("failed", privateError);
26281
- const result2 = partialTerminalResult(job, options, maxQuestions, terminalProgress);
26282
- if (await completeRunningJob(job.id, result2)) {
26417
+ const result2 = partialTerminalResult(job, options, maxQuestions, terminalProgress, recoveryCount);
26418
+ if (await completeDurablePaaJob(job.id, leaseToken, result2)) {
26283
26419
  await settleCompletedJob(job.id);
26284
26420
  if (job.callback_url) await deliverWebhook(job.callback_url, { job_id: job.id, status: "done", result: result2 });
26285
26421
  return { jobId: job.id, status: "done", totalQuestions: questionCount(result2), partial: true, artifactStored: false };
26286
26422
  }
26287
26423
  }
26288
- await failRunningJob(job.id, privateError, harvestProblemEnvelope(problem, { chargeStatus: "refund_pending" }));
26424
+ await failDurablePaaJob(job.id, leaseToken, privateError, harvestProblemEnvelope(problem, { chargeStatus: "refund_pending" }));
26289
26425
  await settlePaaHarvestJob(job.id).then(async (settled) => {
26290
26426
  if (!settled) return;
26291
26427
  const settledJob = await getJob(job.id);
@@ -26303,6 +26439,8 @@ async function runPaaHarvestJob(jobId2) {
26303
26439
  });
26304
26440
  }
26305
26441
  throw error;
26442
+ } finally {
26443
+ clearInterval(heartbeat);
26306
26444
  }
26307
26445
  }
26308
26446
 
@@ -26501,22 +26639,48 @@ function recoverableRecords(result2) {
26501
26639
  questions.set(question, record2);
26502
26640
  continue;
26503
26641
  }
26504
- const completeness = (value) => Number(Boolean(String(value.answer ?? "").trim())) * 2 + Number(Boolean(String(value.sourceCite ?? value.source_cite ?? "").trim()));
26505
- if (completeness(record2) >= completeness(existing)) questions.set(question, record2);
26642
+ const sourcesOf = (value) => Array.isArray(value.sources) ? value.sources.filter((source) => source && typeof source === "object") : [];
26643
+ const completeness = (value) => {
26644
+ const sources = sourcesOf(value);
26645
+ return Number(Boolean(String(value.answer ?? "").trim())) * 4 + Number(sources.length > 0 || Boolean(String(value.sourceCite ?? value.source_cite ?? "").trim())) * 2 + sources.filter((source) => source.resolutionStatus === "resolved").length;
26646
+ };
26647
+ const preferred = completeness(record2) >= completeness(existing) ? record2 : existing;
26648
+ const fallback = preferred === record2 ? existing : record2;
26649
+ const mergedSources = /* @__PURE__ */ new Map();
26650
+ for (const source of [...sourcesOf(fallback), ...sourcesOf(preferred)]) {
26651
+ const key2 = String(source.rawUrl ?? source.url ?? "");
26652
+ if (!key2) continue;
26653
+ const current = mergedSources.get(key2);
26654
+ if (!current || source.resolutionStatus === "resolved") mergedSources.set(key2, source);
26655
+ }
26656
+ questions.set(question, {
26657
+ ...fallback,
26658
+ ...preferred,
26659
+ questionId: String(preferred.questionId ?? fallback.questionId ?? questionIdFor(String(preferred.question ?? fallback.question ?? ""))),
26660
+ googleLinkId: preferred.googleLinkId ?? fallback.googleLinkId ?? null,
26661
+ googleEvidenceId: preferred.googleEvidenceId ?? fallback.googleEvidenceId ?? null,
26662
+ sources: [...mergedSources.values()]
26663
+ });
26506
26664
  }
26507
26665
  return [...questions.values()];
26508
26666
  }
26509
- function stalePartialResult(result2, records, seedQuery) {
26667
+ function stalePartialResult(result2, records, seedQuery, recoveryCount) {
26510
26668
  const current = result2 && typeof result2 === "object" ? result2 : {};
26669
+ const progress = current.progress && typeof current.progress === "object" ? current.progress : {};
26670
+ const material = progress.material && typeof progress.material === "object" ? progress.material : {};
26511
26671
  const diagnostics = current.diagnostics && typeof current.diagnostics === "object" ? current.diagnostics : {};
26512
26672
  const completeness = diagnostics.completeness && typeof diagnostics.completeness === "object" ? diagnostics.completeness : {};
26513
26673
  const flat = records.map((record2) => ({
26514
26674
  seed_query: typeof record2.seed_query === "string" ? record2.seed_query : seedQuery,
26675
+ question_id: typeof record2.question_id === "string" ? record2.question_id : typeof record2.questionId === "string" ? record2.questionId : questionIdFor(String(record2.question ?? "")),
26676
+ google_link_id: typeof record2.google_link_id === "string" ? record2.google_link_id : typeof record2.googleLinkId === "string" ? record2.googleLinkId : "",
26677
+ google_evidence_id: typeof record2.google_evidence_id === "string" ? record2.google_evidence_id : typeof record2.googleEvidenceId === "string" ? record2.googleEvidenceId : "",
26515
26678
  question: typeof record2.question === "string" ? record2.question : "",
26516
26679
  answer: typeof record2.answer === "string" ? record2.answer : "",
26517
26680
  source_title: typeof record2.source_title === "string" ? record2.source_title : typeof record2.sourceTitle === "string" ? record2.sourceTitle : "",
26518
26681
  source_site: typeof record2.source_site === "string" ? record2.source_site : typeof record2.sourceSite === "string" ? record2.sourceSite : "",
26519
26682
  source_cite: typeof record2.source_cite === "string" ? record2.source_cite : typeof record2.sourceCite === "string" ? record2.sourceCite : "",
26683
+ sources: Array.isArray(record2.sources) ? record2.sources : [],
26520
26684
  depth: typeof record2.depth === "number" ? record2.depth : 1,
26521
26685
  parent_question: typeof record2.parent_question === "string" ? record2.parent_question : typeof record2.parentQuestion === "string" ? record2.parentQuestion : "",
26522
26686
  extracted_at: typeof record2.extracted_at === "string" ? record2.extracted_at : typeof record2.observedAt === "string" ? record2.observedAt : (/* @__PURE__ */ new Date()).toISOString()
@@ -26527,6 +26691,15 @@ function stalePartialResult(result2, records, seedQuery) {
26527
26691
  totalQuestions: uniqueCount,
26528
26692
  flat,
26529
26693
  tree: Array.isArray(current.tree) ? current.tree : [],
26694
+ videos: Array.isArray(material.videos) ? material.videos : current.videos ?? [],
26695
+ forums: Array.isArray(material.forums) ? material.forums : current.forums ?? [],
26696
+ organicResults: Array.isArray(material.organicResults) ? material.organicResults : current.organicResults ?? [],
26697
+ localPack: Array.isArray(material.localPack) ? material.localPack : current.localPack ?? [],
26698
+ whatPeopleSaying: Array.isArray(material.whatPeopleSaying) ? material.whatPeopleSaying : current.whatPeopleSaying ?? [],
26699
+ aiOverview: material.aiOverview ?? current.aiOverview,
26700
+ aiMode: material.aiMode ?? current.aiMode,
26701
+ entityIds: material.entityIds ?? current.entityIds,
26702
+ surface: material.surface ?? current.surface,
26530
26703
  partial: true,
26531
26704
  diagnostics: {
26532
26705
  ...diagnostics,
@@ -26534,7 +26707,17 @@ function stalePartialResult(result2, records, seedQuery) {
26534
26707
  problem: null,
26535
26708
  resultQuality: "partial",
26536
26709
  degradedResult: false,
26537
- retryRecommended: false,
26710
+ retryRecommended: true,
26711
+ paaLifecycle: {
26712
+ ...diagnostics.paaLifecycle && typeof diagnostics.paaLifecycle === "object" ? diagnostics.paaLifecycle : {},
26713
+ requestedQuestions: Number(completeness.paaRequested ?? uniqueCount),
26714
+ questionsFound: uniqueCount,
26715
+ targetStatus: uniqueCount >= Number(completeness.paaRequested ?? Number.MAX_SAFE_INTEGER) ? "met" : "not_met",
26716
+ discoveryStatus: "recovery_exhausted",
26717
+ materialStatus: "partial",
26718
+ completionReason: "Automatic runtime recovery was exhausted; all preserved material is returned.",
26719
+ automaticRetries: recoveryCount
26720
+ },
26538
26721
  interruption: {
26539
26722
  code: "timeout",
26540
26723
  reasonSource: "client_runtime",
@@ -26553,7 +26736,16 @@ function stalePartialResult(result2, records, seedQuery) {
26553
26736
  }
26554
26737
  };
26555
26738
  }
26556
- async function reconcilePaaHarvestSettlements(limit = 25) {
26739
+ var MAX_RUNTIME_RECOVERIES = 2;
26740
+ async function dispatchRecovery(origin, jobId2) {
26741
+ if (!process.env.CRON_SECRET) return false;
26742
+ const response = await fetch(`${origin}/harvest/internal/resume/${encodeURIComponent(jobId2)}`, {
26743
+ method: "POST",
26744
+ headers: { Authorization: `Bearer ${process.env.CRON_SECRET}` }
26745
+ });
26746
+ return response.ok || response.status === 409;
26747
+ }
26748
+ async function reconcilePaaHarvestSettlements(limit = 25, origin) {
26557
26749
  let providerChecked = 0;
26558
26750
  let providerCompleted = 0;
26559
26751
  let providerFailed = 0;
@@ -26565,19 +26757,32 @@ async function reconcilePaaHarvestSettlements(limit = 25) {
26565
26757
  } catch {
26566
26758
  providerFailed = 1;
26567
26759
  }
26568
- const stale = await listStaleRunningDurablePaaJobs(limit);
26760
+ const stale = await listRecoverableDurablePaaJobs(limit);
26569
26761
  let stalePartial = 0;
26570
26762
  let staleFailed = 0;
26763
+ let requeued = 0;
26764
+ let redispatched = 0;
26571
26765
  for (const job of stale) {
26572
26766
  try {
26767
+ const recoveryCount = Math.max(0, Number(job.options.paaRecoveryCount ?? 0));
26768
+ if (job.status === "pending") {
26769
+ if (origin && await dispatchRecovery(origin, job.id)) redispatched += 1;
26770
+ continue;
26771
+ }
26772
+ if (recoveryCount < MAX_RUNTIME_RECOVERIES && await requeueExpiredDurablePaaJob(job.id, MAX_RUNTIME_RECOVERIES)) {
26773
+ requeued += 1;
26774
+ if (origin && await dispatchRecovery(origin, job.id)) redispatched += 1;
26775
+ continue;
26776
+ }
26573
26777
  const retained = recoverableRecords(job.result);
26574
- if (retained.length > 0) {
26575
- if (await completeRunningJob(job.id, stalePartialResult(job.result, retained, job.query))) stalePartial += 1;
26778
+ const progress = job.result && typeof job.result === "object" ? job.result.progress : void 0;
26779
+ if (retained.length > 0 || Boolean(progress?.material)) {
26780
+ if (await completeExpiredDurablePaaJob(job.id, stalePartialResult(job.result, retained, job.query, recoveryCount))) stalePartial += 1;
26576
26781
  continue;
26577
26782
  }
26578
- const privateError = "direct PAA invocation exceeded its platform safety window; no recoverable rows were retained";
26783
+ const privateError = "PAA runtime recovery was exhausted; no recoverable rows were retained";
26579
26784
  const problem = classifyHarvestProblem(new Error("harvest timeout"));
26580
- if (await failRunningJob(job.id, privateError, harvestProblemEnvelope(problem, { chargeStatus: "refund_pending" }))) {
26785
+ if (await failExpiredDurablePaaJob(job.id, privateError, harvestProblemEnvelope(problem, { chargeStatus: "refund_pending" }))) {
26581
26786
  staleFailed += 1;
26582
26787
  }
26583
26788
  } catch {
@@ -26607,6 +26812,8 @@ async function reconcilePaaHarvestSettlements(limit = 25) {
26607
26812
  checked: jobs.length,
26608
26813
  stalePartial,
26609
26814
  staleFailed,
26815
+ requeued,
26816
+ redispatched,
26610
26817
  settled,
26611
26818
  failed,
26612
26819
  providerChecked,
@@ -42270,7 +42477,7 @@ async function prepareCommonsEntity(input, user) {
42270
42477
  const duplicateCandidates = await potentialDuplicateCandidates({
42271
42478
  title,
42272
42479
  slug: slug4,
42273
- source: normalizeSource(input.source),
42480
+ source: normalizeSource2(input.source),
42274
42481
  maxCandidates: clampNumber(input.maxCandidates, 1, 20, 8)
42275
42482
  });
42276
42483
  const tagVocabulary = await commonsTagVocabulary();
@@ -42324,7 +42531,7 @@ async function validateCommonsEntity(input, user) {
42324
42531
  const description = optionalText2(input.description, 600);
42325
42532
  const slug4 = normalizeSlug(input.slug || title || "");
42326
42533
  const contract = commonsEntityProfileFor(input.entityType, input.seo?.schemaOrgType);
42327
- const source = normalizeSource(input.source);
42534
+ const source = normalizeSource2(input.source);
42328
42535
  const featuredImage = normalizeFeaturedImage(input.featuredImage);
42329
42536
  const normalizedFeaturedImageUrl = featuredImage?.url ? normalizeCommonsImageUrl(featuredImage.url) : null;
42330
42537
  const registeredFeaturedImage = normalizedFeaturedImageUrl ? await findCommonsHostedImageByUrl(normalizedFeaturedImageUrl) : null;
@@ -43066,7 +43273,7 @@ function normalizeSubmitInput(input) {
43066
43273
  entityType: normalizeEntityType(input.entityType),
43067
43274
  disambiguationName: optionalText2(input.disambiguationName, 240) ?? void 0,
43068
43275
  featuredImage: normalizeFeaturedImage(input.featuredImage),
43069
- source: normalizeSource(input.source),
43276
+ source: normalizeSource2(input.source),
43070
43277
  tags: uniqueStrings(input.tags ?? []).slice(0, 24),
43071
43278
  keywords: uniqueStrings(input.keywords ?? []).slice(0, 50),
43072
43279
  relatedEntities: normalizeRelatedEntities(input.relatedEntities),
@@ -43687,7 +43894,7 @@ function normalizeMedia(media, featuredImage) {
43687
43894
  return true;
43688
43895
  }).slice(0, 300);
43689
43896
  }
43690
- function normalizeSource(source) {
43897
+ function normalizeSource2(source) {
43691
43898
  return {
43692
43899
  originalUrl: optionalText2(source?.originalUrl, 2e3) ?? void 0,
43693
43900
  resolvedUrl: optionalText2(source?.resolvedUrl, 2e3) ?? void 0,
@@ -47156,7 +47363,7 @@ async function callMainOwnedExportPage(identity, input) {
47156
47363
  });
47157
47364
  }
47158
47365
  if (connection.providerConfigKey === "google-mail" && input.dataset === "emails") {
47159
- const { GmailService: GmailService2 } = await import("./gmail-service-TIU4V5JA.js");
47366
+ const { GmailService: GmailService2 } = await import("./gmail-service-HN57R2GS.js");
47160
47367
  const service = new GmailService2();
47161
47368
  const ownerId3 = createHash29("sha256").update(identity.toLowerCase()).digest("hex").slice(0, 24);
47162
47369
  const after = Math.floor(Date.parse(input.from) / 1e3);
@@ -61115,6 +61322,19 @@ async function checkHarvestLimits(user, reuseLockId) {
61115
61322
  }
61116
61323
  return null;
61117
61324
  }
61325
+ app.post("/harvest/internal/resume/:id", async (c) => {
61326
+ const secret = c.req.header("authorization");
61327
+ if (!process.env.CRON_SECRET || secret !== `Bearer ${process.env.CRON_SECRET}`) {
61328
+ return c.json({ error: "Unauthorized" }, 401);
61329
+ }
61330
+ const job = await getJob(c.req.param("id"));
61331
+ if (!job || job.options.executionOwner !== "direct" || job.options.serpOnly === true) {
61332
+ return c.json({ error: "PAA job not found" }, 404);
61333
+ }
61334
+ if (job.status !== "pending") return c.json({ job_id: job.id, status: job.status }, 409);
61335
+ scheduleDirectPaaHarvest(job.id);
61336
+ return c.json({ job_id: job.id, status: "pending", recovery_dispatched: true }, 202);
61337
+ });
61118
61338
  app.post("/harvest", auth5, async (c) => {
61119
61339
  const user = c.get("user");
61120
61340
  const raw = await c.req.json().catch(() => ({}));
@@ -61198,6 +61418,7 @@ app.post("/harvest", auth5, async (c) => {
61198
61418
  await createJobWithId(jobId2, user.id, options.query, {
61199
61419
  ...options,
61200
61420
  executionOwner: "direct",
61421
+ paaRecoveryCount: 0,
61201
61422
  billingHoldMc: harvestCost,
61202
61423
  billingDebitKey,
61203
61424
  ...requestFingerprint3 ? { requestFingerprint: requestFingerprint3 } : {}
@@ -62737,7 +62958,7 @@ app.get("/cron/tick", async (c) => {
62737
62958
  return c.json({ error: "Unauthorized" }, 401);
62738
62959
  }
62739
62960
  const startedAt = Date.now();
62740
- const { drainQueue } = await import("./worker-DR6R7J46.js");
62961
+ const { drainQueue } = await import("./worker-2BYACLNF.js");
62741
62962
  const budget = { maxJobs: 10, deadlineMs: startedAt + CRON_TICK_DRAIN_BUDGET_MS };
62742
62963
  const origin = `${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`;
62743
62964
  const unfinished = [];
@@ -62775,10 +62996,12 @@ app.get("/cron/tick", async (c) => {
62775
62996
  settleWithinTickBudget("siteExtractArtifactCleanup", unfinished, cleanupExpiredSiteExtractArtifacts(), { deleted: 0, store: "none" }),
62776
62997
  settleWithinTickBudget("siteExtractRedispatch", unfinished, redispatchFundedSiteExtractJobs(10), { checked: 0, dispatched: 0, failed: 0 }),
62777
62998
  settleWithinTickBudget("siteExtractReconcile", unfinished, reconcileSiteExtractSettlements(10), { checked: 0, settled: 0, failed: 0 }),
62778
- settleWithinTickBudget("paaReconcile", unfinished, reconcilePaaHarvestSettlements(10), {
62999
+ settleWithinTickBudget("paaReconcile", unfinished, reconcilePaaHarvestSettlements(10, origin), {
62779
63000
  checked: 0,
62780
63001
  stalePartial: 0,
62781
63002
  staleFailed: 0,
63003
+ requeued: 0,
63004
+ redispatched: 0,
62782
63005
  settled: 0,
62783
63006
  failed: 0,
62784
63007
  providerChecked: 0,
@@ -62859,8 +63082,8 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
62859
63082
  finishExtractJob: finishExtractJob2,
62860
63083
  terminalExtractJobStatus: terminalExtractJobStatus2,
62861
63084
  extractJobLimitInfo: extractJobLimitInfo2
62862
- } = await import("./site-extract-repository-B47JB62U.js");
62863
- const { assembleExtractArtifacts } = await import("./extract-bundle-OO74L67T.js");
63085
+ } = await import("./site-extract-repository-XKSVXSSY.js");
63086
+ const { assembleExtractArtifacts } = await import("./extract-bundle-P2DTUVY3.js");
62864
63087
  const existing = await getExtractJob2(jobId2);
62865
63088
  if (!existing) return c.json({ error: "job not found" }, 404);
62866
63089
  if (existing.status !== "failed") {
@@ -62887,7 +63110,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
62887
63110
  if (!updated) return c.json({ error: "job state changed during re-finalization" }, 409);
62888
63111
  let settlement = "already_settled_or_refunded";
62889
63112
  if (job.billedMc == null && job.userId != null) {
62890
- const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-B47JB62U.js");
63113
+ const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-XKSVXSSY.js");
62891
63114
  const heldMc = Number(job.options.heldMc ?? 0);
62892
63115
  const successful = await countSuccessfulPages2(jobId2);
62893
63116
  const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);