@vostride/agent-qa-dashboard 0.1.13 → 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,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, 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
  }
@@ -3600,6 +3660,11 @@ data: ${JSON.stringify(event)}
3600
3660
  });
3601
3661
  return;
3602
3662
  }
3663
+ if (path === "/api/app-metadata" && req.method === "GET") {
3664
+ const version = getAgentQaVersion().trim() || "0.0.0";
3665
+ json(res, { version });
3666
+ return;
3667
+ }
3603
3668
  if (path === "/api/config/targets" && req.method === "GET") {
3604
3669
  if (!configManager) {
3605
3670
  json(res, { error: "Config management not available" }, 501);
@@ -4418,7 +4483,7 @@ data: ${JSON.stringify(event)}
4418
4483
  const networkLogs = summarizeHookNetworkLogs(
4419
4484
  result.networkLogs
4420
4485
  );
4421
- json(res, {
4486
+ json(res, sanitizeAuthStateForResponse({
4422
4487
  success: result.success,
4423
4488
  status: result.success ? "passed" : "failed",
4424
4489
  executedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4436,9 +4501,9 @@ data: ${JSON.stringify(event)}
4436
4501
  networkLogsAvailable: networkLogs.length > 0,
4437
4502
  networkLogs
4438
4503
  }
4439
- });
4504
+ }));
4440
4505
  } catch (err) {
4441
- 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);
4442
4507
  }
4443
4508
  }).catch(() => json(res, { error: "Invalid request body" }, 400));
4444
4509
  return;
@@ -4687,6 +4752,45 @@ data: ${JSON.stringify(event)}
4687
4752
  })();
4688
4753
  return;
4689
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
+ }
4690
4794
  if (path === "/api/live-editor/sessions" && req.method === "POST") {
4691
4795
  if (!sessionManager) {
4692
4796
  json(res, { error: "Live editor not available" }, 503);
@@ -5894,7 +5998,9 @@ import { randomUUID as randomUUID3 } from "crypto";
5894
5998
 
5895
5999
  // src/live-editor/live-session.ts
5896
6000
  import { randomUUID as randomUUID2 } from "crypto";
6001
+ import { dirname as dirname8, resolve as resolvePath } from "path";
5897
6002
  import {
6003
+ AUTH_STATE_SCHEMA_VERSION,
5898
6004
  executeStep,
5899
6005
  createModel,
5900
6006
  getProviderOptions as getProviderOptions2,
@@ -5908,8 +6014,11 @@ import {
5908
6014
  runHooks,
5909
6015
  runHookInSandbox as runHookInSandbox2,
5910
6016
  MobileSetupError as MobileSetupError2,
5911
- redactSecretValue,
5912
- resolveSecretTemplatesInValue
6017
+ readAuthStateMetadata,
6018
+ redactAuthStateValue as redactAuthStateValue2,
6019
+ resolveAuthStatePaths,
6020
+ resolveSecretTemplatesInValue,
6021
+ writeAuthStateFiles
5913
6022
  } from "@vostride/agent-qa-core";
5914
6023
 
5915
6024
  // src/live-editor/mobile-bootstrap.ts
@@ -6064,6 +6173,26 @@ function coerceRunJSResult(value) {
6064
6173
  }
6065
6174
  return JSON.stringify(value);
6066
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
+ }
6067
6196
  function toLiveSubActionData(result) {
6068
6197
  return result.trace?.subActions?.map((subAction) => ({
6069
6198
  index: subAction.index,
@@ -6141,6 +6270,7 @@ var LiveSession = class {
6141
6270
  previousSteps = [];
6142
6271
  currentStep = null;
6143
6272
  platform = "web";
6273
+ targetName = null;
6144
6274
  currentUrl = null;
6145
6275
  executing = false;
6146
6276
  idleTimer = null;
@@ -6178,7 +6308,7 @@ var LiveSession = class {
6178
6308
  this.pendingMessages.push(message);
6179
6309
  }
6180
6310
  redact(value) {
6181
- return redactSecretValue(value, this.secretRedactor);
6311
+ return redactAuthStateValue2(value, { secretRedactor: this.secretRedactor });
6182
6312
  }
6183
6313
  resolveSecrets(value) {
6184
6314
  return resolveSecretTemplatesInValue(value, this.secretStore);
@@ -6408,6 +6538,7 @@ var LiveSession = class {
6408
6538
  }
6409
6539
  async initialize(config) {
6410
6540
  this.platform = config.platform;
6541
+ this.targetName = typeof config.targetName === "string" && config.targetName.trim().length > 0 ? config.targetName.trim() : null;
6411
6542
  this._modelName = config.llmConfig.model ?? "unknown";
6412
6543
  this.variableStore = new VariableStore();
6413
6544
  this.secretStore = config.secretStore;
@@ -6898,6 +7029,62 @@ ${errorLines.join("\n")}`;
6898
7029
  this.messageSink = originalSink;
6899
7030
  }
6900
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
+ }
6901
7088
  cancelStep() {
6902
7089
  this.abortController?.abort();
6903
7090
  }
@@ -6958,6 +7145,7 @@ ${errorLines.join("\n")}`;
6958
7145
  return {
6959
7146
  sessionId: this.sessionId,
6960
7147
  platform: this.adapter?.platform ?? this.platform,
7148
+ targetName: this.targetName,
6961
7149
  status: this.status === "terminated" ? "idle" : this.status === "executing" ? "executing" : this.status,
6962
7150
  currentStep: this.currentStep,
6963
7151
  currentUrl: this.currentUrl,