@muggleai/works 4.12.2 → 4.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-I4VLYJ7M.js → chunk-CPF6AR2I.js} +499 -147
- package/dist/{chunk-2DVZ2LYO.js → chunk-JNI7INIO.js} +2 -2
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/skills/_shared/dev-server-readiness.md +39 -0
- package/dist/plugin/skills/_shared/failure-mode-handling.md +19 -4
- package/dist/plugin/skills/_shared/pr-branch-worktree.md +31 -0
- package/dist/plugin/skills/muggle-test/SKILL.md +7 -3
- package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +6 -1
- package/dist/plugin/skills/muggle-test-prepare/SKILL.md +40 -256
- package/dist/plugin/skills/muggle-test-prepare/steps/check-running.md +31 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/env-file.md +18 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/fresh-install.md +20 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/identify-services.md +28 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/readiness-report.md +26 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/rebase-check.md +3 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/scope.md +11 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/smoke-test.md +26 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/start-commands.md +29 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/start-services.md +21 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/viability-check.md +21 -0
- package/dist/release-manifest.json +4 -4
- package/dist/{src-ARTTHWNP.js → src-YR5UKLPC.js} +1 -1
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/skills/_shared/dev-server-readiness.md +39 -0
- package/plugin/skills/_shared/failure-mode-handling.md +19 -4
- package/plugin/skills/_shared/pr-branch-worktree.md +31 -0
- package/plugin/skills/muggle-test/SKILL.md +7 -3
- package/plugin/skills/muggle-test-feature-local/SKILL.md +6 -1
- package/plugin/skills/muggle-test-prepare/SKILL.md +40 -256
- package/plugin/skills/muggle-test-prepare/steps/check-running.md +31 -0
- package/plugin/skills/muggle-test-prepare/steps/env-file.md +18 -0
- package/plugin/skills/muggle-test-prepare/steps/fresh-install.md +20 -0
- package/plugin/skills/muggle-test-prepare/steps/identify-services.md +28 -0
- package/plugin/skills/muggle-test-prepare/steps/readiness-report.md +26 -0
- package/plugin/skills/muggle-test-prepare/steps/rebase-check.md +3 -0
- package/plugin/skills/muggle-test-prepare/steps/scope.md +11 -0
- package/plugin/skills/muggle-test-prepare/steps/smoke-test.md +26 -0
- package/plugin/skills/muggle-test-prepare/steps/start-commands.md +29 -0
- package/plugin/skills/muggle-test-prepare/steps/start-services.md +21 -0
- package/plugin/skills/muggle-test-prepare/steps/viability-check.md +21 -0
|
@@ -2,15 +2,15 @@ import * as crypto from 'crypto';
|
|
|
2
2
|
import { randomUUID } from 'crypto';
|
|
3
3
|
import * as fs5 from 'fs';
|
|
4
4
|
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
5
|
-
import * as
|
|
5
|
+
import * as path9 from 'path';
|
|
6
6
|
import { join, dirname } from 'path';
|
|
7
|
-
import * as
|
|
7
|
+
import * as os4 from 'os';
|
|
8
8
|
import { platform } from 'os';
|
|
9
9
|
import winston from 'winston';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
-
import {
|
|
11
|
+
import { exec, spawn } from 'child_process';
|
|
12
12
|
import axios, { AxiosError } from 'axios';
|
|
13
|
-
import * as
|
|
13
|
+
import * as fs8 from 'fs/promises';
|
|
14
14
|
import AppInsights from 'applicationinsights';
|
|
15
15
|
import { z } from 'zod';
|
|
16
16
|
|
|
@@ -98,7 +98,7 @@ __export(src_exports, {
|
|
|
98
98
|
});
|
|
99
99
|
var DATA_DIR_NAME = ".muggle-ai";
|
|
100
100
|
function getDataDir() {
|
|
101
|
-
return
|
|
101
|
+
return path9.join(os4.homedir(), DATA_DIR_NAME);
|
|
102
102
|
}
|
|
103
103
|
var DEV_MANIFEST = {
|
|
104
104
|
release: "dev",
|
|
@@ -162,24 +162,24 @@ var configInstance = null;
|
|
|
162
162
|
var muggleConfigCache = null;
|
|
163
163
|
function getPackageRoot() {
|
|
164
164
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
165
|
-
const currentDir =
|
|
166
|
-
if (currentDir.includes(
|
|
167
|
-
return
|
|
165
|
+
const currentDir = path9.dirname(currentFilePath);
|
|
166
|
+
if (currentDir.includes(path9.join("dist", "shared"))) {
|
|
167
|
+
return path9.resolve(currentDir, "..", "..");
|
|
168
168
|
}
|
|
169
169
|
if (currentDir.endsWith("dist")) {
|
|
170
|
-
return
|
|
170
|
+
return path9.resolve(currentDir, "..");
|
|
171
171
|
}
|
|
172
|
-
if (currentDir.includes(
|
|
173
|
-
return
|
|
172
|
+
if (currentDir.includes(path9.join("src", "shared"))) {
|
|
173
|
+
return path9.resolve(currentDir, "..", "..");
|
|
174
174
|
}
|
|
175
|
-
return
|
|
175
|
+
return path9.dirname(currentDir);
|
|
176
176
|
}
|
|
177
177
|
function getMuggleConfig() {
|
|
178
178
|
if (muggleConfigCache) {
|
|
179
179
|
return muggleConfigCache;
|
|
180
180
|
}
|
|
181
181
|
const packageRoot = getPackageRoot();
|
|
182
|
-
const packageJsonPath =
|
|
182
|
+
const packageJsonPath = path9.join(packageRoot, "package.json");
|
|
183
183
|
let packageJson;
|
|
184
184
|
try {
|
|
185
185
|
const content = fs5.readFileSync(packageJsonPath, "utf-8");
|
|
@@ -240,19 +240,19 @@ function getDataDir2() {
|
|
|
240
240
|
return getDataDir();
|
|
241
241
|
}
|
|
242
242
|
function getDownloadedElectronAppPath() {
|
|
243
|
-
const platformName =
|
|
243
|
+
const platformName = os4.platform();
|
|
244
244
|
const version = getElectronAppVersion();
|
|
245
|
-
const baseDir =
|
|
245
|
+
const baseDir = path9.join(getDataDir2(), ELECTRON_APP_DIR, version);
|
|
246
246
|
let binaryPath;
|
|
247
247
|
switch (platformName) {
|
|
248
248
|
case "darwin":
|
|
249
|
-
binaryPath =
|
|
249
|
+
binaryPath = path9.join(baseDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
250
250
|
break;
|
|
251
251
|
case "win32":
|
|
252
|
-
binaryPath =
|
|
252
|
+
binaryPath = path9.join(baseDir, "MuggleAI.exe");
|
|
253
253
|
break;
|
|
254
254
|
case "linux":
|
|
255
|
-
binaryPath =
|
|
255
|
+
binaryPath = path9.join(baseDir, "MuggleAI");
|
|
256
256
|
break;
|
|
257
257
|
default:
|
|
258
258
|
return null;
|
|
@@ -263,18 +263,18 @@ function getDownloadedElectronAppPath() {
|
|
|
263
263
|
return null;
|
|
264
264
|
}
|
|
265
265
|
function getSystemElectronAppPath() {
|
|
266
|
-
const platformName =
|
|
267
|
-
const homeDir =
|
|
266
|
+
const platformName = os4.platform();
|
|
267
|
+
const homeDir = os4.homedir();
|
|
268
268
|
let binaryPath;
|
|
269
269
|
switch (platformName) {
|
|
270
270
|
case "darwin":
|
|
271
|
-
binaryPath =
|
|
271
|
+
binaryPath = path9.join(homeDir, "Applications", "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
272
272
|
break;
|
|
273
273
|
case "win32":
|
|
274
|
-
binaryPath =
|
|
274
|
+
binaryPath = path9.join(homeDir, "AppData", "Local", "Programs", "MuggleAI", "MuggleAI.exe");
|
|
275
275
|
break;
|
|
276
276
|
case "linux":
|
|
277
|
-
binaryPath =
|
|
277
|
+
binaryPath = path9.join(homeDir, ".local", "share", "muggle-ai", "MuggleAI");
|
|
278
278
|
break;
|
|
279
279
|
default:
|
|
280
280
|
return null;
|
|
@@ -305,7 +305,7 @@ function resolveWebServicePath() {
|
|
|
305
305
|
return customPath;
|
|
306
306
|
}
|
|
307
307
|
const packageRoot = getPackageRoot();
|
|
308
|
-
const siblingPath =
|
|
308
|
+
const siblingPath = path9.resolve(packageRoot, "..", "web-service", "dist", "src", "index.js");
|
|
309
309
|
if (fs5.existsSync(siblingPath)) {
|
|
310
310
|
return siblingPath;
|
|
311
311
|
}
|
|
@@ -392,13 +392,13 @@ function buildLocalQaConfig() {
|
|
|
392
392
|
webServiceUrl: process.env.WEB_SERVICE_URL ?? DEFAULT_WEB_SERVICE_URL,
|
|
393
393
|
promptServiceUrl: process.env.PROMPT_SERVICE_BASE_URL ?? defaultPromptServiceUrl,
|
|
394
394
|
dataDir,
|
|
395
|
-
sessionsDir:
|
|
396
|
-
projectsDir:
|
|
397
|
-
tempDir:
|
|
398
|
-
apiKeyFilePath:
|
|
399
|
-
oauthSessionFilePath:
|
|
395
|
+
sessionsDir: path9.join(dataDir, "sessions"),
|
|
396
|
+
projectsDir: path9.join(dataDir, "projects"),
|
|
397
|
+
tempDir: path9.join(dataDir, "temp"),
|
|
398
|
+
apiKeyFilePath: path9.join(dataDir, API_KEY_FILE),
|
|
399
|
+
oauthSessionFilePath: path9.join(dataDir, "oauth-session.json"),
|
|
400
400
|
webServicePath: resolveWebServicePath(),
|
|
401
|
-
webServicePidFile:
|
|
401
|
+
webServicePidFile: path9.join(dataDir, "web-service.pid"),
|
|
402
402
|
auth0: {
|
|
403
403
|
domain: process.env.AUTH0_DOMAIN ?? defaultAuth0Domain,
|
|
404
404
|
clientId: process.env.AUTH0_CLIENT_ID ?? defaultAuth0ClientId,
|
|
@@ -433,7 +433,7 @@ function getElectronAppVersion() {
|
|
|
433
433
|
if (envVersion && /^\d+\.\d+\.\d+$/.test(envVersion)) {
|
|
434
434
|
return envVersion;
|
|
435
435
|
}
|
|
436
|
-
const overridePath =
|
|
436
|
+
const overridePath = path9.join(getDataDir2(), VERSION_OVERRIDE_FILE);
|
|
437
437
|
if (fs5.existsSync(overridePath)) {
|
|
438
438
|
try {
|
|
439
439
|
const content = JSON.parse(fs5.readFileSync(overridePath, "utf-8"));
|
|
@@ -450,7 +450,7 @@ function getElectronAppVersionSource() {
|
|
|
450
450
|
if (envVersion && /^\d+\.\d+\.\d+$/.test(envVersion)) {
|
|
451
451
|
return "env";
|
|
452
452
|
}
|
|
453
|
-
const overridePath =
|
|
453
|
+
const overridePath = path9.join(getDataDir2(), VERSION_OVERRIDE_FILE);
|
|
454
454
|
if (fs5.existsSync(overridePath)) {
|
|
455
455
|
try {
|
|
456
456
|
const content = JSON.parse(fs5.readFileSync(overridePath, "utf-8"));
|
|
@@ -485,7 +485,7 @@ function isElectronAppInstalled() {
|
|
|
485
485
|
}
|
|
486
486
|
function getElectronAppDir(version) {
|
|
487
487
|
const resolvedVersion = version ?? getElectronAppVersion();
|
|
488
|
-
return
|
|
488
|
+
return path9.join(getDataDir2(), ELECTRON_APP_DIR, resolvedVersion);
|
|
489
489
|
}
|
|
490
490
|
|
|
491
491
|
// packages/mcps/src/shared/logger.ts
|
|
@@ -528,9 +528,9 @@ function resetLogger() {
|
|
|
528
528
|
// packages/mcps/src/shared/checksum.ts
|
|
529
529
|
var logger = getLogger();
|
|
530
530
|
function getPlatformKey() {
|
|
531
|
-
const
|
|
531
|
+
const os6 = platform();
|
|
532
532
|
const arch2 = process.arch;
|
|
533
|
-
switch (
|
|
533
|
+
switch (os6) {
|
|
534
534
|
case "darwin":
|
|
535
535
|
return arch2 === "arm64" ? "darwin-arm64" : "darwin-x64";
|
|
536
536
|
case "win32":
|
|
@@ -538,18 +538,18 @@ function getPlatformKey() {
|
|
|
538
538
|
case "linux":
|
|
539
539
|
return "linux-x64";
|
|
540
540
|
default:
|
|
541
|
-
throw new Error(`Unsupported platform: ${
|
|
541
|
+
throw new Error(`Unsupported platform: ${os6}`);
|
|
542
542
|
}
|
|
543
543
|
}
|
|
544
544
|
async function calculateFileChecksum(filePath) {
|
|
545
|
-
return new Promise((
|
|
545
|
+
return new Promise((resolve4, reject) => {
|
|
546
546
|
const hash = crypto.createHash("sha256");
|
|
547
547
|
const stream = fs5.createReadStream(filePath);
|
|
548
548
|
stream.on("data", (data) => {
|
|
549
549
|
hash.update(data);
|
|
550
550
|
});
|
|
551
551
|
stream.on("end", () => {
|
|
552
|
-
|
|
552
|
+
resolve4(hash.digest("hex"));
|
|
553
553
|
});
|
|
554
554
|
stream.on("error", (error) => {
|
|
555
555
|
reject(error);
|
|
@@ -559,7 +559,7 @@ async function calculateFileChecksum(filePath) {
|
|
|
559
559
|
async function verifyFileChecksum(filePath, expectedChecksum) {
|
|
560
560
|
if (!expectedChecksum || expectedChecksum.trim() === "") {
|
|
561
561
|
logger.warn("Checksum verification skipped - no checksum provided", {
|
|
562
|
-
file:
|
|
562
|
+
file: path9.basename(filePath)
|
|
563
563
|
});
|
|
564
564
|
return {
|
|
565
565
|
valid: true,
|
|
@@ -575,13 +575,13 @@ async function verifyFileChecksum(filePath, expectedChecksum) {
|
|
|
575
575
|
const valid = normalizedExpected === normalizedActual;
|
|
576
576
|
if (!valid) {
|
|
577
577
|
logger.error("Checksum verification failed", {
|
|
578
|
-
file:
|
|
578
|
+
file: path9.basename(filePath),
|
|
579
579
|
expected: normalizedExpected,
|
|
580
580
|
actual: normalizedActual
|
|
581
581
|
});
|
|
582
582
|
} else {
|
|
583
583
|
logger.info("Checksum verified successfully", {
|
|
584
|
-
file:
|
|
584
|
+
file: path9.basename(filePath),
|
|
585
585
|
checksum: normalizedActual
|
|
586
586
|
});
|
|
587
587
|
}
|
|
@@ -594,7 +594,7 @@ async function verifyFileChecksum(filePath, expectedChecksum) {
|
|
|
594
594
|
} catch (error) {
|
|
595
595
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
596
596
|
logger.error("Checksum calculation failed", {
|
|
597
|
-
file:
|
|
597
|
+
file: path9.basename(filePath),
|
|
598
598
|
error: errorMessage
|
|
599
599
|
});
|
|
600
600
|
return {
|
|
@@ -627,7 +627,7 @@ function getOpenCommand(url) {
|
|
|
627
627
|
}
|
|
628
628
|
}
|
|
629
629
|
async function openBrowserUrl(options) {
|
|
630
|
-
return new Promise((
|
|
630
|
+
return new Promise((resolve4) => {
|
|
631
631
|
try {
|
|
632
632
|
const command = getOpenCommand(options.url);
|
|
633
633
|
logger2.debug("[Browser] Opening URL", { url: options.url, command });
|
|
@@ -637,13 +637,13 @@ async function openBrowserUrl(options) {
|
|
|
637
637
|
url: options.url,
|
|
638
638
|
error: error.message
|
|
639
639
|
});
|
|
640
|
-
|
|
640
|
+
resolve4({
|
|
641
641
|
opened: false,
|
|
642
642
|
error: error.message
|
|
643
643
|
});
|
|
644
644
|
} else {
|
|
645
645
|
logger2.info("[Browser] URL opened successfully", { url: options.url });
|
|
646
|
-
|
|
646
|
+
resolve4({ opened: true });
|
|
647
647
|
}
|
|
648
648
|
});
|
|
649
649
|
} catch (error) {
|
|
@@ -652,7 +652,7 @@ async function openBrowserUrl(options) {
|
|
|
652
652
|
url: options.url,
|
|
653
653
|
error: errorMessage
|
|
654
654
|
});
|
|
655
|
-
|
|
655
|
+
resolve4({
|
|
656
656
|
opened: false,
|
|
657
657
|
error: errorMessage
|
|
658
658
|
});
|
|
@@ -661,7 +661,7 @@ async function openBrowserUrl(options) {
|
|
|
661
661
|
}
|
|
662
662
|
var API_KEY_FILE2 = "api-key.json";
|
|
663
663
|
function getApiKeyFilePath() {
|
|
664
|
-
return
|
|
664
|
+
return path9.join(getDataDir(), API_KEY_FILE2);
|
|
665
665
|
}
|
|
666
666
|
function ensureDataDir() {
|
|
667
667
|
const dataDir = getDataDir();
|
|
@@ -850,8 +850,8 @@ var AuthService = class {
|
|
|
850
850
|
constructor() {
|
|
851
851
|
const config = getConfig();
|
|
852
852
|
this.oauthSessionFilePath = config.localQa.oauthSessionFilePath;
|
|
853
|
-
this.pendingDeviceCodePath =
|
|
854
|
-
|
|
853
|
+
this.pendingDeviceCodePath = path9.join(
|
|
854
|
+
path9.dirname(config.localQa.oauthSessionFilePath),
|
|
855
855
|
"pending-device-code.json"
|
|
856
856
|
);
|
|
857
857
|
}
|
|
@@ -959,7 +959,7 @@ var AuthService = class {
|
|
|
959
959
|
*/
|
|
960
960
|
storePendingDeviceCode(params) {
|
|
961
961
|
const logger6 = getLogger();
|
|
962
|
-
const dir =
|
|
962
|
+
const dir = path9.dirname(this.pendingDeviceCodePath);
|
|
963
963
|
if (!fs5.existsSync(dir)) {
|
|
964
964
|
fs5.mkdirSync(dir, { recursive: true });
|
|
965
965
|
}
|
|
@@ -1172,7 +1172,7 @@ var AuthService = class {
|
|
|
1172
1172
|
email,
|
|
1173
1173
|
userId
|
|
1174
1174
|
};
|
|
1175
|
-
const dir =
|
|
1175
|
+
const dir = path9.dirname(this.oauthSessionFilePath);
|
|
1176
1176
|
if (!fs5.existsSync(dir)) {
|
|
1177
1177
|
fs5.mkdirSync(dir, { recursive: true });
|
|
1178
1178
|
}
|
|
@@ -1275,7 +1275,7 @@ var AuthService = class {
|
|
|
1275
1275
|
email: storedAuth.email,
|
|
1276
1276
|
userId: storedAuth.userId
|
|
1277
1277
|
};
|
|
1278
|
-
const dir =
|
|
1278
|
+
const dir = path9.dirname(this.oauthSessionFilePath);
|
|
1279
1279
|
if (!fs5.existsSync(dir)) {
|
|
1280
1280
|
fs5.mkdirSync(dir, { recursive: true });
|
|
1281
1281
|
}
|
|
@@ -1356,9 +1356,9 @@ function resetAuthService() {
|
|
|
1356
1356
|
serviceInstance = null;
|
|
1357
1357
|
}
|
|
1358
1358
|
function sleep(params) {
|
|
1359
|
-
return new Promise((
|
|
1359
|
+
return new Promise((resolve4) => {
|
|
1360
1360
|
setTimeout(() => {
|
|
1361
|
-
|
|
1361
|
+
resolve4();
|
|
1362
1362
|
}, params.durationMs);
|
|
1363
1363
|
});
|
|
1364
1364
|
}
|
|
@@ -1398,11 +1398,11 @@ var StorageService = class {
|
|
|
1398
1398
|
createSessionDirectory(sessionId) {
|
|
1399
1399
|
const logger6 = getLogger();
|
|
1400
1400
|
this.ensureDirectories();
|
|
1401
|
-
const sessionDir =
|
|
1401
|
+
const sessionDir = path9.join(this.sessionsDir, sessionId);
|
|
1402
1402
|
if (!fs5.existsSync(sessionDir)) {
|
|
1403
1403
|
fs5.mkdirSync(sessionDir, { recursive: true });
|
|
1404
|
-
fs5.mkdirSync(
|
|
1405
|
-
fs5.mkdirSync(
|
|
1404
|
+
fs5.mkdirSync(path9.join(sessionDir, "screenshots"), { recursive: true });
|
|
1405
|
+
fs5.mkdirSync(path9.join(sessionDir, "logs"), { recursive: true });
|
|
1406
1406
|
logger6.info("Created session directory", { sessionId, path: sessionDir });
|
|
1407
1407
|
}
|
|
1408
1408
|
return sessionDir;
|
|
@@ -1414,7 +1414,7 @@ var StorageService = class {
|
|
|
1414
1414
|
saveSessionMetadata(metadata) {
|
|
1415
1415
|
const logger6 = getLogger();
|
|
1416
1416
|
const sessionDir = this.createSessionDirectory(metadata.sessionId);
|
|
1417
|
-
const metadataPath =
|
|
1417
|
+
const metadataPath = path9.join(sessionDir, "metadata.json");
|
|
1418
1418
|
fs5.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
1419
1419
|
logger6.debug("Saved session metadata", { sessionId: metadata.sessionId });
|
|
1420
1420
|
}
|
|
@@ -1425,7 +1425,7 @@ var StorageService = class {
|
|
|
1425
1425
|
*/
|
|
1426
1426
|
loadSessionMetadata(sessionId) {
|
|
1427
1427
|
const logger6 = getLogger();
|
|
1428
|
-
const metadataPath =
|
|
1428
|
+
const metadataPath = path9.join(this.sessionsDir, sessionId, "metadata.json");
|
|
1429
1429
|
if (!fs5.existsSync(metadataPath)) {
|
|
1430
1430
|
return null;
|
|
1431
1431
|
}
|
|
@@ -1449,7 +1449,7 @@ var StorageService = class {
|
|
|
1449
1449
|
return [];
|
|
1450
1450
|
}
|
|
1451
1451
|
return fs5.readdirSync(this.sessionsDir).filter((entry) => {
|
|
1452
|
-
const entryPath =
|
|
1452
|
+
const entryPath = path9.join(this.sessionsDir, entry);
|
|
1453
1453
|
return fs5.statSync(entryPath).isDirectory();
|
|
1454
1454
|
});
|
|
1455
1455
|
}
|
|
@@ -1458,13 +1458,13 @@ var StorageService = class {
|
|
|
1458
1458
|
* @returns Current session ID, or null.
|
|
1459
1459
|
*/
|
|
1460
1460
|
getCurrentSessionId() {
|
|
1461
|
-
const currentPath =
|
|
1461
|
+
const currentPath = path9.join(this.sessionsDir, "current");
|
|
1462
1462
|
if (!fs5.existsSync(currentPath)) {
|
|
1463
1463
|
return null;
|
|
1464
1464
|
}
|
|
1465
1465
|
try {
|
|
1466
1466
|
const target = fs5.readlinkSync(currentPath);
|
|
1467
|
-
return
|
|
1467
|
+
return path9.basename(target);
|
|
1468
1468
|
} catch {
|
|
1469
1469
|
return null;
|
|
1470
1470
|
}
|
|
@@ -1475,8 +1475,8 @@ var StorageService = class {
|
|
|
1475
1475
|
*/
|
|
1476
1476
|
setCurrentSession(sessionId) {
|
|
1477
1477
|
const logger6 = getLogger();
|
|
1478
|
-
const currentPath =
|
|
1479
|
-
const targetPath =
|
|
1478
|
+
const currentPath = path9.join(this.sessionsDir, "current");
|
|
1479
|
+
const targetPath = path9.join(this.sessionsDir, sessionId);
|
|
1480
1480
|
if (fs5.existsSync(currentPath)) {
|
|
1481
1481
|
fs5.unlinkSync(currentPath);
|
|
1482
1482
|
}
|
|
@@ -1491,7 +1491,7 @@ var StorageService = class {
|
|
|
1491
1491
|
const { sessionId, filename, data } = params;
|
|
1492
1492
|
const logger6 = getLogger();
|
|
1493
1493
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1494
|
-
const screenshotPath =
|
|
1494
|
+
const screenshotPath = path9.join(sessionDir, "screenshots", filename);
|
|
1495
1495
|
fs5.writeFileSync(screenshotPath, data);
|
|
1496
1496
|
logger6.debug("Saved screenshot", { sessionId, filename });
|
|
1497
1497
|
return screenshotPath;
|
|
@@ -1504,7 +1504,7 @@ var StorageService = class {
|
|
|
1504
1504
|
const { sessionId, content } = params;
|
|
1505
1505
|
const logger6 = getLogger();
|
|
1506
1506
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1507
|
-
const resultsPath =
|
|
1507
|
+
const resultsPath = path9.join(sessionDir, "results.md");
|
|
1508
1508
|
fs5.appendFileSync(resultsPath, content + "\n", "utf-8");
|
|
1509
1509
|
logger6.debug("Appended to results", { sessionId });
|
|
1510
1510
|
}
|
|
@@ -1514,7 +1514,7 @@ var StorageService = class {
|
|
|
1514
1514
|
* @returns Results content, or null if not found.
|
|
1515
1515
|
*/
|
|
1516
1516
|
getResults(sessionId) {
|
|
1517
|
-
const resultsPath =
|
|
1517
|
+
const resultsPath = path9.join(this.sessionsDir, sessionId, "results.md");
|
|
1518
1518
|
if (!fs5.existsSync(resultsPath)) {
|
|
1519
1519
|
return null;
|
|
1520
1520
|
}
|
|
@@ -1583,7 +1583,7 @@ var StorageService = class {
|
|
|
1583
1583
|
const { sessionId, targetUrl, testInstructions } = params;
|
|
1584
1584
|
const logger6 = getLogger();
|
|
1585
1585
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1586
|
-
const resultsPath =
|
|
1586
|
+
const resultsPath = path9.join(sessionDir, "results.md");
|
|
1587
1587
|
const header = [
|
|
1588
1588
|
`# Test Results: ${sessionId}`,
|
|
1589
1589
|
"",
|
|
@@ -1607,7 +1607,7 @@ var StorageService = class {
|
|
|
1607
1607
|
const { sessionId, step } = params;
|
|
1608
1608
|
const logger6 = getLogger();
|
|
1609
1609
|
const sessionDir = this.createSessionDirectory(sessionId);
|
|
1610
|
-
const resultsPath =
|
|
1610
|
+
const resultsPath = path9.join(sessionDir, "results.md");
|
|
1611
1611
|
const statusIcon = step.success ? "\u2713" : "\u2717";
|
|
1612
1612
|
const stepContent = [
|
|
1613
1613
|
`### Step ${step.stepNumber}: ${step.action}`,
|
|
@@ -1632,8 +1632,8 @@ var StorageService = class {
|
|
|
1632
1632
|
finalizeResults(params) {
|
|
1633
1633
|
const { sessionId, status, summary } = params;
|
|
1634
1634
|
const logger6 = getLogger();
|
|
1635
|
-
const sessionDir =
|
|
1636
|
-
const resultsPath =
|
|
1635
|
+
const sessionDir = path9.join(this.sessionsDir, sessionId);
|
|
1636
|
+
const resultsPath = path9.join(sessionDir, "results.md");
|
|
1637
1637
|
if (!fs5.existsSync(resultsPath)) {
|
|
1638
1638
|
logger6.warn("Results file not found for finalization", { sessionId });
|
|
1639
1639
|
return;
|
|
@@ -1715,7 +1715,7 @@ var StorageService = class {
|
|
|
1715
1715
|
}
|
|
1716
1716
|
const sessionDate = new Date(metadata.startTime);
|
|
1717
1717
|
if (sessionDate < cutoffDate) {
|
|
1718
|
-
const sessionDir =
|
|
1718
|
+
const sessionDir = path9.join(this.sessionsDir, sessionId);
|
|
1719
1719
|
try {
|
|
1720
1720
|
fs5.rmSync(sessionDir, { recursive: true, force: true });
|
|
1721
1721
|
deletedCount++;
|
|
@@ -1745,7 +1745,7 @@ var StorageService = class {
|
|
|
1745
1745
|
* @returns Session directory path.
|
|
1746
1746
|
*/
|
|
1747
1747
|
getSessionPath(sessionId) {
|
|
1748
|
-
return
|
|
1748
|
+
return path9.join(this.sessionsDir, sessionId);
|
|
1749
1749
|
}
|
|
1750
1750
|
/**
|
|
1751
1751
|
* Delete a specific session.
|
|
@@ -1754,7 +1754,7 @@ var StorageService = class {
|
|
|
1754
1754
|
*/
|
|
1755
1755
|
deleteSession(sessionId) {
|
|
1756
1756
|
const logger6 = getLogger();
|
|
1757
|
-
const sessionDir =
|
|
1757
|
+
const sessionDir = path9.join(this.sessionsDir, sessionId);
|
|
1758
1758
|
if (!fs5.existsSync(sessionDir)) {
|
|
1759
1759
|
logger6.warn("Session not found for deletion", { sessionId });
|
|
1760
1760
|
return false;
|
|
@@ -1762,7 +1762,7 @@ var StorageService = class {
|
|
|
1762
1762
|
try {
|
|
1763
1763
|
const currentId = this.getCurrentSessionId();
|
|
1764
1764
|
if (currentId === sessionId) {
|
|
1765
|
-
const currentPath =
|
|
1765
|
+
const currentPath = path9.join(this.sessionsDir, "current");
|
|
1766
1766
|
if (fs5.existsSync(currentPath)) {
|
|
1767
1767
|
fs5.unlinkSync(currentPath);
|
|
1768
1768
|
}
|
|
@@ -1796,8 +1796,8 @@ var RunResultStorageService = class {
|
|
|
1796
1796
|
constructor() {
|
|
1797
1797
|
const storageService = getStorageService();
|
|
1798
1798
|
const dataDir = storageService.getDataDir();
|
|
1799
|
-
this.runResultsDir =
|
|
1800
|
-
this.testScriptsDir =
|
|
1799
|
+
this.runResultsDir = path9.join(dataDir, "run-results");
|
|
1800
|
+
this.testScriptsDir = path9.join(dataDir, "test-scripts");
|
|
1801
1801
|
this.ensureDirectories();
|
|
1802
1802
|
}
|
|
1803
1803
|
/**
|
|
@@ -1823,7 +1823,7 @@ var RunResultStorageService = class {
|
|
|
1823
1823
|
const results = [];
|
|
1824
1824
|
for (const file of files) {
|
|
1825
1825
|
try {
|
|
1826
|
-
const content = fs5.readFileSync(
|
|
1826
|
+
const content = fs5.readFileSync(path9.join(this.runResultsDir, file), "utf-8");
|
|
1827
1827
|
results.push(JSON.parse(content));
|
|
1828
1828
|
} catch {
|
|
1829
1829
|
logger3.warn("Failed to read run result file", { file });
|
|
@@ -1838,7 +1838,7 @@ var RunResultStorageService = class {
|
|
|
1838
1838
|
* Get a run result by ID.
|
|
1839
1839
|
*/
|
|
1840
1840
|
getRunResult(runId) {
|
|
1841
|
-
const filePath =
|
|
1841
|
+
const filePath = path9.join(this.runResultsDir, `${runId}.json`);
|
|
1842
1842
|
if (!fs5.existsSync(filePath)) {
|
|
1843
1843
|
return void 0;
|
|
1844
1844
|
}
|
|
@@ -1853,7 +1853,7 @@ var RunResultStorageService = class {
|
|
|
1853
1853
|
* Save a run result.
|
|
1854
1854
|
*/
|
|
1855
1855
|
saveRunResult(result) {
|
|
1856
|
-
const filePath =
|
|
1856
|
+
const filePath = path9.join(this.runResultsDir, `${result.id}.json`);
|
|
1857
1857
|
fs5.writeFileSync(filePath, JSON.stringify(result, null, 2));
|
|
1858
1858
|
}
|
|
1859
1859
|
/**
|
|
@@ -1906,7 +1906,7 @@ var RunResultStorageService = class {
|
|
|
1906
1906
|
const scripts = [];
|
|
1907
1907
|
for (const file of files) {
|
|
1908
1908
|
try {
|
|
1909
|
-
const content = fs5.readFileSync(
|
|
1909
|
+
const content = fs5.readFileSync(path9.join(this.testScriptsDir, file), "utf-8");
|
|
1910
1910
|
scripts.push(JSON.parse(content));
|
|
1911
1911
|
} catch {
|
|
1912
1912
|
logger3.warn("Failed to read test script file", { file });
|
|
@@ -1921,7 +1921,7 @@ var RunResultStorageService = class {
|
|
|
1921
1921
|
* Get a test script by ID.
|
|
1922
1922
|
*/
|
|
1923
1923
|
getTestScript(testScriptId) {
|
|
1924
|
-
const filePath =
|
|
1924
|
+
const filePath = path9.join(this.testScriptsDir, `${testScriptId}.json`);
|
|
1925
1925
|
if (!fs5.existsSync(filePath)) {
|
|
1926
1926
|
return void 0;
|
|
1927
1927
|
}
|
|
@@ -1936,7 +1936,7 @@ var RunResultStorageService = class {
|
|
|
1936
1936
|
* Save a test script.
|
|
1937
1937
|
*/
|
|
1938
1938
|
saveTestScript(script) {
|
|
1939
|
-
const filePath =
|
|
1939
|
+
const filePath = path9.join(this.testScriptsDir, `${script.id}.json`);
|
|
1940
1940
|
fs5.writeFileSync(filePath, JSON.stringify(script, null, 2));
|
|
1941
1941
|
}
|
|
1942
1942
|
/**
|
|
@@ -1986,6 +1986,176 @@ function resetRunResultStorageService() {
|
|
|
1986
1986
|
instance = null;
|
|
1987
1987
|
}
|
|
1988
1988
|
|
|
1989
|
+
// packages/mcps/src/mcp/local/services/local-execution-lock-constants.ts
|
|
1990
|
+
var LOCK_FILE_NAME = "local-execution.lock";
|
|
1991
|
+
var MODIFY_LOCK_SUFFIX = ".modify";
|
|
1992
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
1993
|
+
var WAIT_LOG_INTERVAL_MS = 1e4;
|
|
1994
|
+
var MODIFY_LOCK_RETRY_MS = 50;
|
|
1995
|
+
var MODIFY_LOCK_STALE_MS = 5e3;
|
|
1996
|
+
|
|
1997
|
+
// packages/mcps/src/mcp/local/services/local-execution-lock.ts
|
|
1998
|
+
function lockFilePath() {
|
|
1999
|
+
return path9.join(getDataDir(), LOCK_FILE_NAME);
|
|
2000
|
+
}
|
|
2001
|
+
function modifyLockPath() {
|
|
2002
|
+
return lockFilePath() + MODIFY_LOCK_SUFFIX;
|
|
2003
|
+
}
|
|
2004
|
+
function normalizeCwd(cwd) {
|
|
2005
|
+
const resolved = path9.resolve(cwd);
|
|
2006
|
+
return os4.platform() === "win32" ? resolved.toLowerCase() : resolved;
|
|
2007
|
+
}
|
|
2008
|
+
function pidIsAlive(pid) {
|
|
2009
|
+
try {
|
|
2010
|
+
process.kill(pid, 0);
|
|
2011
|
+
return true;
|
|
2012
|
+
} catch (error) {
|
|
2013
|
+
const code = error.code;
|
|
2014
|
+
if (code === "ESRCH") return false;
|
|
2015
|
+
if (code === "EPERM") return true;
|
|
2016
|
+
return false;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
async function readState() {
|
|
2020
|
+
try {
|
|
2021
|
+
const raw = await fs8.readFile(lockFilePath(), "utf-8");
|
|
2022
|
+
return JSON.parse(raw);
|
|
2023
|
+
} catch (error) {
|
|
2024
|
+
if (error.code === "ENOENT") return null;
|
|
2025
|
+
throw error;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
async function writeState(state2) {
|
|
2029
|
+
await fs8.writeFile(lockFilePath(), JSON.stringify(state2, null, 2), "utf-8");
|
|
2030
|
+
}
|
|
2031
|
+
async function deleteStateFile() {
|
|
2032
|
+
try {
|
|
2033
|
+
await fs8.unlink(lockFilePath());
|
|
2034
|
+
} catch (error) {
|
|
2035
|
+
if (error.code !== "ENOENT") throw error;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
async function withModifyLock(fn) {
|
|
2039
|
+
await fs8.mkdir(path9.dirname(modifyLockPath()), { recursive: true });
|
|
2040
|
+
const modifyPath = modifyLockPath();
|
|
2041
|
+
let fileHandle = null;
|
|
2042
|
+
while (fileHandle === null) {
|
|
2043
|
+
try {
|
|
2044
|
+
fileHandle = await fs8.open(modifyPath, "wx");
|
|
2045
|
+
} catch (error) {
|
|
2046
|
+
if (error.code !== "EEXIST") throw error;
|
|
2047
|
+
try {
|
|
2048
|
+
const stat3 = await fs8.stat(modifyPath);
|
|
2049
|
+
if (Date.now() - stat3.mtimeMs > MODIFY_LOCK_STALE_MS) {
|
|
2050
|
+
await fs8.unlink(modifyPath).catch(() => void 0);
|
|
2051
|
+
continue;
|
|
2052
|
+
}
|
|
2053
|
+
} catch {
|
|
2054
|
+
}
|
|
2055
|
+
await new Promise((r) => setTimeout(r, MODIFY_LOCK_RETRY_MS));
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
try {
|
|
2059
|
+
await fileHandle.writeFile(JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
2060
|
+
return await fn();
|
|
2061
|
+
} finally {
|
|
2062
|
+
await fileHandle.close().catch(() => void 0);
|
|
2063
|
+
await fs8.unlink(modifyPath).catch(() => void 0);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
async function tryAcquireOnce(params) {
|
|
2067
|
+
return await withModifyLock(async () => {
|
|
2068
|
+
const existing = await readState();
|
|
2069
|
+
if (existing === null) {
|
|
2070
|
+
await writeState({
|
|
2071
|
+
holderCwd: params.normalizedCwd,
|
|
2072
|
+
holderPids: [params.pid],
|
|
2073
|
+
acquiredAt: Date.now()
|
|
2074
|
+
});
|
|
2075
|
+
return { acquired: true };
|
|
2076
|
+
}
|
|
2077
|
+
const liveHolderPids = existing.holderPids.filter((pid) => pidIsAlive(pid));
|
|
2078
|
+
if (liveHolderPids.length === 0) {
|
|
2079
|
+
getLogger().warn("Breaking stale local-execution lock", {
|
|
2080
|
+
staleCwd: existing.holderCwd,
|
|
2081
|
+
stalePids: existing.holderPids
|
|
2082
|
+
});
|
|
2083
|
+
await writeState({
|
|
2084
|
+
holderCwd: params.normalizedCwd,
|
|
2085
|
+
holderPids: [params.pid],
|
|
2086
|
+
acquiredAt: Date.now()
|
|
2087
|
+
});
|
|
2088
|
+
return { acquired: true };
|
|
2089
|
+
}
|
|
2090
|
+
if (existing.holderCwd === params.normalizedCwd) {
|
|
2091
|
+
const updated = {
|
|
2092
|
+
holderCwd: existing.holderCwd,
|
|
2093
|
+
holderPids: [...liveHolderPids, params.pid],
|
|
2094
|
+
acquiredAt: existing.acquiredAt
|
|
2095
|
+
};
|
|
2096
|
+
await writeState(updated);
|
|
2097
|
+
return { acquired: true };
|
|
2098
|
+
}
|
|
2099
|
+
return {
|
|
2100
|
+
acquired: false,
|
|
2101
|
+
blockingState: { ...existing, holderPids: liveHolderPids }
|
|
2102
|
+
};
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
async function decrementHolder(params) {
|
|
2106
|
+
await withModifyLock(async () => {
|
|
2107
|
+
const existing = await readState();
|
|
2108
|
+
if (existing === null) return;
|
|
2109
|
+
if (existing.holderCwd !== params.normalizedCwd) {
|
|
2110
|
+
getLogger().warn("Release skipped \u2014 holder cwd no longer matches", {
|
|
2111
|
+
ourCwd: params.normalizedCwd,
|
|
2112
|
+
holderCwd: existing.holderCwd
|
|
2113
|
+
});
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
const removalIndex = existing.holderPids.indexOf(params.pid);
|
|
2117
|
+
const remainingPids = removalIndex >= 0 ? [...existing.holderPids.slice(0, removalIndex), ...existing.holderPids.slice(removalIndex + 1)] : existing.holderPids;
|
|
2118
|
+
if (remainingPids.length === 0) {
|
|
2119
|
+
await deleteStateFile();
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
await writeState({
|
|
2123
|
+
holderCwd: existing.holderCwd,
|
|
2124
|
+
holderPids: remainingPids,
|
|
2125
|
+
acquiredAt: existing.acquiredAt
|
|
2126
|
+
});
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
async function acquireLocalExecutionLock(params) {
|
|
2130
|
+
const normalizedCwd = normalizeCwd(params.cwd);
|
|
2131
|
+
const pid = process.pid;
|
|
2132
|
+
const startWaitAt = Date.now();
|
|
2133
|
+
let lastWaitLogAt = 0;
|
|
2134
|
+
while (true) {
|
|
2135
|
+
const outcome = await tryAcquireOnce({ normalizedCwd, pid });
|
|
2136
|
+
if (outcome.acquired) {
|
|
2137
|
+
return {
|
|
2138
|
+
release: () => decrementHolder({ normalizedCwd, pid })
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
const now = Date.now();
|
|
2142
|
+
if (now - lastWaitLogAt >= WAIT_LOG_INTERVAL_MS) {
|
|
2143
|
+
const waitedSec = Math.round((now - startWaitAt) / 1e3);
|
|
2144
|
+
getLogger().info("Waiting for local-execution lock", {
|
|
2145
|
+
ourCwd: normalizedCwd,
|
|
2146
|
+
heldBy: outcome.blockingState?.holderCwd,
|
|
2147
|
+
heldByPids: outcome.blockingState?.holderPids,
|
|
2148
|
+
waitedSec
|
|
2149
|
+
});
|
|
2150
|
+
lastWaitLogAt = now;
|
|
2151
|
+
}
|
|
2152
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
async function readLocalExecutionLockState() {
|
|
2156
|
+
return readState();
|
|
2157
|
+
}
|
|
2158
|
+
|
|
1989
2159
|
// packages/mcps/src/mcp/local/services/replay-url-rewrite.ts
|
|
1990
2160
|
function rewriteActionScriptUrls(params) {
|
|
1991
2161
|
const { actionScript, originalUrl, localUrl } = params;
|
|
@@ -2045,15 +2215,15 @@ function getAuthenticatedUserId() {
|
|
|
2045
2215
|
}
|
|
2046
2216
|
async function findPackageJsonAsync() {
|
|
2047
2217
|
const currentFileUrl = import.meta.url;
|
|
2048
|
-
const currentDir =
|
|
2218
|
+
const currentDir = path9.dirname(fileURLToPath(currentFileUrl));
|
|
2049
2219
|
const candidatePaths = [
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2220
|
+
path9.resolve(currentDir, "../../../package.json"),
|
|
2221
|
+
path9.resolve(currentDir, "../package.json"),
|
|
2222
|
+
path9.resolve(currentDir, "../../package.json")
|
|
2053
2223
|
];
|
|
2054
2224
|
for (const candidatePath of candidatePaths) {
|
|
2055
2225
|
try {
|
|
2056
|
-
const packageJsonRaw = await
|
|
2226
|
+
const packageJsonRaw = await fs8.readFile(candidatePath, "utf-8");
|
|
2057
2227
|
const packageJson = JSON.parse(packageJsonRaw);
|
|
2058
2228
|
if (packageJson.name === "@muggleai/works" && packageJson.version && packageJson.muggleConfig?.electronAppVersion) {
|
|
2059
2229
|
return {
|
|
@@ -2075,8 +2245,8 @@ async function getLocalExecutionContextBaseAsync(params) {
|
|
|
2075
2245
|
originalUrl: params.originalUrl,
|
|
2076
2246
|
productionUrl: params.productionUrl,
|
|
2077
2247
|
runByUserId: params.runByUserId,
|
|
2078
|
-
machineHostname:
|
|
2079
|
-
osInfo: `${
|
|
2248
|
+
machineHostname: os4.hostname(),
|
|
2249
|
+
osInfo: `${os4.platform()} ${os4.release()} ${os4.arch()}`,
|
|
2080
2250
|
electronAppVersion: packageJson.muggleConfig.electronAppVersion,
|
|
2081
2251
|
mcpServerVersion: packageJson.version
|
|
2082
2252
|
};
|
|
@@ -2099,20 +2269,20 @@ function buildStudioAuthContent() {
|
|
|
2099
2269
|
}
|
|
2100
2270
|
async function ensureTempDir() {
|
|
2101
2271
|
const config = getConfig();
|
|
2102
|
-
const tempDir =
|
|
2103
|
-
await
|
|
2272
|
+
const tempDir = path9.join(config.localQa.dataDir, "temp");
|
|
2273
|
+
await fs8.mkdir(tempDir, { recursive: true });
|
|
2104
2274
|
return tempDir;
|
|
2105
2275
|
}
|
|
2106
2276
|
async function writeTempFile(params) {
|
|
2107
2277
|
const tempDir = await ensureTempDir();
|
|
2108
|
-
const filePath =
|
|
2109
|
-
await
|
|
2278
|
+
const filePath = path9.join(tempDir, params.filename);
|
|
2279
|
+
await fs8.writeFile(filePath, JSON.stringify(params.data, null, 2));
|
|
2110
2280
|
return filePath;
|
|
2111
2281
|
}
|
|
2112
2282
|
async function cleanupTempFiles(params) {
|
|
2113
2283
|
for (const filePath of params.filePaths) {
|
|
2114
2284
|
try {
|
|
2115
|
-
await
|
|
2285
|
+
await fs8.unlink(filePath);
|
|
2116
2286
|
} catch {
|
|
2117
2287
|
}
|
|
2118
2288
|
}
|
|
@@ -2120,12 +2290,12 @@ async function cleanupTempFiles(params) {
|
|
|
2120
2290
|
async function moveResultsToArtifacts(params) {
|
|
2121
2291
|
const storageService = getStorageService();
|
|
2122
2292
|
const sessionsDir = storageService.getSessionsDir();
|
|
2123
|
-
const artifactsDir =
|
|
2124
|
-
const actionScriptPath =
|
|
2125
|
-
await
|
|
2126
|
-
await
|
|
2293
|
+
const artifactsDir = path9.join(sessionsDir, params.runId);
|
|
2294
|
+
const actionScriptPath = path9.join(artifactsDir, "action-script.json");
|
|
2295
|
+
await fs8.mkdir(artifactsDir, { recursive: true });
|
|
2296
|
+
await fs8.copyFile(params.generatedScriptPath, actionScriptPath);
|
|
2127
2297
|
try {
|
|
2128
|
-
await
|
|
2298
|
+
await fs8.unlink(params.generatedScriptPath);
|
|
2129
2299
|
} catch {
|
|
2130
2300
|
}
|
|
2131
2301
|
return artifactsDir;
|
|
@@ -2133,19 +2303,92 @@ async function moveResultsToArtifacts(params) {
|
|
|
2133
2303
|
async function writeExecutionLogs(params) {
|
|
2134
2304
|
const storageService = getStorageService();
|
|
2135
2305
|
const sessionsDir = storageService.getSessionsDir();
|
|
2136
|
-
const artifactsDir =
|
|
2137
|
-
await
|
|
2138
|
-
const stdoutPath =
|
|
2139
|
-
const stderrPath =
|
|
2306
|
+
const artifactsDir = path9.join(sessionsDir, params.runId);
|
|
2307
|
+
await fs8.mkdir(artifactsDir, { recursive: true });
|
|
2308
|
+
const stdoutPath = path9.join(artifactsDir, "stdout.log");
|
|
2309
|
+
const stderrPath = path9.join(artifactsDir, "stderr.log");
|
|
2140
2310
|
await Promise.all([
|
|
2141
|
-
|
|
2142
|
-
|
|
2311
|
+
fs8.writeFile(stdoutPath, params.stdout, "utf-8"),
|
|
2312
|
+
fs8.writeFile(stderrPath, params.stderr, "utf-8")
|
|
2143
2313
|
]);
|
|
2144
2314
|
logger4.info("Wrote execution logs to artifacts directory", {
|
|
2145
2315
|
runId: params.runId,
|
|
2146
2316
|
artifactsDir
|
|
2147
2317
|
});
|
|
2148
2318
|
}
|
|
2319
|
+
async function preserveElectronRuntimeArtifacts(params) {
|
|
2320
|
+
try {
|
|
2321
|
+
const tempDir = await ensureTempDir();
|
|
2322
|
+
const markerPath = path9.join(tempDir, `${params.runId}_runtime_dir.txt`);
|
|
2323
|
+
let runtimeDir = null;
|
|
2324
|
+
try {
|
|
2325
|
+
const marker = await fs8.readFile(markerPath, "utf-8");
|
|
2326
|
+
const trimmed = marker.trim();
|
|
2327
|
+
if (trimmed.length > 0) runtimeDir = trimmed;
|
|
2328
|
+
} catch (markerReadError) {
|
|
2329
|
+
if (markerReadError.code !== "ENOENT") {
|
|
2330
|
+
logger4.warn("Failed to read runtime-dir marker", {
|
|
2331
|
+
runId: params.runId,
|
|
2332
|
+
error: String(markerReadError)
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
if (runtimeDir === null) {
|
|
2337
|
+
runtimeDir = await scanForRuntimeDirByMtime({
|
|
2338
|
+
electronCwd: params.electronCwd,
|
|
2339
|
+
spawnAtMs: params.spawnAtMs
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
if (runtimeDir === null) {
|
|
2343
|
+
logger4.info("No Electron runtime dir found to preserve", { runId: params.runId });
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
try {
|
|
2347
|
+
await fs8.access(runtimeDir);
|
|
2348
|
+
} catch {
|
|
2349
|
+
logger4.warn("Electron runtime dir referenced but missing on disk", {
|
|
2350
|
+
runId: params.runId,
|
|
2351
|
+
runtimeDir
|
|
2352
|
+
});
|
|
2353
|
+
await fs8.unlink(markerPath).catch(() => void 0);
|
|
2354
|
+
return;
|
|
2355
|
+
}
|
|
2356
|
+
const storage = getStorageService();
|
|
2357
|
+
const destDir = path9.join(storage.getSessionsDir(), params.runId, "electron-runtime");
|
|
2358
|
+
await fs8.mkdir(destDir, { recursive: true });
|
|
2359
|
+
await fs8.cp(runtimeDir, destDir, { recursive: true });
|
|
2360
|
+
logger4.info("Preserved Electron runtime artifacts", {
|
|
2361
|
+
runId: params.runId,
|
|
2362
|
+
sourceDir: runtimeDir,
|
|
2363
|
+
destDir
|
|
2364
|
+
});
|
|
2365
|
+
await fs8.unlink(markerPath).catch(() => void 0);
|
|
2366
|
+
} catch (preserveError) {
|
|
2367
|
+
logger4.warn("Failed to preserve Electron runtime artifacts", {
|
|
2368
|
+
runId: params.runId,
|
|
2369
|
+
error: preserveError instanceof Error ? preserveError.message : String(preserveError)
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
async function scanForRuntimeDirByMtime(params) {
|
|
2374
|
+
const runtimeRoot = path9.join(params.electronCwd, "user_data", "runtime");
|
|
2375
|
+
try {
|
|
2376
|
+
const entries = await fs8.readdir(runtimeRoot, { withFileTypes: true });
|
|
2377
|
+
let bestCandidate = null;
|
|
2378
|
+
for (const entry of entries) {
|
|
2379
|
+
if (!entry.isDirectory()) continue;
|
|
2380
|
+
const fullPath = path9.join(runtimeRoot, entry.name);
|
|
2381
|
+
const stat3 = await fs8.stat(fullPath);
|
|
2382
|
+
if (stat3.mtimeMs < params.spawnAtMs) continue;
|
|
2383
|
+
if (bestCandidate === null || stat3.mtimeMs > bestCandidate.mtimeMs) {
|
|
2384
|
+
bestCandidate = { path: fullPath, mtimeMs: stat3.mtimeMs };
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
return bestCandidate?.path ?? null;
|
|
2388
|
+
} catch {
|
|
2389
|
+
return null;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2149
2392
|
function getElectronAppPathOrThrow() {
|
|
2150
2393
|
const electronAppPath = resolveElectronAppPathOrNull();
|
|
2151
2394
|
if (electronAppPath && electronAppPath.trim() !== "") {
|
|
@@ -2283,10 +2526,11 @@ async function executeElectronAppAsync(params) {
|
|
|
2283
2526
|
});
|
|
2284
2527
|
const electronEnv = { ...process.env };
|
|
2285
2528
|
delete electronEnv.ELECTRON_RUN_AS_NODE;
|
|
2529
|
+
const electronCwd = path9.dirname(electronAppPath);
|
|
2286
2530
|
const child = spawn(electronAppPath, spawnArgs, {
|
|
2287
2531
|
stdio: ["ignore", "pipe", "pipe"],
|
|
2288
2532
|
env: electronEnv,
|
|
2289
|
-
cwd:
|
|
2533
|
+
cwd: electronCwd
|
|
2290
2534
|
});
|
|
2291
2535
|
const processInfo = {
|
|
2292
2536
|
runId: params.runId,
|
|
@@ -2297,7 +2541,7 @@ async function executeElectronAppAsync(params) {
|
|
|
2297
2541
|
capturedStderr: ""
|
|
2298
2542
|
};
|
|
2299
2543
|
activeProcesses.set(params.runId, processInfo);
|
|
2300
|
-
return await new Promise((
|
|
2544
|
+
return await new Promise((resolve4, reject) => {
|
|
2301
2545
|
let settled = false;
|
|
2302
2546
|
const finalize = (result) => {
|
|
2303
2547
|
if (settled) {
|
|
@@ -2309,7 +2553,7 @@ async function executeElectronAppAsync(params) {
|
|
|
2309
2553
|
}
|
|
2310
2554
|
activeProcesses.delete(params.runId);
|
|
2311
2555
|
if (result.ok) {
|
|
2312
|
-
|
|
2556
|
+
resolve4(result.payload);
|
|
2313
2557
|
} else {
|
|
2314
2558
|
reject(result.payload);
|
|
2315
2559
|
}
|
|
@@ -2363,13 +2607,22 @@ ${processInfo.capturedStderr}`
|
|
|
2363
2607
|
payload: {
|
|
2364
2608
|
exitCode,
|
|
2365
2609
|
stdout: processInfo.capturedStdout,
|
|
2366
|
-
stderr: processInfo.capturedStderr
|
|
2610
|
+
stderr: processInfo.capturedStderr,
|
|
2611
|
+
electronCwd
|
|
2367
2612
|
}
|
|
2368
2613
|
});
|
|
2369
2614
|
});
|
|
2370
2615
|
});
|
|
2371
2616
|
}
|
|
2372
2617
|
async function executeTestGeneration(params) {
|
|
2618
|
+
const lockHandle = await acquireLocalExecutionLock({ cwd: params.cwd });
|
|
2619
|
+
try {
|
|
2620
|
+
return await runTestGenerationLocked(params);
|
|
2621
|
+
} finally {
|
|
2622
|
+
await lockHandle.release();
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
async function runTestGenerationLocked(params) {
|
|
2373
2626
|
const { testCase, localUrl } = params;
|
|
2374
2627
|
const timeoutMs = params.timeoutMs ?? 3e5;
|
|
2375
2628
|
const userId = getAuthenticatedUserId();
|
|
@@ -2438,17 +2691,35 @@ async function executeTestGeneration(params) {
|
|
|
2438
2691
|
stdout: executionResult.stdout,
|
|
2439
2692
|
stderr: executionResult.stderr
|
|
2440
2693
|
});
|
|
2694
|
+
await preserveElectronRuntimeArtifacts({
|
|
2695
|
+
runId,
|
|
2696
|
+
electronCwd: executionResult.electronCwd,
|
|
2697
|
+
spawnAtMs: startedAt
|
|
2698
|
+
});
|
|
2441
2699
|
if (executionResult.exitCode !== 0) {
|
|
2442
2700
|
const failureMessage = `Electron exited with code ${executionResult.exitCode}.
|
|
2443
2701
|
STDOUT:
|
|
2444
2702
|
${executionResult.stdout}
|
|
2445
2703
|
STDERR:
|
|
2446
2704
|
${executionResult.stderr}`;
|
|
2705
|
+
const sessionsDir = getStorageService().getSessionsDir();
|
|
2706
|
+
const artifactsDir2 = path9.join(sessionsDir, runId);
|
|
2707
|
+
const generatedScriptPath2 = path9.join(
|
|
2708
|
+
path9.dirname(inputFilePath),
|
|
2709
|
+
`gen_${path9.basename(inputFilePath)}`
|
|
2710
|
+
);
|
|
2711
|
+
try {
|
|
2712
|
+
await fs8.copyFile(generatedScriptPath2, path9.join(artifactsDir2, "action-script.json"));
|
|
2713
|
+
await fs8.unlink(generatedScriptPath2).catch(() => {
|
|
2714
|
+
});
|
|
2715
|
+
} catch {
|
|
2716
|
+
}
|
|
2447
2717
|
storage.updateRunResult(runId, {
|
|
2448
2718
|
status: "failed",
|
|
2449
2719
|
testScriptId: localTestScript.id,
|
|
2450
2720
|
executionTimeMs,
|
|
2451
2721
|
errorMessage: failureMessage,
|
|
2722
|
+
artifactsDir: artifactsDir2,
|
|
2452
2723
|
localExecutionContext: {
|
|
2453
2724
|
...localExecutionContextBase,
|
|
2454
2725
|
localExecutionCompletedAt: completedAt
|
|
@@ -2465,11 +2736,11 @@ ${executionResult.stderr}`;
|
|
|
2465
2736
|
errorMessage: failureMessage
|
|
2466
2737
|
};
|
|
2467
2738
|
}
|
|
2468
|
-
const generatedScriptPath =
|
|
2469
|
-
|
|
2470
|
-
`gen_${
|
|
2739
|
+
const generatedScriptPath = path9.join(
|
|
2740
|
+
path9.dirname(inputFilePath),
|
|
2741
|
+
`gen_${path9.basename(inputFilePath)}`
|
|
2471
2742
|
);
|
|
2472
|
-
const generatedScriptRaw = await
|
|
2743
|
+
const generatedScriptRaw = await fs8.readFile(generatedScriptPath, "utf-8");
|
|
2473
2744
|
const generatedScript = JSON.parse(generatedScriptRaw);
|
|
2474
2745
|
const generatedSteps = generatedScript.steps;
|
|
2475
2746
|
if (!Array.isArray(generatedSteps)) {
|
|
@@ -2528,6 +2799,14 @@ ${executionResult.stderr}`;
|
|
|
2528
2799
|
}
|
|
2529
2800
|
}
|
|
2530
2801
|
async function executeReplay(params) {
|
|
2802
|
+
const lockHandle = await acquireLocalExecutionLock({ cwd: params.cwd });
|
|
2803
|
+
try {
|
|
2804
|
+
return await runReplayLocked(params);
|
|
2805
|
+
} finally {
|
|
2806
|
+
await lockHandle.release();
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
async function runReplayLocked(params) {
|
|
2531
2810
|
const { testScript, actionScript, localUrl } = params;
|
|
2532
2811
|
const timeoutMs = params.timeoutMs ?? 18e4;
|
|
2533
2812
|
const userId = getAuthenticatedUserId();
|
|
@@ -2590,16 +2869,23 @@ async function executeReplay(params) {
|
|
|
2590
2869
|
stdout: executionResult.stdout,
|
|
2591
2870
|
stderr: executionResult.stderr
|
|
2592
2871
|
});
|
|
2872
|
+
await preserveElectronRuntimeArtifacts({
|
|
2873
|
+
runId,
|
|
2874
|
+
electronCwd: executionResult.electronCwd,
|
|
2875
|
+
spawnAtMs: startedAt
|
|
2876
|
+
});
|
|
2593
2877
|
if (executionResult.exitCode !== 0) {
|
|
2594
2878
|
const failureMessage = `Electron exited with code ${executionResult.exitCode}.
|
|
2595
2879
|
STDOUT:
|
|
2596
2880
|
${executionResult.stdout}
|
|
2597
2881
|
STDERR:
|
|
2598
2882
|
${executionResult.stderr}`;
|
|
2883
|
+
const artifactsDir2 = path9.join(getStorageService().getSessionsDir(), runId);
|
|
2599
2884
|
storage.updateRunResult(runId, {
|
|
2600
2885
|
status: "failed",
|
|
2601
2886
|
executionTimeMs,
|
|
2602
2887
|
errorMessage: failureMessage,
|
|
2888
|
+
artifactsDir: artifactsDir2,
|
|
2603
2889
|
localExecutionContext: {
|
|
2604
2890
|
...localExecutionContextBase,
|
|
2605
2891
|
localExecutionCompletedAt: completedAt
|
|
@@ -2612,7 +2898,7 @@ ${executionResult.stderr}`;
|
|
|
2612
2898
|
errorMessage: failureMessage
|
|
2613
2899
|
};
|
|
2614
2900
|
}
|
|
2615
|
-
const artifactsDir =
|
|
2901
|
+
const artifactsDir = path9.join(getStorageService().getSessionsDir(), runId);
|
|
2616
2902
|
storage.updateRunResult(runId, {
|
|
2617
2903
|
status: "passed",
|
|
2618
2904
|
executionTimeMs,
|
|
@@ -3045,7 +3331,7 @@ var PREFS_FILENAME = "preferences.json";
|
|
|
3045
3331
|
var OAUTH_SESSION_FILENAME = "oauth-session.json";
|
|
3046
3332
|
var RELEASE_MANIFEST_FILENAME = "release-manifest.json";
|
|
3047
3333
|
function getDataDir3() {
|
|
3048
|
-
return
|
|
3334
|
+
return path9.join(os4.homedir(), DATA_DIR_NAME2);
|
|
3049
3335
|
}
|
|
3050
3336
|
|
|
3051
3337
|
var cachedInstallId;
|
|
@@ -3110,9 +3396,9 @@ function isValid2(value) {
|
|
|
3110
3396
|
function readReleaseManifest2() {
|
|
3111
3397
|
if (cachedManifest2 !== void 0)
|
|
3112
3398
|
return cachedManifest2;
|
|
3113
|
-
const
|
|
3399
|
+
const path16 = MANIFEST_PATH2;
|
|
3114
3400
|
try {
|
|
3115
|
-
const raw = readFileSync(
|
|
3401
|
+
const raw = readFileSync(path16, "utf8");
|
|
3116
3402
|
const parsed = JSON.parse(raw);
|
|
3117
3403
|
cachedManifest2 = isValid2(parsed) ? parsed : DEV_MANIFEST2;
|
|
3118
3404
|
return cachedManifest2;
|
|
@@ -3666,7 +3952,11 @@ var ProjectTestScriptsSummaryInputSchema = z.object({
|
|
|
3666
3952
|
projectId: IdSchema.describe("Project ID (UUID) to get test scripts summary for")
|
|
3667
3953
|
});
|
|
3668
3954
|
var ProjectTestRunsSummaryInputSchema = z.object({
|
|
3669
|
-
projectId: IdSchema.describe("Project ID (UUID) to get test runs summary for")
|
|
3955
|
+
projectId: IdSchema.describe("Project ID (UUID) to get test runs summary for"),
|
|
3956
|
+
page: z.number().int().positive().default(1).describe("Page number, 1-based. Defaults to 1."),
|
|
3957
|
+
pageSize: z.number().int().positive().max(100).default(20).describe("Number of runs per page. Defaults to 20, max 100."),
|
|
3958
|
+
sortBy: z.enum(["lastRunAt", "status", "testCaseTitle"]).default("lastRunAt").describe("Sort field for the runs slice. Defaults to lastRunAt."),
|
|
3959
|
+
sortOrder: z.enum(["asc", "desc"]).default("desc").describe("Sort direction. Defaults to desc (most recent / Z\u2192A first).")
|
|
3670
3960
|
});
|
|
3671
3961
|
var ReportStatsSummaryInputSchema = z.object({
|
|
3672
3962
|
projectId: IdSchema.describe("Project ID (UUID) to get report stats for")
|
|
@@ -3872,17 +4162,17 @@ var PromptServiceClient = class {
|
|
|
3872
4162
|
* @param path - Path to validate.
|
|
3873
4163
|
* @throws GatewayError if path is not allowed.
|
|
3874
4164
|
*/
|
|
3875
|
-
validatePath(
|
|
3876
|
-
const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) =>
|
|
4165
|
+
validatePath(path16) {
|
|
4166
|
+
const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) => path16.startsWith(prefix));
|
|
3877
4167
|
if (!isAllowed) {
|
|
3878
4168
|
const logger6 = getLogger();
|
|
3879
4169
|
logger6.error("Path not in allowlist", {
|
|
3880
|
-
path:
|
|
4170
|
+
path: path16,
|
|
3881
4171
|
allowedPrefixes: ALLOWED_UPSTREAM_PREFIXES
|
|
3882
4172
|
});
|
|
3883
4173
|
throw new GatewayError({
|
|
3884
4174
|
code: "FORBIDDEN" /* FORBIDDEN */,
|
|
3885
|
-
message: `Path '${
|
|
4175
|
+
message: `Path '${path16}' is not allowed`
|
|
3886
4176
|
});
|
|
3887
4177
|
}
|
|
3888
4178
|
}
|
|
@@ -4100,6 +4390,59 @@ function getPromptServiceClient() {
|
|
|
4100
4390
|
return clientInstance;
|
|
4101
4391
|
}
|
|
4102
4392
|
|
|
4393
|
+
// packages/mcps/src/mcp/tools/e2e/test-runs-summary-transform.ts
|
|
4394
|
+
var UNKNOWN_STATUS = "UNKNOWN";
|
|
4395
|
+
function slimEntry(raw) {
|
|
4396
|
+
const slim = { status: raw.status ?? UNKNOWN_STATUS };
|
|
4397
|
+
if (raw.testCase?.id) slim.testCaseId = raw.testCase.id;
|
|
4398
|
+
if (raw.testCase?.title) slim.testCaseTitle = raw.testCase.title;
|
|
4399
|
+
if (raw.useCase?.id) slim.useCaseId = raw.useCase.id;
|
|
4400
|
+
if (raw.useCase?.title) slim.useCaseTitle = raw.useCase.title;
|
|
4401
|
+
if (typeof raw.lastRunAt === "number") slim.lastRunAt = raw.lastRunAt;
|
|
4402
|
+
if (raw.error) slim.error = raw.error;
|
|
4403
|
+
if (raw.latestWorkflowRun?.id) slim.latestWorkflowRunId = raw.latestWorkflowRun.id;
|
|
4404
|
+
return slim;
|
|
4405
|
+
}
|
|
4406
|
+
function aggregateByStatus(entries) {
|
|
4407
|
+
const counts = {};
|
|
4408
|
+
for (const entry of entries) {
|
|
4409
|
+
const status = entry.status ?? UNKNOWN_STATUS;
|
|
4410
|
+
counts[status] = (counts[status] ?? 0) + 1;
|
|
4411
|
+
}
|
|
4412
|
+
return counts;
|
|
4413
|
+
}
|
|
4414
|
+
function sortRuns(runs, sortBy, sortOrder) {
|
|
4415
|
+
const sign = sortOrder === "asc" ? 1 : -1;
|
|
4416
|
+
return [...runs].sort((a, b) => {
|
|
4417
|
+
const av = a[sortBy];
|
|
4418
|
+
const bv = b[sortBy];
|
|
4419
|
+
if (av === void 0 && bv === void 0) return 0;
|
|
4420
|
+
if (av === void 0) return 1;
|
|
4421
|
+
if (bv === void 0) return -1;
|
|
4422
|
+
if (av < bv) return -1 * sign;
|
|
4423
|
+
if (av > bv) return 1 * sign;
|
|
4424
|
+
return 0;
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
function mapTestRunsSummary(response, input) {
|
|
4428
|
+
const params = input;
|
|
4429
|
+
const raw = Array.isArray(response.data) ? response.data : [];
|
|
4430
|
+
const byStatus = aggregateByStatus(raw);
|
|
4431
|
+
const sorted = sortRuns(raw.map(slimEntry), params.sortBy, params.sortOrder);
|
|
4432
|
+
const total = sorted.length;
|
|
4433
|
+
const totalPages = Math.max(1, Math.ceil(total / params.pageSize));
|
|
4434
|
+
const start = (params.page - 1) * params.pageSize;
|
|
4435
|
+
const runs = sorted.slice(start, start + params.pageSize);
|
|
4436
|
+
return {
|
|
4437
|
+
totals: { total, byStatus },
|
|
4438
|
+
page: params.page,
|
|
4439
|
+
pageSize: params.pageSize,
|
|
4440
|
+
totalPages,
|
|
4441
|
+
hasMore: params.page < totalPages,
|
|
4442
|
+
runs
|
|
4443
|
+
};
|
|
4444
|
+
}
|
|
4445
|
+
|
|
4103
4446
|
// packages/mcps/src/mcp/tools/e2e/tool-registry.ts
|
|
4104
4447
|
var MUGGLE_TEST_PREFIX = "/v1/protected/muggle-test";
|
|
4105
4448
|
var getWorkflowTimeoutMs = () => getConfig().e2e.workflowTimeoutMs;
|
|
@@ -4896,7 +5239,7 @@ var reportTools = [
|
|
|
4896
5239
|
},
|
|
4897
5240
|
{
|
|
4898
5241
|
name: "muggle-remote-project-test-runs-summary-get",
|
|
4899
|
-
description: "Get a summary of test runs for a project.",
|
|
5242
|
+
description: "Get a paginated, slimmed summary of latest test runs for a project. Response shape: { totals: { total, byStatus }, page, pageSize, totalPages, hasMore, runs: [{ status, testCaseId, testCaseTitle, useCaseId, useCaseTitle, lastRunAt, error, latestWorkflowRunId }] }. Returns 20 runs per page by default (max 100). `totals` covers the full project; `runs` is the page slice. Check `hasMore` to decide whether to fetch additional pages. Use muggle-remote-test-case-get / muggle-remote-wf-get-ts-replay-latest-run for full per-run detail.",
|
|
4900
5243
|
inputSchema: ProjectTestRunsSummaryInputSchema,
|
|
4901
5244
|
mapToUpstream: (input) => {
|
|
4902
5245
|
const data = input;
|
|
@@ -4904,7 +5247,8 @@ var reportTools = [
|
|
|
4904
5247
|
method: "GET",
|
|
4905
5248
|
path: `${MUGGLE_TEST_PREFIX}/projects/${data.projectId}/test-runs/summary`
|
|
4906
5249
|
};
|
|
4907
|
-
}
|
|
5250
|
+
},
|
|
5251
|
+
mapFromUpstream: mapTestRunsSummary
|
|
4908
5252
|
},
|
|
4909
5253
|
{
|
|
4910
5254
|
name: "muggle-remote-report-stats-summary-get",
|
|
@@ -5754,6 +6098,8 @@ var ExecuteTestGenerationInputSchema = z.object({
|
|
|
5754
6098
|
testCase: TestCaseDetailsSchema.describe("Test case details obtained from muggle-remote-test-case-get"),
|
|
5755
6099
|
/** Local URL to test against. */
|
|
5756
6100
|
localUrl: z.string().url().describe("Local URL to test against (e.g., http://localhost:3000)"),
|
|
6101
|
+
/** Absolute path identifying the caller's worktree. Drives the cross-worktree single-flight lock — same path = parallel allowed (intra-change), different path = waits its turn. Required: ambiguity here causes the lock to mis-key, splitting one logical change into two competing holders. */
|
|
6102
|
+
cwd: z.string().min(1).describe("Absolute path of the caller's worktree. Required \u2014 this keys the cross-worktree single-flight lock so concurrent muggle-test runs from different branches serialize."),
|
|
5757
6103
|
/** Optional timeout. */
|
|
5758
6104
|
timeoutMs: z.number().int().positive().optional().describe("Timeout in milliseconds (default: 300000 = 5 min)"),
|
|
5759
6105
|
/** Show the electron-app UI during execution. Default: visible window. Pass false to run headless. */
|
|
@@ -5770,6 +6116,8 @@ var ExecuteReplayInputSchema = z.object({
|
|
|
5770
6116
|
actionScript: z.array(z.unknown()).describe("Action script steps from muggle-remote-action-script-get"),
|
|
5771
6117
|
/** Local URL to test against. */
|
|
5772
6118
|
localUrl: z.string().url().describe("Local URL to test against (e.g., http://localhost:3000)"),
|
|
6119
|
+
/** Absolute path identifying the caller's worktree. Drives the cross-worktree single-flight lock — same path = parallel allowed (intra-change), different path = waits its turn. Required: ambiguity here causes the lock to mis-key, splitting one logical change into two competing holders. */
|
|
6120
|
+
cwd: z.string().min(1).describe("Absolute path of the caller's worktree. Required \u2014 this keys the cross-worktree single-flight lock so concurrent muggle-test runs from different branches serialize."),
|
|
5773
6121
|
/** Optional timeout. */
|
|
5774
6122
|
timeoutMs: z.number().int().positive().optional().describe("Timeout in milliseconds (default: 180000 = 3 min)"),
|
|
5775
6123
|
/** Show the electron-app UI during execution. Default: visible window. Pass false to run headless. */
|
|
@@ -6031,9 +6379,9 @@ var TELEMETRY_DIR_NAME = "telemetry";
|
|
|
6031
6379
|
var FAILURE_EVENTS_FILE_NAME = "failure-events.jsonl";
|
|
6032
6380
|
function appendFailureEvent(record) {
|
|
6033
6381
|
try {
|
|
6034
|
-
const dir =
|
|
6382
|
+
const dir = path9.join(getDataDir(), TELEMETRY_DIR_NAME);
|
|
6035
6383
|
fs5.mkdirSync(dir, { recursive: true });
|
|
6036
|
-
const filePath =
|
|
6384
|
+
const filePath = path9.join(dir, FAILURE_EVENTS_FILE_NAME);
|
|
6037
6385
|
const full = { ts: (/* @__PURE__ */ new Date()).toISOString(), ...record };
|
|
6038
6386
|
fs5.appendFileSync(filePath, `${JSON.stringify(full)}
|
|
6039
6387
|
`, "utf-8");
|
|
@@ -6044,7 +6392,7 @@ function appendFailureEvent(record) {
|
|
|
6044
6392
|
}
|
|
6045
6393
|
}
|
|
6046
6394
|
function isFirstRun(dataDirOverride2) {
|
|
6047
|
-
const filePath =
|
|
6395
|
+
const filePath = path9.join(dataDirOverride2 ?? getDataDir(), PREFERENCES_FILE_NAME);
|
|
6048
6396
|
return !fs5.existsSync(filePath);
|
|
6049
6397
|
}
|
|
6050
6398
|
function readPreferencesFile(filePath) {
|
|
@@ -6063,12 +6411,12 @@ function readPreferencesFile(filePath) {
|
|
|
6063
6411
|
}
|
|
6064
6412
|
}
|
|
6065
6413
|
function readGlobalPreferences(dataDirOverride2) {
|
|
6066
|
-
const filePath =
|
|
6414
|
+
const filePath = path9.join(dataDirOverride2 ?? getDataDir(), PREFERENCES_FILE_NAME);
|
|
6067
6415
|
const partial = readPreferencesFile(filePath);
|
|
6068
6416
|
return { ...DEFAULT_PREFERENCES, ...partial };
|
|
6069
6417
|
}
|
|
6070
6418
|
function readProjectPreferences(cwd) {
|
|
6071
|
-
const filePath =
|
|
6419
|
+
const filePath = path9.join(cwd, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_FILE_NAME);
|
|
6072
6420
|
return readPreferencesFile(filePath);
|
|
6073
6421
|
}
|
|
6074
6422
|
function resolvePreferences(dataDirOverride2, cwd) {
|
|
@@ -6080,9 +6428,9 @@ function resolvePreferences(dataDirOverride2, cwd) {
|
|
|
6080
6428
|
return { ...global, ...project };
|
|
6081
6429
|
}
|
|
6082
6430
|
function writePreferences2(prefs, scope, dataDirOverride2, cwd) {
|
|
6083
|
-
const dir = scope === "project" && cwd ?
|
|
6431
|
+
const dir = scope === "project" && cwd ? path9.join(cwd, PREFERENCES_PROJECT_DIR_NAME) : dataDirOverride2 ?? getDataDir();
|
|
6084
6432
|
fs5.mkdirSync(dir, { recursive: true });
|
|
6085
|
-
const filePath =
|
|
6433
|
+
const filePath = path9.join(dir, PREFERENCES_FILE_NAME);
|
|
6086
6434
|
const existing = readPreferencesFile(filePath);
|
|
6087
6435
|
const merged = { ...existing, ...prefs };
|
|
6088
6436
|
const file = {
|
|
@@ -6093,8 +6441,8 @@ function writePreferences2(prefs, scope, dataDirOverride2, cwd) {
|
|
|
6093
6441
|
`, "utf-8");
|
|
6094
6442
|
}
|
|
6095
6443
|
function resetPreference(key, scope, dataDirOverride2, cwd) {
|
|
6096
|
-
const dir = scope === "project" && cwd ?
|
|
6097
|
-
const filePath =
|
|
6444
|
+
const dir = scope === "project" && cwd ? path9.join(cwd, PREFERENCES_PROJECT_DIR_NAME) : dataDirOverride2 ?? getDataDir();
|
|
6445
|
+
const filePath = path9.join(dir, PREFERENCES_FILE_NAME);
|
|
6098
6446
|
if (!fs5.existsSync(filePath)) {
|
|
6099
6447
|
return;
|
|
6100
6448
|
}
|
|
@@ -6125,7 +6473,7 @@ var LAST_PROJECT_FILE_NAME = "last-project.json";
|
|
|
6125
6473
|
var LAST_PROJECT_DIR_NAME = ".muggle-ai";
|
|
6126
6474
|
var LAST_PROJECT_VERSION = 1;
|
|
6127
6475
|
function readLastProject(cwd) {
|
|
6128
|
-
const filePath =
|
|
6476
|
+
const filePath = path9.join(cwd, LAST_PROJECT_DIR_NAME, LAST_PROJECT_FILE_NAME);
|
|
6129
6477
|
try {
|
|
6130
6478
|
if (!fs5.existsSync(filePath)) {
|
|
6131
6479
|
return null;
|
|
@@ -6141,9 +6489,9 @@ function readLastProject(cwd) {
|
|
|
6141
6489
|
}
|
|
6142
6490
|
}
|
|
6143
6491
|
function writeLastProject(cwd, lastProject) {
|
|
6144
|
-
const dir =
|
|
6492
|
+
const dir = path9.join(cwd, LAST_PROJECT_DIR_NAME);
|
|
6145
6493
|
fs5.mkdirSync(dir, { recursive: true });
|
|
6146
|
-
const filePath =
|
|
6494
|
+
const filePath = path9.join(dir, LAST_PROJECT_FILE_NAME);
|
|
6147
6495
|
const file = {
|
|
6148
6496
|
version: LAST_PROJECT_VERSION,
|
|
6149
6497
|
lastProject: { ...lastProject, savedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
@@ -6152,7 +6500,7 @@ function writeLastProject(cwd, lastProject) {
|
|
|
6152
6500
|
`, "utf-8");
|
|
6153
6501
|
}
|
|
6154
6502
|
function clearLastProject(cwd) {
|
|
6155
|
-
const filePath =
|
|
6503
|
+
const filePath = path9.join(cwd, LAST_PROJECT_DIR_NAME, LAST_PROJECT_FILE_NAME);
|
|
6156
6504
|
if (fs5.existsSync(filePath)) {
|
|
6157
6505
|
fs5.unlinkSync(filePath);
|
|
6158
6506
|
}
|
|
@@ -6161,7 +6509,7 @@ var LAST_HOST_FILE_NAME = "last-host.json";
|
|
|
6161
6509
|
var LAST_HOST_DIR_NAME = ".muggle-ai";
|
|
6162
6510
|
var LAST_HOST_VERSION = 1;
|
|
6163
6511
|
function readLastHost(cwd) {
|
|
6164
|
-
const filePath =
|
|
6512
|
+
const filePath = path9.join(cwd, LAST_HOST_DIR_NAME, LAST_HOST_FILE_NAME);
|
|
6165
6513
|
try {
|
|
6166
6514
|
if (!fs5.existsSync(filePath)) {
|
|
6167
6515
|
return null;
|
|
@@ -6177,9 +6525,9 @@ function readLastHost(cwd) {
|
|
|
6177
6525
|
}
|
|
6178
6526
|
}
|
|
6179
6527
|
function writeLastHost(cwd, host) {
|
|
6180
|
-
const dir =
|
|
6528
|
+
const dir = path9.join(cwd, LAST_HOST_DIR_NAME);
|
|
6181
6529
|
fs5.mkdirSync(dir, { recursive: true });
|
|
6182
|
-
const filePath =
|
|
6530
|
+
const filePath = path9.join(dir, LAST_HOST_FILE_NAME);
|
|
6183
6531
|
const file = {
|
|
6184
6532
|
version: LAST_HOST_VERSION,
|
|
6185
6533
|
lastHost: { host, savedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
@@ -6188,7 +6536,7 @@ function writeLastHost(cwd, host) {
|
|
|
6188
6536
|
`, "utf-8");
|
|
6189
6537
|
}
|
|
6190
6538
|
function clearLastHost(cwd) {
|
|
6191
|
-
const filePath =
|
|
6539
|
+
const filePath = path9.join(cwd, LAST_HOST_DIR_NAME, LAST_HOST_FILE_NAME);
|
|
6192
6540
|
if (fs5.existsSync(filePath)) {
|
|
6193
6541
|
fs5.unlinkSync(filePath);
|
|
6194
6542
|
}
|
|
@@ -6317,11 +6665,11 @@ var runResultGetTool = {
|
|
|
6317
6665
|
`**Location:** \`${result.artifactsDir}\``,
|
|
6318
6666
|
""
|
|
6319
6667
|
);
|
|
6320
|
-
const actionScriptPath =
|
|
6321
|
-
const resultsMdPath =
|
|
6322
|
-
const screenshotsDir =
|
|
6323
|
-
const stdoutLogPath =
|
|
6324
|
-
const stderrLogPath =
|
|
6668
|
+
const actionScriptPath = path9.join(result.artifactsDir, "action-script.json");
|
|
6669
|
+
const resultsMdPath = path9.join(result.artifactsDir, "results.md");
|
|
6670
|
+
const screenshotsDir = path9.join(result.artifactsDir, "screenshots");
|
|
6671
|
+
const stdoutLogPath = path9.join(result.artifactsDir, "stdout.log");
|
|
6672
|
+
const stderrLogPath = path9.join(result.artifactsDir, "stderr.log");
|
|
6325
6673
|
const artifactItems = [];
|
|
6326
6674
|
if (fs5.existsSync(actionScriptPath)) {
|
|
6327
6675
|
artifactItems.push("- `action-script.json` \u2014 generated test steps");
|
|
@@ -6416,6 +6764,7 @@ var executeTestGenerationTool = {
|
|
|
6416
6764
|
const result = await executeTestGeneration({
|
|
6417
6765
|
testCase: input.testCase,
|
|
6418
6766
|
localUrl: input.localUrl,
|
|
6767
|
+
cwd: input.cwd,
|
|
6419
6768
|
mutations: input.mutations,
|
|
6420
6769
|
timeoutMs: input.timeoutMs,
|
|
6421
6770
|
showUi,
|
|
@@ -6457,6 +6806,7 @@ var executeReplayTool = {
|
|
|
6457
6806
|
testScript: input.testScript,
|
|
6458
6807
|
actionScript: input.actionScript,
|
|
6459
6808
|
localUrl: input.localUrl,
|
|
6809
|
+
cwd: input.cwd,
|
|
6460
6810
|
mutations: input.mutations,
|
|
6461
6811
|
timeoutMs: input.timeoutMs,
|
|
6462
6812
|
showUi,
|
|
@@ -7057,6 +7407,7 @@ __export(local_exports2, {
|
|
|
7057
7407
|
TestScriptDetailsSchema: () => TestScriptDetailsSchema,
|
|
7058
7408
|
TestScriptGetInputSchema: () => TestScriptGetInputSchema2,
|
|
7059
7409
|
TestScriptListInputSchema: () => TestScriptListInputSchema2,
|
|
7410
|
+
acquireLocalExecutionLock: () => acquireLocalExecutionLock,
|
|
7060
7411
|
allLocalQaTools: () => allLocalQaTools,
|
|
7061
7412
|
cancelExecution: () => cancelExecution,
|
|
7062
7413
|
executeReplay: () => executeReplay,
|
|
@@ -7068,6 +7419,7 @@ __export(local_exports2, {
|
|
|
7068
7419
|
getStorageService: () => getStorageService,
|
|
7069
7420
|
getTool: () => getTool,
|
|
7070
7421
|
listActiveExecutions: () => listActiveExecutions,
|
|
7422
|
+
readLocalExecutionLockState: () => readLocalExecutionLockState,
|
|
7071
7423
|
resetAuthService: () => resetAuthService,
|
|
7072
7424
|
resetRunResultStorageService: () => resetRunResultStorageService,
|
|
7073
7425
|
resetStorageService: () => resetStorageService
|