hypha-debugger 0.1.4 → 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.
@@ -45,9 +45,21 @@ export declare class HyphaDebugger {
45
45
  private cursor;
46
46
  private server;
47
47
  private serviceInfo;
48
+ private boundBeforeUnload;
48
49
  constructor(config: DebuggerConfig);
49
50
  start(): Promise<DebugSession>;
50
51
  destroy(): Promise<void>;
52
+ /**
53
+ * Persist debugger config to sessionStorage so the debugger can
54
+ * auto-reconnect after a page reload (soft reload injects the script,
55
+ * autoStart() reads this config).
56
+ */
57
+ private saveConfigToStorage;
58
+ /**
59
+ * Detect the URL of the currently loaded hypha-debugger script.
60
+ * Used by navigate.ts to inject the correct script after soft reload.
61
+ */
62
+ private detectScriptUrl;
51
63
  /**
52
64
  * Generate token, build service URL, update overlay instructions, and
53
65
  * return a DebugSession.
@@ -10584,20 +10584,123 @@
10584
10584
  };
10585
10585
 
10586
10586
  /**
10587
- * Navigation service.
10587
+ * Navigation service with auto-reconnect support.
10588
+ *
10589
+ * For agent-triggered reload() and same-origin navigate(), we use a "soft"
10590
+ * approach: fetch the target HTML, inject the debugger <script> tag, then
10591
+ * replace the document via document.write(). The injected script auto-starts
10592
+ * from sessionStorage config, so the debugger reconnects with the same
10593
+ * workspace and service ID. The agent's URL stays stable.
10594
+ *
10595
+ * For cross-origin navigate(), goBack(), and goForward(), we fall back to
10596
+ * normal navigation. The debugger config is saved to sessionStorage so
10597
+ * re-clicking the bookmarklet reconnects seamlessly.
10588
10598
  */
10599
+ const STORAGE_KEY$1 = "__hypha_debugger_config__";
10600
+ /** Read the saved script URL from sessionStorage, or fall back to CDN. */
10601
+ function getScriptUrl() {
10602
+ try {
10603
+ const raw = sessionStorage.getItem(STORAGE_KEY$1);
10604
+ if (raw) {
10605
+ const config = JSON.parse(raw);
10606
+ if (config.script_url)
10607
+ return config.script_url;
10608
+ }
10609
+ }
10610
+ catch {
10611
+ // ignore
10612
+ }
10613
+ return "https://cdn.jsdelivr.net/npm/hypha-debugger/dist/hypha-debugger.min.js";
10614
+ }
10615
+ /**
10616
+ * Inject the debugger loader script into HTML before </body> (or append).
10617
+ * Also injects a tiny inline script that reads sessionStorage and passes
10618
+ * config to the debugger via a global, so autoStart() picks it up.
10619
+ */
10620
+ function injectLoader(html, scriptUrl) {
10621
+ const loader = `<script src="${scriptUrl}"><\/script>`;
10622
+ if (html.includes("</body>")) {
10623
+ return html.replace("</body>", loader + "\n</body>");
10624
+ }
10625
+ if (html.includes("</html>")) {
10626
+ return html.replace("</html>", loader + "\n</html>");
10627
+ }
10628
+ return html + "\n" + loader;
10629
+ }
10630
+ /**
10631
+ * Perform a soft page replacement: fetch HTML, inject debugger script,
10632
+ * replace the document. Returns false if it can't be done (caller should
10633
+ * fall back to hard navigation).
10634
+ */
10635
+ function softReplace(url, pushState) {
10636
+ const scriptUrl = getScriptUrl();
10637
+ fetch(url, { credentials: "same-origin", cache: "reload" })
10638
+ .then((response) => {
10639
+ if (!response.ok)
10640
+ throw new Error(`HTTP ${response.status}`);
10641
+ const contentType = response.headers.get("content-type") ?? "";
10642
+ if (!contentType.includes("text/html")) {
10643
+ throw new Error("Not HTML");
10644
+ }
10645
+ return response.text();
10646
+ })
10647
+ .then((html) => {
10648
+ const modified = injectLoader(html, scriptUrl);
10649
+ document.open();
10650
+ document.write(modified);
10651
+ document.close();
10652
+ if (pushState) {
10653
+ try {
10654
+ history.pushState({}, "", pushState);
10655
+ }
10656
+ catch {
10657
+ // ignore — URL might already match
10658
+ }
10659
+ }
10660
+ })
10661
+ .catch(() => {
10662
+ // Soft replace failed — fall back to hard navigation
10663
+ if (pushState) {
10664
+ window.location.href = pushState;
10665
+ }
10666
+ else {
10667
+ window.location.reload();
10668
+ }
10669
+ });
10670
+ }
10671
+ // ── navigate ──────────────────────────────────────────────────────────
10589
10672
  function navigate(url) {
10590
10673
  try {
10591
- window.location.href = url;
10592
- return { success: true, message: `Navigating to ${url}` };
10674
+ const targetUrl = new URL(url, location.href);
10675
+ const sameOrigin = targetUrl.origin === location.origin;
10676
+ if (sameOrigin) {
10677
+ // Soft navigate: fetch + inject + document.write, then pushState
10678
+ // Schedule after RPC response is sent
10679
+ setTimeout(() => softReplace(targetUrl.href, targetUrl.href), 150);
10680
+ return {
10681
+ success: true,
10682
+ message: `Navigating to ${url} (debugger will auto-reconnect)`,
10683
+ };
10684
+ }
10685
+ else {
10686
+ // Cross-origin: can't soft navigate, fall back to hard
10687
+ window.location.href = url;
10688
+ return {
10689
+ success: true,
10690
+ message: `Navigating to ${url} (cross-origin, debugger will disconnect)`,
10691
+ };
10692
+ }
10593
10693
  }
10594
10694
  catch (err) {
10595
- return { success: false, message: `Navigation failed: ${err.message ?? err}` };
10695
+ return {
10696
+ success: false,
10697
+ message: `Navigation failed: ${err.message ?? err}`,
10698
+ };
10596
10699
  }
10597
10700
  }
10598
10701
  navigate.__schema__ = {
10599
10702
  name: "navigate",
10600
- description: "Navigate the browser to a new URL.",
10703
+ description: "Navigate the browser to a new URL. For same-origin URLs, the debugger auto-reconnects. Cross-origin navigation will disconnect the debugger.",
10601
10704
  parameters: {
10602
10705
  type: "object",
10603
10706
  properties: {
@@ -10609,18 +10712,25 @@
10609
10712
  required: ["url"],
10610
10713
  },
10611
10714
  };
10715
+ // ── goBack / goForward ───────────────────────────────────────────────
10612
10716
  function goBack() {
10613
10717
  try {
10614
10718
  window.history.back();
10615
- return { success: true, message: "Navigated back" };
10719
+ return {
10720
+ success: true,
10721
+ message: "Navigated back (re-click bookmarklet to reconnect if needed)",
10722
+ };
10616
10723
  }
10617
10724
  catch (err) {
10618
- return { success: false, message: `Back navigation failed: ${err.message ?? err}` };
10725
+ return {
10726
+ success: false,
10727
+ message: `Back navigation failed: ${err.message ?? err}`,
10728
+ };
10619
10729
  }
10620
10730
  }
10621
10731
  goBack.__schema__ = {
10622
10732
  name: "goBack",
10623
- description: "Navigate back in browser history.",
10733
+ description: "Navigate back in browser history. The debugger may disconnect — re-click the bookmarklet to reconnect.",
10624
10734
  parameters: {
10625
10735
  type: "object",
10626
10736
  properties: {},
@@ -10629,7 +10739,10 @@
10629
10739
  function goForward() {
10630
10740
  try {
10631
10741
  window.history.forward();
10632
- return { success: true, message: "Navigated forward" };
10742
+ return {
10743
+ success: true,
10744
+ message: "Navigated forward (re-click bookmarklet to reconnect if needed)",
10745
+ };
10633
10746
  }
10634
10747
  catch (err) {
10635
10748
  return {
@@ -10640,16 +10753,21 @@
10640
10753
  }
10641
10754
  goForward.__schema__ = {
10642
10755
  name: "goForward",
10643
- description: "Navigate forward in browser history.",
10756
+ description: "Navigate forward in browser history. The debugger may disconnect — re-click the bookmarklet to reconnect.",
10644
10757
  parameters: {
10645
10758
  type: "object",
10646
10759
  properties: {},
10647
10760
  },
10648
10761
  };
10762
+ // ── reload ───────────────────────────────────────────────────────────
10649
10763
  function reload() {
10650
10764
  try {
10651
- window.location.reload();
10652
- return { success: true, message: "Reloading page" };
10765
+ // Schedule soft reload after RPC response is sent
10766
+ setTimeout(() => softReplace(location.href), 150);
10767
+ return {
10768
+ success: true,
10769
+ message: "Reloading page (debugger will auto-reconnect)",
10770
+ };
10653
10771
  }
10654
10772
  catch (err) {
10655
10773
  return { success: false, message: `Reload failed: ${err.message ?? err}` };
@@ -10657,7 +10775,7 @@
10657
10775
  }
10658
10776
  reload.__schema__ = {
10659
10777
  name: "reload",
10660
- description: "Reload the current page.",
10778
+ description: "Reload the current page. The debugger auto-reconnects after reload using soft page replacement.",
10661
10779
  parameters: {
10662
10780
  type: "object",
10663
10781
  properties: {},
@@ -13768,12 +13886,15 @@
13768
13886
  crypto.getRandomValues(arr);
13769
13887
  return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
13770
13888
  }
13889
+ /** sessionStorage key for persisting debugger config across reloads. */
13890
+ const STORAGE_KEY = "__hypha_debugger_config__";
13771
13891
  class HyphaDebugger {
13772
13892
  constructor(config) {
13773
13893
  this.overlay = null;
13774
13894
  this.cursor = null;
13775
13895
  this.server = null;
13776
13896
  this.serviceInfo = null;
13897
+ this.boundBeforeUnload = null;
13777
13898
  const requireToken = config.require_token ?? false;
13778
13899
  // Always append random suffix unless user provided a custom id.
13779
13900
  let serviceId = config.service_id ?? "web-debugger";
@@ -13833,6 +13954,10 @@
13833
13954
  // Store globally
13834
13955
  w.__HYPHA_DEBUGGER__ = w.__HYPHA_DEBUGGER__ ?? {};
13835
13956
  w.__HYPHA_DEBUGGER__.instance = this;
13957
+ // Persist config to sessionStorage for auto-reconnect after reload
13958
+ this.saveConfigToStorage();
13959
+ this.boundBeforeUnload = () => this.saveConfigToStorage();
13960
+ window.addEventListener("beforeunload", this.boundBeforeUnload);
13836
13961
  return session;
13837
13962
  }
13838
13963
  catch (err) {
@@ -13848,6 +13973,11 @@
13848
13973
  }
13849
13974
  }
13850
13975
  async destroy() {
13976
+ // Remove beforeunload listener
13977
+ if (this.boundBeforeUnload) {
13978
+ window.removeEventListener("beforeunload", this.boundBeforeUnload);
13979
+ this.boundBeforeUnload = null;
13980
+ }
13851
13981
  try {
13852
13982
  if (this.serviceInfo && this.server) {
13853
13983
  await this.server.unregisterService(this.serviceInfo.id);
@@ -13856,6 +13986,13 @@
13856
13986
  catch {
13857
13987
  // Ignore unregister errors on cleanup
13858
13988
  }
13989
+ // Clear sessionStorage config (explicit destroy = user wants to stop)
13990
+ try {
13991
+ sessionStorage.removeItem(STORAGE_KEY);
13992
+ }
13993
+ catch {
13994
+ // ignore
13995
+ }
13859
13996
  disposeController();
13860
13997
  this.cursor?.destroy();
13861
13998
  this.cursor = null;
@@ -13867,6 +14004,48 @@
13867
14004
  delete w.__HYPHA_DEBUGGER__.session;
13868
14005
  }
13869
14006
  }
14007
+ /**
14008
+ * Persist debugger config to sessionStorage so the debugger can
14009
+ * auto-reconnect after a page reload (soft reload injects the script,
14010
+ * autoStart() reads this config).
14011
+ */
14012
+ saveConfigToStorage() {
14013
+ try {
14014
+ const data = {
14015
+ server_url: this.config.server_url,
14016
+ workspace: this.server?.config?.workspace ?? this.config.workspace,
14017
+ token: this.config.token,
14018
+ service_id: this.config.service_id,
14019
+ service_name: this.config.service_name,
14020
+ show_ui: this.config.show_ui,
14021
+ visibility: this.config.visibility,
14022
+ require_token: this.config.require_token,
14023
+ script_url: this.detectScriptUrl(),
14024
+ };
14025
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(data));
14026
+ }
14027
+ catch {
14028
+ // sessionStorage might be unavailable (private browsing, full quota)
14029
+ }
14030
+ }
14031
+ /**
14032
+ * Detect the URL of the currently loaded hypha-debugger script.
14033
+ * Used by navigate.ts to inject the correct script after soft reload.
14034
+ */
14035
+ detectScriptUrl() {
14036
+ try {
14037
+ const scripts = document.querySelectorAll("script[src]");
14038
+ for (const s of Array.from(scripts)) {
14039
+ if (s.src && s.src.includes("hypha-debugger")) {
14040
+ return s.src;
14041
+ }
14042
+ }
14043
+ }
14044
+ catch {
14045
+ // ignore
14046
+ }
14047
+ return "https://cdn.jsdelivr.net/npm/hypha-debugger/dist/hypha-debugger.min.js";
14048
+ }
13870
14049
  /**
13871
14050
  * Generate token, build service URL, update overlay instructions, and
13872
14051
  * return a DebugSession.
@@ -14122,6 +14301,26 @@
14122
14301
  function autoStart() {
14123
14302
  if (typeof window === "undefined" || typeof document === "undefined")
14124
14303
  return;
14304
+ // Skip if already started
14305
+ if (window.__HYPHA_DEBUGGER__?.instance)
14306
+ return;
14307
+ // Check sessionStorage for saved config (auto-reconnect after soft reload)
14308
+ try {
14309
+ const saved = sessionStorage.getItem("__hypha_debugger_config__");
14310
+ if (saved) {
14311
+ const savedConfig = JSON.parse(saved);
14312
+ if (savedConfig.server_url) {
14313
+ console.log("[hypha-debugger] Reconnecting from saved session...");
14314
+ startDebugger(savedConfig).catch((err) => {
14315
+ console.error("[hypha-debugger] Auto-reconnect failed:", err);
14316
+ });
14317
+ return;
14318
+ }
14319
+ }
14320
+ }
14321
+ catch {
14322
+ // sessionStorage not available or parse error — continue to script tag detection
14323
+ }
14125
14324
  // Find our own script tag
14126
14325
  const scripts = document.querySelectorAll("script[src]");
14127
14326
  let scriptEl = null;
@@ -14134,9 +14333,6 @@
14134
14333
  // Skip if data-manual is set
14135
14334
  if (scriptEl?.hasAttribute("data-manual"))
14136
14335
  return;
14137
- // Skip if already started
14138
- if (window.__HYPHA_DEBUGGER__?.instance)
14139
- return;
14140
14336
  const serverUrl = scriptEl?.getAttribute("data-server-url") ?? "https://hypha.aicell.io";
14141
14337
  const config = {
14142
14338
  server_url: serverUrl,