pi-agent-browser-native 0.2.48 → 0.2.50

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