common-qa-mcp 0.7.1 → 0.8.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/dist/adapters/remoteMcp/adapter.js +1 -1
- package/dist/adapters/simplifyqa/adapter.d.ts +24 -0
- package/dist/adapters/simplifyqa/adapter.d.ts.map +1 -1
- package/dist/adapters/simplifyqa/adapter.js +370 -28
- package/dist/adapters/simplifyqa/adapter.js.map +1 -1
- package/dist/adapters/simplifyqa/mapper.d.ts +2 -2
- package/dist/adapters/simplifyqa/mapper.d.ts.map +1 -1
- package/dist/adapters/simplifyqa/mapper.js +257 -64
- package/dist/adapters/simplifyqa/mapper.js.map +1 -1
- package/dist/domain/types.d.ts +36 -6
- package/dist/domain/types.d.ts.map +1 -1
- package/dist/domain/validate.d.ts.map +1 -1
- package/dist/domain/validate.js +5 -1
- package/dist/domain/validate.js.map +1 -1
- package/dist/initAgents.d.ts.map +1 -1
- package/dist/initAgents.js +18 -11
- package/dist/initAgents.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +6 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/recording.tools.d.ts.map +1 -1
- package/dist/tools/recording.tools.js +12 -1
- package/dist/tools/recording.tools.js.map +1 -1
- package/dist/tools/run.tools.d.ts +4 -0
- package/dist/tools/run.tools.d.ts.map +1 -1
- package/dist/tools/run.tools.js +228 -116
- package/dist/tools/run.tools.js.map +1 -1
- package/dist/tools/schemas.d.ts +41 -49
- package/dist/tools/schemas.d.ts.map +1 -1
- package/dist/tools/schemas.js +19 -1
- package/dist/tools/schemas.js.map +1 -1
- package/dist/tools/tcEditPublisher.d.ts +20 -0
- package/dist/tools/tcEditPublisher.d.ts.map +1 -0
- package/dist/tools/tcEditPublisher.js +46 -0
- package/dist/tools/tcEditPublisher.js.map +1 -0
- package/dist/tools/testcase.tools.d.ts.map +1 -1
- package/dist/tools/testcase.tools.js +314 -12
- package/dist/tools/testcase.tools.js.map +1 -1
- package/package.json +1 -1
|
@@ -38,7 +38,7 @@ export class RemoteMcpAdapter {
|
|
|
38
38
|
async client() {
|
|
39
39
|
if (!this.clientPromise) {
|
|
40
40
|
this.clientPromise = (async () => {
|
|
41
|
-
const client = new Client({ name: 'common-qa-mcp', version: '0.
|
|
41
|
+
const client = new Client({ name: 'common-qa-mcp', version: '0.8.1' });
|
|
42
42
|
// The `as Transport` cast bridges exactOptionalPropertyTypes: StreamableHTTPClientTransport
|
|
43
43
|
// implements Transport, but the SDK's own .d.ts (built without EOPT) leaves its optional
|
|
44
44
|
// callbacks (onclose?/onerror?/onmessage?) without `| undefined`, which this project's EOPT
|
|
@@ -25,6 +25,15 @@ export declare class SimplifyQaAdapter {
|
|
|
25
25
|
private backlogDefaultsCache;
|
|
26
26
|
constructor(cfg: ConnectedQaConfig, transport?: SimplifyQaTransport);
|
|
27
27
|
private headers;
|
|
28
|
+
/** Public session context for MCP viewer/recorder windows that need to authenticate the sqa-ui
|
|
29
|
+
* they load. cfg itself stays private; this getter is the one seam through which tc_edit_open
|
|
30
|
+
* and record tools can forward token + baseUrl + projectId to Electron without asking the LLM. */
|
|
31
|
+
getSessionContext(): {
|
|
32
|
+
token: string;
|
|
33
|
+
baseUrl: string;
|
|
34
|
+
projectId?: string;
|
|
35
|
+
userId?: string;
|
|
36
|
+
};
|
|
28
37
|
private requireProjectId;
|
|
29
38
|
private searchBody;
|
|
30
39
|
/** Validates baseUrl + token + (optional) project by running the same list_projects search the
|
|
@@ -320,6 +329,9 @@ export declare class SimplifyQaAdapter {
|
|
|
320
329
|
* Keyed by `${technologyType}::${lowerCaseName}` so the same action name across two techs
|
|
321
330
|
* (e.g. "click" for WEB + ANDROID) resolves to the right numeric id. */
|
|
322
331
|
private actionCatalogCache;
|
|
332
|
+
/** Populated by actionCatalog() on each fetch. Surfaced in resolveAutomationSteps error messages
|
|
333
|
+
* so a user hitting "no SQA action named X" can see what the fetch actually returned. */
|
|
334
|
+
private actionCatalogFetchDiag;
|
|
323
335
|
private actionCatalog;
|
|
324
336
|
private resolveObjectId;
|
|
325
337
|
/** For every automation step, resolve `actionId` (from action name + technology) and
|
|
@@ -330,6 +342,10 @@ export declare class SimplifyQaAdapter {
|
|
|
330
342
|
* collation strength:2, so a case-sensitive search here misses params stored under different
|
|
331
343
|
* casing — the next POST then trips "Duplicate Name Error". Returns null when not found. */
|
|
332
344
|
private findParameterId;
|
|
345
|
+
/** Case-insensitive project-scoped lookup of an existing object by `disname` (the UI's Name).
|
|
346
|
+
* Lets tc_draft_update pass `objectRef:{name}` for an already-defined object without also
|
|
347
|
+
* supplying locators — mirrors what a user picking from the Object Repository dropdown does. */
|
|
348
|
+
private findObjectIdByName;
|
|
333
349
|
private getOrCreateParameter;
|
|
334
350
|
createSuite(s: Suite): Promise<SavedRef>;
|
|
335
351
|
/** Fetch raw suite, apply add/remove/reorder ops in memory, then PUT the whole doc back with a
|
|
@@ -469,6 +485,14 @@ export declare class SimplifyQaAdapter {
|
|
|
469
485
|
private lookupProjectName;
|
|
470
486
|
startRun(target: RunTarget, opts: RunOptions): Promise<RunRef>;
|
|
471
487
|
stopRun(runId: string): Promise<void>;
|
|
488
|
+
/** SUITE dispatch. Mirrors `startRun` above (which handles TC) — same `/exec/execution` endpoint,
|
|
489
|
+
* same LOCAL-agent fast-fail, same project-name / environment resolution. What differs is on the
|
|
490
|
+
* wire: `type: 'SUITE'`, `executionType` sourced from `suite.suiteType`, `mode` defaulting to
|
|
491
|
+
* `suite.executionType`, a populated `globalConfiguration` block, and one `executionItems` entry
|
|
492
|
+
* PER member — each carrying the suite row's persisted `versionId` / `stepId` / `platform` / `seq`
|
|
493
|
+
* (NOT a re-derived random step id). See sqa-ui execution-popup-modal.component.ts:621-1105 SU
|
|
494
|
+
* branch for the authoritative UI builder; every field below traces back to a line there. */
|
|
495
|
+
private startSuiteRun;
|
|
472
496
|
/** GET the agent /mcp with a 1s timeout. Any HTTP response (incl. 4xx from an MCP server rejecting
|
|
473
497
|
* a naked GET) proves the process is up; connect/DNS/timeout errors mean it is down. Replaces
|
|
474
498
|
* the customer-wide `machineRegistration` round trip when all we needed was "is LOCAL possible?". */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/simplifyqa/adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAU,MAAM,YAAY,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EACV,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAC5F,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAC5D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAClE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EACxF,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAClF,YAAY,EAAE,WAAW,EAAE,aAAa,EAAmB,iBAAiB,EAAE,WAAW,EACzF,cAAc,EAAE,QAAQ,EAAE,YAAY,EACtC,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,uBAAuB,EACjF,6BAA6B,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EACnE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EACxD,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EACpF,WAAW,EAAE,kBAAkB,EAAE,mBAAmB,EACrD,MAAM,uBAAuB,CAAC;AAY/B,kFAAkF;AAClF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,cAAc,GAAG,UAAU,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/simplifyqa/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,12 +7,15 @@ function mapRunState(s) {
|
|
|
7
7
|
switch (s.toUpperCase()) {
|
|
8
8
|
case 'PASSED': return 'passed';
|
|
9
9
|
case 'FAILED': return 'failed';
|
|
10
|
+
case 'SKIPPED': return 'skipped';
|
|
11
|
+
case 'NOTEXECUTED': return 'notExecuted';
|
|
10
12
|
case 'STOPPED': return 'stopped';
|
|
13
|
+
case 'ERROR': return 'error';
|
|
11
14
|
case 'RUNNING':
|
|
12
15
|
case 'INPROGRESS': return 'running';
|
|
13
|
-
case 'QUEUED':
|
|
14
|
-
case '
|
|
15
|
-
default: return '
|
|
16
|
+
case 'QUEUED': return 'queued';
|
|
17
|
+
case '': return 'queued'; // no status yet assigned — genuinely not started
|
|
18
|
+
default: return 'error';
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
21
|
/** Formula names round-trip with parens on ("FIRST_NAME()") from the backend and sometimes with a full
|
|
@@ -224,6 +227,9 @@ export class SimplifyQaAdapter {
|
|
|
224
227
|
* Keyed by `${technologyType}::${lowerCaseName}` so the same action name across two techs
|
|
225
228
|
* (e.g. "click" for WEB + ANDROID) resolves to the right numeric id. */
|
|
226
229
|
this.actionCatalogCache = null;
|
|
230
|
+
/** Populated by actionCatalog() on each fetch. Surfaced in resolveAutomationSteps error messages
|
|
231
|
+
* so a user hitting "no SQA action named X" can see what the fetch actually returned. */
|
|
232
|
+
this.actionCatalogFetchDiag = 'never fetched';
|
|
227
233
|
// ---------------------------------------------------------------------- Objects (read + delete)
|
|
228
234
|
// list/create/delete now go direct REST (same /tm/object base getObject already uses), mirroring
|
|
229
235
|
// the testcase/suite search + soft-delete convention.
|
|
@@ -251,6 +257,18 @@ export class SimplifyQaAdapter {
|
|
|
251
257
|
...(this.cfg.projectId ? { 'x-project-id': this.cfg.projectId } : {}),
|
|
252
258
|
};
|
|
253
259
|
}
|
|
260
|
+
/** Public session context for MCP viewer/recorder windows that need to authenticate the sqa-ui
|
|
261
|
+
* they load. cfg itself stays private; this getter is the one seam through which tc_edit_open
|
|
262
|
+
* and record tools can forward token + baseUrl + projectId to Electron without asking the LLM. */
|
|
263
|
+
getSessionContext() {
|
|
264
|
+
// Constructor already rejected missing baseUrl (~line 265), so `!` is safe here.
|
|
265
|
+
return {
|
|
266
|
+
token: this.cfg.token,
|
|
267
|
+
baseUrl: this.cfg.baseUrl,
|
|
268
|
+
...(this.cfg.projectId ? { projectId: this.cfg.projectId } : {}),
|
|
269
|
+
...(this.cfg.userId ? { userId: this.cfg.userId } : {}),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
254
272
|
requireProjectId() {
|
|
255
273
|
if (!this.cfg.projectId)
|
|
256
274
|
throw new Error(NEEDS_PROJECT);
|
|
@@ -467,7 +485,11 @@ export class SimplifyQaAdapter {
|
|
|
467
485
|
// Overlay currentVersionId to the resolved one and flip isCurrent flags on versions[]. Apply
|
|
468
486
|
// even when rows are empty — a caller who pinned versionId still expects the header + versions[]
|
|
469
487
|
// to reflect that choice (a legitimately-empty version shouldn't fall back to the TC's default).
|
|
470
|
-
|
|
488
|
+
// ponytail: mirror fetchStepsCollection's resolution ladder — the raw doc from /tm/testcase/getById
|
|
489
|
+
// does NOT include a top-level versionId, only a `versions[]` array. Without the versions[0] fallback,
|
|
490
|
+
// tc.currentVersionId stays undefined → tc_save emits versionsDetails.id:"" → backend mints a
|
|
491
|
+
// phantom version and orphans the saved steps.
|
|
492
|
+
const resolvedVer = versionId ?? tc.currentVersionId ?? (Array.isArray(tc.versions) ? tc.versions[0]?.id : undefined);
|
|
471
493
|
const patched = { ...withSteps, ...(resolvedVer ? { currentVersionId: resolvedVer } : {}) };
|
|
472
494
|
if (resolvedVer && patched.versions?.length) {
|
|
473
495
|
patched.versions = patched.versions.map((v) => ({ ...v, isCurrent: v.id === resolvedVer }));
|
|
@@ -2314,8 +2336,10 @@ export class SimplifyQaAdapter {
|
|
|
2314
2336
|
return this.actionCatalogCache;
|
|
2315
2337
|
const projectId = this.requireProjectId();
|
|
2316
2338
|
this.actionCatalogCache = (async () => {
|
|
2317
|
-
//
|
|
2318
|
-
//
|
|
2339
|
+
// Use the SAME filter the SQA UI uses (mobile-app/angular/.../steps.model.ts:59):
|
|
2340
|
+
// {projectId: {$in:[0, projectId]}}. Removing it caused /tm/action/search to return 0 rows in
|
|
2341
|
+
// practice — the backend endpoint apparently requires it as-is despite server-side resolvers
|
|
2342
|
+
// using only {customerId, deleted:false}. Keep the UI's filter to stay compatible.
|
|
2319
2343
|
const body = {
|
|
2320
2344
|
searchFields: [
|
|
2321
2345
|
{ column: 'deleted', value: false },
|
|
@@ -2327,12 +2351,25 @@ export class SimplifyQaAdapter {
|
|
|
2327
2351
|
};
|
|
2328
2352
|
const raw = await this.http.postJson('/tm/action/search', body);
|
|
2329
2353
|
const rows = pickArray(raw, 'data', 'content', 'actions');
|
|
2354
|
+
// Capture diagnostic snapshot so downstream errors can surface WHY the fetch returned 0 rows.
|
|
2355
|
+
try {
|
|
2356
|
+
const topKeys = raw && typeof raw === 'object' ? Object.keys(raw).slice(0, 10).join(',') : typeof raw;
|
|
2357
|
+
const totalCount = raw?.totalCount ?? raw?.total ?? 'n/a';
|
|
2358
|
+
const firstRowKeys = rows[0] && typeof rows[0] === 'object' ? Object.keys(rows[0]).slice(0, 12).join(',') : 'no-rows';
|
|
2359
|
+
this.actionCatalogFetchDiag = `rows.length=${rows.length} totalCount=${totalCount} raw.keys=[${topKeys}] firstRowKeys=[${firstRowKeys}] projectId=${projectId}`;
|
|
2360
|
+
}
|
|
2361
|
+
catch { /* diagnostic must not throw */ }
|
|
2330
2362
|
const map = new Map();
|
|
2331
2363
|
for (const row of rows) {
|
|
2332
2364
|
const idStr = str(row, 'id', '_id', 'actionId');
|
|
2333
2365
|
const name = str(row, 'name');
|
|
2334
|
-
|
|
2335
|
-
|
|
2366
|
+
// Diagnostic proved action rows have NO top-level `technology` field
|
|
2367
|
+
// (firstRowKeys=[id,name,objectRequired,objectTemplate]). SQA UI derives tech by looking up
|
|
2368
|
+
// objectTemplate → object_templates.name — we don't (would need a second fetch). Just require
|
|
2369
|
+
// id; drop the tech-in-key and let names collide across techs. Round-trip lookups (by numeric
|
|
2370
|
+
// id) are unaffected. Model-authored by-name lookups get whichever action registered last for
|
|
2371
|
+
// that name — good enough until cross-tech name collisions actually bite.
|
|
2372
|
+
if (!idStr)
|
|
2336
2373
|
continue;
|
|
2337
2374
|
const id = Number.parseInt(idStr, 10);
|
|
2338
2375
|
if (!Number.isFinite(id))
|
|
@@ -2342,14 +2379,17 @@ export class SimplifyQaAdapter {
|
|
|
2342
2379
|
const objectTemplate = typeof otRaw === 'number' ? otRaw
|
|
2343
2380
|
: typeof otRaw === 'string' && /^\d+$/.test(otRaw) ? Number.parseInt(otRaw, 10)
|
|
2344
2381
|
: undefined;
|
|
2345
|
-
|
|
2346
|
-
//
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2382
|
+
const entry = { id, objectRequired, ...(objectTemplate !== undefined ? { objectTemplate } : {}) };
|
|
2383
|
+
// Name-only key for by-name lookups (may collide across techs — see comment above).
|
|
2384
|
+
if (name) {
|
|
2385
|
+
const key = name.toLowerCase();
|
|
2386
|
+
const prev = map.get(key);
|
|
2387
|
+
const isProjectOverride = typeof row?.projectId === 'number' && row.projectId !== 0;
|
|
2388
|
+
if (!prev || isProjectOverride)
|
|
2389
|
+
map.set(key, entry);
|
|
2390
|
+
}
|
|
2391
|
+
// Id-keyed entry so numeric-actionType round-trip lookups are O(1) instead of a scan.
|
|
2392
|
+
map.set(`id::${id}`, entry);
|
|
2353
2393
|
}
|
|
2354
2394
|
return map;
|
|
2355
2395
|
})().catch((e) => { this.actionCatalogCache = null; throw e; });
|
|
@@ -2406,27 +2446,106 @@ export class SimplifyQaAdapter {
|
|
|
2406
2446
|
async resolveAutomationSteps(projectId, moduleId, tc) {
|
|
2407
2447
|
const catalog = await this.actionCatalog();
|
|
2408
2448
|
const steps = Array.isArray(tc.steps) ? tc.steps : [];
|
|
2449
|
+
// ponytail: objectId can be `undefined` here to mean "don't emit — preserve the backend value".
|
|
2450
|
+
// Used when an existing step (sourceId set) has no round-trip objectRef.id, e.g. because it was
|
|
2451
|
+
// recorded before objectRequired was flipped on the action. Emitting null would $set-clobber the DB.
|
|
2452
|
+
// actionId also nullable for control-flow / function / switch / function_end — those steps don't
|
|
2453
|
+
// have an action; buildAutomationPayload omits action/object emit when actionId is null.
|
|
2409
2454
|
const out = [];
|
|
2410
2455
|
for (let i = 0; i < steps.length; i++) {
|
|
2411
2456
|
const s = steps[i];
|
|
2457
|
+
// ponytail: control-flow / function / switch / function_end steps have no action to resolve.
|
|
2458
|
+
// Push a null-actionId slot so index alignment survives; mapper checks and skips action/object
|
|
2459
|
+
// emit for these. Without this, IF/FOR/WHILE/FUNCTION steps throw "missing actionType" on save.
|
|
2460
|
+
if (s.stepType === 'control_flow' || s.stepType === 'function' || s.stepType === 'function_end' || s.stepType === 'switch' || s.stepType === 'manual') {
|
|
2461
|
+
out.push({ actionId: null, objectId: null });
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
2412
2464
|
const tech = (TECH_BY_STEPTYPE[s.stepType] ?? deriveTcType(tc)).toUpperCase();
|
|
2413
2465
|
const name = (s.actionType ?? '').trim();
|
|
2414
2466
|
if (!name) {
|
|
2415
2467
|
throw new Error(`Automation step ${i + 1} ("${s.description}"): missing actionType — the neutral Step.actionType must name an SQA action (e.g. "Click", "Type Text", "Launch Application").`);
|
|
2416
2468
|
}
|
|
2417
|
-
|
|
2469
|
+
// Round-trip support: a TC hydrated via getTestCase carries actionType = String(actionId), so
|
|
2470
|
+
// on save we may see a numeric string here. Catalog is dual-keyed: `id::N` for round-trip,
|
|
2471
|
+
// `<lowercase name>` for model-authored by-name lookups. Tech is not in the key because
|
|
2472
|
+
// action rows have no top-level `technology` field (SQA UI resolves via objectTemplate lookup).
|
|
2473
|
+
const isNumericId = /^\d+$/.test(name);
|
|
2474
|
+
let meta = isNumericId
|
|
2475
|
+
? catalog.get(`id::${Number.parseInt(name, 10)}`)
|
|
2476
|
+
: catalog.get(name.toLowerCase());
|
|
2477
|
+
// Fallback: if the by-id scan misses (action created in a different project and thus not in
|
|
2478
|
+
// the UI's projectId-filtered catalog), fetch it directly via GET /tm/action/:id. Only tries
|
|
2479
|
+
// for numeric-id lookups — model-authored name lookups have no id to fetch.
|
|
2480
|
+
let fallbackDiag = '';
|
|
2481
|
+
if (!meta && isNumericId) {
|
|
2482
|
+
try {
|
|
2483
|
+
const raw = await this.http.getJson(`/tm/action/${Number.parseInt(name, 10)}`);
|
|
2484
|
+
const row = raw?.data && typeof raw.data === 'object' ? raw.data
|
|
2485
|
+
: Array.isArray(raw?.data) ? raw.data[0]
|
|
2486
|
+
: Array.isArray(raw) ? raw[0]
|
|
2487
|
+
: raw;
|
|
2488
|
+
const idStr = str(row, 'id', '_id', 'actionId');
|
|
2489
|
+
// ponytail: no `technology` gate. Catalog builder proved action rows have no top-level
|
|
2490
|
+
// technology field (see comment at ~line 2357); requiring it here made the fallback
|
|
2491
|
+
// never succeed — every cross-project action id threw "no SQA action named X".
|
|
2492
|
+
if (idStr) {
|
|
2493
|
+
const otRaw = row?.objectTemplate;
|
|
2494
|
+
const objectTemplate = typeof otRaw === 'number' ? otRaw
|
|
2495
|
+
: typeof otRaw === 'string' && /^\d+$/.test(otRaw) ? Number.parseInt(otRaw, 10)
|
|
2496
|
+
: undefined;
|
|
2497
|
+
meta = {
|
|
2498
|
+
id: Number.parseInt(idStr, 10),
|
|
2499
|
+
objectRequired: row?.objectRequired === true,
|
|
2500
|
+
...(objectTemplate !== undefined ? { objectTemplate } : {}),
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
else {
|
|
2504
|
+
fallbackDiag = ` [GET /tm/action/${name} returned no usable row (idStr=null). Raw keys: ${row && typeof row === 'object' ? Object.keys(row).join(',') : typeof row}]`;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
catch (e) {
|
|
2508
|
+
fallbackDiag = ` [GET /tm/action/${name} failed: ${e.message}]`;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2418
2511
|
if (!meta) {
|
|
2419
|
-
throw new Error(`Automation step ${i + 1}: no SQA action named "${name}" for technology ${tech}.
|
|
2512
|
+
throw new Error(`Automation step ${i + 1}: no SQA action named "${name}" for technology ${tech}. Catalog size=${catalog.size}, numericId=${isNumericId}. FetchDiag: ${this.actionCatalogFetchDiag}.${fallbackDiag}`);
|
|
2420
2513
|
}
|
|
2421
2514
|
let objectId = null;
|
|
2422
2515
|
if (meta.objectRequired) {
|
|
2423
|
-
|
|
2424
|
-
|
|
2516
|
+
// Resolution ladder (first match wins):
|
|
2517
|
+
// 1. objectRef.id set → use directly (round-trip OR LLM referencing a known object)
|
|
2518
|
+
// 2. sourceId set but no objectRef.id → preserve DB value (existing step, backend may
|
|
2519
|
+
// have null historically; emitting anything would $set-clobber)
|
|
2520
|
+
// 3. objectRef.name only (no locators) → project-scoped name lookup (LLM added a step
|
|
2521
|
+
// referencing an existing Object Repository entry by name — mirrors UI dropdown)
|
|
2522
|
+
// 4. objectRef.name + locators → get-or-create via createObjectSeq (recorder path)
|
|
2523
|
+
// 5. nothing usable → throw with the three-option error so the caller can pick
|
|
2524
|
+
const roundTripId = s.objectRef?.id ? Number.parseInt(String(s.objectRef.id), 10) : NaN;
|
|
2525
|
+
if (Number.isFinite(roundTripId)) {
|
|
2526
|
+
objectId = roundTripId;
|
|
2527
|
+
}
|
|
2528
|
+
else if (s.sourceId) {
|
|
2529
|
+
objectId = undefined;
|
|
2530
|
+
}
|
|
2531
|
+
else if (s.objectRef?.name && !(s.objectRef.locators?.length)) {
|
|
2532
|
+
const found = await this.findObjectIdByName(projectId, s.objectRef.name);
|
|
2533
|
+
if (found != null) {
|
|
2534
|
+
objectId = found;
|
|
2535
|
+
}
|
|
2536
|
+
else {
|
|
2537
|
+
throw new Error(`Automation step ${i + 1} ("${name}"): no object named "${s.objectRef.name}" found in this project. Either pass objectRef.id to reuse an existing one, or add objectRef.locators:[{name, value}, ...] to create a new object with that name.`);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
else if (s.objectRef?.name && s.objectRef.locators?.length) {
|
|
2541
|
+
if (meta.objectTemplate == null) {
|
|
2542
|
+
throw new Error(`Automation step ${i + 1} ("${name}"): action ${meta.id} has no objectTemplate — cannot create/reuse an object without knowing which template it belongs to.`);
|
|
2543
|
+
}
|
|
2544
|
+
objectId = await this.resolveObjectId(s.objectRef, projectId, moduleId, tech, meta.objectTemplate);
|
|
2425
2545
|
}
|
|
2426
|
-
|
|
2427
|
-
throw new Error(`Automation step ${i + 1} ("${name}"): action
|
|
2546
|
+
else {
|
|
2547
|
+
throw new Error(`Automation step ${i + 1} ("${name}"): action requires an object but Step.objectRef is missing. Provide objectRef:{id} to reuse a known one, objectRef:{name} to look one up by name in the project, or objectRef:{name, locators:[{name, value}]} to create a new one.`);
|
|
2428
2548
|
}
|
|
2429
|
-
objectId = await this.resolveObjectId(s.objectRef, projectId, moduleId, tech, meta.objectTemplate);
|
|
2430
2549
|
}
|
|
2431
2550
|
out.push({ actionId: meta.id, objectId });
|
|
2432
2551
|
}
|
|
@@ -2455,6 +2574,28 @@ export class SimplifyQaAdapter {
|
|
|
2455
2574
|
}
|
|
2456
2575
|
return null;
|
|
2457
2576
|
}
|
|
2577
|
+
/** Case-insensitive project-scoped lookup of an existing object by `disname` (the UI's Name).
|
|
2578
|
+
* Lets tc_draft_update pass `objectRef:{name}` for an already-defined object without also
|
|
2579
|
+
* supplying locators — mirrors what a user picking from the Object Repository dropdown does. */
|
|
2580
|
+
async findObjectIdByName(projectId, name) {
|
|
2581
|
+
const body = this.searchBody([
|
|
2582
|
+
{ column: 'disname', value: name },
|
|
2583
|
+
{ column: 'projectId', value: projectId },
|
|
2584
|
+
{ column: 'deleted', value: false },
|
|
2585
|
+
], { startIndex: 0, limit: 10 });
|
|
2586
|
+
body['searchFields'][0]['ignorecase'] = true;
|
|
2587
|
+
const raw = await this.http.postJson('/tm/object/search', body);
|
|
2588
|
+
const rows = pickArray(raw, 'data', 'content', 'objects');
|
|
2589
|
+
for (const row of rows) {
|
|
2590
|
+
const rowName = str(row, 'disname');
|
|
2591
|
+
if (rowName?.toLowerCase() === name.toLowerCase()) {
|
|
2592
|
+
const id = str(row, 'id', '_id');
|
|
2593
|
+
if (id)
|
|
2594
|
+
return Number.parseInt(id, 10);
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
return null;
|
|
2598
|
+
}
|
|
2458
2599
|
async getOrCreateParameter(projectId, moduleId, name, defaultValue) {
|
|
2459
2600
|
const existing = await this.findParameterId(projectId, name);
|
|
2460
2601
|
if (existing != null)
|
|
@@ -2885,7 +3026,19 @@ export class SimplifyQaAdapter {
|
|
|
2885
3026
|
}
|
|
2886
3027
|
// ---------------------------------------------------------------------- Actions catalog
|
|
2887
3028
|
async actionsCatalog(platform) {
|
|
2888
|
-
|
|
3029
|
+
// Build from the live backend catalog (already cached per-connection). Each entry gets its
|
|
3030
|
+
// numeric id + objectRequired flag so callers can round-trip actionType ↔ actionId. Ignores
|
|
3031
|
+
// `platform` — action rows carry no top-level technology; tech disambiguation is a follow-up
|
|
3032
|
+
// (would need an object_templates lookup). Skips the `id::N` reverse-lookup keys.
|
|
3033
|
+
void platform;
|
|
3034
|
+
const catalog = await this.actionCatalog();
|
|
3035
|
+
const defs = [];
|
|
3036
|
+
for (const [key, meta] of catalog) {
|
|
3037
|
+
if (key.startsWith('id::'))
|
|
3038
|
+
continue; // reverse-lookup entry — already covered by name key
|
|
3039
|
+
defs.push({ actionType: key, platform: 'unknown', id: meta.id, objectRequired: meta.objectRequired });
|
|
3040
|
+
}
|
|
3041
|
+
return defs;
|
|
2889
3042
|
}
|
|
2890
3043
|
// ---------------------------------------------------------------------- Testdata generation [Step 3]
|
|
2891
3044
|
/** Fetch the default (template) iteration row for a TC. Fills gaps from the raw TC:
|
|
@@ -3138,10 +3291,14 @@ export class SimplifyQaAdapter {
|
|
|
3138
3291
|
const total = children.length;
|
|
3139
3292
|
const done = children.filter((c) => c.state !== 'queued' && c.state !== 'running').length;
|
|
3140
3293
|
const suiteNum = Number(execDoc.executionTypeId ?? 0);
|
|
3294
|
+
const startedAt = str(execDoc, 'startTime', 'createdAt');
|
|
3295
|
+
const endedAt = str(execDoc, 'endTime');
|
|
3141
3296
|
return {
|
|
3142
3297
|
runId,
|
|
3143
3298
|
state: mapRunState(str(execDoc, 'status') ?? ''),
|
|
3144
3299
|
...(total > 0 ? { progress: { done, total }, children } : {}),
|
|
3300
|
+
...(startedAt ? { startedAt } : {}),
|
|
3301
|
+
...(endedAt ? { endedAt } : {}),
|
|
3145
3302
|
reportUrl: `${base}/user/reports/reports-page?type=SUITE&parentId=${suiteNum}&id=${runIdNum}&projectId=${projectId}`,
|
|
3146
3303
|
};
|
|
3147
3304
|
}
|
|
@@ -3307,11 +3464,10 @@ export class SimplifyQaAdapter {
|
|
|
3307
3464
|
// SUITE runs are not yet ported and still hop (or error clearly).
|
|
3308
3465
|
async startRun(target, opts) {
|
|
3309
3466
|
const projectId = this.requireProjectId();
|
|
3467
|
+
if (target.suiteId && !target.tcId) {
|
|
3468
|
+
return this.startSuiteRun(target.suiteId, opts);
|
|
3469
|
+
}
|
|
3310
3470
|
if (!target.tcId) {
|
|
3311
|
-
if (target.suiteId) {
|
|
3312
|
-
throw new Error('startRun: SUITE runs are not yet ported to direct REST — set QA_MCP_URL so common-mcp ' +
|
|
3313
|
-
'can hop to the SimplifyQA agent for suite dispatch, or run a single TC.');
|
|
3314
|
-
}
|
|
3315
3471
|
throw new Error('startRun: target.tcId (or target.suiteId) is required.');
|
|
3316
3472
|
}
|
|
3317
3473
|
const tcId = target.tcId;
|
|
@@ -3485,6 +3641,192 @@ export class SimplifyQaAdapter {
|
|
|
3485
3641
|
killType: 'STOP_EXECUTION',
|
|
3486
3642
|
});
|
|
3487
3643
|
}
|
|
3644
|
+
/** SUITE dispatch. Mirrors `startRun` above (which handles TC) — same `/exec/execution` endpoint,
|
|
3645
|
+
* same LOCAL-agent fast-fail, same project-name / environment resolution. What differs is on the
|
|
3646
|
+
* wire: `type: 'SUITE'`, `executionType` sourced from `suite.suiteType`, `mode` defaulting to
|
|
3647
|
+
* `suite.executionType`, a populated `globalConfiguration` block, and one `executionItems` entry
|
|
3648
|
+
* PER member — each carrying the suite row's persisted `versionId` / `stepId` / `platform` / `seq`
|
|
3649
|
+
* (NOT a re-derived random step id). See sqa-ui execution-popup-modal.component.ts:621-1105 SU
|
|
3650
|
+
* branch for the authoritative UI builder; every field below traces back to a line there. */
|
|
3651
|
+
async startSuiteRun(suiteId, opts) {
|
|
3652
|
+
const projectId = this.requireProjectId();
|
|
3653
|
+
const suiteNum = Number.parseInt(numericId(suiteId), 10);
|
|
3654
|
+
if (!Number.isFinite(suiteNum))
|
|
3655
|
+
throw new Error(`startRun: could not resolve numeric id for ${suiteId}.`);
|
|
3656
|
+
const rawSuite = await this.fetchRawSuite(suiteId);
|
|
3657
|
+
if (!rawSuite || Object.keys(rawSuite).length === 0) {
|
|
3658
|
+
throw new Error(`startRun: suite ${suiteId} not found.`);
|
|
3659
|
+
}
|
|
3660
|
+
// Effective mode: caller pick wins (matches our tool's cascade UX); fall back to the suite doc's
|
|
3661
|
+
// stored `executionType`, which the UI reads unconditionally (execution-popup-modal.ts:668).
|
|
3662
|
+
const mode = (opts.mode ?? String(rawSuite.executionType ?? 'LOCAL')).toUpperCase();
|
|
3663
|
+
if (mode === 'LOCAL' && !(await this.probeLocalAgent())) {
|
|
3664
|
+
throw new Error(`startRun (LOCAL): SimplifyQA desktop agent is not reachable at ` +
|
|
3665
|
+
`${this.cfg.url ?? '(no agent URL configured — set QA_MCP_URL)'}. Start the desktop ` +
|
|
3666
|
+
`agent, then try again. Use mode:'cloud' to run on a cloud runner instead — it does not ` +
|
|
3667
|
+
`need the desktop agent.`);
|
|
3668
|
+
}
|
|
3669
|
+
const projectName = await this.lookupProjectName(projectId);
|
|
3670
|
+
if (!projectName) {
|
|
3671
|
+
throw new Error(`startRun: could not resolve project name for projectId=${projectId}. ` +
|
|
3672
|
+
`The token may not have read access to this project.`);
|
|
3673
|
+
}
|
|
3674
|
+
const env = await this.resolveEnvironment(projectId, opts.environment);
|
|
3675
|
+
const suiteType = String(rawSuite.suiteType ?? 'AUTOMATION').toUpperCase();
|
|
3676
|
+
// Build one executionItems entry per suite row, in suite order. Silently skip rows whose TC no
|
|
3677
|
+
// longer exists (matches UI :880 `if (!data) return;`). Every item uses the suite row's stored
|
|
3678
|
+
// versionId + stepId + per-mode platform — regenerating any of those splits step correlation
|
|
3679
|
+
// and breaks the agent's iteration join. Missing platform for a row is not an error here (UI
|
|
3680
|
+
// sends `[]`) — the agent tolerates it for MANUAL suites; AUTOMATION suites without a saved
|
|
3681
|
+
// platform were misconfigured upstream and would fail regardless.
|
|
3682
|
+
const members = Array.isArray(rawSuite.testCases) ? rawSuite.testCases : [];
|
|
3683
|
+
const executionItems = [];
|
|
3684
|
+
let seqCounter = 0;
|
|
3685
|
+
for (const row of members) {
|
|
3686
|
+
seqCounter++;
|
|
3687
|
+
const memberTcId = row?.testcaseId;
|
|
3688
|
+
if (memberTcId === undefined || memberTcId === null)
|
|
3689
|
+
continue;
|
|
3690
|
+
let rawTc;
|
|
3691
|
+
try {
|
|
3692
|
+
rawTc = await this.fetchRawTestcase(String(memberTcId));
|
|
3693
|
+
}
|
|
3694
|
+
catch {
|
|
3695
|
+
continue;
|
|
3696
|
+
}
|
|
3697
|
+
if (!rawTc || Object.keys(rawTc).length === 0)
|
|
3698
|
+
continue;
|
|
3699
|
+
const tcType = String(str(rawTc, 'tcType') ?? 'WEB').toUpperCase();
|
|
3700
|
+
const versions = Array.isArray(rawTc?.versions) ? rawTc.versions : [];
|
|
3701
|
+
const rowVersionId = row?.versionId ?? row?.version;
|
|
3702
|
+
const versionMatch = rowVersionId
|
|
3703
|
+
? versions.find((v) => str(v, 'id', '_id') === String(rowVersionId) || str(v, 'name') === String(rowVersionId))
|
|
3704
|
+
: undefined;
|
|
3705
|
+
const versionId = str(versionMatch, 'id', '_id') ?? (rowVersionId !== undefined ? String(rowVersionId) : '');
|
|
3706
|
+
const versionName = str(versionMatch, 'name') ?? str(rawTc, 'versionName', 'selectedVersionName') ?? 'v1.0';
|
|
3707
|
+
// Per-mode platform from the suite row. UI reads `data1.executionPlatform[mode]` (:882-965);
|
|
3708
|
+
// when saved, it's a single object → wrap in `[obj]`; if the row didn't save one for this
|
|
3709
|
+
// mode, send `[]` and let the backend reject/accept per its own rules.
|
|
3710
|
+
const savedPlatform = row?.executionPlatform?.[mode];
|
|
3711
|
+
let platform = [];
|
|
3712
|
+
if (savedPlatform && typeof savedPlatform === 'object') {
|
|
3713
|
+
const p = {
|
|
3714
|
+
name: savedPlatform.name,
|
|
3715
|
+
id: savedPlatform.id ?? savedPlatform.name,
|
|
3716
|
+
technologyType: String(savedPlatform.technologyType ?? tcType).toUpperCase(),
|
|
3717
|
+
type: savedPlatform.type ?? 'NONE',
|
|
3718
|
+
};
|
|
3719
|
+
if (savedPlatform.cloudDeviceType)
|
|
3720
|
+
p['cloudDeviceType'] = savedPlatform.cloudDeviceType;
|
|
3721
|
+
if (savedPlatform.osVersion)
|
|
3722
|
+
p['osVersion'] = savedPlatform.osVersion;
|
|
3723
|
+
platform = [p];
|
|
3724
|
+
}
|
|
3725
|
+
else if (['API', 'MAINFRAME', 'SAP', 'DESKTOP', 'DATABASE'].includes(tcType)) {
|
|
3726
|
+
platform = [{ name: tcType, id: tcType, technologyType: tcType, type: 'NONE' }];
|
|
3727
|
+
}
|
|
3728
|
+
const moduleIdNum = typeof rawTc?.moduleId === 'number' ? rawTc.moduleId : 0;
|
|
3729
|
+
let moduleName = '';
|
|
3730
|
+
if (moduleIdNum > 0) {
|
|
3731
|
+
try {
|
|
3732
|
+
const body = this.searchBody([{ column: 'id', value: moduleIdNum }, { column: 'deleted', value: false }], { selectFields: ['id', 'name'], limit: 1 });
|
|
3733
|
+
const raw = await this.http.postJson('/pm/module/search', body);
|
|
3734
|
+
const rows = pickArray(raw, 'data', 'content', 'modules');
|
|
3735
|
+
moduleName = str(rows[0], 'name') ?? '';
|
|
3736
|
+
}
|
|
3737
|
+
catch { /* leave empty */ }
|
|
3738
|
+
}
|
|
3739
|
+
executionItems.push({
|
|
3740
|
+
id: typeof rawTc?.id === 'number' ? rawTc.id : Number.parseInt(numericId(String(memberTcId)), 10),
|
|
3741
|
+
name: str(rawTc, 'name', 'title') ?? '',
|
|
3742
|
+
description: str(rawTc, 'description') ?? '',
|
|
3743
|
+
versionId,
|
|
3744
|
+
versionName,
|
|
3745
|
+
seq: typeof row?.seq === 'number' ? row.seq : seqCounter,
|
|
3746
|
+
stepId: row?.stepId ?? '', // MUST come from the suite row, never regenerated
|
|
3747
|
+
stepCount: -1, // suite items send -1 (TC sends 0)
|
|
3748
|
+
technologyType: tcType,
|
|
3749
|
+
type: 'TC', // every item is 'TC'; suite-ness lives at top-level type
|
|
3750
|
+
platform,
|
|
3751
|
+
projectId,
|
|
3752
|
+
projectName,
|
|
3753
|
+
configuration: {},
|
|
3754
|
+
iterationsSelected: [],
|
|
3755
|
+
moduleId: moduleIdNum,
|
|
3756
|
+
userstoryId: typeof rawTc?.userstoryId === 'number' ? rawTc.userstoryId : 0,
|
|
3757
|
+
tags: Array.isArray(rawTc?.tags) ? rawTc.tags : [],
|
|
3758
|
+
code: str(rawTc, 'code') ?? '',
|
|
3759
|
+
prerequisite: str(rawTc, 'prerequisite', 'preconditions') ?? '',
|
|
3760
|
+
moduleName,
|
|
3761
|
+
userstoryName: '',
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3764
|
+
if (executionItems.length === 0) {
|
|
3765
|
+
throw new Error(`startRun: suite ${suiteId} has no runnable test cases (all members were deleted, ` +
|
|
3766
|
+
`unreadable, or filtered out).`);
|
|
3767
|
+
}
|
|
3768
|
+
// agentId derivation (mirrors UI :677). LOCAL always uses this machine's agent; REMOTE/CLOUD
|
|
3769
|
+
// pin the suite doc's saved `agentId` (or null when the suite wasn't pinned). CLOUD_AGENT
|
|
3770
|
+
// fallback (:771-774) promotes `cloudType` to 'SQA'.
|
|
3771
|
+
let agentId;
|
|
3772
|
+
if (mode === 'LOCAL') {
|
|
3773
|
+
agentId = await this.resolveLocalAgentId(opts.agentId);
|
|
3774
|
+
}
|
|
3775
|
+
else if (mode === 'REMOTE') {
|
|
3776
|
+
const preferred = opts.agentId ?? (typeof rawSuite.agentId === 'string' ? rawSuite.agentId : undefined);
|
|
3777
|
+
agentId = preferred ? await this.resolveRemoteAgentId(preferred) : null;
|
|
3778
|
+
}
|
|
3779
|
+
else {
|
|
3780
|
+
agentId = typeof rawSuite.agentId === 'string' && rawSuite.agentId.length > 0 ? rawSuite.agentId : null;
|
|
3781
|
+
}
|
|
3782
|
+
let cloudType = mode === 'CLOUD' ? 'VM' : 'NONE';
|
|
3783
|
+
if (!agentId && typeof rawSuite.cloudAgentId === 'string' && rawSuite.cloudAgentId.length > 0) {
|
|
3784
|
+
agentId = rawSuite.cloudAgentId;
|
|
3785
|
+
cloudType = 'SQA';
|
|
3786
|
+
}
|
|
3787
|
+
const gc = rawSuite.globalConfiguration ?? {};
|
|
3788
|
+
const body = {
|
|
3789
|
+
projectId,
|
|
3790
|
+
environmentType: env,
|
|
3791
|
+
executionStyle: (opts.strategy ?? 'serial').toUpperCase(),
|
|
3792
|
+
executionOS: this.detectExecutionOS(),
|
|
3793
|
+
executionTypeId: suiteNum,
|
|
3794
|
+
executionTypeName: str(rawSuite, 'name') ?? '',
|
|
3795
|
+
executionTypeCode: str(rawSuite, 'code') ?? '',
|
|
3796
|
+
mailersList: rawSuite.mailersList ?? {},
|
|
3797
|
+
cloudType,
|
|
3798
|
+
mode,
|
|
3799
|
+
executionCategory: 'EXECUTION',
|
|
3800
|
+
releaseId: opts.releaseId ? Number.parseInt(opts.releaseId, 10) || 0 : 0,
|
|
3801
|
+
iterationId: opts.sprintId ? Number.parseInt(opts.sprintId, 10) || 0 : 0,
|
|
3802
|
+
executionTime: '',
|
|
3803
|
+
status: 'INPROGRESS',
|
|
3804
|
+
iterationsSelected: [],
|
|
3805
|
+
result: '',
|
|
3806
|
+
globalConfiguration: {
|
|
3807
|
+
TERMINATE_ON_FAIL: opts.terminateOnFailure ?? gc.TERMINATE_ON_FAIL ?? false,
|
|
3808
|
+
NEW_BROWSER_SESSION: gc.NEW_BROWSER_SESSION ?? false,
|
|
3809
|
+
RE_EXECUTION_COUNT: gc.RE_EXECUTION_COUNT ?? 0,
|
|
3810
|
+
RE_EXECUTION_TYPE: gc.RE_EXECUTION_TYPE ?? 'AFTER_TESTCASE',
|
|
3811
|
+
MINIMUM_CONTAINER_COUNT: gc.MINIMUM_CONTAINER_COUNT ?? 1,
|
|
3812
|
+
MAXIMUM_CONTAINER_COUNT: opts.maxConcurrent ?? gc.MAXIMUM_CONTAINER_COUNT ?? 5,
|
|
3813
|
+
},
|
|
3814
|
+
isRecordingEnabled: false,
|
|
3815
|
+
offset: -(new Date().getTimezoneOffset()),
|
|
3816
|
+
type: 'SUITE',
|
|
3817
|
+
executionType: suiteType,
|
|
3818
|
+
agentId,
|
|
3819
|
+
executionItems,
|
|
3820
|
+
};
|
|
3821
|
+
const resp = await this.http.postJson('/exec/execution', body);
|
|
3822
|
+
const d = resp?.data ?? resp;
|
|
3823
|
+
const runId = str(d, 'id', 'executionId', '_id') ??
|
|
3824
|
+
(typeof d?.id === 'number' ? String(d.id) : undefined) ??
|
|
3825
|
+
(typeof d?.executionId === 'number' ? String(d.executionId) : undefined);
|
|
3826
|
+
if (!runId)
|
|
3827
|
+
throw new Error('startRun: backend accepted suite trigger but returned no run id.');
|
|
3828
|
+
return { runId };
|
|
3829
|
+
}
|
|
3488
3830
|
/** GET the agent /mcp with a 1s timeout. Any HTTP response (incl. 4xx from an MCP server rejecting
|
|
3489
3831
|
* a naked GET) proves the process is up; connect/DNS/timeout errors mean it is down. Replaces
|
|
3490
3832
|
* the customer-wide `machineRegistration` round trip when all we needed was "is LOCAL possible?". */
|