@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.
- 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 +201 -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 +204 -21
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +200 -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 +202 -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
|
}
|
|
@@ -7100,7 +7160,7 @@ data: ${JSON.stringify(event)}
|
|
|
7100
7160
|
const networkLogs = summarizeHookNetworkLogs(
|
|
7101
7161
|
result.networkLogs
|
|
7102
7162
|
);
|
|
7103
|
-
json(res, {
|
|
7163
|
+
json(res, sanitizeAuthStateForResponse({
|
|
7104
7164
|
success: result.success,
|
|
7105
7165
|
status: result.success ? "passed" : "failed",
|
|
7106
7166
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7118,9 +7178,9 @@ data: ${JSON.stringify(event)}
|
|
|
7118
7178
|
networkLogsAvailable: networkLogs.length > 0,
|
|
7119
7179
|
networkLogs
|
|
7120
7180
|
}
|
|
7121
|
-
});
|
|
7181
|
+
}));
|
|
7122
7182
|
} catch (err) {
|
|
7123
|
-
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);
|
|
7124
7184
|
}
|
|
7125
7185
|
}).catch(() => json(res, { error: "Invalid request body" }, 400));
|
|
7126
7186
|
return;
|
|
@@ -7369,6 +7429,45 @@ data: ${JSON.stringify(event)}
|
|
|
7369
7429
|
})();
|
|
7370
7430
|
return;
|
|
7371
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
|
+
}
|
|
7372
7471
|
if (path === "/api/live-editor/sessions" && req.method === "POST") {
|
|
7373
7472
|
if (!sessionManager) {
|
|
7374
7473
|
json(res, { error: "Live editor not available" }, 503);
|
|
@@ -8573,6 +8672,7 @@ var import_node_crypto4 = require("crypto");
|
|
|
8573
8672
|
|
|
8574
8673
|
// src/live-editor/live-session.ts
|
|
8575
8674
|
var import_node_crypto3 = require("crypto");
|
|
8675
|
+
var import_node_path13 = require("path");
|
|
8576
8676
|
var import_agent_qa_core13 = require("@vostride/agent-qa-core");
|
|
8577
8677
|
|
|
8578
8678
|
// src/live-editor/mobile-bootstrap.ts
|
|
@@ -8724,6 +8824,26 @@ function coerceRunJSResult(value) {
|
|
|
8724
8824
|
}
|
|
8725
8825
|
return JSON.stringify(value);
|
|
8726
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
|
+
}
|
|
8727
8847
|
function toLiveSubActionData(result) {
|
|
8728
8848
|
return result.trace?.subActions?.map((subAction) => ({
|
|
8729
8849
|
index: subAction.index,
|
|
@@ -8801,6 +8921,7 @@ var LiveSession = class {
|
|
|
8801
8921
|
previousSteps = [];
|
|
8802
8922
|
currentStep = null;
|
|
8803
8923
|
platform = "web";
|
|
8924
|
+
targetName = null;
|
|
8804
8925
|
currentUrl = null;
|
|
8805
8926
|
executing = false;
|
|
8806
8927
|
idleTimer = null;
|
|
@@ -8838,7 +8959,7 @@ var LiveSession = class {
|
|
|
8838
8959
|
this.pendingMessages.push(message);
|
|
8839
8960
|
}
|
|
8840
8961
|
redact(value) {
|
|
8841
|
-
return (0, import_agent_qa_core13.
|
|
8962
|
+
return (0, import_agent_qa_core13.redactAuthStateValue)(value, { secretRedactor: this.secretRedactor });
|
|
8842
8963
|
}
|
|
8843
8964
|
resolveSecrets(value) {
|
|
8844
8965
|
return (0, import_agent_qa_core13.resolveSecretTemplatesInValue)(value, this.secretStore);
|
|
@@ -9068,6 +9189,7 @@ var LiveSession = class {
|
|
|
9068
9189
|
}
|
|
9069
9190
|
async initialize(config) {
|
|
9070
9191
|
this.platform = config.platform;
|
|
9192
|
+
this.targetName = typeof config.targetName === "string" && config.targetName.trim().length > 0 ? config.targetName.trim() : null;
|
|
9071
9193
|
this._modelName = config.llmConfig.model ?? "unknown";
|
|
9072
9194
|
this.variableStore = new import_agent_qa_core13.VariableStore();
|
|
9073
9195
|
this.secretStore = config.secretStore;
|
|
@@ -9558,6 +9680,62 @@ ${errorLines.join("\n")}`;
|
|
|
9558
9680
|
this.messageSink = originalSink;
|
|
9559
9681
|
}
|
|
9560
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
|
+
}
|
|
9561
9739
|
cancelStep() {
|
|
9562
9740
|
this.abortController?.abort();
|
|
9563
9741
|
}
|
|
@@ -9618,6 +9796,7 @@ ${errorLines.join("\n")}`;
|
|
|
9618
9796
|
return {
|
|
9619
9797
|
sessionId: this.sessionId,
|
|
9620
9798
|
platform: this.adapter?.platform ?? this.platform,
|
|
9799
|
+
targetName: this.targetName,
|
|
9621
9800
|
status: this.status === "terminated" ? "idle" : this.status === "executing" ? "executing" : this.status,
|
|
9622
9801
|
currentStep: this.currentStep,
|
|
9623
9802
|
currentUrl: this.currentUrl,
|
|
@@ -10096,7 +10275,7 @@ function isAnalyticsPrivacyEnabled(config) {
|
|
|
10096
10275
|
}
|
|
10097
10276
|
async function startServer(opts) {
|
|
10098
10277
|
const { db, port = 3470, uiDir, artifactsDir, workspacePaths, configPath, mcp, llmConfig, authFetch } = opts;
|
|
10099
|
-
const resolvedConfigPath = configPath ?? (0,
|
|
10278
|
+
const resolvedConfigPath = configPath ?? (0, import_node_path14.resolve)("agent-qa.config.yaml");
|
|
10100
10279
|
const configManager = new ConfigManager(resolvedConfigPath);
|
|
10101
10280
|
const config = await configManager.read().catch(() => ({}));
|
|
10102
10281
|
const concurrency = typeof config.concurrency === "number" ? config.concurrency : (0, import_node_os2.cpus)().length;
|
|
@@ -10416,12 +10595,12 @@ async function startServer(opts) {
|
|
|
10416
10595
|
return;
|
|
10417
10596
|
}
|
|
10418
10597
|
if (uiDir) {
|
|
10419
|
-
const filePath = path === "/" ? (0,
|
|
10598
|
+
const filePath = path === "/" ? (0, import_node_path14.join)(uiDir, "index.html") : (0, import_node_path14.join)(uiDir, path);
|
|
10420
10599
|
try {
|
|
10421
10600
|
const fileStat = await (0, import_promises11.stat)(filePath);
|
|
10422
10601
|
if (fileStat.isFile()) {
|
|
10423
10602
|
const content = await (0, import_promises11.readFile)(filePath);
|
|
10424
|
-
const ext = (0,
|
|
10603
|
+
const ext = (0, import_node_path14.extname)(filePath);
|
|
10425
10604
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
10426
10605
|
res.writeHead(200, {
|
|
10427
10606
|
"Content-Type": contentType,
|
|
@@ -10434,7 +10613,7 @@ async function startServer(opts) {
|
|
|
10434
10613
|
} catch {
|
|
10435
10614
|
}
|
|
10436
10615
|
try {
|
|
10437
|
-
const indexPath = (0,
|
|
10616
|
+
const indexPath = (0, import_node_path14.join)(uiDir, "index.html");
|
|
10438
10617
|
const content = await (0, import_promises11.readFile)(indexPath);
|
|
10439
10618
|
res.writeHead(200, {
|
|
10440
10619
|
"Content-Type": "text/html",
|