@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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|