browser-use 0.6.0 → 0.7.0
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/README.md +29 -18
- package/dist/actor/element.js +24 -3
- package/dist/actor/mouse.js +21 -3
- package/dist/actor/page.js +33 -11
- package/dist/agent/gif.js +28 -3
- package/dist/agent/message-manager/service.js +2 -22
- package/dist/agent/message-manager/utils.js +15 -2
- package/dist/agent/message-manager/views.d.ts +7 -7
- package/dist/agent/message-manager/views.js +1 -0
- package/dist/agent/prompts.d.ts +3 -0
- package/dist/agent/prompts.js +22 -12
- package/dist/agent/service.d.ts +9 -1
- package/dist/agent/service.js +215 -81
- package/dist/agent/system_prompt.md +12 -11
- package/dist/agent/system_prompt_anthropic_flash.md +6 -5
- package/dist/agent/system_prompt_no_thinking.md +12 -11
- package/dist/agent/views.d.ts +2 -0
- package/dist/agent/views.js +48 -36
- package/dist/browser/extensions.js +20 -10
- package/dist/browser/profile.d.ts +4 -0
- package/dist/browser/profile.js +107 -4
- package/dist/browser/session.d.ts +28 -1
- package/dist/browser/session.js +1436 -528
- package/dist/browser/watchdogs/default-action-watchdog.js +32 -3
- package/dist/browser/watchdogs/downloads-watchdog.d.ts +4 -0
- package/dist/browser/watchdogs/downloads-watchdog.js +105 -9
- package/dist/browser/watchdogs/har-recording-watchdog.d.ts +1 -0
- package/dist/browser/watchdogs/har-recording-watchdog.js +54 -2
- package/dist/browser/watchdogs/permissions-watchdog.d.ts +5 -0
- package/dist/browser/watchdogs/permissions-watchdog.js +106 -3
- package/dist/browser/watchdogs/recording-watchdog.d.ts +2 -0
- package/dist/browser/watchdogs/recording-watchdog.js +54 -2
- package/dist/browser/watchdogs/security-watchdog.d.ts +1 -0
- package/dist/browser/watchdogs/security-watchdog.js +47 -7
- package/dist/browser/watchdogs/storage-state-watchdog.d.ts +6 -0
- package/dist/browser/watchdogs/storage-state-watchdog.js +206 -14
- package/dist/cli.d.ts +13 -2
- package/dist/cli.js +188 -8
- package/dist/code-use/namespace.js +52 -7
- package/dist/code-use/notebook-export.js +18 -2
- package/dist/code-use/service.js +1 -0
- package/dist/config.js +27 -5
- package/dist/controller/action-timeout.d.ts +9 -0
- package/dist/controller/action-timeout.js +95 -0
- package/dist/controller/registry/service.d.ts +1 -0
- package/dist/controller/registry/service.js +28 -1
- package/dist/controller/registry/views.d.ts +2 -0
- package/dist/controller/registry/views.js +44 -17
- package/dist/controller/service.d.ts +2 -1
- package/dist/controller/service.js +494 -329
- package/dist/filesystem/file-system.js +38 -8
- package/dist/integrations/gmail/service.js +30 -6
- package/dist/llm/browser-use/chat.js +2 -2
- package/dist/llm/codex/auth.d.ts +118 -0
- package/dist/llm/codex/auth.js +599 -0
- package/dist/llm/codex/chat.d.ts +70 -0
- package/dist/llm/codex/chat.js +392 -0
- package/dist/llm/codex/index.d.ts +2 -0
- package/dist/llm/codex/index.js +2 -0
- package/dist/llm/google/chat.js +18 -1
- package/dist/logging-config.js +22 -11
- package/dist/mcp/client.d.ts +1 -0
- package/dist/mcp/client.js +12 -10
- package/dist/mcp/redaction.d.ts +3 -0
- package/dist/mcp/redaction.js +132 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +64 -22
- package/dist/observability.js +1 -1
- package/dist/screenshots/service.js +25 -2
- package/dist/skill-cli/direct.d.ts +4 -1
- package/dist/skill-cli/direct.js +260 -64
- package/dist/skill-cli/server.d.ts +1 -0
- package/dist/skill-cli/server.js +115 -25
- package/dist/skill-cli/tunnel.d.ts +1 -0
- package/dist/skill-cli/tunnel.js +16 -4
- package/dist/sync/auth.js +22 -9
- package/dist/telemetry/service.js +21 -2
- package/dist/telemetry/views.js +31 -8
- package/dist/tokens/custom-pricing.js +2 -2
- package/dist/tokens/openrouter-pricing.d.ts +11 -0
- package/dist/tokens/openrouter-pricing.js +102 -0
- package/dist/tokens/service.js +20 -16
- package/dist/utils.d.ts +3 -1
- package/dist/utils.js +4 -2
- package/package.json +75 -33
|
@@ -14,12 +14,19 @@ You excel at following tasks:
|
|
|
14
14
|
</language_settings>
|
|
15
15
|
<input>
|
|
16
16
|
At every step, your input will consist of:
|
|
17
|
-
1. <
|
|
18
|
-
2. <
|
|
19
|
-
3. <
|
|
20
|
-
4. <
|
|
21
|
-
5. <
|
|
17
|
+
1. <user_request>: Your ultimate objective.
|
|
18
|
+
2. <agent_history>: A chronological event stream including your previous actions and their results.
|
|
19
|
+
3. <agent_state>: Summary of <file_system>, <todo_contents>, and other current agent context.
|
|
20
|
+
4. <browser_state>: Current URL, open tabs, interactive elements indexed for actions, and visible page content.
|
|
21
|
+
5. <browser_vision>: Screenshot of the browser with bounding boxes around interactive elements. If you used screenshot before, this will contain a screenshot.
|
|
22
|
+
6. <read_state> This will be displayed only if your previous action was extract or read_file. This data is only shown in the current step.
|
|
22
23
|
</input>
|
|
24
|
+
<user_request>
|
|
25
|
+
USER REQUEST: This is your ultimate objective and always remains visible.
|
|
26
|
+
- This has the highest priority. Make the user happy.
|
|
27
|
+
- If the user request is very specific - then carefully follow each step and dont skip or hallucinate steps.
|
|
28
|
+
- If the task is open ended you can plan yourself how to get it done.
|
|
29
|
+
</user_request>
|
|
23
30
|
<agent_history>
|
|
24
31
|
Agent history will be given as a list of step information as follows:
|
|
25
32
|
<step_{{step_number}}>:
|
|
@@ -30,12 +37,6 @@ Action Results: Your actions and their results
|
|
|
30
37
|
</step_{{step_number}}>
|
|
31
38
|
and system messages wrapped in <sys> tag.
|
|
32
39
|
</agent_history>
|
|
33
|
-
<user_request>
|
|
34
|
-
USER REQUEST: This is your ultimate objective and always remains visible.
|
|
35
|
-
- This has the highest priority. Make the user happy.
|
|
36
|
-
- If the user request is very specific - then carefully follow each step and dont skip or hallucinate steps.
|
|
37
|
-
- If the task is open ended you can plan yourself how to get it done.
|
|
38
|
-
</user_request>
|
|
39
40
|
<browser_state>
|
|
40
41
|
1. Browser State will be given as:
|
|
41
42
|
Current URL: URL of the page you are currently viewing.
|
|
@@ -101,11 +101,12 @@ BEFORE calling `done` with `success=true`, you MUST perform this verification:
|
|
|
101
101
|
</task_completion_rules>
|
|
102
102
|
<input>
|
|
103
103
|
At every step, your input will consist of:
|
|
104
|
-
1. <
|
|
105
|
-
2. <
|
|
106
|
-
3. <
|
|
107
|
-
4. <
|
|
108
|
-
5. <
|
|
104
|
+
1. <user_request>: Your ultimate objective.
|
|
105
|
+
2. <agent_history>: A chronological event stream including your previous actions and their results.
|
|
106
|
+
3. <agent_state>: Summary of <file_system>, <todo_contents>, and other current agent context.
|
|
107
|
+
4. <browser_state>: Current URL, open tabs, interactive elements indexed for actions, and visible page content.
|
|
108
|
+
5. <browser_vision>: Screenshot of the browser with bounding boxes around interactive elements. This is your GROUND TRUTH.
|
|
109
|
+
6. <read_state> This will be displayed only if your previous action was extract or read_file. This data is only shown in the current step.
|
|
109
110
|
</input>
|
|
110
111
|
<agent_history>
|
|
111
112
|
Agent history will be given as a list of step information as follows:
|
|
@@ -14,12 +14,19 @@ You excel at following tasks:
|
|
|
14
14
|
</language_settings>
|
|
15
15
|
<input>
|
|
16
16
|
At every step, your input will consist of:
|
|
17
|
-
1. <
|
|
18
|
-
2. <
|
|
19
|
-
3. <
|
|
20
|
-
4. <
|
|
21
|
-
5. <
|
|
17
|
+
1. <user_request>: Your ultimate objective.
|
|
18
|
+
2. <agent_history>: A chronological event stream including your previous actions and their results.
|
|
19
|
+
3. <agent_state>: Summary of <file_system>, <todo_contents>, and other current agent context.
|
|
20
|
+
4. <browser_state>: Current URL, open tabs, interactive elements indexed for actions, and visible page content.
|
|
21
|
+
5. <browser_vision>: Screenshot of the browser with bounding boxes around interactive elements. If you used screenshot before, this will contain a screenshot.
|
|
22
|
+
6. <read_state> This will be displayed only if your previous action was extract or read_file. This data is only shown in the current step.
|
|
22
23
|
</input>
|
|
24
|
+
<user_request>
|
|
25
|
+
USER REQUEST: This is your ultimate objective and always remains visible.
|
|
26
|
+
- This has the highest priority. Make the user happy.
|
|
27
|
+
- If the user request is very specific - then carefully follow each step and dont skip or hallucinate steps.
|
|
28
|
+
- If the task is open ended you can plan yourself how to get it done.
|
|
29
|
+
</user_request>
|
|
23
30
|
<agent_history>
|
|
24
31
|
Agent history will be given as a list of step information as follows:
|
|
25
32
|
<step_{{step_number}}>:
|
|
@@ -30,12 +37,6 @@ Action Results: Your actions and their results
|
|
|
30
37
|
</step_{{step_number}}>
|
|
31
38
|
and system messages wrapped in <sys> tag.
|
|
32
39
|
</agent_history>
|
|
33
|
-
<user_request>
|
|
34
|
-
USER REQUEST: This is your ultimate objective and always remains visible.
|
|
35
|
-
- This has the highest priority. Make the user happy.
|
|
36
|
-
- If the user request is very specific - then carefully follow each step and dont skip or hallucinate steps.
|
|
37
|
-
- If the task is open ended you can plan yourself how to get it done.
|
|
38
|
-
</user_request>
|
|
39
40
|
<browser_state>
|
|
40
41
|
1. Browser State will be given as:
|
|
41
42
|
Current URL: URL of the page you are currently viewing.
|
package/dist/agent/views.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface StructuredOutputParser<T = unknown> {
|
|
|
13
13
|
model_json_schema?: () => unknown;
|
|
14
14
|
schema?: unknown;
|
|
15
15
|
}
|
|
16
|
+
type SensitiveDataMap = Record<string, string | Record<string, string>>;
|
|
17
|
+
export declare const redactSensitiveDataFromString: (value: string, sensitive_data: SensitiveDataMap | null) => string;
|
|
16
18
|
export interface ActionResultInit {
|
|
17
19
|
is_done?: boolean | null;
|
|
18
20
|
success?: boolean | null;
|
package/dist/agent/views.js
CHANGED
|
@@ -8,6 +8,45 @@ import { DEFAULT_INCLUDE_ATTRIBUTES, } from '../dom/views.js';
|
|
|
8
8
|
import { MessageManagerState } from './message-manager/views.js';
|
|
9
9
|
// Re-export ActionModel for agent/service.ts
|
|
10
10
|
export { ActionModel };
|
|
11
|
+
export const redactSensitiveDataFromString = (value, sensitive_data) => {
|
|
12
|
+
if (!sensitive_data) {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
const placeholders = {};
|
|
16
|
+
for (const [keyOrDomain, content] of Object.entries(sensitive_data)) {
|
|
17
|
+
if (typeof content === 'string' && content) {
|
|
18
|
+
placeholders[keyOrDomain] = content;
|
|
19
|
+
}
|
|
20
|
+
else if (content && typeof content === 'object') {
|
|
21
|
+
for (const [key, val] of Object.entries(content)) {
|
|
22
|
+
if (val) {
|
|
23
|
+
placeholders[key] = val;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const entries = Object.entries(placeholders).sort(([, left], [, right]) => right.length - left.length);
|
|
29
|
+
if (!entries.length) {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
let filtered = value;
|
|
33
|
+
for (const [key, secret] of entries) {
|
|
34
|
+
filtered = filtered.split(secret).join(`<secret>${key}</secret>`);
|
|
35
|
+
}
|
|
36
|
+
return filtered;
|
|
37
|
+
};
|
|
38
|
+
const chmodPrivateFile = (filePath) => {
|
|
39
|
+
if (process.platform !== 'win32') {
|
|
40
|
+
fs.chmodSync(filePath, 0o600);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const ensurePrivateDirectoryIfCreated = (dirPath) => {
|
|
44
|
+
const existed = fs.existsSync(dirPath);
|
|
45
|
+
fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
|
|
46
|
+
if (!existed && process.platform !== 'win32') {
|
|
47
|
+
fs.chmodSync(dirPath, 0o700);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
11
50
|
const parseStructuredOutput = (schema, value) => {
|
|
12
51
|
if (!schema) {
|
|
13
52
|
return null;
|
|
@@ -539,30 +578,7 @@ export class AgentHistory {
|
|
|
539
578
|
return elements;
|
|
540
579
|
}
|
|
541
580
|
static _filterSensitiveDataFromString(value, sensitive_data) {
|
|
542
|
-
|
|
543
|
-
return value;
|
|
544
|
-
}
|
|
545
|
-
const placeholders = {};
|
|
546
|
-
for (const [keyOrDomain, content] of Object.entries(sensitive_data)) {
|
|
547
|
-
if (typeof content === 'string' && content) {
|
|
548
|
-
placeholders[keyOrDomain] = content;
|
|
549
|
-
}
|
|
550
|
-
else if (content && typeof content === 'object') {
|
|
551
|
-
for (const [key, val] of Object.entries(content)) {
|
|
552
|
-
if (val) {
|
|
553
|
-
placeholders[key] = val;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
if (!Object.keys(placeholders).length) {
|
|
559
|
-
return value;
|
|
560
|
-
}
|
|
561
|
-
let filtered = value;
|
|
562
|
-
for (const [key, secret] of Object.entries(placeholders)) {
|
|
563
|
-
filtered = filtered.split(secret).join(`<secret>${key}</secret>`);
|
|
564
|
-
}
|
|
565
|
-
return filtered;
|
|
581
|
+
return redactSensitiveDataFromString(value, sensitive_data);
|
|
566
582
|
}
|
|
567
583
|
static _filterSensitiveDataFromDict(data, sensitive_data) {
|
|
568
584
|
if (!sensitive_data) {
|
|
@@ -594,16 +610,8 @@ export class AgentHistory {
|
|
|
594
610
|
return filtered;
|
|
595
611
|
}
|
|
596
612
|
toJSON(sensitive_data = null) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
Array.isArray(modelOutput.action) &&
|
|
600
|
-
sensitive_data) {
|
|
601
|
-
modelOutput.action = modelOutput.action.map((action) => Object.prototype.hasOwnProperty.call(action, 'input')
|
|
602
|
-
? AgentHistory._filterSensitiveDataFromDict(action, sensitive_data)
|
|
603
|
-
: action);
|
|
604
|
-
}
|
|
605
|
-
return {
|
|
606
|
-
model_output: modelOutput,
|
|
613
|
+
const payload = {
|
|
614
|
+
model_output: this.model_output?.toJSON() ?? null,
|
|
607
615
|
result: this.result.map((r) => r.toJSON()),
|
|
608
616
|
state: this.state.to_dict(),
|
|
609
617
|
metadata: this.metadata
|
|
@@ -616,6 +624,9 @@ export class AgentHistory {
|
|
|
616
624
|
: null,
|
|
617
625
|
state_message: this.state_message,
|
|
618
626
|
};
|
|
627
|
+
return sensitive_data
|
|
628
|
+
? AgentHistory._filterSensitiveDataFromDict(payload, sensitive_data)
|
|
629
|
+
: payload;
|
|
619
630
|
}
|
|
620
631
|
}
|
|
621
632
|
export class AgentHistoryList {
|
|
@@ -863,8 +874,9 @@ export class AgentHistoryList {
|
|
|
863
874
|
}
|
|
864
875
|
save_to_file(filepath, sensitive_data = null) {
|
|
865
876
|
const dir = path.dirname(filepath);
|
|
866
|
-
|
|
867
|
-
fs.writeFileSync(filepath, JSON.stringify(this.toJSON(sensitive_data), null, 2), 'utf-8');
|
|
877
|
+
ensurePrivateDirectoryIfCreated(dir);
|
|
878
|
+
fs.writeFileSync(filepath, JSON.stringify(this.toJSON(sensitive_data), null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
879
|
+
chmodPrivateFile(filepath);
|
|
868
880
|
}
|
|
869
881
|
static load_from_file(filepath, outputModel) {
|
|
870
882
|
const content = fs.readFileSync(filepath, 'utf-8');
|
|
@@ -13,6 +13,22 @@ import { Readable } from 'node:stream';
|
|
|
13
13
|
import extract from 'extract-zip';
|
|
14
14
|
import { createLogger } from '../logging-config.js';
|
|
15
15
|
const logger = createLogger('browser_use.extensions');
|
|
16
|
+
const chmodPrivatePath = (targetPath, mode) => {
|
|
17
|
+
if (process.platform === 'win32') {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
fs.chmodSync(targetPath, mode);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
/* best effort */
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const createPrivateDirectory = (dirPath) => {
|
|
28
|
+
fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
|
|
29
|
+
chmodPrivatePath(dirPath, 0o700);
|
|
30
|
+
};
|
|
31
|
+
const createPrivateWriteStream = (filePath) => createWriteStream(filePath, { mode: 0o600 });
|
|
16
32
|
/**
|
|
17
33
|
* Generate extension ID from unpacked path
|
|
18
34
|
* Chrome uses SHA256 hash of the directory path
|
|
@@ -42,14 +58,11 @@ async function downloadCrx(crxUrl, crxPath) {
|
|
|
42
58
|
logger.warning(`[⚠️] Failed to download extension: ${response.statusText}`);
|
|
43
59
|
return false;
|
|
44
60
|
}
|
|
45
|
-
// Ensure directory exists
|
|
46
61
|
const dir = path.dirname(crxPath);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
// Download file
|
|
51
|
-
const fileStream = createWriteStream(crxPath);
|
|
62
|
+
createPrivateDirectory(dir);
|
|
63
|
+
const fileStream = createPrivateWriteStream(crxPath);
|
|
52
64
|
await pipeline(Readable.fromWeb(response.body), fileStream);
|
|
65
|
+
chmodPrivatePath(crxPath, 0o600);
|
|
53
66
|
logger.info(`[✅] Downloaded to ${crxPath}`);
|
|
54
67
|
return true;
|
|
55
68
|
}
|
|
@@ -63,10 +76,7 @@ async function downloadCrx(crxUrl, crxPath) {
|
|
|
63
76
|
*/
|
|
64
77
|
async function unpackCrx(crxPath, unpackedPath) {
|
|
65
78
|
try {
|
|
66
|
-
|
|
67
|
-
if (!fs.existsSync(unpackedPath)) {
|
|
68
|
-
fs.mkdirSync(unpackedPath, { recursive: true });
|
|
69
|
-
}
|
|
79
|
+
createPrivateDirectory(unpackedPath);
|
|
70
80
|
// Extract zip file (CRX is essentially a ZIP with extra header)
|
|
71
81
|
await extract(crxPath, { dir: path.resolve(unpackedPath) });
|
|
72
82
|
// Verify manifest exists
|
|
@@ -4,6 +4,7 @@ export declare const DOMAIN_OPTIMIZATION_THRESHOLD = 100;
|
|
|
4
4
|
export declare const CHROME_DISABLED_COMPONENTS: string[];
|
|
5
5
|
export declare const CHROME_HEADLESS_ARGS: string[];
|
|
6
6
|
export declare const CHROME_DOCKER_ARGS: string[];
|
|
7
|
+
export declare const CHROME_PROFILE_TRANSIENT_FILE_PATTERNS: string[];
|
|
7
8
|
export declare const CHROME_DISABLE_SECURITY_ARGS: string[];
|
|
8
9
|
export declare const CHROME_DETERMINISTIC_RENDERING_ARGS: string[];
|
|
9
10
|
export declare const CHROME_DEFAULT_ARGS: string[];
|
|
@@ -181,6 +182,9 @@ export declare class BrowserProfile {
|
|
|
181
182
|
private warnStorageStateUserDataDirConflict;
|
|
182
183
|
private warnUserDataDirNonDefault;
|
|
183
184
|
private warnDeterministicRenderingWeirdness;
|
|
185
|
+
private copyChromeProfileToTempIfNeeded;
|
|
186
|
+
private isChromeBackedProfile;
|
|
187
|
+
private isBrowserUseManagedProfile;
|
|
184
188
|
private ensureDefaultDownloadsPath;
|
|
185
189
|
private getDefaultArgsList;
|
|
186
190
|
private getWindowSizeArgs;
|
package/dist/browser/profile.js
CHANGED
|
@@ -12,6 +12,21 @@ import { log_pretty_path, uuid7str } from '../utils.js';
|
|
|
12
12
|
const logger = createLogger('browser_use.browser.profile');
|
|
13
13
|
export const CHROME_DEBUG_PORT = 9242;
|
|
14
14
|
export const DOMAIN_OPTIMIZATION_THRESHOLD = 100;
|
|
15
|
+
const chmodPrivatePath = (targetPath, mode) => {
|
|
16
|
+
if (process.platform === 'win32') {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
fs.chmodSync(targetPath, mode);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
/* best effort */
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const createPrivateDirectory = (dirPath) => {
|
|
27
|
+
fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
|
|
28
|
+
chmodPrivatePath(dirPath, 0o700);
|
|
29
|
+
};
|
|
15
30
|
export const CHROME_DISABLED_COMPONENTS = [
|
|
16
31
|
'AcceptCHFrame',
|
|
17
32
|
'AutoExpandDetailsElement',
|
|
@@ -54,6 +69,13 @@ export const CHROME_DOCKER_ARGS = [
|
|
|
54
69
|
'--no-zygote',
|
|
55
70
|
'--disable-site-isolation-trials',
|
|
56
71
|
];
|
|
72
|
+
export const CHROME_PROFILE_TRANSIENT_FILE_PATTERNS = [
|
|
73
|
+
'Singleton*',
|
|
74
|
+
'*.lock',
|
|
75
|
+
'*-journal',
|
|
76
|
+
'LOCK',
|
|
77
|
+
'LOCKFILE',
|
|
78
|
+
];
|
|
57
79
|
export const CHROME_DISABLE_SECURITY_ARGS = [
|
|
58
80
|
'--disable-site-isolation-trials',
|
|
59
81
|
'--disable-web-security',
|
|
@@ -381,6 +403,30 @@ const argsAsList = (args) => Object.entries(args).map(([key, value]) => value
|
|
|
381
403
|
? `--${key.replace(/^-+/, '')}=${value}`
|
|
382
404
|
: `--${key.replace(/^-+/, '')}`);
|
|
383
405
|
const cloneDefaultOptions = () => JSON.parse(JSON.stringify(DEFAULT_BROWSER_PROFILE_OPTIONS));
|
|
406
|
+
const isChromeProfileTransientFile = (name) => name.startsWith('Singleton') ||
|
|
407
|
+
name.endsWith('.lock') ||
|
|
408
|
+
name.endsWith('-journal') ||
|
|
409
|
+
name === 'LOCK' ||
|
|
410
|
+
name === 'LOCKFILE';
|
|
411
|
+
const isChromeProfileLockError = (error) => {
|
|
412
|
+
if (!(error instanceof Error)) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
const code = String(error.code ?? '');
|
|
416
|
+
const message = error.message;
|
|
417
|
+
return (code === 'EACCES' ||
|
|
418
|
+
code === 'EPERM' ||
|
|
419
|
+
code === 'EBUSY' ||
|
|
420
|
+
message.includes('WinError 32') ||
|
|
421
|
+
message.includes('being used by another process'));
|
|
422
|
+
};
|
|
423
|
+
const pathContains = (candidate, parent) => {
|
|
424
|
+
const relative = path.relative(path.resolve(parent), path.resolve(candidate));
|
|
425
|
+
return (relative === '' ||
|
|
426
|
+
(relative !== '' &&
|
|
427
|
+
!relative.startsWith('..') &&
|
|
428
|
+
!path.isAbsolute(relative)));
|
|
429
|
+
};
|
|
384
430
|
const normalizeDomainEntry = (entry) => String(entry ?? '')
|
|
385
431
|
.trim()
|
|
386
432
|
.toLowerCase();
|
|
@@ -443,6 +489,7 @@ export class BrowserProfile {
|
|
|
443
489
|
this.warnStorageStateUserDataDirConflict();
|
|
444
490
|
this.warnUserDataDirNonDefault();
|
|
445
491
|
this.warnDeterministicRenderingWeirdness();
|
|
492
|
+
this.copyChromeProfileToTempIfNeeded();
|
|
446
493
|
}
|
|
447
494
|
toString() {
|
|
448
495
|
return `BrowserProfile#${this.options.id.slice(-4)}`;
|
|
@@ -555,6 +602,59 @@ export class BrowserProfile {
|
|
|
555
602
|
logger.warning('⚠️ BrowserSession(deterministic_rendering=True) is NOT RECOMMENDED. It breaks many sites and increases chances of getting blocked by anti-bot systems. It hardcodes the JS random seed and forces browsers across Linux/Mac/Windows to use the same font rendering engine so that identical screenshots can be generated.');
|
|
556
603
|
}
|
|
557
604
|
}
|
|
605
|
+
copyChromeProfileToTempIfNeeded() {
|
|
606
|
+
const userDataDir = this.options.user_data_dir;
|
|
607
|
+
if (!userDataDir) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
const userDataDirText = String(userDataDir);
|
|
611
|
+
if (userDataDirText.toLowerCase().includes('browser-use-user-data-dir-')) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (this.isBrowserUseManagedProfile(userDataDirText)) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (!this.isChromeBackedProfile(userDataDirText)) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'browser-use-user-data-dir-'));
|
|
621
|
+
const originalProfileDir = path.join(userDataDirText, this.options.profile_directory);
|
|
622
|
+
const tempProfileDir = path.join(tempDir, this.options.profile_directory);
|
|
623
|
+
try {
|
|
624
|
+
if (fs.existsSync(originalProfileDir)) {
|
|
625
|
+
fs.cpSync(originalProfileDir, tempProfileDir, {
|
|
626
|
+
recursive: true,
|
|
627
|
+
filter: (source) => !isChromeProfileTransientFile(path.basename(source)),
|
|
628
|
+
});
|
|
629
|
+
const localStateSource = path.join(userDataDirText, 'Local State');
|
|
630
|
+
if (fs.existsSync(localStateSource)) {
|
|
631
|
+
fs.copyFileSync(localStateSource, path.join(tempDir, 'Local State'));
|
|
632
|
+
}
|
|
633
|
+
logger.info(`Copied profile (${this.options.profile_directory}) and Local State to temp directory: ${tempDir}`);
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
fs.mkdirSync(tempProfileDir, { recursive: true });
|
|
637
|
+
logger.info(`Created new profile (${this.options.profile_directory}) in temp directory: ${tempDir}`);
|
|
638
|
+
}
|
|
639
|
+
this.options.user_data_dir = tempDir;
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
643
|
+
if (isChromeProfileLockError(error)) {
|
|
644
|
+
throw new Error(`Unable to copy Chrome profile "${this.options.profile_directory}" because one or more files are locked. ` +
|
|
645
|
+
'Close any Chrome windows using this profile, or start browser-use with --cdp-url to connect to an already-running browser instead of copying the profile.');
|
|
646
|
+
}
|
|
647
|
+
throw error;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
isChromeBackedProfile(userDataDirText) {
|
|
651
|
+
const normalized = userDataDirText.toLowerCase();
|
|
652
|
+
return normalized.includes('chrome') || normalized.includes('chromium');
|
|
653
|
+
}
|
|
654
|
+
isBrowserUseManagedProfile(userDataDirText) {
|
|
655
|
+
const profilesDir = path.dirname(CONFIG.BROWSER_USE_DEFAULT_USER_DATA_DIR);
|
|
656
|
+
return pathContains(userDataDirText, profilesDir);
|
|
657
|
+
}
|
|
558
658
|
ensureDefaultDownloadsPath() {
|
|
559
659
|
if (this.options.downloads_path) {
|
|
560
660
|
return;
|
|
@@ -563,7 +663,7 @@ export class BrowserProfile {
|
|
|
563
663
|
while (fs.existsSync(downloadsPath)) {
|
|
564
664
|
downloadsPath = path.join(os.tmpdir(), `browser-use-downloads-${randomUUID().slice(0, 8)}`);
|
|
565
665
|
}
|
|
566
|
-
|
|
666
|
+
createPrivateDirectory(downloadsPath);
|
|
567
667
|
this.options.downloads_path = downloadsPath;
|
|
568
668
|
}
|
|
569
669
|
getDefaultArgsList() {
|
|
@@ -613,7 +713,7 @@ export class BrowserProfile {
|
|
|
613
713
|
}
|
|
614
714
|
async ensureDefaultExtensionsDownloaded() {
|
|
615
715
|
const cacheDir = CONFIG.BROWSER_USE_EXTENSIONS_DIR;
|
|
616
|
-
|
|
716
|
+
createPrivateDirectory(cacheDir);
|
|
617
717
|
const extensionPaths = [];
|
|
618
718
|
const loadedNames = [];
|
|
619
719
|
for (const ext of DEFAULT_EXTENSIONS) {
|
|
@@ -674,7 +774,10 @@ export class BrowserProfile {
|
|
|
674
774
|
response.on('data', (chunk) => chunks.push(chunk));
|
|
675
775
|
response.on('end', async () => {
|
|
676
776
|
try {
|
|
677
|
-
await fsp.writeFile(outputPath, Buffer.concat(chunks)
|
|
777
|
+
await fsp.writeFile(outputPath, Buffer.concat(chunks), {
|
|
778
|
+
mode: 0o600,
|
|
779
|
+
});
|
|
780
|
+
chmodPrivatePath(outputPath, 0o600);
|
|
678
781
|
resolve();
|
|
679
782
|
}
|
|
680
783
|
catch (error) {
|
|
@@ -689,7 +792,7 @@ export class BrowserProfile {
|
|
|
689
792
|
if (fs.existsSync(extractDir)) {
|
|
690
793
|
await fsp.rm(extractDir, { recursive: true, force: true });
|
|
691
794
|
}
|
|
692
|
-
|
|
795
|
+
createPrivateDirectory(extractDir);
|
|
693
796
|
const buffer = await fsp.readFile(crxPath);
|
|
694
797
|
try {
|
|
695
798
|
const zip = new AdmZip(buffer);
|
|
@@ -95,6 +95,8 @@ export declare class BrowserSession {
|
|
|
95
95
|
private _watchdogs;
|
|
96
96
|
private _defaultWatchdogsAttached;
|
|
97
97
|
private _captchaWatchdog;
|
|
98
|
+
private _scopedExtraHeadersRouteContext;
|
|
99
|
+
private _scopedExtraHeadersRouteHandler;
|
|
98
100
|
readonly RECONNECT_WAIT_TIMEOUT = 54;
|
|
99
101
|
private _reconnecting;
|
|
100
102
|
private _reconnectTask;
|
|
@@ -157,7 +159,10 @@ export declare class BrowserSession {
|
|
|
157
159
|
private _waitWithAbort;
|
|
158
160
|
private _withAbort;
|
|
159
161
|
private _toPlaywrightOptions;
|
|
162
|
+
private _assertHttpCredentialsScoped;
|
|
160
163
|
set_extra_headers(headers: Record<string, string>): Promise<void>;
|
|
164
|
+
private _removeScopedExtraHeadersRoute;
|
|
165
|
+
private _installScopedExtraHeaders;
|
|
161
166
|
private _applyConfiguredExtraHttpHeaders;
|
|
162
167
|
private _usesRemoteBrowserConnection;
|
|
163
168
|
private _connectToConfiguredBrowser;
|
|
@@ -244,6 +249,12 @@ export declare class BrowserSession {
|
|
|
244
249
|
set_auto_download_pdfs(enabled: boolean): void;
|
|
245
250
|
auto_download_pdfs(): boolean;
|
|
246
251
|
static get_unique_filename(directory: string, filename: string): Promise<string>;
|
|
252
|
+
private _cancel_download_best_effort;
|
|
253
|
+
private _assert_download_url_allowed;
|
|
254
|
+
private static _sanitize_download_filename;
|
|
255
|
+
private static _redact_url_for_logging;
|
|
256
|
+
private static _redact_urls_in_text;
|
|
257
|
+
private static _same_origin;
|
|
247
258
|
get_selector_map(options?: BrowserActionOptions): Promise<SelectorMap>;
|
|
248
259
|
static is_file_input(node: DOMElementNode | null): boolean;
|
|
249
260
|
is_file_input(node: DOMElementNode | null): boolean;
|
|
@@ -255,7 +266,9 @@ export declare class BrowserSession {
|
|
|
255
266
|
/**
|
|
256
267
|
* Get all cookies from the current browser context
|
|
257
268
|
*/
|
|
258
|
-
get_cookies(
|
|
269
|
+
get_cookies(options?: {
|
|
270
|
+
include_blocked?: boolean;
|
|
271
|
+
}): Promise<Array<Record<string, any>>>;
|
|
259
272
|
/**
|
|
260
273
|
* Save cookies to a file (deprecated, use save_storage_state instead)
|
|
261
274
|
* @deprecated Use save_storage_state() instead
|
|
@@ -274,10 +287,17 @@ export declare class BrowserSession {
|
|
|
274
287
|
* Load storage state (cookies, localStorage, sessionStorage) from a file
|
|
275
288
|
*/
|
|
276
289
|
load_storage_state(filePath?: string): Promise<void>;
|
|
290
|
+
private _apply_storage_state_origins;
|
|
291
|
+
private _normalize_storage_entries;
|
|
292
|
+
private _sanitize_storage_state_for_save;
|
|
293
|
+
private _filter_storage_state_cookies;
|
|
294
|
+
private _filter_cookies_for_exposure;
|
|
295
|
+
private _get_cookie_access_denial_reason;
|
|
277
296
|
/**
|
|
278
297
|
* Execute JavaScript in the current page context
|
|
279
298
|
*/
|
|
280
299
|
execute_javascript(script: string): Promise<any>;
|
|
300
|
+
validate_page_after_action(page: Page | null, signal?: AbortSignal | null): Promise<void>;
|
|
281
301
|
/**
|
|
282
302
|
* Get comprehensive page information (size, scroll position, etc.)
|
|
283
303
|
*/
|
|
@@ -384,6 +404,7 @@ export declare class BrowserSession {
|
|
|
384
404
|
private _is_ip_address_host;
|
|
385
405
|
private _get_domain_variants;
|
|
386
406
|
private _setEntryMatchesUrl;
|
|
407
|
+
private _domainCollectionHasEntries;
|
|
387
408
|
/**
|
|
388
409
|
* Check if page is displaying a PDF
|
|
389
410
|
*/
|
|
@@ -423,7 +444,13 @@ export declare class BrowserSession {
|
|
|
423
444
|
private _get_url_access_denial_reason;
|
|
424
445
|
private _is_url_allowed;
|
|
425
446
|
private _formatDomainCollection;
|
|
447
|
+
private _has_url_access_restrictions;
|
|
448
|
+
private _sanitize_tab_for_exposure;
|
|
426
449
|
private _assert_url_allowed;
|
|
450
|
+
private _rollback_disallowed_navigation;
|
|
451
|
+
private _replace_disallowed_current_page;
|
|
452
|
+
private _assert_page_url_allowed_or_rollback;
|
|
453
|
+
private _get_disallowed_page_error_after_navigation_error;
|
|
427
454
|
/**
|
|
428
455
|
* Navigate helper with URL validation
|
|
429
456
|
*/
|