@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.
@@ -39,7 +39,7 @@ module.exports = __toCommonJS(server_exports);
39
39
  // src/server/server.ts
40
40
  var import_node_http2 = require("http");
41
41
  var import_promises9 = require("fs/promises");
42
- var import_node_path9 = require("path");
42
+ var import_node_path10 = require("path");
43
43
  var import_node_os2 = require("os");
44
44
 
45
45
  // src/server/routes.ts
@@ -1885,6 +1885,41 @@ function cors(res) {
1885
1885
  function isPlainRecord(value) {
1886
1886
  return value !== null && typeof value === "object" && !Array.isArray(value);
1887
1887
  }
1888
+ function getConfiguredAuthStateDir(config) {
1889
+ const services = isPlainRecord(config.services) ? config.services : void 0;
1890
+ const authState = services && isPlainRecord(services.authState) ? services.authState : void 0;
1891
+ return typeof authState?.dir === "string" ? authState.dir : void 0;
1892
+ }
1893
+ function getSessionTargetNameForAuthState(session) {
1894
+ if (!session || typeof session.getState !== "function") return "selected target";
1895
+ try {
1896
+ const state = session.getState();
1897
+ if (isPlainRecord(state) && typeof state.targetName === "string" && state.targetName.trim().length > 0) {
1898
+ return state.targetName.trim();
1899
+ }
1900
+ } catch {
1901
+ }
1902
+ return "selected target";
1903
+ }
1904
+ function buildAuthStateSaveErrorMessage(stateName, targetName, error) {
1905
+ const raw = error instanceof Error ? error.message : String(error);
1906
+ if (/already exists/i.test(raw)) {
1907
+ return {
1908
+ status: 409,
1909
+ message: `Auth state "${stateName}" for target "${targetName}" already exists. Use replace=true to replace it.`
1910
+ };
1911
+ }
1912
+ if (/web Live Mode|not ready|executing|busy/i.test(raw)) {
1913
+ return {
1914
+ status: 409,
1915
+ message: `Could not save auth state "${stateName}" for target "${targetName}".`
1916
+ };
1917
+ }
1918
+ return {
1919
+ status: 500,
1920
+ message: `Could not save auth state "${stateName}" for target "${targetName}".`
1921
+ };
1922
+ }
1888
1923
  function isDashboardProductEventName(value) {
1889
1924
  return typeof value === "string" && DASHBOARD_PRODUCT_EVENT_NAME_SET.has(value);
1890
1925
  }
@@ -2284,7 +2319,7 @@ function sanitizeSecretsFileMetadata(value) {
2284
2319
  };
2285
2320
  }
2286
2321
  function sanitizeArtifactPayloadForResponse(payload) {
2287
- const sanitized = sanitizeSecretTemplates(payload);
2322
+ const sanitized = (0, import_agent_qa_core5.redactAuthStateValue)(sanitizeSecretTemplates(payload));
2288
2323
  if (!isRecord(sanitized)) return payload;
2289
2324
  const config = sanitized.config;
2290
2325
  if (!isRecord(config) || !("secretsFile" in config)) return sanitized;
@@ -2303,6 +2338,9 @@ function sanitizeArtifactForResponse(artifact) {
2303
2338
  payload: sanitizeArtifactPayloadForResponse(artifact.payload)
2304
2339
  };
2305
2340
  }
2341
+ function sanitizeAuthStateForResponse(value) {
2342
+ return (0, import_agent_qa_core5.redactAuthStateValue)(value);
2343
+ }
2306
2344
  async function normalizeDashboardWorkspacePath(filePath, workspacePaths, kind, requireExisting = true) {
2307
2345
  if (!workspacePaths) {
2308
2346
  throw new Error("Workspace path resolution is required for dashboard-triggered runs");
@@ -2527,6 +2565,28 @@ function createRouter(dbOrDeps, artifactsDir) {
2527
2565
  }
2528
2566
  const url = parseUrl(req);
2529
2567
  const path = url.pathname;
2568
+ if (path === "/api/auth-states" && req.method === "GET") {
2569
+ if (!configManager || !deps.configPath) {
2570
+ json(res, { error: "Auth-state metadata not available" }, 503);
2571
+ return;
2572
+ }
2573
+ ;
2574
+ (async () => {
2575
+ try {
2576
+ const config = await configManager.read();
2577
+ const targetName = url.searchParams.get("target") ?? void 0;
2578
+ const authStates = await (0, import_agent_qa_core5.listAuthStateMetadata)({
2579
+ configDir: (0, import_node_path5.dirname)((0, import_node_path5.resolve)(deps.configPath)),
2580
+ authStateDir: getConfiguredAuthStateDir(config),
2581
+ targetName
2582
+ });
2583
+ json(res, { authStates });
2584
+ } catch {
2585
+ json(res, { error: "Could not list auth states." }, 500);
2586
+ }
2587
+ })();
2588
+ return;
2589
+ }
2530
2590
  if (path === "/api/analytics/events" && req.method === "POST") {
2531
2591
  readJsonBody(req).then((body) => {
2532
2592
  if (!isPlainRecord(body) || !isDashboardProductEventName(body.name) || Object.prototype.hasOwnProperty.call(body, "properties") && body.properties !== void 0 && !isPlainRecord(body.properties)) {
@@ -2640,7 +2700,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2640
2700
  return;
2641
2701
  }
2642
2702
  const bundle = db.getRunArtifactBundle(runId);
2643
- json(res, {
2703
+ json(res, sanitizeAuthStateForResponse({
2644
2704
  run,
2645
2705
  artifact: sanitizeArtifactForResponse(bundle.artifact),
2646
2706
  children: bundle.children.map((child) => ({
@@ -2648,7 +2708,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2648
2708
  artifact: sanitizeArtifactForResponse(child.artifact)
2649
2709
  })),
2650
2710
  missingSections: getArtifactMissingSections(bundle.artifact)
2651
- });
2711
+ }));
2652
2712
  return;
2653
2713
  }
2654
2714
  if (path.startsWith("/api/runs/") && path.endsWith("/accessibility") && req.method === "GET") {
@@ -2674,7 +2734,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2674
2734
  const stepOrder = parseInt(stepOrderStr, 10);
2675
2735
  const trace = db.getReasoningTrace(runId, stepOrder);
2676
2736
  if (trace) {
2677
- json(res, { trace });
2737
+ json(res, sanitizeAuthStateForResponse({ trace }));
2678
2738
  return;
2679
2739
  }
2680
2740
  const steps = db.getSteps(runId);
@@ -2683,7 +2743,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2683
2743
  notFound(res, "Step not found");
2684
2744
  return;
2685
2745
  }
2686
- json(res, {
2746
+ json(res, sanitizeAuthStateForResponse({
2687
2747
  trace: {
2688
2748
  id: null,
2689
2749
  stepId: step.id,
@@ -2704,7 +2764,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2704
2764
  screenStateAfter: null,
2705
2765
  createdAt: step.createdAt
2706
2766
  }
2707
- });
2767
+ }));
2708
2768
  return;
2709
2769
  }
2710
2770
  if (path.startsWith("/api/runs/") && path.endsWith("/execution-logs") && req.method === "GET") {
@@ -2724,11 +2784,11 @@ function createRouter(dbOrDeps, artifactsDir) {
2724
2784
  for (const rid of allRunIds) {
2725
2785
  allLogs.push(...db.getExecutionLogs({ runId: rid, stepId, type }));
2726
2786
  }
2727
- json(res, { logs: allLogs });
2787
+ json(res, sanitizeAuthStateForResponse({ logs: allLogs }));
2728
2788
  return;
2729
2789
  }
2730
2790
  const logs = db.getExecutionLogs({ runId: id, stepId, type });
2731
- json(res, { logs });
2791
+ json(res, sanitizeAuthStateForResponse({ logs }));
2732
2792
  return;
2733
2793
  }
2734
2794
  if (path.startsWith("/api/runs/") && path.endsWith("/logs") && req.method === "GET") {
@@ -2745,7 +2805,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2745
2805
  const limit = url.searchParams.has("limit") ? parseInt(url.searchParams.get("limit"), 10) : 500;
2746
2806
  const offset = url.searchParams.has("offset") ? parseInt(url.searchParams.get("offset"), 10) : 0;
2747
2807
  const logs = db.getLogs({ runId: id, stepId, level, source, limit, offset });
2748
- json(res, { logs, total: logs.length });
2808
+ json(res, sanitizeAuthStateForResponse({ logs, total: logs.length }));
2749
2809
  return;
2750
2810
  }
2751
2811
  if (path.startsWith("/api/runs/") && path.endsWith("/steps") && req.method === "GET") {
@@ -2757,7 +2817,7 @@ function createRouter(dbOrDeps, artifactsDir) {
2757
2817
  return;
2758
2818
  }
2759
2819
  const steps = db.getSteps(id);
2760
- json(res, { steps });
2820
+ json(res, sanitizeAuthStateForResponse({ steps }));
2761
2821
  return;
2762
2822
  }
2763
2823
  if (path.startsWith("/api/runs/") && path.endsWith("/cancel") && req.method === "POST") {
@@ -2831,9 +2891,9 @@ function createRouter(dbOrDeps, artifactsDir) {
2831
2891
  const attempts = db.getRunsByParent(id);
2832
2892
  if (run.suiteId && !run.parentRunId) {
2833
2893
  const tests = db.getRunsByParent(id);
2834
- json(res, { run, steps, attempts, tests });
2894
+ json(res, sanitizeAuthStateForResponse({ run, steps, attempts, tests }));
2835
2895
  } else {
2836
- json(res, { run, steps, attempts });
2896
+ json(res, sanitizeAuthStateForResponse({ run, steps, attempts }));
2837
2897
  }
2838
2898
  return;
2839
2899
  }
@@ -3623,6 +3683,11 @@ data: ${JSON.stringify(event)}
3623
3683
  });
3624
3684
  return;
3625
3685
  }
3686
+ if (path === "/api/app-metadata" && req.method === "GET") {
3687
+ const version = (0, import_agent_qa_core5.getAgentQaVersion)().trim() || "0.0.0";
3688
+ json(res, { version });
3689
+ return;
3690
+ }
3626
3691
  if (path === "/api/config/targets" && req.method === "GET") {
3627
3692
  if (!configManager) {
3628
3693
  json(res, { error: "Config management not available" }, 501);
@@ -4441,7 +4506,7 @@ data: ${JSON.stringify(event)}
4441
4506
  const networkLogs = summarizeHookNetworkLogs(
4442
4507
  result.networkLogs
4443
4508
  );
4444
- json(res, {
4509
+ json(res, sanitizeAuthStateForResponse({
4445
4510
  success: result.success,
4446
4511
  status: result.success ? "passed" : "failed",
4447
4512
  executedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4459,9 +4524,9 @@ data: ${JSON.stringify(event)}
4459
4524
  networkLogsAvailable: networkLogs.length > 0,
4460
4525
  networkLogs
4461
4526
  }
4462
- });
4527
+ }));
4463
4528
  } catch (err) {
4464
- json(res, { error: err instanceof Error ? err.message : "Failed to run hook" }, 500);
4529
+ json(res, sanitizeAuthStateForResponse({ error: err instanceof Error ? err.message : "Failed to run hook" }), 500);
4465
4530
  }
4466
4531
  }).catch(() => json(res, { error: "Invalid request body" }, 400));
4467
4532
  return;
@@ -4710,6 +4775,45 @@ data: ${JSON.stringify(event)}
4710
4775
  })();
4711
4776
  return;
4712
4777
  }
4778
+ const liveEditorAuthStateMatch = path.match(/^\/api\/live-editor\/sessions\/([^/]+)\/auth-state$/);
4779
+ if (liveEditorAuthStateMatch && req.method === "POST") {
4780
+ if (!sessionManager) {
4781
+ json(res, { error: "Live editor not available" }, 503);
4782
+ return;
4783
+ }
4784
+ ;
4785
+ (async () => {
4786
+ let stateName = "";
4787
+ let session;
4788
+ try {
4789
+ const body = await readJsonBody(req);
4790
+ const parsedName = import_agent_qa_core5.AuthStateNameSchema.safeParse(typeof body?.name === "string" ? body.name : "");
4791
+ if (!parsedName.success) {
4792
+ json(res, { error: "Auth state name must be a lowercase slug." }, 400);
4793
+ return;
4794
+ }
4795
+ stateName = parsedName.data;
4796
+ session = sessionManager.getSession(decodeURIComponent(liveEditorAuthStateMatch[1]));
4797
+ if (!session) {
4798
+ notFound(res, "Session not found");
4799
+ return;
4800
+ }
4801
+ if (typeof session.captureWebAuthState !== "function") {
4802
+ json(res, {
4803
+ error: `Could not save auth state "${stateName}" for target "${getSessionTargetNameForAuthState(session)}".`
4804
+ }, 500);
4805
+ return;
4806
+ }
4807
+ const authState = await session.captureWebAuthState(stateName, { replace: body?.replace === true });
4808
+ json(res, { authState });
4809
+ } catch (err) {
4810
+ const targetName = getSessionTargetNameForAuthState(session);
4811
+ const response = buildAuthStateSaveErrorMessage(stateName || "unknown", targetName, err);
4812
+ json(res, { error: response.message }, response.status);
4813
+ }
4814
+ })();
4815
+ return;
4816
+ }
4713
4817
  if (path === "/api/live-editor/sessions" && req.method === "POST") {
4714
4818
  if (!sessionManager) {
4715
4819
  json(res, { error: "Live editor not available" }, 503);
@@ -5914,6 +6018,7 @@ var import_node_crypto3 = require("crypto");
5914
6018
 
5915
6019
  // src/live-editor/live-session.ts
5916
6020
  var import_node_crypto2 = require("crypto");
6021
+ var import_node_path9 = require("path");
5917
6022
  var import_agent_qa_core9 = require("@vostride/agent-qa-core");
5918
6023
 
5919
6024
  // src/live-editor/mobile-bootstrap.ts
@@ -6065,6 +6170,26 @@ function coerceRunJSResult(value) {
6065
6170
  }
6066
6171
  return JSON.stringify(value);
6067
6172
  }
6173
+ function getWebStorageStateContext(adapter) {
6174
+ if (!adapter || !("getPage" in adapter)) return null;
6175
+ const page = adapter.getPage?.();
6176
+ if (!page || typeof page !== "object") return null;
6177
+ const contextFactory = page.context;
6178
+ if (typeof contextFactory !== "function") return null;
6179
+ const context = contextFactory.call(page);
6180
+ if (!context || typeof context !== "object") return null;
6181
+ const storageState = context.storageState;
6182
+ if (typeof storageState !== "function") return null;
6183
+ return context;
6184
+ }
6185
+ function getConfiguredAuthStateDir2(config) {
6186
+ const services = config.services;
6187
+ if (!services || typeof services !== "object") return void 0;
6188
+ const authState = services.authState;
6189
+ if (!authState || typeof authState !== "object") return void 0;
6190
+ const dir = authState.dir;
6191
+ return typeof dir === "string" ? dir : void 0;
6192
+ }
6068
6193
  function toLiveSubActionData(result) {
6069
6194
  return result.trace?.subActions?.map((subAction) => ({
6070
6195
  index: subAction.index,
@@ -6142,6 +6267,7 @@ var LiveSession = class {
6142
6267
  previousSteps = [];
6143
6268
  currentStep = null;
6144
6269
  platform = "web";
6270
+ targetName = null;
6145
6271
  currentUrl = null;
6146
6272
  executing = false;
6147
6273
  idleTimer = null;
@@ -6179,7 +6305,7 @@ var LiveSession = class {
6179
6305
  this.pendingMessages.push(message);
6180
6306
  }
6181
6307
  redact(value) {
6182
- return (0, import_agent_qa_core9.redactSecretValue)(value, this.secretRedactor);
6308
+ return (0, import_agent_qa_core9.redactAuthStateValue)(value, { secretRedactor: this.secretRedactor });
6183
6309
  }
6184
6310
  resolveSecrets(value) {
6185
6311
  return (0, import_agent_qa_core9.resolveSecretTemplatesInValue)(value, this.secretStore);
@@ -6409,6 +6535,7 @@ var LiveSession = class {
6409
6535
  }
6410
6536
  async initialize(config) {
6411
6537
  this.platform = config.platform;
6538
+ this.targetName = typeof config.targetName === "string" && config.targetName.trim().length > 0 ? config.targetName.trim() : null;
6412
6539
  this._modelName = config.llmConfig.model ?? "unknown";
6413
6540
  this.variableStore = new import_agent_qa_core9.VariableStore();
6414
6541
  this.secretStore = config.secretStore;
@@ -6899,6 +7026,62 @@ ${errorLines.join("\n")}`;
6899
7026
  this.messageSink = originalSink;
6900
7027
  }
6901
7028
  }
7029
+ async captureWebAuthState(name, options = {}) {
7030
+ if (this.platform !== "web") {
7031
+ throw new Error("Auth-state capture is only available for web Live Mode sessions.");
7032
+ }
7033
+ if (!this.readyForInteraction || !this.adapter) {
7034
+ throw new Error(this.terminalError ?? "Live session is not ready for auth-state capture.");
7035
+ }
7036
+ if (this.adapter.platform !== "web") {
7037
+ throw new Error("Auth-state capture is only available for web Live Mode sessions.");
7038
+ }
7039
+ if (this.executing) {
7040
+ throw new Error("Cannot save auth state while the Live Mode session is executing.");
7041
+ }
7042
+ if (!this.targetName) {
7043
+ throw new Error("Cannot save auth state because this Live Mode session has no configured target.");
7044
+ }
7045
+ if (!this.deps.configManager || !this.deps.configPath) {
7046
+ throw new Error("Cannot save auth state because workspace config is unavailable.");
7047
+ }
7048
+ const context = getWebStorageStateContext(this.adapter);
7049
+ if (!context) {
7050
+ throw new Error("Cannot save auth state because the active browser context is unavailable.");
7051
+ }
7052
+ const config = await this.deps.configManager.read();
7053
+ const authStateDir = getConfiguredAuthStateDir2(config);
7054
+ const paths = (0, import_agent_qa_core9.resolveAuthStatePaths)({
7055
+ configDir: (0, import_node_path9.dirname)((0, import_node_path9.resolve)(this.deps.configPath)),
7056
+ authStateDir,
7057
+ targetName: this.targetName,
7058
+ stateName: name,
7059
+ target: { platform: "web" }
7060
+ });
7061
+ let existing = null;
7062
+ try {
7063
+ existing = await (0, import_agent_qa_core9.readAuthStateMetadata)(paths);
7064
+ } catch {
7065
+ existing = null;
7066
+ }
7067
+ if (existing && options.replace !== true) {
7068
+ throw new Error(`Auth state "${paths.stateName}" for target "${paths.targetName}" already exists. Use replace=true to replace it.`);
7069
+ }
7070
+ const payload = await context.storageState({ indexedDB: true });
7071
+ const metadata = {
7072
+ version: import_agent_qa_core9.AUTH_STATE_SCHEMA_VERSION,
7073
+ kind: "web",
7074
+ target: paths.targetName,
7075
+ name: paths.stateName,
7076
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
7077
+ };
7078
+ try {
7079
+ await (0, import_agent_qa_core9.writeAuthStateFiles)(paths, { payload, metadata });
7080
+ } catch {
7081
+ throw new Error(`Could not save auth state "${paths.stateName}" for target "${paths.targetName}".`);
7082
+ }
7083
+ return metadata;
7084
+ }
6902
7085
  cancelStep() {
6903
7086
  this.abortController?.abort();
6904
7087
  }
@@ -6959,6 +7142,7 @@ ${errorLines.join("\n")}`;
6959
7142
  return {
6960
7143
  sessionId: this.sessionId,
6961
7144
  platform: this.adapter?.platform ?? this.platform,
7145
+ targetName: this.targetName,
6962
7146
  status: this.status === "terminated" ? "idle" : this.status === "executing" ? "executing" : this.status,
6963
7147
  currentStep: this.currentStep,
6964
7148
  currentUrl: this.currentUrl,
@@ -7437,7 +7621,7 @@ function isAnalyticsPrivacyEnabled(config) {
7437
7621
  }
7438
7622
  async function startServer(opts) {
7439
7623
  const { db, port = 3470, uiDir, artifactsDir, workspacePaths, configPath, mcp, llmConfig, authFetch } = opts;
7440
- const resolvedConfigPath = configPath ?? (0, import_node_path9.resolve)("agent-qa.config.yaml");
7624
+ const resolvedConfigPath = configPath ?? (0, import_node_path10.resolve)("agent-qa.config.yaml");
7441
7625
  const configManager = new ConfigManager(resolvedConfigPath);
7442
7626
  const config = await configManager.read().catch(() => ({}));
7443
7627
  const concurrency = typeof config.concurrency === "number" ? config.concurrency : (0, import_node_os2.cpus)().length;
@@ -7757,12 +7941,12 @@ async function startServer(opts) {
7757
7941
  return;
7758
7942
  }
7759
7943
  if (uiDir) {
7760
- const filePath = path === "/" ? (0, import_node_path9.join)(uiDir, "index.html") : (0, import_node_path9.join)(uiDir, path);
7944
+ const filePath = path === "/" ? (0, import_node_path10.join)(uiDir, "index.html") : (0, import_node_path10.join)(uiDir, path);
7761
7945
  try {
7762
7946
  const fileStat = await (0, import_promises9.stat)(filePath);
7763
7947
  if (fileStat.isFile()) {
7764
7948
  const content = await (0, import_promises9.readFile)(filePath);
7765
- const ext = (0, import_node_path9.extname)(filePath);
7949
+ const ext = (0, import_node_path10.extname)(filePath);
7766
7950
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
7767
7951
  res.writeHead(200, {
7768
7952
  "Content-Type": contentType,
@@ -7775,7 +7959,7 @@ async function startServer(opts) {
7775
7959
  } catch {
7776
7960
  }
7777
7961
  try {
7778
- const indexPath = (0, import_node_path9.join)(uiDir, "index.html");
7962
+ const indexPath = (0, import_node_path10.join)(uiDir, "index.html");
7779
7963
  const content = await (0, import_promises9.readFile)(indexPath);
7780
7964
  res.writeHead(200, {
7781
7965
  "Content-Type": "text/html",