@debugg-ai/debugg-ai-mcp 3.10.0 → 4.1.0

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.
@@ -11,7 +11,8 @@ import { DebuggAIServerClient } from '../services/index.js';
11
11
  import { getEvalTemplateSlug } from '../services/workflows.js';
12
12
  import { adaptVerdict, isEnvironmentDefault } from '../services/verdictAdapter.js';
13
13
  import { TunnelProvisionError } from '../services/tunnels.js';
14
- import { resolveTargetUrl, buildContext, findExistingTunnel, ensureTunnel, sanitizeResponseUrls, touchTunnelById, } from '../utils/tunnelContext.js';
14
+ import { resolveTargetUrl, buildContext, findExistingTunnel, ensureTunnel, acquirePortRoute, releasePortRoute, sanitizeResponseUrls, touchTunnelById, } from '../utils/tunnelContext.js';
15
+ import { randomUUID } from 'node:crypto';
15
16
  import { detectRepoName } from '../utils/gitContext.js';
16
17
  import { disposeUnhealthyTunnel } from '../utils/tunnelDisposition.js';
17
18
  import { probeLocalPort, probeTunnelHealth, extractNgrokErrorCode } from '../utils/localReachability.js';
@@ -175,6 +176,9 @@ async function testPageChangesHandlerInner(input, context, rawProgressCallback)
175
176
  logger.warn(`Best-effort cancel of abandoned execution ${uuid} threw synchronously: ${err}`);
176
177
  }
177
178
  };
179
+ // Unique per-call id for the shared port-route lock's holder bookkeeping
180
+ // (§2.4) — reused for abort wiring purposes elsewhere in this handler.
181
+ const callId = randomUUID();
178
182
  const abortController = new AbortController();
179
183
  const onAbort = () => {
180
184
  clientAborted = true;
@@ -257,6 +261,24 @@ async function testPageChangesHandlerInner(input, context, rawProgressCallback)
257
261
  }
258
262
  logger.info(`Tunnel ready: ${ctx.targetUrl} (id: ${ctx.tunnelId})`);
259
263
  }
264
+ // §2.4: acquire this session's shared Caddy route for our port BEFORE
265
+ // probing/dispatching — probing before the repoint is confirmed would
266
+ // probe whatever port happened to be active a moment ago. Blocks here
267
+ // (not just during the repoint) until any different-port holder in
268
+ // this same session releases.
269
+ ctx = await acquirePortRoute(ctx, {
270
+ callId,
271
+ signal: abortController.signal,
272
+ onWaitProgress: progressCallback
273
+ ? async (info) => {
274
+ await progressCallback({
275
+ progress: 1,
276
+ total: TOTAL_STEPS,
277
+ message: `Waiting for shared tunnel — port ${info.blockingPort} is in use (waited ${Math.round(info.waitedMs / 1000)}s)...`,
278
+ });
279
+ }
280
+ : undefined,
281
+ });
260
282
  // Bead 1om: verify traffic actually flows through the tunnel. The
261
283
  // tunnel can be established (ngrok.connect returns OK) yet refuse
262
284
  // to forward traffic — e.g., IPv4/IPv6 bind mismatch, or the dev
@@ -397,6 +419,12 @@ async function testPageChangesHandlerInner(input, context, rawProgressCallback)
397
419
  if (input.useEnvironmentCredentials === false) {
398
420
  env.useEnvironmentCredentials = false;
399
421
  }
422
+ // Same rule for the session opt-out: send it only when it IS one, so the
423
+ // default (reuse a warm session when one exists for this account) is
424
+ // expressed by absence rather than by an explicit false.
425
+ if (input.freshSession === true) {
426
+ env.freshSession = true;
427
+ }
400
428
  // --- Execute ---
401
429
  // Log the SHAPE of env, never its secrets. It now carries per-account
402
430
  // passwords (taskCredentials), and this log line is not run through the
@@ -913,6 +941,10 @@ async function testPageChangesHandlerInner(input, context, rawProgressCallback)
913
941
  finally {
914
942
  if (requestSignal)
915
943
  requestSignal.removeEventListener('abort', onAbort);
944
+ // §2.4: release this call's claim on the shared port route (no-op if we
945
+ // never acquired one — public URL, dev mode, or an early-return before
946
+ // acquisition). Covers every early-return path above for free.
947
+ releasePortRoute(ctx);
916
948
  // Tunnel is intentionally NOT torn down here — tunnelManager reuses it on
917
949
  // subsequent calls to the same port and auto-shutoffs after 55 min idle.
918
950
  // Process-exit cleanup happens via stopAllTunnels() in the SIGINT/SIGTERM
@@ -18,7 +18,8 @@ import { TunnelProvisionError } from '../services/tunnels.js';
18
18
  import { disposeUnhealthyTunnel } from '../utils/tunnelDisposition.js';
19
19
  import { probeLocalPort, probeTunnelHealth } from '../utils/localReachability.js';
20
20
  import { extractLocalhostPort } from '../utils/urlParser.js';
21
- import { resolveTargetUrl, buildContext, findExistingTunnel, ensureTunnel, sanitizeResponseUrls, touchTunnelById, } from '../utils/tunnelContext.js';
21
+ import { resolveTargetUrl, buildContext, findExistingTunnel, ensureTunnel, acquirePortRoute, releasePortRoute, sanitizeResponseUrls, touchTunnelById, } from '../utils/tunnelContext.js';
22
+ import { randomUUID } from 'node:crypto';
22
23
  import { getCachedTemplateUuid, invalidateTemplateCache } from '../utils/handlerCaches.js';
23
24
  import { detectLocalGitRef } from '../utils/gitContext.js';
24
25
  import { getCrawlTemplateSlug } from '../services/workflows.js';
@@ -67,6 +68,8 @@ export async function triggerCrawlHandler(input, context, rawProgressCallback) {
67
68
  // we get: stdin is not the transport, so the old stdin 'close' listener never
68
69
  // fired and a dropped client kept polling for up to ~10 min. Wiring to
69
70
  // context.signal cancels the poll immediately (parity with 56kd.5).
71
+ // Unique per-call id for the shared port-route lock's holder bookkeeping (§2.4).
72
+ const callId = randomUUID();
70
73
  const abortController = new AbortController();
71
74
  const onAbort = () => {
72
75
  abortController.abort();
@@ -123,6 +126,25 @@ export async function triggerCrawlHandler(input, context, rawProgressCallback) {
123
126
  keyId = tunnel.keyId;
124
127
  ctx = await ensureTunnel(ctx, tunnel.tunnelKey, tunnel.tunnelId, tunnel.keyId, () => client.revokeNgrokKey(tunnel.keyId));
125
128
  }
129
+ // §2.4: acquire this session's shared Caddy route for our port BEFORE
130
+ // probing/dispatching — trigger_crawl polls (pollExecution below) for
131
+ // the full duration real backend traffic can occur, exactly like
132
+ // check_app_in_browser/probe_page, so it holds this lock for the
133
+ // whole call, not just the repoint (verified: it is NOT a
134
+ // fire-and-forget exception — only run_test_suite is, §2.3).
135
+ ctx = await acquirePortRoute(ctx, {
136
+ callId,
137
+ signal: abortController.signal,
138
+ onWaitProgress: progressCallback
139
+ ? async (info) => {
140
+ await progressCallback({
141
+ progress: 1,
142
+ total: 4,
143
+ message: `Waiting for shared tunnel — port ${info.blockingPort} is in use (waited ${Math.round(info.waitedMs / 1000)}s)...`,
144
+ });
145
+ }
146
+ : undefined,
147
+ });
126
148
  // Bead 1om: post-tunnel health check — verify traffic actually flows.
127
149
  if (ctx.targetUrl) {
128
150
  const health = await probeTunnelHealth(ctx.targetUrl);
@@ -336,6 +358,9 @@ export async function triggerCrawlHandler(input, context, rawProgressCallback) {
336
358
  finally {
337
359
  if (requestSignal)
338
360
  requestSignal.removeEventListener('abort', onAbort);
361
+ // §2.4: release this call's claim on the shared port route (no-op if we
362
+ // never acquired one).
363
+ releasePortRoute(ctx);
339
364
  // Tunnel intentionally NOT torn down (reuse path per bead vwd).
340
365
  // If tunnel creation failed after key provision, revoke the orphaned key.
341
366
  if (!ctx.tunnelId && keyId) {