@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.
- package/CHANGELOG.md +42 -0
- package/README.md +53 -1
- package/dist/handlers/environmentHandler.js +15 -0
- package/dist/handlers/environmentSessionsHandler.js +60 -0
- package/dist/handlers/probePageHandler.js +252 -139
- package/dist/handlers/runTestSuiteHandler.js +70 -51
- package/dist/handlers/testPageChangesHandler.js +33 -1
- package/dist/handlers/triggerCrawlHandler.js +26 -1
- package/dist/services/caddy/caddyProxy.js +611 -0
- package/dist/services/caddy/portLock.js +321 -0
- package/dist/services/index.js +31 -0
- package/dist/services/ngrok/tunnelManager.js +526 -625
- package/dist/services/ngrok/tunnelRegistry.js +57 -70
- package/dist/tools/environment.js +9 -3
- package/dist/tools/testPageChanges.js +4 -0
- package/dist/types/index.js +11 -0
- package/dist/utils/confirmDestructive.js +32 -6
- package/dist/utils/telemetry.js +16 -0
- package/dist/utils/tunnelContext.js +52 -15
- package/dist/utils/tunnelDisposition.js +9 -17
- package/package.json +3 -1
|
@@ -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) {
|