@agimon-ai/po-browse-tool 0.0.0-alpha.1

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 (112) hide show
  1. package/LICENSE +52 -0
  2. package/README.md +485 -0
  3. package/dist/ExtensionToolDelegator-5tCMdpOX.mjs +2 -0
  4. package/dist/ExtensionToolDelegator-5tCMdpOX.mjs.map +1 -0
  5. package/dist/ExtensionToolDelegator-BIZjM0fI.cjs +1 -0
  6. package/dist/HttpServerHealthCheck-Dk40Qbal.cjs +1 -0
  7. package/dist/HttpServerHealthCheck-ptiiGMju.mjs +2 -0
  8. package/dist/HttpServerHealthCheck-ptiiGMju.mjs.map +1 -0
  9. package/dist/HttpServerManager-CJy-RcHx.cjs +3 -0
  10. package/dist/HttpServerManager-F8TuQ_sH.mjs +4 -0
  11. package/dist/HttpServerManager-F8TuQ_sH.mjs.map +1 -0
  12. package/dist/ProxyConfigService-B08hNZ4s.cjs +1 -0
  13. package/dist/ProxyConfigService-CyBvsRsw.mjs +2 -0
  14. package/dist/ProxyConfigService-CyBvsRsw.mjs.map +1 -0
  15. package/dist/chrome-serve-D3YnO8oJ.mjs +2 -0
  16. package/dist/chrome-serve-D3YnO8oJ.mjs.map +1 -0
  17. package/dist/chrome-serve-dhLhyk3E.cjs +1 -0
  18. package/dist/cli.cjs +2 -0
  19. package/dist/cli.d.cts +8 -0
  20. package/dist/cli.d.cts.map +1 -0
  21. package/dist/cli.d.mts +8 -0
  22. package/dist/cli.d.mts.map +1 -0
  23. package/dist/cli.mjs +3 -0
  24. package/dist/cli.mjs.map +1 -0
  25. package/dist/config-B3utZ9u8.mjs +2 -0
  26. package/dist/config-B3utZ9u8.mjs.map +1 -0
  27. package/dist/config-B_8FXOaj.cjs +1 -0
  28. package/dist/container-D-D4x8I7.mjs +52 -0
  29. package/dist/container-D-D4x8I7.mjs.map +1 -0
  30. package/dist/container-DrE51Rho.cjs +51 -0
  31. package/dist/custom-tools-CddCGETh.mjs +2 -0
  32. package/dist/custom-tools-CddCGETh.mjs.map +1 -0
  33. package/dist/custom-tools-Dw35YtXF.cjs +1 -0
  34. package/dist/decorate-B3KLIx2E.mjs +1 -0
  35. package/dist/decorate-DCJt-2rt.cjs +1 -0
  36. package/dist/decorateMetadata-D0fK5b4i.mjs +1 -0
  37. package/dist/decorateMetadata-DLp0MYhw.cjs +1 -0
  38. package/dist/docker-build-cft-0LgtjNZL.mjs +2 -0
  39. package/dist/docker-build-cft-0LgtjNZL.mjs.map +1 -0
  40. package/dist/docker-build-cft-0mp1llva.cjs +1 -0
  41. package/dist/dockerChromeForTesting-BMmEQq88.mjs +2 -0
  42. package/dist/dockerChromeForTesting-BMmEQq88.mjs.map +1 -0
  43. package/dist/dockerChromeForTesting-Do46yerb.cjs +1 -0
  44. package/dist/exec-BpDk2ZqA.cjs +1 -0
  45. package/dist/exec-BwmJjxfb.mjs +2 -0
  46. package/dist/exec-BwmJjxfb.mjs.map +1 -0
  47. package/dist/extension/background.js +9901 -0
  48. package/dist/extension/content.js +1105 -0
  49. package/dist/extension/manifest.json +47 -0
  50. package/dist/extension/offscreen.html +9 -0
  51. package/dist/extension/offscreen.js +369 -0
  52. package/dist/extension/popup.html +206 -0
  53. package/dist/extension/popup.js +145 -0
  54. package/dist/extension/recorder.html +26 -0
  55. package/dist/extension/recorder.js +259 -0
  56. package/dist/extension-routes-BNqy4x3X.cjs +1 -0
  57. package/dist/extension-routes-BSgEXfYo.mjs +2 -0
  58. package/dist/extension-routes-BSgEXfYo.mjs.map +1 -0
  59. package/dist/http-serve-B_uziWvT.cjs +481 -0
  60. package/dist/http-serve-D1MxNrxo.mjs +482 -0
  61. package/dist/http-serve-D1MxNrxo.mjs.map +1 -0
  62. package/dist/index.cjs +1 -0
  63. package/dist/index.d.cts +383 -0
  64. package/dist/index.d.cts.map +1 -0
  65. package/dist/index.d.mts +383 -0
  66. package/dist/index.d.mts.map +1 -0
  67. package/dist/index.mjs +2 -0
  68. package/dist/index.mjs.map +1 -0
  69. package/dist/mcp-serve-BSgZnHOS.mjs +2 -0
  70. package/dist/mcp-serve-BSgZnHOS.mjs.map +1 -0
  71. package/dist/mcp-serve-BfDqcbo4.cjs +1 -0
  72. package/dist/mcpToolDefinition-8uiZQtZz.cjs +1 -0
  73. package/dist/mcpToolDefinition-D8qTEh17.mjs +2 -0
  74. package/dist/mcpToolDefinition-D8qTEh17.mjs.map +1 -0
  75. package/dist/networkConfig-C_01QWPz.mjs +2 -0
  76. package/dist/networkConfig-C_01QWPz.mjs.map +1 -0
  77. package/dist/networkConfig-DjohaQE3.cjs +1 -0
  78. package/dist/outputFormatter-Djv_kt7B.mjs +5 -0
  79. package/dist/outputFormatter-Djv_kt7B.mjs.map +1 -0
  80. package/dist/outputFormatter-ZAVLQbZ9.cjs +4 -0
  81. package/dist/ownerContext-CxCqW_Pm.cjs +1 -0
  82. package/dist/ownerContext-DCuEO3xp.mjs +2 -0
  83. package/dist/ownerContext-DCuEO3xp.mjs.map +1 -0
  84. package/dist/playwright-test-CPzecT2T.cjs +252 -0
  85. package/dist/playwright-test-qSbpSyo8.mjs +253 -0
  86. package/dist/playwright-test-qSbpSyo8.mjs.map +1 -0
  87. package/dist/rolldown-runtime-DT7IzrpZ.mjs +1 -0
  88. package/dist/status-eeOCo1Vb.mjs +3 -0
  89. package/dist/status-eeOCo1Vb.mjs.map +1 -0
  90. package/dist/status-hyP1LAGP.cjs +2 -0
  91. package/dist/stop-BwcAUHOO.mjs +2 -0
  92. package/dist/stop-BwcAUHOO.mjs.map +1 -0
  93. package/dist/stop-C7N0ysXz.cjs +1 -0
  94. package/dist/streamable-http-CdadnaWv.cjs +1 -0
  95. package/dist/streamable-http-ka8aH0jj.mjs +2 -0
  96. package/dist/streamable-http-ka8aH0jj.mjs.map +1 -0
  97. package/dist/stubs/playwright-test.cjs +1 -0
  98. package/dist/stubs/playwright-test.d.cts +197 -0
  99. package/dist/stubs/playwright-test.d.cts.map +1 -0
  100. package/dist/stubs/playwright-test.d.mts +197 -0
  101. package/dist/stubs/playwright-test.d.mts.map +1 -0
  102. package/dist/stubs/playwright-test.mjs +1 -0
  103. package/dist/telemetryContext-Bs3fgsz2.mjs +2 -0
  104. package/dist/telemetryContext-Bs3fgsz2.mjs.map +1 -0
  105. package/dist/telemetryContext-vhf1yQI3.cjs +1 -0
  106. package/dist/timeouts-B_WDmAvV.cjs +1 -0
  107. package/dist/timeouts-CUKtmglu.mjs +2 -0
  108. package/dist/timeouts-CUKtmglu.mjs.map +1 -0
  109. package/dist/toolCommands-CKwU-3b5.mjs +3 -0
  110. package/dist/toolCommands-CKwU-3b5.mjs.map +1 -0
  111. package/dist/toolCommands-DEwESguV.cjs +2 -0
  112. package/package.json +115 -0
package/LICENSE ADDED
@@ -0,0 +1,52 @@
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: AgiFlow
6
+ Licensed Work: @agimon-ai/public-packages
7
+ The Licensed Work is (c) 2026 AgiFlow.
8
+ Additional Use Grant: None
9
+ Change Date: 2030-03-06
10
+ Change License: Apache License, Version 2.0
11
+
12
+ Terms
13
+
14
+ The Licensor hereby grants you the right to copy, modify, create derivative
15
+ works, redistribute, and make non-production use of the Licensed Work. The
16
+ Licensor may make an Additional Use Grant, above, permitting limited
17
+ production use.
18
+
19
+ Effective on the Change Date, or the fourth anniversary of the first publicly
20
+ available distribution of a specific version of the Licensed Work under this
21
+ License, whichever comes first, the Licensor hereby grants you rights under
22
+ the terms of the Change License, and the rights granted in the paragraph
23
+ above terminate.
24
+
25
+ If your use of the Licensed Work does not comply with the requirements
26
+ currently in effect as described in this License, you must purchase a
27
+ commercial license from the Licensor, its affiliated entities, or authorized
28
+ resellers, or you must refrain from using the Licensed Work.
29
+
30
+ All copies of the original and modified Licensed Work, and derivative works
31
+ of the Licensed Work, are subject to this License. This License applies
32
+ separately for each version of the Licensed Work and the Change Date may vary
33
+ for each version of the Licensed Work released by Licensor.
34
+
35
+ You must conspicuously display this License on each original or modified copy
36
+ of the Licensed Work. If you receive the Licensed Work in original or
37
+ modified form from a third party, the terms and conditions set forth in this
38
+ License apply to your use of that work.
39
+
40
+ Any use of the Licensed Work in violation of this License will automatically
41
+ terminate your rights under this License for the current and all other
42
+ versions of the Licensed Work.
43
+
44
+ This License does not grant you any right in any trademark or logo of
45
+ Licensor or its affiliates (provided that you may use a trademark or logo of
46
+ Licensor as expressly required by this License).
47
+
48
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
49
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
50
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
51
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
52
+ TITLE.
package/README.md ADDED
@@ -0,0 +1,485 @@
1
+ # @agimon-ai/po-browse-tool
2
+
3
+ MCP (Model Context Protocol) server for browser automation using Playwright. Provides comprehensive browser control, profile management, and LLM-driven automation capabilities.
4
+
5
+ ## Features
6
+
7
+ - 37 browser automation tools for complete browser control
8
+ - Multi-browser support (Chromium, Firefox, WebKit)
9
+ - Profile management for persistent browser sessions
10
+ - Page registry for managing multiple tabs/windows
11
+ - LLM-controlled automation with pause/resume flow
12
+ - Session management for organized browser operations
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pnpm install
18
+ ```
19
+
20
+ ## Development
21
+
22
+ ```bash
23
+ pnpm dev
24
+ ```
25
+
26
+ ## Build
27
+
28
+ ```bash
29
+ pnpm build
30
+ ```
31
+
32
+ ## Test
33
+
34
+ ```bash
35
+ pnpm test
36
+ ```
37
+
38
+ ## Private Po port checks
39
+
40
+ This private port retains the upstream SDK, CLI, extension assets, and license.
41
+ The profile-bound SDK is verified with synthetic local fixtures and real Chromium;
42
+ no live credentials or email sends are used.
43
+
44
+ From the repository root:
45
+
46
+ ```bash
47
+ pnpm exec nx run-many -t lint,typecheck,build,test -p @agimon-ai/po-browse-tool
48
+ ```
49
+
50
+ These targets cover whole-port type-aware lint, source and test typechecking,
51
+ SDK/declaration builds, production extension assets, and synthetic Chromium tests.
52
+ Browser evaluation and native receiver capture retain narrowly documented lint
53
+ exceptions because those operations are intentional features.
54
+
55
+ Check the extension's browser-only declarations separately:
56
+
57
+ ```bash
58
+ pnpm --filter @agimon-ai/po-browse-tool exec tsc --noEmit -p tsconfig.extension.json
59
+ ```
60
+
61
+ The production Vite build is not a substitute for this extension typecheck.
62
+
63
+ ## Usage with Claude Code
64
+
65
+ Add to your Claude Code configuration (`.mcp.json`):
66
+
67
+ ```json
68
+ {
69
+ "mcpServers": {
70
+ "browse-tool": {
71
+ "command": "npx",
72
+ "args": ["browse-tool", "mcp-serve"]
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ### CLI Options
79
+
80
+ ```bash
81
+ browse-tool mcp-serve [options]
82
+
83
+ Options:
84
+ -t, --type <type> Transport type: stdio, streamable-http (default: "stdio")
85
+ -b, --browser <type> Default browser: chromium, firefox, webkit (default: "chromium")
86
+ --headless Run browsers in headless mode
87
+ --no-headless Run browsers in headed mode (visible window)
88
+ --host <host> Default host for HTTP services (default: "localhost")
89
+ --port <port> Port for streamable HTTP transport (default: "3201")
90
+ -p, --profile <name> Default profile name for browser sessions
91
+ --custom-tools <path> Expose tools from a folder containing tools.yaml
92
+ --snippets-dir <path> Directory used by browser_run_code to save and load reusable snippets
93
+ ```
94
+
95
+ ### Streamable HTTP Transport
96
+
97
+ For clients that can connect over MCP streamable HTTP, start `mcp-serve` with:
98
+
99
+ ```bash
100
+ browse-tool mcp-serve --type streamable-http --host 127.0.0.1 --port 3201
101
+ ```
102
+
103
+ This exposes the MCP endpoint at `http://127.0.0.1:3201/mcp` and keeps the existing browse-tool HTTP service for browser execution on its own port.
104
+
105
+ This endpoint serves modern MCP `2026-07-28` only. Send `x-browse-owner: <stable-owner-id>` on **every** stateful MCP request, and send `x-profile: <profile-name>` on every request when pinning a profile. The owner is application routing identity, not authentication. Tool discovery works without an owner; tool calls without one return an error. Browser state survives individual HTTP requests and client disconnects, scoped to the owner and profile, and is cleaned up after inactivity or shutdown. Modern HTTP does not use `Mcp-Session-Id` or a legacy initialize request.
106
+
107
+ ## Custom Tools
108
+
109
+ Custom tools are loaded from a folder containing:
110
+
111
+ - `tools.yaml`
112
+ - one or more `.ts` scripts referenced by the manifest
113
+
114
+ `tools.yaml` entries support:
115
+
116
+ - `name`
117
+ - `description`
118
+ - `script`
119
+ - `capabilities`
120
+ - `inputSchema`
121
+ - optional `suggestionActions`
122
+ - a short text string describing what should be done next after the tool succeeds
123
+ - this value is exposed in custom-tool discovery and injected into the tool execution result
124
+
125
+ If you start the MCP server with `--custom-tools <path>`, those tools are added to the MCP tool list automatically.
126
+
127
+ ### Expose Custom Tools Through MCP
128
+
129
+ ```bash
130
+ browse-tool mcp-serve --custom-tools scripts/automation/linkedin-prospecting
131
+ ```
132
+
133
+ Claude Code example:
134
+
135
+ ```json
136
+ {
137
+ "mcpServers": {
138
+ "browse-tool": {
139
+ "command": "npx",
140
+ "args": ["browse-tool", "mcp-serve", "--custom-tools", "scripts/automation/linkedin-prospecting"]
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ ### Flat CLI Commands
147
+
148
+ List tools from a custom-tool directory:
149
+
150
+ ```bash
151
+ browse-tool list-custom-tools scripts/automation/linkedin-prospecting
152
+ ```
153
+
154
+ Execute a custom tool:
155
+
156
+ ```bash
157
+ browse-tool exec-custom-tool \
158
+ scripts/automation/linkedin-prospecting \
159
+ list_posts_from_linkedin_feed \
160
+ '{"pageId":"page-2","maxPosts":3}'
161
+ ```
162
+
163
+ ### Notes
164
+
165
+ - The flat commands talk to the browse-tool HTTP server.
166
+ - Custom tools are not exposed to MCP unless `mcp-serve` is started with `--custom-tools`.
167
+ - For slow UI-driven tools, prefer bounded inputs such as `maxPosts` so test runs stay manageable.
168
+ - If a manifest defines `suggestionActions`, browse-tool injects that text into the custom tool execution result.
169
+
170
+ ## Tool Reference
171
+
172
+ ### Input Tools (8)
173
+
174
+ | Tool | Description |
175
+ | --------------------- | ------------------------------------------ |
176
+ | `browser_click` | Click on an element |
177
+ | `browser_fill` | Fill a form field with text (clears first) |
178
+ | `browser_type` | Type text into an element (appends) |
179
+ | `browser_hover` | Hover over an element |
180
+ | `browser_select` | Select an option from a dropdown |
181
+ | `browser_drag` | Drag from one element to another |
182
+ | `browser_press_key` | Press a keyboard key |
183
+ | `browser_upload_file` | Upload a file to a file input |
184
+
185
+ ### Navigation Tools (5)
186
+
187
+ | Tool | Description |
188
+ | -------------------- | -------------------------------- |
189
+ | `browser_navigate` | Navigate to a URL |
190
+ | `browser_go_back` | Go back in browser history |
191
+ | `browser_go_forward` | Go forward in browser history |
192
+ | `browser_reload` | Reload the current page |
193
+ | `browser_wait_for` | Wait for an element or condition |
194
+
195
+ ### Snapshot Tools (3)
196
+
197
+ | Tool | Description |
198
+ | -------------------- | ------------------------------- |
199
+ | `browser_screenshot` | Take a screenshot |
200
+ | `browser_pdf` | Generate PDF of the page |
201
+ | `browser_snapshot` | Get accessibility tree snapshot |
202
+
203
+ ### Tab/Page Management Tools (4)
204
+
205
+ | Tool | Description |
206
+ | --------------------- | ------------------------- |
207
+ | `browser_list_pages` | List all open pages |
208
+ | `browser_select_page` | Switch to a specific page |
209
+ | `browser_new_page` | Open a new page |
210
+ | `browser_close_page` | Close a page |
211
+
212
+ ### Dialog/Network/Console Tools (7)
213
+
214
+ | Tool | Description |
215
+ | ------------------------------- | -------------------------------------------------- |
216
+ | `browser_handle_dialog` | Handle JavaScript dialogs (alert, confirm, prompt) |
217
+ | `browser_list_network_requests` | List captured network requests |
218
+ | `browser_get_network_request` | Get details of a specific network request |
219
+ | `browser_list_console_messages` | List console messages |
220
+ | `browser_evaluate_script` | Execute JavaScript in the page |
221
+ | `browser_resize_page` | Resize the browser viewport |
222
+ | `browser_emulate` | Emulate device, geolocation, or network conditions |
223
+
224
+ ### Code Tools (2)
225
+
226
+ | Tool | Description |
227
+ | ----------------------- | -------------------------------------------------------------------------------------------------- |
228
+ | `browser_run_code` | Execute inline code against `{ page, context, browser }`, optionally save it as a reusable snippet |
229
+ | `browser_list_snippets` | List saved snippets by name, description, and snippetPath |
230
+
231
+ ### Testing/Tracing Tools (3)
232
+
233
+ | Tool | Description |
234
+ | --------------------- | -------------------------------- |
235
+ | `browser_expect` | Assert conditions on elements |
236
+ | `browser_start_trace` | Start Playwright tracing |
237
+ | `browser_stop_trace` | Stop tracing and save trace file |
238
+
239
+ ### Profile Management Tools (4)
240
+
241
+ | Tool | Description |
242
+ | ---------------------------- | ------------------------------------- |
243
+ | `browser_list_profiles` | List all saved profiles |
244
+ | `browser_create_profile` | Create a new browser profile |
245
+ | `browser_delete_profile` | Delete a profile |
246
+ | `browser_save_profile_state` | Save current browser state to profile |
247
+
248
+ ## Saved Snippets
249
+
250
+ If you start `mcp-serve` or `http-serve` with `--snippets-dir <path>`, `browser_run_code` can persist inline snippets and later execute them again by `snippetPath`.
251
+
252
+ Save an inline snippet:
253
+
254
+ ```json
255
+ {
256
+ "tool": "browser_run_code",
257
+ "arguments": {
258
+ "pageId": "page-1",
259
+ "code": "return await page.title();",
260
+ "saveAs": {
261
+ "name": "Get Page Title",
262
+ "description": "Return the current page title"
263
+ }
264
+ }
265
+ }
266
+ ```
267
+
268
+ List saved snippets:
269
+
270
+ ```json
271
+ {
272
+ "tool": "browser_list_snippets",
273
+ "arguments": {}
274
+ }
275
+ ```
276
+
277
+ Run a saved snippet:
278
+
279
+ ```json
280
+ {
281
+ "tool": "browser_run_code",
282
+ "arguments": {
283
+ "pageId": "page-1",
284
+ "snippetPath": "get-page-title.ts"
285
+ }
286
+ }
287
+ ```
288
+
289
+ ## Profile Management
290
+
291
+ Profiles allow you to persist browser state (cookies, localStorage, etc.) across sessions.
292
+
293
+ ### Create a Profile
294
+
295
+ ```json
296
+ {
297
+ "tool": "browser_create_profile",
298
+ "arguments": {
299
+ "name": "my-profile",
300
+ "browser": "chromium",
301
+ "headless": false
302
+ }
303
+ }
304
+ ```
305
+
306
+ ### Save Profile State
307
+
308
+ ```json
309
+ {
310
+ "tool": "browser_save_profile_state",
311
+ "arguments": {
312
+ "sessionId": "automation-1",
313
+ "profileName": "my-profile"
314
+ }
315
+ }
316
+ ```
317
+
318
+ ## Proxy Configs
319
+
320
+ `browser_launch` accepts an explicit proxy object, or `proxy: true` to load `session.yaml` from the proxy config directory.
321
+
322
+ ```json
323
+ {
324
+ "tool": "browser_launch",
325
+ "arguments": {
326
+ "profileName": "work",
327
+ "proxy": true
328
+ }
329
+ }
330
+ ```
331
+
332
+ By default, proxy configs live beside the profiles directory. With the default profiles directory, this is `~/.browse-tool/proxy-config`.
333
+
334
+ Files are resolved in this order:
335
+
336
+ 1. Explicit `proxy` object in the `browser_launch` arguments
337
+ 2. `{proxy-config}/{profileName}.yaml`
338
+ 3. `{proxy-config}/session.yaml` when `proxy: true` is used
339
+ 4. `{proxy-config}/default.yaml`
340
+
341
+ Example YAML:
342
+
343
+ ```yaml
344
+ server: http://proxy.example.com:8080
345
+ username: ${PROXY_USER}
346
+ password: ${PROXY_PASSWORD}
347
+ bypass: localhost,127.0.0.1
348
+ ```
349
+
350
+ Set a custom directory with `--proxy-config-dir <path>` or `BROWSE_TOOL_PROXY_CONFIG_DIR`.
351
+
352
+ ## Configuration
353
+
354
+ ### Environment Variables
355
+
356
+ | Variable | Default | Description |
357
+ | ----------------------------------------- | ------------------------------------- | -------------------------------------------------------------------------- |
358
+ | `PLAYWRIGHT_PROFILES_DIR` | `~/.browse-tool/profiles` | Directory for storing browser profiles |
359
+ | `BROWSE_TOOL_PROXY_CONFIG_DIR` | beside profiles dir as `proxy-config` | Directory for browser proxy YAML configs |
360
+ | `PLAYWRIGHT_REGISTRY_DIR` | `.agiflow/registry` | Directory for HTTP server registry |
361
+ | `PLAYWRIGHT_PIDS_DIR` | `.pids` | Directory for tracking server PIDs |
362
+ | `PLAYWRIGHT_BROWSER_IDLE_TIMEOUT_MINUTES` | `30` | Minutes a browser may sit idle before it is closed |
363
+ | `BROWSE_TOOL_SERVER_IDLE_TIMEOUT_MINUTES` | `60` | Minutes the daemon may sit idle before it shuts itself down (`0` disables) |
364
+ | `NODE_ENV` | `development` | Runtime environment (`development`, `production`) |
365
+
366
+ ### CLI Flags vs Environment Variables
367
+
368
+ CLI flags take precedence over environment variables. For example, `--profiles-dir /custom/path` overrides `PLAYWRIGHT_PROFILES_DIR`.
369
+
370
+ ### Idle Cleanup
371
+
372
+ A background sweep runs every 60 seconds and tears down resources no agent is working on:
373
+
374
+ - **Pages** idle for more than 15 minutes are closed.
375
+ - **Browsers** idle for more than the browser idle timeout (default 30 minutes) are closed, including their whole process tree.
376
+ - **Browsers with no pages left** use the shorter 15-minute timeout.
377
+ - **Stealth-mode** Chrome processes are swept on the same browser timeout.
378
+ - **The daemon itself** exits once it has served no request and owns no browsers for the server idle timeout (default 60 minutes), releasing its port and process registry rows on the way out. Set `BROWSE_TOOL_SERVER_IDLE_TIMEOUT_MINUTES=0` to keep it running forever. Health checks do not count as requests: discovery polls them on daemons it never goes on to use.
379
+
380
+ Idle is measured from the last real agent activity (a tool call against the browser, an extension task result, or a recording chunk upload). An open extension WebSocket does **not** count as activity — the extension pings and reconnects on its own, so a live socket would otherwise keep a forgotten browser alive forever. An in-progress recording does keep its browser and pages alive, because each uploaded chunk counts as activity.
381
+
382
+ Set the timeout with `--idle-timeout <minutes>`, `commands.httpServe.idleTimeout` / `commands.mcpServe.idleTimeout` in `.browse-tool/config.json`, or `PLAYWRIGHT_BROWSER_IDLE_TIMEOUT_MINUTES`.
383
+
384
+ > **Note:** the HTTP daemon is long-lived and shared across worktrees, and it reads the idle timeout once at startup. A later client that attaches to an already-running daemon cannot change that daemon's timeout — restart the daemon (`browse-tool stop`) to apply a new value.
385
+
386
+ > **Note:** `browse-tool stop` only stops daemons registered under the current worktree, so it will not take browsers away from an agent working in another one. Use `browse-tool stop --all` when you do want to clear every worktree's daemon.
387
+
388
+ ## Error Handling
389
+
390
+ ### Common Failure Modes
391
+
392
+ | Failure | Cause | Recovery |
393
+ | ------------------------ | -------------------------------------------------- | --------------------------------------------------------------------- |
394
+ | Browser launch failure | Missing browser binary or incompatible version | Run `npx playwright install chromium` to install browsers |
395
+ | Navigation timeout | Page took too long to load | Increase timeout via `browser_wait_for` or check network connectivity |
396
+ | Element not found | Selector did not match any element | Use `browser_snapshot` to inspect the current DOM tree |
397
+ | Profile not found | Referenced profile does not exist | Use `browser_list_profiles` to list available profiles |
398
+ | Session expired | Browser context was closed or crashed | Launch a new browser session with `browser_navigate` |
399
+ | Spec bundler build error | Syntax errors or unresolvable imports in spec file | Check the spec file for errors; see `SpecBundlerBuildError.recovery` |
400
+ | Spec bundler CLI error | `bun` not installed or not in PATH | Install bun: `curl -fsSL https://bun.sh/install \| bash` |
401
+
402
+ ### Error Codes
403
+
404
+ All structured errors include a `code` field for programmatic handling and a `recovery` field with suggested resolution steps.
405
+
406
+ ## Spec Bundler
407
+
408
+ The `SpecBundlerService` bundles Playwright spec files before execution, replacing `@playwright/test` imports with a lightweight stub module so specs can run inside the MCP server.
409
+
410
+ ### `testInfo.attach` compatibility
411
+
412
+ `run_spec` provides a stable subset of Playwright `TestInfo` to test callbacks and per-test hooks:
413
+
414
+ ```typescript
415
+ test('captures evidence', async ({ page }, testInfo) => {
416
+ await testInfo.attach('dashboard', {
417
+ body: await page.screenshot(),
418
+ contentType: 'image/png',
419
+ });
420
+ });
421
+ ```
422
+
423
+ Attachments may use either `body` or `path`. The runner copies the evidence to its temporary attachment directory and returns `{ name, contentType, path }` metadata on the matching test result. Accessing an unsupported `TestInfo` property fails with `RUN_SPEC_UNSUPPORTED_TEST_INFO_CAPABILITY` so compatibility failures remain distinct from browser and product failures.
424
+
425
+ ### Runtime Requirements
426
+
427
+ - **Bun runtime** (preferred): When the MCP server runs under Bun, `Bun.build()` is used directly.
428
+ - **Node.js runtime** (fallback): When running under Node.js, the service spawns `bun` as a CLI subprocess. Ensure `bun` is installed and accessible in `PATH`:
429
+ ```bash
430
+ curl -fsSL https://bun.sh/install | bash
431
+ # or
432
+ npm i -g bun
433
+ ```
434
+
435
+ ### Programmatic Usage
436
+
437
+ ```typescript
438
+ import { SpecBundlerService } from 'browse-tool';
439
+
440
+ const bundler = new SpecBundlerService();
441
+ const { outputPath, cleanup } = await bundler.bundle('/path/to/my-spec.ts');
442
+ try {
443
+ // Use the bundled output at outputPath
444
+ } finally {
445
+ await cleanup();
446
+ }
447
+ ```
448
+
449
+ ### Error Handling
450
+
451
+ | Error Class | Code | When |
452
+ | -------------------------- | --------------------------- | ------------------------------------------- |
453
+ | `SpecBundlerBuildError` | `SPEC_BUNDLER_BUILD_FAILED` | Bundler reports syntax or resolution errors |
454
+ | `SpecBundlerNoOutputError` | `SPEC_BUNDLER_NO_OUTPUT` | Bundling produces no output file |
455
+ | `SpecBundlerCliError` | `SPEC_BUNDLER_CLI_FAILED` | `bun` CLI subprocess fails to execute |
456
+
457
+ ## Architecture
458
+
459
+ ```
460
+ browse-tool/
461
+ ├── src/
462
+ │ ├── commands/ # CLI commands (mcp-serve)
463
+ │ ├── container/ # InversifyJS IoC container
464
+ │ ├── server/ # MCP server factory
465
+ │ ├── services/ # Core services
466
+ │ │ ├── AutomationRunner.ts # Script execution
467
+ │ │ ├── BrowserService.ts # Browser lifecycle
468
+ │ │ ├── PageRegistry.ts # Page tracking
469
+ │ │ ├── PauseController.ts # Pause/resume flow
470
+ │ │ ├── ProfileService.ts # Profile management
471
+ │ │ └── SessionService.ts # Session tracking
472
+ │ ├── tools/ # MCP tool implementations
473
+ │ ├── transports/ # Transport handlers (stdio, streamable HTTP)
474
+ │ └── types/ # TypeScript type definitions
475
+ ├── tests/
476
+ │ ├── services/ # Service unit tests
477
+ │ ├── tools/ # Tool unit tests
478
+ │ ├── integration/ # Integration tests
479
+ │ └── fixtures/ # Test fixtures
480
+ └── README.md
481
+ ```
482
+
483
+ ## License
484
+
485
+ MIT
@@ -0,0 +1,2 @@
1
+ import{o as e,s as t,t as n}from"./timeouts-CUKtmglu.mjs";import{t as r}from"./decorateMetadata-D0fK5b4i.mjs";import{t as i}from"./decorate-B3KLIx2E.mjs";import{s as a}from"./networkConfig-C_01QWPz.mjs";import{n as o,o as s}from"./telemetryContext-Bs3fgsz2.mjs";import"reflect-metadata/lite";import{inject as c,injectable as l,optional as u}from"inversify";import{dirname as d}from"node:path";import{mkdirSync as f,writeFileSync as p}from"node:fs";import{AsyncLocalStorage as m}from"node:async_hooks";import{createNodeTelemetry as h,resolveNodeTelemetryEndpoints as g}from"@agimon-ai/log-sink-mcp/telemetry/node";import{SpanKind as _,SpanStatusCode as v,context as y,trace as b}from"@opentelemetry/api";import{OTLPTraceExporter as x}from"@opentelemetry/exporter-trace-otlp-http";import{resourceFromAttributes as S}from"@opentelemetry/resources";import{BatchSpanProcessor as C,NodeTracerProvider as w,SimpleSpanProcessor as T}from"@opentelemetry/sdk-trace-node";const E=`@agimon-ai/po-browse-tool`,D=/* @__PURE__ */ new Set([`127.0.0.1`,`localhost`,`::1`,`0.0.0.0`]);function O(e){return e===`1`||e===`true`}function k(e){return Array.isArray(e)?JSON.stringify(e):typeof e==`string`||typeof e==`number`||typeof e==`boolean`?e:JSON.stringify(e)}function A(e){if(!e)return;let t={};for(let[n,r]of Object.entries(e))r!=null&&(t[n]=k(r));return Object.keys(t).length>0?t:void 0}function j(e){if(!e)return;let t={};for(let n of e.split(`,`)){let[e,...r]=n.split(`=`),i=e?.trim(),a=r.join(`=`).trim();i&&a&&(t[i]=a)}return Object.keys(t).length>0?t:void 0}function M(e){if(!e)return;let t=Number(e);return Number.isFinite(t)&&t>0?t:void 0}function N(e,t){let n=e.endsWith(`/`)?e:`${e}/`;return n.endsWith(`/v1/${t}/`)||n.endsWith(`/v1/${t}`)?e:new URL(`v1/${t}`,n).toString()}function P(e,t){return(t===`traces`?e.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:e.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT)||(e.OTEL_EXPORTER_OTLP_ENDPOINT?N(e.OTEL_EXPORTER_OTLP_ENDPOINT,t):void 0)}function F(e){return D.has(e)}function I(e,t){try{let n=new URL(e),r=new URL(t);return!F(n.hostname)||F(r.hostname)||(n.hostname=r.hostname),n.toString()}catch{return e}}async function L(e,t,n){let r=await g({env:e,packageName:E,workspaceRoot:a({env:e}),healthCheck:n?.healthCheck});return t===`traces`?r.tracesEndpoint:r.logsEndpoint}async function R(e,t,n,r){let i=t===`traces`?e.BROWSE_TOOL_OTEL_BROWSER_TRACES_ENDPOINT:e.BROWSE_TOOL_OTEL_BROWSER_LOGS_ENDPOINT;if(i)return I(i,n);let a=e.BROWSE_TOOL_OTEL_BROWSER_ENDPOINT;if(a)return I(N(a,t),n);let o=await L(e,t,r);return o?I(o,n):void 0}async function z(e,t,n){let r=e.BROWSE_TOOL_OTEL_BROWSER_SERVICE_NAME??`browse-tool-extension`,[i,a]=await Promise.all([R(e,`traces`,t,n),R(e,`logs`,t,n)]);return{enabled:!!(i||a),tracesEndpoint:i,logsEndpoint:a,serviceName:r}}function B(e,t){let n=t===`traces`?e.OTEL_EXPORTER_OTLP_TRACES_HEADERS:e.OTEL_EXPORTER_OTLP_LOGS_HEADERS,r=t===`traces`?e.OTEL_EXPORTER_OTLP_TRACES_TIMEOUT:e.OTEL_EXPORTER_OTLP_LOGS_TIMEOUT;return{url:P(e,t),headers:j(n??e.OTEL_EXPORTER_OTLP_HEADERS),timeoutMillis:M(r??e.OTEL_EXPORTER_OTLP_TIMEOUT)}}function V(e){return e instanceof Error?e.message:String(e)}function H(e){if(e instanceof Error)return A({"exception.type":e.name,"exception.message":e.message,"exception.stacktrace":e.stack})}let U=class{contextStore=new m;tracesEnabled;logsEnabled;tracerProvider;tracer;logSinkReady;delegateReady;delegateHandle=null;constructor(e={}){let t=e.env??process.env,n=O(t.OTEL_SDK_DISABLED),r=O(t.BROWSE_TOOL_OTEL_ENABLED),i=B(t,`traces`),o=!!e.traceExporter;this.tracesEnabled=!n&&(o||r||!!i.url),this.logsEnabled=!n;let s=e.serviceName??t.BROWSE_TOOL_OTEL_SERVICE_NAME??`browse-tool-http`,c=e.packageName??E,l=e.serviceVersion??t.npm_package_version,u={"service.name":s};l&&(u[`service.version`]=l);let d=S(u);if(this.tracesEnabled&&o){let t=e.traceExporter??new x(i);this.tracerProvider=new w({resource:d,spanProcessors:[e.traceExporter?new T(t):new C(t)]})}this.tracer=this.tracerProvider?.getTracer(s,l)??b.getTracer(s,l),this.delegateReady=!n&&!o?h({env:t,packageName:c,serviceName:s,serviceVersion:l,workspaceRoot:a(),enableTraces:!0,enableLogs:!0}).then(e=>(this.delegateHandle=e,e)).catch(()=>null):Promise.resolve(null),this.logSinkReady=this.logsEnabled&&o?h({env:t,packageName:c,serviceName:s,serviceVersion:l,workspaceRoot:a(),enableTraces:!1,enableLogs:!0}).catch(()=>null):this.delegateReady}isEnabled(){return this.tracesEnabled||this.logsEnabled}getActiveTraceContext(){let e=this.delegateHandle?.getTraceContext();if(e?.traceId||e?.spanId)return e;let t=this.contextStore.getStore();if(!t)return{};let n=b.getSpan(t);if(!n)return{};let r=n.spanContext();return{traceId:r.traceId,spanId:r.spanId}}async runInSpan(e,t,n){let r=await this.delegateReady;if(r)return await r.runInSpan(e,t,n);if(!this.tracesEnabled||!this.tracerProvider)return await n(void 0);let i=this.contextStore.getStore()??y.active(),a=this.tracer.startSpan(e,{kind:t.kind??_.INTERNAL,attributes:A({...s(o()),...t.attributes})},i),c=b.setSpan(i,a);return await this.contextStore.run(c,async()=>{try{return await n(a)}catch(e){throw a.recordException(e instanceof Error?e:Error(V(e))),a.setStatus({code:v.ERROR,message:V(e)}),e}finally{a.end()}})}log(e,t,n={}){this.logsEnabled&&this.logSinkReady.then(r=>{if(!r)return;let i=n.context??this.contextStore.getStore()??y.active(),a=H(n.exception);r.logger[e](t,{attributes:A({...s(o()),...n.attributes,...a}),context:i,exception:n.exception})})}async forceFlush(){let[e,t]=await Promise.all([this.logSinkReady,this.delegateReady]);await Promise.allSettled([this.tracerProvider?.forceFlush(),e?.flush(),t?.flush()])}async shutdown(){let[e,t]=await Promise.all([this.logSinkReady,this.delegateReady]);await Promise.allSettled([this.tracerProvider?.shutdown(),e?.shutdown(),t?.shutdown()])}};U=i([l(),r(`design:paramtypes`,[Object])],U);const W=/* @__PURE__ */ new Set([`browser_close_page`,`browser_handle_dialog`,`browser_reload`]);var G=class extends Error{targetKey;retryAfterMs;code=`BROWSER_EXTENSION_TASK_TIMEOUT`;retryable;targetState;constructor(e,t,n){super(e),this.targetKey=t,this.retryAfterMs=n,this.name=`ExtensionTaskTimeoutError`,this.retryable=t!==void 0,this.targetState=t?`recovery_required`:`timed_out`}},K=class extends Error{targetKey;code=`BROWSER_EXTENSION_TARGET_RECOVERING`;retryable=!0;targetState;retryAfterMs;constructor(e,t){let n=typeof t==`number`;super(n?`Browser target ${e} is recovering from a timed-out task. Retry after ${t}ms or select another page.`:`Browser target ${e} requires recovery after a timed-out task. Run browser_handle_dialog, browser_reload, or browser_close_page before retrying regular work.`),this.targetKey=e,this.name=`ExtensionTargetRecoveringError`,this.targetState=n?`recovering`:`recovery_required`,this.retryAfterMs=n?t:0}};let q=class{webSocketHub;telemetry;pageRegistry;pendingTasks=/* @__PURE__ */ new Map;taskQueue=[];lastPollAt;lastResultAt;maxQueueSize=100;defaultTimeoutMs=Math.min(n,3e4);targetCircuits=/* @__PURE__ */ new Set;warnedAboutUnscopedPolling=!1;constructor(e,t=new U,n){this.webSocketHub=e,this.telemetry=t,this.pageRegistry=n}async queueTask(e,t,n=this.defaultTimeoutMs,r){if(this.pendingTasks.size>=this.maxQueueSize)throw Error(`Task queue full (max ${this.maxQueueSize} tasks)`);let i=typeof t.pageId==`string`?t.pageId:void 0,a=r??t.browserId??this.resolveBrowserIdFromPage(i),o=a?void 0:this.resolveSingleConnectionBrowserId(),s=i??a??o;if(s&&this.targetCircuits.has(s)&&!W.has(e))throw new K(s);let c={id:crypto.randomUUID(),tool:e,arguments:t,createdAt:/* @__PURE__ */ new Date,timeoutMs:n,browserId:a??o,pageId:i,telemetry:this.getTaskTelemetryContext()};return new Promise((e,t)=>{let r=setTimeout(()=>{this.pendingTasks.delete(c.id);let e=this.taskQueue.findIndex(e=>e.id===c.id);e!==-1&&this.taskQueue.splice(e,1),s&&(this.targetCircuits.add(s),this.rejectPendingTasksForTarget(s,c.id));let r=a??o;r&&this.webSocketHub?.cancelTask(r,c.id,`Timed out after ${n}ms`),t(new G(`Task ${c.id} timed out after ${n}ms. The target requires an explicit recovery tool before regular work can resume.`,s,0))},n);if(this.pendingTasks.set(c.id,{task:c,resolve:e,reject:t,timeoutId:r}),this.webSocketHub){let e=a??o;if(e&&this.pushTaskViaWebSocket(c,e))return}this.taskQueue.push(c)})}pushTaskViaWebSocket(e,t){if(!this.webSocketHub)return!1;let n={type:`task:push`,id:crypto.randomUUID(),payload:{taskId:e.id,tool:e.tool,arguments:e.arguments,telemetry:e.telemetry}};return this.webSocketHub.pushTask(t,n)}getNextTask(e){if(this.lastPollAt=/* @__PURE__ */ new Date,e!==void 0){let t=this.taskQueue.findIndex(t=>t.browserId===e||t.browserId===void 0);return t===-1?void 0:this.taskQueue.splice(t,1)[0]}if(new Set(this.taskQueue.map(e=>e.browserId).filter(e=>e!==void 0)).size<=1)return this.taskQueue.shift();this.warnedAboutUnscopedPolling||(this.warnedAboutUnscopedPolling=!0,console.warn(`[ExtensionTaskQueue] An extension is polling without a browserId while several browsers have queued work. Update the extension so its tasks can be routed.`));let t=this.taskQueue.findIndex(e=>e.browserId===void 0);return t===-1?void 0:this.taskQueue.splice(t,1)[0]}failPendingTasksForBrowser(e,t){for(let[n,r]of this.pendingTasks)r.task.browserId===e&&(clearTimeout(r.timeoutId),r.reject(Error(t)),this.pendingTasks.delete(n));this.taskQueue=this.taskQueue.filter(t=>t.browserId!==e)}submitResult(e){this.lastResultAt=/* @__PURE__ */ new Date;let t=this.pendingTasks.get(e.taskId);if(!t)return;clearTimeout(t.timeoutId),this.pendingTasks.delete(e.taskId);let n=this.getTaskTargetKey(t.task);return n&&W.has(t.task.tool)&&e.success&&e.result?.isError!==!0&&this.targetCircuits.delete(n),t.resolve(e),t.task}getConnectionStatus(){let e=/* @__PURE__ */ new Date,t=this.lastPollAt?e.getTime()-this.lastPollAt.getTime()<5e3:!1,n=this.webSocketHub?this.webSocketHub.getStats().totalConnections>0:!1;return{connected:t||n,lastPollAt:this.lastPollAt,lastResultAt:this.lastResultAt,pendingTasks:this.pendingTasks.size}}purgeTargets(e,t){let n=new Set(e);if(n.size===0)return;for(let e of n)this.targetCircuits.delete(e);let r=e=>e.pageId!==void 0&&n.has(e.pageId)||e.browserId!==void 0&&n.has(e.browserId);for(let[e,n]of this.pendingTasks)r(n.task)&&(clearTimeout(n.timeoutId),n.reject(Error(t)),this.pendingTasks.delete(e));this.taskQueue=this.taskQueue.filter(e=>!r(e))}clearAllTasks(e){for(let[t,n]of this.pendingTasks)clearTimeout(n.timeoutId),n.reject(Error(e)),this.pendingTasks.delete(t);this.taskQueue.length=0}get queueSize(){return this.taskQueue.length}get pendingCount(){return this.pendingTasks.size}getTaskTelemetryContext(){let e=this.telemetry.getActiveTraceContext();if(e.traceId||e.spanId)return{traceId:e.traceId,parentSpanId:e.spanId}}resolveBrowserIdFromPage(e){if(e)return this.pageRegistry?.get(e)?.browserId}rejectPendingTasksForTarget(e,t){for(let[n,r]of this.pendingTasks){if(n===t||this.getTaskTargetKey(r.task)!==e)continue;clearTimeout(r.timeoutId),this.pendingTasks.delete(n);let i=this.taskQueue.findIndex(e=>e.id===n);i!==-1&&this.taskQueue.splice(i,1),r.task.browserId&&this.webSocketHub?.cancelTask(r.task.browserId,n,`Target ${e} is recovering from another timed-out task`),r.reject(new K(e))}}getTaskTargetKey(e){return e.pageId??e.browserId}resolveSingleConnectionBrowserId(){if(!this.webSocketHub)return;let e=this.webSocketHub.getStats();if(e.totalConnections!==0){if(new Set(e.connections.map(e=>e.browserId)).size>1)throw Error(`Ambiguous extension task target: provide browserId or pageId when multiple browsers are connected`);return e.connections[0]?.browserId}}};q=i([l(),e(0,c(t.WebSocketHub)),e(0,u()),e(1,c(t.TelemetryService)),e(1,u()),e(2,c(t.PageRegistry)),e(2,u()),r(`design:paramtypes`,[Object,Object,Object])],q);function J(e){return{content:[{type:`text`,text:e.message}],structuredContent:{error:{code:e.code,retryable:e.retryable,targetKey:e.targetKey,targetState:e.targetState,retryAfterMs:e.retryAfterMs}},isError:!0}}const Y=/* @__PURE__ */ new Set(/* @__PURE__ */ `browser_navigate.browser_go_back.browser_go_forward.browser_reload.browser_click.browser_fill.browser_type.browser_upload_file.browser_select.browser_hover.browser_drag.browser_press_key.browser_inspect_element.browser_resolve_locator.browser_pdf.browser_screenshot.browser_snapshot.browser_list_pages.browser_new_page.browser_select_page.browser_close_page.browser_wait_for.browser_evaluate_script.browser_resize_page.browser_handle_dialog.browser_emulate.browser_list_network_requests.browser_get_network_request.browser_list_console_messages.browser_expect`.split(`.`)),X=/* @__PURE__ */ new Set([`browser_launch`,`browser_list_profiles`,`browser_create_profile`,`browser_delete_profile`,`browser_save_profile_state`,`browser_start_trace`,`browser_stop_trace`,`run_spec`,`discover_specs`]);let Z=class{taskQueue;constructor(e){this.taskQueue=e}isToolSupported(e){return Y.has(e)}isToolNotApplicable(e){return X.has(e)}async executeTool(e,t){if(this.isToolNotApplicable(e))return{content:[{type:`text`,text:`Tool "${e}" is not available in Chrome extension mode. This tool requires Playwright and cannot be run through the extension.`}],isError:!0};if(!this.isToolSupported(e))return{content:[{type:`text`,text:`Tool "${e}" is not yet implemented in Chrome extension mode.`}],isError:!0};if(!this.taskQueue.getConnectionStatus().connected)return{content:[{type:`text`,text:`Chrome extension is not connected. Please ensure the extension is running and connected to the server.`}],isError:!0};try{let n=typeof t.timeout==`number`&&Number.isFinite(t.timeout)?t.timeout:void 0,r=await this.taskQueue.queueTask(e,t,n);return r.success?r.result?this.persistScreenshotPath(e,t,r.result):{content:[{type:`text`,text:`Tool executed successfully`}]}:{content:[{type:`text`,text:r.error||r.result?.content?.[0]?.text||`Unknown error from Chrome extension`}],isError:!0}}catch(e){return e instanceof G||e instanceof K?J(e):{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}}persistScreenshotPath(e,t,n){let r=typeof t.path==`string`?t.path.trim():``,i=n.content[0];return e!==`browser_screenshot`||!r||i?.type!==`image`||typeof i.data!=`string`?n:(f(d(r),{recursive:!0}),p(r,Buffer.from(i.data,`base64`)),{content:[{type:`text`,text:`Screenshot saved to: ${r}`}]})}getSupportedTools(){return Array.from(Y)}getNotApplicableTools(){return Array.from(X)}};Z=i([l(),e(0,c(t.ExtensionTaskQueue)),r(`design:paramtypes`,[Object])],Z);export{z as i,q as n,U as r,Z as t};
2
+ //# sourceMappingURL=ExtensionToolDelegator-5tCMdpOX.mjs.map