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.
@@ -2127,20 +2127,123 @@ executeScript.__schema__ = {
2127
2127
  };
2128
2128
 
2129
2129
  /**
2130
- * 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.
2131
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.
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 ──────────────────────────────────────────────────────────
2132
2215
  function navigate(url) {
2133
2216
  try {
2134
- window.location.href = url;
2135
- 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
+ }
2136
2236
  }
2137
2237
  catch (err) {
2138
- return { success: false, message: `Navigation failed: ${err.message ?? err}` };
2238
+ return {
2239
+ success: false,
2240
+ message: `Navigation failed: ${err.message ?? err}`,
2241
+ };
2139
2242
  }
2140
2243
  }
2141
2244
  navigate.__schema__ = {
2142
2245
  name: "navigate",
2143
- 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.",
2144
2247
  parameters: {
2145
2248
  type: "object",
2146
2249
  properties: {
@@ -2152,18 +2255,25 @@ navigate.__schema__ = {
2152
2255
  required: ["url"],
2153
2256
  },
2154
2257
  };
2258
+ // ── goBack / goForward ───────────────────────────────────────────────
2155
2259
  function goBack() {
2156
2260
  try {
2157
2261
  window.history.back();
2158
- return { success: true, message: "Navigated back" };
2262
+ return {
2263
+ success: true,
2264
+ message: "Navigated back (re-click bookmarklet to reconnect if needed)",
2265
+ };
2159
2266
  }
2160
2267
  catch (err) {
2161
- return { success: false, message: `Back navigation failed: ${err.message ?? err}` };
2268
+ return {
2269
+ success: false,
2270
+ message: `Back navigation failed: ${err.message ?? err}`,
2271
+ };
2162
2272
  }
2163
2273
  }
2164
2274
  goBack.__schema__ = {
2165
2275
  name: "goBack",
2166
- description: "Navigate back in browser history.",
2276
+ description: "Navigate back in browser history. The debugger may disconnect — re-click the bookmarklet to reconnect.",
2167
2277
  parameters: {
2168
2278
  type: "object",
2169
2279
  properties: {},
@@ -2172,7 +2282,10 @@ goBack.__schema__ = {
2172
2282
  function goForward() {
2173
2283
  try {
2174
2284
  window.history.forward();
2175
- return { success: true, message: "Navigated forward" };
2285
+ return {
2286
+ success: true,
2287
+ message: "Navigated forward (re-click bookmarklet to reconnect if needed)",
2288
+ };
2176
2289
  }
2177
2290
  catch (err) {
2178
2291
  return {
@@ -2183,16 +2296,21 @@ function goForward() {
2183
2296
  }
2184
2297
  goForward.__schema__ = {
2185
2298
  name: "goForward",
2186
- description: "Navigate forward in browser history.",
2299
+ description: "Navigate forward in browser history. The debugger may disconnect — re-click the bookmarklet to reconnect.",
2187
2300
  parameters: {
2188
2301
  type: "object",
2189
2302
  properties: {},
2190
2303
  },
2191
2304
  };
2305
+ // ── reload ───────────────────────────────────────────────────────────
2192
2306
  function reload() {
2193
2307
  try {
2194
- window.location.reload();
2195
- return { success: true, message: "Reloading page" };
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
+ };
2196
2314
  }
2197
2315
  catch (err) {
2198
2316
  return { success: false, message: `Reload failed: ${err.message ?? err}` };
@@ -2200,7 +2318,7 @@ function reload() {
2200
2318
  }
2201
2319
  reload.__schema__ = {
2202
2320
  name: "reload",
2203
- description: "Reload the current page.",
2321
+ description: "Reload the current page. The debugger auto-reconnects after reload using soft page replacement.",
2204
2322
  parameters: {
2205
2323
  type: "object",
2206
2324
  properties: {},
@@ -5311,12 +5429,15 @@ function randomHex(bytes = 8) {
5311
5429
  crypto.getRandomValues(arr);
5312
5430
  return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
5313
5431
  }
5432
+ /** sessionStorage key for persisting debugger config across reloads. */
5433
+ const STORAGE_KEY = "__hypha_debugger_config__";
5314
5434
  class HyphaDebugger {
5315
5435
  constructor(config) {
5316
5436
  this.overlay = null;
5317
5437
  this.cursor = null;
5318
5438
  this.server = null;
5319
5439
  this.serviceInfo = null;
5440
+ this.boundBeforeUnload = null;
5320
5441
  const requireToken = config.require_token ?? false;
5321
5442
  // Always append random suffix unless user provided a custom id.
5322
5443
  let serviceId = config.service_id ?? "web-debugger";
@@ -5376,6 +5497,10 @@ class HyphaDebugger {
5376
5497
  // Store globally
5377
5498
  w.__HYPHA_DEBUGGER__ = w.__HYPHA_DEBUGGER__ ?? {};
5378
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);
5379
5504
  return session;
5380
5505
  }
5381
5506
  catch (err) {
@@ -5391,6 +5516,11 @@ class HyphaDebugger {
5391
5516
  }
5392
5517
  }
5393
5518
  async destroy() {
5519
+ // Remove beforeunload listener
5520
+ if (this.boundBeforeUnload) {
5521
+ window.removeEventListener("beforeunload", this.boundBeforeUnload);
5522
+ this.boundBeforeUnload = null;
5523
+ }
5394
5524
  try {
5395
5525
  if (this.serviceInfo && this.server) {
5396
5526
  await this.server.unregisterService(this.serviceInfo.id);
@@ -5399,6 +5529,13 @@ class HyphaDebugger {
5399
5529
  catch {
5400
5530
  // Ignore unregister errors on cleanup
5401
5531
  }
5532
+ // Clear sessionStorage config (explicit destroy = user wants to stop)
5533
+ try {
5534
+ sessionStorage.removeItem(STORAGE_KEY);
5535
+ }
5536
+ catch {
5537
+ // ignore
5538
+ }
5402
5539
  disposeController();
5403
5540
  this.cursor?.destroy();
5404
5541
  this.cursor = null;
@@ -5410,6 +5547,48 @@ class HyphaDebugger {
5410
5547
  delete w.__HYPHA_DEBUGGER__.session;
5411
5548
  }
5412
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
+ }
5413
5592
  /**
5414
5593
  * Generate token, build service URL, update overlay instructions, and
5415
5594
  * return a DebugSession.
@@ -5665,6 +5844,26 @@ async function startDebugger(config) {
5665
5844
  function autoStart() {
5666
5845
  if (typeof window === "undefined" || typeof document === "undefined")
5667
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
+ }
5668
5867
  // Find our own script tag
5669
5868
  const scripts = document.querySelectorAll("script[src]");
5670
5869
  let scriptEl = null;
@@ -5677,9 +5876,6 @@ function autoStart() {
5677
5876
  // Skip if data-manual is set
5678
5877
  if (scriptEl?.hasAttribute("data-manual"))
5679
5878
  return;
5680
- // Skip if already started
5681
- if (window.__HYPHA_DEBUGGER__?.instance)
5682
- return;
5683
5879
  const serverUrl = scriptEl?.getAttribute("data-server-url") ?? "https://hypha.aicell.io";
5684
5880
  const config = {
5685
5881
  server_url: serverUrl,