@vostride/agent-qa-dashboard 0.1.14 → 0.1.16

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,4 +1,4 @@
1
- export { f as createRouter, r as readJsonBody, s as startServer } from '../index-BL7OautO.cjs';
1
+ export { f as createRouter, r as readJsonBody, s as startServer } from '../index-ygQLgXzT.cjs';
2
2
  import 'node:http';
3
3
  import '@vostride/agent-qa-core';
4
4
  import '@vostride/agent-qa-mcp';
@@ -1,4 +1,4 @@
1
- export { f as createRouter, r as readJsonBody, s as startServer } from '../index-BL7OautO.js';
1
+ export { f as createRouter, r as readJsonBody, s as startServer } from '../index-ygQLgXzT.js';
2
2
  import 'node:http';
3
3
  import '@vostride/agent-qa-core';
4
4
  import '@vostride/agent-qa-mcp';
@@ -1572,7 +1572,7 @@ function readJsonBody(req, maxBytes = DEFAULT_MAX_BYTES) {
1572
1572
  }
1573
1573
 
1574
1574
  // src/server/routes.ts
1575
- import { buildAnalyticsEvent, buildInternalRunAttributes, captureAnalytics, mergeRunAttributes, readAuth, writeAuth, removeAuth, getAgentQaVersion, getProviderOptions, getLLMAuthProviderPlugin, listLLMAuthProviderPlugins, ModelConfigSchema, NamedLLMConfigSchema, WorkspaceSchema, ServicesSchema, RegistrySchema, UseSchema, MobileAppStateSchema, hashStepInstruction, TimeoutConfigSchema, CacheConfigSchema, HealingConfigSchema, PlannerConfigSchema, LoggingConfigSchema, LogCaptureConfigSchema, AccessibilityConfigSchema, DashboardConfigSchema, McpConfigSchema, RecordingConfigSchema, BrowserConfigSchema, AnalyticsSchema, AgentQaConfigSchema as AgentQaConfigSchema3, TestDefinitionSchema, SuiteDefinitionSchema, parseEnvFile, serializeEnvFile, runHookInSandbox, RUNTIME_IMAGE_MAP, SecretStore, SecretRedactor, validateUserRunAttributes, discoverWorkspaceFiles as discoverWorkspaceFiles4, isWorkspacePathMatch, resolveAnalyticsStandardProperties, resolveMemoryRoot as resolveMemoryRoot2, resolveWorkspaceFileTarget as resolveWorkspaceFileTarget2 } from "@vostride/agent-qa-core";
1575
+ import { AuthStateNameSchema, buildAnalyticsEvent, buildInternalRunAttributes, captureAnalytics, mergeRunAttributes, readAuth, writeAuth, removeAuth, getAgentQaVersion, getProviderOptions, getLLMAuthProviderPlugin, listAuthStateMetadata, listLLMAuthProviderPlugins, ModelConfigSchema, NamedLLMConfigSchema, WorkspaceSchema, ServicesSchema, RegistrySchema, UseSchema, MobileAppStateSchema, hashStepInstruction, TimeoutConfigSchema, CacheConfigSchema, HealingConfigSchema, PlannerConfigSchema, LoggingConfigSchema, LogCaptureConfigSchema, AccessibilityConfigSchema, DashboardConfigSchema, McpConfigSchema, RecordingConfigSchema, BrowserConfigSchema, AnalyticsSchema, AgentQaConfigSchema as AgentQaConfigSchema3, TestDefinitionSchema, SuiteDefinitionSchema, parseEnvFile, serializeEnvFile, runHookInSandbox, RUNTIME_IMAGE_MAP, SecretStore, SecretRedactor, redactAuthStateValue, validateUserRunAttributes, discoverWorkspaceFiles as discoverWorkspaceFiles4, isWorkspacePathMatch, resolveAnalyticsStandardProperties, resolveMemoryRoot as resolveMemoryRoot2, resolveWorkspaceFileTarget as resolveWorkspaceFileTarget2 } from "@vostride/agent-qa-core";
1576
1576
  import { parse as parseYaml5 } from "yaml";
1577
1577
  var LLM_PROVIDER_MODES = /* @__PURE__ */ new Set([
1578
1578
  "openai-compatible",
@@ -1862,6 +1862,41 @@ function cors(res) {
1862
1862
  function isPlainRecord(value) {
1863
1863
  return value !== null && typeof value === "object" && !Array.isArray(value);
1864
1864
  }
1865
+ function getConfiguredAuthStateDir(config) {
1866
+ const services = isPlainRecord(config.services) ? config.services : void 0;
1867
+ const authState = services && isPlainRecord(services.authState) ? services.authState : void 0;
1868
+ return typeof authState?.dir === "string" ? authState.dir : void 0;
1869
+ }
1870
+ function getSessionTargetNameForAuthState(session) {
1871
+ if (!session || typeof session.getState !== "function") return "selected target";
1872
+ try {
1873
+ const state = session.getState();
1874
+ if (isPlainRecord(state) && typeof state.targetName === "string" && state.targetName.trim().length > 0) {
1875
+ return state.targetName.trim();
1876
+ }
1877
+ } catch {
1878
+ }
1879
+ return "selected target";
1880
+ }
1881
+ function buildAuthStateSaveErrorMessage(stateName, targetName, error) {
1882
+ const raw = error instanceof Error ? error.message : String(error);
1883
+ if (/already exists/i.test(raw)) {
1884
+ return {
1885
+ status: 409,
1886
+ message: `Auth state "${stateName}" for target "${targetName}" already exists. Use replace=true to replace it.`
1887
+ };
1888
+ }
1889
+ if (/web Live Mode|not ready|executing|busy/i.test(raw)) {
1890
+ return {
1891
+ status: 409,
1892
+ message: `Could not save auth state "${stateName}" for target "${targetName}".`
1893
+ };
1894
+ }
1895
+ return {
1896
+ status: 500,
1897
+ message: `Could not save auth state "${stateName}" for target "${targetName}".`
1898
+ };
1899
+ }
1865
1900
  function isDashboardProductEventName(value) {
1866
1901
  return typeof value === "string" && DASHBOARD_PRODUCT_EVENT_NAME_SET.has(value);
1867
1902
  }
@@ -2261,7 +2296,7 @@ function sanitizeSecretsFileMetadata(value) {
2261
2296
  };
2262
2297
  }
2263
2298
  function sanitizeArtifactPayloadForResponse(payload) {
2264
- const sanitized = sanitizeSecretTemplates(payload);
2299
+ const sanitized = redactAuthStateValue(sanitizeSecretTemplates(payload));
2265
2300
  if (!isRecord(sanitized)) return payload;
2266
2301
  const config = sanitized.config;
2267
2302
  if (!isRecord(config) || !("secretsFile" in config)) return sanitized;
@@ -2280,6 +2315,9 @@ function sanitizeArtifactForResponse(artifact) {
2280
2315
  payload: sanitizeArtifactPayloadForResponse(artifact.payload)
2281
2316
  };
2282
2317
  }
2318
+ function sanitizeAuthStateForResponse(value) {
2319
+ return redactAuthStateValue(value);
2320
+ }
2283
2321
  async function normalizeDashboardWorkspacePath(filePath, workspacePaths, kind, requireExisting = true) {
2284
2322
  if (!workspacePaths) {
2285
2323
  throw new Error("Workspace path resolution is required for dashboard-triggered runs");
@@ -2504,6 +2542,28 @@ function createRouter(dbOrDeps, artifactsDir) {
2504
2542
  }
2505
2543
  const url = parseUrl(req);
2506
2544
  const path = url.pathname;
2545
+ if (path === "/api/auth-states" && req.method === "GET") {
2546
+ if (!configManager || !deps.configPath) {
2547
+ json(res, { error: "Auth-state metadata not available" }, 503);
2548
+ return;
2549
+ }
2550
+ ;
2551
+ (async () => {
2552
+ try {
2553
+ const config = await configManager.read();
2554
+ const targetName = url.searchParams.get("target") ?? void 0;
2555
+ const authStates = await listAuthStateMetadata({
2556
+ configDir: dirname4(resolve3(deps.configPath)),
2557
+ authStateDir: getConfiguredAuthStateDir(config),
2558
+ targetName
2559
+ });
2560
+ json(res, { authStates });
2561
+ } catch {
2562
+ json(res, { error: "Could not list auth states." }, 500);
2563
+ }
2564
+ })();
2565
+ return;
2566
+ }
2507
2567
  if (path === "/api/analytics/events" && req.method === "POST") {
2508
2568
  readJsonBody(req).then((body) => {
2509
2569
  if (!isPlainRecord(body) || !isDashboardProductEventName(body.name) || Object.prototype.hasOwnProperty.call(body, "properties") && body.properties !== void 0 && !isPlainRecord(body.properties)) {
@@ -2617,7 +2677,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2617
2677
  return;
2618
2678
  }
2619
2679
  const bundle = db.getRunArtifactBundle(runId);
2620
- json(res, {
2680
+ json(res, sanitizeAuthStateForResponse({
2621
2681
  run,
2622
2682
  artifact: sanitizeArtifactForResponse(bundle.artifact),
2623
2683
  children: bundle.children.map((child) => ({
@@ -2625,7 +2685,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2625
2685
  artifact: sanitizeArtifactForResponse(child.artifact)
2626
2686
  })),
2627
2687
  missingSections: getArtifactMissingSections(bundle.artifact)
2628
- });
2688
+ }));
2629
2689
  return;
2630
2690
  }
2631
2691
  if (path.startsWith("/api/runs/") && path.endsWith("/accessibility") && req.method === "GET") {
@@ -2651,7 +2711,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2651
2711
  const stepOrder = parseInt(stepOrderStr, 10);
2652
2712
  const trace = db.getReasoningTrace(runId, stepOrder);
2653
2713
  if (trace) {
2654
- json(res, { trace });
2714
+ json(res, sanitizeAuthStateForResponse({ trace }));
2655
2715
  return;
2656
2716
  }
2657
2717
  const steps = db.getSteps(runId);
@@ -2660,7 +2720,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2660
2720
  notFound(res, "Step not found");
2661
2721
  return;
2662
2722
  }
2663
- json(res, {
2723
+ json(res, sanitizeAuthStateForResponse({
2664
2724
  trace: {
2665
2725
  id: null,
2666
2726
  stepId: step.id,
@@ -2681,7 +2741,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2681
2741
  screenStateAfter: null,
2682
2742
  createdAt: step.createdAt
2683
2743
  }
2684
- });
2744
+ }));
2685
2745
  return;
2686
2746
  }
2687
2747
  if (path.startsWith("/api/runs/") && path.endsWith("/execution-logs") && req.method === "GET") {
@@ -2701,11 +2761,11 @@ function createRouter(dbOrDeps, artifactsDir) {
2701
2761
  for (const rid of allRunIds) {
2702
2762
  allLogs.push(...db.getExecutionLogs({ runId: rid, stepId, type }));
2703
2763
  }
2704
- json(res, { logs: allLogs });
2764
+ json(res, sanitizeAuthStateForResponse({ logs: allLogs }));
2705
2765
  return;
2706
2766
  }
2707
2767
  const logs = db.getExecutionLogs({ runId: id, stepId, type });
2708
- json(res, { logs });
2768
+ json(res, sanitizeAuthStateForResponse({ logs }));
2709
2769
  return;
2710
2770
  }
2711
2771
  if (path.startsWith("/api/runs/") && path.endsWith("/logs") && req.method === "GET") {
@@ -2722,7 +2782,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2722
2782
  const limit = url.searchParams.has("limit") ? parseInt(url.searchParams.get("limit"), 10) : 500;
2723
2783
  const offset = url.searchParams.has("offset") ? parseInt(url.searchParams.get("offset"), 10) : 0;
2724
2784
  const logs = db.getLogs({ runId: id, stepId, level, source, limit, offset });
2725
- json(res, { logs, total: logs.length });
2785
+ json(res, sanitizeAuthStateForResponse({ logs, total: logs.length }));
2726
2786
  return;
2727
2787
  }
2728
2788
  if (path.startsWith("/api/runs/") && path.endsWith("/steps") && req.method === "GET") {
@@ -2734,7 +2794,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2734
2794
  return;
2735
2795
  }
2736
2796
  const steps = db.getSteps(id);
2737
- json(res, { steps });
2797
+ json(res, sanitizeAuthStateForResponse({ steps }));
2738
2798
  return;
2739
2799
  }
2740
2800
  if (path.startsWith("/api/runs/") && path.endsWith("/cancel") && req.method === "POST") {
@@ -2808,9 +2868,9 @@ function createRouter(dbOrDeps, artifactsDir) {
2808
2868
  const attempts = db.getRunsByParent(id);
2809
2869
  if (run.suiteId && !run.parentRunId) {
2810
2870
  const tests = db.getRunsByParent(id);
2811
- json(res, { run, steps, attempts, tests });
2871
+ json(res, sanitizeAuthStateForResponse({ run, steps, attempts, tests }));
2812
2872
  } else {
2813
- json(res, { run, steps, attempts });
2873
+ json(res, sanitizeAuthStateForResponse({ run, steps, attempts }));
2814
2874
  }
2815
2875
  return;
2816
2876
  }
@@ -4423,7 +4483,7 @@ data: ${JSON.stringify(event)}
4423
4483
  const networkLogs = summarizeHookNetworkLogs(
4424
4484
  result.networkLogs
4425
4485
  );
4426
- json(res, {
4486
+ json(res, sanitizeAuthStateForResponse({
4427
4487
  success: result.success,
4428
4488
  status: result.success ? "passed" : "failed",
4429
4489
  executedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4441,9 +4501,9 @@ data: ${JSON.stringify(event)}
4441
4501
  networkLogsAvailable: networkLogs.length > 0,
4442
4502
  networkLogs
4443
4503
  }
4444
- });
4504
+ }));
4445
4505
  } catch (err) {
4446
- json(res, { error: err instanceof Error ? err.message : "Failed to run hook" }, 500);
4506
+ json(res, sanitizeAuthStateForResponse({ error: err instanceof Error ? err.message : "Failed to run hook" }), 500);
4447
4507
  }
4448
4508
  }).catch(() => json(res, { error: "Invalid request body" }, 400));
4449
4509
  return;
@@ -4692,6 +4752,45 @@ data: ${JSON.stringify(event)}
4692
4752
  })();
4693
4753
  return;
4694
4754
  }
4755
+ const liveEditorAuthStateMatch = path.match(/^\/api\/live-editor\/sessions\/([^/]+)\/auth-state$/);
4756
+ if (liveEditorAuthStateMatch && req.method === "POST") {
4757
+ if (!sessionManager) {
4758
+ json(res, { error: "Live editor not available" }, 503);
4759
+ return;
4760
+ }
4761
+ ;
4762
+ (async () => {
4763
+ let stateName = "";
4764
+ let session;
4765
+ try {
4766
+ const body = await readJsonBody(req);
4767
+ const parsedName = AuthStateNameSchema.safeParse(typeof body?.name === "string" ? body.name : "");
4768
+ if (!parsedName.success) {
4769
+ json(res, { error: "Auth state name must be a lowercase slug." }, 400);
4770
+ return;
4771
+ }
4772
+ stateName = parsedName.data;
4773
+ session = sessionManager.getSession(decodeURIComponent(liveEditorAuthStateMatch[1]));
4774
+ if (!session) {
4775
+ notFound(res, "Session not found");
4776
+ return;
4777
+ }
4778
+ if (typeof session.captureWebAuthState !== "function") {
4779
+ json(res, {
4780
+ error: `Could not save auth state "${stateName}" for target "${getSessionTargetNameForAuthState(session)}".`
4781
+ }, 500);
4782
+ return;
4783
+ }
4784
+ const authState = await session.captureWebAuthState(stateName, { replace: body?.replace === true });
4785
+ json(res, { authState });
4786
+ } catch (err) {
4787
+ const targetName = getSessionTargetNameForAuthState(session);
4788
+ const response = buildAuthStateSaveErrorMessage(stateName || "unknown", targetName, err);
4789
+ json(res, { error: response.message }, response.status);
4790
+ }
4791
+ })();
4792
+ return;
4793
+ }
4695
4794
  if (path === "/api/live-editor/sessions" && req.method === "POST") {
4696
4795
  if (!sessionManager) {
4697
4796
  json(res, { error: "Live editor not available" }, 503);
@@ -5899,7 +5998,9 @@ import { randomUUID as randomUUID3 } from "crypto";
5899
5998
 
5900
5999
  // src/live-editor/live-session.ts
5901
6000
  import { randomUUID as randomUUID2 } from "crypto";
6001
+ import { dirname as dirname8, resolve as resolvePath } from "path";
5902
6002
  import {
6003
+ AUTH_STATE_SCHEMA_VERSION,
5903
6004
  executeStep,
5904
6005
  createModel,
5905
6006
  getProviderOptions as getProviderOptions2,
@@ -5913,8 +6014,11 @@ import {
5913
6014
  runHooks,
5914
6015
  runHookInSandbox as runHookInSandbox2,
5915
6016
  MobileSetupError as MobileSetupError2,
5916
- redactSecretValue,
5917
- resolveSecretTemplatesInValue
6017
+ readAuthStateMetadata,
6018
+ redactAuthStateValue as redactAuthStateValue2,
6019
+ resolveAuthStatePaths,
6020
+ resolveSecretTemplatesInValue,
6021
+ writeAuthStateFiles
5918
6022
  } from "@vostride/agent-qa-core";
5919
6023
 
5920
6024
  // src/live-editor/mobile-bootstrap.ts
@@ -6069,6 +6173,26 @@ function coerceRunJSResult(value) {
6069
6173
  }
6070
6174
  return JSON.stringify(value);
6071
6175
  }
6176
+ function getWebStorageStateContext(adapter) {
6177
+ if (!adapter || !("getPage" in adapter)) return null;
6178
+ const page = adapter.getPage?.();
6179
+ if (!page || typeof page !== "object") return null;
6180
+ const contextFactory = page.context;
6181
+ if (typeof contextFactory !== "function") return null;
6182
+ const context = contextFactory.call(page);
6183
+ if (!context || typeof context !== "object") return null;
6184
+ const storageState = context.storageState;
6185
+ if (typeof storageState !== "function") return null;
6186
+ return context;
6187
+ }
6188
+ function getConfiguredAuthStateDir2(config) {
6189
+ const services = config.services;
6190
+ if (!services || typeof services !== "object") return void 0;
6191
+ const authState = services.authState;
6192
+ if (!authState || typeof authState !== "object") return void 0;
6193
+ const dir = authState.dir;
6194
+ return typeof dir === "string" ? dir : void 0;
6195
+ }
6072
6196
  function toLiveSubActionData(result) {
6073
6197
  return result.trace?.subActions?.map((subAction) => ({
6074
6198
  index: subAction.index,
@@ -6146,6 +6270,7 @@ var LiveSession = class {
6146
6270
  previousSteps = [];
6147
6271
  currentStep = null;
6148
6272
  platform = "web";
6273
+ targetName = null;
6149
6274
  currentUrl = null;
6150
6275
  executing = false;
6151
6276
  idleTimer = null;
@@ -6183,7 +6308,7 @@ var LiveSession = class {
6183
6308
  this.pendingMessages.push(message);
6184
6309
  }
6185
6310
  redact(value) {
6186
- return redactSecretValue(value, this.secretRedactor);
6311
+ return redactAuthStateValue2(value, { secretRedactor: this.secretRedactor });
6187
6312
  }
6188
6313
  resolveSecrets(value) {
6189
6314
  return resolveSecretTemplatesInValue(value, this.secretStore);
@@ -6413,6 +6538,7 @@ var LiveSession = class {
6413
6538
  }
6414
6539
  async initialize(config) {
6415
6540
  this.platform = config.platform;
6541
+ this.targetName = typeof config.targetName === "string" && config.targetName.trim().length > 0 ? config.targetName.trim() : null;
6416
6542
  this._modelName = config.llmConfig.model ?? "unknown";
6417
6543
  this.variableStore = new VariableStore();
6418
6544
  this.secretStore = config.secretStore;
@@ -6903,6 +7029,62 @@ ${errorLines.join("\n")}`;
6903
7029
  this.messageSink = originalSink;
6904
7030
  }
6905
7031
  }
7032
+ async captureWebAuthState(name, options = {}) {
7033
+ if (this.platform !== "web") {
7034
+ throw new Error("Auth-state capture is only available for web Live Mode sessions.");
7035
+ }
7036
+ if (!this.readyForInteraction || !this.adapter) {
7037
+ throw new Error(this.terminalError ?? "Live session is not ready for auth-state capture.");
7038
+ }
7039
+ if (this.adapter.platform !== "web") {
7040
+ throw new Error("Auth-state capture is only available for web Live Mode sessions.");
7041
+ }
7042
+ if (this.executing) {
7043
+ throw new Error("Cannot save auth state while the Live Mode session is executing.");
7044
+ }
7045
+ if (!this.targetName) {
7046
+ throw new Error("Cannot save auth state because this Live Mode session has no configured target.");
7047
+ }
7048
+ if (!this.deps.configManager || !this.deps.configPath) {
7049
+ throw new Error("Cannot save auth state because workspace config is unavailable.");
7050
+ }
7051
+ const context = getWebStorageStateContext(this.adapter);
7052
+ if (!context) {
7053
+ throw new Error("Cannot save auth state because the active browser context is unavailable.");
7054
+ }
7055
+ const config = await this.deps.configManager.read();
7056
+ const authStateDir = getConfiguredAuthStateDir2(config);
7057
+ const paths = resolveAuthStatePaths({
7058
+ configDir: dirname8(resolvePath(this.deps.configPath)),
7059
+ authStateDir,
7060
+ targetName: this.targetName,
7061
+ stateName: name,
7062
+ target: { platform: "web" }
7063
+ });
7064
+ let existing = null;
7065
+ try {
7066
+ existing = await readAuthStateMetadata(paths);
7067
+ } catch {
7068
+ existing = null;
7069
+ }
7070
+ if (existing && options.replace !== true) {
7071
+ throw new Error(`Auth state "${paths.stateName}" for target "${paths.targetName}" already exists. Use replace=true to replace it.`);
7072
+ }
7073
+ const payload = await context.storageState({ indexedDB: true });
7074
+ const metadata = {
7075
+ version: AUTH_STATE_SCHEMA_VERSION,
7076
+ kind: "web",
7077
+ target: paths.targetName,
7078
+ name: paths.stateName,
7079
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
7080
+ };
7081
+ try {
7082
+ await writeAuthStateFiles(paths, { payload, metadata });
7083
+ } catch {
7084
+ throw new Error(`Could not save auth state "${paths.stateName}" for target "${paths.targetName}".`);
7085
+ }
7086
+ return metadata;
7087
+ }
6906
7088
  cancelStep() {
6907
7089
  this.abortController?.abort();
6908
7090
  }
@@ -6963,6 +7145,7 @@ ${errorLines.join("\n")}`;
6963
7145
  return {
6964
7146
  sessionId: this.sessionId,
6965
7147
  platform: this.adapter?.platform ?? this.platform,
7148
+ targetName: this.targetName,
6966
7149
  status: this.status === "terminated" ? "idle" : this.status === "executing" ? "executing" : this.status,
6967
7150
  currentStep: this.currentStep,
6968
7151
  currentUrl: this.currentUrl,