@muggleai/mcp 1.0.20 → 1.0.22
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 +88 -0
- package/bin/muggle-mcp.js +0 -0
- package/dist/{chunk-HOXCZIJC.js → chunk-DGEO3CP2.js} +984 -266
- package/dist/chunk-DGEO3CP2.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +2 -2
- package/dist/local-qa/contracts/project-schemas.d.ts +40 -0
- package/dist/local-qa/contracts/project-schemas.d.ts.map +1 -1
- package/dist/local-qa/services/auth-service.d.ts.map +1 -1
- package/dist/local-qa/services/execution-service.d.ts.map +1 -1
- package/dist/local-qa/services/run-result-storage-service.d.ts +37 -0
- package/dist/local-qa/services/run-result-storage-service.d.ts.map +1 -1
- package/dist/local-qa/tools/tool-registry.d.ts.map +1 -1
- package/dist/qa/contracts/index.d.ts +361 -41
- package/dist/qa/contracts/index.d.ts.map +1 -1
- package/dist/qa/contracts/local-run-schemas.d.ts +123 -0
- package/dist/qa/contracts/local-run-schemas.d.ts.map +1 -0
- package/dist/qa/tools/tool-registry.d.ts.map +1 -1
- package/dist/shared/auth.d.ts.map +1 -1
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/types.d.ts +2 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/scripts/postinstall.mjs +268 -9
- package/dist/chunk-HOXCZIJC.js.map +0 -1
|
@@ -9,7 +9,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSche
|
|
|
9
9
|
import { v4 } from 'uuid';
|
|
10
10
|
import { z, ZodError } from 'zod';
|
|
11
11
|
import axios, { AxiosError } from 'axios';
|
|
12
|
-
import { exec } from 'child_process';
|
|
12
|
+
import { spawn, exec } from 'child_process';
|
|
13
13
|
import * as fs5 from 'fs/promises';
|
|
14
14
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
15
|
|
|
@@ -24,14 +24,18 @@ var __export = (target, all) => {
|
|
|
24
24
|
for (var name in all)
|
|
25
25
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
26
|
};
|
|
27
|
-
var
|
|
27
|
+
var DEFAULT_PROMPT_SERVICE_PRODUCTION_URL = "https://promptservice.muggle-ai.com";
|
|
28
|
+
var DEFAULT_PROMPT_SERVICE_DEV_URL = "http://localhost:5050";
|
|
28
29
|
var DEFAULT_WEB_SERVICE_URL = "http://localhost:3001";
|
|
29
30
|
var DATA_DIR_NAME = ".muggle-ai";
|
|
30
31
|
var ELECTRON_APP_DIR = "electron-app";
|
|
31
32
|
var CREDENTIALS_FILE = "credentials.json";
|
|
32
|
-
var
|
|
33
|
-
var
|
|
34
|
-
var
|
|
33
|
+
var DEFAULT_AUTH0_PRODUCTION_DOMAIN = "login.muggle-ai.com";
|
|
34
|
+
var DEFAULT_AUTH0_PRODUCTION_CLIENT_ID = "UgG5UjoyLksxMciWWKqVpwfWrJ4rFvtT";
|
|
35
|
+
var DEFAULT_AUTH0_PRODUCTION_AUDIENCE = "https://muggleai.us.auth0.com/api/v2/";
|
|
36
|
+
var DEFAULT_AUTH0_DEV_DOMAIN = "dev-po4mxmz0rd8a0w8w.us.auth0.com";
|
|
37
|
+
var DEFAULT_AUTH0_DEV_CLIENT_ID = "hihMM2cxb40yHaZMH2MMXwO2ZRJQ3MxA";
|
|
38
|
+
var DEFAULT_AUTH0_DEV_AUDIENCE = "https://dev-po4mxmz0rd8a0w8w.us.auth0.com/api/v2/";
|
|
35
39
|
var DEFAULT_AUTH0_SCOPE = "openid profile email offline_access";
|
|
36
40
|
var configInstance = null;
|
|
37
41
|
var muggleConfigCache = null;
|
|
@@ -93,10 +97,20 @@ function getMuggleConfig() {
|
|
|
93
97
|
This is a bug - please report it.`
|
|
94
98
|
);
|
|
95
99
|
}
|
|
100
|
+
if (config.runtimeTargetDefault !== void 0 && config.runtimeTargetDefault !== "production" && config.runtimeTargetDefault !== "dev") {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Invalid muggleConfig.runtimeTargetDefault in package.json.
|
|
103
|
+
Path: ${packageJsonPath}
|
|
104
|
+
Value: ${JSON.stringify(config.runtimeTargetDefault)}
|
|
105
|
+
Expected: "production" or "dev"
|
|
106
|
+
This is a bug - please report it.`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
96
109
|
muggleConfigCache = {
|
|
97
110
|
electronAppVersion: config.electronAppVersion,
|
|
98
111
|
downloadBaseUrl: config.downloadBaseUrl,
|
|
99
|
-
checksums: config.checksums || {}
|
|
112
|
+
checksums: config.checksums || {},
|
|
113
|
+
runtimeTargetDefault: config.runtimeTargetDefault
|
|
100
114
|
};
|
|
101
115
|
return muggleConfigCache;
|
|
102
116
|
}
|
|
@@ -104,12 +118,12 @@ function getDataDir() {
|
|
|
104
118
|
return path.join(os.homedir(), DATA_DIR_NAME);
|
|
105
119
|
}
|
|
106
120
|
function getDownloadedElectronAppPath() {
|
|
107
|
-
const
|
|
121
|
+
const platform4 = os.platform();
|
|
108
122
|
const config = getMuggleConfig();
|
|
109
123
|
const version = config.electronAppVersion;
|
|
110
124
|
const baseDir = path.join(getDataDir(), ELECTRON_APP_DIR, version);
|
|
111
125
|
let binaryPath;
|
|
112
|
-
switch (
|
|
126
|
+
switch (platform4) {
|
|
113
127
|
case "darwin":
|
|
114
128
|
binaryPath = path.join(baseDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
115
129
|
break;
|
|
@@ -128,10 +142,10 @@ function getDownloadedElectronAppPath() {
|
|
|
128
142
|
return null;
|
|
129
143
|
}
|
|
130
144
|
function getSystemElectronAppPath() {
|
|
131
|
-
const
|
|
145
|
+
const platform4 = os.platform();
|
|
132
146
|
const homeDir = os.homedir();
|
|
133
147
|
let binaryPath;
|
|
134
|
-
switch (
|
|
148
|
+
switch (platform4) {
|
|
135
149
|
case "darwin":
|
|
136
150
|
binaryPath = path.join(homeDir, "Applications", "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
137
151
|
break;
|
|
@@ -183,17 +197,65 @@ function parseInteger(value, defaultValue) {
|
|
|
183
197
|
const parsed = parseInt(value, 10);
|
|
184
198
|
return isNaN(parsed) ? defaultValue : parsed;
|
|
185
199
|
}
|
|
200
|
+
function getPromptServiceRuntimeTarget() {
|
|
201
|
+
const runtimeTargetFromEnv = process.env.MUGGLE_MCP_PROMPT_SERVICE_TARGET;
|
|
202
|
+
if (runtimeTargetFromEnv) {
|
|
203
|
+
if (runtimeTargetFromEnv === "production" || runtimeTargetFromEnv === "dev") {
|
|
204
|
+
return runtimeTargetFromEnv;
|
|
205
|
+
}
|
|
206
|
+
throw new Error(
|
|
207
|
+
`Invalid MUGGLE_MCP_PROMPT_SERVICE_TARGET value: '${runtimeTargetFromEnv}'. Expected 'production' or 'dev'.`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
const muggleConfig = getMuggleConfig();
|
|
211
|
+
if (muggleConfig.runtimeTargetDefault) {
|
|
212
|
+
return muggleConfig.runtimeTargetDefault;
|
|
213
|
+
}
|
|
214
|
+
return "dev";
|
|
215
|
+
}
|
|
216
|
+
function getDefaultPromptServiceUrl() {
|
|
217
|
+
const runtimeTarget = getPromptServiceRuntimeTarget();
|
|
218
|
+
if (runtimeTarget === "dev") {
|
|
219
|
+
return DEFAULT_PROMPT_SERVICE_DEV_URL;
|
|
220
|
+
}
|
|
221
|
+
return DEFAULT_PROMPT_SERVICE_PRODUCTION_URL;
|
|
222
|
+
}
|
|
223
|
+
function getDefaultAuth0Domain() {
|
|
224
|
+
const runtimeTarget = getPromptServiceRuntimeTarget();
|
|
225
|
+
if (runtimeTarget === "dev") {
|
|
226
|
+
return DEFAULT_AUTH0_DEV_DOMAIN;
|
|
227
|
+
}
|
|
228
|
+
return DEFAULT_AUTH0_PRODUCTION_DOMAIN;
|
|
229
|
+
}
|
|
230
|
+
function getDefaultAuth0ClientId() {
|
|
231
|
+
const runtimeTarget = getPromptServiceRuntimeTarget();
|
|
232
|
+
if (runtimeTarget === "dev") {
|
|
233
|
+
return DEFAULT_AUTH0_DEV_CLIENT_ID;
|
|
234
|
+
}
|
|
235
|
+
return DEFAULT_AUTH0_PRODUCTION_CLIENT_ID;
|
|
236
|
+
}
|
|
237
|
+
function getDefaultAuth0Audience() {
|
|
238
|
+
const runtimeTarget = getPromptServiceRuntimeTarget();
|
|
239
|
+
if (runtimeTarget === "dev") {
|
|
240
|
+
return DEFAULT_AUTH0_DEV_AUDIENCE;
|
|
241
|
+
}
|
|
242
|
+
return DEFAULT_AUTH0_PRODUCTION_AUDIENCE;
|
|
243
|
+
}
|
|
186
244
|
function buildAuth0Config() {
|
|
245
|
+
const defaultAuth0Domain = getDefaultAuth0Domain();
|
|
246
|
+
const defaultAuth0ClientId = getDefaultAuth0ClientId();
|
|
247
|
+
const defaultAuth0Audience = getDefaultAuth0Audience();
|
|
187
248
|
return {
|
|
188
|
-
domain: process.env.AUTH0_DOMAIN ??
|
|
189
|
-
clientId: process.env.AUTH0_CLIENT_ID ??
|
|
190
|
-
audience: process.env.AUTH0_AUDIENCE ??
|
|
249
|
+
domain: process.env.AUTH0_DOMAIN ?? defaultAuth0Domain,
|
|
250
|
+
clientId: process.env.AUTH0_CLIENT_ID ?? defaultAuth0ClientId,
|
|
251
|
+
audience: process.env.AUTH0_AUDIENCE ?? defaultAuth0Audience,
|
|
191
252
|
scope: process.env.AUTH0_SCOPE ?? DEFAULT_AUTH0_SCOPE
|
|
192
253
|
};
|
|
193
254
|
}
|
|
194
255
|
function buildQaConfig() {
|
|
256
|
+
const defaultPromptServiceUrl = getDefaultPromptServiceUrl();
|
|
195
257
|
return {
|
|
196
|
-
promptServiceBaseUrl: process.env.PROMPT_SERVICE_BASE_URL ??
|
|
258
|
+
promptServiceBaseUrl: process.env.PROMPT_SERVICE_BASE_URL ?? defaultPromptServiceUrl,
|
|
197
259
|
requestTimeoutMs: parseInteger(process.env.REQUEST_TIMEOUT_MS, 3e4),
|
|
198
260
|
workflowTimeoutMs: parseInteger(process.env.WORKFLOW_TIMEOUT_MS, 12e4)
|
|
199
261
|
};
|
|
@@ -201,9 +263,13 @@ function buildQaConfig() {
|
|
|
201
263
|
function buildLocalQaConfig() {
|
|
202
264
|
const dataDir = getDataDir();
|
|
203
265
|
const auth0Scopes = (process.env.AUTH0_SCOPE ?? DEFAULT_AUTH0_SCOPE).split(" ");
|
|
266
|
+
const defaultPromptServiceUrl = getDefaultPromptServiceUrl();
|
|
267
|
+
const defaultAuth0Domain = getDefaultAuth0Domain();
|
|
268
|
+
const defaultAuth0ClientId = getDefaultAuth0ClientId();
|
|
269
|
+
const defaultAuth0Audience = getDefaultAuth0Audience();
|
|
204
270
|
return {
|
|
205
271
|
webServiceUrl: process.env.WEB_SERVICE_URL ?? DEFAULT_WEB_SERVICE_URL,
|
|
206
|
-
promptServiceUrl: process.env.PROMPT_SERVICE_BASE_URL ??
|
|
272
|
+
promptServiceUrl: process.env.PROMPT_SERVICE_BASE_URL ?? defaultPromptServiceUrl,
|
|
207
273
|
dataDir,
|
|
208
274
|
sessionsDir: path.join(dataDir, "sessions"),
|
|
209
275
|
projectsDir: path.join(dataDir, "projects"),
|
|
@@ -214,9 +280,9 @@ function buildLocalQaConfig() {
|
|
|
214
280
|
webServicePath: resolveWebServicePath(),
|
|
215
281
|
webServicePidFile: path.join(dataDir, "web-service.pid"),
|
|
216
282
|
auth0: {
|
|
217
|
-
domain: process.env.AUTH0_DOMAIN ??
|
|
218
|
-
clientId: process.env.AUTH0_CLIENT_ID ??
|
|
219
|
-
audience: process.env.AUTH0_AUDIENCE ??
|
|
283
|
+
domain: process.env.AUTH0_DOMAIN ?? defaultAuth0Domain,
|
|
284
|
+
clientId: process.env.AUTH0_CLIENT_ID ?? defaultAuth0ClientId,
|
|
285
|
+
audience: process.env.AUTH0_AUDIENCE ?? defaultAuth0Audience,
|
|
220
286
|
scopes: auth0Scopes
|
|
221
287
|
}
|
|
222
288
|
};
|
|
@@ -320,8 +386,8 @@ function getLogger() {
|
|
|
320
386
|
return loggerInstance;
|
|
321
387
|
}
|
|
322
388
|
function createChildLogger(correlationId) {
|
|
323
|
-
const
|
|
324
|
-
return
|
|
389
|
+
const logger6 = getLogger();
|
|
390
|
+
return logger6.child({ correlationId });
|
|
325
391
|
}
|
|
326
392
|
function resetLogger() {
|
|
327
393
|
loggerInstance = null;
|
|
@@ -341,7 +407,7 @@ function getOpenCommand(url) {
|
|
|
341
407
|
}
|
|
342
408
|
}
|
|
343
409
|
async function openBrowserUrl(options) {
|
|
344
|
-
return new Promise((
|
|
410
|
+
return new Promise((resolve3) => {
|
|
345
411
|
try {
|
|
346
412
|
const command = getOpenCommand(options.url);
|
|
347
413
|
logger.debug("[Browser] Opening URL", { url: options.url, command });
|
|
@@ -351,13 +417,13 @@ async function openBrowserUrl(options) {
|
|
|
351
417
|
url: options.url,
|
|
352
418
|
error: error.message
|
|
353
419
|
});
|
|
354
|
-
|
|
420
|
+
resolve3({
|
|
355
421
|
opened: false,
|
|
356
422
|
error: error.message
|
|
357
423
|
});
|
|
358
424
|
} else {
|
|
359
425
|
logger.info("[Browser] URL opened successfully", { url: options.url });
|
|
360
|
-
|
|
426
|
+
resolve3({ opened: true });
|
|
361
427
|
}
|
|
362
428
|
});
|
|
363
429
|
} catch (error) {
|
|
@@ -366,7 +432,7 @@ async function openBrowserUrl(options) {
|
|
|
366
432
|
url: options.url,
|
|
367
433
|
error: errorMessage
|
|
368
434
|
});
|
|
369
|
-
|
|
435
|
+
resolve3({
|
|
370
436
|
opened: false,
|
|
371
437
|
error: errorMessage
|
|
372
438
|
});
|
|
@@ -472,16 +538,16 @@ var AuthService = class {
|
|
|
472
538
|
* Get current authentication status.
|
|
473
539
|
*/
|
|
474
540
|
getAuthStatus() {
|
|
475
|
-
const
|
|
541
|
+
const logger6 = getLogger();
|
|
476
542
|
const storedAuth = this.loadStoredAuth();
|
|
477
543
|
if (!storedAuth) {
|
|
478
|
-
|
|
544
|
+
logger6.debug("No stored auth found");
|
|
479
545
|
return { authenticated: false };
|
|
480
546
|
}
|
|
481
547
|
const now = /* @__PURE__ */ new Date();
|
|
482
548
|
const expiresAt = new Date(storedAuth.expiresAt);
|
|
483
549
|
const isExpired = now >= expiresAt;
|
|
484
|
-
|
|
550
|
+
logger6.debug("Auth status checked", {
|
|
485
551
|
email: storedAuth.email,
|
|
486
552
|
isExpired,
|
|
487
553
|
expiresAt: storedAuth.expiresAt
|
|
@@ -498,10 +564,10 @@ var AuthService = class {
|
|
|
498
564
|
* Start the device code flow.
|
|
499
565
|
*/
|
|
500
566
|
async startDeviceCodeFlow() {
|
|
501
|
-
const
|
|
567
|
+
const logger6 = getLogger();
|
|
502
568
|
const config = getConfig();
|
|
503
569
|
const { domain, clientId, audience, scopes } = config.localQa.auth0;
|
|
504
|
-
|
|
570
|
+
logger6.info("Starting device code flow");
|
|
505
571
|
const url = `https://${domain}/oauth/device/code`;
|
|
506
572
|
const body = new URLSearchParams({
|
|
507
573
|
client_id: clientId,
|
|
@@ -517,14 +583,14 @@ var AuthService = class {
|
|
|
517
583
|
});
|
|
518
584
|
if (!response.ok) {
|
|
519
585
|
const errorText = await response.text();
|
|
520
|
-
|
|
586
|
+
logger6.error("Device code request failed", {
|
|
521
587
|
status: response.status,
|
|
522
588
|
error: errorText
|
|
523
589
|
});
|
|
524
590
|
throw new Error(`Failed to start device code flow: ${response.status} ${errorText}`);
|
|
525
591
|
}
|
|
526
592
|
const data = await response.json();
|
|
527
|
-
|
|
593
|
+
logger6.info("Device code flow started", {
|
|
528
594
|
userCode: data.user_code,
|
|
529
595
|
verificationUri: data.verification_uri,
|
|
530
596
|
expiresIn: data.expires_in
|
|
@@ -538,9 +604,9 @@ var AuthService = class {
|
|
|
538
604
|
url: data.verification_uri_complete
|
|
539
605
|
});
|
|
540
606
|
if (browserOpenResult.opened) {
|
|
541
|
-
|
|
607
|
+
logger6.info("Browser opened for device code login");
|
|
542
608
|
} else {
|
|
543
|
-
|
|
609
|
+
logger6.warn("Failed to open browser for device code login", {
|
|
544
610
|
error: browserOpenResult.error,
|
|
545
611
|
verificationUriComplete: data.verification_uri_complete
|
|
546
612
|
});
|
|
@@ -560,7 +626,7 @@ var AuthService = class {
|
|
|
560
626
|
* Store a pending device code for later retrieval.
|
|
561
627
|
*/
|
|
562
628
|
storePendingDeviceCode(params) {
|
|
563
|
-
const
|
|
629
|
+
const logger6 = getLogger();
|
|
564
630
|
const dir = path.dirname(this.pendingDeviceCodePath);
|
|
565
631
|
if (!fs3.existsSync(dir)) {
|
|
566
632
|
fs3.mkdirSync(dir, { recursive: true });
|
|
@@ -569,15 +635,15 @@ var AuthService = class {
|
|
|
569
635
|
encoding: "utf-8",
|
|
570
636
|
mode: 384
|
|
571
637
|
});
|
|
572
|
-
|
|
638
|
+
logger6.debug("Pending device code stored", { userCode: params.userCode });
|
|
573
639
|
}
|
|
574
640
|
/**
|
|
575
641
|
* Get the pending device code if one exists and is not expired.
|
|
576
642
|
*/
|
|
577
643
|
getPendingDeviceCode() {
|
|
578
|
-
const
|
|
644
|
+
const logger6 = getLogger();
|
|
579
645
|
if (!fs3.existsSync(this.pendingDeviceCodePath)) {
|
|
580
|
-
|
|
646
|
+
logger6.debug("No pending device code found");
|
|
581
647
|
return null;
|
|
582
648
|
}
|
|
583
649
|
try {
|
|
@@ -586,13 +652,13 @@ var AuthService = class {
|
|
|
586
652
|
const now = /* @__PURE__ */ new Date();
|
|
587
653
|
const expiresAt = new Date(data.expiresAt);
|
|
588
654
|
if (now >= expiresAt) {
|
|
589
|
-
|
|
655
|
+
logger6.debug("Pending device code expired");
|
|
590
656
|
this.clearPendingDeviceCode();
|
|
591
657
|
return null;
|
|
592
658
|
}
|
|
593
659
|
return data.deviceCode;
|
|
594
660
|
} catch (error) {
|
|
595
|
-
|
|
661
|
+
logger6.warn("Failed to read pending device code", {
|
|
596
662
|
error: error instanceof Error ? error.message : String(error)
|
|
597
663
|
});
|
|
598
664
|
return null;
|
|
@@ -602,13 +668,13 @@ var AuthService = class {
|
|
|
602
668
|
* Clear the pending device code file.
|
|
603
669
|
*/
|
|
604
670
|
clearPendingDeviceCode() {
|
|
605
|
-
const
|
|
671
|
+
const logger6 = getLogger();
|
|
606
672
|
if (fs3.existsSync(this.pendingDeviceCodePath)) {
|
|
607
673
|
try {
|
|
608
674
|
fs3.unlinkSync(this.pendingDeviceCodePath);
|
|
609
|
-
|
|
675
|
+
logger6.debug("Pending device code cleared");
|
|
610
676
|
} catch (error) {
|
|
611
|
-
|
|
677
|
+
logger6.warn("Failed to clear pending device code", {
|
|
612
678
|
error: error instanceof Error ? error.message : String(error)
|
|
613
679
|
});
|
|
614
680
|
}
|
|
@@ -618,10 +684,10 @@ var AuthService = class {
|
|
|
618
684
|
* Poll for device code authorization completion.
|
|
619
685
|
*/
|
|
620
686
|
async pollDeviceCode(deviceCode) {
|
|
621
|
-
const
|
|
687
|
+
const logger6 = getLogger();
|
|
622
688
|
const config = getConfig();
|
|
623
689
|
const { domain, clientId } = config.localQa.auth0;
|
|
624
|
-
|
|
690
|
+
logger6.debug("Polling for device code authorization");
|
|
625
691
|
const url = `https://${domain}/oauth/token`;
|
|
626
692
|
const body = new URLSearchParams({
|
|
627
693
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
@@ -651,7 +717,7 @@ var AuthService = class {
|
|
|
651
717
|
userId: userInfo.sub
|
|
652
718
|
});
|
|
653
719
|
this.clearPendingDeviceCode();
|
|
654
|
-
|
|
720
|
+
logger6.info("Device code authorization complete", { email: userInfo.email });
|
|
655
721
|
return {
|
|
656
722
|
status: "complete" /* Complete */,
|
|
657
723
|
message: "Authentication successful!",
|
|
@@ -660,35 +726,35 @@ var AuthService = class {
|
|
|
660
726
|
}
|
|
661
727
|
const errorData = await response.json();
|
|
662
728
|
if (errorData.error === "authorization_pending") {
|
|
663
|
-
|
|
729
|
+
logger6.debug("Authorization pending");
|
|
664
730
|
return {
|
|
665
731
|
status: "pending" /* Pending */,
|
|
666
732
|
message: "Waiting for user to complete authorization..."
|
|
667
733
|
};
|
|
668
734
|
}
|
|
669
735
|
if (errorData.error === "slow_down") {
|
|
670
|
-
|
|
736
|
+
logger6.debug("Polling too fast");
|
|
671
737
|
return {
|
|
672
738
|
status: "pending" /* Pending */,
|
|
673
739
|
message: "Polling too fast, slowing down..."
|
|
674
740
|
};
|
|
675
741
|
}
|
|
676
742
|
if (errorData.error === "expired_token") {
|
|
677
|
-
|
|
743
|
+
logger6.warn("Device code expired");
|
|
678
744
|
return {
|
|
679
745
|
status: "expired" /* Expired */,
|
|
680
746
|
message: "The authorization code has expired. Please start again."
|
|
681
747
|
};
|
|
682
748
|
}
|
|
683
749
|
if (errorData.error === "access_denied") {
|
|
684
|
-
|
|
750
|
+
logger6.warn("Access denied");
|
|
685
751
|
return {
|
|
686
752
|
status: "error" /* Error */,
|
|
687
753
|
message: "Access was denied by the user.",
|
|
688
754
|
error: errorData.error_description ?? errorData.error
|
|
689
755
|
};
|
|
690
756
|
}
|
|
691
|
-
|
|
757
|
+
logger6.error("Unexpected error during polling", { error: errorData });
|
|
692
758
|
return {
|
|
693
759
|
status: "error" /* Error */,
|
|
694
760
|
message: errorData.error_description ?? errorData.error,
|
|
@@ -696,7 +762,7 @@ var AuthService = class {
|
|
|
696
762
|
};
|
|
697
763
|
} catch (error) {
|
|
698
764
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
699
|
-
|
|
765
|
+
logger6.error("Poll request failed", { error: errorMessage });
|
|
700
766
|
return {
|
|
701
767
|
status: "error" /* Error */,
|
|
702
768
|
message: `Poll request failed: ${errorMessage}`,
|
|
@@ -708,11 +774,11 @@ var AuthService = class {
|
|
|
708
774
|
* Poll for device code authorization until completion or timeout.
|
|
709
775
|
*/
|
|
710
776
|
async waitForDeviceCodeAuthorization(params) {
|
|
711
|
-
const
|
|
777
|
+
const logger6 = getLogger();
|
|
712
778
|
const timeoutMs = params.timeoutMs ?? DEFAULT_LOGIN_WAIT_TIMEOUT_MS;
|
|
713
779
|
const pollIntervalMs = Math.max(params.intervalSeconds, 1) * 1e3;
|
|
714
780
|
const startedAt = Date.now();
|
|
715
|
-
|
|
781
|
+
logger6.info("Waiting for device code authorization", {
|
|
716
782
|
timeoutMs,
|
|
717
783
|
pollIntervalMs
|
|
718
784
|
});
|
|
@@ -736,7 +802,7 @@ var AuthService = class {
|
|
|
736
802
|
* Get user info from Auth0.
|
|
737
803
|
*/
|
|
738
804
|
async getUserInfo(accessToken) {
|
|
739
|
-
const
|
|
805
|
+
const logger6 = getLogger();
|
|
740
806
|
const config = getConfig();
|
|
741
807
|
const { domain } = config.localQa.auth0;
|
|
742
808
|
const url = `https://${domain}/userinfo`;
|
|
@@ -748,13 +814,13 @@ var AuthService = class {
|
|
|
748
814
|
}
|
|
749
815
|
});
|
|
750
816
|
if (!response.ok) {
|
|
751
|
-
|
|
817
|
+
logger6.warn("Failed to get user info", { status: response.status });
|
|
752
818
|
return {};
|
|
753
819
|
}
|
|
754
820
|
const data = await response.json();
|
|
755
821
|
return data;
|
|
756
822
|
} catch (error) {
|
|
757
|
-
|
|
823
|
+
logger6.warn("User info request failed", {
|
|
758
824
|
error: error instanceof Error ? error.message : String(error)
|
|
759
825
|
});
|
|
760
826
|
return {};
|
|
@@ -765,7 +831,7 @@ var AuthService = class {
|
|
|
765
831
|
*/
|
|
766
832
|
async storeAuth(params) {
|
|
767
833
|
const { tokenResponse, email, userId } = params;
|
|
768
|
-
const
|
|
834
|
+
const logger6 = getLogger();
|
|
769
835
|
const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1e3).toISOString();
|
|
770
836
|
const storedAuth = {
|
|
771
837
|
accessToken: tokenResponse.accessToken,
|
|
@@ -782,13 +848,13 @@ var AuthService = class {
|
|
|
782
848
|
encoding: "utf-8",
|
|
783
849
|
mode: 384
|
|
784
850
|
});
|
|
785
|
-
|
|
851
|
+
logger6.info("Auth stored successfully", { email, expiresAt });
|
|
786
852
|
}
|
|
787
853
|
/**
|
|
788
854
|
* Load stored authentication.
|
|
789
855
|
*/
|
|
790
856
|
loadStoredAuth() {
|
|
791
|
-
const
|
|
857
|
+
const logger6 = getLogger();
|
|
792
858
|
if (!fs3.existsSync(this.authFilePath)) {
|
|
793
859
|
return null;
|
|
794
860
|
}
|
|
@@ -796,7 +862,7 @@ var AuthService = class {
|
|
|
796
862
|
const content = fs3.readFileSync(this.authFilePath, "utf-8");
|
|
797
863
|
return JSON.parse(content);
|
|
798
864
|
} catch (error) {
|
|
799
|
-
|
|
865
|
+
logger6.error("Failed to load stored auth", {
|
|
800
866
|
error: error instanceof Error ? error.message : String(error)
|
|
801
867
|
});
|
|
802
868
|
return null;
|
|
@@ -837,15 +903,15 @@ var AuthService = class {
|
|
|
837
903
|
* @returns New access token or null if refresh failed.
|
|
838
904
|
*/
|
|
839
905
|
async refreshAccessToken() {
|
|
840
|
-
const
|
|
906
|
+
const logger6 = getLogger();
|
|
841
907
|
const storedAuth = this.loadStoredAuth();
|
|
842
908
|
if (!storedAuth?.refreshToken) {
|
|
843
|
-
|
|
909
|
+
logger6.debug("No refresh token available");
|
|
844
910
|
return null;
|
|
845
911
|
}
|
|
846
912
|
const config = getConfig();
|
|
847
913
|
const { domain, clientId } = config.localQa.auth0;
|
|
848
|
-
|
|
914
|
+
logger6.info("Refreshing access token");
|
|
849
915
|
const url = `https://${domain}/oauth/token`;
|
|
850
916
|
const body = new URLSearchParams({
|
|
851
917
|
grant_type: "refresh_token",
|
|
@@ -862,7 +928,7 @@ var AuthService = class {
|
|
|
862
928
|
});
|
|
863
929
|
if (!response.ok) {
|
|
864
930
|
const errorText = await response.text();
|
|
865
|
-
|
|
931
|
+
logger6.error("Token refresh failed", {
|
|
866
932
|
status: response.status,
|
|
867
933
|
error: errorText
|
|
868
934
|
});
|
|
@@ -885,10 +951,10 @@ var AuthService = class {
|
|
|
885
951
|
encoding: "utf-8",
|
|
886
952
|
mode: 384
|
|
887
953
|
});
|
|
888
|
-
|
|
954
|
+
logger6.info("Access token refreshed", { expiresAt: newExpiresAt });
|
|
889
955
|
return tokenData.access_token;
|
|
890
956
|
} catch (error) {
|
|
891
|
-
|
|
957
|
+
logger6.error("Token refresh request failed", {
|
|
892
958
|
error: error instanceof Error ? error.message : String(error)
|
|
893
959
|
});
|
|
894
960
|
return null;
|
|
@@ -900,38 +966,49 @@ var AuthService = class {
|
|
|
900
966
|
* @returns Valid access token or null if not authenticated or refresh failed.
|
|
901
967
|
*/
|
|
902
968
|
async getValidAccessToken() {
|
|
903
|
-
const
|
|
969
|
+
const logger6 = getLogger();
|
|
904
970
|
const storedAuth = this.loadStoredAuth();
|
|
905
971
|
if (!storedAuth) {
|
|
906
|
-
|
|
972
|
+
logger6.debug("No stored auth, cannot get valid token");
|
|
907
973
|
return null;
|
|
908
974
|
}
|
|
909
|
-
|
|
975
|
+
const now = /* @__PURE__ */ new Date();
|
|
976
|
+
const expiresAt = new Date(storedAuth.expiresAt);
|
|
977
|
+
const isStrictlyExpired = now >= expiresAt;
|
|
978
|
+
if (!isStrictlyExpired && !this.isAccessTokenExpired()) {
|
|
910
979
|
return storedAuth.accessToken;
|
|
911
980
|
}
|
|
912
|
-
|
|
981
|
+
if (!isStrictlyExpired) {
|
|
982
|
+
logger6.debug("Access token in buffer zone, attempting proactive refresh");
|
|
983
|
+
} else {
|
|
984
|
+
logger6.info("Access token expired, attempting refresh");
|
|
985
|
+
}
|
|
913
986
|
const refreshedToken = await this.refreshAccessToken();
|
|
914
987
|
if (refreshedToken) {
|
|
915
988
|
return refreshedToken;
|
|
916
989
|
}
|
|
917
|
-
|
|
990
|
+
if (!isStrictlyExpired) {
|
|
991
|
+
logger6.warn("Token refresh failed, but token not yet expired - using existing token");
|
|
992
|
+
return storedAuth.accessToken;
|
|
993
|
+
}
|
|
994
|
+
logger6.warn("Token refresh failed and token is expired, user needs to re-authenticate");
|
|
918
995
|
return null;
|
|
919
996
|
}
|
|
920
997
|
/**
|
|
921
998
|
* Clear stored authentication (logout).
|
|
922
999
|
*/
|
|
923
1000
|
logout() {
|
|
924
|
-
const
|
|
1001
|
+
const logger6 = getLogger();
|
|
925
1002
|
if (!fs3.existsSync(this.authFilePath)) {
|
|
926
|
-
|
|
1003
|
+
logger6.debug("No auth to clear");
|
|
927
1004
|
return false;
|
|
928
1005
|
}
|
|
929
1006
|
try {
|
|
930
1007
|
fs3.unlinkSync(this.authFilePath);
|
|
931
|
-
|
|
1008
|
+
logger6.info("Auth cleared successfully");
|
|
932
1009
|
return true;
|
|
933
1010
|
} catch (error) {
|
|
934
|
-
|
|
1011
|
+
logger6.error("Failed to clear auth", {
|
|
935
1012
|
error: error instanceof Error ? error.message : String(error)
|
|
936
1013
|
});
|
|
937
1014
|
return false;
|
|
@@ -947,9 +1024,9 @@ function resetAuthService() {
|
|
|
947
1024
|
serviceInstance = null;
|
|
948
1025
|
}
|
|
949
1026
|
function sleep(params) {
|
|
950
|
-
return new Promise((
|
|
1027
|
+
return new Promise((resolve3) => {
|
|
951
1028
|
setTimeout(() => {
|
|
952
|
-
|
|
1029
|
+
resolve3();
|
|
953
1030
|
}, params.durationMs);
|
|
954
1031
|
});
|
|
955
1032
|
}
|
|
@@ -971,14 +1048,14 @@ var StorageService = class {
|
|
|
971
1048
|
* Ensure the base directories exist.
|
|
972
1049
|
*/
|
|
973
1050
|
ensureDirectories() {
|
|
974
|
-
const
|
|
1051
|
+
const logger6 = getLogger();
|
|
975
1052
|
if (!fs3.existsSync(this.dataDir)) {
|
|
976
1053
|
fs3.mkdirSync(this.dataDir, { recursive: true });
|
|
977
|
-
|
|
1054
|
+
logger6.info("Created data directory", { path: this.dataDir });
|
|
978
1055
|
}
|
|
979
1056
|
if (!fs3.existsSync(this.sessionsDir)) {
|
|
980
1057
|
fs3.mkdirSync(this.sessionsDir, { recursive: true });
|
|
981
|
-
|
|
1058
|
+
logger6.info("Created sessions directory", { path: this.sessionsDir });
|
|
982
1059
|
}
|
|
983
1060
|
}
|
|
984
1061
|
/**
|
|
@@ -987,14 +1064,14 @@ var StorageService = class {
|
|
|
987
1064
|
* @returns Path to the session directory.
|
|
988
1065
|
*/
|
|
989
1066
|
createSessionDirectory(sessionId) {
|
|
990
|
-
const
|
|
1067
|
+
const logger6 = getLogger();
|
|
991
1068
|
this.ensureDirectories();
|
|
992
1069
|
const sessionDir = path.join(this.sessionsDir, sessionId);
|
|
993
1070
|
if (!fs3.existsSync(sessionDir)) {
|
|
994
1071
|
fs3.mkdirSync(sessionDir, { recursive: true });
|
|
995
1072
|
fs3.mkdirSync(path.join(sessionDir, "screenshots"), { recursive: true });
|
|
996
1073
|
fs3.mkdirSync(path.join(sessionDir, "logs"), { recursive: true });
|
|
997
|
-
|
|
1074
|
+
logger6.info("Created session directory", { sessionId, path: sessionDir });
|
|
998
1075
|
}
|
|
999
1076
|
return sessionDir;
|
|
1000
1077
|
}
|
|
@@ -1003,11 +1080,11 @@ var StorageService = class {
|
|
|
1003
1080
|
* @param metadata - Session metadata to save.
|
|
1004
1081
|
*/
|
|
1005
1082
|
saveSessionMetadata(metadata) {
|
|
1006
|
-
const
|
|
1083
|
+
const logger6 = getLogger();
|
|
1007
1084
|
const sessionDir = this.createSessionDirectory(metadata.sessionId);
|
|
1008
1085
|
const metadataPath = path.join(sessionDir, "metadata.json");
|
|
1009
1086
|
fs3.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
1010
|
-
|
|
1087
|
+
logger6.debug("Saved session metadata", { sessionId: metadata.sessionId });
|
|
1011
1088
|
}
|
|
1012
1089
|
/**
|
|
1013
1090
|
* Load session metadata.
|
|
@@ -1015,7 +1092,7 @@ var StorageService = class {
|
|
|
1015
1092
|
* @returns Session metadata, or null if not found.
|
|
1016
1093
|
*/
|
|
1017
1094
|
loadSessionMetadata(sessionId) {
|
|
1018
|
-
const
|
|
1095
|
+
const logger6 = getLogger();
|
|
1019
1096
|
const metadataPath = path.join(this.sessionsDir, sessionId, "metadata.json");
|
|
1020
1097
|
if (!fs3.existsSync(metadataPath)) {
|
|
1021
1098
|
return null;
|
|
@@ -1024,7 +1101,7 @@ var StorageService = class {
|
|
|
1024
1101
|
const content = fs3.readFileSync(metadataPath, "utf-8");
|
|
1025
1102
|
return JSON.parse(content);
|
|
1026
1103
|
} catch (error) {
|
|
1027
|
-
|
|
1104
|
+
logger6.error("Failed to load session metadata", {
|
|
1028
1105
|
sessionId,
|
|
1029
1106
|
error: error instanceof Error ? error.message : String(error)
|
|
1030
1107
|
});
|
|
@@ -1065,14 +1142,14 @@ var StorageService = class {
|
|
|
1065
1142
|
* @param sessionId - Session ID to set as current.
|
|
1066
1143
|
*/
|
|
1067
1144
|
setCurrentSession(sessionId) {
|
|
1068
|
-
const
|
|
1145
|
+
const logger6 = getLogger();
|
|
1069
1146
|
const currentPath = path.join(this.sessionsDir, "current");
|
|
1070
1147
|
const targetPath = path.join(this.sessionsDir, sessionId);
|
|
1071
1148
|
if (fs3.existsSync(currentPath)) {
|
|
1072
1149
|
fs3.unlinkSync(currentPath);
|
|
1073
1150
|
}
|
|
1074
1151
|
fs3.symlinkSync(targetPath, currentPath);
|
|
1075
|
-
|
|
1152
|
+
logger6.info("Set current session", { sessionId });
|
|
1076
1153
|
}
|
|
1077
1154
|
/**
|
|
1078
1155
|
* Save a screenshot to the session directory.
|
|
@@ -1080,11 +1157,11 @@ var StorageService = class {
|
|
|
1080
1157
|
*/
|
|
1081
1158
|
saveScreenshot(params) {
|
|
1082
1159
|
const { sessionId, filename, data } = params;
|
|
1083
|
-
const
|
|
1160
|
+
const logger6 = getLogger();
|
|
1084
1161
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1085
1162
|
const screenshotPath = path.join(sessionDir, "screenshots", filename);
|
|
1086
1163
|
fs3.writeFileSync(screenshotPath, data);
|
|
1087
|
-
|
|
1164
|
+
logger6.debug("Saved screenshot", { sessionId, filename });
|
|
1088
1165
|
return screenshotPath;
|
|
1089
1166
|
}
|
|
1090
1167
|
/**
|
|
@@ -1093,11 +1170,11 @@ var StorageService = class {
|
|
|
1093
1170
|
*/
|
|
1094
1171
|
appendToResults(params) {
|
|
1095
1172
|
const { sessionId, content } = params;
|
|
1096
|
-
const
|
|
1173
|
+
const logger6 = getLogger();
|
|
1097
1174
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1098
1175
|
const resultsPath = path.join(sessionDir, "results.md");
|
|
1099
1176
|
fs3.appendFileSync(resultsPath, content + "\n", "utf-8");
|
|
1100
|
-
|
|
1177
|
+
logger6.debug("Appended to results", { sessionId });
|
|
1101
1178
|
}
|
|
1102
1179
|
/**
|
|
1103
1180
|
* Get the results markdown content.
|
|
@@ -1132,7 +1209,7 @@ var StorageService = class {
|
|
|
1132
1209
|
*/
|
|
1133
1210
|
createSession(params) {
|
|
1134
1211
|
const { sessionId, targetUrl, testInstructions } = params;
|
|
1135
|
-
const
|
|
1212
|
+
const logger6 = getLogger();
|
|
1136
1213
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1137
1214
|
const metadata = {
|
|
1138
1215
|
sessionId,
|
|
@@ -1144,7 +1221,7 @@ var StorageService = class {
|
|
|
1144
1221
|
};
|
|
1145
1222
|
this.saveSessionMetadata(metadata);
|
|
1146
1223
|
this.setCurrentSession(sessionId);
|
|
1147
|
-
|
|
1224
|
+
logger6.info("Created session", { sessionId, targetUrl });
|
|
1148
1225
|
return sessionDir;
|
|
1149
1226
|
}
|
|
1150
1227
|
/**
|
|
@@ -1153,10 +1230,10 @@ var StorageService = class {
|
|
|
1153
1230
|
*/
|
|
1154
1231
|
updateSessionStatus(params) {
|
|
1155
1232
|
const { sessionId, status } = params;
|
|
1156
|
-
const
|
|
1233
|
+
const logger6 = getLogger();
|
|
1157
1234
|
const metadata = this.loadSessionMetadata(sessionId);
|
|
1158
1235
|
if (!metadata) {
|
|
1159
|
-
|
|
1236
|
+
logger6.warn("Session not found for status update", { sessionId });
|
|
1160
1237
|
return;
|
|
1161
1238
|
}
|
|
1162
1239
|
metadata.status = status;
|
|
@@ -1164,7 +1241,7 @@ var StorageService = class {
|
|
|
1164
1241
|
metadata.endTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1165
1242
|
}
|
|
1166
1243
|
this.saveSessionMetadata(metadata);
|
|
1167
|
-
|
|
1244
|
+
logger6.debug("Updated session status", { sessionId, status });
|
|
1168
1245
|
}
|
|
1169
1246
|
/**
|
|
1170
1247
|
* Initialize the results.md file with a header.
|
|
@@ -1172,7 +1249,7 @@ var StorageService = class {
|
|
|
1172
1249
|
*/
|
|
1173
1250
|
initializeResults(params) {
|
|
1174
1251
|
const { sessionId, targetUrl, testInstructions } = params;
|
|
1175
|
-
const
|
|
1252
|
+
const logger6 = getLogger();
|
|
1176
1253
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1177
1254
|
const resultsPath = path.join(sessionDir, "results.md");
|
|
1178
1255
|
const header = [
|
|
@@ -1188,7 +1265,7 @@ var StorageService = class {
|
|
|
1188
1265
|
""
|
|
1189
1266
|
].join("\n");
|
|
1190
1267
|
fs3.writeFileSync(resultsPath, header, "utf-8");
|
|
1191
|
-
|
|
1268
|
+
logger6.debug("Initialized results.md", { sessionId });
|
|
1192
1269
|
}
|
|
1193
1270
|
/**
|
|
1194
1271
|
* Append a test step to the results.md file.
|
|
@@ -1196,7 +1273,7 @@ var StorageService = class {
|
|
|
1196
1273
|
*/
|
|
1197
1274
|
appendStepToResults(params) {
|
|
1198
1275
|
const { sessionId, step } = params;
|
|
1199
|
-
const
|
|
1276
|
+
const logger6 = getLogger();
|
|
1200
1277
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1201
1278
|
const resultsPath = path.join(sessionDir, "results.md");
|
|
1202
1279
|
const statusIcon = step.success ? "\u2713" : "\u2717";
|
|
@@ -1209,7 +1286,7 @@ var StorageService = class {
|
|
|
1209
1286
|
""
|
|
1210
1287
|
].filter(Boolean).join("\n");
|
|
1211
1288
|
fs3.appendFileSync(resultsPath, stepContent + "\n", "utf-8");
|
|
1212
|
-
|
|
1289
|
+
logger6.debug("Appended step to results", { sessionId, stepNumber: step.stepNumber });
|
|
1213
1290
|
const metadata = this.loadSessionMetadata(sessionId);
|
|
1214
1291
|
if (metadata) {
|
|
1215
1292
|
metadata.stepsCount = (metadata.stepsCount ?? 0) + 1;
|
|
@@ -1222,11 +1299,11 @@ var StorageService = class {
|
|
|
1222
1299
|
*/
|
|
1223
1300
|
finalizeResults(params) {
|
|
1224
1301
|
const { sessionId, status, summary } = params;
|
|
1225
|
-
const
|
|
1302
|
+
const logger6 = getLogger();
|
|
1226
1303
|
const sessionDir = path.join(this.sessionsDir, sessionId);
|
|
1227
1304
|
const resultsPath = path.join(sessionDir, "results.md");
|
|
1228
1305
|
if (!fs3.existsSync(resultsPath)) {
|
|
1229
|
-
|
|
1306
|
+
logger6.warn("Results file not found for finalization", { sessionId });
|
|
1230
1307
|
return;
|
|
1231
1308
|
}
|
|
1232
1309
|
const metadata = this.loadSessionMetadata(sessionId);
|
|
@@ -1249,7 +1326,7 @@ var StorageService = class {
|
|
|
1249
1326
|
summary ? summary : ""
|
|
1250
1327
|
].join("\n");
|
|
1251
1328
|
fs3.appendFileSync(resultsPath, footer, "utf-8");
|
|
1252
|
-
|
|
1329
|
+
logger6.debug("Finalized results.md", { sessionId, status });
|
|
1253
1330
|
if (metadata) {
|
|
1254
1331
|
metadata.durationMs = durationMs;
|
|
1255
1332
|
metadata.endTime = endTime.toISOString();
|
|
@@ -1291,7 +1368,7 @@ var StorageService = class {
|
|
|
1291
1368
|
*/
|
|
1292
1369
|
cleanupOldSessions(params) {
|
|
1293
1370
|
const maxAgeDays = params?.maxAgeDays ?? DEFAULT_SESSION_MAX_AGE_DAYS;
|
|
1294
|
-
const
|
|
1371
|
+
const logger6 = getLogger();
|
|
1295
1372
|
const cutoffDate = /* @__PURE__ */ new Date();
|
|
1296
1373
|
cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
|
|
1297
1374
|
const sessionIds = this.listSessions();
|
|
@@ -1310,12 +1387,12 @@ var StorageService = class {
|
|
|
1310
1387
|
try {
|
|
1311
1388
|
fs3.rmSync(sessionDir, { recursive: true, force: true });
|
|
1312
1389
|
deletedCount++;
|
|
1313
|
-
|
|
1390
|
+
logger6.info("Deleted old session", {
|
|
1314
1391
|
sessionId,
|
|
1315
1392
|
age: Math.floor((Date.now() - sessionDate.getTime()) / (1e3 * 60 * 60 * 24))
|
|
1316
1393
|
});
|
|
1317
1394
|
} catch (error) {
|
|
1318
|
-
|
|
1395
|
+
logger6.error("Failed to delete session", {
|
|
1319
1396
|
sessionId,
|
|
1320
1397
|
error: error instanceof Error ? error.message : String(error)
|
|
1321
1398
|
});
|
|
@@ -1323,7 +1400,7 @@ var StorageService = class {
|
|
|
1323
1400
|
}
|
|
1324
1401
|
}
|
|
1325
1402
|
if (deletedCount > 0) {
|
|
1326
|
-
|
|
1403
|
+
logger6.info("Session cleanup completed", {
|
|
1327
1404
|
deletedCount,
|
|
1328
1405
|
maxAgeDays
|
|
1329
1406
|
});
|
|
@@ -1344,10 +1421,10 @@ var StorageService = class {
|
|
|
1344
1421
|
* @returns Whether deletion succeeded.
|
|
1345
1422
|
*/
|
|
1346
1423
|
deleteSession(sessionId) {
|
|
1347
|
-
const
|
|
1424
|
+
const logger6 = getLogger();
|
|
1348
1425
|
const sessionDir = path.join(this.sessionsDir, sessionId);
|
|
1349
1426
|
if (!fs3.existsSync(sessionDir)) {
|
|
1350
|
-
|
|
1427
|
+
logger6.warn("Session not found for deletion", { sessionId });
|
|
1351
1428
|
return false;
|
|
1352
1429
|
}
|
|
1353
1430
|
try {
|
|
@@ -1359,10 +1436,10 @@ var StorageService = class {
|
|
|
1359
1436
|
}
|
|
1360
1437
|
}
|
|
1361
1438
|
fs3.rmSync(sessionDir, { recursive: true, force: true });
|
|
1362
|
-
|
|
1439
|
+
logger6.info("Deleted session", { sessionId });
|
|
1363
1440
|
return true;
|
|
1364
1441
|
} catch (error) {
|
|
1365
|
-
|
|
1442
|
+
logger6.error("Failed to delete session", {
|
|
1366
1443
|
sessionId,
|
|
1367
1444
|
error: error instanceof Error ? error.message : String(error)
|
|
1368
1445
|
});
|
|
@@ -1458,7 +1535,11 @@ var RunResultStorageService = class {
|
|
|
1458
1535
|
runType: params.runType,
|
|
1459
1536
|
status: "pending",
|
|
1460
1537
|
cloudTestCaseId: params.cloudTestCaseId,
|
|
1538
|
+
projectId: params.projectId,
|
|
1539
|
+
useCaseId: params.useCaseId,
|
|
1461
1540
|
localUrl: params.localUrl,
|
|
1541
|
+
productionUrl: params.productionUrl,
|
|
1542
|
+
localExecutionContext: params.localExecutionContext,
|
|
1462
1543
|
createdAt: now,
|
|
1463
1544
|
updatedAt: now
|
|
1464
1545
|
};
|
|
@@ -1572,6 +1653,7 @@ function getRunResultStorageService() {
|
|
|
1572
1653
|
function resetRunResultStorageService() {
|
|
1573
1654
|
instance = null;
|
|
1574
1655
|
}
|
|
1656
|
+
var logger3 = getLogger();
|
|
1575
1657
|
var activeProcesses = /* @__PURE__ */ new Map();
|
|
1576
1658
|
function getAuthenticatedUserId() {
|
|
1577
1659
|
const authService = getAuthService();
|
|
@@ -1584,6 +1666,44 @@ function getAuthenticatedUserId() {
|
|
|
1584
1666
|
}
|
|
1585
1667
|
return authStatus.userId;
|
|
1586
1668
|
}
|
|
1669
|
+
async function findPackageJsonAsync() {
|
|
1670
|
+
const currentFileUrl = import.meta.url;
|
|
1671
|
+
const currentDir = path.dirname(new URL(currentFileUrl).pathname);
|
|
1672
|
+
const candidatePaths = [
|
|
1673
|
+
path.resolve(currentDir, "../../../package.json"),
|
|
1674
|
+
path.resolve(currentDir, "../package.json"),
|
|
1675
|
+
path.resolve(currentDir, "../../package.json")
|
|
1676
|
+
];
|
|
1677
|
+
for (const candidatePath of candidatePaths) {
|
|
1678
|
+
try {
|
|
1679
|
+
const packageJsonRaw = await fs5.readFile(candidatePath, "utf-8");
|
|
1680
|
+
const packageJson = JSON.parse(packageJsonRaw);
|
|
1681
|
+
if (packageJson.name === "@muggleai/mcp" && packageJson.version && packageJson.muggleConfig?.electronAppVersion) {
|
|
1682
|
+
return {
|
|
1683
|
+
version: packageJson.version,
|
|
1684
|
+
muggleConfig: { electronAppVersion: packageJson.muggleConfig.electronAppVersion }
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
} catch {
|
|
1688
|
+
continue;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
throw new Error(
|
|
1692
|
+
`Could not find @muggleai/mcp package.json with required fields. Searched paths: ${candidatePaths.join(", ")}`
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
async function getLocalExecutionContextBaseAsync(params) {
|
|
1696
|
+
const packageJson = await findPackageJsonAsync();
|
|
1697
|
+
return {
|
|
1698
|
+
originalUrl: params.originalUrl,
|
|
1699
|
+
productionUrl: params.productionUrl,
|
|
1700
|
+
runByUserId: params.runByUserId,
|
|
1701
|
+
machineHostname: os.hostname(),
|
|
1702
|
+
osInfo: `${os.platform()} ${os.release()} ${os.arch()}`,
|
|
1703
|
+
electronAppVersion: packageJson.muggleConfig.electronAppVersion,
|
|
1704
|
+
mcpServerVersion: packageJson.version
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1587
1707
|
function buildStudioAuthContent() {
|
|
1588
1708
|
const authService = getAuthService();
|
|
1589
1709
|
const authStatus = authService.getAuthStatus();
|
|
@@ -1620,15 +1740,259 @@ async function cleanupTempFiles(params) {
|
|
|
1620
1740
|
}
|
|
1621
1741
|
}
|
|
1622
1742
|
}
|
|
1743
|
+
async function moveResultsToArtifacts(params) {
|
|
1744
|
+
const storageService = getStorageService();
|
|
1745
|
+
const sessionsDir = storageService.getSessionsDir();
|
|
1746
|
+
const artifactsDir = path.join(sessionsDir, params.runId);
|
|
1747
|
+
const actionScriptPath = path.join(artifactsDir, "action-script.json");
|
|
1748
|
+
await fs5.mkdir(artifactsDir, { recursive: true });
|
|
1749
|
+
await fs5.copyFile(params.generatedScriptPath, actionScriptPath);
|
|
1750
|
+
try {
|
|
1751
|
+
await fs5.unlink(params.generatedScriptPath);
|
|
1752
|
+
} catch {
|
|
1753
|
+
}
|
|
1754
|
+
return artifactsDir;
|
|
1755
|
+
}
|
|
1756
|
+
async function writeExecutionLogs(params) {
|
|
1757
|
+
const storageService = getStorageService();
|
|
1758
|
+
const sessionsDir = storageService.getSessionsDir();
|
|
1759
|
+
const artifactsDir = path.join(sessionsDir, params.runId);
|
|
1760
|
+
await fs5.mkdir(artifactsDir, { recursive: true });
|
|
1761
|
+
const stdoutPath = path.join(artifactsDir, "stdout.log");
|
|
1762
|
+
const stderrPath = path.join(artifactsDir, "stderr.log");
|
|
1763
|
+
await Promise.all([
|
|
1764
|
+
fs5.writeFile(stdoutPath, params.stdout, "utf-8"),
|
|
1765
|
+
fs5.writeFile(stderrPath, params.stderr, "utf-8")
|
|
1766
|
+
]);
|
|
1767
|
+
logger3.info("Wrote execution logs to artifacts directory", {
|
|
1768
|
+
runId: params.runId,
|
|
1769
|
+
artifactsDir
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
function getElectronAppPathOrThrow() {
|
|
1773
|
+
const config = getConfig();
|
|
1774
|
+
const electronAppPath = config.localQa.electronAppPath;
|
|
1775
|
+
if (!electronAppPath || electronAppPath.trim() === "") {
|
|
1776
|
+
throw new Error(
|
|
1777
|
+
"Electron app binary not found. Run 'muggle-mcp setup' or set ELECTRON_APP_PATH."
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
return electronAppPath;
|
|
1781
|
+
}
|
|
1782
|
+
function getRequiredStringField(params) {
|
|
1783
|
+
const value = params.source[params.fieldName];
|
|
1784
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1785
|
+
throw new Error(
|
|
1786
|
+
`Missing required field '${params.fieldName}' in ${params.sourceLabel}. Please pass the full object from the corresponding muggle-remote-* get tool.`
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
return value;
|
|
1790
|
+
}
|
|
1791
|
+
function buildGenerationActionScript(params) {
|
|
1792
|
+
const testCaseRecord = params.testCase;
|
|
1793
|
+
const projectId = getRequiredStringField({
|
|
1794
|
+
source: testCaseRecord,
|
|
1795
|
+
fieldName: "projectId",
|
|
1796
|
+
sourceLabel: "testCase"
|
|
1797
|
+
});
|
|
1798
|
+
const useCaseId = getRequiredStringField({
|
|
1799
|
+
source: testCaseRecord,
|
|
1800
|
+
fieldName: "useCaseId",
|
|
1801
|
+
sourceLabel: "testCase"
|
|
1802
|
+
});
|
|
1803
|
+
return {
|
|
1804
|
+
actionScriptId: params.localTestScriptId,
|
|
1805
|
+
actionScriptName: `Local Generation ${params.testCase.title}`,
|
|
1806
|
+
actionType: "UserDefined",
|
|
1807
|
+
actionParams: {
|
|
1808
|
+
type: "Test Script Generation Workflow",
|
|
1809
|
+
name: `Local Generation ${params.testCase.title}`,
|
|
1810
|
+
ownerId: params.ownerUserId,
|
|
1811
|
+
projectId,
|
|
1812
|
+
useCaseId,
|
|
1813
|
+
testCaseId: params.testCase.id,
|
|
1814
|
+
testScriptId: params.localTestScriptId,
|
|
1815
|
+
actionScriptId: params.localTestScriptId,
|
|
1816
|
+
workflowRunId: params.runId,
|
|
1817
|
+
url: params.localUrl,
|
|
1818
|
+
sharedTestMemoryId: ""
|
|
1819
|
+
},
|
|
1820
|
+
goal: params.testCase.goal,
|
|
1821
|
+
url: params.localUrl,
|
|
1822
|
+
description: params.testCase.title,
|
|
1823
|
+
precondition: params.testCase.precondition ?? "",
|
|
1824
|
+
expectedResult: params.testCase.expectedResult,
|
|
1825
|
+
steps: [],
|
|
1826
|
+
ownerId: params.ownerUserId,
|
|
1827
|
+
createdAt: Date.now(),
|
|
1828
|
+
isRemoteScript: false,
|
|
1829
|
+
status: "active"
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
function buildReplayActionScript(params) {
|
|
1833
|
+
const testScriptRecord = params.testScript;
|
|
1834
|
+
const projectId = getRequiredStringField({
|
|
1835
|
+
source: testScriptRecord,
|
|
1836
|
+
fieldName: "projectId",
|
|
1837
|
+
sourceLabel: "testScript"
|
|
1838
|
+
});
|
|
1839
|
+
const useCaseId = getRequiredStringField({
|
|
1840
|
+
source: testScriptRecord,
|
|
1841
|
+
fieldName: "useCaseId",
|
|
1842
|
+
sourceLabel: "testScript"
|
|
1843
|
+
});
|
|
1844
|
+
const rewrittenActionScript = rewriteActionScriptUrls({
|
|
1845
|
+
actionScript: params.testScript.actionScript,
|
|
1846
|
+
originalUrl: params.testScript.url,
|
|
1847
|
+
localUrl: params.localUrl
|
|
1848
|
+
});
|
|
1849
|
+
return {
|
|
1850
|
+
actionScriptId: params.testScript.id,
|
|
1851
|
+
actionScriptName: params.testScript.name,
|
|
1852
|
+
actionType: "UserDefined",
|
|
1853
|
+
actionParams: {
|
|
1854
|
+
type: "Test Script Replay Workflow",
|
|
1855
|
+
name: params.testScript.name,
|
|
1856
|
+
ownerId: params.ownerUserId,
|
|
1857
|
+
projectId,
|
|
1858
|
+
useCaseId,
|
|
1859
|
+
testCaseId: params.testScript.testCaseId,
|
|
1860
|
+
testScriptId: params.testScript.id,
|
|
1861
|
+
workflowRunId: params.runId,
|
|
1862
|
+
sharedTestMemoryId: ""
|
|
1863
|
+
},
|
|
1864
|
+
goal: params.testScript.name,
|
|
1865
|
+
url: params.localUrl,
|
|
1866
|
+
description: params.testScript.name,
|
|
1867
|
+
precondition: "",
|
|
1868
|
+
expectedResult: "Replay completes without critical failures.",
|
|
1869
|
+
steps: rewrittenActionScript,
|
|
1870
|
+
ownerId: params.ownerUserId,
|
|
1871
|
+
createdAt: Date.now(),
|
|
1872
|
+
isRemoteScript: true,
|
|
1873
|
+
status: "active"
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
async function executeElectronAppAsync(params) {
|
|
1877
|
+
const mode = params.runType === "generation" ? "explore" : "engine";
|
|
1878
|
+
const electronAppPath = getElectronAppPathOrThrow();
|
|
1879
|
+
const spawnArgs = [mode, params.scriptFilePath, "", params.authFilePath];
|
|
1880
|
+
logger3.info("Spawning electron-app for local execution", {
|
|
1881
|
+
runId: params.runId,
|
|
1882
|
+
mode,
|
|
1883
|
+
electronAppPath,
|
|
1884
|
+
spawnArgs
|
|
1885
|
+
});
|
|
1886
|
+
const electronEnv = { ...process.env };
|
|
1887
|
+
delete electronEnv.ELECTRON_RUN_AS_NODE;
|
|
1888
|
+
const child = spawn(electronAppPath, spawnArgs, {
|
|
1889
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1890
|
+
env: electronEnv,
|
|
1891
|
+
cwd: path.dirname(electronAppPath)
|
|
1892
|
+
});
|
|
1893
|
+
const processInfo = {
|
|
1894
|
+
runId: params.runId,
|
|
1895
|
+
process: child,
|
|
1896
|
+
status: "running",
|
|
1897
|
+
startedAt: Date.now(),
|
|
1898
|
+
capturedStdout: "",
|
|
1899
|
+
capturedStderr: ""
|
|
1900
|
+
};
|
|
1901
|
+
activeProcesses.set(params.runId, processInfo);
|
|
1902
|
+
return await new Promise((resolve3, reject) => {
|
|
1903
|
+
let settled = false;
|
|
1904
|
+
const finalize = (result) => {
|
|
1905
|
+
if (settled) {
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
settled = true;
|
|
1909
|
+
if (processInfo.timeoutTimer) {
|
|
1910
|
+
clearTimeout(processInfo.timeoutTimer);
|
|
1911
|
+
}
|
|
1912
|
+
activeProcesses.delete(params.runId);
|
|
1913
|
+
if (result.ok) {
|
|
1914
|
+
resolve3(result.payload);
|
|
1915
|
+
} else {
|
|
1916
|
+
reject(result.payload);
|
|
1917
|
+
}
|
|
1918
|
+
};
|
|
1919
|
+
processInfo.timeoutTimer = setTimeout(() => {
|
|
1920
|
+
processInfo.process.kill("SIGTERM");
|
|
1921
|
+
finalize({
|
|
1922
|
+
ok: false,
|
|
1923
|
+
payload: new Error(
|
|
1924
|
+
`Electron execution timed out after ${params.timeoutMs}ms.
|
|
1925
|
+
STDOUT:
|
|
1926
|
+
${processInfo.capturedStdout}
|
|
1927
|
+
STDERR:
|
|
1928
|
+
${processInfo.capturedStderr}`
|
|
1929
|
+
)
|
|
1930
|
+
});
|
|
1931
|
+
}, params.timeoutMs);
|
|
1932
|
+
if (child.stdout) {
|
|
1933
|
+
child.stdout.on("data", (chunk) => {
|
|
1934
|
+
processInfo.capturedStdout += chunk.toString();
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
if (child.stderr) {
|
|
1938
|
+
child.stderr.on("data", (chunk) => {
|
|
1939
|
+
processInfo.capturedStderr += chunk.toString();
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
child.on("error", (error) => {
|
|
1943
|
+
finalize({
|
|
1944
|
+
ok: false,
|
|
1945
|
+
payload: new Error(`Failed to start electron-app: ${error.message}`)
|
|
1946
|
+
});
|
|
1947
|
+
});
|
|
1948
|
+
child.on("close", (code, signal) => {
|
|
1949
|
+
const exitCode = code ?? -1;
|
|
1950
|
+
if (signal) {
|
|
1951
|
+
finalize({
|
|
1952
|
+
ok: false,
|
|
1953
|
+
payload: new Error(
|
|
1954
|
+
`Electron execution terminated by signal ${signal}.
|
|
1955
|
+
STDOUT:
|
|
1956
|
+
${processInfo.capturedStdout}
|
|
1957
|
+
STDERR:
|
|
1958
|
+
${processInfo.capturedStderr}`
|
|
1959
|
+
)
|
|
1960
|
+
});
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
finalize({
|
|
1964
|
+
ok: true,
|
|
1965
|
+
payload: {
|
|
1966
|
+
exitCode,
|
|
1967
|
+
stdout: processInfo.capturedStdout,
|
|
1968
|
+
stderr: processInfo.capturedStderr
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
});
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1623
1974
|
async function executeTestGeneration(params) {
|
|
1624
1975
|
const { testCase, localUrl } = params;
|
|
1625
|
-
|
|
1976
|
+
const timeoutMs = params.timeoutMs ?? 3e5;
|
|
1977
|
+
const userId = getAuthenticatedUserId();
|
|
1626
1978
|
const authContent = buildStudioAuthContent();
|
|
1979
|
+
if (!testCase.url) {
|
|
1980
|
+
throw new Error("Missing required testCase.url for local run upload metadata");
|
|
1981
|
+
}
|
|
1982
|
+
const localExecutionContextBase = await getLocalExecutionContextBaseAsync({
|
|
1983
|
+
runByUserId: userId,
|
|
1984
|
+
originalUrl: localUrl,
|
|
1985
|
+
productionUrl: testCase.url
|
|
1986
|
+
});
|
|
1627
1987
|
const storage = getRunResultStorageService();
|
|
1628
1988
|
const runResult = storage.createRunResult({
|
|
1629
1989
|
runType: "generation",
|
|
1630
1990
|
cloudTestCaseId: testCase.id,
|
|
1631
|
-
|
|
1991
|
+
projectId: testCase.projectId,
|
|
1992
|
+
useCaseId: testCase.useCaseId,
|
|
1993
|
+
localUrl,
|
|
1994
|
+
productionUrl: testCase.url,
|
|
1995
|
+
localExecutionContext: localExecutionContextBase
|
|
1632
1996
|
});
|
|
1633
1997
|
try {
|
|
1634
1998
|
const localTestScript = storage.createTestScript({
|
|
@@ -1637,22 +2001,15 @@ async function executeTestGeneration(params) {
|
|
|
1637
2001
|
cloudTestCaseId: testCase.id,
|
|
1638
2002
|
goal: testCase.goal
|
|
1639
2003
|
});
|
|
1640
|
-
const actionScript = {
|
|
1641
|
-
steps: [
|
|
1642
|
-
{
|
|
1643
|
-
type: "navigate",
|
|
1644
|
-
url: localUrl
|
|
1645
|
-
},
|
|
1646
|
-
{
|
|
1647
|
-
type: "explore",
|
|
1648
|
-
goal: testCase.goal,
|
|
1649
|
-
instructions: testCase.instructions,
|
|
1650
|
-
expectedResult: testCase.expectedResult
|
|
1651
|
-
}
|
|
1652
|
-
]
|
|
1653
|
-
};
|
|
1654
2004
|
const runId = runResult.id;
|
|
1655
2005
|
const startedAt = Date.now();
|
|
2006
|
+
const actionScript = buildGenerationActionScript({
|
|
2007
|
+
testCase,
|
|
2008
|
+
localUrl,
|
|
2009
|
+
runId,
|
|
2010
|
+
localTestScriptId: localTestScript.id,
|
|
2011
|
+
ownerUserId: authContent.userId
|
|
2012
|
+
});
|
|
1656
2013
|
const inputFilePath = await writeTempFile({
|
|
1657
2014
|
filename: `${runId}_input.json`,
|
|
1658
2015
|
data: actionScript
|
|
@@ -1661,27 +2018,99 @@ async function executeTestGeneration(params) {
|
|
|
1661
2018
|
filename: `${runId}_auth.json`,
|
|
1662
2019
|
data: authContent
|
|
1663
2020
|
});
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
2021
|
+
try {
|
|
2022
|
+
const executionResult = await executeElectronAppAsync({
|
|
2023
|
+
runId,
|
|
2024
|
+
runType: "generation",
|
|
2025
|
+
scriptFilePath: inputFilePath,
|
|
2026
|
+
authFilePath,
|
|
2027
|
+
timeoutMs
|
|
2028
|
+
});
|
|
2029
|
+
const completedAt = Date.now();
|
|
2030
|
+
const executionTimeMs = completedAt - startedAt;
|
|
2031
|
+
await writeExecutionLogs({
|
|
2032
|
+
runId,
|
|
2033
|
+
stdout: executionResult.stdout,
|
|
2034
|
+
stderr: executionResult.stderr
|
|
2035
|
+
});
|
|
2036
|
+
if (executionResult.exitCode !== 0) {
|
|
2037
|
+
const failureMessage = `Electron exited with code ${executionResult.exitCode}.
|
|
2038
|
+
STDOUT:
|
|
2039
|
+
${executionResult.stdout}
|
|
2040
|
+
STDERR:
|
|
2041
|
+
${executionResult.stderr}`;
|
|
2042
|
+
storage.updateRunResult(runId, {
|
|
2043
|
+
status: "failed",
|
|
2044
|
+
testScriptId: localTestScript.id,
|
|
2045
|
+
executionTimeMs,
|
|
2046
|
+
errorMessage: failureMessage,
|
|
2047
|
+
localExecutionContext: {
|
|
2048
|
+
...localExecutionContextBase,
|
|
2049
|
+
localExecutionCompletedAt: completedAt
|
|
2050
|
+
}
|
|
2051
|
+
});
|
|
2052
|
+
storage.updateTestScript(localTestScript.id, {
|
|
2053
|
+
status: "failed"
|
|
2054
|
+
});
|
|
2055
|
+
return {
|
|
2056
|
+
id: runId,
|
|
2057
|
+
testScriptId: localTestScript.id,
|
|
2058
|
+
status: "failed",
|
|
2059
|
+
executionTimeMs,
|
|
2060
|
+
errorMessage: failureMessage
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
const generatedScriptPath = path.join(
|
|
2064
|
+
path.dirname(inputFilePath),
|
|
2065
|
+
`gen_${path.basename(inputFilePath)}`
|
|
2066
|
+
);
|
|
2067
|
+
const generatedScriptRaw = await fs5.readFile(generatedScriptPath, "utf-8");
|
|
2068
|
+
const generatedScript = JSON.parse(generatedScriptRaw);
|
|
2069
|
+
const generatedSteps = generatedScript.steps;
|
|
2070
|
+
if (!Array.isArray(generatedSteps)) {
|
|
2071
|
+
throw new Error(
|
|
2072
|
+
`Generated script does not contain a valid 'steps' array. File: ${generatedScriptPath}`
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
storage.updateTestScript(localTestScript.id, {
|
|
2076
|
+
status: "generated",
|
|
2077
|
+
actionScript: generatedSteps
|
|
2078
|
+
});
|
|
2079
|
+
const artifactsDir = await moveResultsToArtifacts({
|
|
2080
|
+
runId,
|
|
2081
|
+
generatedScriptPath
|
|
2082
|
+
});
|
|
2083
|
+
storage.updateRunResult(runId, {
|
|
2084
|
+
status: "passed",
|
|
2085
|
+
testScriptId: localTestScript.id,
|
|
2086
|
+
executionTimeMs,
|
|
2087
|
+
artifactsDir,
|
|
2088
|
+
localExecutionContext: {
|
|
2089
|
+
...localExecutionContextBase,
|
|
2090
|
+
localExecutionCompletedAt: completedAt
|
|
2091
|
+
}
|
|
2092
|
+
});
|
|
2093
|
+
return {
|
|
2094
|
+
id: runId,
|
|
2095
|
+
testScriptId: localTestScript.id,
|
|
2096
|
+
status: "passed",
|
|
2097
|
+
executionTimeMs
|
|
2098
|
+
};
|
|
2099
|
+
} finally {
|
|
2100
|
+
await cleanupTempFiles({
|
|
2101
|
+
filePaths: [inputFilePath, authFilePath]
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
1680
2104
|
} catch (error) {
|
|
1681
2105
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2106
|
+
const completedAt = Date.now();
|
|
1682
2107
|
storage.updateRunResult(runResult.id, {
|
|
1683
2108
|
status: "failed",
|
|
1684
|
-
errorMessage
|
|
2109
|
+
errorMessage,
|
|
2110
|
+
localExecutionContext: {
|
|
2111
|
+
...localExecutionContextBase,
|
|
2112
|
+
localExecutionCompletedAt: completedAt
|
|
2113
|
+
}
|
|
1685
2114
|
});
|
|
1686
2115
|
return {
|
|
1687
2116
|
id: runResult.id,
|
|
@@ -1693,49 +2122,111 @@ async function executeTestGeneration(params) {
|
|
|
1693
2122
|
}
|
|
1694
2123
|
async function executeReplay(params) {
|
|
1695
2124
|
const { testScript, localUrl } = params;
|
|
1696
|
-
|
|
2125
|
+
const timeoutMs = params.timeoutMs ?? 18e4;
|
|
2126
|
+
const userId = getAuthenticatedUserId();
|
|
1697
2127
|
const authContent = buildStudioAuthContent();
|
|
2128
|
+
if (!testScript.url) {
|
|
2129
|
+
throw new Error("Missing required testScript.url for local run upload metadata");
|
|
2130
|
+
}
|
|
2131
|
+
const localExecutionContextBase = await getLocalExecutionContextBaseAsync({
|
|
2132
|
+
runByUserId: userId,
|
|
2133
|
+
originalUrl: localUrl,
|
|
2134
|
+
productionUrl: testScript.url
|
|
2135
|
+
});
|
|
1698
2136
|
const storage = getRunResultStorageService();
|
|
1699
2137
|
const runResult = storage.createRunResult({
|
|
1700
2138
|
runType: "replay",
|
|
1701
2139
|
cloudTestCaseId: testScript.testCaseId,
|
|
1702
|
-
|
|
2140
|
+
projectId: testScript.projectId,
|
|
2141
|
+
useCaseId: testScript.useCaseId,
|
|
2142
|
+
localUrl,
|
|
2143
|
+
productionUrl: testScript.url,
|
|
2144
|
+
localExecutionContext: localExecutionContextBase
|
|
1703
2145
|
});
|
|
1704
2146
|
try {
|
|
1705
2147
|
const runId = runResult.id;
|
|
1706
2148
|
const startedAt = Date.now();
|
|
1707
|
-
const
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
2149
|
+
const actionScript = buildReplayActionScript({
|
|
2150
|
+
testScript,
|
|
2151
|
+
localUrl,
|
|
2152
|
+
runId,
|
|
2153
|
+
ownerUserId: authContent.userId
|
|
1711
2154
|
});
|
|
1712
2155
|
const inputFilePath = await writeTempFile({
|
|
1713
2156
|
filename: `${runId}_input.json`,
|
|
1714
|
-
data:
|
|
2157
|
+
data: actionScript
|
|
1715
2158
|
});
|
|
1716
2159
|
const authFilePath = await writeTempFile({
|
|
1717
2160
|
filename: `${runId}_auth.json`,
|
|
1718
2161
|
data: authContent
|
|
1719
2162
|
});
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
2163
|
+
try {
|
|
2164
|
+
const executionResult = await executeElectronAppAsync({
|
|
2165
|
+
runId,
|
|
2166
|
+
runType: "replay",
|
|
2167
|
+
scriptFilePath: inputFilePath,
|
|
2168
|
+
authFilePath,
|
|
2169
|
+
timeoutMs
|
|
2170
|
+
});
|
|
2171
|
+
const completedAt = Date.now();
|
|
2172
|
+
const executionTimeMs = completedAt - startedAt;
|
|
2173
|
+
await writeExecutionLogs({
|
|
2174
|
+
runId,
|
|
2175
|
+
stdout: executionResult.stdout,
|
|
2176
|
+
stderr: executionResult.stderr
|
|
2177
|
+
});
|
|
2178
|
+
if (executionResult.exitCode !== 0) {
|
|
2179
|
+
const failureMessage = `Electron exited with code ${executionResult.exitCode}.
|
|
2180
|
+
STDOUT:
|
|
2181
|
+
${executionResult.stdout}
|
|
2182
|
+
STDERR:
|
|
2183
|
+
${executionResult.stderr}`;
|
|
2184
|
+
storage.updateRunResult(runId, {
|
|
2185
|
+
status: "failed",
|
|
2186
|
+
executionTimeMs,
|
|
2187
|
+
errorMessage: failureMessage,
|
|
2188
|
+
localExecutionContext: {
|
|
2189
|
+
...localExecutionContextBase,
|
|
2190
|
+
localExecutionCompletedAt: completedAt
|
|
2191
|
+
}
|
|
2192
|
+
});
|
|
2193
|
+
return {
|
|
2194
|
+
id: runId,
|
|
2195
|
+
status: "failed",
|
|
2196
|
+
executionTimeMs,
|
|
2197
|
+
errorMessage: failureMessage
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
const artifactsDir = path.join(getStorageService().getSessionsDir(), runId);
|
|
2201
|
+
storage.updateRunResult(runId, {
|
|
2202
|
+
status: "passed",
|
|
2203
|
+
executionTimeMs,
|
|
2204
|
+
artifactsDir,
|
|
2205
|
+
localExecutionContext: {
|
|
2206
|
+
...localExecutionContextBase,
|
|
2207
|
+
localExecutionCompletedAt: completedAt
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
return {
|
|
2211
|
+
id: runId,
|
|
2212
|
+
status: "passed",
|
|
2213
|
+
executionTimeMs
|
|
2214
|
+
};
|
|
2215
|
+
} finally {
|
|
2216
|
+
await cleanupTempFiles({
|
|
2217
|
+
filePaths: [inputFilePath, authFilePath]
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
1734
2220
|
} catch (error) {
|
|
1735
2221
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2222
|
+
const completedAt = Date.now();
|
|
1736
2223
|
storage.updateRunResult(runResult.id, {
|
|
1737
2224
|
status: "failed",
|
|
1738
|
-
errorMessage
|
|
2225
|
+
errorMessage,
|
|
2226
|
+
localExecutionContext: {
|
|
2227
|
+
...localExecutionContextBase,
|
|
2228
|
+
localExecutionCompletedAt: completedAt
|
|
2229
|
+
}
|
|
1739
2230
|
});
|
|
1740
2231
|
return {
|
|
1741
2232
|
id: runResult.id,
|
|
@@ -1789,52 +2280,52 @@ function ensureDataDir() {
|
|
|
1789
2280
|
}
|
|
1790
2281
|
}
|
|
1791
2282
|
function loadCredentials() {
|
|
1792
|
-
const
|
|
2283
|
+
const logger6 = getLogger();
|
|
1793
2284
|
const credentialsPath = getCredentialsFilePath();
|
|
1794
2285
|
try {
|
|
1795
2286
|
if (!fs3.existsSync(credentialsPath)) {
|
|
1796
|
-
|
|
2287
|
+
logger6.debug("No credentials file found", { path: credentialsPath });
|
|
1797
2288
|
return null;
|
|
1798
2289
|
}
|
|
1799
2290
|
const content = fs3.readFileSync(credentialsPath, "utf-8");
|
|
1800
2291
|
const credentials = JSON.parse(content);
|
|
1801
2292
|
if (!credentials.accessToken || !credentials.expiresAt) {
|
|
1802
|
-
|
|
2293
|
+
logger6.warn("Invalid credentials file - missing required fields");
|
|
1803
2294
|
return null;
|
|
1804
2295
|
}
|
|
1805
2296
|
return credentials;
|
|
1806
2297
|
} catch (error) {
|
|
1807
|
-
|
|
2298
|
+
logger6.warn("Failed to load credentials", {
|
|
1808
2299
|
error: error instanceof Error ? error.message : String(error)
|
|
1809
2300
|
});
|
|
1810
2301
|
return null;
|
|
1811
2302
|
}
|
|
1812
2303
|
}
|
|
1813
2304
|
function saveCredentials(credentials) {
|
|
1814
|
-
const
|
|
2305
|
+
const logger6 = getLogger();
|
|
1815
2306
|
const credentialsPath = getCredentialsFilePath();
|
|
1816
2307
|
try {
|
|
1817
2308
|
ensureDataDir();
|
|
1818
2309
|
const content = JSON.stringify(credentials, null, 2);
|
|
1819
2310
|
fs3.writeFileSync(credentialsPath, content, { mode: 384 });
|
|
1820
|
-
|
|
2311
|
+
logger6.info("Credentials saved", { path: credentialsPath });
|
|
1821
2312
|
} catch (error) {
|
|
1822
|
-
|
|
2313
|
+
logger6.error("Failed to save credentials", {
|
|
1823
2314
|
error: error instanceof Error ? error.message : String(error)
|
|
1824
2315
|
});
|
|
1825
2316
|
throw error;
|
|
1826
2317
|
}
|
|
1827
2318
|
}
|
|
1828
2319
|
function deleteCredentials() {
|
|
1829
|
-
const
|
|
2320
|
+
const logger6 = getLogger();
|
|
1830
2321
|
const credentialsPath = getCredentialsFilePath();
|
|
1831
2322
|
try {
|
|
1832
2323
|
if (fs3.existsSync(credentialsPath)) {
|
|
1833
2324
|
fs3.unlinkSync(credentialsPath);
|
|
1834
|
-
|
|
2325
|
+
logger6.info("Credentials deleted", { path: credentialsPath });
|
|
1835
2326
|
}
|
|
1836
2327
|
} catch (error) {
|
|
1837
|
-
|
|
2328
|
+
logger6.warn("Failed to delete credentials", {
|
|
1838
2329
|
error: error instanceof Error ? error.message : String(error)
|
|
1839
2330
|
});
|
|
1840
2331
|
}
|
|
@@ -1864,7 +2355,7 @@ function getApiKey() {
|
|
|
1864
2355
|
return credentials?.apiKey ?? null;
|
|
1865
2356
|
}
|
|
1866
2357
|
function saveApiKey(params) {
|
|
1867
|
-
const
|
|
2358
|
+
const logger6 = getLogger();
|
|
1868
2359
|
const credentialsPath = getCredentialsFilePath();
|
|
1869
2360
|
try {
|
|
1870
2361
|
ensureDataDir();
|
|
@@ -1876,9 +2367,9 @@ function saveApiKey(params) {
|
|
|
1876
2367
|
};
|
|
1877
2368
|
const content = JSON.stringify(credentials, null, 2);
|
|
1878
2369
|
fs3.writeFileSync(credentialsPath, content, { mode: 384 });
|
|
1879
|
-
|
|
2370
|
+
logger6.info("API key saved", { path: credentialsPath });
|
|
1880
2371
|
} catch (error) {
|
|
1881
|
-
|
|
2372
|
+
logger6.error("Failed to save API key", {
|
|
1882
2373
|
error: error instanceof Error ? error.message : String(error)
|
|
1883
2374
|
});
|
|
1884
2375
|
throw error;
|
|
@@ -1886,11 +2377,11 @@ function saveApiKey(params) {
|
|
|
1886
2377
|
}
|
|
1887
2378
|
|
|
1888
2379
|
// src/shared/auth.ts
|
|
1889
|
-
var
|
|
2380
|
+
var logger4 = getLogger();
|
|
1890
2381
|
async function startDeviceCodeFlow(config) {
|
|
1891
2382
|
const deviceCodeUrl = `https://${config.domain}/oauth/device/code`;
|
|
1892
2383
|
try {
|
|
1893
|
-
|
|
2384
|
+
logger4.info("[Auth] Starting device code flow", {
|
|
1894
2385
|
domain: config.domain,
|
|
1895
2386
|
clientId: config.clientId
|
|
1896
2387
|
});
|
|
@@ -1908,7 +2399,7 @@ async function startDeviceCodeFlow(config) {
|
|
|
1908
2399
|
}
|
|
1909
2400
|
);
|
|
1910
2401
|
const data = response.data;
|
|
1911
|
-
|
|
2402
|
+
logger4.info("[Auth] Device code flow started successfully", {
|
|
1912
2403
|
userCode: data.user_code,
|
|
1913
2404
|
expiresIn: data.expires_in
|
|
1914
2405
|
});
|
|
@@ -1916,9 +2407,9 @@ async function startDeviceCodeFlow(config) {
|
|
|
1916
2407
|
url: data.verification_uri_complete
|
|
1917
2408
|
});
|
|
1918
2409
|
if (browserOpenResult.opened) {
|
|
1919
|
-
|
|
2410
|
+
logger4.info("[Auth] Browser opened for device code login");
|
|
1920
2411
|
} else {
|
|
1921
|
-
|
|
2412
|
+
logger4.warn("[Auth] Failed to open browser", {
|
|
1922
2413
|
error: browserOpenResult.error,
|
|
1923
2414
|
verificationUriComplete: data.verification_uri_complete
|
|
1924
2415
|
});
|
|
@@ -1935,7 +2426,7 @@ async function startDeviceCodeFlow(config) {
|
|
|
1935
2426
|
};
|
|
1936
2427
|
} catch (error) {
|
|
1937
2428
|
if (error instanceof AxiosError) {
|
|
1938
|
-
|
|
2429
|
+
logger4.error("[Auth] Failed to start device code flow", {
|
|
1939
2430
|
status: error.response?.status,
|
|
1940
2431
|
data: error.response?.data
|
|
1941
2432
|
});
|
|
@@ -1962,7 +2453,7 @@ async function pollDeviceCode(config, deviceCode) {
|
|
|
1962
2453
|
}
|
|
1963
2454
|
}
|
|
1964
2455
|
);
|
|
1965
|
-
|
|
2456
|
+
logger4.info("[Auth] Authorization successful");
|
|
1966
2457
|
return {
|
|
1967
2458
|
status: "authorized",
|
|
1968
2459
|
accessToken: response.data.access_token,
|
|
@@ -2001,7 +2492,7 @@ async function pollDeviceCode(config, deviceCode) {
|
|
|
2001
2492
|
errorDescription: data.error_description || "User denied access"
|
|
2002
2493
|
};
|
|
2003
2494
|
}
|
|
2004
|
-
|
|
2495
|
+
logger4.error("[Auth] Unexpected error during poll", {
|
|
2005
2496
|
status: error.response.status,
|
|
2006
2497
|
error: errorCode,
|
|
2007
2498
|
description: data.error_description
|
|
@@ -2017,7 +2508,7 @@ async function createApiKeyWithToken(accessToken, keyName, expiry = "90d") {
|
|
|
2017
2508
|
const config = getConfig();
|
|
2018
2509
|
const apiKeyUrl = `${config.qa.promptServiceBaseUrl}/v1/protected/api-keys`;
|
|
2019
2510
|
try {
|
|
2020
|
-
|
|
2511
|
+
logger4.info("[Auth] Creating API key", {
|
|
2021
2512
|
keyName,
|
|
2022
2513
|
expiry
|
|
2023
2514
|
});
|
|
@@ -2034,13 +2525,13 @@ async function createApiKeyWithToken(accessToken, keyName, expiry = "90d") {
|
|
|
2034
2525
|
}
|
|
2035
2526
|
}
|
|
2036
2527
|
);
|
|
2037
|
-
|
|
2528
|
+
logger4.info("[Auth] API key created successfully", {
|
|
2038
2529
|
keyId: response.data.id
|
|
2039
2530
|
});
|
|
2040
2531
|
return response.data;
|
|
2041
2532
|
} catch (error) {
|
|
2042
2533
|
if (error instanceof AxiosError) {
|
|
2043
|
-
|
|
2534
|
+
logger4.error("[Auth] Failed to create API key", {
|
|
2044
2535
|
status: error.response?.status,
|
|
2045
2536
|
data: error.response?.data
|
|
2046
2537
|
});
|
|
@@ -2079,7 +2570,7 @@ async function performLogin(keyName, keyExpiry = "90d", timeoutMs = 12e4) {
|
|
|
2079
2570
|
userId: storedAuth?.userId
|
|
2080
2571
|
};
|
|
2081
2572
|
if (keyName && storedAuth?.accessToken) {
|
|
2082
|
-
|
|
2573
|
+
logger4.info("[Auth] Creating API key as explicitly requested", {
|
|
2083
2574
|
keyName
|
|
2084
2575
|
});
|
|
2085
2576
|
const apiKeyResult = await createApiKeyWithToken(
|
|
@@ -2127,7 +2618,7 @@ function performLogout() {
|
|
|
2127
2618
|
const authService = getAuthService();
|
|
2128
2619
|
authService.logout();
|
|
2129
2620
|
deleteCredentials();
|
|
2130
|
-
|
|
2621
|
+
logger4.info("[Auth] Logged out successfully");
|
|
2131
2622
|
}
|
|
2132
2623
|
function getCallerCredentials() {
|
|
2133
2624
|
const credentials = getValidCredentials();
|
|
@@ -2203,8 +2694,7 @@ function toolRequiresAuth(toolName) {
|
|
|
2203
2694
|
"muggle-local-workflow-file-list-available",
|
|
2204
2695
|
"muggle-local-workflow-file-get",
|
|
2205
2696
|
"muggle-local-workflow-file-update",
|
|
2206
|
-
"muggle-local-workflow-file-delete"
|
|
2207
|
-
"muggle-local-publish-test-script"
|
|
2697
|
+
"muggle-local-workflow-file-delete"
|
|
2208
2698
|
];
|
|
2209
2699
|
return !noAuthTools.includes(toolName);
|
|
2210
2700
|
}
|
|
@@ -2336,7 +2826,7 @@ async function handleJitAuth(toolName, correlationId) {
|
|
|
2336
2826
|
return { credentials: {}, authTriggered: false };
|
|
2337
2827
|
}
|
|
2338
2828
|
function createUnifiedMcpServer(options) {
|
|
2339
|
-
const
|
|
2829
|
+
const logger6 = getLogger();
|
|
2340
2830
|
const config = getConfig();
|
|
2341
2831
|
const server = new Server(
|
|
2342
2832
|
{
|
|
@@ -2352,7 +2842,7 @@ function createUnifiedMcpServer(options) {
|
|
|
2352
2842
|
);
|
|
2353
2843
|
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
2354
2844
|
const tools = getAllTools();
|
|
2355
|
-
|
|
2845
|
+
logger6.debug("Listing tools", { count: tools.length });
|
|
2356
2846
|
const toolDefinitions = tools.map((tool) => {
|
|
2357
2847
|
const jsonSchema = zodToJsonSchema(tool.inputSchema);
|
|
2358
2848
|
return {
|
|
@@ -2428,8 +2918,8 @@ function createUnifiedMcpServer(options) {
|
|
|
2428
2918
|
errors: error.errors
|
|
2429
2919
|
});
|
|
2430
2920
|
const issueMessages = error.errors.slice(0, 3).map((issue) => {
|
|
2431
|
-
const
|
|
2432
|
-
return
|
|
2921
|
+
const path8 = issue.path.join(".");
|
|
2922
|
+
return path8 ? `'${path8}': ${issue.message}` : issue.message;
|
|
2433
2923
|
});
|
|
2434
2924
|
return {
|
|
2435
2925
|
content: [
|
|
@@ -2463,12 +2953,12 @@ function createUnifiedMcpServer(options) {
|
|
|
2463
2953
|
}
|
|
2464
2954
|
});
|
|
2465
2955
|
server.setRequestHandler(ListResourcesRequestSchema, () => {
|
|
2466
|
-
|
|
2956
|
+
logger6.debug("Listing resources");
|
|
2467
2957
|
return { resources: [] };
|
|
2468
2958
|
});
|
|
2469
2959
|
server.setRequestHandler(ReadResourceRequestSchema, (request) => {
|
|
2470
2960
|
const uri = request.params.uri;
|
|
2471
|
-
|
|
2961
|
+
logger6.debug("Reading resource", { uri });
|
|
2472
2962
|
return {
|
|
2473
2963
|
contents: [
|
|
2474
2964
|
{
|
|
@@ -2479,7 +2969,7 @@ function createUnifiedMcpServer(options) {
|
|
|
2479
2969
|
]
|
|
2480
2970
|
};
|
|
2481
2971
|
});
|
|
2482
|
-
|
|
2972
|
+
logger6.info("Unified MCP server configured", {
|
|
2483
2973
|
tools: getAllTools().length,
|
|
2484
2974
|
enableQaTools: options.enableQaTools,
|
|
2485
2975
|
enableLocalTools: options.enableLocalTools
|
|
@@ -2496,14 +2986,14 @@ __export(server_exports, {
|
|
|
2496
2986
|
registerTools: () => registerTools,
|
|
2497
2987
|
startStdioServer: () => startStdioServer
|
|
2498
2988
|
});
|
|
2499
|
-
var
|
|
2989
|
+
var logger5 = getLogger();
|
|
2500
2990
|
async function startStdioServer(server) {
|
|
2501
|
-
|
|
2991
|
+
logger5.info("Starting stdio server transport");
|
|
2502
2992
|
const transport = new StdioServerTransport();
|
|
2503
2993
|
await server.connect(transport);
|
|
2504
|
-
|
|
2994
|
+
logger5.info("Stdio server connected");
|
|
2505
2995
|
const shutdown = (signal) => {
|
|
2506
|
-
|
|
2996
|
+
logger5.info(`Received ${signal}, shutting down...`);
|
|
2507
2997
|
process.exit(0);
|
|
2508
2998
|
};
|
|
2509
2999
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
@@ -2522,6 +3012,8 @@ __export(qa_exports, {
|
|
|
2522
3012
|
EmptyInputSchema: () => EmptyInputSchema,
|
|
2523
3013
|
GatewayError: () => GatewayError,
|
|
2524
3014
|
IdSchema: () => IdSchema,
|
|
3015
|
+
LocalExecutionContextInputSchema: () => LocalExecutionContextInputSchema,
|
|
3016
|
+
LocalRunUploadInputSchema: () => LocalRunUploadInputSchema,
|
|
2525
3017
|
McpErrorCode: () => McpErrorCode,
|
|
2526
3018
|
PaginationInputSchema: () => PaginationInputSchema,
|
|
2527
3019
|
PrdFileDeleteInputSchema: () => PrdFileDeleteInputSchema,
|
|
@@ -2575,6 +3067,7 @@ __export(qa_exports, {
|
|
|
2575
3067
|
WorkflowGetLatestScriptGenByTestCaseInputSchema: () => WorkflowGetLatestScriptGenByTestCaseInputSchema,
|
|
2576
3068
|
WorkflowGetReplayBulkBatchSummaryInputSchema: () => WorkflowGetReplayBulkBatchSummaryInputSchema,
|
|
2577
3069
|
WorkflowListRuntimesInputSchema: () => WorkflowListRuntimesInputSchema,
|
|
3070
|
+
WorkflowMemoryParamsSchema: () => WorkflowMemoryParamsSchema,
|
|
2578
3071
|
WorkflowParamsSchema: () => WorkflowParamsSchema,
|
|
2579
3072
|
WorkflowStartTestCaseDetectionInputSchema: () => WorkflowStartTestCaseDetectionInputSchema,
|
|
2580
3073
|
WorkflowStartTestScriptGenerationInputSchema: () => WorkflowStartTestScriptGenerationInputSchema,
|
|
@@ -2587,12 +3080,43 @@ __export(qa_exports, {
|
|
|
2587
3080
|
getQaToolByName: () => getQaToolByName,
|
|
2588
3081
|
getQaTools: () => getQaTools
|
|
2589
3082
|
});
|
|
3083
|
+
var LocalExecutionContextInputSchema = z.object({
|
|
3084
|
+
originalUrl: z.string().url().describe("Original local URL used during local execution (typically localhost)"),
|
|
3085
|
+
productionUrl: z.string().url().describe("Cloud production URL for the test case"),
|
|
3086
|
+
runByUserId: z.string().min(1).describe("User ID who executed the run locally"),
|
|
3087
|
+
machineHostname: z.string().optional().describe("Local machine hostname"),
|
|
3088
|
+
osInfo: z.string().optional().describe("Local OS information"),
|
|
3089
|
+
electronAppVersion: z.string().optional().describe("Electron app version used for local run"),
|
|
3090
|
+
mcpServerVersion: z.string().optional().describe("MCP server version used for local run"),
|
|
3091
|
+
localExecutionCompletedAt: z.number().int().positive().describe("Epoch milliseconds when local run completed"),
|
|
3092
|
+
uploadedAt: z.number().int().positive().optional().describe("Epoch milliseconds when uploaded to cloud")
|
|
3093
|
+
});
|
|
3094
|
+
var LocalRunUploadInputSchema = z.object({
|
|
3095
|
+
projectId: z.string().min(1).describe("Project ID for the local run"),
|
|
3096
|
+
useCaseId: z.string().min(1).describe("Use case ID for the local run"),
|
|
3097
|
+
testCaseId: z.string().min(1).describe("Test case ID for the local run"),
|
|
3098
|
+
runType: z.enum(["generation", "replay"]).describe("Type of local run to upload"),
|
|
3099
|
+
productionUrl: z.string().url().describe("Cloud production URL associated with the run"),
|
|
3100
|
+
localExecutionContext: LocalExecutionContextInputSchema.describe("Local execution metadata"),
|
|
3101
|
+
actionScript: z.array(z.unknown()).min(1).describe("Generated action script steps from local execution"),
|
|
3102
|
+
status: z.enum(["passed", "failed"]).describe("Final local run status"),
|
|
3103
|
+
executionTimeMs: z.number().int().nonnegative().describe("Run duration in milliseconds"),
|
|
3104
|
+
errorMessage: z.string().optional().describe("Error message when status is failed")
|
|
3105
|
+
});
|
|
3106
|
+
|
|
3107
|
+
// src/qa/contracts/index.ts
|
|
2590
3108
|
var PaginationInputSchema = z.object({
|
|
2591
3109
|
page: z.number().int().positive().optional().describe("Page number (1-based)"),
|
|
2592
3110
|
pageSize: z.number().int().positive().max(100).optional().describe("Number of items per page")
|
|
2593
3111
|
});
|
|
2594
3112
|
var IdSchema = z.string().min(1).describe("Unique identifier");
|
|
2595
|
-
var
|
|
3113
|
+
var WorkflowMemoryParamsSchema = z.object({
|
|
3114
|
+
enableSharedTestMemory: z.boolean().optional().describe("Override to enable/disable SharedTestMemory for this workflow run"),
|
|
3115
|
+
enableEverMemOS: z.boolean().optional().describe("Override to enable/disable EverMemOS for this workflow run")
|
|
3116
|
+
});
|
|
3117
|
+
var WorkflowParamsSchema = z.object({
|
|
3118
|
+
memory: WorkflowMemoryParamsSchema.optional().describe("Per-run memory override settings")
|
|
3119
|
+
}).passthrough().optional().describe("Optional workflow parameters for workflow-level overrides");
|
|
2596
3120
|
var ProjectCreateInputSchema = z.object({
|
|
2597
3121
|
projectName: z.string().min(1).max(255).describe("Name of the project"),
|
|
2598
3122
|
description: z.string().min(1).describe("Project description"),
|
|
@@ -2917,17 +3441,17 @@ var PromptServiceClient = class {
|
|
|
2917
3441
|
* @param path - Path to validate.
|
|
2918
3442
|
* @throws GatewayError if path is not allowed.
|
|
2919
3443
|
*/
|
|
2920
|
-
validatePath(
|
|
2921
|
-
const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) =>
|
|
3444
|
+
validatePath(path8) {
|
|
3445
|
+
const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) => path8.startsWith(prefix));
|
|
2922
3446
|
if (!isAllowed) {
|
|
2923
|
-
const
|
|
2924
|
-
|
|
2925
|
-
path:
|
|
3447
|
+
const logger6 = getLogger();
|
|
3448
|
+
logger6.error("Path not in allowlist", {
|
|
3449
|
+
path: path8,
|
|
2926
3450
|
allowedPrefixes: ALLOWED_UPSTREAM_PREFIXES
|
|
2927
3451
|
});
|
|
2928
3452
|
throw new GatewayError({
|
|
2929
3453
|
code: "FORBIDDEN" /* FORBIDDEN */,
|
|
2930
|
-
message: `Path '${
|
|
3454
|
+
message: `Path '${path8}' is not allowed`
|
|
2931
3455
|
});
|
|
2932
3456
|
}
|
|
2933
3457
|
}
|
|
@@ -3027,7 +3551,7 @@ var PromptServiceClient = class {
|
|
|
3027
3551
|
* @throws GatewayError on validation or upstream errors.
|
|
3028
3552
|
*/
|
|
3029
3553
|
async execute(call, credentials, correlationId) {
|
|
3030
|
-
const
|
|
3554
|
+
const logger6 = getLogger();
|
|
3031
3555
|
if (!credentials.bearerToken && !credentials.apiKey) {
|
|
3032
3556
|
throw new GatewayError({
|
|
3033
3557
|
code: "UNAUTHORIZED" /* UNAUTHORIZED */,
|
|
@@ -3039,7 +3563,7 @@ var PromptServiceClient = class {
|
|
|
3039
3563
|
const headers = this.buildHeaders(credentials, correlationId);
|
|
3040
3564
|
const timeout = call.timeoutMs || this.requestTimeoutMs;
|
|
3041
3565
|
const startTime = Date.now();
|
|
3042
|
-
|
|
3566
|
+
logger6.info("Upstream request", {
|
|
3043
3567
|
correlationId,
|
|
3044
3568
|
method: call.method,
|
|
3045
3569
|
path: call.path,
|
|
@@ -3066,7 +3590,7 @@ var PromptServiceClient = class {
|
|
|
3066
3590
|
}
|
|
3067
3591
|
const response = await this.httpClient.request(requestConfig);
|
|
3068
3592
|
const latency = Date.now() - startTime;
|
|
3069
|
-
|
|
3593
|
+
logger6.info("Upstream response", {
|
|
3070
3594
|
correlationId,
|
|
3071
3595
|
statusCode: response.status,
|
|
3072
3596
|
latencyMs: latency
|
|
@@ -3097,7 +3621,7 @@ var PromptServiceClient = class {
|
|
|
3097
3621
|
throw error;
|
|
3098
3622
|
}
|
|
3099
3623
|
if (error instanceof AxiosError) {
|
|
3100
|
-
|
|
3624
|
+
logger6.error("Upstream request failed", {
|
|
3101
3625
|
correlationId,
|
|
3102
3626
|
error: error.message,
|
|
3103
3627
|
code: error.code,
|
|
@@ -3116,7 +3640,7 @@ var PromptServiceClient = class {
|
|
|
3116
3640
|
details: { upstreamPath: call.path }
|
|
3117
3641
|
});
|
|
3118
3642
|
}
|
|
3119
|
-
|
|
3643
|
+
logger6.error("Unknown upstream error", {
|
|
3120
3644
|
correlationId,
|
|
3121
3645
|
error: String(error),
|
|
3122
3646
|
latencyMs: latency
|
|
@@ -3680,6 +4204,40 @@ var workflowTools = [
|
|
|
3680
4204
|
path: `${MUGGLE_TEST_PREFIX}/workflow/runtimes/${data.workflowRuntimeId}/cancel`
|
|
3681
4205
|
};
|
|
3682
4206
|
}
|
|
4207
|
+
},
|
|
4208
|
+
{
|
|
4209
|
+
name: "muggle-remote-local-run-upload",
|
|
4210
|
+
description: "Upload a locally executed run (generation/replay) to cloud workflow records.",
|
|
4211
|
+
inputSchema: LocalRunUploadInputSchema,
|
|
4212
|
+
mapToUpstream: (input) => {
|
|
4213
|
+
const data = input;
|
|
4214
|
+
return {
|
|
4215
|
+
method: "POST",
|
|
4216
|
+
path: `${MUGGLE_TEST_PREFIX}/local-run/upload`,
|
|
4217
|
+
body: {
|
|
4218
|
+
projectId: data.projectId,
|
|
4219
|
+
useCaseId: data.useCaseId,
|
|
4220
|
+
testCaseId: data.testCaseId,
|
|
4221
|
+
runType: data.runType,
|
|
4222
|
+
productionUrl: data.productionUrl,
|
|
4223
|
+
localExecutionContext: {
|
|
4224
|
+
originalUrl: data.localExecutionContext.originalUrl,
|
|
4225
|
+
productionUrl: data.localExecutionContext.productionUrl,
|
|
4226
|
+
runByUserId: data.localExecutionContext.runByUserId,
|
|
4227
|
+
machineHostname: data.localExecutionContext.machineHostname,
|
|
4228
|
+
osInfo: data.localExecutionContext.osInfo,
|
|
4229
|
+
electronAppVersion: data.localExecutionContext.electronAppVersion,
|
|
4230
|
+
mcpServerVersion: data.localExecutionContext.mcpServerVersion,
|
|
4231
|
+
localExecutionCompletedAt: data.localExecutionContext.localExecutionCompletedAt,
|
|
4232
|
+
uploadedAt: data.localExecutionContext.uploadedAt
|
|
4233
|
+
},
|
|
4234
|
+
actionScript: data.actionScript,
|
|
4235
|
+
status: data.status,
|
|
4236
|
+
executionTimeMs: data.executionTimeMs,
|
|
4237
|
+
errorMessage: data.errorMessage
|
|
4238
|
+
}
|
|
4239
|
+
};
|
|
4240
|
+
}
|
|
3683
4241
|
}
|
|
3684
4242
|
];
|
|
3685
4243
|
var reportTools = [
|
|
@@ -4372,7 +4930,7 @@ function defaultResponseMapper(response) {
|
|
|
4372
4930
|
return response.data;
|
|
4373
4931
|
}
|
|
4374
4932
|
async function executeQaTool(toolName, input, correlationId) {
|
|
4375
|
-
const
|
|
4933
|
+
const logger6 = createChildLogger(correlationId);
|
|
4376
4934
|
const tool = getQaToolByName(toolName);
|
|
4377
4935
|
if (!tool) {
|
|
4378
4936
|
return {
|
|
@@ -4413,7 +4971,7 @@ async function executeQaTool(toolName, input, correlationId) {
|
|
|
4413
4971
|
}
|
|
4414
4972
|
} catch (error) {
|
|
4415
4973
|
if (error instanceof GatewayError) {
|
|
4416
|
-
|
|
4974
|
+
logger6.warn("Tool call failed with gateway error", {
|
|
4417
4975
|
tool: toolName,
|
|
4418
4976
|
code: error.code,
|
|
4419
4977
|
message: error.message
|
|
@@ -4424,7 +4982,7 @@ async function executeQaTool(toolName, input, correlationId) {
|
|
|
4424
4982
|
};
|
|
4425
4983
|
}
|
|
4426
4984
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4427
|
-
|
|
4985
|
+
logger6.error("Tool call failed", { tool: toolName, error: errorMessage });
|
|
4428
4986
|
return {
|
|
4429
4987
|
content: JSON.stringify({ error: "INTERNAL_ERROR", message: errorMessage }),
|
|
4430
4988
|
isError: true
|
|
@@ -4514,7 +5072,11 @@ var TestCaseDetailsSchema = z.object({
|
|
|
4514
5072
|
/** Step-by-step instructions (optional). */
|
|
4515
5073
|
instructions: z.string().optional().describe("Step-by-step instructions for the test"),
|
|
4516
5074
|
/** Original cloud URL (for reference, replaced by localUrl). */
|
|
4517
|
-
url: z.string().url().optional().describe("Original cloud URL (replaced by localUrl during execution)")
|
|
5075
|
+
url: z.string().url().optional().describe("Original cloud URL (replaced by localUrl during execution)"),
|
|
5076
|
+
/** Cloud project ID (required for electron workflow context). */
|
|
5077
|
+
projectId: z.string().min(1).describe("Cloud project ID"),
|
|
5078
|
+
/** Cloud use case ID (required for electron workflow context). */
|
|
5079
|
+
useCaseId: z.string().min(1).describe("Cloud use case ID")
|
|
4518
5080
|
});
|
|
4519
5081
|
var TestScriptDetailsSchema = z.object({
|
|
4520
5082
|
/** Cloud test script ID. */
|
|
@@ -4526,7 +5088,11 @@ var TestScriptDetailsSchema = z.object({
|
|
|
4526
5088
|
/** Action script steps. */
|
|
4527
5089
|
actionScript: z.array(z.unknown()).describe("Action script steps to replay"),
|
|
4528
5090
|
/** Original cloud URL (for reference, replaced by localUrl). */
|
|
4529
|
-
url: z.string().url().optional().describe("Original cloud URL (replaced by localUrl during execution)")
|
|
5091
|
+
url: z.string().url().optional().describe("Original cloud URL (replaced by localUrl during execution)"),
|
|
5092
|
+
/** Cloud project ID (required for electron workflow context). */
|
|
5093
|
+
projectId: z.string().min(1).describe("Cloud project ID"),
|
|
5094
|
+
/** Cloud use case ID (required for electron workflow context). */
|
|
5095
|
+
useCaseId: z.string().min(1).describe("Cloud use case ID")
|
|
4530
5096
|
});
|
|
4531
5097
|
var ExecuteTestGenerationInputSchema = z.object({
|
|
4532
5098
|
/** Test case details from qa_test_case_get. */
|
|
@@ -4577,12 +5143,12 @@ var CleanupSessionsInputSchema = z.object({
|
|
|
4577
5143
|
|
|
4578
5144
|
// src/local-qa/tools/tool-registry.ts
|
|
4579
5145
|
function createChildLogger2(correlationId) {
|
|
4580
|
-
const
|
|
5146
|
+
const logger6 = getLogger();
|
|
4581
5147
|
return {
|
|
4582
|
-
info: (msg, meta) =>
|
|
4583
|
-
error: (msg, meta) =>
|
|
4584
|
-
warn: (msg, meta) =>
|
|
4585
|
-
debug: (msg, meta) =>
|
|
5148
|
+
info: (msg, meta) => logger6.info(msg, { ...meta, correlationId }),
|
|
5149
|
+
error: (msg, meta) => logger6.error(msg, { ...meta, correlationId }),
|
|
5150
|
+
warn: (msg, meta) => logger6.warn(msg, { ...meta, correlationId }),
|
|
5151
|
+
debug: (msg, meta) => logger6.debug(msg, { ...meta, correlationId })
|
|
4586
5152
|
};
|
|
4587
5153
|
}
|
|
4588
5154
|
var checkStatusTool = {
|
|
@@ -4590,8 +5156,8 @@ var checkStatusTool = {
|
|
|
4590
5156
|
description: "Check the status of Muggle Test Local. This verifies the connection to web-service and shows current session information.",
|
|
4591
5157
|
inputSchema: EmptyInputSchema2,
|
|
4592
5158
|
execute: async (ctx) => {
|
|
4593
|
-
const
|
|
4594
|
-
|
|
5159
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5160
|
+
logger6.info("Executing muggle-local-check-status");
|
|
4595
5161
|
const authService = getAuthService();
|
|
4596
5162
|
const storageService = getStorageService();
|
|
4597
5163
|
const authStatus = authService.getAuthStatus();
|
|
@@ -4614,8 +5180,8 @@ var listSessionsTool = {
|
|
|
4614
5180
|
description: "List all stored testing sessions. Shows session IDs, status, and metadata for each session.",
|
|
4615
5181
|
inputSchema: ListSessionsInputSchema,
|
|
4616
5182
|
execute: async (ctx) => {
|
|
4617
|
-
const
|
|
4618
|
-
|
|
5183
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5184
|
+
logger6.info("Executing muggle-local-list-sessions");
|
|
4619
5185
|
const input = ListSessionsInputSchema.parse(ctx.input);
|
|
4620
5186
|
const storageService = getStorageService();
|
|
4621
5187
|
const sessions = storageService.listSessionsWithMetadata();
|
|
@@ -4642,8 +5208,8 @@ var runResultListTool = {
|
|
|
4642
5208
|
description: "List run results (test generation and replay history), optionally filtered by cloud test case ID.",
|
|
4643
5209
|
inputSchema: RunResultListInputSchema,
|
|
4644
5210
|
execute: async (ctx) => {
|
|
4645
|
-
const
|
|
4646
|
-
|
|
5211
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5212
|
+
logger6.info("Executing muggle-local-run-result-list");
|
|
4647
5213
|
const input = RunResultListInputSchema.parse(ctx.input);
|
|
4648
5214
|
const storage = getRunResultStorageService();
|
|
4649
5215
|
let results = storage.listRunResults();
|
|
@@ -4667,15 +5233,15 @@ var runResultGetTool = {
|
|
|
4667
5233
|
description: "Get detailed information about a run result including screenshots and action script output.",
|
|
4668
5234
|
inputSchema: RunResultGetInputSchema,
|
|
4669
5235
|
execute: async (ctx) => {
|
|
4670
|
-
const
|
|
4671
|
-
|
|
5236
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5237
|
+
logger6.info("Executing muggle-local-run-result-get");
|
|
4672
5238
|
const input = RunResultGetInputSchema.parse(ctx.input);
|
|
4673
5239
|
const storage = getRunResultStorageService();
|
|
4674
5240
|
const result = storage.getRunResult(input.runId);
|
|
4675
5241
|
if (!result) {
|
|
4676
5242
|
return { content: `Run result not found: ${input.runId}`, isError: true };
|
|
4677
5243
|
}
|
|
4678
|
-
const
|
|
5244
|
+
const contentParts = [
|
|
4679
5245
|
"## Run Result Details",
|
|
4680
5246
|
"",
|
|
4681
5247
|
`**ID:** ${result.id}`,
|
|
@@ -4684,7 +5250,56 @@ var runResultGetTool = {
|
|
|
4684
5250
|
`**Cloud Test Case:** ${result.cloudTestCaseId}`,
|
|
4685
5251
|
`**Duration:** ${result.executionTimeMs ?? 0}ms`,
|
|
4686
5252
|
result.errorMessage ? `**Error:** ${result.errorMessage}` : ""
|
|
4687
|
-
]
|
|
5253
|
+
];
|
|
5254
|
+
let testScriptSteps;
|
|
5255
|
+
if (result.testScriptId) {
|
|
5256
|
+
const testScript = storage.getTestScript(result.testScriptId);
|
|
5257
|
+
testScriptSteps = testScript?.actionScript?.length;
|
|
5258
|
+
}
|
|
5259
|
+
if (result.artifactsDir && fs3.existsSync(result.artifactsDir)) {
|
|
5260
|
+
contentParts.push(
|
|
5261
|
+
"",
|
|
5262
|
+
"### Artifacts (view action script + screenshots)",
|
|
5263
|
+
"",
|
|
5264
|
+
`**Location:** \`${result.artifactsDir}\``,
|
|
5265
|
+
""
|
|
5266
|
+
);
|
|
5267
|
+
const actionScriptPath = path.join(result.artifactsDir, "action-script.json");
|
|
5268
|
+
const resultsMdPath = path.join(result.artifactsDir, "results.md");
|
|
5269
|
+
const screenshotsDir = path.join(result.artifactsDir, "screenshots");
|
|
5270
|
+
const stdoutLogPath = path.join(result.artifactsDir, "stdout.log");
|
|
5271
|
+
const stderrLogPath = path.join(result.artifactsDir, "stderr.log");
|
|
5272
|
+
const artifactItems = [];
|
|
5273
|
+
if (fs3.existsSync(actionScriptPath)) {
|
|
5274
|
+
artifactItems.push("- `action-script.json` \u2014 generated test steps");
|
|
5275
|
+
}
|
|
5276
|
+
if (fs3.existsSync(resultsMdPath)) {
|
|
5277
|
+
artifactItems.push("- `results.md` \u2014 step-by-step report with screenshot links");
|
|
5278
|
+
}
|
|
5279
|
+
if (fs3.existsSync(screenshotsDir)) {
|
|
5280
|
+
const screenshots = fs3.readdirSync(screenshotsDir).filter((f) => /\.(png|jpg|jpeg|webp)$/i.test(f));
|
|
5281
|
+
artifactItems.push(`- \`screenshots/\` \u2014 ${screenshots.length} image(s)`);
|
|
5282
|
+
}
|
|
5283
|
+
if (fs3.existsSync(stdoutLogPath)) {
|
|
5284
|
+
artifactItems.push("- `stdout.log` \u2014 electron-app stdout output");
|
|
5285
|
+
}
|
|
5286
|
+
if (fs3.existsSync(stderrLogPath)) {
|
|
5287
|
+
artifactItems.push("- `stderr.log` \u2014 electron-app stderr output");
|
|
5288
|
+
}
|
|
5289
|
+
if (artifactItems.length > 0) {
|
|
5290
|
+
contentParts.push(artifactItems.join("\n"), "");
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
contentParts.push(
|
|
5294
|
+
"",
|
|
5295
|
+
"### Ending state",
|
|
5296
|
+
"",
|
|
5297
|
+
`- **Status:** ${result.status}`,
|
|
5298
|
+
`- **Duration:** ${result.executionTimeMs ?? 0}ms`,
|
|
5299
|
+
testScriptSteps !== void 0 ? `- **Steps generated:** ${testScriptSteps}` : "",
|
|
5300
|
+
result.artifactsDir ? `- **Artifacts path:** \`${result.artifactsDir}\`` : ""
|
|
5301
|
+
);
|
|
5302
|
+
const content = contentParts.filter(Boolean).join("\n");
|
|
4688
5303
|
return { content, isError: false, data: result };
|
|
4689
5304
|
}
|
|
4690
5305
|
};
|
|
@@ -4693,8 +5308,8 @@ var testScriptListTool = {
|
|
|
4693
5308
|
description: "List locally generated test scripts, optionally filtered by cloud test case ID.",
|
|
4694
5309
|
inputSchema: TestScriptListInputSchema2,
|
|
4695
5310
|
execute: async (ctx) => {
|
|
4696
|
-
const
|
|
4697
|
-
|
|
5311
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5312
|
+
logger6.info("Executing muggle-local-test-script-list");
|
|
4698
5313
|
const input = TestScriptListInputSchema2.parse(ctx.input);
|
|
4699
5314
|
const storage = getRunResultStorageService();
|
|
4700
5315
|
let scripts = storage.listTestScripts();
|
|
@@ -4714,8 +5329,8 @@ var testScriptGetTool = {
|
|
|
4714
5329
|
description: "Get details of a locally generated test script including action script steps.",
|
|
4715
5330
|
inputSchema: TestScriptGetInputSchema2,
|
|
4716
5331
|
execute: async (ctx) => {
|
|
4717
|
-
const
|
|
4718
|
-
|
|
5332
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5333
|
+
logger6.info("Executing muggle-local-test-script-get");
|
|
4719
5334
|
const input = TestScriptGetInputSchema2.parse(ctx.input);
|
|
4720
5335
|
const storage = getRunResultStorageService();
|
|
4721
5336
|
const testScript = storage.getTestScript(input.testScriptId);
|
|
@@ -4740,8 +5355,8 @@ var executeTestGenerationTool = {
|
|
|
4740
5355
|
description: "Execute test script generation for a test case. First call qa_test_case_get to get test case details, then pass them here along with the localhost URL. Requires explicit approval before launching electron-app in explore mode.",
|
|
4741
5356
|
inputSchema: ExecuteTestGenerationInputSchema,
|
|
4742
5357
|
execute: async (ctx) => {
|
|
4743
|
-
const
|
|
4744
|
-
|
|
5358
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5359
|
+
logger6.info("Executing muggle-local-execute-test-generation");
|
|
4745
5360
|
const input = ExecuteTestGenerationInputSchema.parse(ctx.input);
|
|
4746
5361
|
if (!input.approveElectronAppLaunch) {
|
|
4747
5362
|
return {
|
|
@@ -4782,7 +5397,7 @@ var executeTestGenerationTool = {
|
|
|
4782
5397
|
};
|
|
4783
5398
|
} catch (error) {
|
|
4784
5399
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4785
|
-
|
|
5400
|
+
logger6.error("Test generation failed", { error: errorMessage });
|
|
4786
5401
|
return { content: `Test generation failed: ${errorMessage}`, isError: true };
|
|
4787
5402
|
}
|
|
4788
5403
|
}
|
|
@@ -4792,8 +5407,8 @@ var executeReplayTool = {
|
|
|
4792
5407
|
description: "Execute test script replay. First call qa_test_script_get to get test script details (including actionScript), then pass them here along with the localhost URL. Requires explicit approval before launching electron-app in engine mode.",
|
|
4793
5408
|
inputSchema: ExecuteReplayInputSchema,
|
|
4794
5409
|
execute: async (ctx) => {
|
|
4795
|
-
const
|
|
4796
|
-
|
|
5410
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5411
|
+
logger6.info("Executing muggle-local-execute-replay");
|
|
4797
5412
|
const input = ExecuteReplayInputSchema.parse(ctx.input);
|
|
4798
5413
|
if (!input.approveElectronAppLaunch) {
|
|
4799
5414
|
return {
|
|
@@ -4835,7 +5450,7 @@ var executeReplayTool = {
|
|
|
4835
5450
|
};
|
|
4836
5451
|
} catch (error) {
|
|
4837
5452
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4838
|
-
|
|
5453
|
+
logger6.error("Test replay failed", { error: errorMessage });
|
|
4839
5454
|
return { content: `Test replay failed: ${errorMessage}`, isError: true };
|
|
4840
5455
|
}
|
|
4841
5456
|
}
|
|
@@ -4845,8 +5460,8 @@ var cancelExecutionTool = {
|
|
|
4845
5460
|
description: "Cancel an active test generation or replay execution.",
|
|
4846
5461
|
inputSchema: CancelExecutionInputSchema,
|
|
4847
5462
|
execute: async (ctx) => {
|
|
4848
|
-
const
|
|
4849
|
-
|
|
5463
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5464
|
+
logger6.info("Executing muggle-local-cancel-execution");
|
|
4850
5465
|
const input = CancelExecutionInputSchema.parse(ctx.input);
|
|
4851
5466
|
const cancelled = cancelExecution({ runId: input.runId });
|
|
4852
5467
|
if (cancelled) {
|
|
@@ -4860,8 +5475,8 @@ var publishTestScriptTool = {
|
|
|
4860
5475
|
description: "Publish a locally generated test script to the cloud. Uses the run ID from muggle_execute_test_generation to find the script and uploads it to the specified cloud test case.",
|
|
4861
5476
|
inputSchema: PublishTestScriptInputSchema,
|
|
4862
5477
|
execute: async (ctx) => {
|
|
4863
|
-
const
|
|
4864
|
-
|
|
5478
|
+
const logger6 = createChildLogger2(ctx.correlationId);
|
|
5479
|
+
logger6.info("Executing muggle-local-publish-test-script");
|
|
4865
5480
|
const input = PublishTestScriptInputSchema.parse(ctx.input);
|
|
4866
5481
|
const storage = getRunResultStorageService();
|
|
4867
5482
|
const runResult = storage.getRunResult(input.runId);
|
|
@@ -4875,18 +5490,121 @@ var publishTestScriptTool = {
|
|
|
4875
5490
|
if (!testScript) {
|
|
4876
5491
|
return { content: `Test script not found: ${runResult.testScriptId}`, isError: true };
|
|
4877
5492
|
}
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
}
|
|
5493
|
+
if (runResult.runType !== "generation") {
|
|
5494
|
+
return {
|
|
5495
|
+
content: `Only generation runs can be published. Run ${input.runId} is '${runResult.runType}'.`,
|
|
5496
|
+
isError: true
|
|
5497
|
+
};
|
|
5498
|
+
}
|
|
5499
|
+
if (runResult.status !== "passed" && runResult.status !== "failed") {
|
|
5500
|
+
return {
|
|
5501
|
+
content: `Run ${input.runId} must be in passed/failed state before publishing. Current status: ${runResult.status}.`,
|
|
5502
|
+
isError: true
|
|
5503
|
+
};
|
|
5504
|
+
}
|
|
5505
|
+
if (!Array.isArray(testScript.actionScript) || testScript.actionScript.length === 0) {
|
|
5506
|
+
return {
|
|
5507
|
+
content: `Test script ${testScript.id} has no generated actionScript steps to publish.`,
|
|
5508
|
+
isError: true
|
|
5509
|
+
};
|
|
5510
|
+
}
|
|
5511
|
+
if (!runResult.projectId) {
|
|
5512
|
+
return { content: `Run result ${input.runId} is missing projectId.`, isError: true };
|
|
5513
|
+
}
|
|
5514
|
+
if (!runResult.useCaseId) {
|
|
5515
|
+
return { content: `Run result ${input.runId} is missing useCaseId.`, isError: true };
|
|
5516
|
+
}
|
|
5517
|
+
if (!runResult.productionUrl) {
|
|
5518
|
+
return { content: `Run result ${input.runId} is missing productionUrl.`, isError: true };
|
|
5519
|
+
}
|
|
5520
|
+
if (!runResult.executionTimeMs && runResult.executionTimeMs !== 0) {
|
|
5521
|
+
return { content: `Run result ${input.runId} is missing executionTimeMs.`, isError: true };
|
|
5522
|
+
}
|
|
5523
|
+
if (!runResult.localExecutionContext) {
|
|
5524
|
+
return { content: `Run result ${input.runId} is missing localExecutionContext.`, isError: true };
|
|
5525
|
+
}
|
|
5526
|
+
if (!runResult.localExecutionContext.localExecutionCompletedAt) {
|
|
5527
|
+
return {
|
|
5528
|
+
content: `Run result ${input.runId} is missing localExecutionCompletedAt in localExecutionContext.`,
|
|
5529
|
+
isError: true
|
|
5530
|
+
};
|
|
5531
|
+
}
|
|
5532
|
+
const authStatus = getAuthService().getAuthStatus();
|
|
5533
|
+
if (!authStatus.userId) {
|
|
5534
|
+
return { content: "Authenticated user ID is missing. Please login again.", isError: true };
|
|
5535
|
+
}
|
|
5536
|
+
try {
|
|
5537
|
+
const credentials = await getCallerCredentialsAsync();
|
|
5538
|
+
const client = getPromptServiceClient();
|
|
5539
|
+
const uploadedAt = Date.now();
|
|
5540
|
+
const response = await client.execute(
|
|
5541
|
+
{
|
|
5542
|
+
method: "POST",
|
|
5543
|
+
path: "/v1/protected/muggle-test/local-run/upload",
|
|
5544
|
+
body: {
|
|
5545
|
+
projectId: runResult.projectId,
|
|
5546
|
+
useCaseId: runResult.useCaseId,
|
|
5547
|
+
testCaseId: input.cloudTestCaseId,
|
|
5548
|
+
runType: runResult.runType,
|
|
5549
|
+
productionUrl: runResult.productionUrl,
|
|
5550
|
+
localExecutionContext: {
|
|
5551
|
+
originalUrl: runResult.localExecutionContext.originalUrl,
|
|
5552
|
+
productionUrl: runResult.localExecutionContext.productionUrl,
|
|
5553
|
+
runByUserId: authStatus.userId,
|
|
5554
|
+
machineHostname: runResult.localExecutionContext.machineHostname,
|
|
5555
|
+
osInfo: runResult.localExecutionContext.osInfo,
|
|
5556
|
+
electronAppVersion: runResult.localExecutionContext.electronAppVersion,
|
|
5557
|
+
mcpServerVersion: runResult.localExecutionContext.mcpServerVersion,
|
|
5558
|
+
localExecutionCompletedAt: runResult.localExecutionContext.localExecutionCompletedAt,
|
|
5559
|
+
uploadedAt
|
|
5560
|
+
},
|
|
5561
|
+
actionScript: testScript.actionScript,
|
|
5562
|
+
status: runResult.status === "passed" ? "passed" : "failed",
|
|
5563
|
+
executionTimeMs: runResult.executionTimeMs,
|
|
5564
|
+
errorMessage: runResult.errorMessage
|
|
5565
|
+
}
|
|
5566
|
+
},
|
|
5567
|
+
credentials,
|
|
5568
|
+
ctx.correlationId
|
|
5569
|
+
);
|
|
5570
|
+
storage.updateTestScript(testScript.id, {
|
|
5571
|
+
status: "published",
|
|
5572
|
+
cloudActionScriptId: response.data.actionScriptId
|
|
5573
|
+
});
|
|
5574
|
+
storage.updateRunResult(runResult.id, {
|
|
5575
|
+
localExecutionContext: {
|
|
5576
|
+
...runResult.localExecutionContext,
|
|
5577
|
+
runByUserId: authStatus.userId
|
|
5578
|
+
}
|
|
5579
|
+
});
|
|
5580
|
+
return {
|
|
5581
|
+
content: [
|
|
5582
|
+
"## Test Script Published",
|
|
5583
|
+
"",
|
|
5584
|
+
`**Run ID:** ${input.runId}`,
|
|
5585
|
+
`**Local Test Script ID:** ${testScript.id}`,
|
|
5586
|
+
`**Cloud Test Case ID:** ${input.cloudTestCaseId}`,
|
|
5587
|
+
`**Cloud Test Script ID:** ${response.data.testScriptId}`,
|
|
5588
|
+
`**Cloud Action Script ID:** ${response.data.actionScriptId}`,
|
|
5589
|
+
`**Workflow Runtime ID:** ${response.data.workflowRuntimeId}`,
|
|
5590
|
+
`**Workflow Run ID:** ${response.data.workflowRunId}`,
|
|
5591
|
+
`**View URL:** ${response.data.viewUrl}`
|
|
5592
|
+
].join("\n"),
|
|
5593
|
+
isError: false,
|
|
5594
|
+
data: response.data
|
|
5595
|
+
};
|
|
5596
|
+
} catch (error) {
|
|
5597
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5598
|
+
logger6.error("Failed to publish local test script to cloud", {
|
|
5599
|
+
runId: input.runId,
|
|
5600
|
+
cloudTestCaseId: input.cloudTestCaseId,
|
|
5601
|
+
error: errorMessage
|
|
5602
|
+
});
|
|
5603
|
+
return {
|
|
5604
|
+
content: `Failed to publish test script: ${errorMessage}`,
|
|
5605
|
+
isError: true
|
|
5606
|
+
};
|
|
5607
|
+
}
|
|
4890
5608
|
}
|
|
4891
5609
|
};
|
|
4892
5610
|
var allLocalQaTools = [
|
|
@@ -4958,5 +5676,5 @@ function isLocalOnlyTool(toolName) {
|
|
|
4958
5676
|
}
|
|
4959
5677
|
|
|
4960
5678
|
export { __export, __require, createApiKeyWithToken, createChildLogger, createUnifiedMcpServer, deleteCredentials, getApiKey, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getConfig, getCredentialsFilePath, getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getQaTools, getValidCredentials, hasApiKey, isCredentialsExpired, isElectronAppInstalled, loadCredentials, local_qa_exports, openBrowserUrl, performLogin, performLogout, pollDeviceCode, qa_exports, registerTools, resetConfig, resetLogger, saveApiKey, saveCredentials, server_exports, startDeviceCodeFlow, startStdioServer, toolRequiresAuth };
|
|
4961
|
-
//# sourceMappingURL=chunk-
|
|
4962
|
-
//# sourceMappingURL=chunk-
|
|
5679
|
+
//# sourceMappingURL=chunk-DGEO3CP2.js.map
|
|
5680
|
+
//# sourceMappingURL=chunk-DGEO3CP2.js.map
|