@debugg-ai/debugg-ai-mcp 4.2.1 → 4.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.
@@ -12,7 +12,7 @@ import { DebuggAIServerClient } from '../services/index.js';
12
12
  import { getEvalTemplateSlug } from '../services/workflows.js';
13
13
  import { adaptVerdict, isEnvironmentDefault } from '../services/verdictAdapter.js';
14
14
  import { TunnelProvisionError } from '../services/tunnels.js';
15
- import { resolveTargetUrl, buildContext, findExistingTunnel, ensureTunnel, acquirePortRoute, releasePortRoute, sanitizeResponseUrls, touchTunnelById, } from '../utils/tunnelContext.js';
15
+ import { resolveTargetUrl, buildContext, findExistingTunnel, ensureTunnel, acquirePortRoute, releasePortRoute, sanitizeResponseUrls, touchTunnelById, retargetAuxiliaryUrl, } from '../utils/tunnelContext.js';
16
16
  import { randomUUID } from 'node:crypto';
17
17
  import { detectRepoName } from '../utils/gitContext.js';
18
18
  import { disposeUnhealthyTunnel } from '../utils/tunnelDisposition.js';
@@ -379,10 +379,33 @@ async function testPageChangesHandlerInner(input, context, rawProgressCallback)
379
379
  auth.environmentId = input.auth.environmentId;
380
380
  if (input.auth.precondition)
381
381
  auth.precondition = input.auth.precondition;
382
- if (input.auth.entryUrl)
383
- auth.entryUrl = input.auth.entryUrl;
384
- if (input.auth.deepUrl)
385
- auth.deepUrl = input.auth.deepUrl;
382
+ // Bead go1m: entryUrl/deepUrl are URLs the run NAVIGATES, so they need the
383
+ // same localhost→tunnel rewrite `url` gets (contextData.targetUrl above).
384
+ // Forwarded verbatim they reached the remote browser as literal localhost
385
+ // and it dialled its own loopback: offscope_host + ERR_CONNECTION_REFUSED.
386
+ for (const field of ['entryUrl', 'deepUrl']) {
387
+ const supplied = input.auth[field];
388
+ if (!supplied)
389
+ continue;
390
+ const rewrite = retargetAuxiliaryUrl(ctx, supplied);
391
+ if (!rewrite.ok) {
392
+ const payload = {
393
+ error: 'AuthUrlPortMismatch',
394
+ message: `auth.${field} points at localhost:${rewrite.port} but url points at ` +
395
+ `localhost:${rewrite.primaryPort}. A single call tunnels exactly one local ` +
396
+ `port, so the remote browser cannot reach both. Put the login page and the ` +
397
+ `target page on the same port, or drop auth.${field} and pass username/password ` +
398
+ `at the top level so the agent signs in on the page it already reached.`,
399
+ detail: { field, authPort: rewrite.port, urlPort: rewrite.primaryPort, url: originalUrl },
400
+ };
401
+ logger.warn(`check_app_in_browser: ${payload.message}`);
402
+ return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }], isError: true };
403
+ }
404
+ auth[field] = rewrite.url;
405
+ if (rewrite.rewritten) {
406
+ logger.info(`check_app_in_browser: tunneled auth.${field} ${supplied} -> ${rewrite.url}`);
407
+ }
408
+ }
386
409
  // WHICH account the precondition logs in as. Absent → the environment's
387
410
  // default credential (the pre-existing behaviour, correct for a caller
388
411
  // that named nobody).
@@ -69,6 +69,46 @@ export async function ensureTunnel(ctx, tunnelKey, tunnelId, keyId, revokeKey) {
69
69
  const info = await tunnelManager.ensureSessionTunnel(getSessionKey(), tunnelKey, tunnelId, keyId, revokeKey);
70
70
  return { ...ctx, tunnelId: info.tunnelId, targetUrl: retargetTunnelUrl(info.tunnelUrl, ctx.originalUrl) };
71
71
  }
72
+ /**
73
+ * Retarget an auxiliary URL the CALLER supplied (`auth.entryUrl`, `auth.deepUrl`)
74
+ * onto the same tunnel the run's primary `url` already uses.
75
+ *
76
+ * Bead go1m: only the top-level `url` ever went through the localhost→tunnel
77
+ * rewrite. `auth.entryUrl`/`auth.deepUrl` were forwarded verbatim, so a
78
+ * localhost entry URL reached the REMOTE browser as a literal `localhost:PORT`
79
+ * and it dialled its own loopback — `offscope_host` + ERR_CONNECTION_REFUSED on
80
+ * the documented "log in THEN deep-navigate" path. Every URL the run may
81
+ * navigate has to go through the same rewrite as `url`.
82
+ *
83
+ * Three cases, deliberately distinguished:
84
+ * - not localhost (public URL, or dev mode / no tunnel) → returned unchanged.
85
+ * Matches how the primary `url` is treated in those same conditions.
86
+ * - localhost on the SAME port as the primary URL → retargeted onto the
87
+ * tunnel origin, preserving path + query + hash (`retargetTunnelUrl`).
88
+ * - localhost on a DIFFERENT port → REFUSED, never silently retargeted. The
89
+ * session's single Caddy upstream is pointed at the primary URL's port for
90
+ * the whole call (see `acquirePortRoute`), so rewriting a cross-port URL
91
+ * onto the same origin would send the login navigation to whichever port
92
+ * Caddy happens to hold — the silent misdirection the port lock exists to
93
+ * prevent. Fail fast and tell the caller instead.
94
+ */
95
+ export function retargetAuxiliaryUrl(ctx, auxUrl) {
96
+ // No tunnel in play (public target, or dev mode where the backend reaches
97
+ // localhost directly): the primary URL isn't rewritten either, so neither is this.
98
+ if (!ctx.isLocalhost || !ctx.tunnelId || !ctx.targetUrl) {
99
+ return { ok: true, url: auxUrl, rewritten: false };
100
+ }
101
+ // A public auxiliary URL is reachable by the remote browser as-is.
102
+ if (!isLocalhostUrl(auxUrl)) {
103
+ return { ok: true, url: auxUrl, rewritten: false };
104
+ }
105
+ const port = extractLocalhostPort(auxUrl);
106
+ const primaryPort = extractLocalhostPort(ctx.originalUrl);
107
+ if (port !== undefined && primaryPort !== undefined && port !== primaryPort) {
108
+ return { ok: false, reason: 'port_mismatch', port, primaryPort };
109
+ }
110
+ return { ok: true, url: retargetTunnelUrl(ctx.targetUrl, auxUrl), rewritten: true };
111
+ }
72
112
  // ─── Port route lock (§2.4) ──────────────────────────────────────────────────
73
113
  /**
74
114
  * Acquire this session's shared Caddy route for `ctx`'s port, blocking until
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@debugg-ai/debugg-ai-mcp",
3
- "version": "4.2.1",
3
+ "version": "4.2.2",
4
4
  "description": "Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.",
5
5
  "type": "module",
6
6
  "caddy": "2.11.3",