@debugg-ai/debugg-ai-mcp 6.1.1 → 6.1.3

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.
@@ -608,7 +608,7 @@ async function testPageChangesHandlerInner(input, context, rawProgressCallback)
608
608
  // the response) must not POST a cancel for finished work. EXCEPTION: on a
609
609
  // poll-deadline timeout the execution may still be running backend-side,
610
610
  // so keep it cancellable.
611
- if (!finalExecution.timedOut)
611
+ if (!finalExecution.timedOut && !finalExecution.stalled)
612
612
  currentExecutionUuid = '';
613
613
  // Decide retry vs exit: only retry on documented transient signatures
614
614
  // AND while we still have budget. Otherwise break and surface whatever
@@ -617,7 +617,7 @@ async function testPageChangesHandlerInner(input, context, rawProgressCallback)
617
617
  break;
618
618
  // A poll-deadline timeout (bead 56kd.3) is never retried — surface the
619
619
  // partial result instead of burning another 10 minutes.
620
- if (finalExecution.timedOut)
620
+ if (finalExecution.timedOut || finalExecution.stalled)
621
621
  break;
622
622
  if (!isTransientWorkflowError(finalExecution))
623
623
  break;
@@ -641,6 +641,7 @@ async function testPageChangesHandlerInner(input, context, rawProgressCallback)
641
641
  // real numbers from the deadline path, so the reason is a record of what
642
642
  // we observed rather than a fixed sentence
643
643
  pollTimeout: timedOut ? finalExecution.pollTimeout : undefined,
644
+ stalled: finalExecution.stalled ? finalExecution.stall : undefined,
644
645
  });
645
646
  const relayActionTrace = verdict.actionTrace;
646
647
  // Evaluation: the backend derives it from the SAME verdict as the headline
@@ -150,6 +150,13 @@ export function credentialSubstitutions(logins, namedIdentities) {
150
150
  && typeof l.username === 'string' && l.username.trim() !== ''
151
151
  && !named.has(norm(l.username)));
152
152
  }
153
+ /** Backend agent loop was never started: an internal init bug, not a verdict on the user's page. */
154
+ const BRAIN_UNINITIALISED = /BrowserUseBrain\.initialize\(\)|must be called before decide_next_action/i;
155
+ export function stalledReason(facts) {
156
+ const where = facts.executionUuid ? ` (execution ${facts.executionUuid})` : '';
157
+ const secs = typeof facts.elapsedMs === 'number' ? ` after ${Math.round(facts.elapsedMs / 1000)}s` : '';
158
+ return `The run never started its agent loop${secs} (0 steps taken)${where}. This is a backend/browser-session start-up failure, not a result about your page; retry, and if it repeats report this execution id.`;
159
+ }
153
160
  /**
154
161
  * Map a workflow execution onto the MCP relay verdict. Never throws.
155
162
  */
@@ -166,8 +173,9 @@ export function adaptVerdict(execution, opts = {}) {
166
173
  const rawOutcome = opts.outcomeOverride ?? verdict?.outcome;
167
174
  const raw = typeof rawOutcome === 'string' ? rawOutcome.trim() : '';
168
175
  const isOurPollTimeout = opts.outcomeOverride === 'timeout';
176
+ const isStalled = !!opts.stalled;
169
177
  const backendReason = typeof verdict?.reason === 'string' && verdict.reason.trim() !== '' ? verdict.reason : null;
170
- if (!isOurPollTimeout && !raw) {
178
+ if (!isOurPollTimeout && !isStalled && !raw) {
171
179
  // A run that never attempted a test (platform-98fv.16): no verdict, and the
172
180
  // backend's recorded reason, verbatim.
173
181
  if (verdict?.skipped === true) {
@@ -189,7 +197,7 @@ export function adaptVerdict(execution, opts = {}) {
189
197
  // error covers: the backend's own 'error'/'timeout', every retired non-answer
190
198
  // (inconclusive/unverified/unknown/abandoned), anything a newer backend
191
199
  // invents, and a verdict that never arrived.
192
- const outcome = toUserFacingOutcome(raw);
200
+ const outcome = isStalled ? 'error' : toUserFacingOutcome(raw);
193
201
  const relay = { outcome };
194
202
  if (budget && typeof budget === 'object')
195
203
  relay.budget = budget;
@@ -202,13 +210,19 @@ export function adaptVerdict(execution, opts = {}) {
202
210
  // any reason we are holding came from a mid-flight poll — it describes a run
203
211
  // still in progress, not its result. Relaying it next to outcome='error'
204
212
  // would pair someone else's "looks good" with our failure.
205
- if (isOurPollTimeout) {
213
+ if (isStalled) {
214
+ relay.reason = stalledReason({ executionUuid: execution?.uuid, ...opts.stalled });
215
+ }
216
+ else if (isOurPollTimeout) {
206
217
  relay.reason = pollTimeoutReason({
207
218
  executionUuid: execution?.uuid,
208
219
  lastStatus: execution?.status,
209
220
  ...(opts.pollTimeout ?? {}),
210
221
  });
211
222
  }
223
+ else if (backendReason && BRAIN_UNINITIALISED.test(backendReason)) {
224
+ relay.reason = `${stalledReason({ executionUuid: execution?.uuid, elapsedMs: execution?.durationMs ?? undefined })} Backend said: ${backendReason}`;
225
+ }
212
226
  else if (backendReason) {
213
227
  relay.reason = backendReason;
214
228
  }
@@ -13,6 +13,15 @@ const POLL_INTERVAL_INITIAL_MS = 1000;
13
13
  const POLL_INTERVAL_MAX_MS = 5000;
14
14
  const POLL_BACKOFF_MULTIPLIER = 1.5;
15
15
  const EXECUTION_TIMEOUT_MS = 10 * 60 * 1000; // 10 min
16
+ // A run that is still non-terminal with ZERO agent steps this long after we
17
+ // started polling is not "slow", it is wedged (e.g. the backend brain never
18
+ // initialised, so the agent loop never starts). Stop waiting and say so rather
19
+ // than burning the full run budget. Override with DEBUGGAI_NO_PROGRESS_TIMEOUT_MS.
20
+ const DEFAULT_NO_PROGRESS_TIMEOUT_MS = 4 * 60 * 1000;
21
+ const noProgressTimeoutMs = () => {
22
+ const n = Number(process.env.DEBUGGAI_NO_PROGRESS_TIMEOUT_MS);
23
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_NO_PROGRESS_TIMEOUT_MS;
24
+ };
16
25
  /**
17
26
  * Stable dispatch slugs for the three MCP browser workflows (bug clka /
18
27
  * bead 56kd.8). All three handlers pin to a STABLE slug so a backend
@@ -145,6 +154,24 @@ export const createWorkflowsService = (tx) => {
145
154
  if (signal?.aborted) {
146
155
  throw new Error(`Polling cancelled for execution ${executionUuid}`);
147
156
  }
157
+ const hasSteps = (execution.state?.stepsTaken ?? 0) > 0 ||
158
+ (execution.nodeExecutions ?? []).some(n => n.nodeType === 'brain.step');
159
+ if (!hasSteps && Date.now() - pollStart >= noProgressTimeoutMs()) {
160
+ Telemetry.capture(TelemetryEvents.WORKFLOW_EXECUTED, {
161
+ status: execution.status,
162
+ success: false,
163
+ outcome: 'stalled',
164
+ stepsTaken: 0,
165
+ durationMs: Date.now() - pollStart,
166
+ pollCount,
167
+ finalIntervalMs: intervalMs,
168
+ });
169
+ return {
170
+ ...execution,
171
+ stalled: true,
172
+ stall: { elapsedMs: Date.now() - pollStart, pollCount },
173
+ };
174
+ }
148
175
  const sleepMs = intervalMs;
149
176
  await new Promise((resolve, reject) => {
150
177
  const timer = setTimeout(resolve, sleepMs);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@debugg-ai/debugg-ai-mcp",
3
- "version": "6.1.1",
3
+ "version": "6.1.3",
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",