common-qa-mcp 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/remoteMcp/adapter.js +1 -1
- package/dist/adapters/simplifyqa/adapter.d.ts +40 -10
- package/dist/adapters/simplifyqa/adapter.d.ts.map +1 -1
- package/dist/adapters/simplifyqa/adapter.js +217 -68
- package/dist/adapters/simplifyqa/adapter.js.map +1 -1
- package/dist/adapters/types.d.ts +7 -2
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/domain/types.d.ts +3 -2
- package/dist/domain/types.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/tools/backlog.tools.d.ts.map +1 -1
- package/dist/tools/backlog.tools.js +130 -24
- package/dist/tools/backlog.tools.js.map +1 -1
- package/dist/tools/discovery.tools.d.ts.map +1 -1
- package/dist/tools/discovery.tools.js +42 -1
- package/dist/tools/discovery.tools.js.map +1 -1
- package/dist/tools/run.tools.d.ts.map +1 -1
- package/dist/tools/run.tools.js +6 -0
- package/dist/tools/run.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.5.0' });
|
|
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
|
|
@@ -22,6 +22,7 @@ export declare class SimplifyQaAdapter {
|
|
|
22
22
|
private readonly http;
|
|
23
23
|
private agentHop;
|
|
24
24
|
private formulaCache;
|
|
25
|
+
private backlogDefaultsCache;
|
|
25
26
|
constructor(cfg: ConnectedQaConfig, transport?: SimplifyQaTransport);
|
|
26
27
|
private headers;
|
|
27
28
|
private requireProjectId;
|
|
@@ -89,6 +90,23 @@ export declare class SimplifyQaAdapter {
|
|
|
89
90
|
listDefects(q: DefectQuery): Promise<ListResult<DefectSummary>>;
|
|
90
91
|
/** `POST /pm/task/search` — same searchFields protocol as defect/userstory search. */
|
|
91
92
|
listTasks(q: TaskQuery): Promise<ListResult<TaskSummary>>;
|
|
93
|
+
/** Fetch the project's default `state` and `priority` for a backlog entity.
|
|
94
|
+
* parentId: 1 = Epic, 2 = Feature, 3 = User Story. Two-source resolution, in order:
|
|
95
|
+
*
|
|
96
|
+
* 1. `/pm/fields/:parentId?fieldType=defaultFld` — pick the field value marked `isDefault: true`;
|
|
97
|
+
* fall back to the first non-archived value (seed ships with `isDefault: false` everywhere
|
|
98
|
+
* until a customer configures one).
|
|
99
|
+
* 2. If either state or priority is still missing, hit the entity's `/search` endpoint for one
|
|
100
|
+
* existing row in the project and steal its state / priority. Real data is a more reliable
|
|
101
|
+
* signal than the fields config, which is customer-scoped for Epic/Feature (parentIds 1/2)
|
|
102
|
+
* and can be missing entirely on customers that pre-date the seeding step.
|
|
103
|
+
*
|
|
104
|
+
* Returns the value's `.key` (uppercase enum like `PROPOSED`, `MEDIUM`) — that is what the entity
|
|
105
|
+
* row stores; the field metadata declares `keyname: "key"` / `type: "KEY"` and the SQA UI binding
|
|
106
|
+
* (see epic-form.component.html: `keyname="key" type="KEY" [displayKeys]="{'field':'value'}"`) confirms
|
|
107
|
+
* key is stored, `.value` is only for display. Both sources are wrapped in try/catch: a transient
|
|
108
|
+
* upstream error must not block create; the backend surfaces its own error if invalid. */
|
|
109
|
+
private getBacklogDefaults;
|
|
92
110
|
createStory(input: StoryInput): Promise<SavedRef>;
|
|
93
111
|
updateStory(id: string, patch: StoryUpdate): Promise<SavedRef>;
|
|
94
112
|
listEpics(q: EpicQuery): Promise<ListResult<EpicSummary>>;
|
|
@@ -183,6 +201,7 @@ export declare class SimplifyQaAdapter {
|
|
|
183
201
|
moduleId: number;
|
|
184
202
|
copyTestData?: boolean;
|
|
185
203
|
executionType?: 'AUTOMATION' | 'MANUAL';
|
|
204
|
+
versionId?: string | undefined;
|
|
186
205
|
}): Promise<SavedRef>;
|
|
187
206
|
/** Look up the freshly-created FN's version id — STEPSTOFN's response inlines the FN but not its
|
|
188
207
|
* version, and the replace-in-TC step needs `fnVersionId`. Mirrors the FE
|
|
@@ -207,6 +226,7 @@ export declare class SimplifyQaAdapter {
|
|
|
207
226
|
fnVersionId: string;
|
|
208
227
|
fnModuleId: number;
|
|
209
228
|
technologyType: string;
|
|
229
|
+
versionId?: string | undefined;
|
|
210
230
|
}): Promise<SavedRef>;
|
|
211
231
|
/** One-shot extract-to-function-and-replace. Chains STEPSTOFN → fetch fn version id → replace in
|
|
212
232
|
* source TC. If step 2 or 3 fails after step 1 succeeded, the caller sees the fn id in the error
|
|
@@ -217,6 +237,7 @@ export declare class SimplifyQaAdapter {
|
|
|
217
237
|
moduleId: number;
|
|
218
238
|
copyTestData?: boolean;
|
|
219
239
|
executionType?: 'AUTOMATION' | 'MANUAL';
|
|
240
|
+
versionId?: string | undefined;
|
|
220
241
|
}): Promise<SavedRef & {
|
|
221
242
|
fnId: string;
|
|
222
243
|
fnVersionId: string;
|
|
@@ -408,21 +429,30 @@ export declare class SimplifyQaAdapter {
|
|
|
408
429
|
startRun(target: RunTarget, opts: RunOptions): Promise<RunRef>;
|
|
409
430
|
stopRun(runId: string): Promise<void>;
|
|
410
431
|
listCloudRunners(): Promise<CloudRunner[]>;
|
|
432
|
+
/** Gather every identifier this process can claim: bare hostname, non-loopback MAC addresses,
|
|
433
|
+
* non-loopback IPs. Cached per-adapter — network interfaces do not change mid-session. */
|
|
434
|
+
private localIdsCache;
|
|
435
|
+
private getLocalIdentifiers;
|
|
436
|
+
/** Does a machineRegistration row belong to the machine this MCP is running on?
|
|
437
|
+
* A single positive match on hostname, MAC, or IP is enough — the local agent registers with
|
|
438
|
+
* ALL of these, but the surviving match on the SQA row depends on OS quirks (Windows writes a
|
|
439
|
+
* padded hostname, macOS writes `foo.local`, IPs shift on Wi-Fi handoff). Any one match is
|
|
440
|
+
* proof enough; we don't try to reconcile the others. */
|
|
441
|
+
private isThisMachine;
|
|
411
442
|
listLocalAgents(): Promise<LocalAgent[]>;
|
|
412
443
|
/**
|
|
413
|
-
* Pick THE ACTING USER'S OWN
|
|
414
|
-
*
|
|
415
|
-
*
|
|
444
|
+
* Pick THE ACTING USER'S OWN agent — never row [0] of a shared-tenant roster (which would
|
|
445
|
+
* silently dispatch a local run to a teammate's box, and the run would sit queued until TTL
|
|
446
|
+
* because it lands in a Redis key that no local agent on the caller's machine polls).
|
|
416
447
|
*
|
|
417
448
|
* SQA's `machineRegistration` row carries no owner_user_id column (unlike walnut, which matches
|
|
418
|
-
* the JWT `id` claim to `owner_user_id`), so we identify "your" agent by
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
* `
|
|
422
|
-
* Zero/many matches refuse honestly; a silent guess would just repeat the exact bug we're fixing.
|
|
449
|
+
* the JWT `id` claim to `owner_user_id`), so we identify "your" agent by physical identity:
|
|
450
|
+
* hostname, MAC address, or IP — any one match against `os.networkInterfaces()` on the box this
|
|
451
|
+
* MCP is running on. When exactly one row is "this machine", pick it regardless of the DB
|
|
452
|
+
* `active` flag (that flag is stale by design — see listLocalAgents for the executionQueue bug).
|
|
423
453
|
*
|
|
424
|
-
* ponytail:
|
|
425
|
-
* to machineRegistration (mirror walnut's `resolveLocalAgentCredential` at that point).
|
|
454
|
+
* ponytail: multi-identifier heuristic. Upgrade to owner-user-id match if the backend adds that
|
|
455
|
+
* column to machineRegistration (mirror walnut's `resolveLocalAgentCredential` at that point).
|
|
426
456
|
*/
|
|
427
457
|
private resolveLocalAgentId;
|
|
428
458
|
private detectExecutionOS;
|
|
@@ -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,iBAAiB,EAAE,kBAAkB,EACpE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,EAChF,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;AA0NrG;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAiB;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/simplifyqa/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -231,6 +231,9 @@ export class SimplifyQaAdapter {
|
|
|
231
231
|
// (from the Promise.all in saveGeneratedIterations) all await the same fetch instead of each firing
|
|
232
232
|
// their own /tm/syncFormulas/fetch before the cache is populated.
|
|
233
233
|
this.formulaCache = null;
|
|
234
|
+
// Same rationale for the backlog default state/priority per parentId (1=Epic, 2=Feature, 3=Story).
|
|
235
|
+
// Fields config barely changes mid-session; cache the in-flight promise so parallel creates share it.
|
|
236
|
+
this.backlogDefaultsCache = new Map();
|
|
234
237
|
// ---------------------------------------------------------------------- Automation resolvers
|
|
235
238
|
/** POST /tm/action/search → per (technology, name) → row (id + objectRequired + objectTemplate).
|
|
236
239
|
* Cached per adapter instance: the catalog is customer-wide and rarely changes during a session.
|
|
@@ -244,6 +247,9 @@ export class SimplifyQaAdapter {
|
|
|
244
247
|
* formulaCache. Populated from `POST /tm/objectTemplate/search` (confirmed endpoint,
|
|
245
248
|
* api-endpoints.ts `object.search`'s sibling `objectTemplate.search`). */
|
|
246
249
|
this.objectTemplateCache = null;
|
|
250
|
+
/** Gather every identifier this process can claim: bare hostname, non-loopback MAC addresses,
|
|
251
|
+
* non-loopback IPs. Cached per-adapter — network interfaces do not change mid-session. */
|
|
252
|
+
this.localIdsCache = null;
|
|
247
253
|
if (!cfg.baseUrl) {
|
|
248
254
|
throw new Error('SimplifyQaAdapter requires a baseUrl — set QA_BASE_URL or use a JWT with a `url` claim.');
|
|
249
255
|
}
|
|
@@ -738,12 +744,102 @@ export class SimplifyQaAdapter {
|
|
|
738
744
|
const rows = pickArray(raw, 'data', 'content', 'tasks');
|
|
739
745
|
return withTotal(rows.map(toTaskSummary), raw);
|
|
740
746
|
}
|
|
747
|
+
// ---------------------------------------------------------------------- Backlog defaults
|
|
748
|
+
/** Fetch the project's default `state` and `priority` for a backlog entity.
|
|
749
|
+
* parentId: 1 = Epic, 2 = Feature, 3 = User Story. Two-source resolution, in order:
|
|
750
|
+
*
|
|
751
|
+
* 1. `/pm/fields/:parentId?fieldType=defaultFld` — pick the field value marked `isDefault: true`;
|
|
752
|
+
* fall back to the first non-archived value (seed ships with `isDefault: false` everywhere
|
|
753
|
+
* until a customer configures one).
|
|
754
|
+
* 2. If either state or priority is still missing, hit the entity's `/search` endpoint for one
|
|
755
|
+
* existing row in the project and steal its state / priority. Real data is a more reliable
|
|
756
|
+
* signal than the fields config, which is customer-scoped for Epic/Feature (parentIds 1/2)
|
|
757
|
+
* and can be missing entirely on customers that pre-date the seeding step.
|
|
758
|
+
*
|
|
759
|
+
* Returns the value's `.key` (uppercase enum like `PROPOSED`, `MEDIUM`) — that is what the entity
|
|
760
|
+
* row stores; the field metadata declares `keyname: "key"` / `type: "KEY"` and the SQA UI binding
|
|
761
|
+
* (see epic-form.component.html: `keyname="key" type="KEY" [displayKeys]="{'field':'value'}"`) confirms
|
|
762
|
+
* key is stored, `.value` is only for display. Both sources are wrapped in try/catch: a transient
|
|
763
|
+
* upstream error must not block create; the backend surfaces its own error if invalid. */
|
|
764
|
+
async getBacklogDefaults(parentId) {
|
|
765
|
+
const cached = this.backlogDefaultsCache.get(parentId);
|
|
766
|
+
if (cached)
|
|
767
|
+
return cached;
|
|
768
|
+
const projectId = this.requireProjectId();
|
|
769
|
+
const p = (async () => {
|
|
770
|
+
const out = {};
|
|
771
|
+
// Source 1: /pm/fields config.
|
|
772
|
+
try {
|
|
773
|
+
const raw = await this.http.getJson(`/pm/fields/${parentId}?fieldType=defaultFld&projectId=${projectId}`);
|
|
774
|
+
const fields = Array.isArray(raw) ? raw : pickArray(raw, 'data', 'content', 'fields');
|
|
775
|
+
const pick = (key) => {
|
|
776
|
+
const field = fields.find((f) => f?.key === key);
|
|
777
|
+
const values = Array.isArray(field?.values) ? field.values : [];
|
|
778
|
+
const chosen = values.find((v) => v?.isDefault === true && v?.archive !== true)
|
|
779
|
+
?? values.find((v) => v?.archive !== true);
|
|
780
|
+
// Entity rows persist the uppercase enum (value.key). value.value is display-only.
|
|
781
|
+
const stored = str(chosen ?? {}, 'key', 'value');
|
|
782
|
+
return stored && stored.length > 0 ? stored : undefined;
|
|
783
|
+
};
|
|
784
|
+
const s = pick('state');
|
|
785
|
+
const pr = pick('priority');
|
|
786
|
+
if (s)
|
|
787
|
+
out.state = s;
|
|
788
|
+
if (pr)
|
|
789
|
+
out.priority = pr;
|
|
790
|
+
}
|
|
791
|
+
catch { /* fall through to Source 2 */ }
|
|
792
|
+
// Source 2: fall back to state/priority from an existing entity of the same kind in this project.
|
|
793
|
+
// Epic has no priority field, so we only need state for parentId=1.
|
|
794
|
+
const needsPriority = parentId !== 1 && !out.priority;
|
|
795
|
+
if (!out.state || needsPriority) {
|
|
796
|
+
try {
|
|
797
|
+
const path = parentId === 1 ? '/pm/epic/search'
|
|
798
|
+
: parentId === 2 ? '/pm/feature/search'
|
|
799
|
+
: '/pm/userstory/search';
|
|
800
|
+
// Story rows carry a scalar `projectId`; Epic/Feature rows carry an array `projectIds`.
|
|
801
|
+
const filters = [
|
|
802
|
+
{ column: 'deleted', value: false },
|
|
803
|
+
parentId === 3
|
|
804
|
+
? { column: 'projectId', value: projectId }
|
|
805
|
+
: { column: 'projectIds', value: projectId },
|
|
806
|
+
];
|
|
807
|
+
const body = this.searchBody(filters, { startIndex: 0, limit: 1 });
|
|
808
|
+
const raw = await this.http.postJson(path, body);
|
|
809
|
+
const rows = pickArray(raw, 'data', 'content');
|
|
810
|
+
const row = rows[0];
|
|
811
|
+
if (row) {
|
|
812
|
+
if (!out.state) {
|
|
813
|
+
const s = str(row, 'state', 'status');
|
|
814
|
+
if (s)
|
|
815
|
+
out.state = s;
|
|
816
|
+
}
|
|
817
|
+
if (needsPriority) {
|
|
818
|
+
const pr = str(row, 'priority');
|
|
819
|
+
if (pr)
|
|
820
|
+
out.priority = pr;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
catch { /* leave out.state / out.priority absent; caller can override */ }
|
|
825
|
+
}
|
|
826
|
+
return out;
|
|
827
|
+
})();
|
|
828
|
+
this.backlogDefaultsCache.set(parentId, p);
|
|
829
|
+
return p;
|
|
830
|
+
}
|
|
741
831
|
// ---------------------------------------------------------------------- Stories (write)
|
|
742
832
|
async createStory(input) {
|
|
833
|
+
const defaults = await this.getBacklogDefaults(3);
|
|
834
|
+
const state = input.state ?? defaults.state;
|
|
835
|
+
const priority = input.priority ?? defaults.priority;
|
|
743
836
|
const body = {
|
|
744
837
|
...input,
|
|
745
838
|
// SQA userstory expects a singular numeric projectId; fill from the connected project when omitted.
|
|
746
839
|
projectId: input.projectId ?? this.requireProjectId(),
|
|
840
|
+
...(state ? { state } : {}),
|
|
841
|
+
...(priority ? { priority } : {}),
|
|
842
|
+
customFields: input.customFields ?? {},
|
|
747
843
|
};
|
|
748
844
|
const raw = await this.http.postJson('/pm/userstory', body);
|
|
749
845
|
return toSavedRef(raw, true);
|
|
@@ -791,9 +887,13 @@ export class SimplifyQaAdapter {
|
|
|
791
887
|
return toEpic(doc && typeof doc === 'object' ? doc : {});
|
|
792
888
|
}
|
|
793
889
|
async createEpic(input) {
|
|
890
|
+
const defaults = await this.getBacklogDefaults(1);
|
|
891
|
+
const state = input.state ?? defaults.state;
|
|
794
892
|
const body = {
|
|
795
893
|
...input,
|
|
796
894
|
projectIds: input.projectIds ?? [this.requireProjectId()],
|
|
895
|
+
...(state ? { state } : {}),
|
|
896
|
+
customFields: input.customFields ?? {},
|
|
797
897
|
};
|
|
798
898
|
const raw = await this.http.postJson('/pm/epic', body);
|
|
799
899
|
return toSavedRef(raw, true);
|
|
@@ -855,9 +955,15 @@ export class SimplifyQaAdapter {
|
|
|
855
955
|
return toFeature(doc && typeof doc === 'object' ? doc : {});
|
|
856
956
|
}
|
|
857
957
|
async createFeature(input) {
|
|
958
|
+
const defaults = await this.getBacklogDefaults(2);
|
|
959
|
+
const state = input.state ?? defaults.state;
|
|
960
|
+
const priority = input.priority ?? defaults.priority;
|
|
858
961
|
const body = {
|
|
859
962
|
...input,
|
|
860
963
|
projectIds: input.projectIds ?? [this.requireProjectId()],
|
|
964
|
+
...(state ? { state } : {}),
|
|
965
|
+
...(priority ? { priority } : {}),
|
|
966
|
+
customFields: input.customFields ?? {},
|
|
861
967
|
};
|
|
862
968
|
const raw = await this.http.postJson('/pm/feature', body);
|
|
863
969
|
return toSavedRef(raw, true);
|
|
@@ -1245,14 +1351,15 @@ export class SimplifyQaAdapter {
|
|
|
1245
1351
|
if (!rawTc || Object.keys(rawTc).length === 0) {
|
|
1246
1352
|
throw new Error(`test case ${tcId} not found`);
|
|
1247
1353
|
}
|
|
1248
|
-
const sourceVersionId =
|
|
1249
|
-
(rawTc
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1354
|
+
const sourceVersionId = opts.versionId ||
|
|
1355
|
+
(str(rawTc, 'versionId', 'selectedVersionId') ??
|
|
1356
|
+
(rawTc?.versionsDetails && typeof rawTc.versionsDetails === 'object'
|
|
1357
|
+
? str(rawTc.versionsDetails, 'id', '_id', 'versionId')
|
|
1358
|
+
: undefined) ??
|
|
1359
|
+
(Array.isArray(rawTc?.versions) && rawTc.versions.length > 0
|
|
1360
|
+
? str(rawTc.versions[0], 'id', '_id')
|
|
1361
|
+
: undefined));
|
|
1362
|
+
const rawSteps = await this.fetchStepsCollection(tcId, rawTc, opts.versionId);
|
|
1256
1363
|
const picked = rawSteps
|
|
1257
1364
|
.filter((s) => seqSet.has(Number(s.seq)))
|
|
1258
1365
|
.sort((a, b) => Number(a.seq) - Number(b.seq));
|
|
@@ -1392,23 +1499,32 @@ export class SimplifyQaAdapter {
|
|
|
1392
1499
|
const rawTc = await this.fetchRawTestcase(tcId);
|
|
1393
1500
|
if (!rawTc || Object.keys(rawTc).length === 0)
|
|
1394
1501
|
throw new Error(`test case ${tcId} not found`);
|
|
1395
|
-
|
|
1502
|
+
// `versionsDetailsRaw` reflects the TC's CURRENTLY-SELECTED version's inline details — only
|
|
1503
|
+
// trustworthy when opts.versionId targets that same version (the common case). When targeting a
|
|
1504
|
+
// DIFFERENT version, prefer that version's own row from rawTc.versions[] for name/executionType/
|
|
1505
|
+
// reviewApproveStatus below; syncONOFF isn't carried per-version there, so it falls back to ''.
|
|
1506
|
+
const versionsDetailsRaw = rawTc?.versionsDetails && typeof rawTc.versionsDetails === 'object' ? rawTc.versionsDetails : {};
|
|
1507
|
+
const currentVersionId = str(rawTc, 'versionId', 'selectedVersionId') ?? str(versionsDetailsRaw, 'id', '_id', 'versionId');
|
|
1508
|
+
const targetsCurrentVersion = !opts.versionId || opts.versionId === currentVersionId;
|
|
1509
|
+
const versionMatch = Array.isArray(rawTc?.versions)
|
|
1510
|
+
? rawTc.versions.find((v) => str(v, 'id', '_id') === opts.versionId)
|
|
1511
|
+
: undefined;
|
|
1512
|
+
const execType = String((targetsCurrentVersion ? str(rawTc, 'executionType') : str(versionMatch, 'executionType')) ?? '').toUpperCase();
|
|
1396
1513
|
if (execType === 'DUO') {
|
|
1397
1514
|
throw new Error('DUO test cases are not yet supported for replaceInSource — split the extraction into AUTOMATION-only or MANUAL-only first.');
|
|
1398
1515
|
}
|
|
1399
|
-
const
|
|
1400
|
-
const syncOnOff = str(versionsDetailsRaw, 'syncONOFF');
|
|
1516
|
+
const syncOnOff = targetsCurrentVersion ? str(versionsDetailsRaw, 'syncONOFF') : undefined;
|
|
1401
1517
|
if (syncOnOff === 'SYNCON') {
|
|
1402
1518
|
throw new Error('SYNCON (mirrored automation/manual) test cases are not yet supported for replaceInSource.');
|
|
1403
1519
|
}
|
|
1404
|
-
const sourceVersionId =
|
|
1405
|
-
|
|
1520
|
+
const sourceVersionId = opts.versionId ||
|
|
1521
|
+
currentVersionId ||
|
|
1406
1522
|
(Array.isArray(rawTc?.versions) && rawTc.versions.length > 0
|
|
1407
1523
|
? str(rawTc.versions[0], 'id', '_id')
|
|
1408
1524
|
: undefined);
|
|
1409
1525
|
if (!sourceVersionId)
|
|
1410
1526
|
throw new Error(`test case ${tcId} has no versionId — cannot save`);
|
|
1411
|
-
const rawSteps = await this.fetchStepsCollection(tcId, rawTc);
|
|
1527
|
+
const rawSteps = await this.fetchStepsCollection(tcId, rawTc, opts.versionId);
|
|
1412
1528
|
// Refuse picks that would break brackets. FUNCTION/FUNCTION_END pairs must not be half-picked;
|
|
1413
1529
|
// control-flow starts (IF/WHILE_START/FOR_START/…) must not span the range partially.
|
|
1414
1530
|
const CONTROL_TYPES = new Set([
|
|
@@ -1503,18 +1619,16 @@ export class SimplifyQaAdapter {
|
|
|
1503
1619
|
tcfnObj['projectId'] = typeof tcfnObj['projectId'] === 'number' ? tcfnObj['projectId'] : projectId;
|
|
1504
1620
|
if (typeof tcfnObj['obsolete'] !== 'boolean')
|
|
1505
1621
|
tcfnObj['obsolete'] = false;
|
|
1506
|
-
// Backend joi validator refuses an empty versionsDetails.name (BADREQUEST from /tm/testcase).
|
|
1507
|
-
//
|
|
1508
|
-
//
|
|
1509
|
-
//
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
if (!versionName)
|
|
1517
|
-
versionName = 'v1.0';
|
|
1622
|
+
// Backend joi validator refuses an empty versionsDetails.name (BADREQUEST from /tm/testcase).
|
|
1623
|
+
// `versionsDetailsRaw` only describes the CURRENT version — when targeting a different one,
|
|
1624
|
+
// its name/reviewApproveStatus belong to the wrong version, so prefer `versionMatch` instead.
|
|
1625
|
+
// Fall back to 'v1.0' (the SQA default set by project.service.js) if nothing resolves.
|
|
1626
|
+
const versionName = (targetsCurrentVersion ? str(versionsDetailsRaw, 'name') : undefined) ??
|
|
1627
|
+
str(versionMatch, 'name') ??
|
|
1628
|
+
'v1.0';
|
|
1629
|
+
const reviewApproveStatus = (targetsCurrentVersion ? str(versionsDetailsRaw, 'reviewApproveStatus') : undefined) ??
|
|
1630
|
+
str(versionMatch, 'reviewApproveStatus') ??
|
|
1631
|
+
'';
|
|
1518
1632
|
const versionsDetails = {
|
|
1519
1633
|
name: versionName,
|
|
1520
1634
|
id: sourceVersionId,
|
|
@@ -1522,7 +1636,7 @@ export class SimplifyQaAdapter {
|
|
|
1522
1636
|
syncONOFF: syncOnOff ?? '',
|
|
1523
1637
|
executionType: execType || 'AUTOMATION',
|
|
1524
1638
|
functionProjectIds: [projectId],
|
|
1525
|
-
reviewApproveStatus
|
|
1639
|
+
reviewApproveStatus,
|
|
1526
1640
|
};
|
|
1527
1641
|
const payload = {
|
|
1528
1642
|
tcfnObj,
|
|
@@ -1556,7 +1670,7 @@ export class SimplifyQaAdapter {
|
|
|
1556
1670
|
// Reuse tech detection from saveStepsAsFunction (same rule: single tech across picks → that
|
|
1557
1671
|
// tech, else fall back to the source TC's tcType).
|
|
1558
1672
|
const rawTc = await this.fetchRawTestcase(tcId);
|
|
1559
|
-
const rawSteps = await this.fetchStepsCollection(tcId, rawTc);
|
|
1673
|
+
const rawSteps = await this.fetchStepsCollection(tcId, rawTc, opts.versionId);
|
|
1560
1674
|
const picked = rawSteps.filter((s) => opts.stepSeqs.includes(Number(s.seq)));
|
|
1561
1675
|
const techs = new Set(picked.map((s) => str(s, 'technologyType')).filter(Boolean));
|
|
1562
1676
|
const technologyType = techs.size === 1 ? [...techs][0] : (str(rawTc, 'tcType') ?? 'WEB');
|
|
@@ -1567,6 +1681,7 @@ export class SimplifyQaAdapter {
|
|
|
1567
1681
|
fnVersionId,
|
|
1568
1682
|
fnModuleId: opts.moduleId,
|
|
1569
1683
|
technologyType,
|
|
1684
|
+
versionId: opts.versionId,
|
|
1570
1685
|
});
|
|
1571
1686
|
return { ...tcRef, fnId: fnRef.id, fnVersionId };
|
|
1572
1687
|
}
|
|
@@ -2946,6 +3061,46 @@ export class SimplifyQaAdapter {
|
|
|
2946
3061
|
};
|
|
2947
3062
|
});
|
|
2948
3063
|
}
|
|
3064
|
+
getLocalIdentifiers() {
|
|
3065
|
+
if (this.localIdsCache)
|
|
3066
|
+
return this.localIdsCache;
|
|
3067
|
+
const hostFull = os.hostname().toLowerCase();
|
|
3068
|
+
const hostBare = hostFull.replace(/\.(local|lan|localdomain)$/, '').split('.')[0] ?? hostFull;
|
|
3069
|
+
const macs = new Set();
|
|
3070
|
+
const ips = new Set();
|
|
3071
|
+
const ifaces = os.networkInterfaces();
|
|
3072
|
+
for (const list of Object.values(ifaces)) {
|
|
3073
|
+
if (!list)
|
|
3074
|
+
continue;
|
|
3075
|
+
for (const iface of list) {
|
|
3076
|
+
if (iface.internal)
|
|
3077
|
+
continue;
|
|
3078
|
+
if (iface.mac && iface.mac !== '00:00:00:00:00:00')
|
|
3079
|
+
macs.add(iface.mac.toLowerCase());
|
|
3080
|
+
if (iface.address)
|
|
3081
|
+
ips.add(iface.address);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
this.localIdsCache = { hostBare, macs, ips };
|
|
3085
|
+
return this.localIdsCache;
|
|
3086
|
+
}
|
|
3087
|
+
/** Does a machineRegistration row belong to the machine this MCP is running on?
|
|
3088
|
+
* A single positive match on hostname, MAC, or IP is enough — the local agent registers with
|
|
3089
|
+
* ALL of these, but the surviving match on the SQA row depends on OS quirks (Windows writes a
|
|
3090
|
+
* padded hostname, macOS writes `foo.local`, IPs shift on Wi-Fi handoff). Any one match is
|
|
3091
|
+
* proof enough; we don't try to reconcile the others. */
|
|
3092
|
+
isThisMachine(row, ids = this.getLocalIdentifiers()) {
|
|
3093
|
+
const name = (str(row, 'machineName', 'name', 'host') ?? '').toLowerCase();
|
|
3094
|
+
if (ids.hostBare.length > 0 && name.length > 0 && name.includes(ids.hostBare))
|
|
3095
|
+
return true;
|
|
3096
|
+
const mac = (str(row, 'macAddress') ?? '').toLowerCase();
|
|
3097
|
+
if (mac.length > 0 && ids.macs.has(mac))
|
|
3098
|
+
return true;
|
|
3099
|
+
const ip = str(row, 'ipAddress', 'hostAddress') ?? '';
|
|
3100
|
+
if (ip.length > 0 && ids.ips.has(ip))
|
|
3101
|
+
return true;
|
|
3102
|
+
return false;
|
|
3103
|
+
}
|
|
2949
3104
|
async listLocalAgents() {
|
|
2950
3105
|
// ponytail: no projectId filter — machine_registration.projectId is never written by
|
|
2951
3106
|
// test-management/services/machine_registration.service.js (create only stamps
|
|
@@ -2954,50 +3109,43 @@ export class SimplifyQaAdapter {
|
|
|
2954
3109
|
const body = this.searchBody([{ column: 'deleted', value: false }], { limit: 1000 });
|
|
2955
3110
|
const raw = await this.http.postJson('/tm/machineRegistration/search', body);
|
|
2956
3111
|
const rows = pickArray(raw, 'data', 'content', 'agents', 'machines');
|
|
2957
|
-
// ponytail:
|
|
2958
|
-
//
|
|
2959
|
-
//
|
|
2960
|
-
//
|
|
2961
|
-
// is
|
|
2962
|
-
//
|
|
2963
|
-
|
|
2964
|
-
// Redis-presence endpoint (ZSCORE SQAAGENTTRACKER), consult that here instead.
|
|
2965
|
-
const hostFull = os.hostname().toLowerCase();
|
|
2966
|
-
// Bare hostname: strip trailing .local/.lan/.localdomain AND take the first label. Handles
|
|
2967
|
-
// both directions of "short vs FQDN" mismatch — the agent may register as `Pratiks-MBP`
|
|
2968
|
-
// while os.hostname() returns `pratiks-mbp.local`, or vice-versa.
|
|
2969
|
-
const hostBare = hostFull.replace(/\.(local|lan|localdomain)$/, '').split('.')[0] ?? hostFull;
|
|
3112
|
+
// ponytail: mirror sqa-ui/machine-registration.component.ts:169. The DB `active` flag is only
|
|
3113
|
+
// written to `true` on FIRST registration (fire-and-forget in executions/services/
|
|
3114
|
+
// executionQueue.service.js:49) and back to `false` by a 7s-stale cron. Every subsequent poll
|
|
3115
|
+
// is a no-op for the DB — a still-polling agent frequently shows `active:false`. Executions
|
|
3116
|
+
// service is off-limits for the fix, so when the row belongs to THIS machine (any of hostname
|
|
3117
|
+
// / MAC / IP matches), trust that over the stale DB flag.
|
|
3118
|
+
const ids = this.getLocalIdentifiers();
|
|
2970
3119
|
return rows.map((n) => {
|
|
2971
3120
|
const id = str(n, 'id', '_id', 'agentId') ?? '';
|
|
2972
3121
|
const agentName = str(n, 'machineName', 'name', 'host');
|
|
2973
3122
|
const owner = str(n, 'owner', 'userName', 'createdBy');
|
|
2974
3123
|
const hostAddr = str(n, 'macAddress', 'hostAddress');
|
|
2975
3124
|
const dbActive = typeof n?.active === 'boolean' ? n.active : false;
|
|
2976
|
-
const
|
|
2977
|
-
const isMyBox = nameLc.length > 0 && hostBare.length > 0 && nameLc.includes(hostBare);
|
|
3125
|
+
const isMyBox = this.isThisMachine(n, ids);
|
|
2978
3126
|
return {
|
|
2979
3127
|
id,
|
|
2980
3128
|
...(agentName ? { name: agentName } : {}),
|
|
2981
3129
|
online: dbActive || isMyBox,
|
|
3130
|
+
...(isMyBox ? { isThisMachine: true } : {}),
|
|
2982
3131
|
...(owner ? { owner } : {}),
|
|
2983
3132
|
...(hostAddr ? { host: hostAddr } : {}),
|
|
2984
3133
|
};
|
|
2985
3134
|
});
|
|
2986
3135
|
}
|
|
2987
3136
|
/**
|
|
2988
|
-
* Pick THE ACTING USER'S OWN
|
|
2989
|
-
*
|
|
2990
|
-
*
|
|
3137
|
+
* Pick THE ACTING USER'S OWN agent — never row [0] of a shared-tenant roster (which would
|
|
3138
|
+
* silently dispatch a local run to a teammate's box, and the run would sit queued until TTL
|
|
3139
|
+
* because it lands in a Redis key that no local agent on the caller's machine polls).
|
|
2991
3140
|
*
|
|
2992
3141
|
* SQA's `machineRegistration` row carries no owner_user_id column (unlike walnut, which matches
|
|
2993
|
-
* the JWT `id` claim to `owner_user_id`), so we identify "your" agent by
|
|
2994
|
-
*
|
|
2995
|
-
*
|
|
2996
|
-
* `
|
|
2997
|
-
* Zero/many matches refuse honestly; a silent guess would just repeat the exact bug we're fixing.
|
|
3142
|
+
* the JWT `id` claim to `owner_user_id`), so we identify "your" agent by physical identity:
|
|
3143
|
+
* hostname, MAC address, or IP — any one match against `os.networkInterfaces()` on the box this
|
|
3144
|
+
* MCP is running on. When exactly one row is "this machine", pick it regardless of the DB
|
|
3145
|
+
* `active` flag (that flag is stale by design — see listLocalAgents for the executionQueue bug).
|
|
2998
3146
|
*
|
|
2999
|
-
* ponytail:
|
|
3000
|
-
* to machineRegistration (mirror walnut's `resolveLocalAgentCredential` at that point).
|
|
3147
|
+
* ponytail: multi-identifier heuristic. Upgrade to owner-user-id match if the backend adds that
|
|
3148
|
+
* column to machineRegistration (mirror walnut's `resolveLocalAgentCredential` at that point).
|
|
3001
3149
|
*/
|
|
3002
3150
|
async resolveLocalAgentId(preferred) {
|
|
3003
3151
|
if (preferred && preferred.length > 0)
|
|
@@ -3007,24 +3155,25 @@ export class SimplifyQaAdapter {
|
|
|
3007
3155
|
throw new Error('startRun (LOCAL): no local agent registered. Register a machine in the SimplifyQA UI ' +
|
|
3008
3156
|
'first, or pass mode: "cloud" if a cloud runner is provisioned.');
|
|
3009
3157
|
}
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
}
|
|
3015
|
-
const hostFull = os.hostname().toLowerCase();
|
|
3016
|
-
// Same short-vs-FQDN handling as listLocalAgents — see the ponytail note there.
|
|
3017
|
-
const host = hostFull.replace(/\.(local|lan|localdomain)$/, '').split('.')[0] ?? hostFull;
|
|
3018
|
-
const mine = active.filter((a) => host.length > 0 && (a.name ?? '').toLowerCase().includes(host));
|
|
3158
|
+
// Priority 1: rows that physically match this machine. Ignore the DB `active` flag — a running
|
|
3159
|
+
// agent frequently shows `active:false` between the 5s stale-cron and the next heartbeat, and
|
|
3160
|
+
// the executions service is off-limits for the real fix.
|
|
3161
|
+
const mine = agents.filter((a) => a.isThisMachine);
|
|
3019
3162
|
if (mine.length === 1)
|
|
3020
3163
|
return mine[0].id;
|
|
3021
|
-
const listing = (arr) => arr.map((a) => ` - ${a.name ?? a.id} \`${a.id}\``).join('\n');
|
|
3022
3164
|
if (mine.length > 1) {
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3165
|
+
const listing = mine.map((a) => ` - ${a.name ?? a.id} \`${a.id}\``).join('\n');
|
|
3166
|
+
throw new Error(`startRun (LOCAL): multiple agents registered from this machine. Pass \`agentId\` to ` +
|
|
3167
|
+
`pick one:\n${listing}`);
|
|
3168
|
+
}
|
|
3169
|
+
// No physical match — fall back to the old online-only pool (roster shared across teammates).
|
|
3170
|
+
// No physical match for this machine. Refuse to guess — dispatching to a teammate's agent is
|
|
3171
|
+
// the exact silent-misroute bug isThisMachine was written to prevent. Surface the roster so
|
|
3172
|
+
// the caller can pass `agentId` explicitly.
|
|
3173
|
+
const listing = agents.map((a) => ` - ${a.name ?? a.id} \`${a.id}\`${a.online ? '' : ' (offline)'}`).join('\n');
|
|
3174
|
+
throw new Error(`startRun (LOCAL): no agent registered from this machine (hostname / MAC / IP did not match ` +
|
|
3175
|
+
`any row). Launch the SimplifyQA desktop agent on this box, or pass \`agentId\` explicitly. ` +
|
|
3176
|
+
`Registered agents:\n${listing}`);
|
|
3028
3177
|
}
|
|
3029
3178
|
detectExecutionOS() {
|
|
3030
3179
|
if (process.platform === 'win32')
|