pi-agent-browser-native 0.3.0 → 0.6.5
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/CHANGELOG.md +265 -0
- package/README.md +130 -54
- package/dist/extensions/agent-browser/index.js +781 -169
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -3
- package/dist/extensions/agent-browser/lib/argv-grammar.js +50 -2
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +5 -8
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +53 -12
- package/dist/extensions/agent-browser/lib/config-policy.js +25 -1
- package/dist/extensions/agent-browser/lib/config.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -13
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -24
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -12
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +26 -4
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +6 -139
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +26 -116
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -4
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +54 -25
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +54 -48
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +71 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +6 -7
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +119 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +7 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +152 -64
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +244 -102
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +63 -37
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +20 -21
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +36 -18
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -21
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/page-target-validation.js +270 -0
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +32 -10
- package/dist/extensions/agent-browser/lib/playbook.js +29 -25
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +5 -12
- package/dist/extensions/agent-browser/lib/process.js +130 -104
- package/dist/extensions/agent-browser/lib/recording-reservations.js +116 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -6
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +62 -4
- package/dist/extensions/agent-browser/lib/results/categories.js +6 -1
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -5
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -38
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +86 -18
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +38 -2
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +18 -17
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +38 -20
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +60 -15
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -10
- package/dist/extensions/agent-browser/lib/results/presentation.js +36 -6
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +3 -1
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +9 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +54 -11
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +2 -1
- package/dist/extensions/agent-browser/lib/results/snapshot.js +4 -4
- package/dist/extensions/agent-browser/lib/runtime.js +186 -108
- package/dist/extensions/agent-browser/lib/session-page-state.js +71 -10
- package/dist/extensions/agent-browser/lib/temp.js +1 -2
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/lib/web-search.js +108 -24
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +21 -0
- package/docs/ARCHITECTURE.md +57 -34
- package/docs/COMMAND_REFERENCE.md +255 -68
- package/docs/ELECTRON.md +2 -2
- package/docs/RELEASE.md +12 -10
- package/docs/REQUIREMENTS.md +11 -8
- package/docs/SUPPORT_MATRIX.md +36 -24
- package/docs/TOOL_CONTRACT.md +169 -95
- package/package.json +3 -1
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +87 -9
- package/scripts/agent-browser-target.mjs +21 -0
- package/scripts/build.mjs +41 -0
- package/scripts/config.mjs +1 -0
- package/scripts/doctor.mjs +16 -9
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -20
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +0 -583
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -78
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -37
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS, optionalGlobalValueFlagConsumesNext } from "./argv-grammar.js";
|
|
1
|
+
import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS, optionalGlobalValueFlagConsumesNext, stripUpstreamGlobalFlags } from "./argv-grammar.js";
|
|
2
2
|
import { isOpenNavigationCommand } from "./command-taxonomy.js";
|
|
3
3
|
function isBooleanLiteral(token) {
|
|
4
4
|
const normalized = token?.trim().toLowerCase();
|
|
@@ -33,6 +33,33 @@ export function extractCommandTokens(args) {
|
|
|
33
33
|
const commandStartIndex = findCommandStartIndex(args);
|
|
34
34
|
return commandStartIndex === undefined ? [] : args.slice(commandStartIndex);
|
|
35
35
|
}
|
|
36
|
+
export function extractUpstreamCommandTokens(args) {
|
|
37
|
+
return stripUpstreamGlobalFlags(extractCommandTokens(args));
|
|
38
|
+
}
|
|
39
|
+
export function parseWaitCommandTokens(commandTokens) {
|
|
40
|
+
if (commandTokens[0] !== "wait")
|
|
41
|
+
return {};
|
|
42
|
+
const considered = commandTokens.slice(1).map((token, offset) => ({ index: offset + 1, token }));
|
|
43
|
+
const timeoutIndex = considered.findIndex((entry) => entry.token === "--timeout");
|
|
44
|
+
if (timeoutIndex >= 0)
|
|
45
|
+
considered.splice(timeoutIndex, Math.min(2, considered.length - timeoutIndex));
|
|
46
|
+
for (const flags of [["--url", "-u"], ["--load", "-l"], ["--fn", "-f"], ["--text", "-t"]]) {
|
|
47
|
+
const match = considered.find((entry) => flags.includes(entry.token));
|
|
48
|
+
if (match)
|
|
49
|
+
return { subcommand: match.token };
|
|
50
|
+
}
|
|
51
|
+
const download = considered.find((entry) => entry.token === "--download" || entry.token === "-d");
|
|
52
|
+
if (download) {
|
|
53
|
+
const downloadPathIndex = download.index + 1;
|
|
54
|
+
const candidate = commandTokens[downloadPathIndex];
|
|
55
|
+
return {
|
|
56
|
+
downloadPath: candidate && !candidate.startsWith("--") ? candidate : undefined,
|
|
57
|
+
downloadPathIndex: candidate && !candidate.startsWith("--") ? downloadPathIndex : undefined,
|
|
58
|
+
subcommand: download.token,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { subcommand: considered[0]?.token };
|
|
62
|
+
}
|
|
36
63
|
function getOpenCommandTarget(commandTokens) {
|
|
37
64
|
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
38
65
|
const token = commandTokens[index];
|
|
@@ -51,10 +78,13 @@ function getOpenCommandTarget(commandTokens) {
|
|
|
51
78
|
return undefined;
|
|
52
79
|
}
|
|
53
80
|
export function parseCommandInfoFromTokens(commandTokens) {
|
|
54
|
-
const
|
|
81
|
+
const upstreamCommandTokens = stripUpstreamGlobalFlags(commandTokens);
|
|
82
|
+
const command = upstreamCommandTokens[0];
|
|
55
83
|
return {
|
|
56
84
|
command,
|
|
57
|
-
subcommand: isOpenNavigationCommand(command)
|
|
85
|
+
subcommand: isOpenNavigationCommand(command)
|
|
86
|
+
? getOpenCommandTarget(upstreamCommandTokens)
|
|
87
|
+
: command === "wait" ? parseWaitCommandTokens(upstreamCommandTokens).subcommand : upstreamCommandTokens[1],
|
|
58
88
|
};
|
|
59
89
|
}
|
|
60
90
|
export function parseCommandInfo(args) {
|
|
@@ -62,8 +92,10 @@ export function parseCommandInfo(args) {
|
|
|
62
92
|
}
|
|
63
93
|
export function parseArgvDescriptor(args) {
|
|
64
94
|
const commandTokens = extractCommandTokens(args);
|
|
95
|
+
const upstreamCommandTokens = stripUpstreamGlobalFlags(commandTokens);
|
|
65
96
|
return {
|
|
66
97
|
commandInfo: parseCommandInfoFromTokens(commandTokens),
|
|
67
98
|
commandTokens,
|
|
99
|
+
upstreamCommandTokens,
|
|
68
100
|
};
|
|
69
101
|
}
|
|
@@ -12,6 +12,7 @@ export const GLOBAL_VALUE_FLAGS = [
|
|
|
12
12
|
"--restore-check-fn",
|
|
13
13
|
"--proxy",
|
|
14
14
|
"--proxy-bypass",
|
|
15
|
+
"--ca-cert",
|
|
15
16
|
"--headers",
|
|
16
17
|
"--executable-path",
|
|
17
18
|
"--extension",
|
|
@@ -37,6 +38,7 @@ export const GLOBAL_VALUE_FLAGS = [
|
|
|
37
38
|
"--idle-timeout",
|
|
38
39
|
];
|
|
39
40
|
export const COMMAND_VALUE_FLAGS = [
|
|
41
|
+
"--allowed-origins",
|
|
40
42
|
"--baseline",
|
|
41
43
|
"--body",
|
|
42
44
|
"--categories",
|
|
@@ -47,6 +49,7 @@ export const COMMAND_VALUE_FLAGS = [
|
|
|
47
49
|
"--domain",
|
|
48
50
|
"--expires",
|
|
49
51
|
"--filter",
|
|
52
|
+
"--frame",
|
|
50
53
|
"--fn",
|
|
51
54
|
"--label",
|
|
52
55
|
"--load",
|
|
@@ -57,6 +60,7 @@ export const COMMAND_VALUE_FLAGS = [
|
|
|
57
60
|
"--prefix",
|
|
58
61
|
"--path",
|
|
59
62
|
"--port",
|
|
63
|
+
"--params",
|
|
60
64
|
"--resource-type",
|
|
61
65
|
"--resource-types",
|
|
62
66
|
"--sameSite",
|
|
@@ -90,6 +94,13 @@ export const GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES = new Set([
|
|
|
90
94
|
"--ignore-https-errors",
|
|
91
95
|
"--json",
|
|
92
96
|
"--no-auto-dialog",
|
|
97
|
+
"--no-ca-cert",
|
|
98
|
+
"--no-pin-tab",
|
|
99
|
+
"--no-webmcp",
|
|
100
|
+
"--offline",
|
|
101
|
+
"--pin-tab",
|
|
102
|
+
"--quick",
|
|
103
|
+
"--fix",
|
|
93
104
|
"--quiet",
|
|
94
105
|
"-q",
|
|
95
106
|
"--verbose",
|
|
@@ -148,7 +159,7 @@ export function canonicalizeAgentBrowserNamespace(value) {
|
|
|
148
159
|
}
|
|
149
160
|
return normalized.replace(/[-_]+$/u, "") || undefined;
|
|
150
161
|
}
|
|
151
|
-
function foldAgentBrowserFilesystemIdentity(value, platform) {
|
|
162
|
+
export function foldAgentBrowserFilesystemIdentity(value, platform) {
|
|
152
163
|
if (platform !== "darwin" && platform !== "win32")
|
|
153
164
|
return value;
|
|
154
165
|
// APFS aliases include full Unicode folds such as ß/SS and ς/Σ, not just ASCII case.
|
|
@@ -160,7 +171,11 @@ export function getAgentBrowserSessionIdentityKey(sessionName, namespace, platfo
|
|
|
160
171
|
const canonicalSessionName = foldAgentBrowserFilesystemIdentity(sessionName, platform);
|
|
161
172
|
return identityNamespace ? `${identityNamespace}\0${canonicalSessionName}` : canonicalSessionName;
|
|
162
173
|
}
|
|
163
|
-
|
|
174
|
+
export function isAgentBrowserSessionIdentityKeyInNamespace(identityKey, namespace) {
|
|
175
|
+
const prefix = getAgentBrowserSessionIdentityKey("", namespace);
|
|
176
|
+
return prefix ? identityKey.startsWith(prefix) : !identityKey.includes("\0");
|
|
177
|
+
}
|
|
178
|
+
/** Mirror upstream global parsing: full argv, no `--` sentinel, and only global value payloads are skipped. */
|
|
164
179
|
export function scanUpstreamGlobalFlagOccurrences(args, targetFlag) {
|
|
165
180
|
const occurrences = [];
|
|
166
181
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -257,6 +272,39 @@ export function optionalGlobalValueFlagConsumesNext(flag, nextToken) {
|
|
|
257
272
|
return false;
|
|
258
273
|
return !isKnownCommandToken(nextToken);
|
|
259
274
|
}
|
|
275
|
+
export function projectUpstreamGlobalFlags(args) {
|
|
276
|
+
const indices = [];
|
|
277
|
+
const tokens = [];
|
|
278
|
+
let seenCommand = false;
|
|
279
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
280
|
+
const token = args[index];
|
|
281
|
+
if (token.startsWith("--restore="))
|
|
282
|
+
continue;
|
|
283
|
+
if (token === "--restore") {
|
|
284
|
+
if (!seenCommand && optionalGlobalValueFlagConsumesNext(token, args[index + 1]))
|
|
285
|
+
index += 1;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (PREVALIDATED_VALUE_FLAGS.has(token)) {
|
|
289
|
+
index += 1;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES.has(token)) {
|
|
293
|
+
if (["true", "false"].includes(args[index + 1] ?? ""))
|
|
294
|
+
index += 1;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
tokens.push(token);
|
|
298
|
+
indices.push(index);
|
|
299
|
+
if (isKnownCommandToken(token))
|
|
300
|
+
seenCommand = true;
|
|
301
|
+
}
|
|
302
|
+
return { indices, tokens };
|
|
303
|
+
}
|
|
304
|
+
/** Mirror upstream clean_args: remove global flags wherever they appear before command parsing. */
|
|
305
|
+
export function stripUpstreamGlobalFlags(args) {
|
|
306
|
+
return projectUpstreamGlobalFlags(args).tokens;
|
|
307
|
+
}
|
|
260
308
|
export function stripSessionlessShapeGlobalFlags(commandTokens) {
|
|
261
309
|
const stripped = [];
|
|
262
310
|
for (let index = 0; index < commandTokens.length; index += 1) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { extractUpstreamCommandTokens } from "./argv-descriptor.js";
|
|
2
|
+
import { isCloseAllCommand, isCloseCommand } from "./command-taxonomy.js";
|
|
3
|
+
import { isRecord } from "./parsing.js";
|
|
4
|
+
function getRowBrowserLaunched(row) {
|
|
5
|
+
const result = isRecord(row.result) ? row.result : isRecord(row.data) ? row.data : undefined;
|
|
6
|
+
const lifecycle = isRecord(row.lifecycle) ? row.lifecycle : isRecord(result?.lifecycle) ? result.lifecycle : undefined;
|
|
7
|
+
const effectiveLaunch = isRecord(lifecycle?.effectiveLaunch) ? lifecycle.effectiveLaunch : undefined;
|
|
8
|
+
return typeof effectiveLaunch?.browserLaunched === "boolean" ? effectiveLaunch.browserLaunched : undefined;
|
|
9
|
+
}
|
|
10
|
+
export function batchHasSuccessfulCloseAll(data, fallbackCommands = []) {
|
|
11
|
+
if (!Array.isArray(data))
|
|
12
|
+
return false;
|
|
13
|
+
return data.some((row, index) => {
|
|
14
|
+
if (!isRecord(row) || row.success !== true)
|
|
15
|
+
return false;
|
|
16
|
+
const rowCommand = Array.isArray(row.command) && row.command.every((token) => typeof token === "string")
|
|
17
|
+
? row.command
|
|
18
|
+
: fallbackCommands[index];
|
|
19
|
+
return rowCommand ? isCloseAllCommand(extractUpstreamCommandTokens(rowCommand)) : false;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function getSuccessfulBatchCloseLifecycle(rows, fallbackCommands = []) {
|
|
23
|
+
if (!Array.isArray(rows))
|
|
24
|
+
return undefined;
|
|
25
|
+
let sawClose = false;
|
|
26
|
+
let endsClosed = false;
|
|
27
|
+
let browserActiveAfterClose = false;
|
|
28
|
+
let recordingClosedAfterBatch = false;
|
|
29
|
+
let statePath;
|
|
30
|
+
for (const [index, row] of rows.entries()) {
|
|
31
|
+
if (!isRecord(row))
|
|
32
|
+
continue;
|
|
33
|
+
const stepSucceeded = row.success === true;
|
|
34
|
+
const rowCommand = Array.isArray(row.command) && row.command.every((token) => typeof token === "string")
|
|
35
|
+
? row.command
|
|
36
|
+
: fallbackCommands[index];
|
|
37
|
+
const browserLaunched = getRowBrowserLaunched(row);
|
|
38
|
+
if (!rowCommand) {
|
|
39
|
+
if (sawClose && browserLaunched !== false) {
|
|
40
|
+
endsClosed = false;
|
|
41
|
+
browserActiveAfterClose = true;
|
|
42
|
+
recordingClosedAfterBatch = false;
|
|
43
|
+
}
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const [command, subcommand] = extractUpstreamCommandTokens(rowCommand);
|
|
47
|
+
if (stepSucceeded && isCloseCommand(command)) {
|
|
48
|
+
sawClose = true;
|
|
49
|
+
endsClosed = true;
|
|
50
|
+
browserActiveAfterClose = false;
|
|
51
|
+
recordingClosedAfterBatch = true;
|
|
52
|
+
const result = isRecord(row.result) ? row.result : isRecord(row.data) ? row.data : undefined;
|
|
53
|
+
statePath = typeof result?.statePath === "string" ? result.statePath : undefined;
|
|
54
|
+
}
|
|
55
|
+
else if (sawClose && command === "record") {
|
|
56
|
+
if (browserLaunched !== false) {
|
|
57
|
+
endsClosed = false;
|
|
58
|
+
browserActiveAfterClose = true;
|
|
59
|
+
}
|
|
60
|
+
if (stepSucceeded && subcommand === "stop")
|
|
61
|
+
recordingClosedAfterBatch = true;
|
|
62
|
+
else if (stepSucceeded && browserActiveAfterClose && (subcommand === "start" || subcommand === "restart"))
|
|
63
|
+
recordingClosedAfterBatch = false;
|
|
64
|
+
}
|
|
65
|
+
else if (sawClose && browserLaunched !== false) {
|
|
66
|
+
endsClosed = false;
|
|
67
|
+
browserActiveAfterClose = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return sawClose ? { endsClosed, recordingClosedAfterBatch, statePath } : undefined;
|
|
71
|
+
}
|
|
@@ -5,8 +5,7 @@ const EMPTY_BOOLEAN_FLAGS = new Set();
|
|
|
5
5
|
const JSON_BOOLEAN_FLAGS = new Set(["--json"]);
|
|
6
6
|
const AUTH_SAVE_BOOLEAN_FLAGS = new Set(["--json", "--password-stdin"]);
|
|
7
7
|
const AUTH_SAVE_VALUE_FLAGS = new Set(["--password", "--password-selector", "--submit-selector", "--url", "--username", "--username-selector"]);
|
|
8
|
-
const
|
|
9
|
-
const DASHBOARD_START_VALUE_FLAGS = new Set(["--port"]);
|
|
8
|
+
const DASHBOARD_VALUE_FLAGS = new Set(["--allowed-origins", "--port"]);
|
|
10
9
|
const DOCTOR_BOOLEAN_FLAGS = new Set(["--fix", "--headed", "--json", "--offline", "--quick", "--webgpu"]);
|
|
11
10
|
const INSTALL_BOOLEAN_FLAGS = new Set(["--with-deps", "-d"]);
|
|
12
11
|
const STATE_SESSIONLESS_SUBCOMMANDS = new Set(["list", "show", "clear", "clean", "rename"]);
|
|
@@ -26,11 +25,9 @@ function isSessionlessAuthCommand(commandTokens) {
|
|
|
26
25
|
}
|
|
27
26
|
function isSessionlessDashboardCommand(commandTokens) {
|
|
28
27
|
const [, subcommand, ...rest] = commandTokens;
|
|
29
|
-
if (subcommand ===
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
return false;
|
|
33
|
-
return subcommand === "start" ? hasOnlyOptionFlags(rest, JSON_BOOLEAN_FLAGS, DASHBOARD_START_VALUE_FLAGS) : rest.length === 0;
|
|
28
|
+
if (subcommand === "stop")
|
|
29
|
+
return rest.length === 0;
|
|
30
|
+
return hasOnlyOptionFlags(subcommand === "start" ? rest : commandTokens.slice(1), JSON_BOOLEAN_FLAGS, DASHBOARD_VALUE_FLAGS);
|
|
34
31
|
}
|
|
35
32
|
function isSessionlessStateCommand(commandTokens) {
|
|
36
33
|
const [, subcommand, firstArg, secondArg, ...rest] = commandTokens;
|
|
@@ -96,5 +93,5 @@ function isSessionlessCommand(commandTokens) {
|
|
|
96
93
|
return false;
|
|
97
94
|
}
|
|
98
95
|
export function needsManagedSession(descriptor) {
|
|
99
|
-
return !isSessionlessCommand(descriptor.
|
|
96
|
+
return !isSessionlessCommand(descriptor.upstreamCommandTokens);
|
|
100
97
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const ADDITIONAL_COMMAND_TOKENS = [
|
|
2
|
-
"a11y", "auth", "chat", "clipboard", "confirm", "connect", "dashboard", "deny", "device", "dialog", "diff", "doctor", "errors", "eval", "find", "frame", "get", "highlight", "inspect", "install", "is", "mcp", "plugin", "plugins", "profiles", "profiler", "react", "record", "removeinitscript", "session", "set", "skills", "snapshot", "state", "stream", "trace", "upgrade", "vitals", "wait", "web-vitals", "window",
|
|
2
|
+
"a11y", "auth", "chat", "clipboard", "confirm", "connect", "dashboard", "deny", "device", "dialog", "diff", "doctor", "errors", "eval", "find", "frame", "get", "highlight", "inspect", "install", "is", "mcp", "plugin", "plugins", "profiles", "profiler", "react", "record", "removeinitscript", "session", "set", "skills", "snapshot", "state", "stream", "trace", "upgrade", "vitals", "wait", "web-vitals", "webmcp", "window",
|
|
3
3
|
];
|
|
4
|
+
const WEBMCP_PAGE_MUTATION_SUBCOMMANDS = new Set(["invoke", "result", "cancel"]);
|
|
4
5
|
const COMMAND_CAPABILITIES = [
|
|
5
6
|
{
|
|
6
7
|
command: "back",
|
|
@@ -61,6 +62,10 @@ const COMMAND_CAPABILITIES = [
|
|
|
61
62
|
invalidatesBatchRefs: true,
|
|
62
63
|
triggersPostMutationSnapshot: true,
|
|
63
64
|
},
|
|
65
|
+
{
|
|
66
|
+
command: "diff",
|
|
67
|
+
guardsPageRefs: true,
|
|
68
|
+
},
|
|
64
69
|
{
|
|
65
70
|
command: "download",
|
|
66
71
|
eligibleForPageChangeSummary: true,
|
|
@@ -92,6 +97,10 @@ const COMMAND_CAPABILITIES = [
|
|
|
92
97
|
command: "find",
|
|
93
98
|
eligibleForElectronHealthProbe: true,
|
|
94
99
|
},
|
|
100
|
+
{
|
|
101
|
+
command: "frame",
|
|
102
|
+
guardsPageRefs: true,
|
|
103
|
+
},
|
|
95
104
|
{
|
|
96
105
|
command: "focus",
|
|
97
106
|
guardsPageRefs: true,
|
|
@@ -104,6 +113,14 @@ const COMMAND_CAPABILITIES = [
|
|
|
104
113
|
navigationObservable: true,
|
|
105
114
|
triggersPostMutationSnapshot: true,
|
|
106
115
|
},
|
|
116
|
+
{
|
|
117
|
+
command: "get",
|
|
118
|
+
guardsPageRefs: true,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
command: "highlight",
|
|
122
|
+
guardsPageRefs: true,
|
|
123
|
+
},
|
|
107
124
|
{
|
|
108
125
|
command: "hover",
|
|
109
126
|
eligibleForPageChangeSummary: true,
|
|
@@ -111,6 +128,10 @@ const COMMAND_CAPABILITIES = [
|
|
|
111
128
|
invalidatesBatchRefs: true,
|
|
112
129
|
triggersPostMutationSnapshot: true,
|
|
113
130
|
},
|
|
131
|
+
{
|
|
132
|
+
command: "is",
|
|
133
|
+
guardsPageRefs: true,
|
|
134
|
+
},
|
|
114
135
|
{
|
|
115
136
|
command: "keydown",
|
|
116
137
|
eligibleForElectronHealthProbe: true,
|
|
@@ -185,10 +206,12 @@ const COMMAND_CAPABILITIES = [
|
|
|
185
206
|
{
|
|
186
207
|
command: "screenshot",
|
|
187
208
|
eligibleForPageChangeSummary: true,
|
|
209
|
+
guardsPageRefs: true,
|
|
188
210
|
},
|
|
189
211
|
{
|
|
190
212
|
command: "scroll",
|
|
191
213
|
eligibleForPageChangeSummary: true,
|
|
214
|
+
guardsPageRefs: true,
|
|
192
215
|
invalidatesBatchRefs: true,
|
|
193
216
|
triggersPostMutationSnapshot: true,
|
|
194
217
|
},
|
|
@@ -281,11 +304,14 @@ export function normalizeCommandName(command) {
|
|
|
281
304
|
export function isCloseCommand(command) {
|
|
282
305
|
return hasCommandCapability(command, "closesSession");
|
|
283
306
|
}
|
|
307
|
+
export function isCloseAllCommand(commandTokens) {
|
|
308
|
+
return isCloseCommand(commandTokens[0]) && commandTokens.slice(1).includes("--all");
|
|
309
|
+
}
|
|
284
310
|
export function isOpenNavigationCommand(command) {
|
|
285
311
|
return hasCommandCapability(command, "openNavigation");
|
|
286
312
|
}
|
|
287
|
-
export function isReadOnlyDiagnosticSessionTargetCommand(command,
|
|
288
|
-
return hasCommandCapability(command, "readOnlyDiagnosticSessionTarget");
|
|
313
|
+
export function isReadOnlyDiagnosticSessionTargetCommand(command, subcommand) {
|
|
314
|
+
return hasCommandCapability(command, "readOnlyDiagnosticSessionTarget") || (command === "webmcp" && subcommand === "list");
|
|
289
315
|
}
|
|
290
316
|
export function isSessionTabPinningExcludedCommand(command) {
|
|
291
317
|
return hasCommandCapability(command, "excludedFromPinning");
|
|
@@ -293,8 +319,19 @@ export function isSessionTabPinningExcludedCommand(command) {
|
|
|
293
319
|
export function isSessionTabPostCommandCorrectionExcludedCommand(command) {
|
|
294
320
|
return hasCommandCapability(command, "excludedFromPostCommandCorrection");
|
|
295
321
|
}
|
|
296
|
-
|
|
297
|
-
|
|
322
|
+
/** Upstream 0.33.2 record start swaps to a fresh active page before its already-active check, so even a failed start can replace the page; record restart navigates the current page only when a URL operand (any fourth token, mirroring upstream's positional slot) is present. */
|
|
323
|
+
export function isRecordPageTransitionCommand(tokens) {
|
|
324
|
+
if (tokens[0] !== "record")
|
|
325
|
+
return false;
|
|
326
|
+
if (tokens[1] === "start")
|
|
327
|
+
return true;
|
|
328
|
+
return tokens[1] === "restart" && tokens.length >= 4;
|
|
329
|
+
}
|
|
330
|
+
export function isWebMcpPageMutationCommand(tokens) {
|
|
331
|
+
return isWebMcpPageMutation(tokens[0], tokens[1]);
|
|
332
|
+
}
|
|
333
|
+
export function isRefInvalidatingBatchCommand(step) {
|
|
334
|
+
return hasCommandCapability(step[0], "invalidatesBatchRefs") || isRecordPageTransitionCommand(step) || isWebMcpPageMutationCommand(step);
|
|
298
335
|
}
|
|
299
336
|
export function isRefGuardedCommand(command) {
|
|
300
337
|
return hasCommandCapability(command, "guardsPageRefs");
|
|
@@ -302,17 +339,21 @@ export function isRefGuardedCommand(command) {
|
|
|
302
339
|
export function isElectronPostCommandHealthCommand(command) {
|
|
303
340
|
return hasCommandCapability(command, "eligibleForElectronHealthProbe");
|
|
304
341
|
}
|
|
305
|
-
|
|
306
|
-
return
|
|
342
|
+
function isWebMcpPageMutation(command, subcommand) {
|
|
343
|
+
return command === "webmcp" && WEBMCP_PAGE_MUTATION_SUBCOMMANDS.has(subcommand ?? "");
|
|
344
|
+
}
|
|
345
|
+
export function isNavigationObservableCommandName(command, subcommand) {
|
|
346
|
+
return hasCommandCapability(command, "navigationObservable") || isWebMcpPageMutation(command, subcommand);
|
|
307
347
|
}
|
|
308
348
|
export function isUnverifiedPageTransitionCommand(command, subcommand) {
|
|
309
349
|
return ["back", "connect", "eval", "forward", "reload"].includes(command ?? "")
|
|
310
350
|
|| (command === "state" && subcommand === "load")
|
|
311
|
-
|| (command === "tab" && subcommand !== undefined && !["list", "new"].includes(subcommand))
|
|
351
|
+
|| (command === "tab" && subcommand !== undefined && !["list", "new"].includes(subcommand))
|
|
352
|
+
|| isWebMcpPageMutation(command, subcommand);
|
|
312
353
|
}
|
|
313
|
-
export function isPageMutationCommand(command) {
|
|
314
|
-
return hasCommandCapability(command, "triggersPostMutationSnapshot");
|
|
354
|
+
export function isPageMutationCommand(command, subcommand) {
|
|
355
|
+
return hasCommandCapability(command, "triggersPostMutationSnapshot") || isWebMcpPageMutation(command, subcommand);
|
|
315
356
|
}
|
|
316
|
-
export function isPageChangeSummaryCommand(command) {
|
|
317
|
-
return hasCommandCapability(command, "eligibleForPageChangeSummary");
|
|
357
|
+
export function isPageChangeSummaryCommand(command, subcommand) {
|
|
358
|
+
return hasCommandCapability(command, "eligibleForPageChangeSummary") || isWebMcpPageMutation(command, subcommand);
|
|
318
359
|
}
|
|
@@ -7,10 +7,11 @@ import { join, resolve } from "node:path";
|
|
|
7
7
|
/** @typedef {"global" | "project" | "override"} ConfigLayerScope */
|
|
8
8
|
/** @typedef {"literal" | "env" | "command"} CredentialSourceKind */
|
|
9
9
|
/** @typedef {"exa" | "brave"} WebSearchProvider */
|
|
10
|
+
/** @typedef {"auto" | "fast" | "instant" | "deep-lite" | "deep" | "deep-reasoning"} ExaSearchType */
|
|
10
11
|
/** @typedef {"exaApiKey" | "braveApiKey"} WebSearchProviderConfigKey */
|
|
11
12
|
/** @typedef {{ provider: WebSearchProvider; apiKeyEnv: string; configKey: WebSearchProviderConfigKey; label: string }} WebSearchProviderDescriptor */
|
|
12
13
|
/** @typedef {{ name: string; policy?: BrowserDefaultProfilePolicy }} BrowserDefaultProfileConfig */
|
|
13
|
-
/** @typedef {{ enabled?: boolean; preferredProvider?: WebSearchProvider; braveApiKey?: string; exaApiKey?: string }} WebSearchConfig */
|
|
14
|
+
/** @typedef {{ enabled?: boolean; preferredProvider?: WebSearchProvider; defaultSearchType?: ExaSearchType; braveApiKey?: string; exaApiKey?: string }} WebSearchConfig */
|
|
14
15
|
/** @typedef {{ defaultProfile?: BrowserDefaultProfileConfig; executablePath?: string }} BrowserConfig */
|
|
15
16
|
/** @typedef {{ version?: 1; webSearch?: WebSearchConfig; browser?: BrowserConfig }} AgentBrowserConfig */
|
|
16
17
|
/** @typedef {{ config: AgentBrowserConfig; path: string; scope: ConfigLayerScope }} ConfigLayer */
|
|
@@ -45,6 +46,8 @@ export const WEB_SEARCH_PROVIDER_DESCRIPTORS = Object.freeze({
|
|
|
45
46
|
export const WEB_SEARCH_PROVIDERS = Object.freeze(["exa", "brave"]);
|
|
46
47
|
/** @type {WebSearchProvider} */
|
|
47
48
|
export const DEFAULT_WEB_SEARCH_PROVIDER = "exa";
|
|
49
|
+
/** @type {readonly ExaSearchType[]} */
|
|
50
|
+
export const EXA_SEARCH_TYPES = Object.freeze(["auto", "fast", "instant", "deep-lite", "deep", "deep-reasoning"]);
|
|
48
51
|
/** @type {Readonly<Record<WebSearchProvider, WebSearchProviderConfigKey>>} */
|
|
49
52
|
export const WEB_SEARCH_PROVIDER_CONFIG_KEYS = Object.freeze({
|
|
50
53
|
exa: WEB_SEARCH_PROVIDER_DESCRIPTORS.exa.configKey,
|
|
@@ -182,6 +185,24 @@ export function validateWebSearchProvider(value, path, errors) {
|
|
|
182
185
|
}
|
|
183
186
|
return provider;
|
|
184
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* @param {unknown} value
|
|
190
|
+
* @param {string} path
|
|
191
|
+
* @param {string[]} errors
|
|
192
|
+
* @returns {ExaSearchType | undefined}
|
|
193
|
+
*/
|
|
194
|
+
function validateExaSearchType(value, path, errors) {
|
|
195
|
+
if (value === undefined)
|
|
196
|
+
return undefined;
|
|
197
|
+
const searchType = validateString(value, path, errors)?.trim();
|
|
198
|
+
if (searchType === undefined)
|
|
199
|
+
return undefined;
|
|
200
|
+
if (!EXA_SEARCH_TYPES.includes(/** @type {ExaSearchType} */ (searchType))) {
|
|
201
|
+
errors.push(`${path} must be one of ${EXA_SEARCH_TYPES.join(", ")}.`);
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
return /** @type {ExaSearchType} */ (searchType);
|
|
205
|
+
}
|
|
185
206
|
/**
|
|
186
207
|
* @param {unknown} value
|
|
187
208
|
* @param {string} path
|
|
@@ -251,6 +272,9 @@ export function validateAgentBrowserConfig(value, path, errors, warnings) {
|
|
|
251
272
|
const preferredProvider = validateWebSearchProvider(value.webSearch.preferredProvider, `${path}.webSearch.preferredProvider`, errors);
|
|
252
273
|
if (preferredProvider)
|
|
253
274
|
webSearch.preferredProvider = preferredProvider;
|
|
275
|
+
const defaultSearchType = validateExaSearchType(value.webSearch.defaultSearchType, `${path}.webSearch.defaultSearchType`, errors);
|
|
276
|
+
if (defaultSearchType)
|
|
277
|
+
webSearch.defaultSearchType = defaultSearchType;
|
|
254
278
|
for (const provider of WEB_SEARCH_PROVIDERS) {
|
|
255
279
|
const descriptor = getWebSearchProviderDescriptor(provider);
|
|
256
280
|
const apiKey = validateString(value.webSearch[descriptor.configKey], `${path}.webSearch.${descriptor.configKey}`, errors);
|
|
@@ -2,7 +2,7 @@ import { exec as execCallback } from "node:child_process";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
import { SECRET_COMMAND_TIMEOUT_MS, buildAgentBrowserConfigState, getAgentBrowserConfigPaths, getWebSearchCredentialSource, getWebSearchProviderOrder, loadAgentBrowserConfigStateSync, mergeAgentBrowserConfig, parseAgentBrowserConfigLayer, resolveEnvInterpolations, } from "./config-policy.js";
|
|
5
|
-
export { AGENT_BROWSER_CONFIG_ENV, BRAVE_API_KEY_ENV, CONFIG_RELATIVE_PATH, DEFAULT_WEB_SEARCH_PROVIDER, EXA_API_KEY_ENV, GLOBAL_CONFIG_RELATIVE_PATH, SECRET_COMMAND_TIMEOUT_MS, WEB_SEARCH_PROVIDER_CONFIG_KEYS, WEB_SEARCH_PROVIDER_DESCRIPTORS, WEB_SEARCH_PROVIDER_ENV_VARS, WEB_SEARCH_PROVIDERS, buildAgentBrowserConfigState, buildWebSearchCredentialSources, canRegisterWebSearchTool, classifyCredentialSource, formatBrowserExecutableStatus, formatBrowserProfileStatus, getAgentBrowserConfigPaths, getCredentialSourceSummary, getGlobalAgentBrowserConfigPath, getProjectAgentBrowserConfigPath, getWebSearchCredentialSource, getWebSearchProviderConfigKey, getWebSearchProviderDescriptor, getWebSearchProviderEnvVar, getWebSearchProviderLabel, getWebSearchProviderOrder, hasPotentialCredentialSource, isPlaintextCredentialValue, isProjectSafeCredentialValueForProvider, isWebSearchProvider, loadAgentBrowserConfigStateSync, mergeAgentBrowserConfig, parseAgentBrowserConfigLayer, resolveEnvInterpolations, summarizeConfigFiles, validateAgentBrowserConfig, validateWebSearchProvider, } from "./config-policy.js";
|
|
5
|
+
export { AGENT_BROWSER_CONFIG_ENV, BRAVE_API_KEY_ENV, CONFIG_RELATIVE_PATH, DEFAULT_WEB_SEARCH_PROVIDER, EXA_API_KEY_ENV, EXA_SEARCH_TYPES, GLOBAL_CONFIG_RELATIVE_PATH, SECRET_COMMAND_TIMEOUT_MS, WEB_SEARCH_PROVIDER_CONFIG_KEYS, WEB_SEARCH_PROVIDER_DESCRIPTORS, WEB_SEARCH_PROVIDER_ENV_VARS, WEB_SEARCH_PROVIDERS, buildAgentBrowserConfigState, buildWebSearchCredentialSources, canRegisterWebSearchTool, classifyCredentialSource, formatBrowserExecutableStatus, formatBrowserProfileStatus, getAgentBrowserConfigPaths, getCredentialSourceSummary, getGlobalAgentBrowserConfigPath, getProjectAgentBrowserConfigPath, getWebSearchCredentialSource, getWebSearchProviderConfigKey, getWebSearchProviderDescriptor, getWebSearchProviderEnvVar, getWebSearchProviderLabel, getWebSearchProviderOrder, hasPotentialCredentialSource, isPlaintextCredentialValue, isProjectSafeCredentialValueForProvider, isWebSearchProvider, loadAgentBrowserConfigStateSync, mergeAgentBrowserConfig, parseAgentBrowserConfigLayer, resolveEnvInterpolations, summarizeConfigFiles, validateAgentBrowserConfig, validateWebSearchProvider, } from "./config-policy.js";
|
|
6
6
|
const exec = promisify(execCallback);
|
|
7
7
|
async function readConfigLayer(path, scope, errors, warnings) {
|
|
8
8
|
let raw;
|
|
@@ -213,15 +213,6 @@ export function compileAgentBrowserJob(input) {
|
|
|
213
213
|
}
|
|
214
214
|
return { compiled: { args: failFast ? ["batch", "--bail"] : ["batch"], failFast, stdin: JSON.stringify(steps.map((step) => step.args)), steps } };
|
|
215
215
|
}
|
|
216
|
-
export function isHttpOrHttpsUrl(url) {
|
|
217
|
-
try {
|
|
218
|
-
const protocol = new URL(url).protocol;
|
|
219
|
-
return protocol === "http:" || protocol === "https:";
|
|
220
|
-
}
|
|
221
|
-
catch {
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
216
|
function describeQaChecksRun(checks) {
|
|
226
217
|
const parts = [`load:${checks.loadState}`];
|
|
227
218
|
if (checks.expectedText.length > 0)
|
|
@@ -266,7 +257,7 @@ export function buildQaCompactPassText(options) {
|
|
|
266
257
|
lines.push(`Page: ${pageParts.join(" — ")}`);
|
|
267
258
|
lines.push(`Checks run: ${describeQaChecksRun(options.checks)} (${options.batchStepCount} batch step${options.batchStepCount === 1 ? "" : "s"})`);
|
|
268
259
|
if (options.checks.diagnosticsResetAtStart && (options.checks.checkNetwork || options.checks.checkConsole || options.checks.checkErrors)) {
|
|
269
|
-
lines.push("Diagnostic
|
|
260
|
+
lines.push("Diagnostic isolation: URL QA clears enabled network/console buffers, then snapshots any page-error residue before opening the target. Only unchanged residue is ignored because upstream page-error clear is not reliable.");
|
|
270
261
|
}
|
|
271
262
|
if (options.checks.attached && !options.checks.diagnosticsResetAtStart && (options.checks.checkNetwork || options.checks.checkConsole || options.checks.checkErrors)) {
|
|
272
263
|
lines.push("Attached diagnostics: existing upstream session console/network/error buffers were preserved; rows may include events from before qa.attached started.");
|
|
@@ -280,6 +271,19 @@ export function buildQaCompactPassText(options) {
|
|
|
280
271
|
lines.push("Full diagnostic matrix: see details.qaPreset and details.batchSteps.");
|
|
281
272
|
return lines.join("\n");
|
|
282
273
|
}
|
|
274
|
+
export function buildQaCompactFailureText(options) {
|
|
275
|
+
const lines = [options.qaPreset.summary];
|
|
276
|
+
const pageParts = [options.page?.title, options.page?.url].filter((part) => typeof part === "string" && part.length > 0);
|
|
277
|
+
if (pageParts.length > 0)
|
|
278
|
+
lines.push(`Page: ${pageParts.join(" — ")}`);
|
|
279
|
+
if (options.qaPreset.failedChecks.length > 0)
|
|
280
|
+
lines.push("Failed checks:", ...options.qaPreset.failedChecks.map((failure) => `- ${failure}`));
|
|
281
|
+
if (options.qaPreset.warnings.length > 0)
|
|
282
|
+
lines.push("Warnings:", ...options.qaPreset.warnings.map((warning) => `- ${warning}`));
|
|
283
|
+
lines.push(`Checks run: ${describeQaChecksRun(options.checks)} (${options.batchStepCount} batch step${options.batchStepCount === 1 ? "" : "s"})`);
|
|
284
|
+
lines.push("Full diagnostic matrix: see details.qaPreset and details.batchSteps.");
|
|
285
|
+
return lines.join("\n");
|
|
286
|
+
}
|
|
283
287
|
const QA_VISIBLE_TEXT_TIMEOUT_MS = 5_000;
|
|
284
288
|
function formatQaExpectedTextPreview(text) {
|
|
285
289
|
return JSON.stringify(text.length > 80 ? `${text.slice(0, 77)}...` : text);
|
|
@@ -348,6 +352,34 @@ function extractQaTextAssertionResultText(item) {
|
|
|
348
352
|
}
|
|
349
353
|
return undefined;
|
|
350
354
|
}
|
|
355
|
+
function qaErrorSignature(error) {
|
|
356
|
+
if (typeof error === "string")
|
|
357
|
+
return error;
|
|
358
|
+
try {
|
|
359
|
+
return JSON.stringify(error);
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return String(error);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function subtractQaBaselineErrors(errors, baselineErrors) {
|
|
366
|
+
const baselineCounts = new Map();
|
|
367
|
+
for (const error of baselineErrors) {
|
|
368
|
+
const signature = qaErrorSignature(error);
|
|
369
|
+
baselineCounts.set(signature, (baselineCounts.get(signature) ?? 0) + 1);
|
|
370
|
+
}
|
|
371
|
+
let ignoredCount = 0;
|
|
372
|
+
const novelErrors = errors.filter((error) => {
|
|
373
|
+
const signature = qaErrorSignature(error);
|
|
374
|
+
const count = baselineCounts.get(signature) ?? 0;
|
|
375
|
+
if (count === 0)
|
|
376
|
+
return true;
|
|
377
|
+
baselineCounts.set(signature, count - 1);
|
|
378
|
+
ignoredCount += 1;
|
|
379
|
+
return false;
|
|
380
|
+
});
|
|
381
|
+
return { ignoredCount, novelErrors };
|
|
382
|
+
}
|
|
351
383
|
function isDiagnosticResetCommand(item) {
|
|
352
384
|
const command = item.command;
|
|
353
385
|
if (!Array.isArray(command) || !command.every((token) => typeof token === "string"))
|
|
@@ -372,17 +404,29 @@ export function analyzeQaPresetResults(data, compiled) {
|
|
|
372
404
|
return undefined;
|
|
373
405
|
const failedChecks = [];
|
|
374
406
|
const warnings = [];
|
|
375
|
-
|
|
407
|
+
const baselineErrorIndex = compiled?.checks.diagnosticsResetAtStart && compiled.checks.checkErrors
|
|
408
|
+
? compiled.steps.findIndex((step) => step.generatedFrom === "qa.errorBaselineAfterClear")
|
|
409
|
+
: -1;
|
|
410
|
+
const baselineErrorItem = baselineErrorIndex >= 0 ? items[baselineErrorIndex] : undefined;
|
|
411
|
+
const baselineErrorResult = isRecord(baselineErrorItem?.result) ? baselineErrorItem.result : undefined;
|
|
412
|
+
const baselineErrors = Array.isArray(baselineErrorResult?.errors) ? baselineErrorResult.errors : [];
|
|
413
|
+
for (const [index, item] of items.entries()) {
|
|
376
414
|
if (item.success === false) {
|
|
377
415
|
failedChecks.push(`${getCommandNameFromBatchItem(item) ?? "step"} failed`);
|
|
378
416
|
}
|
|
417
|
+
if (index === baselineErrorIndex)
|
|
418
|
+
continue;
|
|
379
419
|
const result = isRecord(item.result) ? item.result : undefined;
|
|
380
420
|
const commandName = getCommandNameFromBatchItem(item);
|
|
381
421
|
if (compiled?.checks.diagnosticsResetAtStart && isDiagnosticResetCommand(item)) {
|
|
382
422
|
continue;
|
|
383
423
|
}
|
|
384
424
|
if (commandName === "errors" && Array.isArray(result?.errors) && result.errors.length > 0) {
|
|
385
|
-
|
|
425
|
+
const { ignoredCount, novelErrors } = subtractQaBaselineErrors(result.errors, baselineErrors);
|
|
426
|
+
if (novelErrors.length > 0)
|
|
427
|
+
failedChecks.push(`${novelErrors.length} page error(s)`);
|
|
428
|
+
if (ignoredCount > 0)
|
|
429
|
+
warnings.push(`${ignoredCount} post-clear page error residue row(s) ignored as unchanged`);
|
|
386
430
|
}
|
|
387
431
|
if (commandName === "console" && Array.isArray(result?.messages)) {
|
|
388
432
|
const errorCount = result.messages.filter((message) => isRecord(message) && /error/i.test(String(message.type ?? message.level ?? ""))).length;
|
|
@@ -477,11 +521,15 @@ export function compileAgentBrowserQaPreset(input) {
|
|
|
477
521
|
steps.push({ action: "wait", args: ["network", "requests", "--clear"] });
|
|
478
522
|
if (diagnosticsResetAtStart && checkConsole)
|
|
479
523
|
steps.push({ action: "wait", args: ["console", "--clear"] });
|
|
480
|
-
if (diagnosticsResetAtStart && checkErrors)
|
|
524
|
+
if (diagnosticsResetAtStart && checkErrors) {
|
|
481
525
|
steps.push({ action: "wait", args: ["errors", "--clear"] });
|
|
526
|
+
steps.push({ action: "wait", args: ["errors"], generatedFrom: "qa.errorBaselineAfterClear" });
|
|
527
|
+
}
|
|
482
528
|
if (!attached && normalizedUrl)
|
|
483
529
|
steps.push({ action: "open", args: ["open", normalizedUrl] });
|
|
484
530
|
steps.push({ action: "wait", args: ["wait", "--load", loadState] });
|
|
531
|
+
if (checkConsole || checkErrors)
|
|
532
|
+
steps.push({ action: "wait", args: ["wait", "150"], generatedFrom: "qa.diagnosticSettle" });
|
|
485
533
|
for (const text of expectedText) {
|
|
486
534
|
steps.push({ action: "assertText", args: ["wait", "--fn", buildQaVisibleTextPredicate(text), "--timeout", String(QA_VISIBLE_TEXT_TIMEOUT_MS)] });
|
|
487
535
|
}
|
|
@@ -261,8 +261,8 @@ export function compileAgentBrowserNetworkSourceLookup(input) {
|
|
|
261
261
|
return { error: "networkSourceLookup.filter must be a non-empty string when provided." };
|
|
262
262
|
if (requestId !== undefined && (typeof requestId !== "string" || requestId.trim().length === 0))
|
|
263
263
|
return { error: "networkSourceLookup.requestId must be a non-empty string when provided." };
|
|
264
|
-
if (namespace !== undefined && (typeof namespace !== "string" || namespace.trim().length === 0))
|
|
265
|
-
return { error: "networkSourceLookup.namespace must be a non-empty string when provided." };
|
|
264
|
+
if (namespace !== undefined && (typeof namespace !== "string" || (namespace !== "" && namespace.trim().length === 0)))
|
|
265
|
+
return { error: "networkSourceLookup.namespace must be a non-empty string or the empty default namespace when provided." };
|
|
266
266
|
if (session !== undefined && (typeof session !== "string" || session.trim().length === 0))
|
|
267
267
|
return { error: "networkSourceLookup.session must be a non-empty string when provided." };
|
|
268
268
|
if (url !== undefined && (typeof url !== "string" || url.trim().length === 0))
|