browser-use 0.2.0 → 0.3.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.
Files changed (259) hide show
  1. package/README.md +295 -686
  2. package/dist/actor/element.d.ts +19 -0
  3. package/dist/actor/element.js +46 -0
  4. package/dist/actor/index.d.ts +4 -0
  5. package/dist/actor/index.js +4 -0
  6. package/dist/actor/mouse.d.ts +19 -0
  7. package/dist/actor/mouse.js +39 -0
  8. package/dist/actor/page.d.ts +29 -0
  9. package/dist/actor/page.js +88 -0
  10. package/dist/actor/utils.d.ts +4 -0
  11. package/dist/actor/utils.js +35 -0
  12. package/dist/agent/cloud-events.d.ts +18 -0
  13. package/dist/agent/cloud-events.js +65 -2
  14. package/dist/agent/gif.d.ts +1 -0
  15. package/dist/agent/gif.js +24 -2
  16. package/dist/agent/judge.d.ts +17 -0
  17. package/dist/agent/judge.js +197 -0
  18. package/dist/agent/message-manager/service.d.ts +12 -4
  19. package/dist/agent/message-manager/service.js +205 -39
  20. package/dist/agent/message-manager/utils.js +0 -1
  21. package/dist/agent/message-manager/views.d.ts +4 -0
  22. package/dist/agent/message-manager/views.js +11 -7
  23. package/dist/agent/prompts.d.ts +24 -3
  24. package/dist/agent/prompts.js +274 -59
  25. package/dist/agent/service.d.ts +99 -41
  26. package/dist/agent/service.js +2266 -472
  27. package/dist/agent/variable-detector.d.ts +12 -0
  28. package/dist/agent/variable-detector.js +211 -0
  29. package/dist/agent/views.d.ts +237 -18
  30. package/dist/agent/views.js +446 -33
  31. package/dist/browser/cloud/cloud.d.ts +20 -0
  32. package/dist/browser/cloud/cloud.js +129 -0
  33. package/dist/browser/cloud/index.d.ts +2 -0
  34. package/dist/browser/cloud/index.js +2 -0
  35. package/dist/browser/cloud/views.d.ts +41 -0
  36. package/dist/browser/cloud/views.js +35 -0
  37. package/dist/browser/events.d.ts +345 -0
  38. package/dist/browser/events.js +566 -0
  39. package/dist/browser/extensions.js +17 -17
  40. package/dist/browser/index.d.ts +4 -0
  41. package/dist/browser/index.js +4 -0
  42. package/dist/browser/profile.d.ts +8 -2
  43. package/dist/browser/profile.js +79 -12
  44. package/dist/browser/session-manager.d.ts +85 -0
  45. package/dist/browser/session-manager.js +208 -0
  46. package/dist/browser/session.d.ts +100 -8
  47. package/dist/browser/session.js +1097 -58
  48. package/dist/browser/types.d.ts +0 -2
  49. package/dist/browser/views.d.ts +39 -0
  50. package/dist/browser/views.js +32 -0
  51. package/dist/browser/watchdogs/aboutblank-watchdog.d.ts +12 -0
  52. package/dist/browser/watchdogs/aboutblank-watchdog.js +131 -0
  53. package/dist/browser/watchdogs/base.d.ts +21 -0
  54. package/dist/browser/watchdogs/base.js +81 -0
  55. package/dist/browser/watchdogs/cdp-session-watchdog.d.ts +14 -0
  56. package/dist/browser/watchdogs/cdp-session-watchdog.js +177 -0
  57. package/dist/browser/watchdogs/crash-watchdog.d.ts +38 -0
  58. package/dist/browser/watchdogs/crash-watchdog.js +296 -0
  59. package/dist/browser/watchdogs/default-action-watchdog.d.ts +49 -0
  60. package/dist/browser/watchdogs/default-action-watchdog.js +212 -0
  61. package/dist/browser/watchdogs/dom-watchdog.d.ts +8 -0
  62. package/dist/browser/watchdogs/dom-watchdog.js +31 -0
  63. package/dist/browser/watchdogs/downloads-watchdog.d.ts +77 -0
  64. package/dist/browser/watchdogs/downloads-watchdog.js +409 -0
  65. package/dist/browser/watchdogs/har-recording-watchdog.d.ts +19 -0
  66. package/dist/browser/watchdogs/har-recording-watchdog.js +317 -0
  67. package/dist/browser/watchdogs/index.d.ts +15 -0
  68. package/dist/browser/watchdogs/index.js +15 -0
  69. package/dist/browser/watchdogs/local-browser-watchdog.d.ts +10 -0
  70. package/dist/browser/watchdogs/local-browser-watchdog.js +32 -0
  71. package/dist/browser/watchdogs/permissions-watchdog.d.ts +8 -0
  72. package/dist/browser/watchdogs/permissions-watchdog.js +73 -0
  73. package/dist/browser/watchdogs/popups-watchdog.d.ts +13 -0
  74. package/dist/browser/watchdogs/popups-watchdog.js +77 -0
  75. package/dist/browser/watchdogs/recording-watchdog.d.ts +27 -0
  76. package/dist/browser/watchdogs/recording-watchdog.js +249 -0
  77. package/dist/browser/watchdogs/screenshot-watchdog.d.ts +6 -0
  78. package/dist/browser/watchdogs/screenshot-watchdog.js +13 -0
  79. package/dist/browser/watchdogs/security-watchdog.d.ts +10 -0
  80. package/dist/browser/watchdogs/security-watchdog.js +84 -0
  81. package/dist/browser/watchdogs/storage-state-watchdog.d.ts +24 -0
  82. package/dist/browser/watchdogs/storage-state-watchdog.js +288 -0
  83. package/dist/cli.d.ts +7 -2
  84. package/dist/cli.js +182 -25
  85. package/dist/code-use/formatting.d.ts +3 -0
  86. package/dist/code-use/formatting.js +18 -0
  87. package/dist/code-use/index.d.ts +6 -0
  88. package/dist/code-use/index.js +6 -0
  89. package/dist/code-use/namespace.d.ts +5 -0
  90. package/dist/code-use/namespace.js +81 -0
  91. package/dist/code-use/notebook-export.d.ts +3 -0
  92. package/dist/code-use/notebook-export.js +56 -0
  93. package/dist/code-use/service.d.ts +24 -0
  94. package/dist/code-use/service.js +104 -0
  95. package/dist/code-use/utils.d.ts +4 -0
  96. package/dist/code-use/utils.js +98 -0
  97. package/dist/code-use/views.d.ts +108 -0
  98. package/dist/code-use/views.js +165 -0
  99. package/dist/config.d.ts +13 -0
  100. package/dist/config.js +69 -3
  101. package/dist/controller/registry/service.d.ts +10 -1
  102. package/dist/controller/registry/service.js +266 -10
  103. package/dist/controller/registry/views.d.ts +4 -1
  104. package/dist/controller/registry/views.js +25 -2
  105. package/dist/controller/service.d.ts +10 -1
  106. package/dist/controller/service.js +1807 -268
  107. package/dist/controller/views.d.ts +78 -155
  108. package/dist/controller/views.js +61 -12
  109. package/dist/dom/history-tree-processor/service.d.ts +5 -0
  110. package/dist/dom/history-tree-processor/service.js +169 -14
  111. package/dist/dom/history-tree-processor/view.d.ts +7 -1
  112. package/dist/dom/history-tree-processor/view.js +10 -1
  113. package/dist/dom/markdown-extractor.d.ts +37 -0
  114. package/dist/dom/markdown-extractor.js +345 -0
  115. package/dist/dom/service.d.ts +3 -1
  116. package/dist/dom/service.js +76 -0
  117. package/dist/dom/views.d.ts +1 -0
  118. package/dist/dom/views.js +45 -0
  119. package/dist/event-bus.d.ts +107 -7
  120. package/dist/event-bus.js +313 -10
  121. package/dist/exceptions.d.ts +0 -3
  122. package/dist/exceptions.js +0 -7
  123. package/dist/filesystem/file-system.d.ts +18 -0
  124. package/dist/filesystem/file-system.js +503 -42
  125. package/dist/index.d.ts +7 -0
  126. package/dist/index.js +6 -0
  127. package/dist/integrations/gmail/actions.d.ts +3 -3
  128. package/dist/integrations/gmail/actions.js +4 -4
  129. package/dist/llm/anthropic/chat.d.ts +18 -1
  130. package/dist/llm/anthropic/chat.js +123 -55
  131. package/dist/llm/anthropic/serializer.d.ts +2 -0
  132. package/dist/llm/anthropic/serializer.js +81 -9
  133. package/dist/llm/aws/chat-anthropic.d.ts +17 -0
  134. package/dist/llm/aws/chat-anthropic.js +126 -26
  135. package/dist/llm/aws/chat-bedrock.d.ts +28 -1
  136. package/dist/llm/aws/chat-bedrock.js +161 -34
  137. package/dist/llm/aws/serializer.d.ts +13 -1
  138. package/dist/llm/aws/serializer.js +56 -17
  139. package/dist/llm/azure/chat.d.ts +53 -2
  140. package/dist/llm/azure/chat.js +366 -54
  141. package/dist/llm/base.d.ts +2 -0
  142. package/dist/llm/browser-use/chat.d.ts +40 -0
  143. package/dist/llm/browser-use/chat.js +305 -0
  144. package/dist/llm/browser-use/index.d.ts +1 -0
  145. package/dist/llm/browser-use/index.js +1 -0
  146. package/dist/llm/cerebras/chat.d.ts +39 -0
  147. package/dist/llm/cerebras/chat.js +178 -0
  148. package/dist/llm/cerebras/index.d.ts +2 -0
  149. package/dist/llm/cerebras/index.js +2 -0
  150. package/dist/llm/cerebras/serializer.d.ts +7 -0
  151. package/dist/llm/cerebras/serializer.js +82 -0
  152. package/dist/llm/deepseek/chat.d.ts +19 -2
  153. package/dist/llm/deepseek/chat.js +138 -25
  154. package/dist/llm/google/chat.d.ts +46 -2
  155. package/dist/llm/google/chat.js +267 -64
  156. package/dist/llm/google/serializer.d.ts +9 -1
  157. package/dist/llm/google/serializer.js +141 -34
  158. package/dist/llm/groq/chat.d.ts +21 -2
  159. package/dist/llm/groq/chat.js +125 -26
  160. package/dist/llm/groq/parser.js +3 -1
  161. package/dist/llm/mistral/chat.d.ts +43 -0
  162. package/dist/llm/mistral/chat.js +154 -0
  163. package/dist/llm/mistral/index.d.ts +2 -0
  164. package/dist/llm/mistral/index.js +2 -0
  165. package/dist/llm/mistral/schema.d.ts +8 -0
  166. package/dist/llm/mistral/schema.js +27 -0
  167. package/dist/llm/models.d.ts +2 -0
  168. package/dist/llm/models.js +317 -0
  169. package/dist/llm/ollama/chat.d.ts +13 -1
  170. package/dist/llm/ollama/chat.js +110 -19
  171. package/dist/llm/ollama/serializer.d.ts +1 -0
  172. package/dist/llm/ollama/serializer.js +34 -12
  173. package/dist/llm/openai/chat.d.ts +16 -0
  174. package/dist/llm/openai/chat.js +94 -44
  175. package/dist/llm/openai/like.d.ts +5 -3
  176. package/dist/llm/openai/like.js +7 -3
  177. package/dist/llm/openai/responses-serializer.d.ts +18 -0
  178. package/dist/llm/openai/responses-serializer.js +72 -0
  179. package/dist/llm/openrouter/chat.d.ts +28 -2
  180. package/dist/llm/openrouter/chat.js +115 -29
  181. package/dist/llm/schema.d.ts +11 -1
  182. package/dist/llm/schema.js +81 -1
  183. package/dist/llm/vercel/chat.d.ts +50 -0
  184. package/dist/llm/vercel/chat.js +276 -0
  185. package/dist/llm/vercel/index.d.ts +1 -0
  186. package/dist/llm/vercel/index.js +1 -0
  187. package/dist/llm/vercel/serializer.d.ts +5 -0
  188. package/dist/llm/vercel/serializer.js +7 -0
  189. package/dist/llm/views.d.ts +2 -1
  190. package/dist/llm/views.js +3 -1
  191. package/dist/logging-config.d.ts +2 -0
  192. package/dist/logging-config.js +82 -29
  193. package/dist/mcp/client.d.ts +10 -5
  194. package/dist/mcp/client.js +14 -9
  195. package/dist/mcp/controller.d.ts +42 -3
  196. package/dist/mcp/controller.js +56 -31
  197. package/dist/mcp/server.d.ts +14 -0
  198. package/dist/mcp/server.js +255 -52
  199. package/dist/observability.js +10 -4
  200. package/dist/sandbox/index.d.ts +2 -0
  201. package/dist/sandbox/index.js +2 -0
  202. package/dist/sandbox/sandbox.d.ts +19 -0
  203. package/dist/sandbox/sandbox.js +140 -0
  204. package/dist/sandbox/views.d.ts +67 -0
  205. package/dist/sandbox/views.js +121 -0
  206. package/dist/skill-cli/index.d.ts +3 -0
  207. package/dist/skill-cli/index.js +3 -0
  208. package/dist/skill-cli/protocol.d.ts +30 -0
  209. package/dist/skill-cli/protocol.js +48 -0
  210. package/dist/skill-cli/server.d.ts +11 -0
  211. package/dist/skill-cli/server.js +85 -0
  212. package/dist/skill-cli/sessions.d.ts +24 -0
  213. package/dist/skill-cli/sessions.js +47 -0
  214. package/dist/skills/index.d.ts +3 -0
  215. package/dist/skills/index.js +3 -0
  216. package/dist/skills/service.d.ts +27 -0
  217. package/dist/skills/service.js +266 -0
  218. package/dist/skills/utils.d.ts +6 -0
  219. package/dist/skills/utils.js +53 -0
  220. package/dist/skills/views.d.ts +40 -0
  221. package/dist/skills/views.js +10 -0
  222. package/dist/sync/auth.js +8 -3
  223. package/dist/sync/service.d.ts +6 -6
  224. package/dist/sync/service.js +54 -89
  225. package/dist/telemetry/views.d.ts +20 -6
  226. package/dist/telemetry/views.js +23 -5
  227. package/dist/tokens/custom-pricing.d.ts +2 -0
  228. package/dist/tokens/custom-pricing.js +22 -0
  229. package/dist/tokens/index.d.ts +2 -0
  230. package/dist/tokens/index.js +2 -0
  231. package/dist/tokens/mappings.d.ts +1 -0
  232. package/dist/tokens/mappings.js +3 -0
  233. package/dist/tokens/service.js +27 -8
  234. package/dist/tools/extraction/index.d.ts +2 -0
  235. package/dist/tools/extraction/index.js +2 -0
  236. package/dist/tools/extraction/schema-utils.d.ts +6 -0
  237. package/dist/tools/extraction/schema-utils.js +237 -0
  238. package/dist/tools/extraction/views.d.ts +7 -0
  239. package/dist/tools/index.d.ts +5 -0
  240. package/dist/tools/index.js +5 -0
  241. package/dist/tools/registry/index.d.ts +2 -0
  242. package/dist/tools/registry/index.js +2 -0
  243. package/dist/tools/registry/service.d.ts +1 -0
  244. package/dist/tools/registry/service.js +1 -0
  245. package/dist/tools/registry/views.d.ts +1 -0
  246. package/dist/tools/registry/views.js +1 -0
  247. package/dist/tools/service.d.ts +2 -0
  248. package/dist/tools/service.js +1 -0
  249. package/dist/tools/utils.d.ts +2 -0
  250. package/dist/tools/utils.js +57 -0
  251. package/dist/tools/views.d.ts +1 -0
  252. package/dist/tools/views.js +1 -0
  253. package/dist/utils.d.ts +10 -1
  254. package/dist/utils.js +70 -3
  255. package/package.json +87 -26
  256. package/dist/dom/playground/process-dom.js +0 -5
  257. package/dist/dom/playground/test-accessibility.d.ts +0 -44
  258. package/dist/dom/playground/test-accessibility.js +0 -111
  259. /package/dist/{dom/playground/process-dom.d.ts → tools/extraction/views.js} +0 -0
@@ -0,0 +1,266 @@
1
+ import { CONFIG } from '../config.js';
2
+ import { createLogger } from '../logging-config.js';
3
+ import { build_skill_parameters_schema, get_skill_slug } from './utils.js';
4
+ import { MissingCookieException, } from './views.js';
5
+ const logger = createLogger('browser_use.skills');
6
+ const toSkillParameter = (raw) => {
7
+ if (!raw || typeof raw !== 'object') {
8
+ return null;
9
+ }
10
+ const record = raw;
11
+ const name = typeof record.name === 'string' ? record.name.trim() : '';
12
+ const type = typeof record.type === 'string' ? record.type.trim() : '';
13
+ if (!name || !type) {
14
+ return null;
15
+ }
16
+ if (type !== 'string' &&
17
+ type !== 'number' &&
18
+ type !== 'boolean' &&
19
+ type !== 'object' &&
20
+ type !== 'array' &&
21
+ type !== 'cookie') {
22
+ return null;
23
+ }
24
+ return {
25
+ name,
26
+ type,
27
+ required: typeof record.required === 'boolean' ? record.required : undefined,
28
+ description: typeof record.description === 'string' ? record.description : undefined,
29
+ };
30
+ };
31
+ const toSkillDefinition = (raw) => {
32
+ if (!raw || typeof raw !== 'object') {
33
+ return null;
34
+ }
35
+ const record = raw;
36
+ const id = typeof record.id === 'string' ? record.id.trim() : '';
37
+ const title = typeof record.title === 'string' ? record.title.trim() : '';
38
+ const description = typeof record.description === 'string' ? record.description.trim() : '';
39
+ if (!id || !title) {
40
+ return null;
41
+ }
42
+ const parametersRaw = Array.isArray(record.parameters)
43
+ ? record.parameters
44
+ : [];
45
+ const parameters = parametersRaw
46
+ .map((entry) => toSkillParameter(entry))
47
+ .filter((entry) => entry != null);
48
+ const output_schema = record.output_schema && typeof record.output_schema === 'object'
49
+ ? record.output_schema
50
+ : null;
51
+ return {
52
+ id,
53
+ title,
54
+ description,
55
+ parameters,
56
+ output_schema,
57
+ };
58
+ };
59
+ export class CloudSkillService {
60
+ skill_ids;
61
+ api_key;
62
+ base_url;
63
+ fetch_impl;
64
+ initialized = false;
65
+ skills = new Map();
66
+ constructor(options) {
67
+ this.skill_ids = options.skill_ids;
68
+ this.api_key = options.api_key ?? process.env.BROWSER_USE_API_KEY ?? '';
69
+ this.base_url = options.base_url ?? CONFIG.BROWSER_USE_CLOUD_API_URL;
70
+ this.fetch_impl = options.fetch_impl ?? fetch;
71
+ if (!this.api_key) {
72
+ throw new Error('BROWSER_USE_API_KEY environment variable is not set');
73
+ }
74
+ }
75
+ async requestJson(path, init = {}) {
76
+ const response = await this.fetch_impl(`${this.base_url}${path}`, {
77
+ ...init,
78
+ headers: {
79
+ Authorization: `Bearer ${this.api_key}`,
80
+ ...(init.body ? { 'Content-Type': 'application/json' } : {}),
81
+ ...(init.headers ?? {}),
82
+ },
83
+ });
84
+ let payload;
85
+ try {
86
+ payload = await response.json();
87
+ }
88
+ catch {
89
+ payload = null;
90
+ }
91
+ if (!response.ok) {
92
+ const details = payload && typeof payload === 'object'
93
+ ? JSON.stringify(payload)
94
+ : String(payload ?? '');
95
+ throw new Error(`Skill API request failed (${response.status}): ${details}`);
96
+ }
97
+ return payload;
98
+ }
99
+ async listSkillsPage(page_number, page_size) {
100
+ const query = new URLSearchParams({
101
+ page_size: String(page_size),
102
+ page_number: String(page_number),
103
+ is_enabled: 'true',
104
+ });
105
+ const payload = (await this.requestJson(`/api/v1/skills?${query.toString()}`));
106
+ const items = Array.isArray(payload?.items) ? payload.items : [];
107
+ const skills = [];
108
+ for (const item of items) {
109
+ const status = item && typeof item === 'object'
110
+ ? item.status
111
+ : undefined;
112
+ if (typeof status === 'string' && status !== 'finished') {
113
+ continue;
114
+ }
115
+ const skill = toSkillDefinition(item);
116
+ if (skill) {
117
+ skills.push(skill);
118
+ }
119
+ }
120
+ return skills;
121
+ }
122
+ async ensureInitialized() {
123
+ if (this.initialized) {
124
+ return;
125
+ }
126
+ try {
127
+ const useWildcard = this.skill_ids.includes('*');
128
+ const requestedIds = new Set(this.skill_ids.filter((entry) => entry !== '*'));
129
+ const page_size = 100;
130
+ const max_pages = useWildcard ? 1 : 5;
131
+ const loaded = [];
132
+ let reachedPaginationLimit = true;
133
+ for (let page = 1; page <= max_pages; page += 1) {
134
+ const pageSkills = await this.listSkillsPage(page, page_size);
135
+ loaded.push(...pageSkills);
136
+ if (pageSkills.length < page_size) {
137
+ reachedPaginationLimit = false;
138
+ break;
139
+ }
140
+ if (!useWildcard && requestedIds.size > 0) {
141
+ const found = new Set(loaded.map((entry) => entry.id).filter((id) => requestedIds.has(id)));
142
+ if (found.size === requestedIds.size) {
143
+ reachedPaginationLimit = false;
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ if (useWildcard && loaded.length >= page_size) {
149
+ logger.warning('Wildcard "*" limited to first 100 skills. Specify explicit skill IDs if you need specific skills beyond this limit.');
150
+ }
151
+ if (!useWildcard && reachedPaginationLimit) {
152
+ logger.warning('Reached pagination limit (5 pages) before finding all requested skills');
153
+ }
154
+ const selected = useWildcard
155
+ ? loaded
156
+ : loaded.filter((entry) => requestedIds.has(entry.id));
157
+ if (!useWildcard && requestedIds.size > 0) {
158
+ const foundIds = new Set(selected.map((entry) => entry.id));
159
+ const missingIds = Array.from(requestedIds).filter((id) => !foundIds.has(id));
160
+ if (missingIds.length > 0) {
161
+ logger.warning(`Requested skills not found or not available: ${missingIds.join(', ')}`);
162
+ }
163
+ }
164
+ for (const skill of selected) {
165
+ this.skills.set(skill.id, skill);
166
+ }
167
+ this.initialized = true;
168
+ logger.info(`Loaded ${this.skills.size} skills${useWildcard ? ' (wildcard mode)' : ''}`);
169
+ }
170
+ catch (error) {
171
+ // Match Python semantics: avoid retry loops after an initialization failure.
172
+ this.initialized = true;
173
+ throw error;
174
+ }
175
+ }
176
+ async get_skill(skill_id) {
177
+ await this.ensureInitialized();
178
+ return this.skills.get(skill_id) ?? null;
179
+ }
180
+ async get_all_skills() {
181
+ await this.ensureInitialized();
182
+ return Array.from(this.skills.values());
183
+ }
184
+ async execute_skill(input) {
185
+ await this.ensureInitialized();
186
+ const skill = this.skills.get(input.skill_id);
187
+ if (!skill) {
188
+ throw new Error(`Skill ${input.skill_id} not found in cache. Available skills: ${Array.from(this.skills.keys()).join(', ')}`);
189
+ }
190
+ const cookieMap = new Map();
191
+ for (const cookie of input.cookies ?? []) {
192
+ if (!cookie?.name) {
193
+ continue;
194
+ }
195
+ cookieMap.set(cookie.name, cookie.value ?? '');
196
+ }
197
+ const payload = {
198
+ ...(input.parameters ?? {}),
199
+ };
200
+ for (const param of skill.parameters) {
201
+ if (param.type !== 'cookie') {
202
+ continue;
203
+ }
204
+ const required = param.required !== false;
205
+ if (required && !cookieMap.has(param.name)) {
206
+ throw new MissingCookieException(param.name, param.description || 'No description provided');
207
+ }
208
+ if (cookieMap.has(param.name)) {
209
+ payload[param.name] = cookieMap.get(param.name) ?? '';
210
+ }
211
+ }
212
+ const validator = build_skill_parameters_schema(skill.parameters, {
213
+ exclude_cookies: false,
214
+ });
215
+ const validated = validator.safeParse(payload);
216
+ if (!validated.success) {
217
+ throw new Error(`Parameter validation failed for skill ${skill.title}: ${validated.error.message}`);
218
+ }
219
+ try {
220
+ const response = (await this.requestJson(`/api/v1/skills/${encodeURIComponent(input.skill_id)}/execute`, {
221
+ method: 'POST',
222
+ body: JSON.stringify({ parameters: validated.data }),
223
+ }));
224
+ const success = response.success === true;
225
+ const result = response.result ?? response.output ?? response.data ?? null;
226
+ const error = typeof response.error === 'string' ? response.error : null;
227
+ const latency_ms = typeof response.latency_ms === 'number' ? response.latency_ms : null;
228
+ return {
229
+ success,
230
+ result,
231
+ error,
232
+ latency_ms,
233
+ };
234
+ }
235
+ catch (error) {
236
+ const errorText = error instanceof Error
237
+ ? `${error.name}: ${error.message}`
238
+ : String(error);
239
+ return {
240
+ success: false,
241
+ error: `Failed to execute skill: ${errorText}`,
242
+ };
243
+ }
244
+ }
245
+ async close() {
246
+ this.skills.clear();
247
+ this.initialized = false;
248
+ }
249
+ }
250
+ export const register_skills_as_actions = async (skills, registerAction) => {
251
+ for (const skill of skills) {
252
+ const slug = get_skill_slug(skill, skills);
253
+ const paramSchema = build_skill_parameters_schema(skill.parameters, {
254
+ exclude_cookies: true,
255
+ });
256
+ const description = `${skill.description} (Skill: "${skill.title}")`;
257
+ registerAction(slug, description, paramSchema, skill);
258
+ }
259
+ };
260
+ export const cookies_to_map = (cookies) => {
261
+ const map = new Map();
262
+ for (const cookie of cookies) {
263
+ map.set(cookie.name, cookie.value);
264
+ }
265
+ return map;
266
+ };
@@ -0,0 +1,6 @@
1
+ import { z } from 'zod';
2
+ import type { SkillDefinition, SkillParameterSchema } from './views.js';
3
+ export declare const get_skill_slug: (skill: SkillDefinition, all_skills: SkillDefinition[]) => string;
4
+ export declare const build_skill_parameters_schema: (parameters: SkillParameterSchema[], options?: {
5
+ exclude_cookies?: boolean;
6
+ }) => z.ZodObject<Record<string, z.ZodTypeAny>>;
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod';
2
+ const normalizeSlug = (title) => {
3
+ const cleaned = title
4
+ .toLowerCase()
5
+ .replace(/[^\w\s-]/g, '')
6
+ .replace(/[\s-]+/g, '_')
7
+ .replace(/^_+|_+$/g, '');
8
+ return cleaned || 'skill';
9
+ };
10
+ const normalizeTypeSchema = (param) => {
11
+ switch (param.type) {
12
+ case 'string':
13
+ case 'cookie':
14
+ return z.string();
15
+ case 'number':
16
+ return z.number();
17
+ case 'boolean':
18
+ return z.boolean();
19
+ case 'array':
20
+ return z.array(z.unknown());
21
+ case 'object':
22
+ return z.record(z.string(), z.unknown());
23
+ default:
24
+ return z.unknown();
25
+ }
26
+ };
27
+ export const get_skill_slug = (skill, all_skills) => {
28
+ const slug = normalizeSlug(skill.title);
29
+ const duplicateCount = all_skills.filter((entry) => {
30
+ return normalizeSlug(entry.title) === slug;
31
+ }).length;
32
+ if (duplicateCount > 1) {
33
+ return `${slug}_${skill.id.slice(0, 4)}`;
34
+ }
35
+ return slug;
36
+ };
37
+ export const build_skill_parameters_schema = (parameters, options = {}) => {
38
+ const { exclude_cookies = false } = options;
39
+ const shape = {};
40
+ for (const param of parameters) {
41
+ if (exclude_cookies && param.type === 'cookie') {
42
+ continue;
43
+ }
44
+ const description = typeof param.description === 'string' ? param.description.trim() : '';
45
+ const required = param.required !== false;
46
+ let schema = normalizeTypeSchema(param);
47
+ if (description) {
48
+ schema = schema.describe(description);
49
+ }
50
+ shape[param.name] = required ? schema : schema.optional();
51
+ }
52
+ return z.object(shape);
53
+ };
@@ -0,0 +1,40 @@
1
+ export type SkillParameterType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'cookie';
2
+ export interface SkillParameterSchema {
3
+ name: string;
4
+ type: SkillParameterType;
5
+ required?: boolean | null;
6
+ description?: string | null;
7
+ }
8
+ export interface SkillDefinition {
9
+ id: string;
10
+ title: string;
11
+ description: string;
12
+ parameters: SkillParameterSchema[];
13
+ output_schema?: Record<string, unknown> | null;
14
+ }
15
+ export interface SkillExecutionResult {
16
+ success: boolean;
17
+ result?: unknown;
18
+ error?: string | null;
19
+ latency_ms?: number | null;
20
+ }
21
+ export interface BrowserCookie {
22
+ name: string;
23
+ value: string;
24
+ }
25
+ export interface ExecuteSkillInput {
26
+ skill_id: string;
27
+ parameters: Record<string, unknown>;
28
+ cookies: BrowserCookie[];
29
+ }
30
+ export interface SkillService {
31
+ get_skill?(skill_id: string): Promise<SkillDefinition | null>;
32
+ get_all_skills(): Promise<SkillDefinition[]>;
33
+ execute_skill(input: ExecuteSkillInput): Promise<SkillExecutionResult>;
34
+ close?(): Promise<void> | void;
35
+ }
36
+ export declare class MissingCookieException extends Error {
37
+ cookie_name: string;
38
+ cookie_description: string;
39
+ constructor(cookie_name: string, cookie_description: string);
40
+ }
@@ -0,0 +1,10 @@
1
+ export class MissingCookieException extends Error {
2
+ cookie_name;
3
+ cookie_description;
4
+ constructor(cookie_name, cookie_description) {
5
+ super(`Missing required cookie '${cookie_name}': ${cookie_description}`);
6
+ this.name = 'MissingCookieException';
7
+ this.cookie_name = cookie_name;
8
+ this.cookie_description = cookie_description;
9
+ }
10
+ }
package/dist/sync/auth.js CHANGED
@@ -100,7 +100,7 @@ export class DeviceAuthClient {
100
100
  const response = await this.postForm('/api/v1/oauth/device/authorize', {
101
101
  client_id: this.clientId,
102
102
  scope: this.scope,
103
- agent_session_id: agent_session_id ?? undefined,
103
+ agent_session_id: agent_session_id ?? '',
104
104
  device_id: this.device_id,
105
105
  });
106
106
  return response.data;
@@ -160,7 +160,7 @@ export class DeviceAuthClient {
160
160
  const replaceHost = (value) => value?.replace(this.baseUrl, frontendBase);
161
161
  const verificationUri = replaceHost(deviceAuth.verification_uri);
162
162
  const verificationUriComplete = replaceHost(deviceAuth.verification_uri_complete);
163
- if (show_instructions) {
163
+ if (show_instructions && CONFIG.BROWSER_USE_CLOUD_SYNC) {
164
164
  const divider = '─'.repeat(terminalWidth());
165
165
  logger.info(divider);
166
166
  logger.info('🌐 View the details of this run in Browser Use Cloud:');
@@ -200,6 +200,11 @@ export class DeviceAuthClient {
200
200
  }
201
201
  clear_auth() {
202
202
  this.authConfig = { api_token: null, user_id: null, authorized_at: null };
203
- saveAuthConfig(this.authConfig);
203
+ try {
204
+ fs.unlinkSync(CLOUD_AUTH_PATH());
205
+ }
206
+ catch {
207
+ /* noop */
208
+ }
204
209
  }
205
210
  }
@@ -3,19 +3,19 @@ import { DeviceAuthClient } from './auth.js';
3
3
  export interface CloudSyncOptions {
4
4
  baseUrl?: string;
5
5
  enableAuth?: boolean;
6
+ allowSessionEventsForAuth?: boolean;
6
7
  }
7
8
  export declare class CloudSync {
8
9
  private readonly baseUrl;
9
- private readonly enableAuth;
10
- readonly auth_client?: DeviceAuthClient;
11
- private pendingEvents;
12
- private authTask;
10
+ private readonly enabled;
11
+ readonly auth_client: DeviceAuthClient;
13
12
  private sessionId;
13
+ private allowSessionEventsForAuth;
14
+ private authFlowActive;
14
15
  constructor(options?: CloudSyncOptions);
15
16
  handle_event(event: BaseEvent): Promise<void>;
16
17
  private sendEvent;
17
- private backgroundAuth;
18
- private resendPendingEvents;
18
+ set_auth_flow_active(): void;
19
19
  wait_for_auth(): Promise<void>;
20
20
  authenticate(showInstructions?: boolean): Promise<boolean>;
21
21
  }
@@ -7,40 +7,39 @@ const stripTrailingSlash = (input) => input.replace(/\/+$/, '');
7
7
  const ensureArray = (value) => Array.isArray(value) ? value : [value];
8
8
  export class CloudSync {
9
9
  baseUrl;
10
- enableAuth;
10
+ enabled;
11
11
  auth_client;
12
- pendingEvents = [];
13
- authTask = null;
14
12
  sessionId = null;
13
+ allowSessionEventsForAuth;
14
+ authFlowActive = false;
15
15
  constructor(options = {}) {
16
+ const enableAuth = options.enableAuth ?? true;
16
17
  this.baseUrl = stripTrailingSlash(options.baseUrl ?? CONFIG.BROWSER_USE_CLOUD_API_URL);
17
- this.enableAuth = options.enableAuth ?? true;
18
- this.auth_client = this.enableAuth
19
- ? new DeviceAuthClient(this.baseUrl)
20
- : undefined;
18
+ this.enabled = CONFIG.BROWSER_USE_CLOUD_SYNC && enableAuth;
19
+ this.allowSessionEventsForAuth = options.allowSessionEventsForAuth ?? false;
20
+ this.auth_client = new DeviceAuthClient(this.baseUrl);
21
21
  }
22
22
  async handle_event(event) {
23
23
  try {
24
+ if (!this.enabled) {
25
+ return;
26
+ }
24
27
  if (event.event_type === 'CreateAgentSessionEvent' &&
25
- event?.id) {
28
+ event?.id != null) {
26
29
  this.sessionId = String(event.id);
27
30
  }
28
- if (event.event_type === 'CreateAgentStepEvent') {
29
- const raw = event;
30
- const step = raw?.step ?? raw?.payload?.step;
31
- if (step === 2 &&
32
- this.enableAuth &&
33
- this.auth_client &&
34
- !this.authTask) {
35
- if (this.sessionId) {
36
- this.authTask = this.backgroundAuth(this.sessionId);
37
- }
38
- else {
39
- logger.warning('Cannot start cloud auth, session_id missing');
40
- }
31
+ if (this.auth_client.is_authenticated) {
32
+ await this.sendEvent(event);
33
+ return;
34
+ }
35
+ if (this.allowSessionEventsForAuth || this.authFlowActive) {
36
+ await this.sendEvent(event);
37
+ if (event.event_type === 'CreateAgentSessionEvent') {
38
+ this.authFlowActive = true;
41
39
  }
40
+ return;
42
41
  }
43
- await this.sendEvent(event);
42
+ logger.debug(`Skipping event ${event.event_type} - user not authenticated`);
44
43
  }
45
44
  catch (error) {
46
45
  logger.error(`Failed to handle ${event.event_type}: ${error.message}`);
@@ -49,13 +48,18 @@ export class CloudSync {
49
48
  async sendEvent(event) {
50
49
  try {
51
50
  const headers = {};
52
- const authClient = this.auth_client;
53
- const userId = authClient ? authClient.user_id : TEMP_USER_ID;
54
- event.user_id = userId;
55
- if (authClient) {
56
- Object.assign(headers, authClient.get_headers());
57
- event.device_id = authClient.device_id;
51
+ const currentUserId = event.user_id ?? null;
52
+ if (this.auth_client.is_authenticated) {
53
+ if (currentUserId !== TEMP_USER_ID) {
54
+ event.user_id = this.auth_client.user_id;
55
+ }
58
56
  }
57
+ else if (!currentUserId) {
58
+ event.user_id = TEMP_USER_ID;
59
+ }
60
+ Object.assign(headers, this.auth_client.get_headers());
61
+ event.device_id =
62
+ event.device_id ?? this.auth_client.device_id;
59
63
  const payload = typeof event?.toJSON === 'function'
60
64
  ? event.toJSON()
61
65
  : { ...event };
@@ -63,84 +67,45 @@ export class CloudSync {
63
67
  await axios.post(`${this.baseUrl}/api/v1/events`, {
64
68
  events: events.map((entry) => ({
65
69
  ...entry,
66
- device_id: entry.device_id ?? authClient?.device_id,
70
+ device_id: entry.device_id ?? this.auth_client.device_id,
67
71
  })),
68
72
  }, { headers, timeout: 10_000 });
69
73
  }
70
74
  catch (error) {
71
75
  const status = error?.response?.status;
72
- if (status === 401 &&
73
- this.auth_client &&
74
- !this.auth_client.is_authenticated) {
75
- this.pendingEvents.push(event);
76
- return;
77
- }
78
76
  if (status) {
79
- logger.debug(`Cloud sync HTTP ${status}: ${error?.response?.data ?? error}`);
77
+ logger.debug(`Failed to send sync event: POST ${this.baseUrl}/api/v1/events ${status} - ${String(error?.response?.data ?? '')}`);
80
78
  }
81
79
  else if (error?.code === 'ECONNABORTED') {
82
- logger.warning(`Cloud sync timeout sending ${event.event_type}`);
80
+ logger.debug(`Event send timed out after 10 seconds: ${event}`);
83
81
  }
84
82
  else {
85
- logger.warning(`Cloud sync error: ${error?.message ?? error}`);
86
- }
87
- }
88
- }
89
- async backgroundAuth(agentSessionId) {
90
- const authClient = this.auth_client;
91
- if (!authClient) {
92
- return;
93
- }
94
- try {
95
- if (authClient.is_authenticated) {
96
- const frontend = CONFIG.BROWSER_USE_CLOUD_UI_URL ||
97
- this.baseUrl.replace('//api.', '//cloud.');
98
- const sessionUrl = `${stripTrailingSlash(frontend)}/agent/${agentSessionId}`;
99
- const divider = '─'.repeat(Math.max((process.stdout?.columns ?? 80) - 40, 20));
100
- logger.info(divider);
101
- logger.info('🌐 View the details of this run in Browser Use Cloud:');
102
- logger.info(` 👉 ${sessionUrl}`);
103
- logger.info(divider + '\n');
104
- return;
105
- }
106
- const success = await authClient.authenticate(agentSessionId, true);
107
- if (success) {
108
- await this.resendPendingEvents();
109
- }
110
- }
111
- catch (error) {
112
- logger.debug(`Cloud sync auth error: ${error.message}`);
113
- }
114
- }
115
- async resendPendingEvents() {
116
- if (!this.pendingEvents.length) {
117
- return;
118
- }
119
- const events = [...this.pendingEvents];
120
- this.pendingEvents = [];
121
- for (const event of events) {
122
- try {
123
- await this.sendEvent(event);
124
- }
125
- catch (error) {
126
- logger.warning(`Failed to resend event ${event.event_type}: ${error.message}`);
83
+ logger.debug(`Unexpected error sending event ${event}: ${typeOfError(error)}`);
127
84
  }
128
85
  }
129
86
  }
130
- async wait_for_auth() {
131
- if (this.authTask) {
132
- try {
133
- await this.authTask;
134
- }
135
- catch {
136
- /* ignore */
137
- }
138
- }
87
+ set_auth_flow_active() {
88
+ this.authFlowActive = true;
89
+ this.allowSessionEventsForAuth = true;
139
90
  }
91
+ // Backward-compatible no-op; auth is no longer background-scheduled here.
92
+ async wait_for_auth() { }
140
93
  async authenticate(showInstructions = true) {
141
- if (!this.auth_client) {
94
+ if (!this.enabled) {
142
95
  return false;
143
96
  }
97
+ if (this.auth_client.is_authenticated) {
98
+ if (showInstructions) {
99
+ logger.info('✅ Already authenticated! Skipping OAuth flow.');
100
+ }
101
+ return true;
102
+ }
144
103
  return this.auth_client.authenticate(this.sessionId, showInstructions);
145
104
  }
146
105
  }
106
+ const typeOfError = (error) => {
107
+ if (error instanceof Error) {
108
+ return `${error.name}: ${error.message}`;
109
+ }
110
+ return String(error);
111
+ };