common-qa-mcp 0.5.0 โ 0.7.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/README.md +37 -79
- package/dist/adapters/remoteMcp/adapter.d.ts +7 -1
- package/dist/adapters/remoteMcp/adapter.d.ts.map +1 -1
- package/dist/adapters/remoteMcp/adapter.js +8 -3
- package/dist/adapters/remoteMcp/adapter.js.map +1 -1
- package/dist/adapters/simplifyqa/adapter.d.ts +112 -26
- package/dist/adapters/simplifyqa/adapter.d.ts.map +1 -1
- package/dist/adapters/simplifyqa/adapter.js +731 -240
- package/dist/adapters/simplifyqa/adapter.js.map +1 -1
- package/dist/adapters/simplifyqa/mapper.d.ts +5 -1
- package/dist/adapters/simplifyqa/mapper.d.ts.map +1 -1
- package/dist/adapters/simplifyqa/mapper.js +45 -1
- package/dist/adapters/simplifyqa/mapper.js.map +1 -1
- package/dist/adapters/types.d.ts +55 -13
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/walnut/actionTypes.d.ts +1 -1
- package/dist/adapters/walnut/actionTypes.d.ts.map +1 -1
- package/dist/adapters/walnut/actionTypes.js +37 -0
- package/dist/adapters/walnut/actionTypes.js.map +1 -1
- package/dist/adapters/walnut/mapper.d.ts.map +1 -1
- package/dist/adapters/walnut/mapper.js +12 -1
- package/dist/adapters/walnut/mapper.js.map +1 -1
- package/dist/domain/types.d.ts +105 -15
- package/dist/domain/types.d.ts.map +1 -1
- package/dist/initAgents.d.ts.map +1 -1
- package/dist/initAgents.js +132 -26
- package/dist/initAgents.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +95 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/backlog.tools.d.ts.map +1 -1
- package/dist/tools/backlog.tools.js +24 -2
- package/dist/tools/backlog.tools.js.map +1 -1
- package/dist/tools/connection.tools.d.ts.map +1 -1
- package/dist/tools/connection.tools.js +15 -3
- package/dist/tools/connection.tools.js.map +1 -1
- package/dist/tools/defects.tools.d.ts.map +1 -1
- package/dist/tools/defects.tools.js +29 -3
- package/dist/tools/defects.tools.js.map +1 -1
- package/dist/tools/discovery.tools.d.ts.map +1 -1
- package/dist/tools/discovery.tools.js +304 -137
- package/dist/tools/discovery.tools.js.map +1 -1
- package/dist/tools/gapAnalysis.tools.d.ts +31 -0
- package/dist/tools/gapAnalysis.tools.d.ts.map +1 -0
- package/dist/tools/gapAnalysis.tools.js +287 -0
- package/dist/tools/gapAnalysis.tools.js.map +1 -0
- package/dist/tools/impactAnalysis.tools.d.ts +17 -0
- package/dist/tools/impactAnalysis.tools.d.ts.map +1 -0
- package/dist/tools/impactAnalysis.tools.js +352 -0
- package/dist/tools/impactAnalysis.tools.js.map +1 -0
- package/dist/tools/markdown.d.ts.map +1 -1
- package/dist/tools/markdown.js +9 -5
- package/dist/tools/markdown.js.map +1 -1
- package/dist/tools/objects.tools.d.ts.map +1 -1
- package/dist/tools/objects.tools.js +174 -18
- package/dist/tools/objects.tools.js.map +1 -1
- package/dist/tools/recording.tools.d.ts.map +1 -1
- package/dist/tools/recording.tools.js +108 -10
- package/dist/tools/recording.tools.js.map +1 -1
- package/dist/tools/run.tools.d.ts.map +1 -1
- package/dist/tools/run.tools.js +257 -63
- package/dist/tools/run.tools.js.map +1 -1
- package/dist/tools/tasks.tools.d.ts.map +1 -1
- package/dist/tools/tasks.tools.js +29 -3
- package/dist/tools/tasks.tools.js.map +1 -1
- package/dist/tools/testcase.tools.d.ts.map +1 -1
- package/dist/tools/testcase.tools.js +14 -5
- package/dist/tools/testcase.tools.js.map +1 -1
- package/package.json +1 -1
package/dist/tools/run.tools.js
CHANGED
|
@@ -14,6 +14,14 @@ function localAgentLine(a) {
|
|
|
14
14
|
const host = a.host && a.host !== a.name ? ` ยท ${a.host}` : '';
|
|
15
15
|
return `- ${a.online ? '๐ข' : 'โช'} ${a.name ?? a.id}${host} \`${a.id}\`${a.status ? ` (${a.status})` : ''}`;
|
|
16
16
|
}
|
|
17
|
+
function mobileDeviceLine(d) {
|
|
18
|
+
// `active` here is the last-known connection flag from the SQA DB (stale by design). Show it as
|
|
19
|
+
// a hint, not a gate โ a device the user just plugged in may still read `active:false` here.
|
|
20
|
+
const dot = d.active ? '๐ข' : 'โช';
|
|
21
|
+
const os = d.osVersion ? ` ยท ${d.platform.toLowerCase()} ${d.osVersion}` : ` ยท ${d.platform.toLowerCase()}`;
|
|
22
|
+
const status = d.status ? ` (${d.status})` : '';
|
|
23
|
+
return `- ${dot} ${d.name}${os} \`${d.id}\`${status}`;
|
|
24
|
+
}
|
|
17
25
|
/** One line per schedule for management listings โ what it runs, cadence, where + pinned agent,
|
|
18
26
|
* browser, next run, status; the credential-free id in backticks is what the management tools take. */
|
|
19
27
|
function scheduleLine(s) {
|
|
@@ -88,8 +96,12 @@ function statusText(s) {
|
|
|
88
96
|
return parts.length ? ` ยท ${parts.join(', ')}` : '';
|
|
89
97
|
})()
|
|
90
98
|
: '';
|
|
91
|
-
const
|
|
92
|
-
|
|
99
|
+
const CHILD_CAP = 100;
|
|
100
|
+
const total = s.children?.length ?? 0;
|
|
101
|
+
const shown = s.children?.slice(0, CHILD_CAP) ?? [];
|
|
102
|
+
const kids = shown.length
|
|
103
|
+
? '\n\n' + shown.map((c) => `- ${icon(c.state)} ${c.id}${c.title ? ` โ ${c.title}` : ''} (${c.state})`).join('\n') +
|
|
104
|
+
(total > CHILD_CAP ? `\nโฆ and ${total - CHILD_CAP} more (${total} total)` : '')
|
|
93
105
|
: '';
|
|
94
106
|
return `Run ${s.runId} ยท **${s.state}**${prog}${tally}${link}${kids}`;
|
|
95
107
|
}
|
|
@@ -120,13 +132,12 @@ export function registerRunTools(server, deps) {
|
|
|
120
132
|
}
|
|
121
133
|
});
|
|
122
134
|
server.registerTool('local_agents', {
|
|
123
|
-
title:
|
|
124
|
-
description:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
'registration + a stale cron, so it lags behind). Other agents\' status still comes from the DB.',
|
|
135
|
+
title: "List THIS machine's local agent(s)",
|
|
136
|
+
description: "List the LOCAL agent(s) registered from THIS machine โ the desktop agent running on the box " +
|
|
137
|
+
"this MCP is on. Use for a LOCAL tc_run / suite_run. There is exactly one desktop agent per " +
|
|
138
|
+
"machine, so this is 0 or 1 row: empty means no agent is registered from this box (start the " +
|
|
139
|
+
"SimplifyQA desktop agent, or use mode:'remote' to dispatch to a teammate's). Use `remote_agents` " +
|
|
140
|
+
"for the other registered machines. Requires a live connection.",
|
|
130
141
|
inputSchema: {},
|
|
131
142
|
annotations: { readOnlyHint: true },
|
|
132
143
|
}, async () => {
|
|
@@ -136,11 +147,43 @@ export function registerRunTools(server, deps) {
|
|
|
136
147
|
if (!adapter.listLocalAgents)
|
|
137
148
|
return err('This product has no local agents to list.');
|
|
138
149
|
try {
|
|
139
|
-
const
|
|
150
|
+
const all = await adapter.listLocalAgents();
|
|
151
|
+
const agents = all.filter((a) => a.isThisMachine);
|
|
152
|
+
if (!agents.length) {
|
|
153
|
+
return ok("No local agent is registered from this machine. Start the SimplifyQA desktop agent on " +
|
|
154
|
+
"this box, or use remote_agents + mode:'remote' to run on a teammate's machine.", { agents: [] });
|
|
155
|
+
}
|
|
140
156
|
const online = agents.filter((a) => a.online);
|
|
157
|
+
const text = `${agents.length} local agent${agents.length === 1 ? '' : 's'} on this machine ยท ${online.length} online:\n\n` +
|
|
158
|
+
agents.map(localAgentLine).join('\n');
|
|
159
|
+
return ok(text, { agents, online: online.length });
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
return err(describeError(e));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
server.registerTool('remote_agents', {
|
|
166
|
+
title: 'List REMOTE agents (other registered machines)',
|
|
167
|
+
description: "List the LOCAL agents registered on OTHER machines (teammates' boxes) โ the pool a REMOTE " +
|
|
168
|
+
"run dispatches to. Use to pick an `agentId` for a REMOTE tc_run / suite_run. Excludes this " +
|
|
169
|
+
"machine's own agent (that's `local_agents`). REMOTE dispatches to a specific teammate's " +
|
|
170
|
+
"registered desktop agent through the SimplifyQA queue; the picked agent's browsers / adb " +
|
|
171
|
+
"devices are what actually execute. Requires a live connection.",
|
|
172
|
+
inputSchema: {},
|
|
173
|
+
annotations: { readOnlyHint: true },
|
|
174
|
+
}, async () => {
|
|
175
|
+
const adapter = deps.getAdapter();
|
|
176
|
+
if (!adapter)
|
|
177
|
+
return err(NOT_CONNECTED);
|
|
178
|
+
if (!adapter.listLocalAgents)
|
|
179
|
+
return err('This product has no local agents to list.');
|
|
180
|
+
try {
|
|
181
|
+
const all = await adapter.listLocalAgents();
|
|
182
|
+
const agents = all.filter((a) => !a.isThisMachine);
|
|
141
183
|
if (!agents.length)
|
|
142
|
-
return ok('No
|
|
143
|
-
const
|
|
184
|
+
return ok('No other machines are registered as agents for this customer.', { agents: [] });
|
|
185
|
+
const online = agents.filter((a) => a.online);
|
|
186
|
+
const text = `${agents.length} remote agent${agents.length === 1 ? '' : 's'} ยท ${online.length} online:\n\n` +
|
|
144
187
|
agents.map(localAgentLine).join('\n');
|
|
145
188
|
return ok(text, { agents, online: online.length });
|
|
146
189
|
}
|
|
@@ -148,24 +191,81 @@ export function registerRunTools(server, deps) {
|
|
|
148
191
|
return err(describeError(e));
|
|
149
192
|
}
|
|
150
193
|
});
|
|
194
|
+
server.registerTool('mobile_list_devices', {
|
|
195
|
+
title: 'List mobile devices (Android or iOS)',
|
|
196
|
+
description: "List MOBILE devices available to the project โ union of the LOCAL roster (real devices + " +
|
|
197
|
+
"emulators/simulators registered via the desktop agent, from `/tm/mobileDevices/search`) " +
|
|
198
|
+
"and the CLOUD roster (whichever farm the project has integrated: LambdaTest / " +
|
|
199
|
+
"BrowserStack / SauceLabs / SimplifyQA Device Farm โ the first non-empty one wins, " +
|
|
200
|
+
"matching the UI's single-provider-per-project model). Use before `tc_run` for an ANDROID " +
|
|
201
|
+
"or IOS test case to pick a `deviceId` (and read `deviceType` + `cloudDeviceType` off the " +
|
|
202
|
+
"same row). The `active` flag on local rows is last-known: stale by design (the SQA " +
|
|
203
|
+
"backend writes it when the desktop agent connects/disconnects, and a 7s-stale cron flips " +
|
|
204
|
+
"it back); a device the user just plugged in may still read `active:false` here. Cloud " +
|
|
205
|
+
"rows don't carry `active` โ cloud farms are always assumed reachable. Requires a live " +
|
|
206
|
+
"connection.",
|
|
207
|
+
inputSchema: {
|
|
208
|
+
platform: z.enum(['android', 'ios']).describe("Which mobile platform to list devices for."),
|
|
209
|
+
},
|
|
210
|
+
annotations: { readOnlyHint: true },
|
|
211
|
+
}, async ({ platform }) => {
|
|
212
|
+
const adapter = deps.getAdapter();
|
|
213
|
+
if (!adapter)
|
|
214
|
+
return err(NOT_CONNECTED);
|
|
215
|
+
if (!adapter.listMobileDevices)
|
|
216
|
+
return err('This product has no mobile device roster to list.');
|
|
217
|
+
try {
|
|
218
|
+
const devices = await adapter.listMobileDevices(platform.toUpperCase());
|
|
219
|
+
if (!devices.length) {
|
|
220
|
+
return ok(`No ${platform.toUpperCase()} devices are registered to this project. Connect a device ` +
|
|
221
|
+
`via the SimplifyQA desktop agent, then try again.`, { devices: [] });
|
|
222
|
+
}
|
|
223
|
+
// Group by deviceType so the client can present the same tabs the UI does (Real / Emulator
|
|
224
|
+
// / Simulator / Cloud Device). Cloud rows come from the configured farm (LT / BS / SL / SQA
|
|
225
|
+
// Device Farm); the row's `cloudDeviceType` names which one โ pass it back on `tc_run`.
|
|
226
|
+
const groups = {};
|
|
227
|
+
for (const d of devices) {
|
|
228
|
+
if (!groups[d.deviceType])
|
|
229
|
+
groups[d.deviceType] = [];
|
|
230
|
+
groups[d.deviceType].push(d);
|
|
231
|
+
}
|
|
232
|
+
const active = devices.filter((d) => d.active).length;
|
|
233
|
+
const sections = Object.entries(groups)
|
|
234
|
+
.map(([type, rows]) => `**${type}** (${rows.length})\n${rows.map(mobileDeviceLine).join('\n')}`)
|
|
235
|
+
.join('\n\n');
|
|
236
|
+
const text = `${devices.length} ${platform.toUpperCase()} device${devices.length === 1 ? '' : 's'} ยท ${active} active:\n\n` +
|
|
237
|
+
sections;
|
|
238
|
+
return ok(text, { devices, active });
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
return err(describeError(e));
|
|
242
|
+
}
|
|
243
|
+
});
|
|
151
244
|
server.registerTool('tc_run', {
|
|
152
|
-
title: 'Run a test case (local
|
|
245
|
+
title: 'Run a test case (local / remote / cloud; now or scheduled)',
|
|
153
246
|
description: "Execute ONE existing test case in the connected product. ASK the user for EVERY field the " +
|
|
154
247
|
"Execute dialog asks โ never assume โ and in ORDER: (1) `releaseId` (from list_releases; " +
|
|
155
248
|
"pass '' if the user skips), (2) `sprintId` (from list_sprints; pass '' if the user skips), " +
|
|
156
249
|
"(3) `versionId` (from tc_get's `versions[]`; pass '' to accept the TC's current version), " +
|
|
157
|
-
'(4) `environment` (from tc_list_environments โ required), (5) `browser`
|
|
158
|
-
'(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
'
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
250
|
+
'(4) `environment` (from tc_list_environments โ required), (5) FOR WEB TCs: `browser` ' +
|
|
251
|
+
'(required); FOR ANDROID/IOS TCs: skip browser โ `deviceType` + `deviceId` are asked in step ' +
|
|
252
|
+
"(9) instead (call `mobile_list_devices` to enumerate), (6) `mode` local/remote/cloud, (7) " +
|
|
253
|
+
"`when` immediate/schedule, (8) for a schedule the start time + recurrence + timezone, (9) " +
|
|
254
|
+
'THEN offer the eligible agent/runner and (for mobile) the device โ pass the picks. The tool ' +
|
|
255
|
+
'REFUSES when any of (1)-(7) is omitted, and additionally when `deviceType`/`deviceId` are ' +
|
|
256
|
+
'omitted on a mobile TC โ that is deliberate; it forces the ask instead of silently ' +
|
|
257
|
+
'defaulting. `mode` decides WHERE it runs:\n' +
|
|
258
|
+
" โข 'local' โ THIS machine's desktop agent (its own browser). Call `local_agents` (0 or 1 row per box). " +
|
|
259
|
+
"For when:'immediate' the tool first probes the desktop agent (:4040) and refuses cleanly if it's not up. " +
|
|
260
|
+
"Omit `agentId` to auto-pick this machine's agent (or pass it explicitly โ must be this machine's row). " +
|
|
261
|
+
"For when:'schedule' `agentId` is REQUIRED.\n" +
|
|
262
|
+
" โข 'remote' โ ANOTHER registered machine's desktop agent (a teammate's box). Call `remote_agents` and " +
|
|
263
|
+
"OFFER the list; `agentId` is REQUIRED (no auto-pick across boxes). For when:'immediate' only ONLINE " +
|
|
264
|
+
"remote agents are eligible; for when:'schedule' ANY remote agent works (it runs when it fires).\n" +
|
|
165
265
|
" โข 'cloud' โ an online cloud runner VM (ONLINE-only whether now or scheduled). Pass `cloudRunnerId` " +
|
|
166
266
|
'from cloud_runners; for a schedule omit it to let the backend auto-pick a live VM at fire time.\n' +
|
|
167
|
-
'IF THE USER HAS NOT SAID local or cloud, ASK โ this tool refuses when `mode` is omitted. For cloud,
|
|
168
|
-
'no runner is online it reports that there are no active cloud runners (nothing is run).\n' +
|
|
267
|
+
'IF THE USER HAS NOT SAID local, remote, or cloud, ASK โ this tool refuses when `mode` is omitted. For cloud, ' +
|
|
268
|
+
'if no runner is online it reports that there are no active cloud runners (nothing is run).\n' +
|
|
169
269
|
'`when` decides WHEN it runs (this tool REFUSES when `when` is omitted โ ASK the user):\n' +
|
|
170
270
|
" โข 'immediate' โ dispatch now; returns the run id โ poll run_status for the result.\n" +
|
|
171
271
|
" โข 'schedule' โ create a schedule instead (requires `scheduledFor`); ASK the start time, recurrence, " +
|
|
@@ -173,8 +273,8 @@ export function registerRunTools(server, deps) {
|
|
|
173
273
|
'NOTE: MANUAL test cases cannot be run or scheduled by an agent โ the backend rejects them.',
|
|
174
274
|
inputSchema: {
|
|
175
275
|
tcId: z.string().describe('The test case id (or business key) to run.'),
|
|
176
|
-
mode: z.enum(['local', 'cloud']).optional().describe("Where to run. REQUIRED โ ASK the user if unstated (the tool refuses when omitted)."),
|
|
177
|
-
agentId: z.string().optional().describe("For local: a specific agent credential_id
|
|
276
|
+
mode: z.enum(['local', 'remote', 'cloud']).optional().describe("Where to run. REQUIRED โ ASK the user if unstated (the tool refuses when omitted)."),
|
|
277
|
+
agentId: z.string().optional().describe("For local: a specific agent credential_id from `local_agents` (must be THIS machine's row; omit to auto-pick). For remote: REQUIRED โ a credential_id from `remote_agents` (a teammate's row). when:'immediate' โ the picked agent must be ONLINE; when:'schedule' โ offline agents are OK (they run when they fire) and `agentId` is REQUIRED for both local and remote."),
|
|
178
278
|
releaseId: z.string().optional().describe('Tag the run with a RELEASE (from list_releases). Optional in the UI; omit to leave untagged.'),
|
|
179
279
|
sprintId: z.string().optional().describe('Tag the run with a SPRINT (from list_sprints). Optional in the UI; omit to leave untagged.'),
|
|
180
280
|
versionId: z.string().optional().describe("Override the TC's currently-selected VERSION for this run (an id from the TC's `versions[]`). Omit to run the current version โ that's what the UI's Version chip defaults to."),
|
|
@@ -182,7 +282,10 @@ export function registerRunTools(server, deps) {
|
|
|
182
282
|
browser: z.string().optional().describe("Browser, default 'chromium' (bundled + isolated). Others: 'chrome'/'msedge' (installed channel), 'firefox', 'webkit'. NOTE: a schedule accepts only chromium/msedge/firefox/webkit."),
|
|
183
283
|
headless: z.boolean().optional().describe('Headless. Defaults: cloud=true, local=false. (Immediate runs only.)'),
|
|
184
284
|
cloudRunnerId: z.string().optional().describe("For cloud: a specific ONLINE runner id (from cloud_runners). For when:'schedule' omit to auto-pick a live VM at fire time."),
|
|
185
|
-
deviceId: z.string().optional().describe("REQUIRED for ANDROID/IOS test cases โ the target device id. LOCAL: adb serial or emulator name. CLOUD: the marketplace device name. Enumerate with
|
|
285
|
+
deviceId: z.string().optional().describe("REQUIRED for ANDROID/IOS test cases โ the target device id. LOCAL: adb serial or emulator name. CLOUD: the marketplace device name. Enumerate with `mobile_list_devices`."),
|
|
286
|
+
deviceType: z.enum(['real_device', 'emulator', 'simulator', 'cloud_device', 'cloud_emulator', 'cloud_simulator']).optional().describe("REQUIRED for ANDROID/IOS test cases โ the KIND of device the picked `deviceId` is (read off the matching row from `mobile_list_devices`). Drives the wire execution platform's `type` field."),
|
|
287
|
+
cloudDeviceType: z.enum(['lambda_test', 'browser_stack', 'sauce_labs', 'simplify_qa_device_form']).optional().describe("REQUIRED when `deviceType` is cloud_* โ which cloud farm the device came from (read off the matching row's `cloudDeviceType` from `mobile_list_devices`). Note SIMPLIFY_QA_DEVICE_FORM's legacy 'FORM' spelling โ mirrors the SQA wire schema exactly."),
|
|
288
|
+
osVersion: z.string().optional().describe("Optional OS version to stamp on the wire platform object for ANDROID/IOS โ read off the matching row from `mobile_list_devices`. Ignored for non-mobile tcTypes."),
|
|
186
289
|
when: z.enum(['immediate', 'schedule']).optional().describe("When to run โ REQUIRED, ASK the user if unstated (the tool refuses when omitted). 'immediate' dispatches now; 'schedule' creates a schedule (needs scheduledFor)."),
|
|
187
290
|
scheduledFor: z.string().optional().describe("REQUIRED for when:'schedule' โ ISO datetime of the first/only run (e.g. '2026-08-20T09:00:00Z')."),
|
|
188
291
|
scheduleType: z.enum(['once', 'hourly', 'daily', 'weekdays', 'weekly', 'monthly']).optional().describe("Recurrence for a schedule. Default 'once'. hourly/daily/weekdays require an endDate."),
|
|
@@ -190,7 +293,7 @@ export function registerRunTools(server, deps) {
|
|
|
190
293
|
timezone: z.string().optional().describe("IANA timezone for a schedule (e.g. 'Asia/Kolkata'). Default 'UTC'."),
|
|
191
294
|
},
|
|
192
295
|
annotations: { readOnlyHint: false },
|
|
193
|
-
}, async ({ tcId, mode, agentId, environment, browser, headless, cloudRunnerId, deviceId, when, scheduledFor, scheduleType, endDate, timezone, releaseId, sprintId, versionId }) => {
|
|
296
|
+
}, async ({ tcId, mode, agentId, environment, browser, headless, cloudRunnerId, deviceId, deviceType, cloudDeviceType, osVersion, when, scheduledFor, scheduleType, endDate, timezone, releaseId, sprintId, versionId }) => {
|
|
194
297
|
const adapter = deps.getAdapter();
|
|
195
298
|
if (!adapter)
|
|
196
299
|
return err(NOT_CONNECTED);
|
|
@@ -205,25 +308,49 @@ export function registerRunTools(server, deps) {
|
|
|
205
308
|
return err("ASK the user which SPRINT this run belongs to (from list_sprints), then call tc_run " +
|
|
206
309
|
"again with sprintId. If the user wants no sprint, pass sprintId: '' (empty string).");
|
|
207
310
|
}
|
|
311
|
+
// Pre-flight the TC once โ needed both to inline the version list (below) and to know the
|
|
312
|
+
// tcType (WEB vs ANDROID/IOS/โฆ) so the refuse cascade asks the right platform-specific
|
|
313
|
+
// fields: browser for WEB, deviceType + deviceId for mobile. Mirrors tc_get's refusal at
|
|
314
|
+
// discovery.tools.ts:226; without this the model guesses a version NAME and the backend 500s.
|
|
315
|
+
let preview;
|
|
316
|
+
try {
|
|
317
|
+
preview = await adapter.getTestCase(tcId);
|
|
318
|
+
}
|
|
319
|
+
catch (e) {
|
|
320
|
+
return err(describeError(e));
|
|
321
|
+
}
|
|
322
|
+
const tcType = (preview.technologyType ?? 'WEB').toUpperCase();
|
|
323
|
+
const isMobile = tcType === 'ANDROID' || tcType === 'IOS';
|
|
208
324
|
if (versionId === undefined) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
325
|
+
const vs = preview.versions ?? [];
|
|
326
|
+
if (vs.length > 1) {
|
|
327
|
+
const listing = vs
|
|
328
|
+
.map((v) => ` โข \`${v.id}\` โ **${v.name}**${v.isCurrent ? ' (current)' : ''}`)
|
|
329
|
+
.join('\n');
|
|
330
|
+
return err(`Test case ${preview.id ?? tcId} has ${vs.length} versions โ ASK the user which ` +
|
|
331
|
+
`one to run, then call tc_run again with versionId (pass the version id OR its ` +
|
|
332
|
+
`name โ either resolves). Pass versionId: '' to accept the TC's current ` +
|
|
333
|
+
`version.\n\n${listing}`);
|
|
334
|
+
}
|
|
335
|
+
versionId = '';
|
|
212
336
|
}
|
|
213
337
|
if (!environment) {
|
|
214
338
|
return err('ASK the user which ENVIRONMENT to run against (from tc_list_environments), then call ' +
|
|
215
339
|
'tc_run again with environment set to the chosen env key. Required โ the UI marks this ' +
|
|
216
340
|
'field with a red asterisk.');
|
|
217
341
|
}
|
|
218
|
-
|
|
342
|
+
// Browser is a WEB-only field โ the SQA Execute dialog doesn't render it for mobile TCs
|
|
343
|
+
// (they show a device picker instead). Skip the refuse for ANDROID/IOS; the adapter's
|
|
344
|
+
// buildRunPlatform will ignore any browser passed alongside a mobile deviceId.
|
|
345
|
+
if (!isMobile && !browser) {
|
|
219
346
|
return err("ASK the user which BROWSER to run in (chromium / chrome / msedge / firefox / webkit), " +
|
|
220
347
|
'then call tc_run again with browser set. Required โ the UI marks this field with a ' +
|
|
221
348
|
'red asterisk.');
|
|
222
349
|
}
|
|
223
350
|
if (!mode) {
|
|
224
|
-
return err("Specify where to run: call tc_run again with mode: 'local' or 'cloud'. " +
|
|
225
|
-
|
|
226
|
-
'cloud runs on an online cloud runner VM.
|
|
351
|
+
return err("Specify where to run: call tc_run again with mode: 'local', 'remote', or 'cloud'. " +
|
|
352
|
+
"If the user has not said which, ASK them first โ local runs on THIS machine's agent, " +
|
|
353
|
+
"remote runs on a teammate's registered agent, cloud runs on an online cloud runner VM.");
|
|
227
354
|
}
|
|
228
355
|
// Never assume now-vs-scheduled โ it decides which agents are eligible (online-only for now,
|
|
229
356
|
// online+offline for a local schedule) and, for a schedule, the start time asked next.
|
|
@@ -258,13 +385,48 @@ export function registerRunTools(server, deps) {
|
|
|
258
385
|
}
|
|
259
386
|
// when:'schedule' with no cloudRunnerId โ leave unpinned; the backend auto-picks a live VM at fire time.
|
|
260
387
|
}
|
|
261
|
-
// LOCAL run-NOW to a specific agent must be online (can't dispatch to an offline agent).
|
|
262
|
-
// SCHEDULE
|
|
263
|
-
|
|
388
|
+
// LOCAL/REMOTE run-NOW to a specific agent must be online (can't dispatch to an offline agent).
|
|
389
|
+
// The SCHEDULE branch below allows an offline agent since it runs when it fires. REMOTE also
|
|
390
|
+
// REQUIRES an explicit agentId at all times โ auto-pick across teammate boxes is unsafe.
|
|
391
|
+
if (mode === 'remote' && !agentId) {
|
|
392
|
+
return err("REMOTE runs need an explicit agentId. Call remote_agents to see the teammate machines, " +
|
|
393
|
+
"then pass one as agentId (must be an ONLINE row for when:'immediate').");
|
|
394
|
+
}
|
|
395
|
+
if ((mode === 'local' || mode === 'remote') && when === 'immediate' && agentId) {
|
|
264
396
|
const offlineMsg = await localAgentOfflineForNow(adapter, agentId);
|
|
265
397
|
if (offlineMsg)
|
|
266
398
|
return err(offlineMsg);
|
|
267
399
|
}
|
|
400
|
+
// Mobile refuse cascade โ asked AFTER mode/when so the client can filter the device list by
|
|
401
|
+
// where it's going to run. Order mirrors the UI: pick the KIND of device (Real / Emulator /
|
|
402
|
+
// Cloud โฆ), then the specific device off the matching tab. Cloud tabs show even in LOCAL
|
|
403
|
+
// mode (see device-dropdown.component.html:104-111) โ LOCAL orchestrator can drive a cloud
|
|
404
|
+
// device via Appium.
|
|
405
|
+
if (isMobile) {
|
|
406
|
+
if (!deviceType) {
|
|
407
|
+
const platform = tcType.toLowerCase();
|
|
408
|
+
const localKinds = platform === 'ios' ? "'real_device', 'simulator'" : "'real_device', 'emulator'";
|
|
409
|
+
return err(`ASK the user which DEVICE TYPE this ${tcType} run should use: ${localKinds}, or ` +
|
|
410
|
+
`'cloud_device' / 'cloud_emulator' / 'cloud_simulator' for a farm device. Then call ` +
|
|
411
|
+
`tc_run again with deviceType set. (Also call mobile_list_devices({ platform: '${platform}' }) ` +
|
|
412
|
+
`to show the available devices grouped by kind โ the user picks a device and passes ` +
|
|
413
|
+
`\`id\` as deviceId, its \`deviceType\`, and โ for cloud rows โ its \`cloudDeviceType\`.)`);
|
|
414
|
+
}
|
|
415
|
+
const isCloudPick = deviceType.startsWith('cloud_');
|
|
416
|
+
if (isCloudPick && !cloudDeviceType) {
|
|
417
|
+
const platform = tcType.toLowerCase();
|
|
418
|
+
return err(`Cloud device picks need \`cloudDeviceType\` โ which farm the device came from. Call ` +
|
|
419
|
+
`mobile_list_devices({ platform: '${platform}' }); the matching row's \`cloudDeviceType\` ` +
|
|
420
|
+
`is one of 'lambda_test' / 'browser_stack' / 'sauce_labs' / 'simplify_qa_device_form'. ` +
|
|
421
|
+
`Pass it alongside deviceType and deviceId.`);
|
|
422
|
+
}
|
|
423
|
+
if (!deviceId) {
|
|
424
|
+
const platform = tcType.toLowerCase();
|
|
425
|
+
return err(`ASK the user which DEVICE to run on. Call mobile_list_devices({ platform: '${platform}' }) ` +
|
|
426
|
+
`and offer the rows whose deviceType matches '${deviceType}' โ the user picks one; pass its ` +
|
|
427
|
+
`\`id\` as deviceId (and optionally its \`osVersion\`) to tc_run.`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
268
430
|
if (when === 'schedule') {
|
|
269
431
|
if (!adapter.createSchedule)
|
|
270
432
|
return err('This product does not support scheduling runs.');
|
|
@@ -273,7 +435,8 @@ export function registerRunTools(server, deps) {
|
|
|
273
435
|
"ISO datetime (e.g. '2026-08-20T09:00:00Z'). Ask the user when the run should start (and the " +
|
|
274
436
|
'recurrence, end date and timezone if any).');
|
|
275
437
|
}
|
|
276
|
-
// A schedule fires detached from any live session โ a LOCAL schedule must pin a
|
|
438
|
+
// A schedule fires detached from any live session โ a LOCAL or REMOTE schedule must pin a
|
|
439
|
+
// specific agent (mode:'remote' already fenced above, so this catches LOCAL).
|
|
277
440
|
if (mode === 'local' && !agentId)
|
|
278
441
|
return err(await localScheduleNeedsAgent(adapter));
|
|
279
442
|
// Cloud: pin the runner's CREDENTIAL only when the user explicitly picked one; otherwise leave
|
|
@@ -282,7 +445,7 @@ export function registerRunTools(server, deps) {
|
|
|
282
445
|
const schedOpts = {
|
|
283
446
|
mode,
|
|
284
447
|
scheduledFor,
|
|
285
|
-
...(mode === 'local' && agentId ? { agentId } : {}),
|
|
448
|
+
...((mode === 'local' || mode === 'remote') && agentId ? { agentId } : {}),
|
|
286
449
|
...(pinnedRunner?.credentialId ? { cloudRunnerCredentialId: pinnedRunner.credentialId } : {}),
|
|
287
450
|
...(browser !== undefined ? { browser } : {}),
|
|
288
451
|
...(environment !== undefined ? { environmentId: environment } : {}),
|
|
@@ -306,12 +469,15 @@ export function registerRunTools(server, deps) {
|
|
|
306
469
|
}
|
|
307
470
|
const opts = {
|
|
308
471
|
mode,
|
|
309
|
-
...(mode === 'local' && agentId ? { agentId } : {}),
|
|
472
|
+
...((mode === 'local' || mode === 'remote') && agentId ? { agentId } : {}),
|
|
310
473
|
...(environment !== undefined ? { environment } : {}),
|
|
311
474
|
...(browser !== undefined ? { browser } : {}),
|
|
312
475
|
...(headless !== undefined ? { headless } : {}),
|
|
313
476
|
...(resolvedRunner ? { cloudRunnerId: resolvedRunner.id } : {}),
|
|
314
477
|
...(deviceId !== undefined ? { deviceId } : {}),
|
|
478
|
+
...(deviceType !== undefined ? { deviceType: deviceType.toUpperCase() } : {}),
|
|
479
|
+
...(cloudDeviceType !== undefined ? { cloudDeviceType: cloudDeviceType.toUpperCase() } : {}),
|
|
480
|
+
...(osVersion !== undefined ? { osVersion } : {}),
|
|
315
481
|
...(releaseId ? { releaseId } : {}),
|
|
316
482
|
...(sprintId ? { sprintId } : {}),
|
|
317
483
|
...(versionId ? { versionId } : {}),
|
|
@@ -320,7 +486,9 @@ export function registerRunTools(server, deps) {
|
|
|
320
486
|
const run = await adapter.startRun({ tcId }, opts);
|
|
321
487
|
const where = mode === 'cloud'
|
|
322
488
|
? `on cloud runner ${resolvedRunner ? `**${resolvedRunner.name}**` : ''}`
|
|
323
|
-
:
|
|
489
|
+
: mode === 'remote'
|
|
490
|
+
? `on remote agent \`${agentId}\``
|
|
491
|
+
: `locally${agentId ? ` on agent \`${agentId}\`` : ' on your online agent'}`;
|
|
324
492
|
return ok(`Started ${tcId} ${where}. Run id \`${run.runId}\` โ poll run_status({ runId: "${run.runId}" }) for progress.`, { run, mode, ...(resolvedRunner ? { runner: resolvedRunner } : {}) });
|
|
325
493
|
}
|
|
326
494
|
catch (e) {
|
|
@@ -328,20 +496,22 @@ export function registerRunTools(server, deps) {
|
|
|
328
496
|
}
|
|
329
497
|
});
|
|
330
498
|
server.registerTool('suite_run', {
|
|
331
|
-
title: 'Run a test suite (local
|
|
499
|
+
title: 'Run a test suite (local / remote / cloud; now or scheduled)',
|
|
332
500
|
description: 'Execute an existing test SUITE โ all its member test cases, in order โ in the connected product. ' +
|
|
333
501
|
'ASK the user for EVERY field the UI\'s Execute-suite dialog asks โ never assume โ and in ORDER ' +
|
|
334
502
|
'(mirrors the product UI, `_parentComponent==="SU"`): (1) `releaseId` (from list_releases โ the UI ' +
|
|
335
503
|
'marks it required with `*` for a suite), (2) `sprintId` (from list_sprints โ also `*` for a suite), ' +
|
|
336
|
-
'(3) `environment` (from tc_list_environments โ `*`), (4) `mode` local/cloud, (5) `when` immediate/schedule, ' +
|
|
504
|
+
'(3) `environment` (from tc_list_environments โ `*`), (4) `mode` local/remote/cloud, (5) `when` immediate/schedule, ' +
|
|
337
505
|
'(6) for a schedule the start time + recurrence + timezone, (7) THEN offer the eligible agent/runner. ' +
|
|
338
506
|
'The tool REFUSES when any of (1)โ(5) is omitted โ that is deliberate; it forces the ask instead of ' +
|
|
339
507
|
'silently defaulting. UNLIKE `tc_run`, a suite does NOT ask for `versionId` or `browser` โ each TC ' +
|
|
340
508
|
"in the suite carries its own pinned version + execution platform (that's what the suite editor picks per row). " +
|
|
341
509
|
'`mode` decides WHERE it runs:\n' +
|
|
342
|
-
" โข 'local' โ
|
|
343
|
-
"when:'immediate'
|
|
344
|
-
"when:'schedule'
|
|
510
|
+
" โข 'local' โ THIS machine's desktop agent, always serial. Call `local_agents` (0 or 1 row). For " +
|
|
511
|
+
"when:'immediate' the tool probes :4040 and refuses if the desktop agent is down. Omit `agentId` to " +
|
|
512
|
+
"auto-pick this machine's agent; for when:'schedule' `agentId` is REQUIRED.\n" +
|
|
513
|
+
" โข 'remote' โ ANOTHER registered machine's desktop agent, always serial. Call `remote_agents`; " +
|
|
514
|
+
"`agentId` is REQUIRED (no auto-pick). when:'immediate' โ ONLINE only; when:'schedule' โ offline OK.\n" +
|
|
345
515
|
" โข 'cloud' โ online cloud runner VM(s) (ONLINE-only whether now or scheduled). Pass `cloudRunnerId` " +
|
|
346
516
|
'from cloud_runners; for a schedule omit it to auto-pick a live VM at fire time.\n' +
|
|
347
517
|
'For cloud, if no runner is online it reports that there are no active cloud runners (nothing is run).\n' +
|
|
@@ -355,8 +525,8 @@ export function registerRunTools(server, deps) {
|
|
|
355
525
|
suiteId: z.string().describe('The suite id to run (from suite_list / suite_get).'),
|
|
356
526
|
releaseId: z.string().optional().describe('REQUIRED for a suite (UI marks it `*` for _parentComponent==="SU"). A release id from list_releases. Ask the user.'),
|
|
357
527
|
sprintId: z.string().optional().describe('REQUIRED for a suite (UI marks it `*` for _parentComponent==="SU"). A sprint id from list_sprints (scoped to the picked release). Ask the user.'),
|
|
358
|
-
mode: z.enum(['local', 'cloud']).optional().describe('Where to run. REQUIRED โ ASK the user if unstated (the tool refuses when omitted).'),
|
|
359
|
-
agentId: z.string().optional().describe("For local: a
|
|
528
|
+
mode: z.enum(['local', 'remote', 'cloud']).optional().describe('Where to run. REQUIRED โ ASK the user if unstated (the tool refuses when omitted).'),
|
|
529
|
+
agentId: z.string().optional().describe("For local: a credential_id from `local_agents` (this machine only; omit to auto-pick). For remote: REQUIRED โ a credential_id from `remote_agents`. when:'immediate' โ picked agent must be ONLINE; when:'schedule' โ offline OK, agentId REQUIRED for both local and remote."),
|
|
360
530
|
environment: z.string().optional().describe('Environment key (from tc_list_environments). REQUIRED โ the UI marks this with a red asterisk for a suite.'),
|
|
361
531
|
browser: z.string().optional().describe("Optional at the SUITE level โ each TC in the suite has its own execution platform (that's the suite-editor pick per row). Provide only to OVERRIDE for every WEB TC. Values: 'chromium' (default), 'chrome'/'msedge', 'firefox', 'webkit'. NOTE: a schedule accepts only chromium/msedge/firefox/webkit."),
|
|
362
532
|
headless: z.boolean().optional().describe('Headless. Defaults: cloud=true, local=false. (Immediate runs only.)'),
|
|
@@ -395,9 +565,9 @@ export function registerRunTools(server, deps) {
|
|
|
395
565
|
'field with a red asterisk for a suite.');
|
|
396
566
|
}
|
|
397
567
|
if (!mode) {
|
|
398
|
-
return err("Specify where to run: call suite_run again with mode: 'local' or 'cloud'. " +
|
|
399
|
-
|
|
400
|
-
'cloud runs on online cloud runner VM(s).
|
|
568
|
+
return err("Specify where to run: call suite_run again with mode: 'local', 'remote', or 'cloud'. " +
|
|
569
|
+
"If the user has not said which, ASK them first โ local runs on THIS machine's agent, " +
|
|
570
|
+
"remote runs on a teammate's registered agent, cloud runs on online cloud runner VM(s).");
|
|
401
571
|
}
|
|
402
572
|
// Never assume now-vs-scheduled โ it decides which agents are eligible (online-only for now,
|
|
403
573
|
// online+offline for a local schedule) and, for a schedule, the start time asked next.
|
|
@@ -432,9 +602,13 @@ export function registerRunTools(server, deps) {
|
|
|
432
602
|
}
|
|
433
603
|
// when:'schedule' with no cloudRunnerId โ leave unpinned; the backend auto-picks a live VM at fire time.
|
|
434
604
|
}
|
|
435
|
-
// LOCAL run-NOW to a specific agent must be online
|
|
436
|
-
//
|
|
437
|
-
if (mode === '
|
|
605
|
+
// LOCAL/REMOTE run-NOW to a specific agent must be online. SCHEDULE branch below allows offline.
|
|
606
|
+
// REMOTE always requires an explicit agentId (auto-pick across teammate boxes is unsafe).
|
|
607
|
+
if (mode === 'remote' && !agentId) {
|
|
608
|
+
return err("REMOTE runs need an explicit agentId. Call remote_agents to see the teammate machines, " +
|
|
609
|
+
"then pass one as agentId (must be an ONLINE row for when:'immediate').");
|
|
610
|
+
}
|
|
611
|
+
if ((mode === 'local' || mode === 'remote') && when === 'immediate' && agentId) {
|
|
438
612
|
const offlineMsg = await localAgentOfflineForNow(adapter, agentId);
|
|
439
613
|
if (offlineMsg)
|
|
440
614
|
return err(offlineMsg);
|
|
@@ -448,7 +622,8 @@ export function registerRunTools(server, deps) {
|
|
|
448
622
|
'recurrence, end date and timezone if any). NOTE: strategy/maxConcurrent/terminateOnFailure are ' +
|
|
449
623
|
"immediate-run only โ a schedule uses the suite's own settings.");
|
|
450
624
|
}
|
|
451
|
-
// A schedule fires detached from any live session โ
|
|
625
|
+
// A schedule fires detached from any live session โ LOCAL and REMOTE schedules must pin a
|
|
626
|
+
// specific agent. REMOTE was already fenced above; this catches LOCAL.
|
|
452
627
|
if (mode === 'local' && !agentId)
|
|
453
628
|
return err(await localScheduleNeedsAgent(adapter));
|
|
454
629
|
// Cloud: pin the runner's CREDENTIAL only when the user explicitly picked one; otherwise leave
|
|
@@ -457,7 +632,7 @@ export function registerRunTools(server, deps) {
|
|
|
457
632
|
const schedOpts = {
|
|
458
633
|
mode,
|
|
459
634
|
scheduledFor,
|
|
460
|
-
...(mode === 'local' && agentId ? { agentId } : {}),
|
|
635
|
+
...((mode === 'local' || mode === 'remote') && agentId ? { agentId } : {}),
|
|
461
636
|
...(pinnedRunner?.credentialId ? { cloudRunnerCredentialId: pinnedRunner.credentialId } : {}),
|
|
462
637
|
...(browser !== undefined ? { browser } : {}),
|
|
463
638
|
...(environment !== undefined ? { environmentId: environment } : {}),
|
|
@@ -481,7 +656,7 @@ export function registerRunTools(server, deps) {
|
|
|
481
656
|
}
|
|
482
657
|
const opts = {
|
|
483
658
|
mode,
|
|
484
|
-
...(mode === 'local' && agentId ? { agentId } : {}),
|
|
659
|
+
...((mode === 'local' || mode === 'remote') && agentId ? { agentId } : {}),
|
|
485
660
|
...(environment !== undefined ? { environment } : {}),
|
|
486
661
|
...(browser !== undefined ? { browser } : {}),
|
|
487
662
|
...(headless !== undefined ? { headless } : {}),
|
|
@@ -496,7 +671,9 @@ export function registerRunTools(server, deps) {
|
|
|
496
671
|
const run = await adapter.startRun({ suiteId }, opts);
|
|
497
672
|
const where = mode === 'cloud'
|
|
498
673
|
? `on cloud runner ${resolvedRunner ? `**${resolvedRunner.name}**` : ''}`
|
|
499
|
-
:
|
|
674
|
+
: mode === 'remote'
|
|
675
|
+
? `on remote agent \`${agentId}\``
|
|
676
|
+
: `locally${agentId ? ` on agent \`${agentId}\`` : ' on your online agent'}`;
|
|
500
677
|
return ok(`Started suite ${suiteId} ${where}. Run id \`${run.runId}\` โ poll run_status({ runId: "${run.runId}" }) for progress.`, { run, mode, ...(resolvedRunner ? { runner: resolvedRunner } : {}) });
|
|
501
678
|
}
|
|
502
679
|
catch (e) {
|
|
@@ -505,16 +682,33 @@ export function registerRunTools(server, deps) {
|
|
|
505
682
|
});
|
|
506
683
|
server.registerTool('run_status', {
|
|
507
684
|
title: 'Get a run status',
|
|
508
|
-
description: 'Read the status of a run
|
|
509
|
-
'
|
|
510
|
-
|
|
685
|
+
description: 'Read the status of a run โ state (queued/running/passed/failed/stopped/error), progress, ' +
|
|
686
|
+
'per-test-case rollup for a SUITE run (capped at 100 shown), and a link. Pass EITHER `runId` ' +
|
|
687
|
+
'(from tc_run / suite_run) or `suiteId` (from suite_list) to read that suite\'s LATEST ' +
|
|
688
|
+
'execution. Requires a live connection.',
|
|
689
|
+
inputSchema: {
|
|
690
|
+
runId: z.string().optional().describe('The run id returned by tc_run or suite_run.'),
|
|
691
|
+
suiteId: z.string().optional().describe('A suite id (from suite_list) โ resolves to the suite\'s latest execution.'),
|
|
692
|
+
},
|
|
511
693
|
annotations: { readOnlyHint: true },
|
|
512
|
-
}, async ({ runId }) => {
|
|
694
|
+
}, async ({ runId, suiteId }) => {
|
|
513
695
|
const adapter = deps.getAdapter();
|
|
514
696
|
if (!adapter)
|
|
515
697
|
return err(NOT_CONNECTED);
|
|
698
|
+
if ((runId && suiteId) || (!runId && !suiteId)) {
|
|
699
|
+
return err('Pass exactly one of `runId` or `suiteId`.');
|
|
700
|
+
}
|
|
516
701
|
try {
|
|
517
|
-
|
|
702
|
+
let effectiveRunId = runId;
|
|
703
|
+
if (suiteId) {
|
|
704
|
+
if (!adapter.getLatestSuiteRun)
|
|
705
|
+
return err('This product does not support latest-suite-run lookup โ pass `runId` instead.');
|
|
706
|
+
const latest = await adapter.getLatestSuiteRun(suiteId);
|
|
707
|
+
if (!latest)
|
|
708
|
+
return ok(`Suite ${suiteId} has no executions yet.`);
|
|
709
|
+
effectiveRunId = latest;
|
|
710
|
+
}
|
|
711
|
+
const status = await adapter.getRun(effectiveRunId);
|
|
518
712
|
return ok(statusText(status), { run: status });
|
|
519
713
|
}
|
|
520
714
|
catch (e) {
|