pi-agent-browser-native 0.2.40 → 0.2.41

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.
@@ -1,217 +1,97 @@
1
1
  /**
2
2
  * Purpose: Load pi-agent-browser-native package configuration from Pi-scoped global, project, or explicit paths.
3
- * Responsibilities: Resolve config paths, validate the v1 JSON shape, merge layers, classify credential sources, resolve secrets without exposing values, and provide redacted status for tools/CLIs.
4
- * Scope: Package-owned configuration only; browser command execution and web-search API calls live in focused modules.
5
- * Usage: The extension reads this at startup to decide optional tool registration; scripts/config.mjs mirrors the file locations for user setup.
6
- * Invariants/Assumptions: Raw project-local plaintext credentials are unsafe and rejected; command credentials are resolved lazily at execution time.
3
+ * Responsibilities: Resolve config layers, resolve secrets without exposing values, and provide redacted status for tools/CLIs.
4
+ * Scope: Package-owned configuration only; canonical config policy lives in config-policy.js, browser command execution and web-search API calls live in focused modules.
5
+ * Invariants/Assumptions: Raw project-local plaintext credentials are unsafe and rejected by the shared config policy; command credentials are resolved lazily at execution time.
7
6
  */
8
7
 
9
8
  import { exec as execCallback } from "node:child_process";
10
- import { readFileSync } from "node:fs";
11
9
  import { readFile } from "node:fs/promises";
12
- import { homedir } from "node:os";
13
- import { join, resolve } from "node:path";
14
10
  import { promisify } from "node:util";
15
11
 
16
- const exec = promisify(execCallback);
17
-
18
- export const AGENT_BROWSER_CONFIG_ENV = "PI_AGENT_BROWSER_CONFIG";
19
- export const BRAVE_API_KEY_ENV = "BRAVE_API_KEY";
20
- export const CONFIG_RELATIVE_PATH = [".pi", "config", "pi-agent-browser-native", "config.json"] as const;
21
- export const GLOBAL_CONFIG_RELATIVE_PATH = [".pi", "config", "pi-agent-browser-native", "config.json"] as const;
22
- export const SECRET_COMMAND_TIMEOUT_MS = 15_000;
23
-
24
- export type BrowserDefaultProfilePolicy = "explicit-only" | "authenticated-only" | "always";
25
- export type AgentBrowserConfigScope = "global" | "project" | "override" | "env-fallback";
26
- export type CredentialSourceKind = "literal" | "env" | "command";
27
-
28
- export interface BrowserDefaultProfileConfig {
29
- name: string;
30
- policy?: BrowserDefaultProfilePolicy;
31
- }
32
-
33
- export interface AgentBrowserConfig {
34
- version?: 1;
35
- webSearch?: {
36
- braveApiKey?: string;
37
- };
38
- browser?: {
39
- defaultProfile?: BrowserDefaultProfileConfig;
40
- defaultLaunchArgs?: string[];
41
- };
42
- }
12
+ import {
13
+ SECRET_COMMAND_TIMEOUT_MS,
14
+ buildAgentBrowserConfigState,
15
+ getAgentBrowserConfigPaths,
16
+ getWebSearchCredentialSource,
17
+ getWebSearchProviderOrder,
18
+ loadAgentBrowserConfigStateSync,
19
+ mergeAgentBrowserConfig,
20
+ parseAgentBrowserConfigLayer,
21
+ resolveEnvInterpolations,
22
+ } from "./config-policy.js";
23
+ import type {
24
+ AgentBrowserConfig,
25
+ AgentBrowserConfigScope,
26
+ AgentBrowserConfigState,
27
+ BrowserDefaultProfileConfig,
28
+ BrowserDefaultProfilePolicy,
29
+ ConfigLayer,
30
+ CredentialSource,
31
+ CredentialSourceKind,
32
+ WebSearchProvider,
33
+ } from "./config-policy.js";
34
+
35
+ export {
36
+ AGENT_BROWSER_CONFIG_ENV,
37
+ BRAVE_API_KEY_ENV,
38
+ CONFIG_RELATIVE_PATH,
39
+ DEFAULT_WEB_SEARCH_PROVIDER,
40
+ EXA_API_KEY_ENV,
41
+ GLOBAL_CONFIG_RELATIVE_PATH,
42
+ SECRET_COMMAND_TIMEOUT_MS,
43
+ WEB_SEARCH_PROVIDER_CONFIG_KEYS,
44
+ WEB_SEARCH_PROVIDER_DESCRIPTORS,
45
+ WEB_SEARCH_PROVIDER_ENV_VARS,
46
+ WEB_SEARCH_PROVIDERS,
47
+ buildAgentBrowserConfigState,
48
+ buildWebSearchCredentialSources,
49
+ canRegisterWebSearchTool,
50
+ classifyCredentialSource,
51
+ formatBrowserExecutableStatus,
52
+ formatBrowserProfileStatus,
53
+ getAgentBrowserConfigPaths,
54
+ getCredentialSourceSummary,
55
+ getGlobalAgentBrowserConfigPath,
56
+ getProjectAgentBrowserConfigPath,
57
+ getWebSearchCredentialSource,
58
+ getWebSearchProviderConfigKey,
59
+ getWebSearchProviderDescriptor,
60
+ getWebSearchProviderEnvVar,
61
+ getWebSearchProviderLabel,
62
+ getWebSearchProviderOrder,
63
+ hasPotentialCredentialSource,
64
+ isPlaintextCredentialValue,
65
+ isProjectSafeCredentialValueForProvider,
66
+ isWebSearchProvider,
67
+ loadAgentBrowserConfigStateSync,
68
+ mergeAgentBrowserConfig,
69
+ parseAgentBrowserConfigLayer,
70
+ resolveEnvInterpolations,
71
+ summarizeConfigFiles,
72
+ validateAgentBrowserConfig,
73
+ validateWebSearchProvider,
74
+ } from "./config-policy.js";
75
+ export type {
76
+ AgentBrowserConfig,
77
+ AgentBrowserConfigScope,
78
+ AgentBrowserConfigState,
79
+ BrowserDefaultProfileConfig,
80
+ BrowserDefaultProfilePolicy,
81
+ ConfigLayer,
82
+ CredentialSource,
83
+ CredentialSourceKind,
84
+ WebSearchProvider,
85
+ WebSearchProviderDescriptor,
86
+ } from "./config-policy.js";
43
87
 
44
- export interface ConfigLayer {
45
- config: AgentBrowserConfig;
46
- path: string;
47
- scope: Exclude<AgentBrowserConfigScope, "env-fallback">;
48
- }
49
-
50
- export interface CredentialSource {
51
- kind: CredentialSourceKind;
52
- rawValue: string;
53
- scope: AgentBrowserConfigScope;
54
- }
55
-
56
- export interface AgentBrowserConfigState {
57
- browserDefaultProfile?: Required<BrowserDefaultProfileConfig>;
58
- config: AgentBrowserConfig;
59
- credentialSource?: CredentialSource;
60
- errors: string[];
61
- layers: ConfigLayer[];
62
- paths: {
63
- global: string;
64
- project: string;
65
- override?: string;
66
- };
67
- warnings: string[];
68
- }
88
+ const exec = promisify(execCallback);
69
89
 
70
90
  export interface ResolvedCredential {
71
91
  source: CredentialSource;
72
92
  value: string;
73
93
  }
74
94
 
75
- function isRecord(value: unknown): value is Record<string, unknown> {
76
- return typeof value === "object" && value !== null && !Array.isArray(value);
77
- }
78
-
79
- function hasOwn(value: Record<string, unknown>, key: string): boolean {
80
- return Object.prototype.hasOwnProperty.call(value, key);
81
- }
82
-
83
- export function getGlobalAgentBrowserConfigPath(env: NodeJS.ProcessEnv = process.env): string {
84
- const home = env.HOME?.trim() || env.USERPROFILE?.trim() || homedir();
85
- return join(home, ...GLOBAL_CONFIG_RELATIVE_PATH);
86
- }
87
-
88
- export function getProjectAgentBrowserConfigPath(cwd = process.cwd()): string {
89
- return resolve(cwd, ...CONFIG_RELATIVE_PATH);
90
- }
91
-
92
- export function getAgentBrowserConfigPaths(options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}) {
93
- const env = options.env ?? process.env;
94
- const override = env[AGENT_BROWSER_CONFIG_ENV]?.trim();
95
- return {
96
- global: getGlobalAgentBrowserConfigPath(env),
97
- project: getProjectAgentBrowserConfigPath(options.cwd),
98
- override: override ? resolve(override) : undefined,
99
- };
100
- }
101
-
102
- function mergeConfig(base: AgentBrowserConfig, override: AgentBrowserConfig): AgentBrowserConfig {
103
- return {
104
- ...base,
105
- ...override,
106
- browser: {
107
- ...(base.browser ?? {}),
108
- ...(override.browser ?? {}),
109
- defaultProfile: override.browser?.defaultProfile ?? base.browser?.defaultProfile,
110
- },
111
- webSearch: {
112
- ...(base.webSearch ?? {}),
113
- ...(override.webSearch ?? {}),
114
- },
115
- };
116
- }
117
-
118
- function validateString(value: unknown, path: string, errors: string[]): string | undefined {
119
- if (value === undefined) return undefined;
120
- if (typeof value !== "string") {
121
- errors.push(`${path} must be a string.`);
122
- return undefined;
123
- }
124
- return value;
125
- }
126
-
127
- function validateStringArray(value: unknown, path: string, errors: string[]): string[] | undefined {
128
- if (value === undefined) return undefined;
129
- if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
130
- errors.push(`${path} must be an array of strings.`);
131
- return undefined;
132
- }
133
- return value;
134
- }
135
-
136
- function validateBrowserDefaultProfile(value: unknown, path: string, errors: string[]): BrowserDefaultProfileConfig | undefined {
137
- if (value === undefined) return undefined;
138
- if (!isRecord(value)) {
139
- errors.push(`${path} must be an object.`);
140
- return undefined;
141
- }
142
- const name = validateString(value.name, `${path}.name`, errors)?.trim();
143
- if (!name) {
144
- errors.push(`${path}.name must not be blank.`);
145
- return undefined;
146
- }
147
- const rawPolicy = validateString(value.policy, `${path}.policy`, errors);
148
- const policy = rawPolicy ?? "authenticated-only";
149
- if (!(["explicit-only", "authenticated-only", "always"] as const).includes(policy as BrowserDefaultProfilePolicy)) {
150
- errors.push(`${path}.policy must be one of explicit-only, authenticated-only, always.`);
151
- return undefined;
152
- }
153
- return { name, policy: policy as BrowserDefaultProfilePolicy };
154
- }
155
-
156
- function validateConfig(value: unknown, path: string, scope: ConfigLayer["scope"], errors: string[], warnings: string[]): AgentBrowserConfig | undefined {
157
- if (!isRecord(value)) {
158
- errors.push(`${path} must contain a JSON object.`);
159
- return undefined;
160
- }
161
- if (value.version !== undefined && value.version !== 1) {
162
- errors.push(`${path}.version must be 1 when present.`);
163
- }
164
- const config: AgentBrowserConfig = value.version === 1 ? { version: 1 } : {};
165
-
166
- if (value.webSearch !== undefined) {
167
- if (!isRecord(value.webSearch)) {
168
- errors.push(`${path}.webSearch must be an object.`);
169
- } else {
170
- const braveApiKey = validateString(value.webSearch.braveApiKey, `${path}.webSearch.braveApiKey`, errors);
171
- if (braveApiKey !== undefined) {
172
- config.webSearch = { braveApiKey };
173
- if (scope === "project" && !isProjectSafeCredentialValue(braveApiKey)) {
174
- errors.push(`${path}.webSearch.braveApiKey must be exactly $ENV_VAR or ${"${ENV_VAR}"} in project-local config; plaintext, interpolation literals, malformed env references, and command-backed project secrets are not allowed.`);
175
- }
176
- }
177
- }
178
- }
179
-
180
- if (value.browser !== undefined) {
181
- if (!isRecord(value.browser)) {
182
- errors.push(`${path}.browser must be an object.`);
183
- } else {
184
- config.browser = {};
185
- const defaultProfile = validateBrowserDefaultProfile(value.browser.defaultProfile, `${path}.browser.defaultProfile`, errors);
186
- if (defaultProfile) config.browser.defaultProfile = defaultProfile;
187
- const defaultLaunchArgs = validateStringArray(value.browser.defaultLaunchArgs, `${path}.browser.defaultLaunchArgs`, errors);
188
- if (defaultLaunchArgs) {
189
- config.browser.defaultLaunchArgs = defaultLaunchArgs;
190
- warnings.push(`${path}.browser.defaultLaunchArgs is recorded for future use; current releases do not auto-inject default launch args.`);
191
- }
192
- }
193
- }
194
-
195
- for (const key of Object.keys(value)) {
196
- if (!["version", "webSearch", "browser"].includes(key)) {
197
- warnings.push(`${path}.${key} is not a recognized pi-agent-browser-native config field and was ignored.`);
198
- }
199
- }
200
- return config;
201
- }
202
-
203
- function parseConfigLayer(raw: string, path: string, scope: ConfigLayer["scope"], errors: string[], warnings: string[]): ConfigLayer | undefined {
204
- let parsed: unknown;
205
- try {
206
- parsed = JSON.parse(raw);
207
- } catch (error) {
208
- errors.push(`Could not parse ${scope} config ${path}: ${error instanceof Error ? error.message : String(error)}`);
209
- return undefined;
210
- }
211
- const config = validateConfig(parsed, path, scope, errors, warnings);
212
- return config ? { config, path, scope } : undefined;
213
- }
214
-
215
95
  async function readConfigLayer(path: string, scope: ConfigLayer["scope"], errors: string[], warnings: string[]): Promise<ConfigLayer | undefined> {
216
96
  let raw: string;
217
97
  try {
@@ -223,78 +103,7 @@ async function readConfigLayer(path: string, scope: ConfigLayer["scope"], errors
223
103
  errors.push(`Could not read ${scope} config ${path}: ${error instanceof Error ? error.message : String(error)}`);
224
104
  return undefined;
225
105
  }
226
- return parseConfigLayer(raw, path, scope, errors, warnings);
227
- }
228
-
229
- function readConfigLayerSync(path: string, scope: ConfigLayer["scope"], errors: string[], warnings: string[]): ConfigLayer | undefined {
230
- let raw: string;
231
- try {
232
- raw = readFileSync(path, "utf8");
233
- } catch (error) {
234
- if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
235
- return undefined;
236
- }
237
- errors.push(`Could not read ${scope} config ${path}: ${error instanceof Error ? error.message : String(error)}`);
238
- return undefined;
239
- }
240
- return parseConfigLayer(raw, path, scope, errors, warnings);
241
- }
242
-
243
- export function isPlaintextCredentialValue(rawValue: string): boolean {
244
- const trimmed = rawValue.trim();
245
- return Boolean(trimmed) && !trimmed.startsWith("!") && !trimmed.startsWith("$");
246
- }
247
-
248
- export function isProjectSafeCredentialValue(rawValue: string): boolean {
249
- const trimmed = rawValue.trim();
250
- return /^\$[A-Za-z_][A-Za-z0-9_]*$/.test(trimmed) || /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(trimmed);
251
- }
252
-
253
- export function classifyCredentialSource(rawValue: string, scope: AgentBrowserConfigScope): CredentialSource | undefined {
254
- const trimmed = rawValue.trim();
255
- if (!trimmed) return undefined;
256
- if (trimmed.startsWith("!")) return { kind: "command", rawValue: trimmed, scope };
257
- if (trimmed.includes("$")) return { kind: "env", rawValue: trimmed, scope };
258
- return { kind: "literal", rawValue: trimmed, scope };
259
- }
260
-
261
- function getBrowserDefaultProfile(config: AgentBrowserConfig): Required<BrowserDefaultProfileConfig> | undefined {
262
- const profile = config.browser?.defaultProfile;
263
- if (!profile?.name.trim()) return undefined;
264
- return { name: profile.name.trim(), policy: profile.policy ?? "authenticated-only" };
265
- }
266
-
267
- function buildConfigState(options: {
268
- env: NodeJS.ProcessEnv;
269
- layers: ConfigLayer[];
270
- mergedConfig: AgentBrowserConfig;
271
- paths: AgentBrowserConfigState["paths"];
272
- errors: string[];
273
- warnings: string[];
274
- }): AgentBrowserConfigState {
275
- let credentialScope: AgentBrowserConfigScope = "global";
276
- for (let index = options.layers.length - 1; index >= 0; index -= 1) {
277
- const layer = options.layers[index];
278
- if (layer?.config.webSearch?.braveApiKey !== undefined) {
279
- credentialScope = layer.scope;
280
- break;
281
- }
282
- }
283
- let credentialSource = options.mergedConfig.webSearch?.braveApiKey === undefined
284
- ? undefined
285
- : classifyCredentialSource(options.mergedConfig.webSearch.braveApiKey, credentialScope);
286
- if (!credentialSource && options.env[BRAVE_API_KEY_ENV]?.trim()) {
287
- credentialSource = { kind: "literal", rawValue: options.env[BRAVE_API_KEY_ENV] ?? "", scope: "env-fallback" };
288
- }
289
- return {
290
- browserDefaultProfile: getBrowserDefaultProfile(options.mergedConfig),
291
- config: options.mergedConfig,
292
- credentialSource,
293
- errors: options.errors,
294
- layers: options.layers,
295
- paths: options.paths,
296
- warnings: options.warnings,
297
- };
106
+ return parseAgentBrowserConfigLayer(raw, path, scope, errors, warnings);
298
107
  }
299
108
 
300
109
  export async function loadAgentBrowserConfig(options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}): Promise<AgentBrowserConfigState> {
@@ -313,72 +122,13 @@ export async function loadAgentBrowserConfig(options: { cwd?: string; env?: Node
313
122
  const layer = await readConfigLayer(candidate.path, candidate.scope, errors, warnings);
314
123
  if (!layer) continue;
315
124
  layers.push(layer);
316
- mergedConfig = mergeConfig(mergedConfig, layer.config);
125
+ mergedConfig = mergeAgentBrowserConfig(mergedConfig, layer.config);
317
126
  }
318
- return buildConfigState({ env, errors, layers, mergedConfig, paths, warnings });
127
+ return buildAgentBrowserConfigState({ env, errors, layers, mergedConfig, paths, warnings });
319
128
  }
320
129
 
321
130
  export function loadAgentBrowserConfigSync(options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}): AgentBrowserConfigState {
322
- const env = options.env ?? process.env;
323
- const paths = getAgentBrowserConfigPaths({ cwd: options.cwd, env });
324
- const errors: string[] = [];
325
- const warnings: string[] = [];
326
- const layerCandidates = [
327
- { path: paths.global, scope: "global" as const },
328
- { path: paths.project, scope: "project" as const },
329
- ...(paths.override ? [{ path: paths.override, scope: "override" as const }] : []),
330
- ];
331
- const layers: ConfigLayer[] = [];
332
- let mergedConfig: AgentBrowserConfig = {};
333
- for (const candidate of layerCandidates) {
334
- const layer = readConfigLayerSync(candidate.path, candidate.scope, errors, warnings);
335
- if (!layer) continue;
336
- layers.push(layer);
337
- mergedConfig = mergeConfig(mergedConfig, layer.config);
338
- }
339
- return buildConfigState({ env, errors, layers, mergedConfig, paths, warnings });
340
- }
341
-
342
- function resolveEnvInterpolations(rawValue: string, env: NodeJS.ProcessEnv): string | undefined {
343
- let output = "";
344
- for (let index = 0; index < rawValue.length; index += 1) {
345
- const char = rawValue[index];
346
- if (char !== "$") {
347
- output += char;
348
- continue;
349
- }
350
- const next = rawValue[index + 1];
351
- if (next === "$") {
352
- output += "$";
353
- index += 1;
354
- continue;
355
- }
356
- if (next === "!") {
357
- output += "!";
358
- index += 1;
359
- continue;
360
- }
361
- let name = "";
362
- if (next === "{") {
363
- const end = rawValue.indexOf("}", index + 2);
364
- if (end === -1) return undefined;
365
- name = rawValue.slice(index + 2, end);
366
- index = end;
367
- } else {
368
- const match = rawValue.slice(index + 1).match(/^([A-Za-z_][A-Za-z0-9_]*)/);
369
- if (!match) {
370
- output += "$";
371
- continue;
372
- }
373
- name = match[1] ?? "";
374
- index += name.length;
375
- }
376
- if (!name) return undefined;
377
- const value = env[name];
378
- if (value === undefined) return undefined;
379
- output += value;
380
- }
381
- return output;
131
+ return loadAgentBrowserConfigStateSync(options);
382
132
  }
383
133
 
384
134
  async function resolveCommandCredential(rawValue: string, signal?: AbortSignal): Promise<string | undefined> {
@@ -414,33 +164,35 @@ export async function resolveCredentialSource(
414
164
  return value ? { source, value } : undefined;
415
165
  }
416
166
 
417
- export async function resolveBraveApiKey(
167
+ export async function resolveWebSearchCredential(
418
168
  state: AgentBrowserConfigState,
169
+ provider: WebSearchProvider,
419
170
  options: { env?: NodeJS.ProcessEnv; signal?: AbortSignal } = {},
420
171
  ): Promise<ResolvedCredential | undefined> {
421
- return resolveCredentialSource(state.credentialSource, options);
172
+ return resolveCredentialSource(getWebSearchCredentialSource(state, provider), options);
422
173
  }
423
174
 
424
- export function canRegisterWebSearchTool(state: AgentBrowserConfigState, env: NodeJS.ProcessEnv = process.env): boolean {
425
- if (!state.credentialSource || state.errors.length > 0) return false;
426
- if (state.credentialSource.kind === "command") return true;
427
- if (state.credentialSource.kind === "env") return Boolean(resolveEnvInterpolations(state.credentialSource.rawValue, env)?.trim());
428
- return Boolean(state.credentialSource.rawValue.trim());
175
+ export async function resolvePreferredWebSearchCredential(
176
+ state: AgentBrowserConfigState,
177
+ options: { env?: NodeJS.ProcessEnv; provider?: WebSearchProvider | "auto"; signal?: AbortSignal } = {},
178
+ ): Promise<{ provider: WebSearchProvider; credential: ResolvedCredential } | undefined> {
179
+ for (const provider of getWebSearchProviderOrder(state, options.provider)) {
180
+ const credential = await resolveWebSearchCredential(state, provider, options);
181
+ if (credential) return { provider, credential };
182
+ }
183
+ return undefined;
429
184
  }
430
185
 
431
186
  export async function hasResolvableCredentialSource(
432
187
  state: AgentBrowserConfigState,
433
188
  options: { env?: NodeJS.ProcessEnv } = {},
434
189
  ): Promise<boolean> {
435
- if (!state.credentialSource || state.errors.length > 0) return false;
436
- if (state.credentialSource.kind === "command") return true;
437
- return Boolean((await resolveCredentialSource(state.credentialSource, options))?.value);
438
- }
439
-
440
- export function getCredentialSourceSummary(source: CredentialSource | undefined): string {
441
- if (!source) return "not configured";
442
- if (source.kind === "command") return `configured via command (${source.scope})`;
443
- if (source.kind === "env") return `configured via environment interpolation (${source.scope})`;
444
- if (source.scope === "env-fallback") return `configured via ${BRAVE_API_KEY_ENV} environment fallback`;
445
- return `configured as plaintext ${source.scope} value [redacted]`;
190
+ if (!state.webSearchEnabled || state.errors.length > 0) return false;
191
+ for (const provider of getWebSearchProviderOrder(state)) {
192
+ const source = getWebSearchCredentialSource(state, provider);
193
+ if (!source) continue;
194
+ if (source.kind === "command") return true;
195
+ if ((await resolveCredentialSource(source, options))?.value) return true;
196
+ }
197
+ return false;
446
198
  }
@@ -181,7 +181,7 @@ export const AGENT_BROWSER_PARAMS = Type.Object({
181
181
  sessionMode: Type.Optional(
182
182
  StringEnum(["auto", "fresh"] as const, {
183
183
  description:
184
- "Session handling mode. `auto` reuses the extension-managed pi-scoped session when possible. `fresh` switches that managed session to a fresh upstream launch so launch-scoped flags like --profile, --session-name, --cdp, --state, --auto-connect, --init-script, --enable, -p/--provider, or iOS --device apply and later auto calls follow the new browser.",
184
+ "Session handling mode. `auto` reuses the extension-managed pi-scoped session when possible. `fresh` switches that managed session to a fresh upstream launch so launch-scoped flags like --profile, --executable-path, --session-name, --cdp, --state, --auto-connect, --init-script, --enable, -p/--provider, or iOS --device apply and later auto calls follow the new browser.",
185
185
  default: DEFAULT_SESSION_MODE,
186
186
  }),
187
187
  ),
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Purpose: Canonical launch-scoped agent-browser flag metadata shared by runtime planning and agent-facing guidance.
3
+ * Responsibilities: Define which upstream flags require a fresh launch, explain why, and expose stable guidance labels.
4
+ * Scope: Metadata only; argv parsing and execution planning live in runtime.ts.
5
+ */
6
+
7
+ export interface LaunchScopedFlagDefinition {
8
+ flag: string;
9
+ reason: string;
10
+ }
11
+
12
+ export const LAUNCH_SCOPED_FLAG_DEFINITIONS = [
13
+ {
14
+ flag: "--auto-connect",
15
+ reason: "attaches to an already-running browser at launch time instead of reusing an existing named session",
16
+ },
17
+ {
18
+ flag: "--cdp",
19
+ reason: "selects the browser/CDP endpoint used when an upstream session is launched",
20
+ },
21
+ {
22
+ flag: "--enable",
23
+ reason: "selects built-in page init scripts before the upstream browser session is launched",
24
+ },
25
+ {
26
+ flag: "--executable-path",
27
+ reason: "selects the browser executable used for the upstream launch",
28
+ },
29
+ {
30
+ flag: "--init-script",
31
+ reason: "registers page init scripts before the upstream browser session is launched",
32
+ },
33
+ {
34
+ flag: "--device",
35
+ reason: "selects the provider device for the upstream launch",
36
+ },
37
+ {
38
+ flag: "--profile",
39
+ reason: "selects Chrome profile state for the upstream launch",
40
+ },
41
+ {
42
+ flag: "--provider",
43
+ reason: "selects the upstream browser provider for the launch",
44
+ },
45
+ {
46
+ flag: "-p",
47
+ reason: "selects the upstream browser provider for the launch",
48
+ },
49
+ {
50
+ flag: "--session-name",
51
+ reason: "selects upstream saved auth/session state for the launch",
52
+ },
53
+ {
54
+ flag: "--state",
55
+ reason: "loads persisted upstream browser/auth state at launch time",
56
+ },
57
+ ] as const satisfies readonly LaunchScopedFlagDefinition[];
58
+
59
+ export const LAUNCH_SCOPED_FLAGS = LAUNCH_SCOPED_FLAG_DEFINITIONS.map((definition) => definition.flag);
60
+ export const LAUNCH_SCOPED_FLAG_LABEL = LAUNCH_SCOPED_FLAGS.join(", ");
61
+
62
+ /**
63
+ * The subset of launch-scoped flags that can restore browser/auth state with pre-existing tabs
64
+ * and are plausible wrong-active-tab sources after a fresh launch. These trigger post-open
65
+ * tab-correction (the `tab list` + re-select cycle).
66
+ */
67
+ export const LAUNCH_SCOPED_TAB_CORRECTION_FLAGS = new Set(["--profile", "--session-name", "--state"] as const);