@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.
@@ -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
  }
@@ -4446,7 +4506,7 @@ data: ${JSON.stringify(event)}
4446
4506
  const networkLogs = summarizeHookNetworkLogs(
4447
4507
  result.networkLogs
4448
4508
  );
4449
- json(res, {
4509
+ json(res, sanitizeAuthStateForResponse({
4450
4510
  success: result.success,
4451
4511
  status: result.success ? "passed" : "failed",
4452
4512
  executedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4464,9 +4524,9 @@ data: ${JSON.stringify(event)}
4464
4524
  networkLogsAvailable: networkLogs.length > 0,
4465
4525
  networkLogs
4466
4526
  }
4467
- });
4527
+ }));
4468
4528
  } catch (err) {
4469
- 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);
4470
4530
  }
4471
4531
  }).catch(() => json(res, { error: "Invalid request body" }, 400));
4472
4532
  return;
@@ -4715,6 +4775,45 @@ data: ${JSON.stringify(event)}
4715
4775
  })();
4716
4776
  return;
4717
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
+ }
4718
4817
  if (path === "/api/live-editor/sessions" && req.method === "POST") {
4719
4818
  if (!sessionManager) {
4720
4819
  json(res, { error: "Live editor not available" }, 503);
@@ -5919,6 +6018,7 @@ var import_node_crypto3 = require("crypto");
5919
6018
 
5920
6019
  // src/live-editor/live-session.ts
5921
6020
  var import_node_crypto2 = require("crypto");
6021
+ var import_node_path9 = require("path");
5922
6022
  var import_agent_qa_core9 = require("@vostride/agent-qa-core");
5923
6023
 
5924
6024
  // src/live-editor/mobile-bootstrap.ts
@@ -6070,6 +6170,26 @@ function coerceRunJSResult(value) {
6070
6170
  }
6071
6171
  return JSON.stringify(value);
6072
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
+ }
6073
6193
  function toLiveSubActionData(result) {
6074
6194
  return result.trace?.subActions?.map((subAction) => ({
6075
6195
  index: subAction.index,
@@ -6147,6 +6267,7 @@ var LiveSession = class {
6147
6267
  previousSteps = [];
6148
6268
  currentStep = null;
6149
6269
  platform = "web";
6270
+ targetName = null;
6150
6271
  currentUrl = null;
6151
6272
  executing = false;
6152
6273
  idleTimer = null;
@@ -6184,7 +6305,7 @@ var LiveSession = class {
6184
6305
  this.pendingMessages.push(message);
6185
6306
  }
6186
6307
  redact(value) {
6187
- return (0, import_agent_qa_core9.redactSecretValue)(value, this.secretRedactor);
6308
+ return (0, import_agent_qa_core9.redactAuthStateValue)(value, { secretRedactor: this.secretRedactor });
6188
6309
  }
6189
6310
  resolveSecrets(value) {
6190
6311
  return (0, import_agent_qa_core9.resolveSecretTemplatesInValue)(value, this.secretStore);
@@ -6414,6 +6535,7 @@ var LiveSession = class {
6414
6535
  }
6415
6536
  async initialize(config) {
6416
6537
  this.platform = config.platform;
6538
+ this.targetName = typeof config.targetName === "string" && config.targetName.trim().length > 0 ? config.targetName.trim() : null;
6417
6539
  this._modelName = config.llmConfig.model ?? "unknown";
6418
6540
  this.variableStore = new import_agent_qa_core9.VariableStore();
6419
6541
  this.secretStore = config.secretStore;
@@ -6904,6 +7026,62 @@ ${errorLines.join("\n")}`;
6904
7026
  this.messageSink = originalSink;
6905
7027
  }
6906
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
+ }
6907
7085
  cancelStep() {
6908
7086
  this.abortController?.abort();
6909
7087
  }
@@ -6964,6 +7142,7 @@ ${errorLines.join("\n")}`;
6964
7142
  return {
6965
7143
  sessionId: this.sessionId,
6966
7144
  platform: this.adapter?.platform ?? this.platform,
7145
+ targetName: this.targetName,
6967
7146
  status: this.status === "terminated" ? "idle" : this.status === "executing" ? "executing" : this.status,
6968
7147
  currentStep: this.currentStep,
6969
7148
  currentUrl: this.currentUrl,
@@ -7442,7 +7621,7 @@ function isAnalyticsPrivacyEnabled(config) {
7442
7621
  }
7443
7622
  async function startServer(opts) {
7444
7623
  const { db, port = 3470, uiDir, artifactsDir, workspacePaths, configPath, mcp, llmConfig, authFetch } = opts;
7445
- 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");
7446
7625
  const configManager = new ConfigManager(resolvedConfigPath);
7447
7626
  const config = await configManager.read().catch(() => ({}));
7448
7627
  const concurrency = typeof config.concurrency === "number" ? config.concurrency : (0, import_node_os2.cpus)().length;
@@ -7762,12 +7941,12 @@ async function startServer(opts) {
7762
7941
  return;
7763
7942
  }
7764
7943
  if (uiDir) {
7765
- 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);
7766
7945
  try {
7767
7946
  const fileStat = await (0, import_promises9.stat)(filePath);
7768
7947
  if (fileStat.isFile()) {
7769
7948
  const content = await (0, import_promises9.readFile)(filePath);
7770
- const ext = (0, import_node_path9.extname)(filePath);
7949
+ const ext = (0, import_node_path10.extname)(filePath);
7771
7950
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
7772
7951
  res.writeHead(200, {
7773
7952
  "Content-Type": contentType,
@@ -7780,7 +7959,7 @@ async function startServer(opts) {
7780
7959
  } catch {
7781
7960
  }
7782
7961
  try {
7783
- const indexPath = (0, import_node_path9.join)(uiDir, "index.html");
7962
+ const indexPath = (0, import_node_path10.join)(uiDir, "index.html");
7784
7963
  const content = await (0, import_promises9.readFile)(indexPath);
7785
7964
  res.writeHead(200, {
7786
7965
  "Content-Type": "text/html",