@vostride/agent-qa-dashboard 0.1.14 → 0.1.15

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.
@@ -1,5 +1,5 @@
1
1
  import { Server, IncomingMessage, ServerResponse } from 'node:http';
2
- import { RunAttributes, RunArtifactKind, RunArtifactStageInput, RunArtifactPayload, RunArtifactFinalizeInput, LogEntry, ResolvedWorkspacePaths, ModelConfig, ResolvedAppiumExecutable, SecretStore, SecretRedactor, HookDefinition, ConsoleLogEntry, NetworkLogEntry, StepAnnotation, StepPhaseEvent, StepResult, buildAnalyticsEvent, captureAnalytics, resolveAnalyticsStandardProperties } from '@vostride/agent-qa-core';
2
+ import { RunAttributes, RunArtifactKind, RunArtifactStageInput, RunArtifactPayload, RunArtifactFinalizeInput, LogEntry, ResolvedWorkspacePaths, ModelConfig, ResolvedAppiumExecutable, SecretStore, SecretRedactor, HookDefinition, ConsoleLogEntry, NetworkLogEntry, StepAnnotation, StepPhaseEvent, StepResult, AuthStateMetadata, buildAnalyticsEvent, captureAnalytics, resolveAnalyticsStandardProperties } from '@vostride/agent-qa-core';
3
3
  import { LocalMcpEndpointConfig, LocalMcpTransport } from '@vostride/agent-qa-mcp';
4
4
  import { Document } from 'yaml';
5
5
  import { EventEmitter } from 'node:events';
@@ -792,6 +792,7 @@ type ServerMessage = {
792
792
  interface SessionState {
793
793
  sessionId: string;
794
794
  platform: 'web' | 'android' | 'ios';
795
+ targetName?: string | null;
795
796
  status: 'idle' | 'executing' | 'disconnected';
796
797
  currentStep: string | null;
797
798
  currentUrl: string | null;
@@ -858,6 +859,7 @@ declare class LiveSession {
858
859
  private previousSteps;
859
860
  private currentStep;
860
861
  private platform;
862
+ private targetName;
861
863
  private currentUrl;
862
864
  private executing;
863
865
  private idleTimer;
@@ -909,6 +911,9 @@ declare class LiveSession {
909
911
  teardown: string[];
910
912
  }, onPhase?: (event: StepPhaseEvent) => void): Promise<LiveTestResultPayload>;
911
913
  private runLifecycleHooksCapturing;
914
+ captureWebAuthState(name: string, options?: {
915
+ replace?: boolean;
916
+ }): Promise<AuthStateMetadata>;
912
917
  cancelStep(): void;
913
918
  navigate(url: string): Promise<string | null>;
914
919
  refreshPage(): Promise<string | null>;
@@ -1,5 +1,5 @@
1
1
  import { Server, IncomingMessage, ServerResponse } from 'node:http';
2
- import { RunAttributes, RunArtifactKind, RunArtifactStageInput, RunArtifactPayload, RunArtifactFinalizeInput, LogEntry, ResolvedWorkspacePaths, ModelConfig, ResolvedAppiumExecutable, SecretStore, SecretRedactor, HookDefinition, ConsoleLogEntry, NetworkLogEntry, StepAnnotation, StepPhaseEvent, StepResult, buildAnalyticsEvent, captureAnalytics, resolveAnalyticsStandardProperties } from '@vostride/agent-qa-core';
2
+ import { RunAttributes, RunArtifactKind, RunArtifactStageInput, RunArtifactPayload, RunArtifactFinalizeInput, LogEntry, ResolvedWorkspacePaths, ModelConfig, ResolvedAppiumExecutable, SecretStore, SecretRedactor, HookDefinition, ConsoleLogEntry, NetworkLogEntry, StepAnnotation, StepPhaseEvent, StepResult, AuthStateMetadata, buildAnalyticsEvent, captureAnalytics, resolveAnalyticsStandardProperties } from '@vostride/agent-qa-core';
3
3
  import { LocalMcpEndpointConfig, LocalMcpTransport } from '@vostride/agent-qa-mcp';
4
4
  import { Document } from 'yaml';
5
5
  import { EventEmitter } from 'node:events';
@@ -792,6 +792,7 @@ type ServerMessage = {
792
792
  interface SessionState {
793
793
  sessionId: string;
794
794
  platform: 'web' | 'android' | 'ios';
795
+ targetName?: string | null;
795
796
  status: 'idle' | 'executing' | 'disconnected';
796
797
  currentStep: string | null;
797
798
  currentUrl: string | null;
@@ -858,6 +859,7 @@ declare class LiveSession {
858
859
  private previousSteps;
859
860
  private currentStep;
860
861
  private platform;
862
+ private targetName;
861
863
  private currentUrl;
862
864
  private executing;
863
865
  private idleTimer;
@@ -909,6 +911,9 @@ declare class LiveSession {
909
911
  teardown: string[];
910
912
  }, onPhase?: (event: StepPhaseEvent) => void): Promise<LiveTestResultPayload>;
911
913
  private runLifecycleHooksCapturing;
914
+ captureWebAuthState(name: string, options?: {
915
+ replace?: boolean;
916
+ }): Promise<AuthStateMetadata>;
912
917
  cancelStep(): void;
913
918
  navigate(url: string): Promise<string | null>;
914
919
  refreshPage(): Promise<string | null>;
package/dist/index.cjs CHANGED
@@ -1960,7 +1960,7 @@ var DashboardReporter = class {
1960
1960
  this.redactor = redactor;
1961
1961
  }
1962
1962
  redactValue(value) {
1963
- return (0, import_agent_qa_core4.redactSecretValue)(value, this.redactor);
1963
+ return (0, import_agent_qa_core4.redactAuthStateValue)(value, { secretRedactor: this.redactor });
1964
1964
  }
1965
1965
  redactString(value) {
1966
1966
  return value === void 0 ? void 0 : this.redactValue(value);
@@ -2693,7 +2693,7 @@ var DashboardReporter = class {
2693
2693
  // src/server/server.ts
2694
2694
  var import_node_http2 = require("http");
2695
2695
  var import_promises11 = require("fs/promises");
2696
- var import_node_path13 = require("path");
2696
+ var import_node_path14 = require("path");
2697
2697
  var import_node_os2 = require("os");
2698
2698
 
2699
2699
  // src/server/routes.ts
@@ -4539,6 +4539,41 @@ function cors(res) {
4539
4539
  function isPlainRecord(value) {
4540
4540
  return value !== null && typeof value === "object" && !Array.isArray(value);
4541
4541
  }
4542
+ function getConfiguredAuthStateDir(config) {
4543
+ const services = isPlainRecord(config.services) ? config.services : void 0;
4544
+ const authState = services && isPlainRecord(services.authState) ? services.authState : void 0;
4545
+ return typeof authState?.dir === "string" ? authState.dir : void 0;
4546
+ }
4547
+ function getSessionTargetNameForAuthState(session) {
4548
+ if (!session || typeof session.getState !== "function") return "selected target";
4549
+ try {
4550
+ const state = session.getState();
4551
+ if (isPlainRecord(state) && typeof state.targetName === "string" && state.targetName.trim().length > 0) {
4552
+ return state.targetName.trim();
4553
+ }
4554
+ } catch {
4555
+ }
4556
+ return "selected target";
4557
+ }
4558
+ function buildAuthStateSaveErrorMessage(stateName, targetName, error) {
4559
+ const raw = error instanceof Error ? error.message : String(error);
4560
+ if (/already exists/i.test(raw)) {
4561
+ return {
4562
+ status: 409,
4563
+ message: `Auth state "${stateName}" for target "${targetName}" already exists. Use replace=true to replace it.`
4564
+ };
4565
+ }
4566
+ if (/web Live Mode|not ready|executing|busy/i.test(raw)) {
4567
+ return {
4568
+ status: 409,
4569
+ message: `Could not save auth state "${stateName}" for target "${targetName}".`
4570
+ };
4571
+ }
4572
+ return {
4573
+ status: 500,
4574
+ message: `Could not save auth state "${stateName}" for target "${targetName}".`
4575
+ };
4576
+ }
4542
4577
  function isDashboardProductEventName(value) {
4543
4578
  return typeof value === "string" && DASHBOARD_PRODUCT_EVENT_NAME_SET.has(value);
4544
4579
  }
@@ -4938,7 +4973,7 @@ function sanitizeSecretsFileMetadata(value) {
4938
4973
  };
4939
4974
  }
4940
4975
  function sanitizeArtifactPayloadForResponse(payload) {
4941
- const sanitized = sanitizeSecretTemplates(payload);
4976
+ const sanitized = (0, import_agent_qa_core9.redactAuthStateValue)(sanitizeSecretTemplates(payload));
4942
4977
  if (!isRecord2(sanitized)) return payload;
4943
4978
  const config = sanitized.config;
4944
4979
  if (!isRecord2(config) || !("secretsFile" in config)) return sanitized;
@@ -4957,6 +4992,9 @@ function sanitizeArtifactForResponse(artifact) {
4957
4992
  payload: sanitizeArtifactPayloadForResponse(artifact.payload)
4958
4993
  };
4959
4994
  }
4995
+ function sanitizeAuthStateForResponse(value) {
4996
+ return (0, import_agent_qa_core9.redactAuthStateValue)(value);
4997
+ }
4960
4998
  async function normalizeDashboardWorkspacePath(filePath, workspacePaths, kind, requireExisting = true) {
4961
4999
  if (!workspacePaths) {
4962
5000
  throw new Error("Workspace path resolution is required for dashboard-triggered runs");
@@ -5181,6 +5219,28 @@ function createRouter(dbOrDeps, artifactsDir) {
5181
5219
  }
5182
5220
  const url = parseUrl(req);
5183
5221
  const path = url.pathname;
5222
+ if (path === "/api/auth-states" && req.method === "GET") {
5223
+ if (!configManager || !deps.configPath) {
5224
+ json(res, { error: "Auth-state metadata not available" }, 503);
5225
+ return;
5226
+ }
5227
+ ;
5228
+ (async () => {
5229
+ try {
5230
+ const config = await configManager.read();
5231
+ const targetName = url.searchParams.get("target") ?? void 0;
5232
+ const authStates = await (0, import_agent_qa_core9.listAuthStateMetadata)({
5233
+ configDir: (0, import_node_path9.dirname)((0, import_node_path9.resolve)(deps.configPath)),
5234
+ authStateDir: getConfiguredAuthStateDir(config),
5235
+ targetName
5236
+ });
5237
+ json(res, { authStates });
5238
+ } catch {
5239
+ json(res, { error: "Could not list auth states." }, 500);
5240
+ }
5241
+ })();
5242
+ return;
5243
+ }
5184
5244
  if (path === "/api/analytics/events" && req.method === "POST") {
5185
5245
  readJsonBody(req).then((body) => {
5186
5246
  if (!isPlainRecord(body) || !isDashboardProductEventName(body.name) || Object.prototype.hasOwnProperty.call(body, "properties") && body.properties !== void 0 && !isPlainRecord(body.properties)) {
@@ -5294,7 +5354,7 @@ function createRouter(dbOrDeps, artifactsDir) {
5294
5354
  return;
5295
5355
  }
5296
5356
  const bundle = db.getRunArtifactBundle(runId);
5297
- json(res, {
5357
+ json(res, sanitizeAuthStateForResponse({
5298
5358
  run,
5299
5359
  artifact: sanitizeArtifactForResponse(bundle.artifact),
5300
5360
  children: bundle.children.map((child) => ({
@@ -5302,7 +5362,7 @@ function createRouter(dbOrDeps, artifactsDir) {
5302
5362
  artifact: sanitizeArtifactForResponse(child.artifact)
5303
5363
  })),
5304
5364
  missingSections: getArtifactMissingSections(bundle.artifact)
5305
- });
5365
+ }));
5306
5366
  return;
5307
5367
  }
5308
5368
  if (path.startsWith("/api/runs/") && path.endsWith("/accessibility") && req.method === "GET") {
@@ -5328,7 +5388,7 @@ function createRouter(dbOrDeps, artifactsDir) {
5328
5388
  const stepOrder = parseInt(stepOrderStr, 10);
5329
5389
  const trace = db.getReasoningTrace(runId, stepOrder);
5330
5390
  if (trace) {
5331
- json(res, { trace });
5391
+ json(res, sanitizeAuthStateForResponse({ trace }));
5332
5392
  return;
5333
5393
  }
5334
5394
  const steps = db.getSteps(runId);
@@ -5337,7 +5397,7 @@ function createRouter(dbOrDeps, artifactsDir) {
5337
5397
  notFound(res, "Step not found");
5338
5398
  return;
5339
5399
  }
5340
- json(res, {
5400
+ json(res, sanitizeAuthStateForResponse({
5341
5401
  trace: {
5342
5402
  id: null,
5343
5403
  stepId: step.id,
@@ -5358,7 +5418,7 @@ function createRouter(dbOrDeps, artifactsDir) {
5358
5418
  screenStateAfter: null,
5359
5419
  createdAt: step.createdAt
5360
5420
  }
5361
- });
5421
+ }));
5362
5422
  return;
5363
5423
  }
5364
5424
  if (path.startsWith("/api/runs/") && path.endsWith("/execution-logs") && req.method === "GET") {
@@ -5378,11 +5438,11 @@ function createRouter(dbOrDeps, artifactsDir) {
5378
5438
  for (const rid of allRunIds) {
5379
5439
  allLogs.push(...db.getExecutionLogs({ runId: rid, stepId, type }));
5380
5440
  }
5381
- json(res, { logs: allLogs });
5441
+ json(res, sanitizeAuthStateForResponse({ logs: allLogs }));
5382
5442
  return;
5383
5443
  }
5384
5444
  const logs = db.getExecutionLogs({ runId: id, stepId, type });
5385
- json(res, { logs });
5445
+ json(res, sanitizeAuthStateForResponse({ logs }));
5386
5446
  return;
5387
5447
  }
5388
5448
  if (path.startsWith("/api/runs/") && path.endsWith("/logs") && req.method === "GET") {
@@ -5399,7 +5459,7 @@ function createRouter(dbOrDeps, artifactsDir) {
5399
5459
  const limit = url.searchParams.has("limit") ? parseInt(url.searchParams.get("limit"), 10) : 500;
5400
5460
  const offset = url.searchParams.has("offset") ? parseInt(url.searchParams.get("offset"), 10) : 0;
5401
5461
  const logs = db.getLogs({ runId: id, stepId, level, source, limit, offset });
5402
- json(res, { logs, total: logs.length });
5462
+ json(res, sanitizeAuthStateForResponse({ logs, total: logs.length }));
5403
5463
  return;
5404
5464
  }
5405
5465
  if (path.startsWith("/api/runs/") && path.endsWith("/steps") && req.method === "GET") {
@@ -5411,7 +5471,7 @@ function createRouter(dbOrDeps, artifactsDir) {
5411
5471
  return;
5412
5472
  }
5413
5473
  const steps = db.getSteps(id);
5414
- json(res, { steps });
5474
+ json(res, sanitizeAuthStateForResponse({ steps }));
5415
5475
  return;
5416
5476
  }
5417
5477
  if (path.startsWith("/api/runs/") && path.endsWith("/cancel") && req.method === "POST") {
@@ -5485,9 +5545,9 @@ function createRouter(dbOrDeps, artifactsDir) {
5485
5545
  const attempts = db.getRunsByParent(id);
5486
5546
  if (run.suiteId && !run.parentRunId) {
5487
5547
  const tests = db.getRunsByParent(id);
5488
- json(res, { run, steps, attempts, tests });
5548
+ json(res, sanitizeAuthStateForResponse({ run, steps, attempts, tests }));
5489
5549
  } else {
5490
- json(res, { run, steps, attempts });
5550
+ json(res, sanitizeAuthStateForResponse({ run, steps, attempts }));
5491
5551
  }
5492
5552
  return;
5493
5553
  }
@@ -7100,7 +7160,7 @@ data: ${JSON.stringify(event)}
7100
7160
  const networkLogs = summarizeHookNetworkLogs(
7101
7161
  result.networkLogs
7102
7162
  );
7103
- json(res, {
7163
+ json(res, sanitizeAuthStateForResponse({
7104
7164
  success: result.success,
7105
7165
  status: result.success ? "passed" : "failed",
7106
7166
  executedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -7118,9 +7178,9 @@ data: ${JSON.stringify(event)}
7118
7178
  networkLogsAvailable: networkLogs.length > 0,
7119
7179
  networkLogs
7120
7180
  }
7121
- });
7181
+ }));
7122
7182
  } catch (err) {
7123
- json(res, { error: err instanceof Error ? err.message : "Failed to run hook" }, 500);
7183
+ json(res, sanitizeAuthStateForResponse({ error: err instanceof Error ? err.message : "Failed to run hook" }), 500);
7124
7184
  }
7125
7185
  }).catch(() => json(res, { error: "Invalid request body" }, 400));
7126
7186
  return;
@@ -7369,6 +7429,45 @@ data: ${JSON.stringify(event)}
7369
7429
  })();
7370
7430
  return;
7371
7431
  }
7432
+ const liveEditorAuthStateMatch = path.match(/^\/api\/live-editor\/sessions\/([^/]+)\/auth-state$/);
7433
+ if (liveEditorAuthStateMatch && req.method === "POST") {
7434
+ if (!sessionManager) {
7435
+ json(res, { error: "Live editor not available" }, 503);
7436
+ return;
7437
+ }
7438
+ ;
7439
+ (async () => {
7440
+ let stateName = "";
7441
+ let session;
7442
+ try {
7443
+ const body = await readJsonBody(req);
7444
+ const parsedName = import_agent_qa_core9.AuthStateNameSchema.safeParse(typeof body?.name === "string" ? body.name : "");
7445
+ if (!parsedName.success) {
7446
+ json(res, { error: "Auth state name must be a lowercase slug." }, 400);
7447
+ return;
7448
+ }
7449
+ stateName = parsedName.data;
7450
+ session = sessionManager.getSession(decodeURIComponent(liveEditorAuthStateMatch[1]));
7451
+ if (!session) {
7452
+ notFound(res, "Session not found");
7453
+ return;
7454
+ }
7455
+ if (typeof session.captureWebAuthState !== "function") {
7456
+ json(res, {
7457
+ error: `Could not save auth state "${stateName}" for target "${getSessionTargetNameForAuthState(session)}".`
7458
+ }, 500);
7459
+ return;
7460
+ }
7461
+ const authState = await session.captureWebAuthState(stateName, { replace: body?.replace === true });
7462
+ json(res, { authState });
7463
+ } catch (err) {
7464
+ const targetName = getSessionTargetNameForAuthState(session);
7465
+ const response = buildAuthStateSaveErrorMessage(stateName || "unknown", targetName, err);
7466
+ json(res, { error: response.message }, response.status);
7467
+ }
7468
+ })();
7469
+ return;
7470
+ }
7372
7471
  if (path === "/api/live-editor/sessions" && req.method === "POST") {
7373
7472
  if (!sessionManager) {
7374
7473
  json(res, { error: "Live editor not available" }, 503);
@@ -8573,6 +8672,7 @@ var import_node_crypto4 = require("crypto");
8573
8672
 
8574
8673
  // src/live-editor/live-session.ts
8575
8674
  var import_node_crypto3 = require("crypto");
8675
+ var import_node_path13 = require("path");
8576
8676
  var import_agent_qa_core13 = require("@vostride/agent-qa-core");
8577
8677
 
8578
8678
  // src/live-editor/mobile-bootstrap.ts
@@ -8724,6 +8824,26 @@ function coerceRunJSResult(value) {
8724
8824
  }
8725
8825
  return JSON.stringify(value);
8726
8826
  }
8827
+ function getWebStorageStateContext(adapter) {
8828
+ if (!adapter || !("getPage" in adapter)) return null;
8829
+ const page = adapter.getPage?.();
8830
+ if (!page || typeof page !== "object") return null;
8831
+ const contextFactory = page.context;
8832
+ if (typeof contextFactory !== "function") return null;
8833
+ const context = contextFactory.call(page);
8834
+ if (!context || typeof context !== "object") return null;
8835
+ const storageState = context.storageState;
8836
+ if (typeof storageState !== "function") return null;
8837
+ return context;
8838
+ }
8839
+ function getConfiguredAuthStateDir2(config) {
8840
+ const services = config.services;
8841
+ if (!services || typeof services !== "object") return void 0;
8842
+ const authState = services.authState;
8843
+ if (!authState || typeof authState !== "object") return void 0;
8844
+ const dir = authState.dir;
8845
+ return typeof dir === "string" ? dir : void 0;
8846
+ }
8727
8847
  function toLiveSubActionData(result) {
8728
8848
  return result.trace?.subActions?.map((subAction) => ({
8729
8849
  index: subAction.index,
@@ -8801,6 +8921,7 @@ var LiveSession = class {
8801
8921
  previousSteps = [];
8802
8922
  currentStep = null;
8803
8923
  platform = "web";
8924
+ targetName = null;
8804
8925
  currentUrl = null;
8805
8926
  executing = false;
8806
8927
  idleTimer = null;
@@ -8838,7 +8959,7 @@ var LiveSession = class {
8838
8959
  this.pendingMessages.push(message);
8839
8960
  }
8840
8961
  redact(value) {
8841
- return (0, import_agent_qa_core13.redactSecretValue)(value, this.secretRedactor);
8962
+ return (0, import_agent_qa_core13.redactAuthStateValue)(value, { secretRedactor: this.secretRedactor });
8842
8963
  }
8843
8964
  resolveSecrets(value) {
8844
8965
  return (0, import_agent_qa_core13.resolveSecretTemplatesInValue)(value, this.secretStore);
@@ -9068,6 +9189,7 @@ var LiveSession = class {
9068
9189
  }
9069
9190
  async initialize(config) {
9070
9191
  this.platform = config.platform;
9192
+ this.targetName = typeof config.targetName === "string" && config.targetName.trim().length > 0 ? config.targetName.trim() : null;
9071
9193
  this._modelName = config.llmConfig.model ?? "unknown";
9072
9194
  this.variableStore = new import_agent_qa_core13.VariableStore();
9073
9195
  this.secretStore = config.secretStore;
@@ -9558,6 +9680,62 @@ ${errorLines.join("\n")}`;
9558
9680
  this.messageSink = originalSink;
9559
9681
  }
9560
9682
  }
9683
+ async captureWebAuthState(name, options = {}) {
9684
+ if (this.platform !== "web") {
9685
+ throw new Error("Auth-state capture is only available for web Live Mode sessions.");
9686
+ }
9687
+ if (!this.readyForInteraction || !this.adapter) {
9688
+ throw new Error(this.terminalError ?? "Live session is not ready for auth-state capture.");
9689
+ }
9690
+ if (this.adapter.platform !== "web") {
9691
+ throw new Error("Auth-state capture is only available for web Live Mode sessions.");
9692
+ }
9693
+ if (this.executing) {
9694
+ throw new Error("Cannot save auth state while the Live Mode session is executing.");
9695
+ }
9696
+ if (!this.targetName) {
9697
+ throw new Error("Cannot save auth state because this Live Mode session has no configured target.");
9698
+ }
9699
+ if (!this.deps.configManager || !this.deps.configPath) {
9700
+ throw new Error("Cannot save auth state because workspace config is unavailable.");
9701
+ }
9702
+ const context = getWebStorageStateContext(this.adapter);
9703
+ if (!context) {
9704
+ throw new Error("Cannot save auth state because the active browser context is unavailable.");
9705
+ }
9706
+ const config = await this.deps.configManager.read();
9707
+ const authStateDir = getConfiguredAuthStateDir2(config);
9708
+ const paths = (0, import_agent_qa_core13.resolveAuthStatePaths)({
9709
+ configDir: (0, import_node_path13.dirname)((0, import_node_path13.resolve)(this.deps.configPath)),
9710
+ authStateDir,
9711
+ targetName: this.targetName,
9712
+ stateName: name,
9713
+ target: { platform: "web" }
9714
+ });
9715
+ let existing = null;
9716
+ try {
9717
+ existing = await (0, import_agent_qa_core13.readAuthStateMetadata)(paths);
9718
+ } catch {
9719
+ existing = null;
9720
+ }
9721
+ if (existing && options.replace !== true) {
9722
+ throw new Error(`Auth state "${paths.stateName}" for target "${paths.targetName}" already exists. Use replace=true to replace it.`);
9723
+ }
9724
+ const payload = await context.storageState({ indexedDB: true });
9725
+ const metadata = {
9726
+ version: import_agent_qa_core13.AUTH_STATE_SCHEMA_VERSION,
9727
+ kind: "web",
9728
+ target: paths.targetName,
9729
+ name: paths.stateName,
9730
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
9731
+ };
9732
+ try {
9733
+ await (0, import_agent_qa_core13.writeAuthStateFiles)(paths, { payload, metadata });
9734
+ } catch {
9735
+ throw new Error(`Could not save auth state "${paths.stateName}" for target "${paths.targetName}".`);
9736
+ }
9737
+ return metadata;
9738
+ }
9561
9739
  cancelStep() {
9562
9740
  this.abortController?.abort();
9563
9741
  }
@@ -9618,6 +9796,7 @@ ${errorLines.join("\n")}`;
9618
9796
  return {
9619
9797
  sessionId: this.sessionId,
9620
9798
  platform: this.adapter?.platform ?? this.platform,
9799
+ targetName: this.targetName,
9621
9800
  status: this.status === "terminated" ? "idle" : this.status === "executing" ? "executing" : this.status,
9622
9801
  currentStep: this.currentStep,
9623
9802
  currentUrl: this.currentUrl,
@@ -10096,7 +10275,7 @@ function isAnalyticsPrivacyEnabled(config) {
10096
10275
  }
10097
10276
  async function startServer(opts) {
10098
10277
  const { db, port = 3470, uiDir, artifactsDir, workspacePaths, configPath, mcp, llmConfig, authFetch } = opts;
10099
- const resolvedConfigPath = configPath ?? (0, import_node_path13.resolve)("agent-qa.config.yaml");
10278
+ const resolvedConfigPath = configPath ?? (0, import_node_path14.resolve)("agent-qa.config.yaml");
10100
10279
  const configManager = new ConfigManager(resolvedConfigPath);
10101
10280
  const config = await configManager.read().catch(() => ({}));
10102
10281
  const concurrency = typeof config.concurrency === "number" ? config.concurrency : (0, import_node_os2.cpus)().length;
@@ -10416,12 +10595,12 @@ async function startServer(opts) {
10416
10595
  return;
10417
10596
  }
10418
10597
  if (uiDir) {
10419
- const filePath = path === "/" ? (0, import_node_path13.join)(uiDir, "index.html") : (0, import_node_path13.join)(uiDir, path);
10598
+ const filePath = path === "/" ? (0, import_node_path14.join)(uiDir, "index.html") : (0, import_node_path14.join)(uiDir, path);
10420
10599
  try {
10421
10600
  const fileStat = await (0, import_promises11.stat)(filePath);
10422
10601
  if (fileStat.isFile()) {
10423
10602
  const content = await (0, import_promises11.readFile)(filePath);
10424
- const ext = (0, import_node_path13.extname)(filePath);
10603
+ const ext = (0, import_node_path14.extname)(filePath);
10425
10604
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
10426
10605
  res.writeHead(200, {
10427
10606
  "Content-Type": contentType,
@@ -10434,7 +10613,7 @@ async function startServer(opts) {
10434
10613
  } catch {
10435
10614
  }
10436
10615
  try {
10437
- const indexPath = (0, import_node_path13.join)(uiDir, "index.html");
10616
+ const indexPath = (0, import_node_path14.join)(uiDir, "index.html");
10438
10617
  const content = await (0, import_promises11.readFile)(indexPath);
10439
10618
  res.writeHead(200, {
10440
10619
  "Content-Type": "text/html",