pi-agent-browser-native 0.2.48 → 0.2.49
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 +17 -0
- package/README.md +16 -6
- package/dist/extensions/agent-browser/index.js +785 -0
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
- package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
- package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
- package/dist/extensions/agent-browser/lib/config-policy.js +686 -0
- package/dist/extensions/agent-browser/lib/config.js +122 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
- package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
- package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
- package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
- package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
- package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
- package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
- package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
- package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
- package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
- package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
- package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
- package/dist/extensions/agent-browser/lib/playbook.js +121 -0
- package/dist/extensions/agent-browser/lib/process.js +448 -0
- package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
- package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
- package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
- package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
- package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
- package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
- package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
- package/dist/extensions/agent-browser/lib/results/network.js +73 -0
- package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +960 -0
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
- package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
- package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
- package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
- package/dist/extensions/agent-browser/lib/results/text.js +40 -0
- package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
- package/dist/extensions/agent-browser/lib/runtime.js +816 -0
- package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
- package/dist/extensions/agent-browser/lib/temp.js +498 -0
- package/dist/extensions/agent-browser/lib/web-search.js +562 -0
- package/docs/RELEASE.md +22 -11
- package/docs/SUPPORT_MATRIX.md +4 -3
- package/package.json +9 -5
- package/scripts/config.mjs +8 -2
- package/scripts/doctor.mjs +8 -7
- package/extensions/agent-browser/index.ts +0 -961
- package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
- package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
- package/extensions/agent-browser/lib/bash-guard.ts +0 -205
- package/extensions/agent-browser/lib/command-policy.ts +0 -71
- package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
- package/extensions/agent-browser/lib/config-policy.js +0 -690
- package/extensions/agent-browser/lib/config.ts +0 -211
- package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
- package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
- package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
- package/extensions/agent-browser/lib/electron/launch.ts +0 -499
- package/extensions/agent-browser/lib/executable-path.ts +0 -19
- package/extensions/agent-browser/lib/fs-utils.ts +0 -18
- package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
- package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
- package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
- package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
- package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
- package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
- package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
- package/extensions/agent-browser/lib/input-modes.ts +0 -45
- package/extensions/agent-browser/lib/json-schema.ts +0 -73
- package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
- package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
- package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
- package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
- package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
- package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
- package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
- package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
- package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
- package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
- package/extensions/agent-browser/lib/playbook.ts +0 -142
- package/extensions/agent-browser/lib/process.ts +0 -516
- package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
- package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
- package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
- package/extensions/agent-browser/lib/results/categories.ts +0 -106
- package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
- package/extensions/agent-browser/lib/results/contracts.ts +0 -241
- package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
- package/extensions/agent-browser/lib/results/envelope.ts +0 -195
- package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
- package/extensions/agent-browser/lib/results/network.ts +0 -78
- package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
- package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
- package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
- package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
- package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
- package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
- package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
- package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
- package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
- package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
- package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
- package/extensions/agent-browser/lib/results/presentation.ts +0 -257
- package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
- package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
- package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
- package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
- package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
- package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
- package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
- package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
- package/extensions/agent-browser/lib/results/text.ts +0 -40
- package/extensions/agent-browser/lib/runtime.ts +0 -988
- package/extensions/agent-browser/lib/session-page-state.ts +0 -512
- package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
- package/extensions/agent-browser/lib/temp.ts +0 -577
- package/extensions/agent-browser/lib/web-search.ts +0 -728
- /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purpose: Build safe, deterministic agent-browser invocations and persisted session state for the pi-agent-browser extension.
|
|
3
|
+
* Responsibilities: Validate raw tool arguments, derive extension-managed session names from the pi session identity, restore managed-session state from persisted tool details, redact sensitive invocation text, classify browser-oriented prompts, and build the effective CLI argument list passed to the upstream agent-browser binary.
|
|
4
|
+
* Scope: Pure runtime-planning helpers only; no subprocess execution or filesystem access lives here.
|
|
5
|
+
* Usage: Imported by the extension entrypoint and unit tests before spawning the upstream CLI.
|
|
6
|
+
* Invariants/Assumptions: The wrapper stays thin, preserves upstream command vocabulary, keeps plain-text inspection stateless,
|
|
7
|
+
* and only injects wrapper-owned flags: `--json`, an extension-managed `--session` when appropriate, and the narrow
|
|
8
|
+
* OpenAI/ChatGPT headless compatibility `--user-agent` when that workaround applies.
|
|
9
|
+
*/
|
|
10
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
11
|
+
import { basename } from "node:path";
|
|
12
|
+
import { findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, } from "./argv-descriptor.js";
|
|
13
|
+
import { GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE, PREVALIDATED_VALUE_FLAGS, } from "./argv-grammar.js";
|
|
14
|
+
import { needsManagedSession } from "./command-policy.js";
|
|
15
|
+
import { isCloseCommand, isOpenNavigationCommand } from "./command-taxonomy.js";
|
|
16
|
+
import { LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAG_LABEL, LAUNCH_SCOPED_TAB_CORRECTION_FLAGS } from "./launch-scoped-flags.js";
|
|
17
|
+
export { extractCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo } from "./argv-descriptor.js";
|
|
18
|
+
import { isRecord } from "./parsing.js";
|
|
19
|
+
const OPENAI_HEADLESS_COMPAT_HOSTS = new Set(["chat.com", "chat.openai.com", "chatgpt.com"]);
|
|
20
|
+
const AGENT_BROWSER_IDLE_TIMEOUT_ENV = "AGENT_BROWSER_IDLE_TIMEOUT_MS";
|
|
21
|
+
const IMPLICIT_SESSION_IDLE_TIMEOUT_ENV = "PI_AGENT_BROWSER_IMPLICIT_SESSION_IDLE_TIMEOUT_MS";
|
|
22
|
+
const IMPLICIT_SESSION_CLOSE_TIMEOUT_ENV = "PI_AGENT_BROWSER_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS";
|
|
23
|
+
const DEFAULT_IMPLICIT_SESSION_IDLE_TIMEOUT_MS = 15 * 60 * 1000;
|
|
24
|
+
const DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS = 5_000;
|
|
25
|
+
const INSPECTION_FLAGS = new Set(["--help", "-h", "--version", "-V"]);
|
|
26
|
+
const SENSITIVE_VALUE_FLAGS = new Set(["--body", "--headers", "--password", "--proxy"]);
|
|
27
|
+
const SENSITIVE_QUERY_PARAM_PATTERN = /^(?:access(?:_|-)?token|api(?:_|-)?key|auth|authorization|bearer|client(?:_|-)?secret|code|cookie|id(?:_|-)?token|key|pass(?:word)?|refresh(?:_|-)?token|secret|sentry(?:_|-)?key|session(?:_|-)?id|sig(?:nature)?|token|write(?:_|-)?key)$/i;
|
|
28
|
+
const SENSITIVE_FIELD_NAME_PATTERN = /^(?:access(?:_|-)?token|api(?:_|-)?key|auth(?:orization)?|bearer|client(?:_|-)?secret|cookie|id(?:_|-)?token|pass(?:word)?|proxy(?:_|-)?authorization|refresh(?:_|-)?token|secret|sentry(?:_|-)?key|session(?:_|-)?id|set(?:_|-)?cookie|sig(?:nature)?|token|write(?:_|-)?key|x(?:_|-)?api(?:_|-)?key)$/i;
|
|
29
|
+
const DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM = {
|
|
30
|
+
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
31
|
+
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
32
|
+
win32: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
33
|
+
};
|
|
34
|
+
const FALLBACK_HEADLESS_COMPAT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
|
|
35
|
+
const SHELL_OPERATOR_TOKENS = new Set(["&&", "||", "|", ";", ">", ">>", "<"]);
|
|
36
|
+
const MAX_PROJECT_SLUG_LENGTH = 24;
|
|
37
|
+
const SESSION_NAME_CWD_HASH_LENGTH = 8;
|
|
38
|
+
const SESSION_NAME_SESSION_ID_LENGTH = 12;
|
|
39
|
+
function isStringArray(value) {
|
|
40
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
41
|
+
}
|
|
42
|
+
function shouldRedactQueryParam(name) {
|
|
43
|
+
return SENSITIVE_QUERY_PARAM_PATTERN.test(name);
|
|
44
|
+
}
|
|
45
|
+
function redactUrlToken(token) {
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = new URL(token);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return token;
|
|
52
|
+
}
|
|
53
|
+
if (!["http:", "https:", "ws:", "wss:"].includes(parsed.protocol)) {
|
|
54
|
+
return token;
|
|
55
|
+
}
|
|
56
|
+
let mutated = false;
|
|
57
|
+
if (parsed.username.length > 0) {
|
|
58
|
+
parsed.username = "[REDACTED]";
|
|
59
|
+
mutated = true;
|
|
60
|
+
}
|
|
61
|
+
if (parsed.password.length > 0) {
|
|
62
|
+
parsed.password = "[REDACTED]";
|
|
63
|
+
mutated = true;
|
|
64
|
+
}
|
|
65
|
+
for (const [name] of parsed.searchParams) {
|
|
66
|
+
if (shouldRedactQueryParam(name)) {
|
|
67
|
+
parsed.searchParams.set(name, "[REDACTED]");
|
|
68
|
+
mutated = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const hashText = parsed.hash.startsWith("#") ? parsed.hash.slice(1) : parsed.hash;
|
|
72
|
+
if (hashText.includes("=")) {
|
|
73
|
+
const hashParams = new URLSearchParams(hashText);
|
|
74
|
+
for (const [name] of hashParams) {
|
|
75
|
+
if (shouldRedactQueryParam(name)) {
|
|
76
|
+
hashParams.set(name, "[REDACTED]");
|
|
77
|
+
mutated = true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (mutated) {
|
|
81
|
+
parsed.hash = `#${hashParams.toString()}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return parsed.toString();
|
|
85
|
+
}
|
|
86
|
+
function redactLooseUrlMatches(text) {
|
|
87
|
+
return text.replace(/\b(?:https?|wss?):\/\/[^\s"'`<>\])]+/g, (match) => redactUrlToken(match));
|
|
88
|
+
}
|
|
89
|
+
function findBalancedJsonEnd(text, startIndex) {
|
|
90
|
+
const opener = text[startIndex];
|
|
91
|
+
const closer = opener === "{" ? "}" : opener === "[" ? "]" : undefined;
|
|
92
|
+
if (!closer)
|
|
93
|
+
return undefined;
|
|
94
|
+
const stack = [closer];
|
|
95
|
+
let inString = false;
|
|
96
|
+
let escaped = false;
|
|
97
|
+
for (let index = startIndex + 1; index < text.length; index += 1) {
|
|
98
|
+
const char = text[index];
|
|
99
|
+
if (inString) {
|
|
100
|
+
if (escaped) {
|
|
101
|
+
escaped = false;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (char === "\\") {
|
|
105
|
+
escaped = true;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (char === '"') {
|
|
109
|
+
inString = false;
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (char === '"') {
|
|
114
|
+
inString = true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (char === "{") {
|
|
118
|
+
stack.push("}");
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (char === "[") {
|
|
122
|
+
stack.push("]");
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (char === "}" || char === "]") {
|
|
126
|
+
if (stack.pop() !== char)
|
|
127
|
+
return undefined;
|
|
128
|
+
if (stack.length === 0)
|
|
129
|
+
return index;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
function redactEmbeddedStructuredText(text) {
|
|
135
|
+
let output = "";
|
|
136
|
+
let cursor = 0;
|
|
137
|
+
while (cursor < text.length) {
|
|
138
|
+
const char = text[cursor];
|
|
139
|
+
if (char !== "{" && char !== "[") {
|
|
140
|
+
output += char;
|
|
141
|
+
cursor += 1;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const endIndex = findBalancedJsonEnd(text, cursor);
|
|
145
|
+
if (endIndex === undefined) {
|
|
146
|
+
output += char;
|
|
147
|
+
cursor += 1;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const candidate = text.slice(cursor, endIndex + 1);
|
|
151
|
+
try {
|
|
152
|
+
const parsed = JSON.parse(candidate);
|
|
153
|
+
const redacted = typeof parsed === "string" ? redactSensitiveText(parsed) : JSON.stringify(redactSensitiveValue(parsed));
|
|
154
|
+
const original = typeof parsed === "string" ? parsed : JSON.stringify(parsed);
|
|
155
|
+
output += redacted === original ? candidate : redacted;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
output += candidate;
|
|
159
|
+
}
|
|
160
|
+
cursor = endIndex + 1;
|
|
161
|
+
}
|
|
162
|
+
return output;
|
|
163
|
+
}
|
|
164
|
+
function redactStandaloneBasicCredential(text) {
|
|
165
|
+
return text.replace(/\b(Basic)\s+([A-Za-z0-9+/=]{12,})/gi, (match, label, credential) => {
|
|
166
|
+
if (!/[0-9+/=]/.test(credential))
|
|
167
|
+
return match;
|
|
168
|
+
return `${label} [REDACTED]`;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function credentialTrailingPunctuation(credential) {
|
|
172
|
+
return credential.match(/^(.+?)([,.]+)$/)?.[2] ?? "";
|
|
173
|
+
}
|
|
174
|
+
function isBearerHelpPlaceholder(label, credential, trailing) {
|
|
175
|
+
return label.toLowerCase() === "authorization bearer" && credential.toLowerCase() === "token" && trailing === ")";
|
|
176
|
+
}
|
|
177
|
+
function formatRedactedCredential(label, credential, trailing = "") {
|
|
178
|
+
return `${label} [REDACTED]${credentialTrailingPunctuation(credential)}${trailing}`;
|
|
179
|
+
}
|
|
180
|
+
function redactBearerCredentials(text) {
|
|
181
|
+
return text
|
|
182
|
+
.replace(/\b(Authorization\s*:\s*Bearer)\s+([^\s"',)\[\]]+)([),.]?)/gi, (_match, label, credential, trailing) => {
|
|
183
|
+
return formatRedactedCredential(label, credential, trailing);
|
|
184
|
+
})
|
|
185
|
+
.replace(/\b((?:Authorization\s+)?Bearer)\s+([^\s"',)\[\]]+)([),.]?)/gi, (match, label, credential, trailing) => {
|
|
186
|
+
if (isBearerHelpPlaceholder(label, credential, trailing))
|
|
187
|
+
return match;
|
|
188
|
+
return formatRedactedCredential(label, credential, trailing);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
export function redactSensitiveText(text) {
|
|
192
|
+
return redactEmbeddedStructuredText(redactStandaloneBasicCredential(redactBearerCredentials(redactLooseUrlMatches(text))
|
|
193
|
+
.replace(/\b(Authorization\s*:\s*Basic)\s+[^\s",]+/gi, "$1 [REDACTED]")
|
|
194
|
+
.replace(/\b(Cookie|Set-Cookie)\s*:\s*[^\n\r"]+/gi, "$1: [REDACTED]")));
|
|
195
|
+
}
|
|
196
|
+
export function redactSensitiveValue(value) {
|
|
197
|
+
if (typeof value === "string") {
|
|
198
|
+
return redactSensitiveText(value);
|
|
199
|
+
}
|
|
200
|
+
if (Array.isArray(value)) {
|
|
201
|
+
return value.map((item) => redactSensitiveValue(item));
|
|
202
|
+
}
|
|
203
|
+
if (!isRecord(value)) {
|
|
204
|
+
return value;
|
|
205
|
+
}
|
|
206
|
+
return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => {
|
|
207
|
+
if (SENSITIVE_FIELD_NAME_PATTERN.test(key)) {
|
|
208
|
+
return [key, "[REDACTED]"];
|
|
209
|
+
}
|
|
210
|
+
return [key, redactSensitiveValue(entryValue)];
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
function redactFlagValue(flag, value) {
|
|
214
|
+
if (SENSITIVE_VALUE_FLAGS.has(flag)) {
|
|
215
|
+
return "[REDACTED]";
|
|
216
|
+
}
|
|
217
|
+
return redactUrlToken(value);
|
|
218
|
+
}
|
|
219
|
+
export function redactInvocationArgs(args) {
|
|
220
|
+
const redacted = [];
|
|
221
|
+
let pendingValueFlag;
|
|
222
|
+
for (const token of args) {
|
|
223
|
+
if (pendingValueFlag) {
|
|
224
|
+
redacted.push(redactFlagValue(pendingValueFlag, token));
|
|
225
|
+
pendingValueFlag = undefined;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const normalizedToken = token.split("=", 1)[0] ?? token;
|
|
229
|
+
if (SENSITIVE_VALUE_FLAGS.has(normalizedToken)) {
|
|
230
|
+
if (token.includes("=")) {
|
|
231
|
+
redacted.push(`${normalizedToken}=[REDACTED]`);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
redacted.push(token);
|
|
235
|
+
pendingValueFlag = normalizedToken;
|
|
236
|
+
}
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
redacted.push(redactSensitiveText(redactUrlToken(token)));
|
|
240
|
+
}
|
|
241
|
+
const commandStartIndex = findCommandStartIndex(args);
|
|
242
|
+
if (commandStartIndex !== undefined && args[commandStartIndex] === "set" && args[commandStartIndex + 1] === "credentials") {
|
|
243
|
+
for (const index of [commandStartIndex + 2, commandStartIndex + 3]) {
|
|
244
|
+
if (redacted[index] !== undefined) {
|
|
245
|
+
redacted[index] = "[REDACTED]";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (commandStartIndex !== undefined && args[commandStartIndex] === "cookies" && args[commandStartIndex + 1] === "set" && redacted[commandStartIndex + 3] !== undefined) {
|
|
250
|
+
redacted[commandStartIndex + 3] = "[REDACTED]";
|
|
251
|
+
}
|
|
252
|
+
if (commandStartIndex !== undefined
|
|
253
|
+
&& args[commandStartIndex] === "storage"
|
|
254
|
+
&& ["local", "session"].includes(args[commandStartIndex + 1] ?? "")
|
|
255
|
+
&& args[commandStartIndex + 2] === "set"
|
|
256
|
+
&& redacted[commandStartIndex + 4] !== undefined) {
|
|
257
|
+
redacted[commandStartIndex + 4] = "[REDACTED]";
|
|
258
|
+
}
|
|
259
|
+
if (commandStartIndex !== undefined && args[commandStartIndex] === "clipboard" && args[commandStartIndex + 1] === "write") {
|
|
260
|
+
for (let index = commandStartIndex + 2; index < redacted.length; index += 1) {
|
|
261
|
+
redacted[index] = "[REDACTED]";
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return redacted;
|
|
265
|
+
}
|
|
266
|
+
export function isPlainTextInspectionArgs(args) {
|
|
267
|
+
return args.some((token) => INSPECTION_FLAGS.has(token));
|
|
268
|
+
}
|
|
269
|
+
function parseTimeoutMs(rawValue, minimumValue) {
|
|
270
|
+
if (typeof rawValue !== "string")
|
|
271
|
+
return undefined;
|
|
272
|
+
const normalizedValue = rawValue.trim();
|
|
273
|
+
if (!/^\d+$/.test(normalizedValue))
|
|
274
|
+
return undefined;
|
|
275
|
+
const parsedValue = Number(normalizedValue);
|
|
276
|
+
if (!Number.isSafeInteger(parsedValue) || parsedValue < minimumValue) {
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
return parsedValue;
|
|
280
|
+
}
|
|
281
|
+
export function getImplicitSessionIdleTimeoutMs(env = process.env) {
|
|
282
|
+
return parseTimeoutMs(env[IMPLICIT_SESSION_IDLE_TIMEOUT_ENV], 0) ??
|
|
283
|
+
parseTimeoutMs(env[AGENT_BROWSER_IDLE_TIMEOUT_ENV], 0) ??
|
|
284
|
+
DEFAULT_IMPLICIT_SESSION_IDLE_TIMEOUT_MS;
|
|
285
|
+
}
|
|
286
|
+
export function getImplicitSessionCloseTimeoutMs(env = process.env) {
|
|
287
|
+
return parseTimeoutMs(env[IMPLICIT_SESSION_CLOSE_TIMEOUT_ENV], 0) ?? DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS;
|
|
288
|
+
}
|
|
289
|
+
export function resolveManagedSessionState(options) {
|
|
290
|
+
const { command, managedSessionName, priorActive, priorSessionName, succeeded } = options;
|
|
291
|
+
if (!managedSessionName) {
|
|
292
|
+
return { active: priorActive, sessionName: priorSessionName };
|
|
293
|
+
}
|
|
294
|
+
if (isCloseCommand(command) && managedSessionName === priorSessionName) {
|
|
295
|
+
return { active: succeeded ? false : priorActive, sessionName: priorSessionName };
|
|
296
|
+
}
|
|
297
|
+
if (!succeeded) {
|
|
298
|
+
return { active: priorActive, sessionName: priorSessionName };
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
active: true,
|
|
302
|
+
replacedSessionName: priorActive && priorSessionName !== managedSessionName ? priorSessionName : undefined,
|
|
303
|
+
sessionName: managedSessionName,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function isRestorableManagedSessionName(sessionName, fallbackSessionName) {
|
|
307
|
+
return sessionName === fallbackSessionName || sessionName.startsWith(`${fallbackSessionName}-fresh-`);
|
|
308
|
+
}
|
|
309
|
+
function getManagedSessionRestoreRank(options) {
|
|
310
|
+
const { fallbackSessionName, freshSessionRanks, sessionName } = options;
|
|
311
|
+
if (sessionName === fallbackSessionName) {
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
if (!sessionName.startsWith(`${fallbackSessionName}-fresh-`)) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
const existingRank = freshSessionRanks.get(sessionName);
|
|
318
|
+
if (existingRank !== undefined) {
|
|
319
|
+
return existingRank;
|
|
320
|
+
}
|
|
321
|
+
const nextRank = freshSessionRanks.size + 1;
|
|
322
|
+
freshSessionRanks.set(sessionName, nextRank);
|
|
323
|
+
return nextRank;
|
|
324
|
+
}
|
|
325
|
+
function getRestorableManagedSessionName(value, fallbackSessionName) {
|
|
326
|
+
return typeof value === "string" && isRestorableManagedSessionName(value, fallbackSessionName) ? value : undefined;
|
|
327
|
+
}
|
|
328
|
+
function getElectronCleanupClosedManagedSessionNames(details, fallbackSessionName) {
|
|
329
|
+
const electron = isRecord(details.electron) ? details.electron : undefined;
|
|
330
|
+
const cleanup = isRecord(electron?.cleanup) ? electron.cleanup : undefined;
|
|
331
|
+
const results = Array.isArray(cleanup?.results) ? cleanup.results : [];
|
|
332
|
+
const closedSessionNames = new Set();
|
|
333
|
+
for (const result of results) {
|
|
334
|
+
if (!isRecord(result) || !Array.isArray(result.steps))
|
|
335
|
+
continue;
|
|
336
|
+
const record = isRecord(result.record) ? result.record : undefined;
|
|
337
|
+
for (const step of result.steps) {
|
|
338
|
+
if (!isRecord(step) || step.resource !== "managed-session")
|
|
339
|
+
continue;
|
|
340
|
+
if (step.state !== "removed" && step.state !== "already-gone")
|
|
341
|
+
continue;
|
|
342
|
+
const sessionName = getRestorableManagedSessionName(step.sessionName, fallbackSessionName)
|
|
343
|
+
?? getRestorableManagedSessionName(record?.sessionName, fallbackSessionName);
|
|
344
|
+
if (sessionName)
|
|
345
|
+
closedSessionNames.add(sessionName);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return [...closedSessionNames];
|
|
349
|
+
}
|
|
350
|
+
export function restoreManagedSessionStateFromBranch(branch, fallbackSessionName) {
|
|
351
|
+
let restoredState = {
|
|
352
|
+
active: false,
|
|
353
|
+
sessionName: fallbackSessionName,
|
|
354
|
+
};
|
|
355
|
+
let activeRestoreRank = 0;
|
|
356
|
+
let closedSessionName;
|
|
357
|
+
let freshSessionOrdinal = 0;
|
|
358
|
+
const freshSessionRanks = new Map();
|
|
359
|
+
const applyManagedClose = (sessionName) => {
|
|
360
|
+
const restoreRank = getManagedSessionRestoreRank({
|
|
361
|
+
fallbackSessionName,
|
|
362
|
+
freshSessionRanks,
|
|
363
|
+
sessionName,
|
|
364
|
+
});
|
|
365
|
+
if (restoreRank === undefined || sessionName !== restoredState.sessionName)
|
|
366
|
+
return;
|
|
367
|
+
restoredState = { active: false, sessionName: restoredState.sessionName };
|
|
368
|
+
closedSessionName = sessionName;
|
|
369
|
+
};
|
|
370
|
+
for (const entry of branch) {
|
|
371
|
+
if (!isRecord(entry) || entry.type !== "message") {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
375
|
+
if (!message || message.toolName !== "agent_browser") {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const details = isRecord(message.details) ? message.details : undefined;
|
|
379
|
+
if (!details) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
const args = isStringArray(details.args) ? details.args : [];
|
|
383
|
+
if (isPlainTextInspectionArgs(args)) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
for (const sessionName of getElectronCleanupClosedManagedSessionNames(details, fallbackSessionName)) {
|
|
387
|
+
applyManagedClose(sessionName);
|
|
388
|
+
}
|
|
389
|
+
const explicitSessionName = extractExplicitSessionName(args);
|
|
390
|
+
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
391
|
+
const sessionMode = details.sessionMode === "fresh" || details.sessionMode === "auto" ? details.sessionMode : undefined;
|
|
392
|
+
const usedImplicitSession = details.usedImplicitSession === true;
|
|
393
|
+
const command = typeof details.command === "string" ? details.command : parseCommandInfo(args).command;
|
|
394
|
+
const commandClosesSession = isCloseCommand(command);
|
|
395
|
+
const outcome = typeof details.managedSessionOutcome === "object" && details.managedSessionOutcome !== null ? details.managedSessionOutcome : undefined;
|
|
396
|
+
const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
|
|
397
|
+
const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
|
|
398
|
+
const outcomeAttemptedSessionName = getRestorableManagedSessionName(outcome?.attemptedSessionName, fallbackSessionName);
|
|
399
|
+
const outcomeClosedSessionName = outcomeStatus === "closed" && outcome?.succeeded === true
|
|
400
|
+
? outcomeAttemptedSessionName ?? getRestorableManagedSessionName(outcomeCurrentSessionName, fallbackSessionName) ?? getRestorableManagedSessionName(sessionName, fallbackSessionName)
|
|
401
|
+
: undefined;
|
|
402
|
+
const restorableDetailSessionName = getRestorableManagedSessionName(sessionName, fallbackSessionName);
|
|
403
|
+
const explicitCloseSessionName = commandClosesSession && explicitSessionName && restorableDetailSessionName === explicitSessionName
|
|
404
|
+
? restorableDetailSessionName
|
|
405
|
+
: undefined;
|
|
406
|
+
const managedSessionName = !explicitSessionName &&
|
|
407
|
+
restorableDetailSessionName &&
|
|
408
|
+
(usedImplicitSession || sessionMode === "fresh")
|
|
409
|
+
? restorableDetailSessionName
|
|
410
|
+
: commandClosesSession
|
|
411
|
+
? outcomeClosedSessionName ?? explicitCloseSessionName
|
|
412
|
+
: undefined;
|
|
413
|
+
if (!managedSessionName) {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
const restoreRank = getManagedSessionRestoreRank({
|
|
417
|
+
fallbackSessionName,
|
|
418
|
+
freshSessionRanks,
|
|
419
|
+
sessionName: managedSessionName,
|
|
420
|
+
});
|
|
421
|
+
if (restoreRank === undefined) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
freshSessionOrdinal = Math.max(freshSessionOrdinal, restoreRank);
|
|
425
|
+
const messageIsError = typeof message.isError === "boolean" ? message.isError : undefined;
|
|
426
|
+
const exitCode = typeof details.exitCode === "number" ? details.exitCode : undefined;
|
|
427
|
+
const outcomeActiveAfter = outcome?.activeAfter === true;
|
|
428
|
+
const outcomeRepresentsActiveCurrentSession = outcomeActiveAfter && outcomeCurrentSessionName === managedSessionName && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged");
|
|
429
|
+
const succeeded = outcomeRepresentsActiveCurrentSession ? true : messageIsError === undefined ? exitCode === undefined || exitCode === 0 : !messageIsError;
|
|
430
|
+
if (commandClosesSession) {
|
|
431
|
+
if (succeeded)
|
|
432
|
+
applyManagedClose(managedSessionName);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const staleCompletion = succeeded && restoreRank < activeRestoreRank;
|
|
436
|
+
if (staleCompletion) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
restoredState = resolveManagedSessionState({
|
|
440
|
+
command,
|
|
441
|
+
managedSessionName,
|
|
442
|
+
priorActive: restoredState.active,
|
|
443
|
+
priorSessionName: restoredState.sessionName,
|
|
444
|
+
succeeded,
|
|
445
|
+
});
|
|
446
|
+
if (succeeded && restoredState.active) {
|
|
447
|
+
activeRestoreRank = restoreRank;
|
|
448
|
+
closedSessionName = undefined;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
...restoredState,
|
|
453
|
+
...(closedSessionName ? { closedSessionName } : {}),
|
|
454
|
+
freshSessionOrdinal,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
export function createEphemeralSessionSeed() {
|
|
458
|
+
return randomUUID();
|
|
459
|
+
}
|
|
460
|
+
function createCwdHash(cwd) {
|
|
461
|
+
return createHash("sha256").update(`cwd:${cwd}`).digest("hex").slice(0, SESSION_NAME_CWD_HASH_LENGTH);
|
|
462
|
+
}
|
|
463
|
+
export function createImplicitSessionName(sessionId, cwd, ephemeralSeed) {
|
|
464
|
+
const slug = basename(cwd)
|
|
465
|
+
.toLowerCase()
|
|
466
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
467
|
+
.replace(/^-+|-+$/g, "")
|
|
468
|
+
.slice(0, MAX_PROJECT_SLUG_LENGTH) || "project";
|
|
469
|
+
const cwdHash = createCwdHash(cwd);
|
|
470
|
+
const stableSessionId = sessionId?.replace(/-/g, "").slice(0, SESSION_NAME_SESSION_ID_LENGTH);
|
|
471
|
+
if (stableSessionId && stableSessionId.length > 0) {
|
|
472
|
+
return `piab-${slug}-${stableSessionId}-${cwdHash}`;
|
|
473
|
+
}
|
|
474
|
+
const digest = createHash("sha256")
|
|
475
|
+
.update(`ephemeral:${cwd}:${ephemeralSeed}`)
|
|
476
|
+
.digest("hex")
|
|
477
|
+
.slice(0, SESSION_NAME_SESSION_ID_LENGTH);
|
|
478
|
+
return `piab-${slug}-${digest}-${cwdHash}`;
|
|
479
|
+
}
|
|
480
|
+
export function createFreshSessionName(baseSessionName, ephemeralSeed, ordinal) {
|
|
481
|
+
const suffix = createHash("sha256")
|
|
482
|
+
.update(`fresh:${baseSessionName}:${ephemeralSeed}:${ordinal}`)
|
|
483
|
+
.digest("hex")
|
|
484
|
+
.slice(0, 10);
|
|
485
|
+
return `${baseSessionName}-fresh-${suffix}`;
|
|
486
|
+
}
|
|
487
|
+
function getSingleKeyCommandValidationError(args) {
|
|
488
|
+
const { commandInfo, commandTokens } = parseArgvDescriptor(args);
|
|
489
|
+
const command = commandInfo.command;
|
|
490
|
+
if (command !== "press" && command !== "key" && command !== "keydown" && command !== "keyup")
|
|
491
|
+
return undefined;
|
|
492
|
+
if (commandTokens.length === 2)
|
|
493
|
+
return undefined;
|
|
494
|
+
const label = command === "key" ? "key/press" : command;
|
|
495
|
+
return `agent-browser ${label} accepts exactly one key argument. Do not pass a selector or ref to ${label}; focus or click the target first, then run ${command} <key> (for example: focus @e1, then press Enter).`;
|
|
496
|
+
}
|
|
497
|
+
export function validateToolArgs(args) {
|
|
498
|
+
if (args.length === 0) {
|
|
499
|
+
return "`args` must contain at least one agent-browser command token.";
|
|
500
|
+
}
|
|
501
|
+
const shellOperator = args.find((token) => SHELL_OPERATOR_TOKENS.has(token));
|
|
502
|
+
if (shellOperator) {
|
|
503
|
+
return `Do not pass shell operators like \`${shellOperator}\`. Pass exact agent-browser CLI arguments only.`;
|
|
504
|
+
}
|
|
505
|
+
const sessionModeArg = args.find((token) => token === "--session-mode" || token.startsWith("--session-mode="));
|
|
506
|
+
if (sessionModeArg) {
|
|
507
|
+
return "Do not pass `--session-mode` in args. Use the top-level agent_browser `sessionMode` field instead, for example { args: [\"--profile\", \"Default\", \"open\", \"https://example.com\"], sessionMode: \"fresh\" }.";
|
|
508
|
+
}
|
|
509
|
+
return getSingleKeyCommandValidationError(args);
|
|
510
|
+
}
|
|
511
|
+
function getInvalidValueFlagDetails(args) {
|
|
512
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
513
|
+
const token = args[index];
|
|
514
|
+
if (!token.startsWith("-")) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const normalizedToken = token.split("=", 1)[0] ?? token;
|
|
518
|
+
if (!PREVALIDATED_VALUE_FLAGS.has(normalizedToken)) {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (token.includes("=")) {
|
|
522
|
+
const value = token.slice(token.indexOf("=") + 1).trim();
|
|
523
|
+
if (value.length === 0) {
|
|
524
|
+
return {
|
|
525
|
+
flag: normalizedToken,
|
|
526
|
+
index,
|
|
527
|
+
reason: "missing-value",
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
const receivedToken = args[index + 1];
|
|
533
|
+
if (receivedToken === undefined) {
|
|
534
|
+
return {
|
|
535
|
+
flag: normalizedToken,
|
|
536
|
+
index,
|
|
537
|
+
reason: "missing-value",
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
if (receivedToken.startsWith("-") && !GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE.has(normalizedToken)) {
|
|
541
|
+
return {
|
|
542
|
+
flag: normalizedToken,
|
|
543
|
+
index,
|
|
544
|
+
reason: "unexpected-flag",
|
|
545
|
+
receivedToken,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
index += 1;
|
|
549
|
+
}
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
function formatInvalidValueFlagError(details) {
|
|
553
|
+
if (details.reason === "unexpected-flag" && details.receivedToken) {
|
|
554
|
+
return `Flag \`${details.flag}\` requires a value, but received \`${details.receivedToken}\` instead. Pass a non-flag value immediately after \`${details.flag}\`.`;
|
|
555
|
+
}
|
|
556
|
+
return `Flag \`${details.flag}\` requires a value immediately after it. Pass a non-flag token like \`${details.flag} demo\`.`;
|
|
557
|
+
}
|
|
558
|
+
function hasFlagToken(args, flag) {
|
|
559
|
+
return args.some((token) => token === flag || token.startsWith(`${flag}=`));
|
|
560
|
+
}
|
|
561
|
+
function getFlagValue(args, flag) {
|
|
562
|
+
for (const [index, token] of args.entries()) {
|
|
563
|
+
if (token === flag) {
|
|
564
|
+
return args[index + 1];
|
|
565
|
+
}
|
|
566
|
+
if (token.startsWith(`${flag}=`)) {
|
|
567
|
+
return token.slice(flag.length + 1);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return undefined;
|
|
571
|
+
}
|
|
572
|
+
function isBooleanFlagEnabled(args, flag) {
|
|
573
|
+
for (const [index, token] of args.entries()) {
|
|
574
|
+
if (token === flag) {
|
|
575
|
+
const nextToken = args[index + 1]?.trim().toLowerCase();
|
|
576
|
+
if (nextToken === "false") {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
if (token.startsWith(`${flag}=`)) {
|
|
582
|
+
return token.slice(flag.length + 1).trim().toLowerCase() !== "false";
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
function normalizeComparableUrl(url) {
|
|
588
|
+
const normalizedUrl = url.trim();
|
|
589
|
+
if (normalizedUrl.length === 0) {
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
const parsedUrl = new URL(normalizedUrl);
|
|
594
|
+
parsedUrl.hash = "";
|
|
595
|
+
return parsedUrl.toString();
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function normalizeTabSelectionValue(value) {
|
|
602
|
+
const normalizedValue = value?.trim();
|
|
603
|
+
return normalizedValue && normalizedValue.length > 0 ? normalizedValue : undefined;
|
|
604
|
+
}
|
|
605
|
+
function extractTabSelection(tab) {
|
|
606
|
+
const tabId = normalizeTabSelectionValue(tab.tabId);
|
|
607
|
+
if (tabId) {
|
|
608
|
+
return { selectedTab: tabId, selectionKind: "tabId" };
|
|
609
|
+
}
|
|
610
|
+
const label = normalizeTabSelectionValue(tab.label);
|
|
611
|
+
if (label) {
|
|
612
|
+
return { selectedTab: label, selectionKind: "label" };
|
|
613
|
+
}
|
|
614
|
+
if (typeof tab.index === "number" && Number.isInteger(tab.index) && tab.index >= 0) {
|
|
615
|
+
return { selectedTab: String(tab.index), selectionKind: "index" };
|
|
616
|
+
}
|
|
617
|
+
return undefined;
|
|
618
|
+
}
|
|
619
|
+
function parseComparableNavigationUrl(url) {
|
|
620
|
+
try {
|
|
621
|
+
return new URL(url);
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
try {
|
|
625
|
+
return new URL(`https://${url}`);
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function getDefaultHeadlessCompatUserAgent(platform = process.platform) {
|
|
633
|
+
return DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM[platform] ?? FALLBACK_HEADLESS_COMPAT_USER_AGENT;
|
|
634
|
+
}
|
|
635
|
+
function getCompatibilityWorkaround(args, commandInfo) {
|
|
636
|
+
if (!commandInfo.command || !isOpenNavigationCommand(commandInfo.command) || !commandInfo.subcommand) {
|
|
637
|
+
return undefined;
|
|
638
|
+
}
|
|
639
|
+
if (hasFlagToken(args, "--user-agent")) {
|
|
640
|
+
return undefined;
|
|
641
|
+
}
|
|
642
|
+
if (isBooleanFlagEnabled(args, "--headed")) {
|
|
643
|
+
return undefined;
|
|
644
|
+
}
|
|
645
|
+
if (hasFlagToken(args, "--cdp") || hasFlagToken(args, "--provider") || hasFlagToken(args, "-p") || isBooleanFlagEnabled(args, "--auto-connect")) {
|
|
646
|
+
return undefined;
|
|
647
|
+
}
|
|
648
|
+
const engine = getFlagValue(args, "--engine");
|
|
649
|
+
if (engine && engine !== "chrome") {
|
|
650
|
+
return undefined;
|
|
651
|
+
}
|
|
652
|
+
const parsedTargetUrl = parseComparableNavigationUrl(commandInfo.subcommand);
|
|
653
|
+
if (!parsedTargetUrl || !["http:", "https:"].includes(parsedTargetUrl.protocol)) {
|
|
654
|
+
return undefined;
|
|
655
|
+
}
|
|
656
|
+
const hostname = parsedTargetUrl.hostname.toLowerCase();
|
|
657
|
+
if (!OPENAI_HEADLESS_COMPAT_HOSTS.has(hostname)) {
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
id: "chatgpt-headless-user-agent",
|
|
662
|
+
reason: "OpenAI web properties currently challenge the default headless Chrome user agent; inject a normal Chrome user agent to preserve the default headless workflow without requiring headed mode or auto-connect.",
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
export function extractExplicitSessionName(args) {
|
|
666
|
+
for (const [index, token] of args.entries()) {
|
|
667
|
+
if (token === "--session") {
|
|
668
|
+
return args[index + 1];
|
|
669
|
+
}
|
|
670
|
+
if (token.startsWith("--session=")) {
|
|
671
|
+
return token.slice("--session=".length);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return undefined;
|
|
675
|
+
}
|
|
676
|
+
function hasLaunchScopedFlagToken(args, flag) {
|
|
677
|
+
const commandStartIndex = findCommandStartIndex(args);
|
|
678
|
+
const command = commandStartIndex === undefined ? undefined : args[commandStartIndex];
|
|
679
|
+
return args.some((token, index) => {
|
|
680
|
+
if (token !== flag && !token.startsWith(`${flag}=`))
|
|
681
|
+
return false;
|
|
682
|
+
if (flag === "--auto-connect")
|
|
683
|
+
return isBooleanFlagEnabled(args, flag);
|
|
684
|
+
if (flag === "--state" && command === "wait" && commandStartIndex !== undefined && index > commandStartIndex) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
return true;
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
export function getStartupScopedFlags(args) {
|
|
691
|
+
return LAUNCH_SCOPED_FLAG_DEFINITIONS
|
|
692
|
+
.map((definition) => definition.flag)
|
|
693
|
+
.filter((flag) => hasLaunchScopedFlagToken(args, flag));
|
|
694
|
+
}
|
|
695
|
+
export function hasLaunchScopedTabCorrectionFlag(args) {
|
|
696
|
+
return args.some((token) => {
|
|
697
|
+
for (const flag of LAUNCH_SCOPED_TAB_CORRECTION_FLAGS) {
|
|
698
|
+
if (token === flag || token.startsWith(`${flag}=`))
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
return false;
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
export function buildExecutionPlan(args, options) {
|
|
705
|
+
const invalidValueFlag = getInvalidValueFlagDetails(args);
|
|
706
|
+
const startupScopedFlags = getStartupScopedFlags(args);
|
|
707
|
+
const plainTextInspection = isPlainTextInspectionArgs(args);
|
|
708
|
+
const argvDescriptor = parseArgvDescriptor(args);
|
|
709
|
+
const commandTokens = argvDescriptor.commandTokens;
|
|
710
|
+
const commandInfo = argvDescriptor.commandInfo;
|
|
711
|
+
const commandNeedsManagedSession = !plainTextInspection && needsManagedSession(argvDescriptor);
|
|
712
|
+
const effectiveArgs = plainTextInspection ? [...args] : args.includes("--json") ? [] : ["--json"];
|
|
713
|
+
if (invalidValueFlag) {
|
|
714
|
+
return {
|
|
715
|
+
commandInfo: {},
|
|
716
|
+
effectiveArgs,
|
|
717
|
+
invalidValueFlag,
|
|
718
|
+
plainTextInspection: false,
|
|
719
|
+
startupScopedFlags: [],
|
|
720
|
+
usedImplicitSession: false,
|
|
721
|
+
validationError: formatInvalidValueFlagError(invalidValueFlag),
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
if (plainTextInspection) {
|
|
725
|
+
return {
|
|
726
|
+
commandInfo,
|
|
727
|
+
effectiveArgs,
|
|
728
|
+
plainTextInspection,
|
|
729
|
+
startupScopedFlags,
|
|
730
|
+
usedImplicitSession: false,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
const explicitSessionName = extractExplicitSessionName(args);
|
|
734
|
+
const shouldCreateFreshManagedSession = !explicitSessionName && options.sessionMode === "fresh" && commandInfo.command !== undefined && !isCloseCommand(commandInfo.command);
|
|
735
|
+
const compatibilityWorkaround = getCompatibilityWorkaround(args, commandInfo);
|
|
736
|
+
let managedSessionName;
|
|
737
|
+
let recoveryHint;
|
|
738
|
+
let sessionName = explicitSessionName;
|
|
739
|
+
let usedImplicitSession = false;
|
|
740
|
+
let validationError;
|
|
741
|
+
if (!explicitSessionName && options.sessionMode === "auto" && commandNeedsManagedSession) {
|
|
742
|
+
if (options.managedSessionActive && startupScopedFlags.length > 0) {
|
|
743
|
+
recoveryHint = {
|
|
744
|
+
exampleArgs: args,
|
|
745
|
+
exampleParams: { args, sessionMode: "fresh" },
|
|
746
|
+
reason: `Launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}) need a fresh upstream launch once the extension-managed session is already active.`,
|
|
747
|
+
recommendedSessionMode: "fresh",
|
|
748
|
+
};
|
|
749
|
+
validationError = [
|
|
750
|
+
`The current extension-managed agent-browser session is already running, so launch-scoped flags ${startupScopedFlags.join(", ")} would be ignored by upstream agent-browser.`,
|
|
751
|
+
"Retry this call with `sessionMode: \"fresh\"` to force a fresh upstream launch, or pass an explicit `--session ...` if you want to name the new session yourself.",
|
|
752
|
+
].join(" ");
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
effectiveArgs.push("--session", options.managedSessionName);
|
|
756
|
+
managedSessionName = options.managedSessionName;
|
|
757
|
+
sessionName = options.managedSessionName;
|
|
758
|
+
usedImplicitSession = true;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
else if (shouldCreateFreshManagedSession && commandNeedsManagedSession) {
|
|
762
|
+
effectiveArgs.push("--session", options.freshSessionName);
|
|
763
|
+
managedSessionName = options.freshSessionName;
|
|
764
|
+
sessionName = options.freshSessionName;
|
|
765
|
+
}
|
|
766
|
+
if (compatibilityWorkaround) {
|
|
767
|
+
effectiveArgs.push("--user-agent", getDefaultHeadlessCompatUserAgent());
|
|
768
|
+
}
|
|
769
|
+
effectiveArgs.push(...args);
|
|
770
|
+
return {
|
|
771
|
+
commandInfo,
|
|
772
|
+
compatibilityWorkaround,
|
|
773
|
+
effectiveArgs,
|
|
774
|
+
managedSessionName,
|
|
775
|
+
plainTextInspection,
|
|
776
|
+
recoveryHint,
|
|
777
|
+
sessionName,
|
|
778
|
+
startupScopedFlags,
|
|
779
|
+
usedImplicitSession,
|
|
780
|
+
validationError,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
export function chooseOpenResultTabCorrection(options) {
|
|
784
|
+
const normalizedTargetUrl = typeof options.targetUrl === "string" ? normalizeComparableUrl(options.targetUrl) : undefined;
|
|
785
|
+
if (!normalizedTargetUrl) {
|
|
786
|
+
return undefined;
|
|
787
|
+
}
|
|
788
|
+
const tabsWithIndices = options.tabs.map((tab, index) => ({
|
|
789
|
+
...tab,
|
|
790
|
+
index: typeof tab.index === "number" ? tab.index : index,
|
|
791
|
+
label: normalizeTabSelectionValue(tab.label),
|
|
792
|
+
tabId: normalizeTabSelectionValue(tab.tabId),
|
|
793
|
+
}));
|
|
794
|
+
const activeTab = tabsWithIndices.find((tab) => tab.active === true) ??
|
|
795
|
+
(typeof options.activeTabIndex === "number" ? tabsWithIndices.find((tab) => tab.index === options.activeTabIndex) : undefined);
|
|
796
|
+
if (activeTab && normalizeComparableUrl(activeTab.url ?? "") === normalizedTargetUrl) {
|
|
797
|
+
return undefined;
|
|
798
|
+
}
|
|
799
|
+
const matchingTabs = tabsWithIndices.filter((tab) => normalizeComparableUrl(tab.url ?? "") === normalizedTargetUrl);
|
|
800
|
+
if (matchingTabs.length === 0) {
|
|
801
|
+
return undefined;
|
|
802
|
+
}
|
|
803
|
+
const trimmedTargetTitle = typeof options.targetTitle === "string" ? options.targetTitle.trim() : "";
|
|
804
|
+
const titledMatch = trimmedTargetTitle.length === 0
|
|
805
|
+
? undefined
|
|
806
|
+
: matchingTabs.find((tab) => typeof tab.title === "string" && tab.title.trim() === trimmedTargetTitle);
|
|
807
|
+
const selectedTab = titledMatch ?? matchingTabs[0];
|
|
808
|
+
const tabSelection = extractTabSelection(selectedTab);
|
|
809
|
+
return tabSelection
|
|
810
|
+
? {
|
|
811
|
+
...tabSelection,
|
|
812
|
+
targetTitle: trimmedTargetTitle.length > 0 ? trimmedTargetTitle : undefined,
|
|
813
|
+
targetUrl: normalizedTargetUrl,
|
|
814
|
+
}
|
|
815
|
+
: undefined;
|
|
816
|
+
}
|