@debugg-ai/debugg-ai-mcp 3.7.3 → 3.7.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.
@@ -149,7 +149,16 @@ export async function probePageHandler(input, context, rawProgressCallback) {
149
149
  },
150
150
  };
151
151
  if (tunneled.tunnelId) {
152
- tunnelManager.stopTunnel(tunneled.tunnelId).catch((err) => logger.warn(`Failed to stop broken tunnel ${tunneled.tunnelId}: ${err}`));
152
+ const deadPort = extractLocalhostPort(tunneled.originalUrl);
153
+ if (health.ngrokErrorCode && typeof deadPort === 'number') {
154
+ // Tunnel-level dead (ERR_NGROK_*): evict the SHARED registry entry
155
+ // so the next call re-provisions instead of re-borrowing the corpse
156
+ // (bead k34o) — plain stopTunnel leaves a borrowed entry to re-poison.
157
+ tunnelManager.markTunnelDead(deadPort, tunneled.tunnelId).catch((err) => logger.warn(`Failed to evict dead tunnel ${tunneled.tunnelId}: ${err}`));
158
+ }
159
+ else {
160
+ tunnelManager.stopTunnel(tunneled.tunnelId).catch((err) => logger.warn(`Failed to stop broken tunnel ${tunneled.tunnelId}: ${err}`));
161
+ }
153
162
  }
154
163
  return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }], isError: true };
155
164
  }
@@ -256,10 +256,17 @@ async function testPageChangesHandlerInner(input, context, rawProgressCallback)
256
256
  };
257
257
  logger.warn(`Tunnel health probe failed for ${ctx.targetUrl}: ${health.code} ${health.ngrokErrorCode ?? ''} in ${health.elapsedMs}ms`);
258
258
  // Tear down the broken tunnel so a subsequent call doesn't reuse it.
259
- // stopTunnel handles both owned (ngrok disconnect + key revoke) and
260
- // borrowed (just drops local ref) cases.
261
259
  if (ctx.tunnelId) {
262
- tunnelManager.stopTunnel(ctx.tunnelId).catch((err) => logger.warn(`Failed to stop broken tunnel ${ctx.tunnelId}: ${err}`));
260
+ const deadPort = extractLocalhostPort(ctx.originalUrl);
261
+ if (health.ngrokErrorCode && typeof deadPort === 'number') {
262
+ // Tunnel-level dead (ERR_NGROK_*): evict the SHARED registry entry
263
+ // so the next call re-provisions instead of re-borrowing the corpse.
264
+ // Plain stopTunnel leaves a borrowed entry to re-poison (bead k34o).
265
+ tunnelManager.markTunnelDead(deadPort, ctx.tunnelId).catch((err) => logger.warn(`Failed to evict dead tunnel ${ctx.tunnelId}: ${err}`));
266
+ }
267
+ else {
268
+ tunnelManager.stopTunnel(ctx.tunnelId).catch((err) => logger.warn(`Failed to stop broken tunnel ${ctx.tunnelId}: ${err}`));
269
+ }
263
270
  }
264
271
  // keyId is consumed by stopTunnel's revoke path; clear so the
265
272
  // outer finally block doesn't double-revoke.
@@ -138,7 +138,16 @@ export async function triggerCrawlHandler(input, context, rawProgressCallback) {
138
138
  };
139
139
  logger.warn(`Tunnel health probe failed for ${ctx.targetUrl}: ${health.code} ${health.ngrokErrorCode ?? ''} in ${health.elapsedMs}ms`);
140
140
  if (ctx.tunnelId) {
141
- tunnelManager.stopTunnel(ctx.tunnelId).catch((err) => logger.warn(`Failed to stop broken tunnel ${ctx.tunnelId}: ${err}`));
141
+ const deadPort = extractLocalhostPort(ctx.originalUrl);
142
+ if (health.ngrokErrorCode && typeof deadPort === 'number') {
143
+ // Tunnel-level dead (ERR_NGROK_*): evict the SHARED registry entry so
144
+ // the next call re-provisions instead of re-borrowing the corpse — plain
145
+ // stopTunnel leaves a borrowed entry to re-poison (bead k34o).
146
+ tunnelManager.markTunnelDead(deadPort, ctx.tunnelId).catch((err) => logger.warn(`Failed to evict dead tunnel ${ctx.tunnelId}: ${err}`));
147
+ }
148
+ else {
149
+ tunnelManager.stopTunnel(ctx.tunnelId).catch((err) => logger.warn(`Failed to stop broken tunnel ${ctx.tunnelId}: ${err}`));
150
+ }
142
151
  }
143
152
  keyId = undefined;
144
153
  return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }], isError: true };
@@ -148,6 +148,42 @@ class TunnelManager {
148
148
  }
149
149
  return existing;
150
150
  }
151
+ /**
152
+ * Evict a tunnel that a health probe PROVED dead (e.g. ERR_NGROK_3200) so no
153
+ * session borrows the corpse again (bead k34o).
154
+ *
155
+ * OWNED: delegate to stopTunnel — it already removes the registry entry,
156
+ * disconnects, revokes the key, and resets the agent. (Self-heals after one
157
+ * failure, which already worked.)
158
+ *
159
+ * BORROWED (the actual gap): stopTunnel only drops our local ref and leaves the
160
+ * SHARED registry entry, so every other session keeps re-borrowing the dead
161
+ * tunnel for up to the 30-min freshness TTL. Here we also evict the shared
162
+ * entry — guarded by tunnelId so a replacement another session just provisioned
163
+ * for the same port is never removed. Best-effort, never throws.
164
+ */
165
+ async markTunnelDead(port, tunnelId) {
166
+ const local = this.activeTunnels.get(tunnelId);
167
+ if (local?.isOwned) {
168
+ await this.stopTunnel(tunnelId).catch(() => { });
169
+ return;
170
+ }
171
+ // Borrowed or no longer local — drop any local ref, then evict the shared entry.
172
+ if (local?.autoShutoffTimer)
173
+ clearTimeout(local.autoShutoffTimer);
174
+ this.activeTunnels.delete(tunnelId);
175
+ try {
176
+ const registry = this.reg.read();
177
+ if (registry[String(port)]?.tunnelId === tunnelId) {
178
+ delete registry[String(port)];
179
+ this.reg.write(registry);
180
+ logger.info(`Evicted dead borrowed tunnel ${tunnelId} for port ${port} from shared registry`);
181
+ }
182
+ }
183
+ catch {
184
+ // best-effort — a failed eviction just means the next call re-probes and re-evicts
185
+ }
186
+ }
151
187
  touchTunnel(tunnelId) {
152
188
  const tunnelInfo = this.activeTunnels.get(tunnelId);
153
189
  if (!tunnelInfo)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@debugg-ai/debugg-ai-mcp",
3
- "version": "3.7.3",
3
+ "version": "3.7.4",
4
4
  "description": "Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.",
5
5
  "type": "module",
6
6
  "bin": {