mcp-scraper 0.79.2 → 0.79.4

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 (38) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +1 -1
  3. package/dist/{analytics-repository-VEUDEPWI.js → analytics-repository-YOY5KMOD.js} +3 -3
  4. package/dist/bin/api-server.js +4 -4
  5. package/dist/bin/mcp-scraper-cli.js +1 -1
  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 +3 -3
  10. package/dist/{chunk-LB2GNACG.js → chunk-3423R23T.js} +3 -3
  11. package/dist/{chunk-NGLZ6UJR.js → chunk-5UAFBKJR.js} +1 -1
  12. package/dist/{chunk-B6L73AO4.js → chunk-6HAV7LCE.js} +3 -1
  13. package/dist/{chunk-Q5L2OCOQ.js → chunk-C5Z4OFKW.js} +1 -1
  14. package/dist/{chunk-KAGHJQFO.js → chunk-EQGTEHLZ.js} +1 -1
  15. package/dist/{chunk-6FL7ZZCF.js → chunk-ETQ7J2EB.js} +1 -1
  16. package/dist/{chunk-BTQ23KCT.js → chunk-F5GQJWZU.js} +1 -1
  17. package/dist/{chunk-DUWZQSSJ.js → chunk-HEUMDX4I.js} +1 -1
  18. package/dist/{chunk-OUBH6XMY.js → chunk-K3Z5AQYE.js} +1 -1
  19. package/dist/{chunk-WEEAXPWK.js → chunk-KNGTQD6U.js} +207 -36
  20. package/dist/{chunk-2RTELL6M.js → chunk-N63ZFFU2.js} +1 -1
  21. package/dist/{chunk-47ZCUBM5.js → chunk-NVUKO5NN.js} +12 -1
  22. package/dist/{chunk-XUM2LEGU.js → chunk-PQVLX5WT.js} +3 -3
  23. package/dist/{chunk-JECPQBLZ.js → chunk-RUBI3OXR.js} +138 -11
  24. package/dist/{chunk-SL7YVA4S.js → chunk-YBR32LOD.js} +1 -1
  25. package/dist/{chunk-OU4FV2RP.js → chunk-YXNDOQXN.js} +49 -0
  26. package/dist/{db-TA567JLA.js → db-Z34LPZNR.js} +5 -1
  27. package/dist/{extract-bundle-I2GNC25L.js → extract-bundle-S3ZGNFML.js} +3 -3
  28. package/dist/{gmail-service-GGQVJUC3.js → gmail-service-YFS2OV4S.js} +4 -4
  29. package/dist/index.cjs +205 -34
  30. package/dist/index.d.cts +35 -0
  31. package/dist/index.d.ts +35 -0
  32. package/dist/index.js +3 -3
  33. package/dist/{lead-list-enrichment-repository-5LEWW4X7.js → lead-list-enrichment-repository-36RPVV6N.js} +2 -2
  34. package/dist/{location-data-repository-AHKDXTEQ.js → location-data-repository-WPRG62GE.js} +2 -2
  35. package/dist/{server-5OSHEEVT.js → server-BMQKWVCZ.js} +73 -28
  36. package/dist/{site-extract-repository-KF736IR6.js → site-extract-repository-WAJMIDPJ.js} +2 -2
  37. package/dist/{worker-2TYCYFPM.js → worker-3LUFQWWJ.js} +5 -5
  38. package/package.json +2 -2
@@ -8,7 +8,7 @@ import {
8
8
  currentCostContext,
9
9
  recordKernelSession,
10
10
  runWithCostContext
11
- } from "./chunk-NGLZ6UJR.js";
11
+ } from "./chunk-5UAFBKJR.js";
12
12
  import {
13
13
  DEFAULT_PROXY_MODE,
14
14
  HarvestOptionsSchema,
@@ -24,7 +24,7 @@ import {
24
24
  RECAPTCHA_INSTRUCTIONS,
25
25
  RequestAbortedError,
26
26
  sanitizeVendorName
27
- } from "./chunk-OU4FV2RP.js";
27
+ } from "./chunk-YXNDOQXN.js";
28
28
 
29
29
  // src/driver/BrowserDriver.ts
30
30
  import { chromium } from "playwright-extra";
@@ -2263,17 +2263,22 @@ async function resolveGoogleOutboundLinks(page, values, options = {}) {
2263
2263
  // src/extractor/PAAExtractor.ts
2264
2264
  var PAA_ANSWER_PLACEHOLDER_PATTERN = String.raw`^(?:an error has occurred(?:[.!…]\s*)?(?:please try again later[.!…]?)?|loading|please wait|try again|something went wrong)[.!…\s]*$`;
2265
2265
  var PAA_CLICK_EARLIEST_CONFIRMATION_MS = 700;
2266
+ var PAA_CLICK_MID_CONFIRMATION_MS = 1e3;
2266
2267
  var PAA_CLICK_ATTEMPT_BUDGET_MS = 1400;
2268
+ var PAA_CLICK_DISPATCH_TIMEOUT_MS = 900;
2267
2269
  var PAA_FRONTIER_BATCH_SIZE = 3;
2270
+ var MAX_RETAINED_INTERACTION_OBSERVATIONS = 250;
2268
2271
  var PAAExtractor = class {
2269
- constructor(driver, reporter, onPaaProgress) {
2272
+ constructor(driver, reporter, onPaaProgress, onProviderSession) {
2270
2273
  this.driver = driver;
2271
2274
  this.reporter = reporter;
2272
2275
  this.onPaaProgress = onPaaProgress;
2276
+ this.onProviderSession = onProviderSession;
2273
2277
  }
2274
2278
  driver;
2275
2279
  reporter;
2276
2280
  onPaaProgress;
2281
+ onProviderSession;
2277
2282
  completeness = {
2278
2283
  paaWithoutAnswer: 0,
2279
2284
  paaWithoutSource: 0,
@@ -2288,6 +2293,7 @@ var PAAExtractor = class {
2288
2293
  clickedQuestions = /* @__PURE__ */ new Set();
2289
2294
  paaLifecycle = this.emptyPaaLifecycle(0);
2290
2295
  checkpointMaterial = null;
2296
+ interactionObservations = [];
2291
2297
  emptyPaaLifecycle(requestedQuestions) {
2292
2298
  return {
2293
2299
  requestedQuestions,
@@ -2300,7 +2306,12 @@ var PAAExtractor = class {
2300
2306
  knownQuestions: 0,
2301
2307
  processedQuestions: 0,
2302
2308
  failedInteractions: 0,
2303
- noGrowthConfirmations: 0
2309
+ noGrowthConfirmations: 0,
2310
+ visibleControls: 0,
2311
+ eligibleControls: 0,
2312
+ dispatchedInteractions: 0,
2313
+ confirmedInteractions: 0,
2314
+ unconfirmedInteractions: 0
2304
2315
  };
2305
2316
  }
2306
2317
  emptyLinkDiagnostics() {
@@ -2562,7 +2573,11 @@ var PAAExtractor = class {
2562
2573
  phase,
2563
2574
  records: this.collectedRows.map((row) => ({ ...row })),
2564
2575
  lifecycle: { ...this.paaLifecycle },
2565
- ...this.checkpointMaterial ? { material: structuredClone(this.checkpointMaterial) } : {}
2576
+ ...this.checkpointMaterial ? { material: structuredClone(this.checkpointMaterial) } : {},
2577
+ interactions: this.interactionObservations.map((observation) => ({
2578
+ ...observation,
2579
+ confirmationSignals: [...observation.confirmationSignals]
2580
+ }))
2566
2581
  };
2567
2582
  try {
2568
2583
  await this.onPaaProgress(snapshot);
@@ -2643,6 +2658,7 @@ var PAAExtractor = class {
2643
2658
  const seenQs = /* @__PURE__ */ new Set();
2644
2659
  const orderedQs = [];
2645
2660
  this.clickedQuestions = /* @__PURE__ */ new Set();
2661
+ this.interactionObservations = [];
2646
2662
  const abandonedNeverReclick = /* @__PURE__ */ new Set();
2647
2663
  const clickAttempts = /* @__PURE__ */ new Map();
2648
2664
  const processedQuestions = /* @__PURE__ */ new Set();
@@ -2650,6 +2666,7 @@ var PAAExtractor = class {
2650
2666
  const capturedItems = /* @__PURE__ */ new Map();
2651
2667
  const MAX_CLICK_ATTEMPTS = 2;
2652
2668
  const EXHAUSTION_CONFIRMATIONS = 3;
2669
+ let batchNumber = 0;
2653
2670
  const itemArgs = {
2654
2671
  sel: PAASelectors.item,
2655
2672
  dataQ: PAASelectors.itemDataQ,
@@ -2659,12 +2676,30 @@ var PAAExtractor = class {
2659
2676
  expandedClass: PAASelectors.expandedClass,
2660
2677
  clickTarget: PAASelectors.clickTarget,
2661
2678
  answerPlaceholderPattern: PAA_ANSWER_PLACEHOLDER_PATTERN,
2662
- questionsOnly: options.questionsOnly
2679
+ questionsOnly: options.questionsOnly,
2680
+ sourceSel: "a[href]"
2663
2681
  };
2664
2682
  const readItemStates = () => page.evaluate(
2665
2683
  ({ sel, dataQ, dataInitQ, questionEl, expandedClass, clickTarget, answerSel, answerPlaceholderPattern, questionsOnly }) => {
2684
+ const cleanVisibleMaterial = (element) => {
2685
+ if (!element) return "";
2686
+ const parts = [];
2687
+ for (const node of element.childNodes) {
2688
+ if (node.nodeType === Node.TEXT_NODE) {
2689
+ const text2 = node.textContent?.trim();
2690
+ if (text2) parts.push(text2);
2691
+ continue;
2692
+ }
2693
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
2694
+ const child = node;
2695
+ if (child.tagName === "STYLE" || child.tagName === "SCRIPT" || child.getAttribute("aria-hidden") === "true") continue;
2696
+ const text = cleanVisibleMaterial(child);
2697
+ if (text) parts.push(text);
2698
+ }
2699
+ return parts.join(" ").replace(/\s+/g, " ").trim();
2700
+ };
2666
2701
  return Array.from(document.querySelectorAll(sel)).map((el, index) => {
2667
- const answerText = (el.querySelector(answerSel)?.textContent ?? "").replace(/\s+/g, " ").trim();
2702
+ const answerText = cleanVisibleMaterial(el.querySelector(answerSel)).replace(/An AI Overview is not available for this search/gi, " ").replace(/Can't generate an AI overview right now\.?\s*Try again later\.?/gi, " ").replace(/\bAI Overview\b/gi, " ").replace(/\s+/g, " ").trim();
2668
2703
  const expanded = el.classList.contains(expandedClass) || el.querySelector(clickTarget)?.getAttribute("aria-expanded") === "true";
2669
2704
  const answerReady = answerText.length >= 10 && !new RegExp(answerPlaceholderPattern, "i").test(answerText);
2670
2705
  return {
@@ -2677,34 +2712,133 @@ var PAAExtractor = class {
2677
2712
  },
2678
2713
  itemArgs
2679
2714
  );
2680
- const expandOneItemSerially = async (q, beforeCount, itemIndex, wasExpanded) => {
2715
+ const expandOneItemSerially = async (q, beforeCount, itemIndex, wasExpanded, controlAttempt, batchNumber2) => {
2681
2716
  const clickStartedMs = Date.now();
2717
+ const baseObservation = {
2718
+ id: `${batchNumber2}:${questionIdFor(q)}:${controlAttempt}`,
2719
+ questionId: questionIdFor(q),
2720
+ question: q,
2721
+ batchNumber: batchNumber2,
2722
+ controlAttempt,
2723
+ observedAt: new Date(clickStartedMs).toISOString(),
2724
+ beforeQuestionCount: beforeCount,
2725
+ expandedBefore: wasExpanded
2726
+ };
2682
2727
  const clickTarget = itemIndex >= 0 ? page.locator(itemArgs.sel).nth(itemIndex).locator(itemArgs.clickTarget) : null;
2728
+ let dispatchErrorCode = null;
2683
2729
  const clicked = clickTarget ? await (async () => {
2684
2730
  if (wasExpanded) {
2685
- await clickTarget.click({ timeout: 2e3, force: true });
2686
- await page.waitForTimeout(250);
2731
+ await clickTarget.click({ timeout: PAA_CLICK_DISPATCH_TIMEOUT_MS, force: true });
2732
+ await page.waitForTimeout(120);
2687
2733
  }
2688
- await clickTarget.click({ timeout: 2e3, force: true });
2734
+ await clickTarget.click({ timeout: PAA_CLICK_DISPATCH_TIMEOUT_MS, force: true });
2689
2735
  return true;
2690
- })().catch(() => false) : false;
2691
- if (!clicked) return { dispatched: false, confirmed: false };
2692
- const remainingConfirmationMs = Math.max(100, PAA_CLICK_ATTEMPT_BUDGET_MS - (Date.now() - clickStartedMs));
2693
- const confirmed = await page.waitForFunction(
2694
- ({ sel, dataQ, dataInitQ, questionEl, expandedClass, clickTarget: clickTarget2, answerSel, answerPlaceholderPattern, questionsOnly, target, min, earliestConfirmationAt }) => {
2695
- if (Date.now() < earliestConfirmationAt) return false;
2696
- const items = Array.from(document.querySelectorAll(sel));
2697
- const item = items.find((element) => (element.getAttribute(dataQ) || element.getAttribute(dataInitQ) || element.querySelector(questionEl)?.innerText?.trim()) === target);
2698
- if (!item) return false;
2699
- const expanded = item.classList.contains(expandedClass) || item.querySelector(clickTarget2)?.getAttribute("aria-expanded") === "true";
2700
- if (questionsOnly) return expanded || items.length > min;
2701
- const answerText = (item.querySelector(answerSel)?.textContent ?? "").replace(/\s+/g, " ").trim();
2702
- return answerText.length >= 10 && !new RegExp(answerPlaceholderPattern, "i").test(answerText);
2703
- },
2704
- { ...itemArgs, target: q, min: beforeCount, earliestConfirmationAt: clickStartedMs + PAA_CLICK_EARLIEST_CONFIRMATION_MS },
2705
- { timeout: remainingConfirmationMs, polling: 100 }
2706
- ).catch(() => null);
2707
- return { dispatched: true, confirmed: Boolean(confirmed) };
2736
+ })().catch((error) => {
2737
+ dispatchErrorCode = error instanceof Error && (error.name === "TimeoutError" || /timeout/i.test(error.message)) ? "click_ack_timeout" : "click_failed";
2738
+ return false;
2739
+ }) : false;
2740
+ const dispatchDurationMs = Date.now() - clickStartedMs;
2741
+ if (!clicked && dispatchErrorCode !== "click_ack_timeout") {
2742
+ const observation2 = {
2743
+ ...baseObservation,
2744
+ dispatched: false,
2745
+ outcome: "dispatch_failed",
2746
+ confirmationSignals: [],
2747
+ confirmationCheckpointMs: null,
2748
+ confirmedAtMs: null,
2749
+ dispatchDurationMs,
2750
+ totalDurationMs: dispatchDurationMs,
2751
+ afterQuestionCount: beforeCount,
2752
+ newQuestionCount: 0,
2753
+ expandedAfter: wasExpanded,
2754
+ answerObserved: false,
2755
+ sourceCount: 0,
2756
+ errorCode: itemIndex < 0 ? "control_missing" : dispatchErrorCode ?? "click_failed"
2757
+ };
2758
+ return { dispatched: false, confirmed: false, observation: observation2 };
2759
+ }
2760
+ let latest = {
2761
+ exists: true,
2762
+ expanded: wasExpanded,
2763
+ answerReady: false,
2764
+ sourceCount: 0,
2765
+ questionCount: beforeCount
2766
+ };
2767
+ let signals = [];
2768
+ let confirmationCheckpointMs = null;
2769
+ let confirmedAtMs = null;
2770
+ let confirmed = false;
2771
+ for (const checkpointMs of [PAA_CLICK_EARLIEST_CONFIRMATION_MS, PAA_CLICK_MID_CONFIRMATION_MS, PAA_CLICK_ATTEMPT_BUDGET_MS]) {
2772
+ const remainingMs = clickStartedMs + checkpointMs - Date.now();
2773
+ if (remainingMs > 0) await page.waitForTimeout(remainingMs);
2774
+ latest = await page.evaluate(
2775
+ ({ sel, dataQ, dataInitQ, questionEl, expandedClass, clickTarget: clickTarget2, answerSel, answerPlaceholderPattern, sourceSel, target }) => {
2776
+ const cleanVisibleMaterial = (element) => {
2777
+ if (!element) return "";
2778
+ const parts = [];
2779
+ for (const node of element.childNodes) {
2780
+ if (node.nodeType === Node.TEXT_NODE) {
2781
+ const text2 = node.textContent?.trim();
2782
+ if (text2) parts.push(text2);
2783
+ continue;
2784
+ }
2785
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
2786
+ const child = node;
2787
+ if (child.tagName === "STYLE" || child.tagName === "SCRIPT" || child.getAttribute("aria-hidden") === "true") continue;
2788
+ const text = cleanVisibleMaterial(child);
2789
+ if (text) parts.push(text);
2790
+ }
2791
+ return parts.join(" ").replace(/\s+/g, " ").trim();
2792
+ };
2793
+ const items = Array.from(document.querySelectorAll(sel));
2794
+ const item = items.find((element) => (element.getAttribute(dataQ) || element.getAttribute(dataInitQ) || element.querySelector(questionEl)?.innerText?.trim()) === target);
2795
+ if (!item) return { exists: false, expanded: false, answerReady: false, sourceCount: 0, questionCount: items.length };
2796
+ const expanded = item.classList.contains(expandedClass) || item.querySelector(clickTarget2)?.getAttribute("aria-expanded") === "true";
2797
+ const answerElement = item.querySelector(answerSel);
2798
+ const answerText = cleanVisibleMaterial(answerElement);
2799
+ const confirmationText = answerText.replace(/An AI Overview is not available for this search/gi, " ").replace(/Can't generate an AI overview right now\.?\s*Try again later\.?/gi, " ").replace(/\bAI Overview\b/gi, " ").replace(/\s+/g, " ").trim();
2800
+ const answerReady = confirmationText.length >= 10 && !new RegExp(answerPlaceholderPattern, "i").test(confirmationText);
2801
+ return {
2802
+ exists: true,
2803
+ expanded,
2804
+ answerReady,
2805
+ sourceCount: answerReady ? answerElement?.querySelectorAll(sourceSel).length ?? 0 : 0,
2806
+ questionCount: items.length
2807
+ };
2808
+ },
2809
+ { ...itemArgs, target: q }
2810
+ ).catch(() => latest);
2811
+ signals = [
2812
+ ...latest.expanded ? ["expanded"] : [],
2813
+ ...latest.answerReady ? ["answer"] : [],
2814
+ ...latest.sourceCount > 0 ? ["source"] : [],
2815
+ ...latest.questionCount > beforeCount ? ["question_growth"] : []
2816
+ ];
2817
+ if (latest.answerReady || latest.sourceCount > 0 || latest.questionCount > beforeCount) {
2818
+ confirmed = true;
2819
+ confirmationCheckpointMs = checkpointMs;
2820
+ confirmedAtMs = Date.now() - clickStartedMs;
2821
+ break;
2822
+ }
2823
+ }
2824
+ const totalDurationMs = Date.now() - clickStartedMs;
2825
+ const observation = {
2826
+ ...baseObservation,
2827
+ dispatched: clicked,
2828
+ outcome: confirmed ? "confirmed" : clicked ? "unconfirmed" : "dispatch_failed",
2829
+ confirmationSignals: signals,
2830
+ confirmationCheckpointMs,
2831
+ confirmedAtMs,
2832
+ dispatchDurationMs,
2833
+ totalDurationMs,
2834
+ afterQuestionCount: latest.questionCount,
2835
+ newQuestionCount: Math.max(0, latest.questionCount - beforeCount),
2836
+ expandedAfter: latest.expanded,
2837
+ answerObserved: latest.answerReady,
2838
+ sourceCount: latest.sourceCount,
2839
+ errorCode: !clicked ? dispatchErrorCode : confirmed ? null : latest.exists ? "confirmation_timeout" : "control_missing"
2840
+ };
2841
+ return { dispatched: clicked, confirmed, observation };
2708
2842
  };
2709
2843
  let round = 0;
2710
2844
  expansion: while (true) {
@@ -2735,7 +2869,8 @@ var PAAExtractor = class {
2735
2869
  targetStatus: orderedQs.length >= options.maxQuestions ? "met" : "not_met",
2736
2870
  knownQuestions: orderedQs.length,
2737
2871
  processedQuestions: processedQuestions.size,
2738
- failedInteractions: failedInteractions.size
2872
+ failedInteractions: failedInteractions.size,
2873
+ visibleControls: states.length
2739
2874
  };
2740
2875
  if (discovered) await this.emitProgress("discovered");
2741
2876
  if (options.questionsOnly && seenQs.size >= options.maxQuestions) {
@@ -2783,6 +2918,7 @@ var PAAExtractor = class {
2783
2918
  const kept = new Set(orderedQs.slice(0, options.maxQuestions));
2784
2919
  const clickable = states.filter((state) => kept.has(state.q) && !state.done && (clickAttempts.get(state.q) ?? 0) < MAX_CLICK_ATTEMPTS && !abandonedNeverReclick.has(state.q));
2785
2920
  const targets = clickable.slice(-PAA_FRONTIER_BATCH_SIZE);
2921
+ this.paaLifecycle = { ...this.paaLifecycle, eligibleControls: clickable.length };
2786
2922
  if (targets.length === 0) {
2787
2923
  if (seenQs.size >= options.maxQuestions) {
2788
2924
  this.paaLifecycle = {
@@ -2832,6 +2968,7 @@ var PAAExtractor = class {
2832
2968
  break;
2833
2969
  }
2834
2970
  await this.throwIfCaptcha(page, "Google PAA expansion");
2971
+ batchNumber += 1;
2835
2972
  for (const target of targets) {
2836
2973
  this.reporter.onDepth(++round);
2837
2974
  if ((clickAttempts.get(target.q) ?? 0) > 0) {
@@ -2844,10 +2981,21 @@ var PAAExtractor = class {
2844
2981
  }
2845
2982
  const expansionResults = await Promise.all(targets.map(async (target) => ({
2846
2983
  target,
2847
- result: await expandOneItemSerially(target.q, states.length, target.index, target.expanded)
2984
+ result: await expandOneItemSerially(
2985
+ target.q,
2986
+ states.length,
2987
+ target.index,
2988
+ target.expanded,
2989
+ clickAttempts.get(target.q) ?? 1,
2990
+ batchNumber
2991
+ )
2848
2992
  })));
2849
2993
  for (const { target, result } of expansionResults) {
2850
- if (result.dispatched) this.clickedQuestions.add(target.q);
2994
+ if (result.dispatched || result.confirmed) this.clickedQuestions.add(target.q);
2995
+ this.interactionObservations.push(result.observation);
2996
+ }
2997
+ if (this.interactionObservations.length > MAX_RETAINED_INTERACTION_OBSERVATIONS) {
2998
+ this.interactionObservations.splice(0, this.interactionObservations.length - MAX_RETAINED_INTERACTION_OBSERVATIONS);
2851
2999
  }
2852
3000
  if (!options.questionsOnly) {
2853
3001
  const targetQuestions = new Set(targets.map((target) => target.q));
@@ -2873,7 +3021,10 @@ var PAAExtractor = class {
2873
3021
  ...this.paaLifecycle,
2874
3022
  processedQuestions: processedQuestions.size,
2875
3023
  failedInteractions: failedInteractions.size,
2876
- noGrowthConfirmations: 0
3024
+ noGrowthConfirmations: 0,
3025
+ dispatchedInteractions: this.interactionObservations.filter((value) => value.dispatched).length,
3026
+ confirmedInteractions: this.interactionObservations.filter((value) => value.outcome === "confirmed").length,
3027
+ unconfirmedInteractions: this.interactionObservations.filter((value) => value.outcome !== "confirmed").length
2877
3028
  };
2878
3029
  await this.emitProgress("click_observed");
2879
3030
  }
@@ -3467,6 +3618,18 @@ var PAAExtractor = class {
3467
3618
  try {
3468
3619
  this.throwIfAborted(signal);
3469
3620
  await this.driver.launch(config);
3621
+ const providerSession = this.driver.getProviderSessionMetadata?.();
3622
+ if (providerSession?.providerSessionId && this.onProviderSession) {
3623
+ try {
3624
+ await this.onProviderSession(providerSession);
3625
+ } catch (err) {
3626
+ console.warn(JSON.stringify({
3627
+ event: "paa_provider_session_sink_failed",
3628
+ provider: providerSession.provider,
3629
+ message: err instanceof Error ? err.message : String(err)
3630
+ }));
3631
+ }
3632
+ }
3470
3633
  this.throwIfAborted(signal);
3471
3634
  const locationResolution = options.location ? resolveLocation(options.location) : null;
3472
3635
  const canonicalLocation = options.location ? locationResolution?.canonical ?? options.location : null;
@@ -4602,10 +4765,10 @@ async function cleanupDisposableProxy(kernelApiKey, proxyId) {
4602
4765
  }));
4603
4766
  }
4604
4767
  }
4605
- async function extractOnce(options, signal, forceManagedSerp = false, onPaaProgress) {
4768
+ async function extractOnce(options, signal, forceManagedSerp = false, onPaaProgress, onProviderSession) {
4606
4769
  const driver = createSerpDriver(options, { forceManaged: forceManagedSerp });
4607
4770
  const reporter = new ProgressReporter();
4608
- const extractor = new PAAExtractor(driver, reporter, onPaaProgress);
4771
+ const extractor = new PAAExtractor(driver, reporter, onPaaProgress, onProviderSession);
4609
4772
  if (signal?.aborted) {
4610
4773
  return {
4611
4774
  result: null,
@@ -4750,12 +4913,20 @@ async function harvest(rawOptions) {
4750
4913
  max_questions: attemptOptions.maxQuestions
4751
4914
  }));
4752
4915
  const baseCtx = currentCostContext();
4916
+ const onProviderSession = (metadata) => emitAttemptEvent(onAttemptEvent, {
4917
+ type: "provider_session_started",
4918
+ attemptNumber,
4919
+ maxAttempts,
4920
+ provider: metadata.provider,
4921
+ providerSessionId: metadata.providerSessionId,
4922
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
4923
+ });
4753
4924
  const allowHeadlessTest = baseCtx?.harvestBrowserMode === "headless-test" && process.env[HEADLESS_HARVEST_TEST_ENV] === "true";
4754
4925
  const attempt = baseCtx ? await runWithCostContext({
4755
4926
  ...baseCtx,
4756
4927
  forceHeadless: allowHeadlessTest,
4757
4928
  forceHeadful: !allowHeadlessTest
4758
- }, () => extractOnce(attemptOptions, signal, forceManagedSerp, onPaaProgress)) : await extractOnce(attemptOptions, signal, forceManagedSerp, onPaaProgress);
4929
+ }, () => extractOnce(attemptOptions, signal, forceManagedSerp, onPaaProgress, onProviderSession)) : await extractOnce(attemptOptions, signal, forceManagedSerp, onPaaProgress, onProviderSession);
4759
4930
  if (attempt.error) {
4760
4931
  const err = attempt.error;
4761
4932
  const outcome2 = classifyAttemptError(err);
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var PACKAGE_VERSION = "0.79.2";
2
+ var PACKAGE_VERSION = "0.79.4";
3
3
 
4
4
  export {
5
5
  PACKAGE_VERSION
@@ -6,11 +6,12 @@ import {
6
6
  LocationMismatchError,
7
7
  PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,
8
8
  RequestAbortedError,
9
+ attachHarvestAttemptProviderSession,
9
10
  buildPublicErrorEnvelope,
10
11
  finishHarvestAttempt,
11
12
  publicErrorMessage,
12
13
  startHarvestAttempt
13
- } from "./chunk-OU4FV2RP.js";
14
+ } from "./chunk-YXNDOQXN.js";
14
15
 
15
16
  // src/api/harvest-problems.ts
16
17
  function errorMessage(err) {
@@ -186,6 +187,16 @@ function createHarvestAttemptRecorder(jobId, userId) {
186
187
  });
187
188
  return;
188
189
  }
190
+ if (event.type === "provider_session_started") {
191
+ await attachHarvestAttemptProviderSession({
192
+ jobId,
193
+ attemptNumber: event.attemptNumber,
194
+ browserProvider: event.provider,
195
+ providerSessionId: event.providerSessionId,
196
+ observedAt: event.observedAt
197
+ });
198
+ return;
199
+ }
189
200
  await finishHarvestAttempt({
190
201
  jobId,
191
202
  attemptNumber: event.attemptNumber,
@@ -12,13 +12,13 @@ import {
12
12
  registerScheduledResultsMcpTools,
13
13
  registerSerpIntelligenceCaptureTools,
14
14
  resolveDeploymentProfile
15
- } from "./chunk-JECPQBLZ.js";
15
+ } from "./chunk-RUBI3OXR.js";
16
16
  import {
17
17
  renderInstallTerminal
18
- } from "./chunk-6FL7ZZCF.js";
18
+ } from "./chunk-ETQ7J2EB.js";
19
19
  import {
20
20
  PACKAGE_VERSION
21
- } from "./chunk-2RTELL6M.js";
21
+ } from "./chunk-N63ZFFU2.js";
22
22
 
23
23
  // src/mcp/stdio-runtime.ts
24
24
  import { readFileSync } from "fs";