hypha-debugger 0.2.0 → 0.2.2

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.
@@ -13852,6 +13852,23 @@
13852
13852
  * Images are downscaled before being returned so agents don't receive
13853
13853
  * multi-megabyte base64 payloads that can crash their context window.
13854
13854
  */
13855
+ /** Extract a useful string from an unknown error value. */
13856
+ function errorMessage(err) {
13857
+ if (!err)
13858
+ return "Unknown error";
13859
+ if (typeof err === "string")
13860
+ return err;
13861
+ if (err.message)
13862
+ return err.message;
13863
+ if (err instanceof Event)
13864
+ return `Event: ${err.type}`;
13865
+ try {
13866
+ return JSON.stringify(err);
13867
+ }
13868
+ catch {
13869
+ return String(err);
13870
+ }
13871
+ }
13855
13872
  /**
13856
13873
  * Resize an image data URL via a canvas. Returns a new data URL at the
13857
13874
  * requested format/quality. Maintains aspect ratio: fits within
@@ -13861,31 +13878,34 @@
13861
13878
  return new Promise((resolve, reject) => {
13862
13879
  const img = new Image();
13863
13880
  img.onload = () => {
13864
- const srcW = img.naturalWidth;
13865
- const srcH = img.naturalHeight;
13866
- // Compute scale to fit within bounds (but never upscale)
13867
- const scale = Math.min(maxWidth / srcW, maxHeight / srcH, 1);
13868
- const dstW = Math.max(1, Math.round(srcW * scale));
13869
- const dstH = Math.max(1, Math.round(srcH * scale));
13870
- const canvas = document.createElement("canvas");
13871
- canvas.width = dstW;
13872
- canvas.height = dstH;
13873
- const ctx = canvas.getContext("2d");
13874
- if (!ctx) {
13875
- reject(new Error("Could not get 2D canvas context"));
13876
- return;
13881
+ try {
13882
+ const srcW = img.naturalWidth;
13883
+ const srcH = img.naturalHeight;
13884
+ const scale = Math.min(maxWidth / srcW, maxHeight / srcH, 1);
13885
+ const dstW = Math.max(1, Math.round(srcW * scale));
13886
+ const dstH = Math.max(1, Math.round(srcH * scale));
13887
+ const canvas = document.createElement("canvas");
13888
+ canvas.width = dstW;
13889
+ canvas.height = dstH;
13890
+ const ctx = canvas.getContext("2d");
13891
+ if (!ctx) {
13892
+ reject(new Error("Could not get 2D canvas context"));
13893
+ return;
13894
+ }
13895
+ if (format === "jpeg") {
13896
+ ctx.fillStyle = "#ffffff";
13897
+ ctx.fillRect(0, 0, dstW, dstH);
13898
+ }
13899
+ ctx.drawImage(img, 0, 0, dstW, dstH);
13900
+ const mime = format === "jpeg" ? "image/jpeg" : "image/png";
13901
+ const out = canvas.toDataURL(mime, quality);
13902
+ resolve({ dataUrl: out, width: dstW, height: dstH });
13877
13903
  }
13878
- // Fill white background for JPEG (no alpha support)
13879
- if (format === "jpeg") {
13880
- ctx.fillStyle = "#ffffff";
13881
- ctx.fillRect(0, 0, dstW, dstH);
13904
+ catch (drawErr) {
13905
+ reject(new Error(`Canvas resize failed: ${errorMessage(drawErr)}`));
13882
13906
  }
13883
- ctx.drawImage(img, 0, 0, dstW, dstH);
13884
- const mime = format === "jpeg" ? "image/jpeg" : "image/png";
13885
- const out = canvas.toDataURL(mime, quality);
13886
- resolve({ dataUrl: out, width: dstW, height: dstH });
13887
13907
  };
13888
- img.onerror = () => reject(new Error("Failed to load image for resizing"));
13908
+ img.onerror = (ev) => reject(new Error(`Failed to load captured image for resizing${ev instanceof Event ? ` (${ev.type})` : ""}`));
13889
13909
  img.src = dataUrl;
13890
13910
  });
13891
13911
  }
@@ -13925,6 +13945,7 @@
13925
13945
  pixelRatio: 1, // always capture at 1x — we'll resize after
13926
13946
  cacheBust: true,
13927
13947
  skipAutoScale: true,
13948
+ skipFonts: true, // CORS-blocked stylesheets can hang font inlining
13928
13949
  filter: (el) => {
13929
13950
  // Exclude the debugger overlay and cursor from screenshots
13930
13951
  return (el.id !== "hypha-debugger-host" &&
@@ -13938,25 +13959,51 @@
13938
13959
  captureOptions.height = viewportH;
13939
13960
  }
13940
13961
  let dataUrl;
13941
- if (fmt === "jpeg") {
13942
- dataUrl = await toJpeg(node, captureOptions);
13962
+ try {
13963
+ const capturePromise = fmt === "jpeg" ? toJpeg(node, captureOptions) : toPng(node, captureOptions);
13964
+ // Hard timeout: pages with cross-origin resources can make
13965
+ // html-to-image wait indefinitely on blocked fetches.
13966
+ const timeoutMs = 15000;
13967
+ dataUrl = await Promise.race([
13968
+ capturePromise,
13969
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Screenshot capture timed out after ${timeoutMs}ms (likely CORS-blocked resources)`)), timeoutMs)),
13970
+ ]);
13971
+ }
13972
+ catch (captureErr) {
13973
+ return {
13974
+ error: `Capture failed (html-to-image): ${errorMessage(captureErr)}`,
13975
+ };
13943
13976
  }
13944
- else {
13945
- dataUrl = await toPng(node, captureOptions);
13977
+ // Resize down to fit within (maxW × maxH) and re-encode. If resize
13978
+ // fails (e.g. data URL too large to load back into an Image), fall
13979
+ // back to returning the original capture so the caller still gets
13980
+ // something useful.
13981
+ try {
13982
+ const resized = await resizeDataUrl(dataUrl, maxW, maxH, fmt, qual);
13983
+ const sizeKb = Math.round((resized.dataUrl.length * 0.75) / 1024);
13984
+ return {
13985
+ data: resized.dataUrl,
13986
+ format: fmt,
13987
+ width: resized.width,
13988
+ height: resized.height,
13989
+ size_kb: sizeKb,
13990
+ };
13991
+ }
13992
+ catch (resizeErr) {
13993
+ const rect = node.getBoundingClientRect();
13994
+ const sizeKb = Math.round((dataUrl.length * 0.75) / 1024);
13995
+ return {
13996
+ data: dataUrl,
13997
+ format: fmt,
13998
+ width: Math.round(rect.width),
13999
+ height: Math.round(rect.height),
14000
+ size_kb: sizeKb,
14001
+ warning: `Resize failed, returning original: ${errorMessage(resizeErr)}`,
14002
+ };
13946
14003
  }
13947
- // Resize down to fit within (maxW × maxH) and re-encode
13948
- const resized = await resizeDataUrl(dataUrl, maxW, maxH, fmt, qual);
13949
- const sizeKb = Math.round((resized.dataUrl.length * 0.75) / 1024); // rough base64 → bytes
13950
- return {
13951
- data: resized.dataUrl,
13952
- format: fmt,
13953
- width: resized.width,
13954
- height: resized.height,
13955
- size_kb: sizeKb,
13956
- };
13957
14004
  }
13958
14005
  catch (err) {
13959
- return { error: `Screenshot failed: ${err.message ?? err}` };
14006
+ return { error: `Screenshot failed: ${errorMessage(err)}` };
13960
14007
  }
13961
14008
  }
13962
14009
  takeScreenshot.__schema__ = {
@@ -17418,7 +17465,14 @@
17418
17465
  if (viewport_only !== undefined) {
17419
17466
  ctrl.config.viewportExpansion = viewport_only ? 0 : -1;
17420
17467
  }
17421
- return ctrl.getBrowserState();
17468
+ // Hard timeout: pages with heavy DOMs or cross-origin iframes can
17469
+ // make the tree walk take much longer than expected. Don't leave
17470
+ // the HTTP caller waiting forever.
17471
+ const timeoutMs = 15000;
17472
+ return Promise.race([
17473
+ ctrl.getBrowserState(),
17474
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`get_browser_state timed out after ${timeoutMs}ms (complex DOM or cross-origin iframes)`)), timeoutMs)),
17475
+ ]);
17422
17476
  }
17423
17477
  getBrowserState.__schema__ = {
17424
17478
  name: "getBrowserState",