@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.
- package/dist/{index-BL7OautO.d.cts → index-ygQLgXzT.d.cts} +6 -1
- package/dist/{index-BL7OautO.d.ts → index-ygQLgXzT.d.ts} +6 -1
- package/dist/index.cjs +206 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +209 -21
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +205 -21
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +207 -19
- package/dist/server/index.js.map +1 -1
- package/package.json +7 -7
|
@@ -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.
|
|
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
|
|
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
|
}
|
|
@@ -6277,6 +6337,11 @@ data: ${JSON.stringify(event)}
|
|
|
6277
6337
|
});
|
|
6278
6338
|
return;
|
|
6279
6339
|
}
|
|
6340
|
+
if (path === "/api/app-metadata" && req.method === "GET") {
|
|
6341
|
+
const version = (0, import_agent_qa_core9.getAgentQaVersion)().trim() || "0.0.0";
|
|
6342
|
+
json(res, { version });
|
|
6343
|
+
return;
|
|
6344
|
+
}
|
|
6280
6345
|
if (path === "/api/config/targets" && req.method === "GET") {
|
|
6281
6346
|
if (!configManager) {
|
|
6282
6347
|
json(res, { error: "Config management not available" }, 501);
|
|
@@ -7095,7 +7160,7 @@ data: ${JSON.stringify(event)}
|
|
|
7095
7160
|
const networkLogs = summarizeHookNetworkLogs(
|
|
7096
7161
|
result.networkLogs
|
|
7097
7162
|
);
|
|
7098
|
-
json(res, {
|
|
7163
|
+
json(res, sanitizeAuthStateForResponse({
|
|
7099
7164
|
success: result.success,
|
|
7100
7165
|
status: result.success ? "passed" : "failed",
|
|
7101
7166
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7113,9 +7178,9 @@ data: ${JSON.stringify(event)}
|
|
|
7113
7178
|
networkLogsAvailable: networkLogs.length > 0,
|
|
7114
7179
|
networkLogs
|
|
7115
7180
|
}
|
|
7116
|
-
});
|
|
7181
|
+
}));
|
|
7117
7182
|
} catch (err) {
|
|
7118
|
-
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);
|
|
7119
7184
|
}
|
|
7120
7185
|
}).catch(() => json(res, { error: "Invalid request body" }, 400));
|
|
7121
7186
|
return;
|
|
@@ -7364,6 +7429,45 @@ data: ${JSON.stringify(event)}
|
|
|
7364
7429
|
})();
|
|
7365
7430
|
return;
|
|
7366
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
|
+
}
|
|
7367
7471
|
if (path === "/api/live-editor/sessions" && req.method === "POST") {
|
|
7368
7472
|
if (!sessionManager) {
|
|
7369
7473
|
json(res, { error: "Live editor not available" }, 503);
|
|
@@ -8568,6 +8672,7 @@ var import_node_crypto4 = require("crypto");
|
|
|
8568
8672
|
|
|
8569
8673
|
// src/live-editor/live-session.ts
|
|
8570
8674
|
var import_node_crypto3 = require("crypto");
|
|
8675
|
+
var import_node_path13 = require("path");
|
|
8571
8676
|
var import_agent_qa_core13 = require("@vostride/agent-qa-core");
|
|
8572
8677
|
|
|
8573
8678
|
// src/live-editor/mobile-bootstrap.ts
|
|
@@ -8719,6 +8824,26 @@ function coerceRunJSResult(value) {
|
|
|
8719
8824
|
}
|
|
8720
8825
|
return JSON.stringify(value);
|
|
8721
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
|
+
}
|
|
8722
8847
|
function toLiveSubActionData(result) {
|
|
8723
8848
|
return result.trace?.subActions?.map((subAction) => ({
|
|
8724
8849
|
index: subAction.index,
|
|
@@ -8796,6 +8921,7 @@ var LiveSession = class {
|
|
|
8796
8921
|
previousSteps = [];
|
|
8797
8922
|
currentStep = null;
|
|
8798
8923
|
platform = "web";
|
|
8924
|
+
targetName = null;
|
|
8799
8925
|
currentUrl = null;
|
|
8800
8926
|
executing = false;
|
|
8801
8927
|
idleTimer = null;
|
|
@@ -8833,7 +8959,7 @@ var LiveSession = class {
|
|
|
8833
8959
|
this.pendingMessages.push(message);
|
|
8834
8960
|
}
|
|
8835
8961
|
redact(value) {
|
|
8836
|
-
return (0, import_agent_qa_core13.
|
|
8962
|
+
return (0, import_agent_qa_core13.redactAuthStateValue)(value, { secretRedactor: this.secretRedactor });
|
|
8837
8963
|
}
|
|
8838
8964
|
resolveSecrets(value) {
|
|
8839
8965
|
return (0, import_agent_qa_core13.resolveSecretTemplatesInValue)(value, this.secretStore);
|
|
@@ -9063,6 +9189,7 @@ var LiveSession = class {
|
|
|
9063
9189
|
}
|
|
9064
9190
|
async initialize(config) {
|
|
9065
9191
|
this.platform = config.platform;
|
|
9192
|
+
this.targetName = typeof config.targetName === "string" && config.targetName.trim().length > 0 ? config.targetName.trim() : null;
|
|
9066
9193
|
this._modelName = config.llmConfig.model ?? "unknown";
|
|
9067
9194
|
this.variableStore = new import_agent_qa_core13.VariableStore();
|
|
9068
9195
|
this.secretStore = config.secretStore;
|
|
@@ -9553,6 +9680,62 @@ ${errorLines.join("\n")}`;
|
|
|
9553
9680
|
this.messageSink = originalSink;
|
|
9554
9681
|
}
|
|
9555
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
|
+
}
|
|
9556
9739
|
cancelStep() {
|
|
9557
9740
|
this.abortController?.abort();
|
|
9558
9741
|
}
|
|
@@ -9613,6 +9796,7 @@ ${errorLines.join("\n")}`;
|
|
|
9613
9796
|
return {
|
|
9614
9797
|
sessionId: this.sessionId,
|
|
9615
9798
|
platform: this.adapter?.platform ?? this.platform,
|
|
9799
|
+
targetName: this.targetName,
|
|
9616
9800
|
status: this.status === "terminated" ? "idle" : this.status === "executing" ? "executing" : this.status,
|
|
9617
9801
|
currentStep: this.currentStep,
|
|
9618
9802
|
currentUrl: this.currentUrl,
|
|
@@ -10091,7 +10275,7 @@ function isAnalyticsPrivacyEnabled(config) {
|
|
|
10091
10275
|
}
|
|
10092
10276
|
async function startServer(opts) {
|
|
10093
10277
|
const { db, port = 3470, uiDir, artifactsDir, workspacePaths, configPath, mcp, llmConfig, authFetch } = opts;
|
|
10094
|
-
const resolvedConfigPath = configPath ?? (0,
|
|
10278
|
+
const resolvedConfigPath = configPath ?? (0, import_node_path14.resolve)("agent-qa.config.yaml");
|
|
10095
10279
|
const configManager = new ConfigManager(resolvedConfigPath);
|
|
10096
10280
|
const config = await configManager.read().catch(() => ({}));
|
|
10097
10281
|
const concurrency = typeof config.concurrency === "number" ? config.concurrency : (0, import_node_os2.cpus)().length;
|
|
@@ -10411,12 +10595,12 @@ async function startServer(opts) {
|
|
|
10411
10595
|
return;
|
|
10412
10596
|
}
|
|
10413
10597
|
if (uiDir) {
|
|
10414
|
-
const filePath = path === "/" ? (0,
|
|
10598
|
+
const filePath = path === "/" ? (0, import_node_path14.join)(uiDir, "index.html") : (0, import_node_path14.join)(uiDir, path);
|
|
10415
10599
|
try {
|
|
10416
10600
|
const fileStat = await (0, import_promises11.stat)(filePath);
|
|
10417
10601
|
if (fileStat.isFile()) {
|
|
10418
10602
|
const content = await (0, import_promises11.readFile)(filePath);
|
|
10419
|
-
const ext = (0,
|
|
10603
|
+
const ext = (0, import_node_path14.extname)(filePath);
|
|
10420
10604
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
10421
10605
|
res.writeHead(200, {
|
|
10422
10606
|
"Content-Type": contentType,
|
|
@@ -10429,7 +10613,7 @@ async function startServer(opts) {
|
|
|
10429
10613
|
} catch {
|
|
10430
10614
|
}
|
|
10431
10615
|
try {
|
|
10432
|
-
const indexPath = (0,
|
|
10616
|
+
const indexPath = (0, import_node_path14.join)(uiDir, "index.html");
|
|
10433
10617
|
const content = await (0, import_promises11.readFile)(indexPath);
|
|
10434
10618
|
res.writeHead(200, {
|
|
10435
10619
|
"Content-Type": "text/html",
|