@debugg-ai/debugg-ai-mcp 6.0.1 → 6.0.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.
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* and reports back what happened.
|
|
12
12
|
*/
|
|
13
13
|
import { config } from '../config/index.js';
|
|
14
|
-
import {
|
|
14
|
+
import { adaptVerdict } from '../services/verdictAdapter.js';
|
|
15
15
|
import { Logger } from '../utils/logger.js';
|
|
16
16
|
import { handleExternalServiceError } from '../utils/errors.js';
|
|
17
17
|
import { DebuggAIServerClient } from '../services/index.js';
|
|
@@ -290,24 +290,24 @@ export async function triggerCrawlHandler(input, context, rawProgressCallback) {
|
|
|
290
290
|
targetUrl: ctx.originalUrl,
|
|
291
291
|
durationMs: finalExecution.durationMs ?? duration,
|
|
292
292
|
};
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
293
|
+
// THE run verdict (platform-4cvj.72): the backend's recorded `verdict`, through
|
|
294
|
+
// the same adaptVerdict path check_app_in_browser uses — never the raw
|
|
295
|
+
// `state.outcome` (the run's internal record), which can say pass for a run
|
|
296
|
+
// the verdict calls error. No verdict yet (still running) -> outcome null.
|
|
297
|
+
const relay = adaptVerdict(finalExecution);
|
|
298
|
+
responsePayload.outcome = relay.outcome;
|
|
299
|
+
if (relay.skipped) {
|
|
298
300
|
// platform-98fv.16: the run never attempted a test — no verdict, the
|
|
299
|
-
// backend's recorded reason verbatim.
|
|
300
|
-
responsePayload.outcome = null;
|
|
301
|
+
// backend's recorded reason verbatim.
|
|
301
302
|
responsePayload.skipped = true;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
responsePayload.skipReason = skipReason;
|
|
303
|
+
if (relay.skipReason)
|
|
304
|
+
responsePayload.skipReason = relay.skipReason;
|
|
305
305
|
}
|
|
306
|
-
else if (
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
306
|
+
else if (relay.outcome === 'error' && relay.reason) {
|
|
307
|
+
const backendReason = typeof finalExecution.verdict?.reason === 'string'
|
|
308
|
+
&& finalExecution.verdict.reason.trim() !== '';
|
|
309
|
+
if (backendReason || !finalExecution.errorMessage)
|
|
310
|
+
responsePayload.reason = relay.reason;
|
|
311
311
|
}
|
|
312
312
|
if (finalExecution.errorMessage)
|
|
313
313
|
responsePayload.errorMessage = finalExecution.errorMessage;
|
package/dist/services/index.js
CHANGED
|
@@ -41,6 +41,22 @@ function pickAuthorizedCredentialHosts(e) {
|
|
|
41
41
|
? { authorizedCredentialHosts: e.authorizedCredentialHosts }
|
|
42
42
|
: {};
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The outcome a suite's run SHOWS (platform-4cvj.72): the run's recorded verdict
|
|
46
|
+
* when the backend sent one, else its raw `outcome`, both through the user-facing
|
|
47
|
+
* allowlist. A run with neither keeps its null.
|
|
48
|
+
*/
|
|
49
|
+
export function suiteRunOutcome(run) {
|
|
50
|
+
const verdict = run?.verdict;
|
|
51
|
+
const fromVerdict = verdict && typeof verdict === 'object' ? verdict.outcome : undefined;
|
|
52
|
+
if (typeof fromVerdict === 'string' && fromVerdict.trim() !== '') {
|
|
53
|
+
return toUserFacingOutcome(fromVerdict);
|
|
54
|
+
}
|
|
55
|
+
const raw = run?.outcome;
|
|
56
|
+
if (typeof raw === 'string' && raw.trim() !== '')
|
|
57
|
+
return toUserFacingOutcome(raw);
|
|
58
|
+
return raw ?? null;
|
|
59
|
+
}
|
|
44
60
|
export class DebuggAIServerClient {
|
|
45
61
|
userApiKey;
|
|
46
62
|
tx;
|
|
@@ -604,11 +620,11 @@ export class DebuggAIServerClient {
|
|
|
604
620
|
lastRun: lastRun ? {
|
|
605
621
|
uuid: lastRun.uuid,
|
|
606
622
|
status: lastRun.status,
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
623
|
+
// platform-4cvj.72: THE run verdict (`verdict.outcome`, what the run
|
|
624
|
+
// page shows) wins; the raw recorded `outcome` is only read for a run
|
|
625
|
+
// the backend sent no verdict for. Same user-facing allowlist as every
|
|
626
|
+
// other surface; a run with no outcome yet keeps its null.
|
|
627
|
+
outcome: suiteRunOutcome(lastRun),
|
|
612
628
|
executionTime: lastRun.executionTime ?? lastRun.execution_time ?? null,
|
|
613
629
|
timestamp: lastRun.timestamp,
|
|
614
630
|
} : null,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 4-step integration: find template → execute → poll → result
|
|
4
4
|
*/
|
|
5
5
|
import { Telemetry, TelemetryEvents } from '../utils/telemetry.js';
|
|
6
|
+
import { redactRunSecrets } from '../utils/redactRunSecrets.js';
|
|
6
7
|
const TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
7
8
|
// Exponential backoff polling: short executions (10-15s crawls) detect terminal
|
|
8
9
|
// status quickly via the early polls; long executions (60-150s browser runs)
|
|
@@ -75,7 +76,9 @@ export const createWorkflowsService = (tx) => {
|
|
|
75
76
|
if (!response) {
|
|
76
77
|
throw new Error(`Execution not found: ${executionUuid}`);
|
|
77
78
|
}
|
|
78
|
-
|
|
79
|
+
// platform-98fv.25: every caller (executions tool, check_app polling) reads
|
|
80
|
+
// the execution through here, so this is where the relay drops run secrets.
|
|
81
|
+
return redactRunSecrets(response);
|
|
79
82
|
},
|
|
80
83
|
async listExecutions(filters) {
|
|
81
84
|
const { makePageInfo } = await import('../utils/pagination.js');
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* platform-98fv.25: the relay never passes a run secret on.
|
|
3
|
+
*
|
|
4
|
+
* The backend used to persist the password a check_app_in_browser run was given
|
|
5
|
+
* and serve it back in `contextData.env.password`, `state.env.password` and every
|
|
6
|
+
* node payload. The backend now seals it at rest and redacts it on read; this is
|
|
7
|
+
* the relay's own copy of that rule, so an older or misbehaving backend still
|
|
8
|
+
* cannot leak through the MCP.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately narrow and exact (mirrors sentinal `run_secrets.redact_run_secrets`):
|
|
11
|
+
* - any non-empty string under a key named exactly `password` / `passwd`;
|
|
12
|
+
* - every string value of a `projectSecrets` / `project_secrets` mapping;
|
|
13
|
+
* - any string carrying the backend's sealed-value prefix.
|
|
14
|
+
* Identifiers such as `browserSessionId` / `resolvedCredentialId` are untouched.
|
|
15
|
+
*/
|
|
16
|
+
export const REDACTED = '[REDACTED]';
|
|
17
|
+
const SEALED_PREFIX = 'sealed:v1:';
|
|
18
|
+
const SECRET_KEYS = new Set(['password', 'passwd']);
|
|
19
|
+
const SECRET_MAPPING_KEYS = new Set(['projectSecrets', 'project_secrets']);
|
|
20
|
+
function isSealed(v) {
|
|
21
|
+
return typeof v === 'string' && v.startsWith(SEALED_PREFIX);
|
|
22
|
+
}
|
|
23
|
+
export function redactRunSecrets(value, inMapping = false) {
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
return value.map((v) => (isSealed(v) ? REDACTED : redactRunSecrets(v)));
|
|
26
|
+
}
|
|
27
|
+
if (value && typeof value === 'object') {
|
|
28
|
+
const out = {};
|
|
29
|
+
for (const [k, v] of Object.entries(value)) {
|
|
30
|
+
if (typeof v === 'string' && v && (inMapping || SECRET_KEYS.has(k) || isSealed(v))) {
|
|
31
|
+
out[k] = REDACTED;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
out[k] = redactRunSecrets(v, SECRET_MAPPING_KEYS.has(k));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|