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.
- package/CHANGELOG.md +23 -0
- package/README.md +71 -18
- package/docs/ARCHITECTURE.md +8 -8
- package/docs/COMMAND_REFERENCE.md +54 -18
- package/docs/RELEASE.md +1 -1
- package/docs/SUPPORT_MATRIX.md +13 -11
- package/docs/TOOL_CONTRACT.md +39 -13
- package/extensions/agent-browser/index.ts +3 -2
- package/extensions/agent-browser/lib/config-policy.js +679 -0
- package/extensions/agent-browser/lib/config.ts +103 -351
- package/extensions/agent-browser/lib/input-modes/params.ts +1 -1
- package/extensions/agent-browser/lib/launch-scoped-flags.ts +67 -0
- package/extensions/agent-browser/lib/playbook.ts +28 -17
- package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +67 -0
- package/extensions/agent-browser/lib/results/presentation/errors.ts +4 -0
- package/extensions/agent-browser/lib/runtime.ts +6 -73
- package/extensions/agent-browser/lib/web-search.ts +403 -52
- package/package.json +1 -1
- package/scripts/config.mjs +183 -99
|
@@ -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
|
|
4
|
-
* Scope: Package-owned configuration only; browser command execution and web-search API calls live in focused modules.
|
|
5
|
-
*
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
125
|
+
mergedConfig = mergeAgentBrowserConfig(mergedConfig, layer.config);
|
|
317
126
|
}
|
|
318
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
|
172
|
+
return resolveCredentialSource(getWebSearchCredentialSource(state, provider), options);
|
|
422
173
|
}
|
|
423
174
|
|
|
424
|
-
export function
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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);
|