@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.
Files changed (45) hide show
  1. package/dist/{chunk-I4VLYJ7M.js → chunk-CPF6AR2I.js} +499 -147
  2. package/dist/{chunk-2DVZ2LYO.js → chunk-JNI7INIO.js} +2 -2
  3. package/dist/cli.js +2 -2
  4. package/dist/index.js +2 -2
  5. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  6. package/dist/plugin/.cursor-plugin/plugin.json +1 -1
  7. package/dist/plugin/skills/_shared/dev-server-readiness.md +39 -0
  8. package/dist/plugin/skills/_shared/failure-mode-handling.md +19 -4
  9. package/dist/plugin/skills/_shared/pr-branch-worktree.md +31 -0
  10. package/dist/plugin/skills/muggle-test/SKILL.md +7 -3
  11. package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +6 -1
  12. package/dist/plugin/skills/muggle-test-prepare/SKILL.md +40 -256
  13. package/dist/plugin/skills/muggle-test-prepare/steps/check-running.md +31 -0
  14. package/dist/plugin/skills/muggle-test-prepare/steps/env-file.md +18 -0
  15. package/dist/plugin/skills/muggle-test-prepare/steps/fresh-install.md +20 -0
  16. package/dist/plugin/skills/muggle-test-prepare/steps/identify-services.md +28 -0
  17. package/dist/plugin/skills/muggle-test-prepare/steps/readiness-report.md +26 -0
  18. package/dist/plugin/skills/muggle-test-prepare/steps/rebase-check.md +3 -0
  19. package/dist/plugin/skills/muggle-test-prepare/steps/scope.md +11 -0
  20. package/dist/plugin/skills/muggle-test-prepare/steps/smoke-test.md +26 -0
  21. package/dist/plugin/skills/muggle-test-prepare/steps/start-commands.md +29 -0
  22. package/dist/plugin/skills/muggle-test-prepare/steps/start-services.md +21 -0
  23. package/dist/plugin/skills/muggle-test-prepare/steps/viability-check.md +21 -0
  24. package/dist/release-manifest.json +4 -4
  25. package/dist/{src-ARTTHWNP.js → src-YR5UKLPC.js} +1 -1
  26. package/package.json +6 -6
  27. package/plugin/.claude-plugin/plugin.json +1 -1
  28. package/plugin/.cursor-plugin/plugin.json +1 -1
  29. package/plugin/skills/_shared/dev-server-readiness.md +39 -0
  30. package/plugin/skills/_shared/failure-mode-handling.md +19 -4
  31. package/plugin/skills/_shared/pr-branch-worktree.md +31 -0
  32. package/plugin/skills/muggle-test/SKILL.md +7 -3
  33. package/plugin/skills/muggle-test-feature-local/SKILL.md +6 -1
  34. package/plugin/skills/muggle-test-prepare/SKILL.md +40 -256
  35. package/plugin/skills/muggle-test-prepare/steps/check-running.md +31 -0
  36. package/plugin/skills/muggle-test-prepare/steps/env-file.md +18 -0
  37. package/plugin/skills/muggle-test-prepare/steps/fresh-install.md +20 -0
  38. package/plugin/skills/muggle-test-prepare/steps/identify-services.md +28 -0
  39. package/plugin/skills/muggle-test-prepare/steps/readiness-report.md +26 -0
  40. package/plugin/skills/muggle-test-prepare/steps/rebase-check.md +3 -0
  41. package/plugin/skills/muggle-test-prepare/steps/scope.md +11 -0
  42. package/plugin/skills/muggle-test-prepare/steps/smoke-test.md +26 -0
  43. package/plugin/skills/muggle-test-prepare/steps/start-commands.md +29 -0
  44. package/plugin/skills/muggle-test-prepare/steps/start-services.md +21 -0
  45. 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 path2 from 'path';
5
+ import * as path9 from 'path';
6
6
  import { join, dirname } from 'path';
7
- import * as os3 from 'os';
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 { spawn, exec } from 'child_process';
11
+ import { exec, spawn } from 'child_process';
12
12
  import axios, { AxiosError } from 'axios';
13
- import * as fs7 from 'fs/promises';
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 path2.join(os3.homedir(), DATA_DIR_NAME);
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 = path2.dirname(currentFilePath);
166
- if (currentDir.includes(path2.join("dist", "shared"))) {
167
- return path2.resolve(currentDir, "..", "..");
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 path2.resolve(currentDir, "..");
170
+ return path9.resolve(currentDir, "..");
171
171
  }
172
- if (currentDir.includes(path2.join("src", "shared"))) {
173
- return path2.resolve(currentDir, "..", "..");
172
+ if (currentDir.includes(path9.join("src", "shared"))) {
173
+ return path9.resolve(currentDir, "..", "..");
174
174
  }
175
- return path2.dirname(currentDir);
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 = path2.join(packageRoot, "package.json");
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 = os3.platform();
243
+ const platformName = os4.platform();
244
244
  const version = getElectronAppVersion();
245
- const baseDir = path2.join(getDataDir2(), ELECTRON_APP_DIR, version);
245
+ const baseDir = path9.join(getDataDir2(), ELECTRON_APP_DIR, version);
246
246
  let binaryPath;
247
247
  switch (platformName) {
248
248
  case "darwin":
249
- binaryPath = path2.join(baseDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
249
+ binaryPath = path9.join(baseDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
250
250
  break;
251
251
  case "win32":
252
- binaryPath = path2.join(baseDir, "MuggleAI.exe");
252
+ binaryPath = path9.join(baseDir, "MuggleAI.exe");
253
253
  break;
254
254
  case "linux":
255
- binaryPath = path2.join(baseDir, "MuggleAI");
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 = os3.platform();
267
- const homeDir = os3.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 = path2.join(homeDir, "Applications", "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
271
+ binaryPath = path9.join(homeDir, "Applications", "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
272
272
  break;
273
273
  case "win32":
274
- binaryPath = path2.join(homeDir, "AppData", "Local", "Programs", "MuggleAI", "MuggleAI.exe");
274
+ binaryPath = path9.join(homeDir, "AppData", "Local", "Programs", "MuggleAI", "MuggleAI.exe");
275
275
  break;
276
276
  case "linux":
277
- binaryPath = path2.join(homeDir, ".local", "share", "muggle-ai", "MuggleAI");
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 = path2.resolve(packageRoot, "..", "web-service", "dist", "src", "index.js");
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: path2.join(dataDir, "sessions"),
396
- projectsDir: path2.join(dataDir, "projects"),
397
- tempDir: path2.join(dataDir, "temp"),
398
- apiKeyFilePath: path2.join(dataDir, API_KEY_FILE),
399
- oauthSessionFilePath: path2.join(dataDir, "oauth-session.json"),
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: path2.join(dataDir, "web-service.pid"),
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 = path2.join(getDataDir2(), VERSION_OVERRIDE_FILE);
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 = path2.join(getDataDir2(), VERSION_OVERRIDE_FILE);
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 path2.join(getDataDir2(), ELECTRON_APP_DIR, resolvedVersion);
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 os5 = platform();
531
+ const os6 = platform();
532
532
  const arch2 = process.arch;
533
- switch (os5) {
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: ${os5}`);
541
+ throw new Error(`Unsupported platform: ${os6}`);
542
542
  }
543
543
  }
544
544
  async function calculateFileChecksum(filePath) {
545
- return new Promise((resolve3, reject) => {
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
- resolve3(hash.digest("hex"));
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: path2.basename(filePath)
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: path2.basename(filePath),
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: path2.basename(filePath),
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: path2.basename(filePath),
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((resolve3) => {
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
- resolve3({
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
- resolve3({ opened: true });
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
- resolve3({
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 path2.join(getDataDir(), API_KEY_FILE2);
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 = path2.join(
854
- path2.dirname(config.localQa.oauthSessionFilePath),
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 = path2.dirname(this.pendingDeviceCodePath);
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 = path2.dirname(this.oauthSessionFilePath);
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 = path2.dirname(this.oauthSessionFilePath);
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((resolve3) => {
1359
+ return new Promise((resolve4) => {
1360
1360
  setTimeout(() => {
1361
- resolve3();
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 = path2.join(this.sessionsDir, sessionId);
1401
+ const sessionDir = path9.join(this.sessionsDir, sessionId);
1402
1402
  if (!fs5.existsSync(sessionDir)) {
1403
1403
  fs5.mkdirSync(sessionDir, { recursive: true });
1404
- fs5.mkdirSync(path2.join(sessionDir, "screenshots"), { recursive: true });
1405
- fs5.mkdirSync(path2.join(sessionDir, "logs"), { recursive: true });
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 = path2.join(sessionDir, "metadata.json");
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 = path2.join(this.sessionsDir, sessionId, "metadata.json");
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 = path2.join(this.sessionsDir, entry);
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 = path2.join(this.sessionsDir, "current");
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 path2.basename(target);
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 = path2.join(this.sessionsDir, "current");
1479
- const targetPath = path2.join(this.sessionsDir, sessionId);
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 = path2.join(sessionDir, "screenshots", filename);
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 = path2.join(sessionDir, "results.md");
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 = path2.join(this.sessionsDir, sessionId, "results.md");
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 = path2.join(sessionDir, "results.md");
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 = path2.join(sessionDir, "results.md");
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 = path2.join(this.sessionsDir, sessionId);
1636
- const resultsPath = path2.join(sessionDir, "results.md");
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 = path2.join(this.sessionsDir, sessionId);
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 path2.join(this.sessionsDir, sessionId);
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 = path2.join(this.sessionsDir, sessionId);
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 = path2.join(this.sessionsDir, "current");
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 = path2.join(dataDir, "run-results");
1800
- this.testScriptsDir = path2.join(dataDir, "test-scripts");
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(path2.join(this.runResultsDir, file), "utf-8");
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 = path2.join(this.runResultsDir, `${runId}.json`);
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 = path2.join(this.runResultsDir, `${result.id}.json`);
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(path2.join(this.testScriptsDir, file), "utf-8");
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 = path2.join(this.testScriptsDir, `${testScriptId}.json`);
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 = path2.join(this.testScriptsDir, `${script.id}.json`);
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 = path2.dirname(fileURLToPath(currentFileUrl));
2218
+ const currentDir = path9.dirname(fileURLToPath(currentFileUrl));
2049
2219
  const candidatePaths = [
2050
- path2.resolve(currentDir, "../../../package.json"),
2051
- path2.resolve(currentDir, "../package.json"),
2052
- path2.resolve(currentDir, "../../package.json")
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 fs7.readFile(candidatePath, "utf-8");
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: os3.hostname(),
2079
- osInfo: `${os3.platform()} ${os3.release()} ${os3.arch()}`,
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 = path2.join(config.localQa.dataDir, "temp");
2103
- await fs7.mkdir(tempDir, { recursive: true });
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 = path2.join(tempDir, params.filename);
2109
- await fs7.writeFile(filePath, JSON.stringify(params.data, null, 2));
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 fs7.unlink(filePath);
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 = path2.join(sessionsDir, params.runId);
2124
- const actionScriptPath = path2.join(artifactsDir, "action-script.json");
2125
- await fs7.mkdir(artifactsDir, { recursive: true });
2126
- await fs7.copyFile(params.generatedScriptPath, actionScriptPath);
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 fs7.unlink(params.generatedScriptPath);
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 = path2.join(sessionsDir, params.runId);
2137
- await fs7.mkdir(artifactsDir, { recursive: true });
2138
- const stdoutPath = path2.join(artifactsDir, "stdout.log");
2139
- const stderrPath = path2.join(artifactsDir, "stderr.log");
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
- fs7.writeFile(stdoutPath, params.stdout, "utf-8"),
2142
- fs7.writeFile(stderrPath, params.stderr, "utf-8")
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: path2.dirname(electronAppPath)
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((resolve3, reject) => {
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
- resolve3(result.payload);
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 = path2.join(
2469
- path2.dirname(inputFilePath),
2470
- `gen_${path2.basename(inputFilePath)}`
2739
+ const generatedScriptPath = path9.join(
2740
+ path9.dirname(inputFilePath),
2741
+ `gen_${path9.basename(inputFilePath)}`
2471
2742
  );
2472
- const generatedScriptRaw = await fs7.readFile(generatedScriptPath, "utf-8");
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 = path2.join(getStorageService().getSessionsDir(), runId);
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 path2.join(os3.homedir(), DATA_DIR_NAME2);
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 path15 = MANIFEST_PATH2;
3399
+ const path16 = MANIFEST_PATH2;
3114
3400
  try {
3115
- const raw = readFileSync(path15, "utf8");
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(path15) {
3876
- const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) => path15.startsWith(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: path15,
4170
+ path: path16,
3881
4171
  allowedPrefixes: ALLOWED_UPSTREAM_PREFIXES
3882
4172
  });
3883
4173
  throw new GatewayError({
3884
4174
  code: "FORBIDDEN" /* FORBIDDEN */,
3885
- message: `Path '${path15}' is not allowed`
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 = path2.join(getDataDir(), TELEMETRY_DIR_NAME);
6382
+ const dir = path9.join(getDataDir(), TELEMETRY_DIR_NAME);
6035
6383
  fs5.mkdirSync(dir, { recursive: true });
6036
- const filePath = path2.join(dir, FAILURE_EVENTS_FILE_NAME);
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 = path2.join(dataDirOverride2 ?? getDataDir(), PREFERENCES_FILE_NAME);
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 = path2.join(dataDirOverride2 ?? getDataDir(), PREFERENCES_FILE_NAME);
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 = path2.join(cwd, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_FILE_NAME);
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 ? path2.join(cwd, PREFERENCES_PROJECT_DIR_NAME) : dataDirOverride2 ?? getDataDir();
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 = path2.join(dir, PREFERENCES_FILE_NAME);
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 ? path2.join(cwd, PREFERENCES_PROJECT_DIR_NAME) : dataDirOverride2 ?? getDataDir();
6097
- const filePath = path2.join(dir, PREFERENCES_FILE_NAME);
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 = path2.join(cwd, LAST_PROJECT_DIR_NAME, LAST_PROJECT_FILE_NAME);
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 = path2.join(cwd, LAST_PROJECT_DIR_NAME);
6492
+ const dir = path9.join(cwd, LAST_PROJECT_DIR_NAME);
6145
6493
  fs5.mkdirSync(dir, { recursive: true });
6146
- const filePath = path2.join(dir, LAST_PROJECT_FILE_NAME);
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 = path2.join(cwd, LAST_PROJECT_DIR_NAME, LAST_PROJECT_FILE_NAME);
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 = path2.join(cwd, LAST_HOST_DIR_NAME, LAST_HOST_FILE_NAME);
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 = path2.join(cwd, LAST_HOST_DIR_NAME);
6528
+ const dir = path9.join(cwd, LAST_HOST_DIR_NAME);
6181
6529
  fs5.mkdirSync(dir, { recursive: true });
6182
- const filePath = path2.join(dir, LAST_HOST_FILE_NAME);
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 = path2.join(cwd, LAST_HOST_DIR_NAME, LAST_HOST_FILE_NAME);
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 = path2.join(result.artifactsDir, "action-script.json");
6321
- const resultsMdPath = path2.join(result.artifactsDir, "results.md");
6322
- const screenshotsDir = path2.join(result.artifactsDir, "screenshots");
6323
- const stdoutLogPath = path2.join(result.artifactsDir, "stdout.log");
6324
- const stderrLogPath = path2.join(result.artifactsDir, "stderr.log");
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