@browserstack/mcp-server 1.5.0-beta.9 → 1.5.1-beta.1
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/README.md +48 -32
- package/dist/config.d.ts +4 -1
- package/dist/config.js +23 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +7 -3
- package/dist/lib/constants.d.ts +3 -0
- package/dist/lib/constants.js +3 -0
- package/dist/lib/instrumentation.d.ts +14 -2
- package/dist/lib/instrumentation.js +104 -29
- package/dist/lib/node-version-notice.d.ts +1 -0
- package/dist/lib/node-version-notice.js +9 -0
- package/dist/lib/tool-latency.d.ts +4 -0
- package/dist/lib/tool-latency.js +16 -0
- package/dist/lib/untrusted-content.d.ts +15 -0
- package/dist/lib/untrusted-content.js +24 -0
- package/dist/lib/upload-validator.d.ts +3 -2
- package/dist/lib/upload-validator.js +18 -15
- package/dist/lib/utils.js +10 -3
- package/dist/lib/version-resolver.d.ts +0 -5
- package/dist/lib/version-resolver.js +8 -2
- package/dist/logger.js +1 -10
- package/dist/server-factory.js +10 -6
- package/dist/tools/accessibility.js +10 -6
- package/dist/tools/accessiblity-utils/accessibility-rag.js +3 -1
- package/dist/tools/accessiblity-utils/auth-config.js +15 -2
- package/dist/tools/appautomate-utils/native-execution/constants.js +4 -2
- package/dist/tools/appautomate.js +3 -1
- package/dist/tools/applive.js +1 -1
- package/dist/tools/ask-browserstack/central-oauth.d.ts +120 -0
- package/dist/tools/ask-browserstack/central-oauth.js +277 -0
- package/dist/tools/ask-browserstack/config.d.ts +102 -0
- package/dist/tools/ask-browserstack/config.js +140 -0
- package/dist/tools/ask-browserstack/egress.d.ts +34 -0
- package/dist/tools/ask-browserstack/egress.js +31 -0
- package/dist/tools/ask-browserstack/register.d.ts +61 -0
- package/dist/tools/ask-browserstack/register.js +416 -0
- package/dist/tools/ask-browserstack/relay.d.ts +201 -0
- package/dist/tools/ask-browserstack/relay.js +577 -0
- package/dist/tools/ask-browserstack/stream.d.ts +116 -0
- package/dist/tools/ask-browserstack/stream.js +236 -0
- package/dist/tools/ask-browserstack/types.d.ts +196 -0
- package/dist/tools/ask-browserstack/types.js +14 -0
- package/dist/tools/automate-utils/list-session-ids.d.ts +28 -0
- package/dist/tools/automate-utils/list-session-ids.js +87 -0
- package/dist/tools/automate-utils/resolve-hashed-build-id.d.ts +30 -0
- package/dist/tools/automate-utils/resolve-hashed-build-id.js +124 -0
- package/dist/tools/automate.d.ts +7 -0
- package/dist/tools/automate.js +104 -1
- package/dist/tools/bstack-sdk.js +2 -2
- package/dist/tools/build-insights.js +41 -2
- package/dist/tools/failurelogs-utils/app-automate.js +4 -3
- package/dist/tools/failurelogs-utils/automate.js +5 -4
- package/dist/tools/failurelogs-utils/resolve-app-build-id.d.ts +2 -0
- package/dist/tools/failurelogs-utils/resolve-app-build-id.js +5 -0
- package/dist/tools/failurelogs-utils/video.d.ts +3 -0
- package/dist/tools/failurelogs-utils/video.js +25 -0
- package/dist/tools/get-failure-logs.js +30 -11
- package/dist/tools/observability.js +3 -1
- package/dist/tools/percy-sdk.js +9 -9
- package/dist/tools/rca-agent-utils/constants.js +2 -2
- package/dist/tools/rca-agent-utils/format-rca.js +5 -4
- package/dist/tools/rca-agent-utils/get-failed-test-id.js +12 -0
- package/dist/tools/rca-agent-utils/types.d.ts +1 -0
- package/dist/tools/rca-agent.js +18 -6
- package/dist/tools/review-agent.js +2 -1
- package/dist/tools/sdk-utils/common/constants.d.ts +1 -1
- package/dist/tools/sdk-utils/common/constants.js +2 -1
- package/dist/tools/sdk-utils/common/utils.js +1 -1
- package/dist/tools/selfheal-utils/fetch-test-code.d.ts +3 -4
- package/dist/tools/selfheal-utils/fetch-test-code.js +30 -57
- package/dist/tools/selfheal.js +28 -35
- package/dist/tools/testmanagement-utils/TCG-utils/api.js +26 -7
- package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +2 -0
- package/dist/tools/testmanagement-utils/TCG-utils/config.js +5 -0
- package/dist/tools/testmanagement-utils/get-sub-testplan.js +6 -3
- package/dist/tools/testmanagement-utils/get-testplan.js +6 -3
- package/dist/tools/testmanagement-utils/testcase-from-file.js +2 -1
- package/dist/tools/testmanagement-utils/upload-file.js +16 -2
- package/dist/tools/testmanagement.js +39 -17
- package/dist/tools/tfa-rca-collaboration.js +1 -1
- package/dist/tools/tool-handoff.d.ts +66 -0
- package/dist/tools/tool-handoff.js +78 -0
- package/package.json +4 -5
- package/capability/loadtesting.capability-index.json +0 -1791
- package/capability/tm.capability-index.json +0 -19966
- package/dist/tools/capability-registry/bind.d.ts +0 -29
- package/dist/tools/capability-registry/bind.js +0 -134
- package/dist/tools/capability-registry/config.d.ts +0 -62
- package/dist/tools/capability-registry/config.js +0 -218
- package/dist/tools/capability-registry/discovery.d.ts +0 -44
- package/dist/tools/capability-registry/discovery.js +0 -99
- package/dist/tools/capability-registry/egress.d.ts +0 -44
- package/dist/tools/capability-registry/egress.js +0 -128
- package/dist/tools/capability-registry/index-loader.d.ts +0 -133
- package/dist/tools/capability-registry/index-loader.js +0 -369
- package/dist/tools/capability-registry/register.d.ts +0 -34
- package/dist/tools/capability-registry/register.js +0 -373
- package/dist/tools/capability-registry/resolve.d.ts +0 -38
- package/dist/tools/capability-registry/resolve.js +0 -45
- package/dist/tools/capability-registry/search.d.ts +0 -66
- package/dist/tools/capability-registry/search.js +0 -416
- package/dist/tools/capability-registry/types.d.ts +0 -219
- package/dist/tools/capability-registry/types.js +0 -33
package/dist/tools/selfheal.js
CHANGED
|
@@ -3,6 +3,7 @@ import { getSelfHealSelectors, fetchSelfHealingReportByBuild, } from "./selfheal
|
|
|
3
3
|
import { fetchTestCodeForSessions, formatTestCodeAsContext, describeTestCodeFetchIssues, } from "./selfheal-utils/fetch-test-code.js";
|
|
4
4
|
import logger from "../logger.js";
|
|
5
5
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
6
|
+
import { NEEDS_SESSION_ID } from "./tool-handoff.js";
|
|
6
7
|
// Local helper: returns the server-configured BrowserStack credentials, or
|
|
7
8
|
// null when either is missing. Lives here because the self-heal tools need
|
|
8
9
|
// to degrade gracefully — `getBrowserStackAuth` throws, which is wrong for
|
|
@@ -17,9 +18,10 @@ function resolveBrowserStackAuth(config) {
|
|
|
17
18
|
return { config };
|
|
18
19
|
}
|
|
19
20
|
const CREDS_PROMPT_TEXT = "BrowserStack credentials are not configured on the MCP server. " +
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"restart the MCP server, and retry.
|
|
21
|
+
"Set BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY in the server " +
|
|
22
|
+
"environment (https://www.browserstack.com/accounts/profile/details), " +
|
|
23
|
+
"restart the MCP server, and retry. Credentials are read from the server " +
|
|
24
|
+
"environment, not from chat.";
|
|
23
25
|
function credsMissingResult() {
|
|
24
26
|
return {
|
|
25
27
|
content: [{ type: "text", text: CREDS_PROMPT_TEXT }],
|
|
@@ -44,20 +46,13 @@ function trimOrUndefined(value) {
|
|
|
44
46
|
const trimmed = value.trim();
|
|
45
47
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
46
48
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Wraps per-status guidance in a HEAD-OF-RESPONSE banner the LLM is unlikely
|
|
49
|
-
* to paraphrase away. The banner must come BEFORE the plan JSON so the model
|
|
50
|
-
* anchors on it when composing its user-facing reply.
|
|
51
|
-
*/
|
|
49
|
+
/** Per-status notes, placed ahead of the plan JSON. */
|
|
52
50
|
function buildWarningBanner(body) {
|
|
53
51
|
return [
|
|
54
|
-
"##
|
|
52
|
+
"## Test code fetch did not return usable source",
|
|
55
53
|
"",
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"possible — do NOT compress multiple statuses into a generic " +
|
|
59
|
-
"'credentials or session issue' message. If the status below is " +
|
|
60
|
-
"`non_sdk_build`, it is definitely NOT a credentials problem.",
|
|
54
|
+
"Each status below has a different cause and is reported separately; " +
|
|
55
|
+
"a `non_sdk_build` status is not a credentials problem.",
|
|
61
56
|
"",
|
|
62
57
|
body,
|
|
63
58
|
"",
|
|
@@ -235,7 +230,7 @@ export async function fetchSelfHealSelectorTool(args, config) {
|
|
|
235
230
|
"Self-healing report fetched successfully (buildUuid mode). " +
|
|
236
231
|
"Work session-by-session: for each entry in `healing_logs[]`, " +
|
|
237
232
|
"pass `healed_selectors[]` to `prepareSelfHealingPlan` so the " +
|
|
238
|
-
"
|
|
233
|
+
"caller can apply the edits with its own file-editing " +
|
|
239
234
|
"tools (this server never writes files).\n" +
|
|
240
235
|
JSON.stringify(report, null, 2) +
|
|
241
236
|
(testCodeContext
|
|
@@ -262,9 +257,9 @@ export async function fetchSelfHealSelectorTool(args, config) {
|
|
|
262
257
|
const PLAN_INSTRUCTIONS = [
|
|
263
258
|
"## How to use this plan",
|
|
264
259
|
"",
|
|
265
|
-
"This tool does
|
|
266
|
-
"and per-session test source code so
|
|
267
|
-
"
|
|
260
|
+
"This tool does not modify any files. It returns the healed-locator plan",
|
|
261
|
+
"and per-session test source code so the caller can make targeted edits",
|
|
262
|
+
"with its own file-editing tools.",
|
|
268
263
|
"",
|
|
269
264
|
"For each session in the plan:",
|
|
270
265
|
" 1. Read each `tests[].code` to understand the test intent — especially",
|
|
@@ -272,10 +267,10 @@ const PLAN_INSTRUCTIONS = [
|
|
|
272
267
|
" 2. Locate the exact call site(s) in the user's local project that",
|
|
273
268
|
" correspond to the original locator. Use `tests[].filename` as the",
|
|
274
269
|
" first place to look.",
|
|
275
|
-
" 3. Edit
|
|
276
|
-
" A single id / class value may appear in many places
|
|
277
|
-
" find/replace across the repo (e.g. two different elements
|
|
278
|
-
' id="foo" must be resolved individually).',
|
|
270
|
+
" 3. Edit only the call sites that belong to this session's failing step.",
|
|
271
|
+
" A single id / class value may appear in many places, so a blanket",
|
|
272
|
+
" find/replace across the repo is unsafe (e.g. two different elements",
|
|
273
|
+
' both using id="foo" must be resolved individually).',
|
|
279
274
|
" 4. The healing report speaks in CSS/xpath, but the source often uses",
|
|
280
275
|
" `By.id(...)`, `By.name(...)`, `By.css(...)` wrappers. Translate as",
|
|
281
276
|
" needed — e.g. `*[id=\"email-field\"]` → `By.id('email-field')`, or",
|
|
@@ -284,7 +279,7 @@ const PLAN_INSTRUCTIONS = [
|
|
|
284
279
|
"",
|
|
285
280
|
"If `tests[]` is empty for a session, the BrowserStack API did not return",
|
|
286
281
|
"test code (credentials missing, or the session has no associated test",
|
|
287
|
-
"runs).
|
|
282
|
+
"runs). The local file path is needed before editing.",
|
|
288
283
|
].join("\n");
|
|
289
284
|
export async function prepareSelfHealingPlanTool(args, config) {
|
|
290
285
|
const rawArgs = args;
|
|
@@ -388,7 +383,7 @@ export async function prepareSelfHealingPlanTool(args, config) {
|
|
|
388
383
|
}
|
|
389
384
|
catch (error) {
|
|
390
385
|
logger.warn("Failed to fetch test code during plan preparation", error);
|
|
391
|
-
warningBanner = buildWarningBanner(`### Transport error while fetching test code\n\nDiagnosis: ${error instanceof Error ? error.message : String(error)}. This is not an auth issue.\n\
|
|
386
|
+
warningBanner = buildWarningBanner(`### Transport error while fetching test code\n\nDiagnosis: ${error instanceof Error ? error.message : String(error)}. This is not an auth issue.\n\nNext step: retry the fetch, or apply the healed locators to the local test file if the user shares its path.`);
|
|
392
387
|
}
|
|
393
388
|
}
|
|
394
389
|
else if (!resolved && sessionIds.length > 0) {
|
|
@@ -399,14 +394,10 @@ export async function prepareSelfHealingPlanTool(args, config) {
|
|
|
399
394
|
"configured on the MCP server, so test code could not be fetched. " +
|
|
400
395
|
"The plan was still generated from the locator pairs you sent.",
|
|
401
396
|
"",
|
|
402
|
-
"
|
|
403
|
-
"
|
|
404
|
-
"
|
|
405
|
-
"
|
|
406
|
-
"the MCP server, or point me at the local test file directly so " +
|
|
407
|
-
'I can apply the healed locators."',
|
|
408
|
-
"",
|
|
409
|
-
"Do NOT ask the user to paste credentials in chat.",
|
|
397
|
+
"Next step: set BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY in " +
|
|
398
|
+
"the server environment and restart the MCP server, or apply the " +
|
|
399
|
+
"healed locators to the local test file if the user shares its path. " +
|
|
400
|
+
"Credentials are read from the server environment, not from chat.",
|
|
410
401
|
].join("\n"));
|
|
411
402
|
}
|
|
412
403
|
const skippedNote = skipped.length > 0
|
|
@@ -430,7 +421,8 @@ export default function addSelfHealTools(server, config) {
|
|
|
430
421
|
"the run. Provide exactly one of `sessionId` (single Automate / " +
|
|
431
422
|
"App-Automate session) or `buildUuid` (full self-healing report for a " +
|
|
432
423
|
"build). Pass the returned locator pairs to `prepareSelfHealingPlan` " +
|
|
433
|
-
"to plan edits."
|
|
424
|
+
"to plan edits." +
|
|
425
|
+
NEEDS_SESSION_ID, {
|
|
434
426
|
sessionId: z
|
|
435
427
|
.string()
|
|
436
428
|
.describe("Session ID. Mutually exclusive with buildUuid.")
|
|
@@ -499,11 +491,12 @@ export default function addSelfHealTools(server, config) {
|
|
|
499
491
|
"[...]}`, the raw report `{healing_logs: [...]}` (with " +
|
|
500
492
|
"`healed_selectors` aliasing `locators`), and snake_case keys " +
|
|
501
493
|
"(`session_id`, `original_locator`, `healed_locator`, " +
|
|
502
|
-
"`healing_thought`)."
|
|
494
|
+
"`healing_thought`)." +
|
|
495
|
+
NEEDS_SESSION_ID, {
|
|
503
496
|
sessions: sessionsFieldSchema.describe("Sessions to plan edits for. See tool description for accepted shapes."),
|
|
504
497
|
}, {
|
|
505
498
|
title: "Prepare Self-Healing Plan",
|
|
506
|
-
readOnlyHint:
|
|
499
|
+
readOnlyHint: false,
|
|
507
500
|
openWorldHint: false,
|
|
508
501
|
destructiveHint: false,
|
|
509
502
|
idempotentHint: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { apiClient } from "../../../lib/apiClient.js";
|
|
2
|
-
import { TCG_TRIGGER_URL, TCG_POLL_URL, FETCH_DETAILS_URL, FORM_FIELDS_URL, BULK_CREATE_URL, TC_DETAILS_MAX_BATCH, } from "./config.js";
|
|
2
|
+
import { TCG_TRIGGER_URL, TCG_POLL_URL, FETCH_DETAILS_URL, FORM_FIELDS_URL, BULK_CREATE_URL, TC_DETAILS_MAX_BATCH, TCG_POLL_INTERVAL_MS, TCG_POLL_MAX_WAIT_MS, } from "./config.js";
|
|
3
3
|
import { createTestCasePayload } from "./helpers.js";
|
|
4
4
|
import { getBrowserStackAuth } from "../../../lib/get-auth.js";
|
|
5
5
|
import { getTMBaseURL } from "../../../lib/tm-base-url.js";
|
|
@@ -118,9 +118,15 @@ export async function pollTestCaseDetails(traceRequestId, config) {
|
|
|
118
118
|
let done = false;
|
|
119
119
|
const tmBaseUrl = await getTMBaseURL(config);
|
|
120
120
|
const TCG_POLL_URL_VALUE = TCG_POLL_URL(tmBaseUrl);
|
|
121
|
+
const deadline = Date.now() + TCG_POLL_MAX_WAIT_MS;
|
|
121
122
|
while (!done) {
|
|
123
|
+
// Bail out if the backend never sends a "termination" message, so a stuck
|
|
124
|
+
// job cannot keep this loop (and its callers) alive forever.
|
|
125
|
+
if (Date.now() > deadline) {
|
|
126
|
+
throw new Error(`TCG test-case detail polling timed out after ${TCG_POLL_MAX_WAIT_MS}ms (trace ${traceRequestId})`);
|
|
127
|
+
}
|
|
122
128
|
// add a bit of jitter to avoid synchronized polling storms
|
|
123
|
-
await new Promise((r) => setTimeout(r,
|
|
129
|
+
await new Promise((r) => setTimeout(r, TCG_POLL_INTERVAL_MS + Math.random() * 5000));
|
|
124
130
|
const poll = await apiClient.post({
|
|
125
131
|
url: `${TCG_POLL_URL_VALUE}?x-bstack-traceRequestId=${encodeURIComponent(traceRequestId)}`,
|
|
126
132
|
headers: {
|
|
@@ -159,7 +165,20 @@ export async function pollScenariosTestDetails(args, traceId, context, documentI
|
|
|
159
165
|
const TCG_POLL_URL_VALUE = TCG_POLL_URL(tmBaseUrl);
|
|
160
166
|
// Promisify interval-style polling using a wrapper
|
|
161
167
|
await new Promise((resolve, reject) => {
|
|
162
|
-
const
|
|
168
|
+
const timers = {};
|
|
169
|
+
const stop = () => {
|
|
170
|
+
if (timers.interval)
|
|
171
|
+
clearInterval(timers.interval);
|
|
172
|
+
if (timers.timeout)
|
|
173
|
+
clearTimeout(timers.timeout);
|
|
174
|
+
};
|
|
175
|
+
// Hard wall-clock deadline: if the backend never sends a "termination"
|
|
176
|
+
// message, reject instead of letting the interval fire forever.
|
|
177
|
+
timers.timeout = setTimeout(() => {
|
|
178
|
+
stop();
|
|
179
|
+
reject(new Error(`TCG scenario polling timed out after ${TCG_POLL_MAX_WAIT_MS}ms (trace ${traceId})`));
|
|
180
|
+
}, TCG_POLL_MAX_WAIT_MS);
|
|
181
|
+
timers.interval = setInterval(async () => {
|
|
163
182
|
try {
|
|
164
183
|
const poll = await apiClient.post({
|
|
165
184
|
url: `${TCG_POLL_URL_VALUE}?x-bstack-traceRequestId=${encodeURIComponent(traceId)}`,
|
|
@@ -169,7 +188,7 @@ export async function pollScenariosTestDetails(args, traceId, context, documentI
|
|
|
169
188
|
body: {},
|
|
170
189
|
});
|
|
171
190
|
if (poll.status !== 200) {
|
|
172
|
-
|
|
191
|
+
stop();
|
|
173
192
|
reject(new Error(`Polling error: ${poll.statusText || poll.status}`));
|
|
174
193
|
return;
|
|
175
194
|
}
|
|
@@ -221,16 +240,16 @@ export async function pollScenariosTestDetails(args, traceId, context, documentI
|
|
|
221
240
|
}
|
|
222
241
|
}
|
|
223
242
|
if (msg.type === "termination") {
|
|
224
|
-
|
|
243
|
+
stop();
|
|
225
244
|
resolve();
|
|
226
245
|
}
|
|
227
246
|
}
|
|
228
247
|
}
|
|
229
248
|
catch (err) {
|
|
230
|
-
|
|
249
|
+
stop();
|
|
231
250
|
reject(err);
|
|
232
251
|
}
|
|
233
|
-
},
|
|
252
|
+
}, TCG_POLL_INTERVAL_MS);
|
|
234
253
|
});
|
|
235
254
|
// once all detail fetches are triggered, wait for them to complete
|
|
236
255
|
const detailsList = await Promise.all(detailPromises);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const TC_DETAILS_MAX_BATCH = 10;
|
|
2
|
+
export declare const TCG_POLL_INTERVAL_MS = 10000;
|
|
3
|
+
export declare const TCG_POLL_MAX_WAIT_MS: number;
|
|
2
4
|
export declare const TCG_TRIGGER_URL: (baseUrl: string) => string;
|
|
3
5
|
export declare const TCG_POLL_URL: (baseUrl: string) => string;
|
|
4
6
|
export declare const FETCH_DETAILS_URL: (baseUrl: string) => string;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export const TC_DETAILS_MAX_BATCH = 10;
|
|
2
|
+
// Wall-clock bounds for the TCG generation polling loops. Without a hard
|
|
3
|
+
// deadline, a job that never emits a "termination" message keeps the loop
|
|
4
|
+
// (and every caller awaiting it) alive forever.
|
|
5
|
+
export const TCG_POLL_INTERVAL_MS = 10_000;
|
|
6
|
+
export const TCG_POLL_MAX_WAIT_MS = 10 * 60 * 1000; // 10 minutes
|
|
2
7
|
export const TCG_TRIGGER_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/suggest-test-cases`;
|
|
3
8
|
export const TCG_POLL_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/test-cases-polling`;
|
|
4
9
|
export const FETCH_DETAILS_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/fetch-test-case-details`;
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { formatAxiosError } from "../../lib/error.js";
|
|
4
4
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
5
5
|
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
6
|
+
import { wrapUntrusted } from "../../lib/untrusted-content.js";
|
|
6
7
|
/**
|
|
7
8
|
* Schema for fetching a single sub-test-plan by identifier under a parent test
|
|
8
9
|
* plan, including its linked test runs.
|
|
@@ -106,7 +107,9 @@ export async function getSubTestPlan(args, config) {
|
|
|
106
107
|
`Sub-Test-Plan ${plan.identifier}: ${plan.name}`,
|
|
107
108
|
`Parent plan: ${plan.parent_plan_id}`,
|
|
108
109
|
`Status: ${plan.active_state}`,
|
|
109
|
-
plan.description
|
|
110
|
+
plan.description
|
|
111
|
+
? `Description:\n${wrapUntrusted("sub-test-plan description", plan.description)}`
|
|
112
|
+
: null,
|
|
110
113
|
tagsLine,
|
|
111
114
|
issuesLine,
|
|
112
115
|
plan.start_date || plan.end_date
|
|
@@ -133,12 +136,12 @@ export async function getSubTestPlan(args, config) {
|
|
|
133
136
|
{ type: "text", text: header + runsBlock },
|
|
134
137
|
{
|
|
135
138
|
type: "text",
|
|
136
|
-
text: JSON.stringify({
|
|
139
|
+
text: wrapUntrusted("sub-test-plan (structured data)", JSON.stringify({
|
|
137
140
|
sub_test_plan: plan,
|
|
138
141
|
linked_test_runs: runs,
|
|
139
142
|
status_summary: statusSummary,
|
|
140
143
|
total_test_cases: totalCases,
|
|
141
|
-
}, null, 2),
|
|
144
|
+
}, null, 2)),
|
|
142
145
|
},
|
|
143
146
|
],
|
|
144
147
|
};
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { formatAxiosError } from "../../lib/error.js";
|
|
4
4
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
5
5
|
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
6
|
+
import { wrapUntrusted } from "../../lib/untrusted-content.js";
|
|
6
7
|
/**
|
|
7
8
|
* Schema for fetching a single test plan by identifier, including its linked test runs.
|
|
8
9
|
*/
|
|
@@ -58,7 +59,9 @@ export async function getTestPlan(args, config) {
|
|
|
58
59
|
const header = [
|
|
59
60
|
`Test Plan ${plan.identifier}: ${plan.name}`,
|
|
60
61
|
`Status: ${plan.active_state}`,
|
|
61
|
-
plan.description
|
|
62
|
+
plan.description
|
|
63
|
+
? `Description:\n${wrapUntrusted("test plan description", plan.description)}`
|
|
64
|
+
: null,
|
|
62
65
|
plan.start_date || plan.end_date
|
|
63
66
|
? `Dates: ${plan.start_date ?? "—"} → ${plan.end_date ?? "—"}`
|
|
64
67
|
: null,
|
|
@@ -83,12 +86,12 @@ export async function getTestPlan(args, config) {
|
|
|
83
86
|
{ type: "text", text: header + runsBlock },
|
|
84
87
|
{
|
|
85
88
|
type: "text",
|
|
86
|
-
text: JSON.stringify({
|
|
89
|
+
text: wrapUntrusted("test plan (structured data)", JSON.stringify({
|
|
87
90
|
test_plan: plan,
|
|
88
91
|
linked_test_runs: runs,
|
|
89
92
|
status_summary: statusSummary,
|
|
90
93
|
total_test_cases: totalCases,
|
|
91
|
-
}, null, 2),
|
|
94
|
+
}, null, 2)),
|
|
92
95
|
},
|
|
93
96
|
],
|
|
94
97
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { wrapUntrusted } from "../../lib/untrusted-content.js";
|
|
1
2
|
import { fetchFormFields, triggerTestCaseGeneration, pollScenariosTestDetails, bulkCreateTestCases, } from "./TCG-utils/api.js";
|
|
2
3
|
import { buildDefaultFieldMaps, findBooleanFieldId, } from "./TCG-utils/helpers.js";
|
|
3
4
|
import { signedUrlMap } from "../../lib/inmemory-store.js";
|
|
@@ -37,7 +38,7 @@ export async function createTestCasesFromFile(args, context, config) {
|
|
|
37
38
|
content: [
|
|
38
39
|
{
|
|
39
40
|
type: "text",
|
|
40
|
-
text: resultString,
|
|
41
|
+
text: wrapUntrusted("AI-generated test cases from the uploaded document", resultString),
|
|
41
42
|
},
|
|
42
43
|
{
|
|
43
44
|
type: "text",
|
|
@@ -19,16 +19,30 @@ export const UploadFileSchema = z.object({
|
|
|
19
19
|
.describe("ID of the project where the file should be uploaded. Do not assume it, always ask user for it."),
|
|
20
20
|
file_path: z
|
|
21
21
|
.string()
|
|
22
|
-
.describe("Full path to the file that should be uploaded"
|
|
22
|
+
.describe("Full path to the file that should be uploaded. Must be inside the " +
|
|
23
|
+
"directory configured via the MCP_UPLOAD_BASE_DIR environment variable."),
|
|
23
24
|
});
|
|
24
25
|
/**
|
|
25
26
|
* Uploads a file to BrowserStack Test Management and returns the signed URL.
|
|
26
27
|
*/
|
|
27
28
|
export async function uploadFile(args, config) {
|
|
28
29
|
const { project_identifier, file_path } = args;
|
|
30
|
+
if (!appConfig.UPLOAD_BASE_DIR) {
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: "text",
|
|
35
|
+
text: "File upload is disabled. Set the MCP_UPLOAD_BASE_DIR environment " +
|
|
36
|
+
"variable to a directory that contains the files you want to upload, " +
|
|
37
|
+
"then restart the MCP server. Uploads are restricted to that directory.",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
isError: true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
29
43
|
try {
|
|
30
44
|
// Canonicalize path and enforce upload safety rules (extension, size,
|
|
31
|
-
// hidden-directory traversal,
|
|
45
|
+
// hidden-directory traversal, base-dir containment).
|
|
32
46
|
const safePath = validateUploadPath(file_path, {
|
|
33
47
|
allowedExtensions: TEST_MANAGEMENT_ATTACHMENT_EXTENSIONS,
|
|
34
48
|
maxSizeBytes: MAX_ATTACHMENT_UPLOAD_BYTES,
|
|
@@ -19,6 +19,7 @@ import { getTestPlan, GetTestPlanSchema, } from "./testmanagement-utils/get-test
|
|
|
19
19
|
import { listSubTestPlans, ListSubTestPlansSchema, } from "./testmanagement-utils/list-sub-testplans.js";
|
|
20
20
|
import { getSubTestPlan, GetSubTestPlanSchema, } from "./testmanagement-utils/get-sub-testplan.js";
|
|
21
21
|
import { elicitCredentialsIfSupported } from "../lib/elicit-credentials.js";
|
|
22
|
+
import { NEEDS_PROJECT_ID, NEEDS_TEST_PLAN_ID, PLAN_WRITES_VIA_AGENT, PROJECT_ID_ONLY_FOR_FOLDER, } from "./tool-handoff.js";
|
|
22
23
|
//TODO: Moving the traceMCP and catch block to the parent(server) function
|
|
23
24
|
/**
|
|
24
25
|
* Wrapper to call createProjectOrFolder util.
|
|
@@ -263,11 +264,11 @@ export async function uploadProductRequirementFileTool(args, config, server) {
|
|
|
263
264
|
*/
|
|
264
265
|
export async function createTestCasesFromFileTool(args, context, config, server) {
|
|
265
266
|
try {
|
|
266
|
-
trackMCP("createTestCasesFromFile", server.server.getClientVersion(), undefined);
|
|
267
|
+
trackMCP("createTestCasesFromFile", server.server.getClientVersion(), undefined, config);
|
|
267
268
|
return await createTestCasesFromFile(args, context, config);
|
|
268
269
|
}
|
|
269
270
|
catch (err) {
|
|
270
|
-
trackMCP("createTestCasesFromFile", server.server.getClientVersion(), err);
|
|
271
|
+
trackMCP("createTestCasesFromFile", server.server.getClientVersion(), err, config);
|
|
271
272
|
return {
|
|
272
273
|
content: [
|
|
273
274
|
{
|
|
@@ -432,35 +433,40 @@ export async function getSubTestPlanTool(args, config, server) {
|
|
|
432
433
|
*/
|
|
433
434
|
export default function addTestManagementTools(server, config) {
|
|
434
435
|
const tools = {};
|
|
435
|
-
tools.createProjectOrFolder = server.tool("createProjectOrFolder", "Create a project and/or folder in BrowserStack Test Management."
|
|
436
|
+
tools.createProjectOrFolder = server.tool("createProjectOrFolder", "Create a project and/or folder in BrowserStack Test Management." +
|
|
437
|
+
PROJECT_ID_ONLY_FOR_FOLDER, CreateProjFoldSchema.shape, {
|
|
436
438
|
title: "Create Project or Folder",
|
|
437
439
|
readOnlyHint: false,
|
|
438
440
|
openWorldHint: false,
|
|
439
441
|
destructiveHint: false,
|
|
440
442
|
idempotentHint: false,
|
|
441
443
|
}, (args) => createProjectOrFolderTool(args, config, server));
|
|
442
|
-
tools.createTestCase = server.tool("createTestCase", "Use this tool to create a test case in BrowserStack Test Management."
|
|
444
|
+
tools.createTestCase = server.tool("createTestCase", "Use this tool to create a test case in BrowserStack Test Management." +
|
|
445
|
+
NEEDS_PROJECT_ID, CreateTestCaseSchema.shape, {
|
|
443
446
|
title: "Create Test Case",
|
|
444
447
|
readOnlyHint: false,
|
|
445
448
|
openWorldHint: false,
|
|
446
449
|
destructiveHint: false,
|
|
447
450
|
idempotentHint: false,
|
|
448
451
|
}, (args) => createTestCaseTool(args, config, server));
|
|
449
|
-
tools.updateTestCase = server.tool("updateTestCase", "Update an existing test case in BrowserStack Test Management. Any subset of the following fields may be changed: name, description, preconditions, test_case_steps, owner, priority, case_type, automation_status, status, tags, issues, custom_fields. Only the supplied fields are modified."
|
|
452
|
+
tools.updateTestCase = server.tool("updateTestCase", "Update an existing test case in BrowserStack Test Management. Any subset of the following fields may be changed: name, description, preconditions, test_case_steps, owner, priority, case_type, automation_status, status, tags, issues, custom_fields. Only the supplied fields are modified." +
|
|
453
|
+
NEEDS_PROJECT_ID, UpdateTestCaseSchema.shape, {
|
|
450
454
|
title: "Update Test Case",
|
|
451
455
|
readOnlyHint: false,
|
|
452
456
|
openWorldHint: false,
|
|
453
457
|
destructiveHint: true,
|
|
454
458
|
idempotentHint: true,
|
|
455
459
|
}, (args) => updateTestCaseTool(args, config, server));
|
|
456
|
-
tools.listTestCases = server.tool("listTestCases", "List test cases in a project, optionally scoped to a specific folder. Omit folder_id to list all test cases in the project; provide folder_id (discoverable via listFolders) to list only that folder's cases. Supports filters: case_type, priority, pagination."
|
|
460
|
+
tools.listTestCases = server.tool("listTestCases", "List test cases in a project, optionally scoped to a specific folder. Omit folder_id to list all test cases in the project; provide folder_id (discoverable via listFolders) to list only that folder's cases. Supports filters: case_type, priority, pagination." +
|
|
461
|
+
NEEDS_PROJECT_ID, ListTestCasesSchema.shape, {
|
|
457
462
|
title: "List Test Cases",
|
|
458
463
|
readOnlyHint: true,
|
|
459
464
|
openWorldHint: false,
|
|
460
465
|
destructiveHint: false,
|
|
461
466
|
idempotentHint: true,
|
|
462
467
|
}, (args) => listTestCasesTool(args, config, server));
|
|
463
|
-
tools.listFolders = server.tool("listFolders", "List folders in a BrowserStack Test Management project, returning each folder's id and name (plus case counts and sub-folder counts). Pass parent_id to list sub-folders under a specific folder instead of top-level folders."
|
|
468
|
+
tools.listFolders = server.tool("listFolders", "List folders in a BrowserStack Test Management project, returning each folder's id and name (plus case counts and sub-folder counts). Pass parent_id to list sub-folders under a specific folder instead of top-level folders." +
|
|
469
|
+
NEEDS_PROJECT_ID, ListFoldersSchema.shape, {
|
|
464
470
|
title: "List Folders",
|
|
465
471
|
readOnlyHint: true,
|
|
466
472
|
openWorldHint: false,
|
|
@@ -474,35 +480,39 @@ export default function addTestManagementTools(server, config) {
|
|
|
474
480
|
destructiveHint: false,
|
|
475
481
|
idempotentHint: true,
|
|
476
482
|
}, (args) => listTemplatesTool(args, config, server));
|
|
477
|
-
tools.createTestRun = server.tool("createTestRun", "Create a test run in BrowserStack Test Management.", CreateTestRunSchema.shape, {
|
|
483
|
+
tools.createTestRun = server.tool("createTestRun", "Create a test run in BrowserStack Test Management." + NEEDS_PROJECT_ID, CreateTestRunSchema.shape, {
|
|
478
484
|
title: "Create Test Run",
|
|
479
485
|
readOnlyHint: false,
|
|
480
486
|
openWorldHint: false,
|
|
481
487
|
destructiveHint: false,
|
|
482
488
|
idempotentHint: false,
|
|
483
489
|
}, (args) => createTestRunTool(args, config, server));
|
|
484
|
-
tools.listTestRuns = server.tool("listTestRuns", "List test runs in a project with optional filters (date ranges, assignee, state, etc.)"
|
|
490
|
+
tools.listTestRuns = server.tool("listTestRuns", "List test runs in a project with optional filters (date ranges, assignee, state, etc.)" +
|
|
491
|
+
NEEDS_PROJECT_ID, ListTestRunsSchema.shape, {
|
|
485
492
|
title: "List Test Runs",
|
|
486
493
|
readOnlyHint: true,
|
|
487
494
|
openWorldHint: false,
|
|
488
495
|
destructiveHint: false,
|
|
489
496
|
idempotentHint: true,
|
|
490
497
|
}, (args) => listTestRunsTool(args, config, server));
|
|
491
|
-
tools.updateTestRun = server.tool("updateTestRun", "Update a test run's metadata and/or add test cases to it."
|
|
498
|
+
tools.updateTestRun = server.tool("updateTestRun", "Update a test run's metadata and/or add test cases to it." +
|
|
499
|
+
NEEDS_PROJECT_ID, UpdateTestRunSchema.shape, {
|
|
492
500
|
title: "Update Test Run",
|
|
493
501
|
readOnlyHint: false,
|
|
494
502
|
openWorldHint: false,
|
|
495
503
|
destructiveHint: true,
|
|
496
504
|
idempotentHint: true,
|
|
497
505
|
}, (args) => updateTestRunTool(args, config, server));
|
|
498
|
-
tools.addTestResult = server.tool("addTestResult", "Add a test result to a specific test run via BrowserStack Test Management API."
|
|
506
|
+
tools.addTestResult = server.tool("addTestResult", "Add a test result to a specific test run via BrowserStack Test Management API." +
|
|
507
|
+
NEEDS_PROJECT_ID, AddTestResultSchema.shape, {
|
|
499
508
|
title: "Add Test Result",
|
|
500
509
|
readOnlyHint: false,
|
|
501
510
|
openWorldHint: false,
|
|
502
511
|
destructiveHint: false,
|
|
503
512
|
idempotentHint: false,
|
|
504
513
|
}, (args) => addTestResultTool(args, config, server));
|
|
505
|
-
tools.uploadProductRequirementFile = server.tool("uploadProductRequirementFile", "Upload files (e.g., PDRs, PDFs) to BrowserStack Test Management and retrieve a file mapping ID. This is utilized for generating test cases from files and is part of the Test Case Generator AI Agent in BrowserStack."
|
|
514
|
+
tools.uploadProductRequirementFile = server.tool("uploadProductRequirementFile", "Upload files (e.g., PDRs, PDFs) to BrowserStack Test Management and retrieve a file mapping ID. This is utilized for generating test cases from files and is part of the Test Case Generator AI Agent in BrowserStack." +
|
|
515
|
+
NEEDS_PROJECT_ID, UploadFileSchema.shape, {
|
|
506
516
|
title: "Upload Product Requirement File",
|
|
507
517
|
readOnlyHint: false,
|
|
508
518
|
openWorldHint: false,
|
|
@@ -516,35 +526,47 @@ export default function addTestManagementTools(server, config) {
|
|
|
516
526
|
destructiveHint: false,
|
|
517
527
|
idempotentHint: false,
|
|
518
528
|
}, (args, context) => createTestCasesFromFileTool(args, context, config, server));
|
|
519
|
-
tools.createLCASteps = server.tool("createLCASteps", "Generate Low Code Automation (LCA) steps for a test case in BrowserStack Test Management using the Low Code Automation Agent."
|
|
529
|
+
tools.createLCASteps = server.tool("createLCASteps", "Generate Low Code Automation (LCA) steps for a test case in BrowserStack Test Management using the Low Code Automation Agent." +
|
|
530
|
+
NEEDS_PROJECT_ID, CreateLCAStepsSchema.shape, {
|
|
520
531
|
title: "Create LCA Steps",
|
|
521
532
|
readOnlyHint: false,
|
|
522
533
|
openWorldHint: false,
|
|
523
534
|
destructiveHint: false,
|
|
524
535
|
idempotentHint: false,
|
|
525
536
|
}, (args, context) => createLCAStepsTool(args, context, config, server));
|
|
526
|
-
tools.listTestPlans = server.tool("listTestPlans", "List test plans in a BrowserStack Test Management project. Returns each plan's identifier (TP-*), name, status, description, dates, and active/closed test-run counts. Supports pagination."
|
|
537
|
+
tools.listTestPlans = server.tool("listTestPlans", "List test plans in a BrowserStack Test Management project. Returns each plan's identifier (TP-*), name, status, description, dates, and active/closed test-run counts. Supports pagination." +
|
|
538
|
+
NEEDS_PROJECT_ID +
|
|
539
|
+
PLAN_WRITES_VIA_AGENT, ListTestPlansSchema.shape, {
|
|
527
540
|
title: "List Test Plans",
|
|
528
541
|
readOnlyHint: true,
|
|
529
542
|
openWorldHint: false,
|
|
530
543
|
destructiveHint: false,
|
|
531
544
|
idempotentHint: true,
|
|
532
545
|
}, (args) => listTestPlansTool(args, config, server));
|
|
533
|
-
tools.getTestPlan = server.tool("getTestPlan", "Fetch a test plan by identifier (TP-*) from BrowserStack Test Management. Returns plan metadata, the full list of linked test runs, total test-case count across runs, and a status summary — suitable for generating test documentation or QA status reports."
|
|
546
|
+
tools.getTestPlan = server.tool("getTestPlan", "Fetch a test plan by identifier (TP-*) from BrowserStack Test Management. Returns plan metadata, the full list of linked test runs, total test-case count across runs, and a status summary — suitable for generating test documentation or QA status reports." +
|
|
547
|
+
NEEDS_PROJECT_ID +
|
|
548
|
+
NEEDS_TEST_PLAN_ID +
|
|
549
|
+
PLAN_WRITES_VIA_AGENT, GetTestPlanSchema.shape, {
|
|
534
550
|
title: "Get Test Plan",
|
|
535
551
|
readOnlyHint: true,
|
|
536
552
|
openWorldHint: false,
|
|
537
553
|
destructiveHint: false,
|
|
538
554
|
idempotentHint: true,
|
|
539
555
|
}, (args) => getTestPlanTool(args, config, server));
|
|
540
|
-
tools.listSubTestPlans = server.tool("listSubTestPlans", "List sub-test-plans under a parent test plan (TP-*) in a Test Management project. Supports pagination."
|
|
556
|
+
tools.listSubTestPlans = server.tool("listSubTestPlans", "List sub-test-plans under a parent test plan (TP-*) in a Test Management project. Supports pagination." +
|
|
557
|
+
NEEDS_PROJECT_ID +
|
|
558
|
+
NEEDS_TEST_PLAN_ID +
|
|
559
|
+
PLAN_WRITES_VIA_AGENT, ListSubTestPlansSchema.shape, {
|
|
541
560
|
title: "List Sub Test Plans",
|
|
542
561
|
readOnlyHint: true,
|
|
543
562
|
openWorldHint: false,
|
|
544
563
|
destructiveHint: false,
|
|
545
564
|
idempotentHint: true,
|
|
546
565
|
}, (args) => listSubTestPlansTool(args, config, server));
|
|
547
|
-
tools.getSubTestPlan = server.tool("getSubTestPlan", "Fetch a sub-test-plan (STP-*) under a parent plan (TP-*). Returns metadata and linked test runs."
|
|
566
|
+
tools.getSubTestPlan = server.tool("getSubTestPlan", "Fetch a sub-test-plan (STP-*) under a parent plan (TP-*). Returns metadata and linked test runs." +
|
|
567
|
+
NEEDS_PROJECT_ID +
|
|
568
|
+
NEEDS_TEST_PLAN_ID +
|
|
569
|
+
PLAN_WRITES_VIA_AGENT, GetSubTestPlanSchema.shape, {
|
|
548
570
|
title: "Get Sub Test Plan",
|
|
549
571
|
readOnlyHint: true,
|
|
550
572
|
openWorldHint: false,
|
|
@@ -83,7 +83,7 @@ export async function listTestsInFailureThemeTool(args, config) {
|
|
|
83
83
|
}
|
|
84
84
|
export default function addTfaRcaCollaborationTools(server, config) {
|
|
85
85
|
const tools = {};
|
|
86
|
-
tools.tfaRcaTurn = server.tool(TOOL_NAME, "Submit one collaborative RCA turn for a test run to the TFA agent, then poll in-call for a result. Omit threadId on the first turn for a test run — this starts a new investigation thread; every following turn on that same test
|
|
86
|
+
tools.tfaRcaTurn = server.tool(TOOL_NAME, "Submit one collaborative RCA turn for a test run to the TFA agent, then poll in-call for a result. Omit threadId on the first turn for a test run — this starts a new investigation thread; every following turn on that same test passes back the threadId from the previous response, since a test run has only one active thread at a time. Returns status RESOLVED (terminal, includes root_cause/related_prs), NEEDS_INFO (fulfill the asks and submit the next turn with the same threadId), or PENDING (still working — poll it with getTfaTurnResult using the returned turnId; calling tfaRcaTurn again for the same turn submits a duplicate).", TFA_RCA_TURN_PARAMS, async (args, context) => {
|
|
87
87
|
try {
|
|
88
88
|
const result = await tfaRcaTurnTool(args, config, context);
|
|
89
89
|
trackMCP(TOOL_NAME, server.server.getClientVersion(), undefined, config);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Precondition sentences appended to a tool's description.
|
|
3
|
+
*
|
|
4
|
+
* WHY THESE EXIST. Nothing routes an MCP call: the client's model picks a tool from the
|
|
5
|
+
* descriptions alone. A tool that needs an identifier the caller does not have is a dead
|
|
6
|
+
* end — the model either asks the user for something they also do not know, or gives up.
|
|
7
|
+
* These sentences turn that dead end into a HANDOFF by naming, in the description itself,
|
|
8
|
+
* where the missing identifier comes from.
|
|
9
|
+
*
|
|
10
|
+
* Point at a sibling tool whenever one can produce the id — it is faster and more
|
|
11
|
+
* predictable than an agent. Point at `askBrowserStackAI` only when NO tool here can.
|
|
12
|
+
*
|
|
13
|
+
* The one that matters most: 15 of the 17 Test Management tools require a project
|
|
14
|
+
* identifier and NONE of them accepts its absence, yet no tool in this server lists
|
|
15
|
+
* projects. "List my projects" is the first step of nearly every Test Management journey
|
|
16
|
+
* and it was unserved, which is exactly why that request did not reach any tool unless a
|
|
17
|
+
* user named one explicitly.
|
|
18
|
+
*
|
|
19
|
+
* Keep these as shared constants, not per-tool prose: the wording is a routing signal, and
|
|
20
|
+
* twenty hand-written variants drift into twenty different signals.
|
|
21
|
+
*/
|
|
22
|
+
/** No tool lists projects, so this genuinely has to go to the agent. */
|
|
23
|
+
export declare const NEEDS_PROJECT_ID: string;
|
|
24
|
+
/** A sibling tool can produce the id — prefer it over the agent. */
|
|
25
|
+
export declare function needsIdFrom(idLabel: string, sourceTool: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* createProjectOrFolder must NOT carry NEEDS_PROJECT_ID: `project_identifier` is optional
|
|
28
|
+
* there, and the create-a-PROJECT half needs no id at all. With the generic constant the
|
|
29
|
+
* tool read "Requires a project identifier ... call askBrowserStackAI", which routed
|
|
30
|
+
* "create me a project" through the agent before letting the tool run.
|
|
31
|
+
*/
|
|
32
|
+
export declare const PROJECT_ID_ONLY_FOR_FOLDER: string;
|
|
33
|
+
/** A test plan id (TP-*) comes from listTestPlans. */
|
|
34
|
+
export declare const NEEDS_TEST_PLAN_ID: string;
|
|
35
|
+
/**
|
|
36
|
+
* The ONLY capability handoff here: every other constant points at a tool that produces a
|
|
37
|
+
* missing *id*, but plan WRITES have no tool at all — the surface is `listTestPlans`,
|
|
38
|
+
* `getTestPlan`, `listSubTestPlans`, `getSubTestPlan` and nothing else. Atlas can do them
|
|
39
|
+
* (the tm harness allows POST /api/v1/projects/{id}/test-plans plus /update, /delete,
|
|
40
|
+
* /clone, /test-runs and /test-runs/unlink), so without this line the model reads the four
|
|
41
|
+
* read tools, finds no create, and reports the capability as absent — which is exactly what
|
|
42
|
+
* a QA eval concluded.
|
|
43
|
+
*
|
|
44
|
+
* Deliberately narrow: it names the specific operations that are missing rather than
|
|
45
|
+
* inviting the model to route plan work to the agent generally, because the tool
|
|
46
|
+
* descriptions otherwise say to prefer a specific tool whenever one fits.
|
|
47
|
+
*
|
|
48
|
+
* Caveat worth knowing: askBrowserStackAI pins every write to human approval, so this path
|
|
49
|
+
* only completes on a client that can show a prompt. On one that cannot, the intended write
|
|
50
|
+
* comes back in `needs_approval` instead of happening.
|
|
51
|
+
*/
|
|
52
|
+
export declare const PLAN_WRITES_VIA_AGENT: string;
|
|
53
|
+
/** A build id comes from either build-lookup tool. */
|
|
54
|
+
export declare const NEEDS_BUILD_ID: string;
|
|
55
|
+
/**
|
|
56
|
+
* Session ids ARE listable now — PR #395 added `listSessions`, which merged into main while
|
|
57
|
+
* this branch was open. This used to send the model to askBrowserStackAI for want of a tool;
|
|
58
|
+
* pointing at the agent when a real tool exists is exactly what the header above forbids.
|
|
59
|
+
*/
|
|
60
|
+
export declare const NEEDS_SESSION_ID: string;
|
|
61
|
+
/** A completed scan's ids come from startAccessibilityScan, or from the agent. */
|
|
62
|
+
export declare const NEEDS_A11Y_SCAN_ID: string;
|
|
63
|
+
/** Auth-config ids are not listable by any tool here. */
|
|
64
|
+
export declare const NEEDS_A11Y_CONFIG_ID: string;
|
|
65
|
+
/** Test ids come from listTestIds, which itself needs a build id. */
|
|
66
|
+
export declare const NEEDS_TEST_IDS: string;
|