hypha-debugger 0.1.3 → 0.1.5

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.
@@ -742,6 +742,31 @@ getPageInfo.__schema__ = {
742
742
  },
743
743
  },
744
744
  };
745
+ function getConsoleLogs(options) {
746
+ const logs = window.__HYPHA_DEBUGGER__?.consoleLogs ?? [];
747
+ const level = options?.level;
748
+ const limit = options?.limit ?? 100;
749
+ let filtered = level ? logs.filter((l) => l.level === level) : logs;
750
+ return filtered.slice(-limit);
751
+ }
752
+ getConsoleLogs.__schema__ = {
753
+ name: "getConsoleLogs",
754
+ description: "Retrieve captured console output (log, warn, error, info).",
755
+ parameters: {
756
+ type: "object",
757
+ properties: {
758
+ level: {
759
+ type: "string",
760
+ description: 'Filter by log level: "log", "warn", "error", "info". Omit for all levels.',
761
+ enum: ["log", "warn", "error", "info"],
762
+ },
763
+ limit: {
764
+ type: "number",
765
+ description: "Maximum number of log entries to return (most recent). Default: 100.",
766
+ },
767
+ },
768
+ },
769
+ };
745
770
  /** Install console interceptor to capture logs. */
746
771
  function installConsoleCapture(maxEntries = 500) {
747
772
  var _a;
@@ -941,6 +966,88 @@ scrollTo.__schema__ = {
941
966
  required: ["target"],
942
967
  },
943
968
  };
969
+ function getComputedStyles(selector, properties) {
970
+ const el = document.querySelector(selector);
971
+ if (!el) {
972
+ return { error: `No element found for selector: ${selector}` };
973
+ }
974
+ const computed = getComputedStyle(el);
975
+ const result = {};
976
+ const props = properties ??
977
+ [
978
+ "display",
979
+ "position",
980
+ "width",
981
+ "height",
982
+ "color",
983
+ "background-color",
984
+ "font-size",
985
+ "font-family",
986
+ "margin",
987
+ "padding",
988
+ "border",
989
+ "opacity",
990
+ "visibility",
991
+ "overflow",
992
+ "z-index",
993
+ ];
994
+ for (const prop of props) {
995
+ result[prop] = computed.getPropertyValue(prop);
996
+ }
997
+ return result;
998
+ }
999
+ getComputedStyles.__schema__ = {
1000
+ name: "getComputedStyles",
1001
+ description: "Get computed CSS styles for an element.",
1002
+ parameters: {
1003
+ type: "object",
1004
+ properties: {
1005
+ selector: {
1006
+ type: "string",
1007
+ description: "CSS selector of the element.",
1008
+ },
1009
+ properties: {
1010
+ type: "array",
1011
+ items: { type: "string" },
1012
+ description: 'CSS property names to retrieve, e.g. ["color", "font-size"]. Omit for common defaults.',
1013
+ },
1014
+ },
1015
+ required: ["selector"],
1016
+ },
1017
+ };
1018
+ function getElementBounds(selector) {
1019
+ const el = document.querySelector(selector);
1020
+ if (!el) {
1021
+ return { error: `No element found for selector: ${selector}` };
1022
+ }
1023
+ const rect = el.getBoundingClientRect();
1024
+ return {
1025
+ bounds: {
1026
+ x: Math.round(rect.x),
1027
+ y: Math.round(rect.y),
1028
+ width: Math.round(rect.width),
1029
+ height: Math.round(rect.height),
1030
+ },
1031
+ visible: rect.width > 0 &&
1032
+ rect.height > 0 &&
1033
+ getComputedStyle(el).visibility !== "hidden" &&
1034
+ getComputedStyle(el).display !== "none",
1035
+ };
1036
+ }
1037
+ getElementBounds.__schema__ = {
1038
+ name: "getElementBounds",
1039
+ description: "Get the bounding rectangle and visibility of a DOM element.",
1040
+ parameters: {
1041
+ type: "object",
1042
+ properties: {
1043
+ selector: {
1044
+ type: "string",
1045
+ description: "CSS selector of the element.",
1046
+ },
1047
+ },
1048
+ required: ["selector"],
1049
+ },
1050
+ };
944
1051
  function getHtml(selector, outer, max_length) {
945
1052
  const useOuter = outer ?? true;
946
1053
  const maxLen = max_length ?? 50000;
@@ -2020,20 +2127,123 @@ executeScript.__schema__ = {
2020
2127
  };
2021
2128
 
2022
2129
  /**
2023
- * Navigation service.
2130
+ * Navigation service with auto-reconnect support.
2131
+ *
2132
+ * For agent-triggered reload() and same-origin navigate(), we use a "soft"
2133
+ * approach: fetch the target HTML, inject the debugger <script> tag, then
2134
+ * replace the document via document.write(). The injected script auto-starts
2135
+ * from sessionStorage config, so the debugger reconnects with the same
2136
+ * workspace and service ID. The agent's URL stays stable.
2137
+ *
2138
+ * For cross-origin navigate(), goBack(), and goForward(), we fall back to
2139
+ * normal navigation. The debugger config is saved to sessionStorage so
2140
+ * re-clicking the bookmarklet reconnects seamlessly.
2141
+ */
2142
+ const STORAGE_KEY$1 = "__hypha_debugger_config__";
2143
+ /** Read the saved script URL from sessionStorage, or fall back to CDN. */
2144
+ function getScriptUrl() {
2145
+ try {
2146
+ const raw = sessionStorage.getItem(STORAGE_KEY$1);
2147
+ if (raw) {
2148
+ const config = JSON.parse(raw);
2149
+ if (config.script_url)
2150
+ return config.script_url;
2151
+ }
2152
+ }
2153
+ catch {
2154
+ // ignore
2155
+ }
2156
+ return "https://cdn.jsdelivr.net/npm/hypha-debugger/dist/hypha-debugger.min.js";
2157
+ }
2158
+ /**
2159
+ * Inject the debugger loader script into HTML before </body> (or append).
2160
+ * Also injects a tiny inline script that reads sessionStorage and passes
2161
+ * config to the debugger via a global, so autoStart() picks it up.
2024
2162
  */
2163
+ function injectLoader(html, scriptUrl) {
2164
+ const loader = `<script src="${scriptUrl}"><\/script>`;
2165
+ if (html.includes("</body>")) {
2166
+ return html.replace("</body>", loader + "\n</body>");
2167
+ }
2168
+ if (html.includes("</html>")) {
2169
+ return html.replace("</html>", loader + "\n</html>");
2170
+ }
2171
+ return html + "\n" + loader;
2172
+ }
2173
+ /**
2174
+ * Perform a soft page replacement: fetch HTML, inject debugger script,
2175
+ * replace the document. Returns false if it can't be done (caller should
2176
+ * fall back to hard navigation).
2177
+ */
2178
+ function softReplace(url, pushState) {
2179
+ const scriptUrl = getScriptUrl();
2180
+ fetch(url, { credentials: "same-origin", cache: "reload" })
2181
+ .then((response) => {
2182
+ if (!response.ok)
2183
+ throw new Error(`HTTP ${response.status}`);
2184
+ const contentType = response.headers.get("content-type") ?? "";
2185
+ if (!contentType.includes("text/html")) {
2186
+ throw new Error("Not HTML");
2187
+ }
2188
+ return response.text();
2189
+ })
2190
+ .then((html) => {
2191
+ const modified = injectLoader(html, scriptUrl);
2192
+ document.open();
2193
+ document.write(modified);
2194
+ document.close();
2195
+ if (pushState) {
2196
+ try {
2197
+ history.pushState({}, "", pushState);
2198
+ }
2199
+ catch {
2200
+ // ignore — URL might already match
2201
+ }
2202
+ }
2203
+ })
2204
+ .catch(() => {
2205
+ // Soft replace failed — fall back to hard navigation
2206
+ if (pushState) {
2207
+ window.location.href = pushState;
2208
+ }
2209
+ else {
2210
+ window.location.reload();
2211
+ }
2212
+ });
2213
+ }
2214
+ // ── navigate ──────────────────────────────────────────────────────────
2025
2215
  function navigate(url) {
2026
2216
  try {
2027
- window.location.href = url;
2028
- return { success: true, message: `Navigating to ${url}` };
2217
+ const targetUrl = new URL(url, location.href);
2218
+ const sameOrigin = targetUrl.origin === location.origin;
2219
+ if (sameOrigin) {
2220
+ // Soft navigate: fetch + inject + document.write, then pushState
2221
+ // Schedule after RPC response is sent
2222
+ setTimeout(() => softReplace(targetUrl.href, targetUrl.href), 150);
2223
+ return {
2224
+ success: true,
2225
+ message: `Navigating to ${url} (debugger will auto-reconnect)`,
2226
+ };
2227
+ }
2228
+ else {
2229
+ // Cross-origin: can't soft navigate, fall back to hard
2230
+ window.location.href = url;
2231
+ return {
2232
+ success: true,
2233
+ message: `Navigating to ${url} (cross-origin, debugger will disconnect)`,
2234
+ };
2235
+ }
2029
2236
  }
2030
2237
  catch (err) {
2031
- return { success: false, message: `Navigation failed: ${err.message ?? err}` };
2238
+ return {
2239
+ success: false,
2240
+ message: `Navigation failed: ${err.message ?? err}`,
2241
+ };
2032
2242
  }
2033
2243
  }
2034
2244
  navigate.__schema__ = {
2035
2245
  name: "navigate",
2036
- description: "Navigate the browser to a new URL.",
2246
+ description: "Navigate the browser to a new URL. For same-origin URLs, the debugger auto-reconnects. Cross-origin navigation will disconnect the debugger.",
2037
2247
  parameters: {
2038
2248
  type: "object",
2039
2249
  properties: {
@@ -2045,6 +2255,75 @@ navigate.__schema__ = {
2045
2255
  required: ["url"],
2046
2256
  },
2047
2257
  };
2258
+ // ── goBack / goForward ───────────────────────────────────────────────
2259
+ function goBack() {
2260
+ try {
2261
+ window.history.back();
2262
+ return {
2263
+ success: true,
2264
+ message: "Navigated back (re-click bookmarklet to reconnect if needed)",
2265
+ };
2266
+ }
2267
+ catch (err) {
2268
+ return {
2269
+ success: false,
2270
+ message: `Back navigation failed: ${err.message ?? err}`,
2271
+ };
2272
+ }
2273
+ }
2274
+ goBack.__schema__ = {
2275
+ name: "goBack",
2276
+ description: "Navigate back in browser history. The debugger may disconnect — re-click the bookmarklet to reconnect.",
2277
+ parameters: {
2278
+ type: "object",
2279
+ properties: {},
2280
+ },
2281
+ };
2282
+ function goForward() {
2283
+ try {
2284
+ window.history.forward();
2285
+ return {
2286
+ success: true,
2287
+ message: "Navigated forward (re-click bookmarklet to reconnect if needed)",
2288
+ };
2289
+ }
2290
+ catch (err) {
2291
+ return {
2292
+ success: false,
2293
+ message: `Forward navigation failed: ${err.message ?? err}`,
2294
+ };
2295
+ }
2296
+ }
2297
+ goForward.__schema__ = {
2298
+ name: "goForward",
2299
+ description: "Navigate forward in browser history. The debugger may disconnect — re-click the bookmarklet to reconnect.",
2300
+ parameters: {
2301
+ type: "object",
2302
+ properties: {},
2303
+ },
2304
+ };
2305
+ // ── reload ───────────────────────────────────────────────────────────
2306
+ function reload() {
2307
+ try {
2308
+ // Schedule soft reload after RPC response is sent
2309
+ setTimeout(() => softReplace(location.href), 150);
2310
+ return {
2311
+ success: true,
2312
+ message: "Reloading page (debugger will auto-reconnect)",
2313
+ };
2314
+ }
2315
+ catch (err) {
2316
+ return { success: false, message: `Reload failed: ${err.message ?? err}` };
2317
+ }
2318
+ }
2319
+ reload.__schema__ = {
2320
+ name: "reload",
2321
+ description: "Reload the current page. The debugger auto-reconnects after reload using soft page replacement.",
2322
+ parameters: {
2323
+ type: "object",
2324
+ properties: {},
2325
+ },
2326
+ };
2048
2327
 
2049
2328
  /**
2050
2329
  * React component tree inspection service.
@@ -2387,6 +2666,33 @@ function generateSkillMd(serviceFunctions, serviceUrl) {
2387
2666
  return [frontmatter, intro, functionDocs.join("\n"), tips].join("\n");
2388
2667
  }
2389
2668
 
2669
+ /**
2670
+ * Wrap a function with correct, unminified parameter names for hypha-rpc.
2671
+ *
2672
+ * In production builds, Babel/Terser minifies parameter names (e.g. 'code' → 'e').
2673
+ * hypha-rpc's getParamNames() parses Function.toString() to map kwargs to
2674
+ * positional args. With minified names, kwargs like {code: '...'} can't be
2675
+ * mapped and args are silently dropped.
2676
+ *
2677
+ * This helper uses new Function() to create a wrapper whose parameter names
2678
+ * are taken from the function's __schema__ property, so hypha-rpc always sees
2679
+ * the real parameter names regardless of minification.
2680
+ */
2681
+ function wrapFn(fn) {
2682
+ const schema = fn.__schema__;
2683
+ const paramNames = schema?.parameters?.properties
2684
+ ? Object.keys(schema.parameters.properties)
2685
+ : [];
2686
+ if (paramNames.length === 0) {
2687
+ return fn;
2688
+ }
2689
+ const paramList = paramNames.join(", ");
2690
+ const wrapper = new Function("fn", `return async function(${paramList}) { return fn(${paramList}); }`)(fn);
2691
+ if (schema)
2692
+ wrapper.__schema__ = schema;
2693
+ return wrapper;
2694
+ }
2695
+
2390
2696
  /**
2391
2697
  * @file port from browser-use
2392
2698
  * @see https://github.com/browser-use/browser-use/commits/main/browser_use/dom/dom_tree/index.js
@@ -5123,12 +5429,15 @@ function randomHex(bytes = 8) {
5123
5429
  crypto.getRandomValues(arr);
5124
5430
  return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
5125
5431
  }
5432
+ /** sessionStorage key for persisting debugger config across reloads. */
5433
+ const STORAGE_KEY = "__hypha_debugger_config__";
5126
5434
  class HyphaDebugger {
5127
5435
  constructor(config) {
5128
5436
  this.overlay = null;
5129
5437
  this.cursor = null;
5130
5438
  this.server = null;
5131
5439
  this.serviceInfo = null;
5440
+ this.boundBeforeUnload = null;
5132
5441
  const requireToken = config.require_token ?? false;
5133
5442
  // Always append random suffix unless user provided a custom id.
5134
5443
  let serviceId = config.service_id ?? "web-debugger";
@@ -5188,6 +5497,10 @@ class HyphaDebugger {
5188
5497
  // Store globally
5189
5498
  w.__HYPHA_DEBUGGER__ = w.__HYPHA_DEBUGGER__ ?? {};
5190
5499
  w.__HYPHA_DEBUGGER__.instance = this;
5500
+ // Persist config to sessionStorage for auto-reconnect after reload
5501
+ this.saveConfigToStorage();
5502
+ this.boundBeforeUnload = () => this.saveConfigToStorage();
5503
+ window.addEventListener("beforeunload", this.boundBeforeUnload);
5191
5504
  return session;
5192
5505
  }
5193
5506
  catch (err) {
@@ -5203,6 +5516,11 @@ class HyphaDebugger {
5203
5516
  }
5204
5517
  }
5205
5518
  async destroy() {
5519
+ // Remove beforeunload listener
5520
+ if (this.boundBeforeUnload) {
5521
+ window.removeEventListener("beforeunload", this.boundBeforeUnload);
5522
+ this.boundBeforeUnload = null;
5523
+ }
5206
5524
  try {
5207
5525
  if (this.serviceInfo && this.server) {
5208
5526
  await this.server.unregisterService(this.serviceInfo.id);
@@ -5211,6 +5529,13 @@ class HyphaDebugger {
5211
5529
  catch {
5212
5530
  // Ignore unregister errors on cleanup
5213
5531
  }
5532
+ // Clear sessionStorage config (explicit destroy = user wants to stop)
5533
+ try {
5534
+ sessionStorage.removeItem(STORAGE_KEY);
5535
+ }
5536
+ catch {
5537
+ // ignore
5538
+ }
5214
5539
  disposeController();
5215
5540
  this.cursor?.destroy();
5216
5541
  this.cursor = null;
@@ -5222,6 +5547,48 @@ class HyphaDebugger {
5222
5547
  delete w.__HYPHA_DEBUGGER__.session;
5223
5548
  }
5224
5549
  }
5550
+ /**
5551
+ * Persist debugger config to sessionStorage so the debugger can
5552
+ * auto-reconnect after a page reload (soft reload injects the script,
5553
+ * autoStart() reads this config).
5554
+ */
5555
+ saveConfigToStorage() {
5556
+ try {
5557
+ const data = {
5558
+ server_url: this.config.server_url,
5559
+ workspace: this.server?.config?.workspace ?? this.config.workspace,
5560
+ token: this.config.token,
5561
+ service_id: this.config.service_id,
5562
+ service_name: this.config.service_name,
5563
+ show_ui: this.config.show_ui,
5564
+ visibility: this.config.visibility,
5565
+ require_token: this.config.require_token,
5566
+ script_url: this.detectScriptUrl(),
5567
+ };
5568
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(data));
5569
+ }
5570
+ catch {
5571
+ // sessionStorage might be unavailable (private browsing, full quota)
5572
+ }
5573
+ }
5574
+ /**
5575
+ * Detect the URL of the currently loaded hypha-debugger script.
5576
+ * Used by navigate.ts to inject the correct script after soft reload.
5577
+ */
5578
+ detectScriptUrl() {
5579
+ try {
5580
+ const scripts = document.querySelectorAll("script[src]");
5581
+ for (const s of Array.from(scripts)) {
5582
+ if (s.src && s.src.includes("hypha-debugger")) {
5583
+ return s.src;
5584
+ }
5585
+ }
5586
+ }
5587
+ catch {
5588
+ // ignore
5589
+ }
5590
+ return "https://cdn.jsdelivr.net/npm/hypha-debugger/dist/hypha-debugger.min.js";
5591
+ }
5225
5592
  /**
5226
5593
  * Generate token, build service URL, update overlay instructions, and
5227
5594
  * return a DebugSession.
@@ -5391,21 +5758,17 @@ class HyphaDebugger {
5391
5758
  return lines.join("\n");
5392
5759
  }
5393
5760
  /**
5394
- * Wrap a service function with logging and correct parameter names.
5761
+ * Wrap a service function with overlay logging + correct parameter names.
5395
5762
  *
5396
- * Uses new Function() to create a wrapper whose parameter names match
5397
- * the __schema__ property names. This is critical for production builds
5398
- * where Babel/Terser minifies parameter names hypha-rpc's
5399
- * getParamNames() parses Function.toString() to map kwargs to positional
5400
- * args, so the wrapper must have the real (unminified) parameter names.
5763
+ * Adds logging around the function, then applies baseWrapFn() which uses
5764
+ * new Function() to create a wrapper with unminified parameter names from
5765
+ * __schema__. This is critical for production builds where Babel/Terser
5766
+ * minifies parameter names hypha-rpc's getParamNames() parses
5767
+ * Function.toString() to map kwargs to positional args.
5401
5768
  */
5402
5769
  wrapFn(fn, name) {
5403
- const schema = fn.__schema__;
5404
- const paramNames = schema?.parameters?.properties
5405
- ? Object.keys(schema.parameters.properties)
5406
- : [];
5407
5770
  const self = this;
5408
- const callAndLog = async (args) => {
5771
+ const logged = async (...args) => {
5409
5772
  self.overlay?.addLog(`${name}(${self.summarizeArgs(args)})`, "call");
5410
5773
  try {
5411
5774
  const result = await fn(...args);
@@ -5423,19 +5786,10 @@ class HyphaDebugger {
5423
5786
  throw err;
5424
5787
  }
5425
5788
  };
5426
- let wrapper;
5427
- if (paramNames.length === 0) {
5428
- wrapper = async (...args) => callAndLog(args);
5429
- }
5430
- else {
5431
- // Create a function with explicit, unminified parameter names so
5432
- // hypha-rpc can parse them from Function.toString().
5433
- const paramList = paramNames.join(", ");
5434
- wrapper = new Function("callAndLog", `return async function(${paramList}) { return callAndLog([${paramList}]); }`)(callAndLog);
5435
- }
5436
- if (schema)
5437
- wrapper.__schema__ = schema;
5438
- return wrapper;
5789
+ // Preserve __schema__ so baseWrapFn can read parameter names
5790
+ if (fn.__schema__)
5791
+ logged.__schema__ = fn.__schema__;
5792
+ return wrapFn(logged);
5439
5793
  }
5440
5794
  summarizeArgs(args) {
5441
5795
  if (args.length === 0)
@@ -5465,7 +5819,11 @@ class HyphaDebugger {
5465
5819
  * Programmatic usage:
5466
5820
  * import { startDebugger } from 'hypha-debugger';
5467
5821
  * const session = await startDebugger({ server_url: 'https://hypha.aicell.io' });
5822
+ *
5823
+ * Library usage (import individual functions):
5824
+ * import { getPageInfo, clickElement, wrapFn, PageController } from 'hypha-debugger';
5468
5825
  */
5826
+ // ── Core debugger ──
5469
5827
  /**
5470
5828
  * Start the Hypha debugger. Connects to a Hypha server and registers
5471
5829
  * a debug service that remote clients can use to inspect and interact
@@ -5486,6 +5844,26 @@ async function startDebugger(config) {
5486
5844
  function autoStart() {
5487
5845
  if (typeof window === "undefined" || typeof document === "undefined")
5488
5846
  return;
5847
+ // Skip if already started
5848
+ if (window.__HYPHA_DEBUGGER__?.instance)
5849
+ return;
5850
+ // Check sessionStorage for saved config (auto-reconnect after soft reload)
5851
+ try {
5852
+ const saved = sessionStorage.getItem("__hypha_debugger_config__");
5853
+ if (saved) {
5854
+ const savedConfig = JSON.parse(saved);
5855
+ if (savedConfig.server_url) {
5856
+ console.log("[hypha-debugger] Reconnecting from saved session...");
5857
+ startDebugger(savedConfig).catch((err) => {
5858
+ console.error("[hypha-debugger] Auto-reconnect failed:", err);
5859
+ });
5860
+ return;
5861
+ }
5862
+ }
5863
+ }
5864
+ catch {
5865
+ // sessionStorage not available or parse error — continue to script tag detection
5866
+ }
5489
5867
  // Find our own script tag
5490
5868
  const scripts = document.querySelectorAll("script[src]");
5491
5869
  let scriptEl = null;
@@ -5498,9 +5876,6 @@ function autoStart() {
5498
5876
  // Skip if data-manual is set
5499
5877
  if (scriptEl?.hasAttribute("data-manual"))
5500
5878
  return;
5501
- // Skip if already started
5502
- if (window.__HYPHA_DEBUGGER__?.instance)
5503
- return;
5504
5879
  const serverUrl = scriptEl?.getAttribute("data-server-url") ?? "https://hypha.aicell.io";
5505
5880
  const config = {
5506
5881
  server_url: serverUrl,
@@ -5534,5 +5909,5 @@ if (typeof window !== "undefined") {
5534
5909
  }
5535
5910
  }
5536
5911
 
5537
- export { HyphaDebugger, startDebugger };
5912
+ export { AICursor, HyphaDebugger, PageController, clickElement$1 as clickElement, clickElementByIndex, disposeController, executeScript, fillInput, generateSkillMd, getBrowserState, getComputedStyles, getConsoleLogs, getElementBounds, getHtml, getPageInfo, getReactTree, goBack, goForward, inputText, installConsoleCapture, navigate, queryDom, reload, removeHighlights, scroll, scrollTo, selectOption, startDebugger, takeScreenshot, wrapFn };
5538
5913
  //# sourceMappingURL=hypha-debugger.mjs.map