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,249 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { AgentFocusChangedEvent, BrowserConnectedEvent, BrowserErrorEvent, BrowserStopEvent, BrowserStoppedEvent, TabCreatedEvent, } from '../events.js';
4
+ import { BaseWatchdog } from './base.js';
5
+ export class RecordingWatchdog extends BaseWatchdog {
6
+ static LISTENS_TO = [
7
+ BrowserConnectedEvent,
8
+ BrowserStopEvent,
9
+ BrowserStoppedEvent,
10
+ AgentFocusChangedEvent,
11
+ TabCreatedEvent,
12
+ ];
13
+ _traceStarted = false;
14
+ _videoCloseListeners = new Map();
15
+ _cdpScreencastSession = null;
16
+ _cdpScreencastHandler = null;
17
+ _cdpScreencastPath = null;
18
+ _cdpScreencastStream = null;
19
+ async on_BrowserConnectedEvent() {
20
+ this._prepareVideoDirectory();
21
+ this._attachVideoListenersToKnownPages();
22
+ await this._startCdpScreencastIfConfigured();
23
+ await this._startTracingIfConfigured();
24
+ }
25
+ async on_BrowserStopEvent() {
26
+ await this._stopCdpScreencastIfStarted();
27
+ await this._stopTracingIfStarted();
28
+ }
29
+ async on_BrowserStoppedEvent() {
30
+ this._detachVideoListeners();
31
+ }
32
+ async on_AgentFocusChangedEvent(event) {
33
+ this._attachVideoListenersToKnownPages();
34
+ await this._startCdpScreencastIfConfigured();
35
+ if (!this._traceStarted) {
36
+ return;
37
+ }
38
+ this.browser_session.logger.debug(`[RecordingWatchdog] Focus changed to ${event.target_id}; tracing remains active`);
39
+ }
40
+ async on_TabCreatedEvent() {
41
+ this._attachVideoListenersToKnownPages();
42
+ await this._startCdpScreencastIfConfigured();
43
+ }
44
+ onDetached() {
45
+ void this._stopCdpScreencastIfStarted();
46
+ this._detachVideoListeners();
47
+ }
48
+ _prepareVideoDirectory() {
49
+ const configuredPath = this.browser_session.browser_profile.config.record_video_dir;
50
+ if (typeof configuredPath !== 'string' || configuredPath.trim() === '') {
51
+ return;
52
+ }
53
+ const resolvedPath = path.resolve(configuredPath);
54
+ fs.mkdirSync(resolvedPath, { recursive: true });
55
+ this.browser_session.browser_profile.config.record_video_dir = resolvedPath;
56
+ }
57
+ async _startTracingIfConfigured() {
58
+ if (this._traceStarted ||
59
+ !this.browser_session.browser_profile.traces_dir) {
60
+ return;
61
+ }
62
+ try {
63
+ await this.browser_session.start_trace_recording();
64
+ this._traceStarted = true;
65
+ }
66
+ catch (error) {
67
+ await this.event_bus.dispatch(new BrowserErrorEvent({
68
+ error_type: 'RecordingStartFailed',
69
+ message: `Failed to start trace recording: ${error.message}`,
70
+ details: {
71
+ traces_dir: this.browser_session.browser_profile.traces_dir,
72
+ },
73
+ }));
74
+ }
75
+ }
76
+ async _stopTracingIfStarted() {
77
+ if (!this._traceStarted) {
78
+ return;
79
+ }
80
+ try {
81
+ await this.browser_session.save_trace_recording();
82
+ }
83
+ catch (error) {
84
+ await this.event_bus.dispatch(new BrowserErrorEvent({
85
+ error_type: 'RecordingStopFailed',
86
+ message: `Failed to save trace recording: ${error.message}`,
87
+ details: {
88
+ traces_dir: this.browser_session.browser_profile.traces_dir,
89
+ },
90
+ }));
91
+ }
92
+ finally {
93
+ this._traceStarted = false;
94
+ }
95
+ }
96
+ _attachVideoListenersToKnownPages() {
97
+ const configuredPath = this.browser_session.browser_profile.config.record_video_dir;
98
+ if (typeof configuredPath !== 'string' || configuredPath.trim() === '') {
99
+ return;
100
+ }
101
+ for (const page of this._getKnownPages()) {
102
+ this._attachVideoListener(page);
103
+ }
104
+ }
105
+ _attachVideoListener(page) {
106
+ if (this._videoCloseListeners.has(page) || typeof page?.on !== 'function') {
107
+ return;
108
+ }
109
+ const listener = () => {
110
+ this._videoCloseListeners.delete(page);
111
+ if (typeof page.off === 'function') {
112
+ page.off('close', listener);
113
+ }
114
+ else if (typeof page.removeListener === 'function') {
115
+ page.removeListener('close', listener);
116
+ }
117
+ void this._captureVideoArtifact(page);
118
+ };
119
+ page.on('close', listener);
120
+ this._videoCloseListeners.set(page, listener);
121
+ }
122
+ _detachVideoListeners() {
123
+ for (const [page, listener] of [...this._videoCloseListeners.entries()]) {
124
+ if (typeof page.off === 'function') {
125
+ page.off('close', listener);
126
+ }
127
+ else if (typeof page.removeListener === 'function') {
128
+ page.removeListener('close', listener);
129
+ }
130
+ }
131
+ this._videoCloseListeners.clear();
132
+ }
133
+ _getKnownPages() {
134
+ const pagesFromContext = typeof this.browser_session.browser_context?.pages === 'function'
135
+ ? this.browser_session.browser_context.pages()
136
+ : [];
137
+ const activePage = this.browser_session
138
+ .agent_current_page;
139
+ if (!activePage) {
140
+ return pagesFromContext;
141
+ }
142
+ if (pagesFromContext.includes(activePage)) {
143
+ return pagesFromContext;
144
+ }
145
+ return [...pagesFromContext, activePage];
146
+ }
147
+ async _captureVideoArtifact(page) {
148
+ try {
149
+ const video = typeof page.video === 'function' ? page.video() : null;
150
+ const videoPath = await video?.path?.();
151
+ if (typeof videoPath === 'string' && videoPath.length > 0) {
152
+ this.browser_session.add_downloaded_file(videoPath);
153
+ }
154
+ }
155
+ catch (error) {
156
+ this.browser_session.logger.debug(`[RecordingWatchdog] Failed to capture video artifact: ${error.message}`);
157
+ }
158
+ }
159
+ async _startCdpScreencastIfConfigured() {
160
+ const configuredPath = this.browser_session.browser_profile.config.record_video_dir;
161
+ if (typeof configuredPath !== 'string' || configuredPath.trim() === '') {
162
+ return;
163
+ }
164
+ if (this._cdpScreencastSession || this._cdpScreencastStream) {
165
+ return;
166
+ }
167
+ try {
168
+ const page = await this.browser_session.get_current_page();
169
+ if (!page) {
170
+ return;
171
+ }
172
+ const session = (await this.browser_session.get_or_create_cdp_session(page));
173
+ const filePath = path.join(configuredPath, `${Date.now()}-${this.browser_session.id.slice(-6)}.cdp-screencast.ndjson`);
174
+ const stream = fs.createWriteStream(filePath, { flags: 'a' });
175
+ const handler = (payload) => {
176
+ const frameData = typeof payload?.data === 'string' ? payload.data : '';
177
+ if (frameData && this._cdpScreencastStream) {
178
+ this._cdpScreencastStream.write(`${JSON.stringify({
179
+ ts: Date.now(),
180
+ session_id: typeof payload?.sessionId === 'number' ||
181
+ typeof payload?.sessionId === 'string'
182
+ ? payload.sessionId
183
+ : null,
184
+ data: frameData,
185
+ })}\n`);
186
+ }
187
+ const sessionId = typeof payload?.sessionId === 'number' ||
188
+ typeof payload?.sessionId === 'string'
189
+ ? payload.sessionId
190
+ : null;
191
+ if (!sessionId) {
192
+ return;
193
+ }
194
+ void session.send?.('Page.screencastFrameAck', {
195
+ sessionId,
196
+ });
197
+ };
198
+ await session.send?.('Page.enable');
199
+ session.on?.('Page.screencastFrame', handler);
200
+ await session.send?.('Page.startScreencast', {
201
+ format: 'jpeg',
202
+ quality: 85,
203
+ everyNthFrame: 1,
204
+ });
205
+ this._cdpScreencastSession = session;
206
+ this._cdpScreencastHandler = handler;
207
+ this._cdpScreencastPath = filePath;
208
+ this._cdpScreencastStream = stream;
209
+ }
210
+ catch (error) {
211
+ await this.event_bus.dispatch(new BrowserErrorEvent({
212
+ error_type: 'RecordingCdpStartFailed',
213
+ message: `Failed to start CDP screencast recording: ${error.message}`,
214
+ details: {
215
+ record_video_dir: this.browser_session.browser_profile.config.record_video_dir,
216
+ },
217
+ }));
218
+ await this._stopCdpScreencastIfStarted();
219
+ }
220
+ }
221
+ async _stopCdpScreencastIfStarted() {
222
+ if (!this._cdpScreencastSession) {
223
+ return;
224
+ }
225
+ try {
226
+ await this._cdpScreencastSession.send?.('Page.stopScreencast');
227
+ }
228
+ catch {
229
+ // Ignore stop errors.
230
+ }
231
+ if (this._cdpScreencastHandler) {
232
+ this._cdpScreencastSession.off?.('Page.screencastFrame', this._cdpScreencastHandler);
233
+ }
234
+ if (this._cdpScreencastStream) {
235
+ await new Promise((resolve) => {
236
+ this._cdpScreencastStream?.end(() => resolve());
237
+ });
238
+ }
239
+ if (this._cdpScreencastPath &&
240
+ fs.existsSync(this._cdpScreencastPath) &&
241
+ fs.statSync(this._cdpScreencastPath).size > 0) {
242
+ this.browser_session.add_downloaded_file(this._cdpScreencastPath);
243
+ }
244
+ this._cdpScreencastSession = null;
245
+ this._cdpScreencastHandler = null;
246
+ this._cdpScreencastPath = null;
247
+ this._cdpScreencastStream = null;
248
+ }
249
+ }
@@ -0,0 +1,6 @@
1
+ import { ScreenshotEvent } from '../events.js';
2
+ import { BaseWatchdog } from './base.js';
3
+ export declare class ScreenshotWatchdog extends BaseWatchdog {
4
+ static LISTENS_TO: (typeof ScreenshotEvent)[];
5
+ on_ScreenshotEvent(event: ScreenshotEvent): Promise<string | null>;
6
+ }
@@ -0,0 +1,13 @@
1
+ import { ScreenshotEvent } from '../events.js';
2
+ import { BaseWatchdog } from './base.js';
3
+ export class ScreenshotWatchdog extends BaseWatchdog {
4
+ static LISTENS_TO = [ScreenshotEvent];
5
+ async on_ScreenshotEvent(event) {
6
+ try {
7
+ return await this.browser_session.take_screenshot(event.full_page);
8
+ }
9
+ finally {
10
+ await this.browser_session.remove_highlights();
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,10 @@
1
+ import { BrowserErrorEvent, CloseTabEvent, NavigateToUrlEvent, NavigationCompleteEvent, TabCreatedEvent } from '../events.js';
2
+ import { BaseWatchdog } from './base.js';
3
+ export declare class SecurityWatchdog extends BaseWatchdog {
4
+ static LISTENS_TO: (typeof NavigateToUrlEvent | typeof TabCreatedEvent)[];
5
+ static EMITS: (typeof CloseTabEvent | typeof BrowserErrorEvent)[];
6
+ on_NavigateToUrlEvent(event: NavigateToUrlEvent): Promise<void>;
7
+ on_NavigationCompleteEvent(event: NavigationCompleteEvent): Promise<void>;
8
+ on_TabCreatedEvent(event: TabCreatedEvent): Promise<void>;
9
+ private _getUrlDenialReason;
10
+ }
@@ -0,0 +1,84 @@
1
+ import { BrowserErrorEvent, CloseTabEvent, NavigateToUrlEvent, NavigationCompleteEvent, TabCreatedEvent, } from '../events.js';
2
+ import { BaseWatchdog } from './base.js';
3
+ export class SecurityWatchdog extends BaseWatchdog {
4
+ static LISTENS_TO = [
5
+ NavigateToUrlEvent,
6
+ NavigationCompleteEvent,
7
+ TabCreatedEvent,
8
+ ];
9
+ static EMITS = [BrowserErrorEvent, CloseTabEvent];
10
+ async on_NavigateToUrlEvent(event) {
11
+ const denialReason = this._getUrlDenialReason(event.url);
12
+ if (!denialReason) {
13
+ return;
14
+ }
15
+ await this.event_bus.dispatch(new BrowserErrorEvent({
16
+ error_type: 'NavigationBlocked',
17
+ message: `Navigation blocked to disallowed URL: ${event.url}`,
18
+ details: {
19
+ url: event.url,
20
+ reason: denialReason,
21
+ },
22
+ }));
23
+ throw new Error(`Navigation to ${event.url} blocked by security policy`);
24
+ }
25
+ async on_NavigationCompleteEvent(event) {
26
+ const denialReason = this._getUrlDenialReason(event.url);
27
+ if (!denialReason) {
28
+ return;
29
+ }
30
+ await this.event_bus.dispatch(new BrowserErrorEvent({
31
+ error_type: 'NavigationBlocked',
32
+ message: `Navigation blocked to non-allowed URL: ${event.url} - redirecting to about:blank`,
33
+ details: {
34
+ url: event.url,
35
+ target_id: event.target_id,
36
+ reason: denialReason,
37
+ },
38
+ }));
39
+ try {
40
+ await this.browser_session.navigate_to('about:blank');
41
+ }
42
+ catch (error) {
43
+ this.browser_session.logger.debug(`SecurityWatchdog failed to redirect to about:blank: ${error.message}`);
44
+ }
45
+ }
46
+ async on_TabCreatedEvent(event) {
47
+ const denialReason = this._getUrlDenialReason(event.url);
48
+ if (!denialReason) {
49
+ return;
50
+ }
51
+ await this.event_bus.dispatch(new BrowserErrorEvent({
52
+ error_type: 'TabCreationBlocked',
53
+ message: `Tab created with non-allowed URL: ${event.url}`,
54
+ details: {
55
+ url: event.url,
56
+ target_id: event.target_id,
57
+ reason: denialReason,
58
+ },
59
+ }));
60
+ await this.event_bus.dispatch(new CloseTabEvent({
61
+ target_id: event.target_id,
62
+ }));
63
+ }
64
+ _getUrlDenialReason(url) {
65
+ const session = this.browser_session;
66
+ if (typeof session._get_url_access_denial_reason === 'function') {
67
+ try {
68
+ return session._get_url_access_denial_reason(url);
69
+ }
70
+ catch {
71
+ // Ignore private method failures and fallback to boolean check.
72
+ }
73
+ }
74
+ if (typeof session._is_url_allowed === 'function') {
75
+ try {
76
+ return session._is_url_allowed(url) ? null : 'blocked';
77
+ }
78
+ catch {
79
+ return 'blocked';
80
+ }
81
+ }
82
+ return null;
83
+ }
84
+ }
@@ -0,0 +1,24 @@
1
+ import { BrowserConnectedEvent, BrowserStopEvent, LoadStorageStateEvent, SaveStorageStateEvent, StorageStateSavedEvent } from '../events.js';
2
+ import { BaseWatchdog } from './base.js';
3
+ export declare class StorageStateWatchdog extends BaseWatchdog {
4
+ static LISTENS_TO: (typeof BrowserStopEvent | typeof BrowserConnectedEvent | typeof SaveStorageStateEvent)[];
5
+ static EMITS: (typeof StorageStateSavedEvent)[];
6
+ private _monitorInterval;
7
+ private _autoSaveIntervalMs;
8
+ private _monitoring;
9
+ private _lastSavedSnapshot;
10
+ on_BrowserConnectedEvent(): Promise<void>;
11
+ on_BrowserStopEvent(): Promise<void>;
12
+ on_SaveStorageStateEvent(event: SaveStorageStateEvent): Promise<void>;
13
+ on_LoadStorageStateEvent(event: LoadStorageStateEvent): Promise<void>;
14
+ protected onDetached(): void;
15
+ private _resolveStoragePath;
16
+ private _startMonitoring;
17
+ private _stopMonitoring;
18
+ private _checkAndAutoSave;
19
+ private _snapshotStorageState;
20
+ private _readStoredState;
21
+ private _mergeStorageStates;
22
+ private _applyOriginsStorage;
23
+ private _normalizeStorageEntries;
24
+ }
@@ -0,0 +1,288 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { BrowserConnectedEvent, BrowserStopEvent, LoadStorageStateEvent, SaveStorageStateEvent, StorageStateLoadedEvent, StorageStateSavedEvent, } from '../events.js';
4
+ import { BaseWatchdog } from './base.js';
5
+ export class StorageStateWatchdog extends BaseWatchdog {
6
+ static LISTENS_TO = [
7
+ BrowserConnectedEvent,
8
+ BrowserStopEvent,
9
+ SaveStorageStateEvent,
10
+ LoadStorageStateEvent,
11
+ ];
12
+ static EMITS = [StorageStateSavedEvent, StorageStateLoadedEvent];
13
+ _monitorInterval = null;
14
+ _autoSaveIntervalMs = 30_000;
15
+ _monitoring = false;
16
+ _lastSavedSnapshot = null;
17
+ async on_BrowserConnectedEvent() {
18
+ this._startMonitoring();
19
+ await this.event_bus.dispatch(new LoadStorageStateEvent());
20
+ }
21
+ async on_BrowserStopEvent() {
22
+ this._stopMonitoring();
23
+ await this.event_bus.dispatch(new SaveStorageStateEvent());
24
+ }
25
+ async on_SaveStorageStateEvent(event) {
26
+ const targetPath = this._resolveStoragePath(event.path);
27
+ if (!targetPath) {
28
+ return;
29
+ }
30
+ const browserContext = this.browser_session.browser_context;
31
+ if (!browserContext?.storageState) {
32
+ this.browser_session.logger.debug('[StorageStateWatchdog] Browser context unavailable for save');
33
+ return;
34
+ }
35
+ const storageState = (await browserContext.storageState());
36
+ const normalized = storageState ?? {};
37
+ const merged = this._mergeStorageStates(this._readStoredState(targetPath), {
38
+ cookies: Array.isArray(normalized.cookies) ? normalized.cookies : [],
39
+ origins: Array.isArray(normalized.origins) ? normalized.origins : [],
40
+ });
41
+ this._lastSavedSnapshot = this._snapshotStorageState(merged);
42
+ const dirPath = path.dirname(targetPath);
43
+ fs.mkdirSync(dirPath, { recursive: true });
44
+ const tempPath = `${targetPath}.tmp`;
45
+ fs.writeFileSync(tempPath, JSON.stringify(merged, null, 2));
46
+ if (fs.existsSync(targetPath)) {
47
+ const backupPath = `${targetPath}.bak`;
48
+ try {
49
+ fs.renameSync(targetPath, backupPath);
50
+ }
51
+ catch {
52
+ // Ignore backup failures and continue with atomic swap.
53
+ }
54
+ }
55
+ fs.renameSync(tempPath, targetPath);
56
+ await this.event_bus.dispatch(new StorageStateSavedEvent({
57
+ path: targetPath,
58
+ cookies_count: Array.isArray(merged.cookies)
59
+ ? merged.cookies.length
60
+ : 0,
61
+ origins_count: Array.isArray(merged.origins)
62
+ ? merged.origins.length
63
+ : 0,
64
+ }));
65
+ }
66
+ async on_LoadStorageStateEvent(event) {
67
+ const targetPath = this._resolveStoragePath(event.path);
68
+ if (!targetPath || !fs.existsSync(targetPath)) {
69
+ return;
70
+ }
71
+ const browserContext = this.browser_session.browser_context;
72
+ if (!browserContext) {
73
+ this.browser_session.logger.debug('[StorageStateWatchdog] Browser context unavailable for load');
74
+ return;
75
+ }
76
+ const raw = fs.readFileSync(targetPath, 'utf-8');
77
+ const parsed = JSON.parse(raw);
78
+ const cookies = Array.isArray(parsed.cookies) ? parsed.cookies : [];
79
+ const origins = Array.isArray(parsed.origins) ? parsed.origins : [];
80
+ this._lastSavedSnapshot = this._snapshotStorageState({
81
+ cookies,
82
+ origins,
83
+ });
84
+ if (cookies.length > 0 && typeof browserContext.addCookies === 'function') {
85
+ await browserContext.addCookies(cookies);
86
+ }
87
+ if (origins.length > 0) {
88
+ await this._applyOriginsStorage(origins);
89
+ }
90
+ await this.event_bus.dispatch(new StorageStateLoadedEvent({
91
+ path: targetPath,
92
+ cookies_count: cookies.length,
93
+ origins_count: origins.length,
94
+ }));
95
+ }
96
+ onDetached() {
97
+ this._stopMonitoring();
98
+ }
99
+ _resolveStoragePath(pathFromEvent) {
100
+ if (typeof pathFromEvent === 'string' && pathFromEvent.trim().length > 0) {
101
+ return path.resolve(pathFromEvent);
102
+ }
103
+ const configured = this.browser_session.browser_profile.config.storage_state;
104
+ if (typeof configured === 'string' && configured.trim().length > 0) {
105
+ return path.resolve(configured);
106
+ }
107
+ const cookiesFile = this.browser_session.browser_profile.cookies_file;
108
+ if (typeof cookiesFile === 'string' && cookiesFile.trim().length > 0) {
109
+ return path.resolve(cookiesFile);
110
+ }
111
+ return null;
112
+ }
113
+ _startMonitoring() {
114
+ if (this._monitorInterval) {
115
+ return;
116
+ }
117
+ if (!this._resolveStoragePath(null)) {
118
+ return;
119
+ }
120
+ this._monitorInterval = setInterval(() => {
121
+ void this._checkAndAutoSave().catch((error) => {
122
+ this.browser_session.logger.debug(`[StorageStateWatchdog] Auto-save monitor failed: ${error.message}`);
123
+ });
124
+ }, this._autoSaveIntervalMs);
125
+ }
126
+ _stopMonitoring() {
127
+ if (!this._monitorInterval) {
128
+ return;
129
+ }
130
+ clearInterval(this._monitorInterval);
131
+ this._monitorInterval = null;
132
+ }
133
+ async _checkAndAutoSave() {
134
+ if (this._monitoring) {
135
+ return;
136
+ }
137
+ const browserContext = this.browser_session.browser_context;
138
+ if (!browserContext?.storageState) {
139
+ return;
140
+ }
141
+ this._monitoring = true;
142
+ try {
143
+ const storageState = (await browserContext.storageState());
144
+ const normalized = storageState ?? {};
145
+ const snapshot = this._snapshotStorageState(normalized);
146
+ if (snapshot === this._lastSavedSnapshot) {
147
+ return;
148
+ }
149
+ await this.event_bus.dispatch(new SaveStorageStateEvent());
150
+ }
151
+ finally {
152
+ this._monitoring = false;
153
+ }
154
+ }
155
+ _snapshotStorageState(state) {
156
+ const cookies = Array.isArray(state.cookies) ? state.cookies : [];
157
+ const origins = Array.isArray(state.origins) ? state.origins : [];
158
+ return JSON.stringify({
159
+ cookies,
160
+ origins,
161
+ });
162
+ }
163
+ _readStoredState(targetPath) {
164
+ if (!fs.existsSync(targetPath)) {
165
+ return {
166
+ cookies: [],
167
+ origins: [],
168
+ };
169
+ }
170
+ try {
171
+ const raw = fs.readFileSync(targetPath, 'utf-8');
172
+ const parsed = JSON.parse(raw);
173
+ return {
174
+ cookies: Array.isArray(parsed.cookies) ? parsed.cookies : [],
175
+ origins: Array.isArray(parsed.origins) ? parsed.origins : [],
176
+ };
177
+ }
178
+ catch (error) {
179
+ this.browser_session.logger.debug(`[StorageStateWatchdog] Failed to parse existing storage state: ${error.message}`);
180
+ return {
181
+ cookies: [],
182
+ origins: [],
183
+ };
184
+ }
185
+ }
186
+ _mergeStorageStates(existing, incoming) {
187
+ const mergedCookies = new Map();
188
+ const toCookieKey = (cookie) => `${String(cookie?.name ?? '')}::${String(cookie?.domain ?? '')}::${String(cookie?.path ?? '')}`;
189
+ for (const cookie of Array.isArray(existing.cookies)
190
+ ? existing.cookies
191
+ : []) {
192
+ mergedCookies.set(toCookieKey(cookie), cookie);
193
+ }
194
+ for (const cookie of Array.isArray(incoming.cookies)
195
+ ? incoming.cookies
196
+ : []) {
197
+ mergedCookies.set(toCookieKey(cookie), cookie);
198
+ }
199
+ const mergedOrigins = new Map();
200
+ const toOriginKey = (origin) => String(origin?.origin ?? '');
201
+ for (const origin of Array.isArray(existing.origins)
202
+ ? existing.origins
203
+ : []) {
204
+ mergedOrigins.set(toOriginKey(origin), origin);
205
+ }
206
+ for (const origin of Array.isArray(incoming.origins)
207
+ ? incoming.origins
208
+ : []) {
209
+ mergedOrigins.set(toOriginKey(origin), origin);
210
+ }
211
+ return {
212
+ cookies: [...mergedCookies.values()],
213
+ origins: [...mergedOrigins.values()],
214
+ };
215
+ }
216
+ async _applyOriginsStorage(origins) {
217
+ const browserContext = this.browser_session.browser_context;
218
+ if (!browserContext?.newPage) {
219
+ return;
220
+ }
221
+ for (const originState of origins) {
222
+ const origin = typeof originState?.origin === 'string'
223
+ ? originState.origin.trim()
224
+ : '';
225
+ if (!origin || !/^https?:\/\//i.test(origin)) {
226
+ continue;
227
+ }
228
+ const localStorageEntries = this._normalizeStorageEntries(originState?.localStorage);
229
+ const sessionStorageEntries = this._normalizeStorageEntries(originState?.sessionStorage);
230
+ if (localStorageEntries.length === 0 &&
231
+ sessionStorageEntries.length === 0) {
232
+ continue;
233
+ }
234
+ let page = null;
235
+ try {
236
+ page = await browserContext.newPage();
237
+ await page.goto?.(origin, {
238
+ waitUntil: 'domcontentloaded',
239
+ timeout: 5_000,
240
+ });
241
+ await page.evaluate?.((payload) => {
242
+ for (const entry of payload.localStorageEntries) {
243
+ window.localStorage.setItem(entry.name, entry.value);
244
+ }
245
+ for (const entry of payload.sessionStorageEntries) {
246
+ window.sessionStorage.setItem(entry.name, entry.value);
247
+ }
248
+ }, {
249
+ localStorageEntries,
250
+ sessionStorageEntries,
251
+ });
252
+ }
253
+ catch (error) {
254
+ this.browser_session.logger.debug(`[StorageStateWatchdog] Failed to apply origin storage for ${origin}: ${error.message}`);
255
+ }
256
+ finally {
257
+ try {
258
+ await page?.close?.();
259
+ }
260
+ catch {
261
+ // Ignore cleanup errors.
262
+ }
263
+ }
264
+ }
265
+ }
266
+ _normalizeStorageEntries(entries) {
267
+ if (!Array.isArray(entries)) {
268
+ return [];
269
+ }
270
+ const normalized = [];
271
+ for (const entry of entries) {
272
+ const name = entry && typeof entry === 'object' && 'name' in entry
273
+ ? String(entry.name ?? '')
274
+ : '';
275
+ if (!name) {
276
+ continue;
277
+ }
278
+ const value = entry && typeof entry === 'object' && 'value' in entry
279
+ ? String(entry.value ?? '')
280
+ : '';
281
+ normalized.push({
282
+ name,
283
+ value,
284
+ });
285
+ }
286
+ return normalized;
287
+ }
288
+ }