@muggleai/mcp 1.0.20 → 1.0.22

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