browser-use 0.0.1 → 0.0.2
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/LICENSE +21 -0
- package/README.md +761 -0
- package/dist/agent/cloud-events.d.ts +264 -0
- package/dist/agent/cloud-events.js +318 -0
- package/dist/agent/gif.d.ts +15 -0
- package/dist/agent/gif.js +215 -0
- package/dist/agent/index.d.ts +8 -0
- package/dist/agent/index.js +8 -0
- package/dist/agent/message-manager/service.d.ts +30 -0
- package/dist/agent/message-manager/service.js +208 -0
- package/dist/agent/message-manager/utils.d.ts +2 -0
- package/dist/agent/message-manager/utils.js +41 -0
- package/dist/agent/message-manager/views.d.ts +26 -0
- package/dist/agent/message-manager/views.js +73 -0
- package/dist/agent/prompts.d.ts +52 -0
- package/dist/agent/prompts.js +259 -0
- package/dist/agent/service.d.ts +290 -0
- package/dist/agent/service.js +2200 -0
- package/dist/agent/views.d.ts +741 -0
- package/dist/agent/views.js +537 -0
- package/dist/browser/browser.d.ts +7 -0
- package/dist/browser/browser.js +5 -0
- package/dist/browser/context.d.ts +8 -0
- package/dist/browser/context.js +4 -0
- package/dist/browser/dvd-screensaver.d.ts +101 -0
- package/dist/browser/dvd-screensaver.js +270 -0
- package/dist/browser/extensions.d.ts +63 -0
- package/dist/browser/extensions.js +359 -0
- package/dist/browser/index.d.ts +10 -0
- package/dist/browser/index.js +9 -0
- package/dist/browser/playwright-manager.d.ts +47 -0
- package/dist/browser/playwright-manager.js +146 -0
- package/dist/browser/profile.d.ts +196 -0
- package/dist/browser/profile.js +815 -0
- package/dist/browser/session.d.ts +505 -0
- package/dist/browser/session.js +3409 -0
- package/dist/browser/types.d.ts +1184 -0
- package/dist/browser/types.js +1 -0
- package/dist/browser/utils.d.ts +1 -0
- package/dist/browser/utils.js +19 -0
- package/dist/browser/views.d.ts +78 -0
- package/dist/browser/views.js +72 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +44 -0
- package/dist/config.d.ts +108 -0
- package/dist/config.js +430 -0
- package/dist/controller/index.d.ts +3 -0
- package/dist/controller/index.js +3 -0
- package/dist/controller/registry/index.d.ts +2 -0
- package/dist/controller/registry/index.js +2 -0
- package/dist/controller/registry/service.d.ts +45 -0
- package/dist/controller/registry/service.js +184 -0
- package/dist/controller/registry/views.d.ts +55 -0
- package/dist/controller/registry/views.js +174 -0
- package/dist/controller/service.d.ts +49 -0
- package/dist/controller/service.js +1176 -0
- package/dist/controller/views.d.ts +241 -0
- package/dist/controller/views.js +88 -0
- package/dist/dom/clickable-element-processor/service.d.ts +11 -0
- package/dist/dom/clickable-element-processor/service.js +60 -0
- package/dist/dom/dom_tree/index.js +1400 -0
- package/dist/dom/history-tree-processor/service.d.ts +14 -0
- package/dist/dom/history-tree-processor/service.js +75 -0
- package/dist/dom/history-tree-processor/view.d.ts +54 -0
- package/dist/dom/history-tree-processor/view.js +56 -0
- package/dist/dom/playground/extraction.d.ts +19 -0
- package/dist/dom/playground/extraction.js +187 -0
- package/dist/dom/playground/process-dom.d.ts +1 -0
- package/dist/dom/playground/process-dom.js +5 -0
- package/dist/dom/playground/test-accessibility.d.ts +44 -0
- package/dist/dom/playground/test-accessibility.js +111 -0
- package/dist/dom/service.d.ts +19 -0
- package/dist/dom/service.js +227 -0
- package/dist/dom/utils.d.ts +1 -0
- package/dist/dom/utils.js +6 -0
- package/dist/dom/views.d.ts +61 -0
- package/dist/dom/views.js +247 -0
- package/dist/event-bus.d.ts +11 -0
- package/dist/event-bus.js +19 -0
- package/dist/exceptions.d.ts +10 -0
- package/dist/exceptions.js +22 -0
- package/dist/filesystem/file-system.d.ts +68 -0
- package/dist/filesystem/file-system.js +412 -0
- package/dist/filesystem/index.d.ts +1 -0
- package/dist/filesystem/index.js +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +33 -0
- package/dist/integrations/gmail/actions.d.ts +12 -0
- package/dist/integrations/gmail/actions.js +113 -0
- package/dist/integrations/gmail/index.d.ts +2 -0
- package/dist/integrations/gmail/index.js +2 -0
- package/dist/integrations/gmail/service.d.ts +61 -0
- package/dist/integrations/gmail/service.js +260 -0
- package/dist/llm/anthropic/chat.d.ts +28 -0
- package/dist/llm/anthropic/chat.js +126 -0
- package/dist/llm/anthropic/index.d.ts +2 -0
- package/dist/llm/anthropic/index.js +2 -0
- package/dist/llm/anthropic/serializer.d.ts +68 -0
- package/dist/llm/anthropic/serializer.js +285 -0
- package/dist/llm/aws/chat-anthropic.d.ts +61 -0
- package/dist/llm/aws/chat-anthropic.js +176 -0
- package/dist/llm/aws/chat-bedrock.d.ts +15 -0
- package/dist/llm/aws/chat-bedrock.js +80 -0
- package/dist/llm/aws/index.d.ts +3 -0
- package/dist/llm/aws/index.js +3 -0
- package/dist/llm/aws/serializer.d.ts +5 -0
- package/dist/llm/aws/serializer.js +68 -0
- package/dist/llm/azure/chat.d.ts +15 -0
- package/dist/llm/azure/chat.js +83 -0
- package/dist/llm/azure/index.d.ts +1 -0
- package/dist/llm/azure/index.js +1 -0
- package/dist/llm/base.d.ts +16 -0
- package/dist/llm/base.js +1 -0
- package/dist/llm/deepseek/chat.d.ts +15 -0
- package/dist/llm/deepseek/chat.js +51 -0
- package/dist/llm/deepseek/index.d.ts +2 -0
- package/dist/llm/deepseek/index.js +2 -0
- package/dist/llm/deepseek/serializer.d.ts +6 -0
- package/dist/llm/deepseek/serializer.js +57 -0
- package/dist/llm/exceptions.d.ts +10 -0
- package/dist/llm/exceptions.js +18 -0
- package/dist/llm/google/chat.d.ts +20 -0
- package/dist/llm/google/chat.js +144 -0
- package/dist/llm/google/index.d.ts +2 -0
- package/dist/llm/google/index.js +2 -0
- package/dist/llm/google/serializer.d.ts +6 -0
- package/dist/llm/google/serializer.js +64 -0
- package/dist/llm/groq/chat.d.ts +15 -0
- package/dist/llm/groq/chat.js +52 -0
- package/dist/llm/groq/index.d.ts +3 -0
- package/dist/llm/groq/index.js +3 -0
- package/dist/llm/groq/parser.d.ts +32 -0
- package/dist/llm/groq/parser.js +189 -0
- package/dist/llm/groq/serializer.d.ts +6 -0
- package/dist/llm/groq/serializer.js +56 -0
- package/dist/llm/messages.d.ts +77 -0
- package/dist/llm/messages.js +157 -0
- package/dist/llm/ollama/chat.d.ts +15 -0
- package/dist/llm/ollama/chat.js +77 -0
- package/dist/llm/ollama/index.d.ts +2 -0
- package/dist/llm/ollama/index.js +2 -0
- package/dist/llm/ollama/serializer.d.ts +6 -0
- package/dist/llm/ollama/serializer.js +53 -0
- package/dist/llm/openai/chat.d.ts +38 -0
- package/dist/llm/openai/chat.js +174 -0
- package/dist/llm/openai/index.d.ts +3 -0
- package/dist/llm/openai/index.js +3 -0
- package/dist/llm/openai/like.d.ts +17 -0
- package/dist/llm/openai/like.js +19 -0
- package/dist/llm/openai/serializer.d.ts +6 -0
- package/dist/llm/openai/serializer.js +57 -0
- package/dist/llm/openrouter/chat.d.ts +15 -0
- package/dist/llm/openrouter/chat.js +74 -0
- package/dist/llm/openrouter/index.d.ts +2 -0
- package/dist/llm/openrouter/index.js +2 -0
- package/dist/llm/openrouter/serializer.d.ts +3 -0
- package/dist/llm/openrouter/serializer.js +3 -0
- package/dist/llm/schema.d.ts +6 -0
- package/dist/llm/schema.js +77 -0
- package/dist/llm/views.d.ts +15 -0
- package/dist/llm/views.js +12 -0
- package/dist/logging-config.d.ts +25 -0
- package/dist/logging-config.js +89 -0
- package/dist/mcp/client.d.ts +142 -0
- package/dist/mcp/client.js +638 -0
- package/dist/mcp/controller.d.ts +6 -0
- package/dist/mcp/controller.js +38 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/server.d.ts +134 -0
- package/dist/mcp/server.js +759 -0
- package/dist/observability-decorators.d.ts +158 -0
- package/dist/observability-decorators.js +286 -0
- package/dist/observability.d.ts +23 -0
- package/dist/observability.js +58 -0
- package/dist/screenshots/index.d.ts +1 -0
- package/dist/screenshots/index.js +1 -0
- package/dist/screenshots/service.d.ts +6 -0
- package/dist/screenshots/service.js +28 -0
- package/dist/sync/auth.d.ts +27 -0
- package/dist/sync/auth.js +205 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.js +2 -0
- package/dist/sync/service.d.ts +21 -0
- package/dist/sync/service.js +146 -0
- package/dist/telemetry/index.d.ts +2 -0
- package/dist/telemetry/index.js +2 -0
- package/dist/telemetry/service.d.ts +12 -0
- package/dist/telemetry/service.js +85 -0
- package/dist/telemetry/views.d.ts +112 -0
- package/dist/telemetry/views.js +112 -0
- package/dist/tokens/index.d.ts +2 -0
- package/dist/tokens/index.js +2 -0
- package/dist/tokens/service.d.ts +35 -0
- package/dist/tokens/service.js +423 -0
- package/dist/tokens/views.d.ts +58 -0
- package/dist/tokens/views.js +1 -0
- package/dist/utils.d.ts +128 -0
- package/dist/utils.js +529 -0
- package/package.json +94 -5
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
type Callback = (() => void) | undefined;
|
|
2
|
+
export interface SignalHandlerOptions {
|
|
3
|
+
pause_callback?: Callback;
|
|
4
|
+
resume_callback?: Callback;
|
|
5
|
+
custom_exit_callback?: Callback;
|
|
6
|
+
exit_on_second_int?: boolean;
|
|
7
|
+
interruptible_task_patterns?: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare class SignalHandler {
|
|
10
|
+
loop: NodeJS.EventEmitter | null;
|
|
11
|
+
pause_callback?: Callback;
|
|
12
|
+
resume_callback?: Callback;
|
|
13
|
+
custom_exit_callback?: Callback;
|
|
14
|
+
exit_on_second_int: boolean;
|
|
15
|
+
interruptible_task_patterns: string[];
|
|
16
|
+
is_windows: boolean;
|
|
17
|
+
private ctrl_c_pressed;
|
|
18
|
+
private waiting_for_input;
|
|
19
|
+
private bound_sigint;
|
|
20
|
+
private bound_sigterm;
|
|
21
|
+
constructor(options?: SignalHandlerOptions);
|
|
22
|
+
register(): void;
|
|
23
|
+
unregister(): void;
|
|
24
|
+
private _handle_second_ctrl_c;
|
|
25
|
+
private _cancel_interruptible_tasks;
|
|
26
|
+
wait_for_resume(): Promise<void>;
|
|
27
|
+
reset(): void;
|
|
28
|
+
private sigint_handler;
|
|
29
|
+
private sigterm_handler;
|
|
30
|
+
}
|
|
31
|
+
export declare const time_execution_sync: (additional_text?: string) => <T extends (...args: any[]) => any>(func: T) => T;
|
|
32
|
+
export declare const time_execution_async: (additional_text?: string) => <T extends (...args: any[]) => Promise<any>>(func: T) => T;
|
|
33
|
+
export declare const singleton: <T extends (...args: any[]) => any>(cls: T) => (...args: Parameters<T>) => ReturnType<T>;
|
|
34
|
+
export declare const check_env_variables: (keys: string[], predicate?: (values: string[]) => boolean) => boolean;
|
|
35
|
+
export declare const is_unsafe_pattern: (pattern: string) => boolean;
|
|
36
|
+
export declare const merge_dicts: (a: Record<string, any>, b: Record<string, any>, path?: (string | number)[]) => Record<string, any>;
|
|
37
|
+
export declare const get_browser_use_version: () => string;
|
|
38
|
+
export declare const get_git_info: () => Record<string, string> | null;
|
|
39
|
+
export declare const _log_pretty_path: (input: unknown) => string;
|
|
40
|
+
export declare const _log_pretty_url: (value: string, max_len?: number | null) => string;
|
|
41
|
+
export declare const log_pretty_path: (input: unknown) => string;
|
|
42
|
+
export declare const log_pretty_url: (value: string, max_len?: number | null) => string;
|
|
43
|
+
export declare const uuid7str: () => string;
|
|
44
|
+
/**
|
|
45
|
+
* Retry configuration options
|
|
46
|
+
*/
|
|
47
|
+
export interface RetryOptions {
|
|
48
|
+
/** Maximum number of retry attempts (default: 3) */
|
|
49
|
+
maxAttempts?: number;
|
|
50
|
+
/** Delay between retries in milliseconds (default: 1000) */
|
|
51
|
+
delayMs?: number;
|
|
52
|
+
/** Exponential backoff multiplier (default: 1 = no backoff) */
|
|
53
|
+
backoffMultiplier?: number;
|
|
54
|
+
/** Maximum delay in milliseconds for exponential backoff (default: 30000) */
|
|
55
|
+
maxDelayMs?: number;
|
|
56
|
+
/** Function to determine if error is retryable (default: all errors retryable) */
|
|
57
|
+
shouldRetry?: (error: Error, attempt: number) => boolean;
|
|
58
|
+
/** Callback called on each retry attempt */
|
|
59
|
+
onRetry?: (error: Error, attempt: number, nextDelayMs: number) => void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Retry an async function with configurable attempts and delays
|
|
63
|
+
* Implements exponential backoff with jitter
|
|
64
|
+
*
|
|
65
|
+
* @param fn - The async function to retry
|
|
66
|
+
* @param options - Retry configuration
|
|
67
|
+
* @returns The result of the function
|
|
68
|
+
* @throws The last error if all retries fail
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* const result = await retryAsync(
|
|
72
|
+
* async () => await fetchData(),
|
|
73
|
+
* { maxAttempts: 3, delayMs: 1000, backoffMultiplier: 2 }
|
|
74
|
+
* );
|
|
75
|
+
*/
|
|
76
|
+
export declare function retryAsync<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
77
|
+
/**
|
|
78
|
+
* Create a semaphore for limiting concurrent operations
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* const semaphore = createSemaphore(3); // Allow max 3 concurrent operations
|
|
82
|
+
* await semaphore.acquire();
|
|
83
|
+
* try {
|
|
84
|
+
* await doWork();
|
|
85
|
+
* } finally {
|
|
86
|
+
* semaphore.release();
|
|
87
|
+
* }
|
|
88
|
+
*/
|
|
89
|
+
export declare function createSemaphore(maxConcurrent: number): {
|
|
90
|
+
/**
|
|
91
|
+
* Acquire a semaphore slot
|
|
92
|
+
* Waits if max concurrent operations are already running
|
|
93
|
+
*/
|
|
94
|
+
acquire(): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Release a semaphore slot
|
|
97
|
+
* Allows next queued operation to proceed
|
|
98
|
+
*/
|
|
99
|
+
release(): void;
|
|
100
|
+
/**
|
|
101
|
+
* Get current active count
|
|
102
|
+
*/
|
|
103
|
+
getActiveCount(): number;
|
|
104
|
+
/**
|
|
105
|
+
* Get queue length
|
|
106
|
+
*/
|
|
107
|
+
getQueueLength(): number;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Check if a URL is a new tab page (about:blank or chrome://new-tab-page).
|
|
111
|
+
*/
|
|
112
|
+
export declare function is_new_tab_page(url: string): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Check if a URL matches a domain pattern. SECURITY CRITICAL.
|
|
115
|
+
*
|
|
116
|
+
* Supports optional glob patterns and schemes:
|
|
117
|
+
* - *.example.com will match sub.example.com and example.com
|
|
118
|
+
* - *google.com will match google.com, agoogle.com, and www.google.com
|
|
119
|
+
* - http*://example.com will match http://example.com, https://example.com
|
|
120
|
+
* - chrome-extension://* will match chrome-extension://aaaaaaaaaaaa and chrome-extension://bbbbbbbbbbbbb
|
|
121
|
+
*
|
|
122
|
+
* When no scheme is specified, https is used by default for security.
|
|
123
|
+
* For example, 'example.com' will match 'https://example.com' but not 'http://example.com'.
|
|
124
|
+
*
|
|
125
|
+
* Note: New tab pages (about:blank, chrome://new-tab-page) must be handled at the callsite, not inside this function.
|
|
126
|
+
*/
|
|
127
|
+
export declare function match_url_with_domain_pattern(url: string, domain_pattern: string, log_warnings?: boolean): boolean;
|
|
128
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import readline from 'node:readline';
|
|
7
|
+
import { stderr } from 'node:process';
|
|
8
|
+
import { performance } from 'node:perf_hooks';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { config as loadEnv } from 'dotenv';
|
|
11
|
+
import { createLogger } from './logging-config.js';
|
|
12
|
+
loadEnv();
|
|
13
|
+
const logger = createLogger('browser_use.utils');
|
|
14
|
+
let _exiting = false;
|
|
15
|
+
export class SignalHandler {
|
|
16
|
+
loop = null;
|
|
17
|
+
pause_callback;
|
|
18
|
+
resume_callback;
|
|
19
|
+
custom_exit_callback;
|
|
20
|
+
exit_on_second_int;
|
|
21
|
+
interruptible_task_patterns;
|
|
22
|
+
is_windows;
|
|
23
|
+
ctrl_c_pressed = false;
|
|
24
|
+
waiting_for_input = false;
|
|
25
|
+
bound_sigint = this.sigint_handler.bind(this);
|
|
26
|
+
bound_sigterm = this.sigterm_handler.bind(this);
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.pause_callback = options.pause_callback;
|
|
29
|
+
this.resume_callback = options.resume_callback;
|
|
30
|
+
this.custom_exit_callback = options.custom_exit_callback;
|
|
31
|
+
this.exit_on_second_int = options.exit_on_second_int ?? true;
|
|
32
|
+
this.interruptible_task_patterns = options.interruptible_task_patterns ?? [
|
|
33
|
+
'step',
|
|
34
|
+
'multi_act',
|
|
35
|
+
'get_next_action',
|
|
36
|
+
];
|
|
37
|
+
this.is_windows = os.platform() === 'win32';
|
|
38
|
+
}
|
|
39
|
+
register() {
|
|
40
|
+
process.on('SIGINT', this.bound_sigint);
|
|
41
|
+
process.on('SIGTERM', this.bound_sigterm);
|
|
42
|
+
}
|
|
43
|
+
unregister() {
|
|
44
|
+
process.off('SIGINT', this.bound_sigint);
|
|
45
|
+
process.off('SIGTERM', this.bound_sigterm);
|
|
46
|
+
}
|
|
47
|
+
_handle_second_ctrl_c() {
|
|
48
|
+
if (!_exiting) {
|
|
49
|
+
_exiting = true;
|
|
50
|
+
if (this.custom_exit_callback) {
|
|
51
|
+
try {
|
|
52
|
+
this.custom_exit_callback();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
logger.error(`Error in exit callback: ${error.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
stderr.write('\n\n🛑 Got second Ctrl+C. Exiting immediately...\n');
|
|
60
|
+
stderr.write('\x1b[?25h\x1b[0m\x1b[?1l\x1b[?2004l\r');
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
_cancel_interruptible_tasks() {
|
|
64
|
+
// Node.js does not provide asyncio-style task cancellation.
|
|
65
|
+
// Users should manage their own interruptible work via pause/resume callbacks.
|
|
66
|
+
}
|
|
67
|
+
async wait_for_resume() {
|
|
68
|
+
this.waiting_for_input = true;
|
|
69
|
+
const green = '\x1b[32;1m';
|
|
70
|
+
const red = '\x1b[31m';
|
|
71
|
+
const blink = '\x1b[33;5m';
|
|
72
|
+
const unblink = '\x1b[0m';
|
|
73
|
+
const reset = '\x1b[0m';
|
|
74
|
+
stderr.write(`➡️ Press ${green}[Enter]${reset} to resume or ${red}[Ctrl+C]${reset} again to exit${blink}...${unblink} `);
|
|
75
|
+
await new Promise((resolve) => {
|
|
76
|
+
const rl = readline.createInterface({
|
|
77
|
+
input: process.stdin,
|
|
78
|
+
output: stderr,
|
|
79
|
+
});
|
|
80
|
+
const cleanup = () => {
|
|
81
|
+
this.waiting_for_input = false;
|
|
82
|
+
rl.close();
|
|
83
|
+
resolve();
|
|
84
|
+
};
|
|
85
|
+
rl.once('line', () => {
|
|
86
|
+
if (this.resume_callback) {
|
|
87
|
+
try {
|
|
88
|
+
this.resume_callback();
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
logger.error(`Error in resume callback: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
cleanup();
|
|
95
|
+
});
|
|
96
|
+
rl.once('SIGINT', () => {
|
|
97
|
+
this._handle_second_ctrl_c();
|
|
98
|
+
cleanup();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
reset() {
|
|
103
|
+
this.ctrl_c_pressed = false;
|
|
104
|
+
this.waiting_for_input = false;
|
|
105
|
+
}
|
|
106
|
+
sigint_handler() {
|
|
107
|
+
if (_exiting) {
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
if (this.ctrl_c_pressed) {
|
|
111
|
+
if (this.waiting_for_input) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (this.exit_on_second_int) {
|
|
115
|
+
this._handle_second_ctrl_c();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
this.ctrl_c_pressed = true;
|
|
119
|
+
this._cancel_interruptible_tasks();
|
|
120
|
+
if (this.pause_callback) {
|
|
121
|
+
try {
|
|
122
|
+
this.pause_callback();
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
logger.error(`Error in pause callback: ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
stderr.write('----------------------------------------------------------------------\n');
|
|
129
|
+
}
|
|
130
|
+
sigterm_handler() {
|
|
131
|
+
if (!_exiting) {
|
|
132
|
+
_exiting = true;
|
|
133
|
+
stderr.write('\n\n🛑 SIGTERM received. Exiting immediately...\n\n');
|
|
134
|
+
if (this.custom_exit_callback) {
|
|
135
|
+
this.custom_exit_callback();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const strip_hyphen = (value) => value.replace(/^-+|-+$/g, '').trim();
|
|
142
|
+
const pick_logger = (args) => {
|
|
143
|
+
if (args.length > 0) {
|
|
144
|
+
const candidate = args[0];
|
|
145
|
+
if (candidate && candidate.logger) {
|
|
146
|
+
return candidate.logger;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return logger;
|
|
150
|
+
};
|
|
151
|
+
export const time_execution_sync = (additional_text = '') => (func) => {
|
|
152
|
+
const label = strip_hyphen(additional_text);
|
|
153
|
+
const wrapper = function (...args) {
|
|
154
|
+
const start = performance.now();
|
|
155
|
+
const result = func.apply(this, args);
|
|
156
|
+
const execution_time = (performance.now() - start) / 1000;
|
|
157
|
+
if (execution_time > 0.25) {
|
|
158
|
+
pick_logger(args).debug(`⏳ ${label}() took ${execution_time.toFixed(2)}s`);
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
};
|
|
162
|
+
return wrapper;
|
|
163
|
+
};
|
|
164
|
+
export const time_execution_async = (additional_text = '') => (func) => {
|
|
165
|
+
const label = strip_hyphen(additional_text);
|
|
166
|
+
const wrapper = async function (...args) {
|
|
167
|
+
const start = performance.now();
|
|
168
|
+
const result = await func.apply(this, args);
|
|
169
|
+
const execution_time = (performance.now() - start) / 1000;
|
|
170
|
+
if (execution_time > 0.25) {
|
|
171
|
+
pick_logger(args).debug(`⏳ ${label}() took ${execution_time.toFixed(2)}s`);
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
};
|
|
175
|
+
return wrapper;
|
|
176
|
+
};
|
|
177
|
+
export const singleton = (cls) => {
|
|
178
|
+
let instance;
|
|
179
|
+
return (...args) => {
|
|
180
|
+
if (instance === undefined) {
|
|
181
|
+
instance = cls(...args);
|
|
182
|
+
}
|
|
183
|
+
return instance;
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
export const check_env_variables = (keys, predicate = (values) => values.every((value) => value.trim().length > 0)) => {
|
|
187
|
+
const values = keys.map((key) => process.env[key] ?? '');
|
|
188
|
+
return predicate(values);
|
|
189
|
+
};
|
|
190
|
+
export const is_unsafe_pattern = (pattern) => {
|
|
191
|
+
if (pattern.includes('://')) {
|
|
192
|
+
const [, ...rest] = pattern.split('://');
|
|
193
|
+
pattern = rest.join('://');
|
|
194
|
+
}
|
|
195
|
+
const bare_domain = pattern.replace('.*', '').replace('*.', '');
|
|
196
|
+
return bare_domain.includes('*');
|
|
197
|
+
};
|
|
198
|
+
export const merge_dicts = (a, b, path = []) => {
|
|
199
|
+
for (const key of Object.keys(b)) {
|
|
200
|
+
if (key in a) {
|
|
201
|
+
if (typeof a[key] === 'object' &&
|
|
202
|
+
!Array.isArray(a[key]) &&
|
|
203
|
+
typeof b[key] === 'object' &&
|
|
204
|
+
!Array.isArray(b[key])) {
|
|
205
|
+
merge_dicts(a[key], b[key], [...path, key]);
|
|
206
|
+
}
|
|
207
|
+
else if (Array.isArray(a[key]) && Array.isArray(b[key])) {
|
|
208
|
+
a[key] = [...a[key], ...b[key]];
|
|
209
|
+
}
|
|
210
|
+
else if (a[key] !== b[key]) {
|
|
211
|
+
throw new Error(`Conflict at ${[...path, key].join('.')}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
a[key] = b[key];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return a;
|
|
219
|
+
};
|
|
220
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
221
|
+
const __dirname = path.dirname(__filename);
|
|
222
|
+
const package_root = path.resolve(__dirname, '..');
|
|
223
|
+
let cached_version = null;
|
|
224
|
+
export const get_browser_use_version = () => {
|
|
225
|
+
if (cached_version) {
|
|
226
|
+
return cached_version;
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
const package_json = JSON.parse(fs.readFileSync(path.join(package_root, 'package.json'), 'utf-8'));
|
|
230
|
+
if (package_json?.version) {
|
|
231
|
+
const version = String(package_json.version);
|
|
232
|
+
cached_version = version;
|
|
233
|
+
process.env.LIBRARY_VERSION = version;
|
|
234
|
+
return version;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
logger.debug(`Error detecting browser-use version: ${error.message}`);
|
|
239
|
+
}
|
|
240
|
+
return 'unknown';
|
|
241
|
+
};
|
|
242
|
+
let cached_git_info;
|
|
243
|
+
export const get_git_info = () => {
|
|
244
|
+
if (cached_git_info !== undefined) {
|
|
245
|
+
return cached_git_info;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
const git_dir = path.join(package_root, '.git');
|
|
249
|
+
if (!fs.existsSync(git_dir)) {
|
|
250
|
+
cached_git_info = null;
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
const commit_hash = execSync('git rev-parse HEAD', {
|
|
254
|
+
cwd: package_root,
|
|
255
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
256
|
+
})
|
|
257
|
+
.toString()
|
|
258
|
+
.trim();
|
|
259
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
260
|
+
cwd: package_root,
|
|
261
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
262
|
+
})
|
|
263
|
+
.toString()
|
|
264
|
+
.trim();
|
|
265
|
+
const remote_url = execSync('git config --get remote.origin.url', {
|
|
266
|
+
cwd: package_root,
|
|
267
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
268
|
+
})
|
|
269
|
+
.toString()
|
|
270
|
+
.trim();
|
|
271
|
+
const commit_timestamp = execSync('git show -s --format=%ci HEAD', {
|
|
272
|
+
cwd: package_root,
|
|
273
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
274
|
+
})
|
|
275
|
+
.toString()
|
|
276
|
+
.trim();
|
|
277
|
+
cached_git_info = { commit_hash, branch, remote_url, commit_timestamp };
|
|
278
|
+
return cached_git_info;
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
logger.debug(`Error getting git info: ${error.message}`);
|
|
282
|
+
cached_git_info = null;
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
export const _log_pretty_path = (input) => {
|
|
287
|
+
if (!input) {
|
|
288
|
+
return '';
|
|
289
|
+
}
|
|
290
|
+
if (typeof input !== 'string') {
|
|
291
|
+
return `<${input.constructor?.name || typeof input}>`;
|
|
292
|
+
}
|
|
293
|
+
const normalized = input.trim();
|
|
294
|
+
if (!normalized) {
|
|
295
|
+
return '';
|
|
296
|
+
}
|
|
297
|
+
let pretty_path = normalized.replace(os.homedir(), '~');
|
|
298
|
+
pretty_path = pretty_path.replace(process.cwd(), '.');
|
|
299
|
+
return pretty_path.includes(' ') ? `"${pretty_path}"` : pretty_path;
|
|
300
|
+
};
|
|
301
|
+
export const _log_pretty_url = (value, max_len = 22) => {
|
|
302
|
+
let sanitized = value
|
|
303
|
+
.replace('https://', '')
|
|
304
|
+
.replace('http://', '')
|
|
305
|
+
.replace('www.', '');
|
|
306
|
+
if (max_len !== null && sanitized.length > max_len) {
|
|
307
|
+
sanitized = `${sanitized.slice(0, max_len)}…`;
|
|
308
|
+
}
|
|
309
|
+
return sanitized;
|
|
310
|
+
};
|
|
311
|
+
export const log_pretty_path = _log_pretty_path;
|
|
312
|
+
export const log_pretty_url = _log_pretty_url;
|
|
313
|
+
export const uuid7str = () => {
|
|
314
|
+
const timestamp = Buffer.alloc(6);
|
|
315
|
+
const now = Date.now();
|
|
316
|
+
timestamp.writeUIntBE(now, 0, 6);
|
|
317
|
+
const random = crypto.randomBytes(10);
|
|
318
|
+
const bytes = Buffer.concat([timestamp, random]);
|
|
319
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x70;
|
|
320
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
321
|
+
const hex = bytes.toString('hex');
|
|
322
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* Retry an async function with configurable attempts and delays
|
|
326
|
+
* Implements exponential backoff with jitter
|
|
327
|
+
*
|
|
328
|
+
* @param fn - The async function to retry
|
|
329
|
+
* @param options - Retry configuration
|
|
330
|
+
* @returns The result of the function
|
|
331
|
+
* @throws The last error if all retries fail
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* const result = await retryAsync(
|
|
335
|
+
* async () => await fetchData(),
|
|
336
|
+
* { maxAttempts: 3, delayMs: 1000, backoffMultiplier: 2 }
|
|
337
|
+
* );
|
|
338
|
+
*/
|
|
339
|
+
export async function retryAsync(fn, options = {}) {
|
|
340
|
+
const { maxAttempts = 3, delayMs = 1000, backoffMultiplier = 1, maxDelayMs = 30000, shouldRetry = () => true, onRetry, } = options;
|
|
341
|
+
let lastError = null;
|
|
342
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
343
|
+
try {
|
|
344
|
+
return await fn();
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
lastError = error;
|
|
348
|
+
// Check if we should retry
|
|
349
|
+
const isLastAttempt = attempt === maxAttempts;
|
|
350
|
+
if (isLastAttempt || !shouldRetry(lastError, attempt)) {
|
|
351
|
+
throw lastError;
|
|
352
|
+
}
|
|
353
|
+
// Calculate delay with exponential backoff and jitter
|
|
354
|
+
const baseDelay = delayMs * Math.pow(backoffMultiplier, attempt - 1);
|
|
355
|
+
const jitter = Math.random() * 0.3 * baseDelay; // Add up to 30% jitter
|
|
356
|
+
const nextDelay = Math.min(baseDelay + jitter, maxDelayMs);
|
|
357
|
+
// Notify about retry
|
|
358
|
+
if (onRetry) {
|
|
359
|
+
onRetry(lastError, attempt, nextDelay);
|
|
360
|
+
}
|
|
361
|
+
// Wait before next attempt
|
|
362
|
+
await new Promise((resolve) => setTimeout(resolve, nextDelay));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// This should never be reached, but TypeScript requires it
|
|
366
|
+
throw lastError || new Error('Retry failed with unknown error');
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Create a semaphore for limiting concurrent operations
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* const semaphore = createSemaphore(3); // Allow max 3 concurrent operations
|
|
373
|
+
* await semaphore.acquire();
|
|
374
|
+
* try {
|
|
375
|
+
* await doWork();
|
|
376
|
+
* } finally {
|
|
377
|
+
* semaphore.release();
|
|
378
|
+
* }
|
|
379
|
+
*/
|
|
380
|
+
export function createSemaphore(maxConcurrent) {
|
|
381
|
+
let activeCount = 0;
|
|
382
|
+
const queue = [];
|
|
383
|
+
return {
|
|
384
|
+
/**
|
|
385
|
+
* Acquire a semaphore slot
|
|
386
|
+
* Waits if max concurrent operations are already running
|
|
387
|
+
*/
|
|
388
|
+
async acquire() {
|
|
389
|
+
if (activeCount < maxConcurrent) {
|
|
390
|
+
activeCount++;
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
await new Promise((resolve) => {
|
|
394
|
+
queue.push(resolve);
|
|
395
|
+
});
|
|
396
|
+
},
|
|
397
|
+
/**
|
|
398
|
+
* Release a semaphore slot
|
|
399
|
+
* Allows next queued operation to proceed
|
|
400
|
+
*/
|
|
401
|
+
release() {
|
|
402
|
+
const next = queue.shift();
|
|
403
|
+
if (next) {
|
|
404
|
+
next();
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
activeCount--;
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
/**
|
|
411
|
+
* Get current active count
|
|
412
|
+
*/
|
|
413
|
+
getActiveCount() {
|
|
414
|
+
return activeCount;
|
|
415
|
+
},
|
|
416
|
+
/**
|
|
417
|
+
* Get queue length
|
|
418
|
+
*/
|
|
419
|
+
getQueueLength() {
|
|
420
|
+
return queue.length;
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Check if a URL is a new tab page (about:blank or chrome://new-tab-page).
|
|
426
|
+
*/
|
|
427
|
+
export function is_new_tab_page(url) {
|
|
428
|
+
return (url === 'about:blank' ||
|
|
429
|
+
url === 'chrome://new-tab-page/' ||
|
|
430
|
+
url === 'chrome://new-tab-page');
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Check if a URL matches a domain pattern. SECURITY CRITICAL.
|
|
434
|
+
*
|
|
435
|
+
* Supports optional glob patterns and schemes:
|
|
436
|
+
* - *.example.com will match sub.example.com and example.com
|
|
437
|
+
* - *google.com will match google.com, agoogle.com, and www.google.com
|
|
438
|
+
* - http*://example.com will match http://example.com, https://example.com
|
|
439
|
+
* - chrome-extension://* will match chrome-extension://aaaaaaaaaaaa and chrome-extension://bbbbbbbbbbbbb
|
|
440
|
+
*
|
|
441
|
+
* When no scheme is specified, https is used by default for security.
|
|
442
|
+
* For example, 'example.com' will match 'https://example.com' but not 'http://example.com'.
|
|
443
|
+
*
|
|
444
|
+
* Note: New tab pages (about:blank, chrome://new-tab-page) must be handled at the callsite, not inside this function.
|
|
445
|
+
*/
|
|
446
|
+
export function match_url_with_domain_pattern(url, domain_pattern, log_warnings = false) {
|
|
447
|
+
try {
|
|
448
|
+
// Note: new tab pages should be handled at the callsite, not here
|
|
449
|
+
if (is_new_tab_page(url)) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
const parsed_url = new URL(url);
|
|
453
|
+
// Extract only the hostname and scheme components
|
|
454
|
+
const scheme = parsed_url.protocol.replace(':', '').toLowerCase();
|
|
455
|
+
const domain = parsed_url.hostname.toLowerCase();
|
|
456
|
+
if (!scheme || !domain) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
// Normalize the domain pattern
|
|
460
|
+
const normalizedPattern = domain_pattern.toLowerCase();
|
|
461
|
+
// Handle pattern with scheme
|
|
462
|
+
let pattern_scheme;
|
|
463
|
+
let pattern_domain;
|
|
464
|
+
if (normalizedPattern.includes('://')) {
|
|
465
|
+
const parts = normalizedPattern.split('://');
|
|
466
|
+
pattern_scheme = parts[0];
|
|
467
|
+
pattern_domain = parts[1];
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
pattern_scheme = 'https'; // Default to matching only https for security
|
|
471
|
+
pattern_domain = normalizedPattern;
|
|
472
|
+
}
|
|
473
|
+
// Handle port in pattern (we strip ports from patterns since we already extracted only the hostname from the URL)
|
|
474
|
+
if (pattern_domain.includes(':') && !pattern_domain.startsWith(':')) {
|
|
475
|
+
pattern_domain = pattern_domain.split(':')[0];
|
|
476
|
+
}
|
|
477
|
+
// If scheme doesn't match using minimatch, return false
|
|
478
|
+
const minimatch = require('minimatch');
|
|
479
|
+
if (!minimatch(scheme, pattern_scheme)) {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
// Check for exact match
|
|
483
|
+
if (pattern_domain === '*' || domain === pattern_domain) {
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
// Handle glob patterns
|
|
487
|
+
if (pattern_domain.includes('*')) {
|
|
488
|
+
// Check for unsafe glob patterns
|
|
489
|
+
// First, check for patterns like *.*.domain which are unsafe
|
|
490
|
+
if ((pattern_domain.match(/\*\./g) || []).length > 1 ||
|
|
491
|
+
(pattern_domain.match(/\.\*/g) || []).length > 1) {
|
|
492
|
+
if (log_warnings) {
|
|
493
|
+
console.error(`⛔️ Multiple wildcards in pattern=[${domain_pattern}] are not supported`);
|
|
494
|
+
}
|
|
495
|
+
return false; // Don't match unsafe patterns
|
|
496
|
+
}
|
|
497
|
+
// Check for wildcards in TLD part (example.*)
|
|
498
|
+
if (pattern_domain.endsWith('.*')) {
|
|
499
|
+
if (log_warnings) {
|
|
500
|
+
console.error(`⛔️ Wildcard TLDs like in pattern=[${domain_pattern}] are not supported for security`);
|
|
501
|
+
}
|
|
502
|
+
return false; // Don't match unsafe patterns
|
|
503
|
+
}
|
|
504
|
+
// Then check for embedded wildcards
|
|
505
|
+
const bare_domain = pattern_domain.replace('*.', '');
|
|
506
|
+
if (bare_domain.includes('*')) {
|
|
507
|
+
if (log_warnings) {
|
|
508
|
+
console.error(`⛔️ Only *.domain style patterns are supported, ignoring pattern=[${domain_pattern}]`);
|
|
509
|
+
}
|
|
510
|
+
return false; // Don't match unsafe patterns
|
|
511
|
+
}
|
|
512
|
+
// Special handling so that *.google.com also matches bare google.com
|
|
513
|
+
if (pattern_domain.startsWith('*.')) {
|
|
514
|
+
const base = pattern_domain.slice(2); // Remove '*.'
|
|
515
|
+
if (domain === base || domain.endsWith('.' + base)) {
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// Use minimatch for pattern matching
|
|
520
|
+
return minimatch(domain, pattern_domain);
|
|
521
|
+
}
|
|
522
|
+
// No match
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
// Invalid URL or pattern
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
}
|