@elizaos/plugin-browser 2.0.0-beta.1 → 2.0.3-beta.3

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 (196) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -64
  3. package/dist/actions/browser-autofill-login.d.ts.map +1 -1
  4. package/dist/actions/browser-autofill-login.js.map +1 -1
  5. package/dist/actions/browser.d.ts +5 -6
  6. package/dist/actions/browser.d.ts.map +1 -1
  7. package/dist/actions/browser.js +312 -60
  8. package/dist/actions/browser.js.map +1 -1
  9. package/dist/actions/manage-browser-bridge.d.ts.map +1 -1
  10. package/dist/actions/manage-browser-bridge.js +10 -14
  11. package/dist/actions/manage-browser-bridge.js.map +1 -1
  12. package/dist/actions/wait-for-url-predicate.d.ts +34 -0
  13. package/dist/actions/wait-for-url-predicate.d.ts.map +1 -0
  14. package/dist/actions/wait-for-url-predicate.js +33 -0
  15. package/dist/actions/wait-for-url-predicate.js.map +1 -0
  16. package/dist/actions/wait-for-url.d.ts +64 -0
  17. package/dist/actions/wait-for-url.d.ts.map +1 -0
  18. package/dist/actions/wait-for-url.js +89 -0
  19. package/dist/actions/wait-for-url.js.map +1 -0
  20. package/dist/bridge-policy.d.ts +10 -0
  21. package/dist/bridge-policy.d.ts.map +1 -0
  22. package/dist/bridge-policy.js +37 -0
  23. package/dist/bridge-policy.js.map +1 -0
  24. package/dist/bridge-readiness.d.ts +16 -0
  25. package/dist/bridge-readiness.d.ts.map +1 -0
  26. package/dist/bridge-readiness.js +82 -0
  27. package/dist/bridge-readiness.js.map +1 -0
  28. package/dist/bridge-records.d.ts +9 -0
  29. package/dist/bridge-records.d.ts.map +1 -0
  30. package/dist/bridge-records.js +37 -0
  31. package/dist/bridge-records.js.map +1 -0
  32. package/dist/browser-capture-hooks.d.ts +9 -0
  33. package/dist/browser-capture-hooks.d.ts.map +1 -0
  34. package/dist/browser-capture-hooks.js +15 -0
  35. package/dist/browser-capture-hooks.js.map +1 -0
  36. package/dist/browser-service.d.ts +22 -4
  37. package/dist/browser-service.d.ts.map +1 -1
  38. package/dist/browser-service.js +63 -15
  39. package/dist/browser-service.js.map +1 -1
  40. package/dist/browser-workspace-hooks.d.ts +14 -0
  41. package/dist/browser-workspace-hooks.d.ts.map +1 -0
  42. package/dist/browser-workspace-hooks.js +15 -0
  43. package/dist/browser-workspace-hooks.js.map +1 -0
  44. package/dist/companion-auth.d.ts +34 -0
  45. package/dist/companion-auth.d.ts.map +1 -0
  46. package/dist/companion-auth.js +98 -0
  47. package/dist/companion-auth.js.map +1 -0
  48. package/dist/index.d.ts +12 -3
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +66 -12
  51. package/dist/index.js.map +1 -1
  52. package/dist/message-adapter.d.ts +9 -0
  53. package/dist/message-adapter.d.ts.map +1 -0
  54. package/dist/message-adapter.js +104 -0
  55. package/dist/message-adapter.js.map +1 -0
  56. package/dist/packaging.d.ts.map +1 -1
  57. package/dist/packaging.js +2 -0
  58. package/dist/packaging.js.map +1 -1
  59. package/dist/parity/browser-matrix.d.ts +45 -0
  60. package/dist/parity/browser-matrix.d.ts.map +1 -0
  61. package/dist/parity/browser-matrix.js +361 -0
  62. package/dist/parity/browser-matrix.js.map +1 -0
  63. package/dist/parity/index.d.ts +5 -0
  64. package/dist/parity/index.d.ts.map +1 -0
  65. package/dist/parity/index.js +13 -0
  66. package/dist/parity/index.js.map +1 -0
  67. package/dist/password-manager-bridge.d.ts +50 -0
  68. package/dist/password-manager-bridge.d.ts.map +1 -0
  69. package/dist/password-manager-bridge.js +437 -0
  70. package/dist/password-manager-bridge.js.map +1 -0
  71. package/dist/plugin.d.ts.map +1 -1
  72. package/dist/plugin.js +8 -4
  73. package/dist/plugin.js.map +1 -1
  74. package/dist/providers/workspace.d.ts +1 -1
  75. package/dist/providers/workspace.js.map +1 -1
  76. package/dist/routes/bridge.d.ts.map +1 -1
  77. package/dist/routes/bridge.js +63 -14
  78. package/dist/routes/bridge.js.map +1 -1
  79. package/dist/routes/workspace-setup.d.ts.map +1 -1
  80. package/dist/routes/workspace-setup.js +1 -1
  81. package/dist/routes/workspace-setup.js.map +1 -1
  82. package/dist/routes/workspace.d.ts +1 -2
  83. package/dist/routes/workspace.d.ts.map +1 -1
  84. package/dist/routes/workspace.js +104 -4
  85. package/dist/routes/workspace.js.map +1 -1
  86. package/dist/schema.d.ts +2 -2
  87. package/dist/schema.js.map +1 -1
  88. package/dist/service.d.ts +1 -1
  89. package/dist/service.d.ts.map +1 -1
  90. package/dist/service.js.map +1 -1
  91. package/dist/targets/bridge-target.d.ts +1 -1
  92. package/dist/targets/bridge-target.d.ts.map +1 -1
  93. package/dist/targets/bridge-target.js.map +1 -1
  94. package/dist/targets/stagehand-target.d.ts +3 -0
  95. package/dist/targets/stagehand-target.d.ts.map +1 -0
  96. package/dist/targets/stagehand-target.js +187 -0
  97. package/dist/targets/stagehand-target.js.map +1 -0
  98. package/dist/workspace/browser-capture.d.ts +1 -1
  99. package/dist/workspace/browser-capture.d.ts.map +1 -1
  100. package/dist/workspace/browser-capture.js +33 -1
  101. package/dist/workspace/browser-capture.js.map +1 -1
  102. package/dist/workspace/browser-workspace-desktop.d.ts +1 -1
  103. package/dist/workspace/browser-workspace-desktop.d.ts.map +1 -1
  104. package/dist/workspace/browser-workspace-desktop.js +66 -30
  105. package/dist/workspace/browser-workspace-desktop.js.map +1 -1
  106. package/dist/workspace/browser-workspace-errors.d.ts +62 -0
  107. package/dist/workspace/browser-workspace-errors.d.ts.map +1 -0
  108. package/dist/workspace/browser-workspace-errors.js +69 -0
  109. package/dist/workspace/browser-workspace-errors.js.map +1 -0
  110. package/dist/workspace/browser-workspace-forms.d.ts.map +1 -1
  111. package/dist/workspace/browser-workspace-forms.js +1 -1
  112. package/dist/workspace/browser-workspace-forms.js.map +1 -1
  113. package/dist/workspace/browser-workspace-helpers.d.ts +7 -0
  114. package/dist/workspace/browser-workspace-helpers.d.ts.map +1 -1
  115. package/dist/workspace/browser-workspace-helpers.js +64 -6
  116. package/dist/workspace/browser-workspace-helpers.js.map +1 -1
  117. package/dist/workspace/browser-workspace-network.d.ts +1 -1
  118. package/dist/workspace/browser-workspace-network.d.ts.map +1 -1
  119. package/dist/workspace/browser-workspace-types.d.ts +15 -0
  120. package/dist/workspace/browser-workspace-types.d.ts.map +1 -1
  121. package/dist/workspace/browser-workspace-types.js.map +1 -1
  122. package/dist/workspace/browser-workspace-web.d.ts.map +1 -1
  123. package/dist/workspace/browser-workspace-web.js +34 -93
  124. package/dist/workspace/browser-workspace-web.js.map +1 -1
  125. package/dist/workspace/browser-workspace.d.ts +1 -1
  126. package/dist/workspace/browser-workspace.d.ts.map +1 -1
  127. package/dist/workspace/browser-workspace.js +9 -4
  128. package/dist/workspace/browser-workspace.js.map +1 -1
  129. package/dist/workspace/index.d.ts +1 -0
  130. package/dist/workspace/index.d.ts.map +1 -1
  131. package/dist/workspace/index.js +1 -0
  132. package/dist/workspace/index.js.map +1 -1
  133. package/package.json +29 -7
  134. package/registry-entry.json +75 -0
  135. package/dist/actions/browser-autofill-login.d.js +0 -1
  136. package/dist/actions/browser-autofill-login.d.js.map +0 -1
  137. package/dist/actions/browser.d.js +0 -1
  138. package/dist/actions/browser.d.js.map +0 -1
  139. package/dist/actions/manage-browser-bridge.d.js +0 -1
  140. package/dist/actions/manage-browser-bridge.d.js.map +0 -1
  141. package/dist/ambient-jsdom.d.js +0 -1
  142. package/dist/ambient-jsdom.d.js.map +0 -1
  143. package/dist/browser-service.d.js +0 -1
  144. package/dist/browser-service.d.js.map +0 -1
  145. package/dist/contracts.d.js +0 -1
  146. package/dist/contracts.d.js.map +0 -1
  147. package/dist/index.d.js +0 -21
  148. package/dist/index.d.js.map +0 -1
  149. package/dist/lifeops-session-contracts.d.js +0 -1
  150. package/dist/lifeops-session-contracts.d.js.map +0 -1
  151. package/dist/packaging.d.js +0 -1
  152. package/dist/packaging.d.js.map +0 -1
  153. package/dist/plugin.d.js +0 -1
  154. package/dist/plugin.d.js.map +0 -1
  155. package/dist/providers/workspace.d.js +0 -1
  156. package/dist/providers/workspace.d.js.map +0 -1
  157. package/dist/routes/bridge.d.js +0 -1
  158. package/dist/routes/bridge.d.js.map +0 -1
  159. package/dist/routes/workspace-account-gate.d.js +0 -1
  160. package/dist/routes/workspace-account-gate.d.js.map +0 -1
  161. package/dist/routes/workspace-setup.d.js +0 -1
  162. package/dist/routes/workspace-setup.d.js.map +0 -1
  163. package/dist/routes/workspace.d.js +0 -1
  164. package/dist/routes/workspace.d.js.map +0 -1
  165. package/dist/schema.d.js +0 -1
  166. package/dist/schema.d.js.map +0 -1
  167. package/dist/service.d.js +0 -1
  168. package/dist/service.d.js.map +0 -1
  169. package/dist/targets/bridge-target.d.js +0 -1
  170. package/dist/targets/bridge-target.d.js.map +0 -1
  171. package/dist/workspace/browser-capture.d.js +0 -1
  172. package/dist/workspace/browser-capture.d.js.map +0 -1
  173. package/dist/workspace/browser-workspace-desktop.d.js +0 -1
  174. package/dist/workspace/browser-workspace-desktop.d.js.map +0 -1
  175. package/dist/workspace/browser-workspace-elements.d.js +0 -1
  176. package/dist/workspace/browser-workspace-elements.d.js.map +0 -1
  177. package/dist/workspace/browser-workspace-forms.d.js +0 -1
  178. package/dist/workspace/browser-workspace-forms.d.js.map +0 -1
  179. package/dist/workspace/browser-workspace-helpers.d.js +0 -1
  180. package/dist/workspace/browser-workspace-helpers.d.js.map +0 -1
  181. package/dist/workspace/browser-workspace-jsdom.d.js +0 -1
  182. package/dist/workspace/browser-workspace-jsdom.d.js.map +0 -1
  183. package/dist/workspace/browser-workspace-network.d.js +0 -1
  184. package/dist/workspace/browser-workspace-network.d.js.map +0 -1
  185. package/dist/workspace/browser-workspace-snapshots.d.js +0 -1
  186. package/dist/workspace/browser-workspace-snapshots.d.js.map +0 -1
  187. package/dist/workspace/browser-workspace-state.d.js +0 -1
  188. package/dist/workspace/browser-workspace-state.d.js.map +0 -1
  189. package/dist/workspace/browser-workspace-types.d.js +0 -1
  190. package/dist/workspace/browser-workspace-types.d.js.map +0 -1
  191. package/dist/workspace/browser-workspace-web.d.js +0 -1
  192. package/dist/workspace/browser-workspace-web.d.js.map +0 -1
  193. package/dist/workspace/browser-workspace.d.js +0 -11
  194. package/dist/workspace/browser-workspace.d.js.map +0 -1
  195. package/dist/workspace/index.d.js +0 -3
  196. package/dist/workspace/index.d.js.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shaw Walters and elizaOS Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,89 +1,131 @@
1
1
  # @elizaos/plugin-browser
2
2
 
3
- Agent Browser Bridge plugin: schema, contracts, HTTP routes, and packaging
4
- utilities for Chrome/Safari browser companions.
3
+ Browser automation and companion bridge plugin for elizaOS. Adds the `BROWSER` action and `MANAGE_BROWSER_BRIDGE` action to any Eliza agent, owns the Eliza browser workspace (electrobun-embedded `BrowserView` on desktop, JSDOM fallback on web/mobile), and manages the Chrome/Safari Agent Browser Bridge companion extension.
5
4
 
6
- ## Scope
5
+ ## What this plugin provides
7
6
 
8
- This plugin owns the generic browser-companion surface:
7
+ ### Actions
9
8
 
10
- - Four Drizzle tables: `browser_bridge_companions`, `browser_bridge_settings`,
11
- `browser_bridge_tabs`, `browser_bridge_page_contexts`.
12
- - `/api/browser-bridge/*` HTTP routes for pairing, settings, companion sync,
13
- tab + page-context ingest, packaging artifacts, and workflow-linked session
14
- progress endpoints.
15
- - Companion package build + download helpers, including release-manifest
16
- synthesis for GitHub Releases.
17
- - Contract types under the `BrowserBridge*` prefix.
9
+ **BROWSER** Controls a registered browser target. The agent picks the best available backend automatically, or you can pin a specific target with the `target` parameter. Supported operations:
18
10
 
19
- The workflow-bound `life_browser_sessions` table intentionally stays in
20
- `@elizaos/app-lifeops` because it carries `workflowId` plus LifeOps-only
21
- scoping columns (`domain`, `subjectType`, `subjectId`, `visibilityScope`,
22
- `contextPolicy`). Session endpoints here therefore call into
23
- `@elizaos/app-lifeops/lifeops/service` to operate on that table.
11
+ | `action` value | What it does |
12
+ |---|---|
13
+ | `open` | Open a URL in a new tab |
14
+ | `navigate` | Navigate an existing tab to a URL |
15
+ | `click` | Click a DOM element by CSS selector |
16
+ | `type` | Type text into a selector |
17
+ | `press` | Press a keyboard key |
18
+ | `get` | Get a DOM value |
19
+ | `state` | Return current tab state (URL, title) |
20
+ | `snapshot` | Capture a DOM snapshot |
21
+ | `screenshot` | Capture a screenshot |
22
+ | `reload` | Reload the current tab |
23
+ | `back` / `forward` | Browser history navigation |
24
+ | `close` | Close a tab |
25
+ | `show` / `hide` | Show or hide the browser window |
26
+ | `wait` | Wait for a selector to appear |
27
+ | `tab` | Tab management (list/new/close/switch) |
28
+ | `realistic_click` | Animated cursor click (visible to user) |
29
+ | `realistic_fill` | Animated fill with per-character delay |
30
+ | `realistic_type` | Animated typing |
31
+ | `realistic_press` | Animated key press |
32
+ | `cursor_move` | Animate cursor to a position |
33
+ | `cursor_hide` | Hide the cursor overlay |
34
+ | `autofill_login` | Fill saved credentials into a browser tab (vault-gated; requires `domain`) |
24
35
 
25
- ## Destructive migration
36
+ **MANAGE_BROWSER_BRIDGE** Manages the Chrome/Safari companion extension. Subactions: `install` (build + reveal + open manager), `reveal_folder` (open the build folder in Finder/Explorer), `open_manager` (`chrome://extensions`), `refresh` (report paired companions and settings). Owner-only.
26
37
 
27
- This plugin renames the four previously `life_browser_*` tables to
28
- `browser_bridge_*`. Because the generated Drizzle migrations will issue a
29
- plain `CREATE TABLE` for the new names (with no `RENAME` bridge), the first
30
- boot after this package lands must run with:
38
+ ### Browser targets
31
39
 
32
- ```
33
- ELIZA_ALLOW_DESTRUCTIVE_MIGRATIONS=true
34
- ```
40
+ The plugin uses a pluggable target registry in `BrowserService`. Targets are selected automatically by availability and score:
35
41
 
36
- set in the environment. That flag is the existing escape valve wired into
37
- `@elizaos/plugin-sql`'s migrator (see `repository.ts:1389` in plugin-sql).
38
- Use that flag only for throwaway or explicitly approved migration runs.
42
+ | Target ID | Backend | When available |
43
+ |---|---|---|
44
+ | `workspace` | Electrobun `BrowserView` (desktop) or JSDOM (web) | Always |
45
+ | `bridge` | Paired Chrome/Safari via companion extension | At least one companion paired |
46
+ | `stagehand` | Playwright/Stagehand via HTTP endpoint | `ELIZA_BROWSER_STAGEHAND_COMMAND_URL` or `STAGEHAND_SERVER_URL` set |
39
47
 
40
- ## Integration
48
+ External plugins can register additional targets by calling `BrowserService.registerTarget(target)`.
41
49
 
42
- Eliza loads `browserBridgePlugin` as a core runtime plugin so the Browser
43
- Workspace UI, agent actions, and companion extension use the same API surface.
50
+ ### Provider
44
51
 
45
- ```ts
46
- import { browserBridgePlugin } from "@elizaos/plugin-browser";
52
+ `browser_workspace` — Injects the current dispatch mode (`desktop` / `web`) and a capped list of open tabs into agent context. Active when the `browser` or `web` context is selected.
53
+
54
+ ### Routes
55
+
56
+ `/api/browser-bridge/*` — HTTP surface for the companion extension: pairing, settings, tab sync, page-context ingest, session progress, and extension package build/download.
57
+
58
+ ## Requirements
59
+
60
+ ### Auto-enable
61
+
62
+ The plugin is opt-in. It activates when `config.features.browser` is truthy in the elizaOS agent config:
63
+
64
+ ```json
65
+ {
66
+ "features": {
67
+ "browser": true
68
+ }
69
+ }
47
70
  ```
48
71
 
49
- ## Authentication
72
+ ### Environment variables
50
73
 
51
- Companion-scoped endpoints (`/api/browser-bridge/companions/sync`,
52
- `/api/browser-bridge/companions/sessions/:id/*`) require two headers:
74
+ | Variable | Purpose |
75
+ |---|---|
76
+ | `ELIZA_BROWSER_STAGEHAND_COMMAND_URL` | Full URL for the Stagehand command endpoint |
77
+ | `STAGEHAND_SERVER_URL` | Stagehand base URL (commands go to `<url>/api/browser-command`) |
78
+ | `ELIZA_BROWSER_STAGEHAND_URL` | Alias for `STAGEHAND_SERVER_URL` |
79
+ | `ELIZA_BROWSER_STAGEHAND_AUTO_SETUP` | Set `false` to disable automatic stagehand-server install/build |
80
+ | `ELIZA_BROWSER_ALLOW_STAGEHAND_ON_MOBILE` | Set `true` to allow stagehand target on mobile |
81
+ | `ELIZA_MOBILE_PLATFORM` / `ELIZA_PLATFORM` / `CAPACITOR_PLATFORM` | Platform hint for target scoring (`ios`/`android`/`mobile`) |
53
82
 
54
- - `X-Browser-Bridge-Companion-Id: <companion uuid>`
55
- - `Authorization: Bearer <pairing token>`
83
+ ### Vault keys (set by the user, not env vars)
56
84
 
57
- The legacy `X-LifeOps-Browser-Companion-Id` and
58
- `x-eliza-browser-companion-id` headers were removed — no alias fallback is
59
- accepted.
85
+ `autofill_login` only fires when the user has pre-authorized it per domain:
60
86
 
61
- ## Connector Browser Auth
87
+ - `creds.<domain>.:autoallow = "1"` — set via Settings → Vault → Logins.
62
88
 
63
- Browser-backed connector auth must use session handles rather than extracted
64
- browser secrets. The workspace helper
65
- `acquireBrowserWorkspaceConnectorSession({ provider, accountId, ... })` binds a
66
- named connector account to either:
89
+ Without this flag, the action returns an error rather than prompting interactively.
67
90
 
68
- - an internal browser partition named
69
- `persist:connector-{provider}-{accountId}-{hash}`; or
70
- - a Browser Bridge companion profile handle when a companion/profile reference
71
- is supplied.
91
+ ## Companion extension authentication
72
92
 
73
- Connector partitions reject raw `cookies`, `storage`, and `state` export/load
74
- operations. Store the returned partition/profile/session references only. Auth
75
- states are explicit: `auth_pending`, `needs_reauth`, and `manual_handoff`
76
- represent login, MFA, CAPTCHA, or other user-required steps.
93
+ Companion-scoped endpoints require two headers:
77
94
 
78
- ## Companion Token Gaps
95
+ ```
96
+ X-Browser-Bridge-Companion-Id: <companion uuid>
97
+ Authorization: Bearer <pairing token>
98
+ ```
99
+
100
+ Legacy header aliases (`X-LifeOps-Browser-Companion-Id`, `x-eliza-browser-companion-id`) are not accepted.
101
+
102
+ ## Database
103
+
104
+ Drizzle tables in the `browser` PostgreSQL schema (applied by elizaOS `plugin-sql` migrator):
79
105
 
80
- The companion bearer token is stored server-side only as a SHA-256 hash and
81
- manual re-pairing rotates the active hash via a bounded pending-token list.
82
- Full TTL/revocation is not complete in the generic Browser Bridge surface yet.
83
- Missing pieces:
106
+ - `browser_bridge_companions`
107
+ - `browser_bridge_settings`
108
+ - `browser_bridge_tabs`
109
+ - `browser_bridge_page_contexts`
84
110
 
85
- - schema columns for active-token expiry, pending-token expiry, and revoked
86
- timestamp/reason;
87
- - repository methods and HTTP routes for explicit revoke;
88
- - companion-side handling that clears local config on revoke/expiry responses;
89
- - a migration path for existing `browser_bridge_companions` rows.
111
+ ## Registering a custom browser target
112
+
113
+ Any plugin can extend the browser dispatch surface at runtime:
114
+
115
+ ```ts
116
+ import { BrowserService, BROWSER_SERVICE_TYPE } from "@elizaos/plugin-browser";
117
+ import type { BrowserTarget } from "@elizaos/plugin-browser";
118
+
119
+ const myTarget: BrowserTarget = {
120
+ id: "my-target",
121
+ name: "My Browser",
122
+ description: "Custom browser backend.",
123
+ kind: "external",
124
+ priority: 50,
125
+ available: async () => true,
126
+ execute: async (command) => { /* ... */ },
127
+ };
128
+
129
+ const browserService = runtime.getService<BrowserService>(BROWSER_SERVICE_TYPE);
130
+ browserService?.registerTarget(myTarget);
131
+ ```
@@ -1 +1 @@
1
- {"version":3,"file":"browser-autofill-login.d.ts","sourceRoot":"","sources":["../../src/actions/browser-autofill-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,aAAa,EACb,MAAM,EACP,MAAM,eAAe,CAAC;AAyIvB;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,EAAE,cAAc,GAAG,SAAS,GAClC,OAAO,CAAC,YAAY,CAAC,CAgLvB"}
1
+ {"version":3,"file":"browser-autofill-login.d.ts","sourceRoot":"","sources":["../../src/actions/browser-autofill-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,aAAa,EACb,MAAM,EACP,MAAM,eAAe,CAAC;AAyIvB;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,EAAE,cAAc,GAAG,SAAS,GAClC,OAAO,CAAC,YAAY,CAAC,CAkLvB"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/actions/browser-autofill-login.ts"],"sourcesContent":["/**\n * BROWSER autofill-login subaction — agent-driven browser login autofill.\n *\n * Invoked via BROWSER with `subaction: \"autofill-login\"` (canonical). The legacy\n * planner action name `BROWSER_AUTOFILL_LOGIN` normalizes to BROWSER with this\n * subaction in the planner dispatch pipeline.\n *\n * Lets the agent say \"log into github.com for me\" and have the saved\n * credentials filled into an open Eliza browser tab without a per-call\n * consent prompt.\n *\n * Authorization model (mirrors the user-driven autofill flow):\n * - The user must have set `creds.<domain>.:autoallow = \"1\"` on the\n * domain. This is the same vault key the React-side consent flow\n * uses; toggling it from Settings -> Vault -> Logins is the\n * SOLE way to let the agent autofill silently.\n * - Without that flag, this action returns\n * `{ ok: false, reason: \"user has not pre-authorized agent autofill for <domain>\" }`.\n * The agent should NOT fall back to the user-driven flow on its own\n * because the user-driven flow is gated by an interactive React\n * modal that an autonomous agent cannot consent to.\n *\n * Tab selection:\n * - Lists the live browser-workspace tabs and picks the first one\n * whose URL hostname matches `domain` (registrable hostname,\n * case-insensitive). Returns a clean error when no such tab exists\n * so the agent can decide whether to open one first via\n * BROWSER (open/navigate).\n *\n * Fill mechanism:\n * - Injects a small JS snippet that mirrors the same form-detection\n * and `setNativeInputValue` helpers the in-tab preload uses, so\n * React-controlled inputs see the change.\n * - When `submit: true`, the snippet also calls `form.submit()` (or\n * clicks a likely submit button) after filling. Off by default —\n * the safer behaviour is fill-only and let the user click submit.\n */\n\nimport type {\n ActionResult,\n HandlerOptions,\n IAgentRuntime,\n Memory,\n} from \"@elizaos/core\";\nimport { logger } from \"@elizaos/core\";\nimport {\n createManager,\n getAutofillAllowed,\n getSavedLogin,\n listSavedLogins,\n type Vault,\n} from \"@elizaos/vault\";\nimport {\n evaluateBrowserWorkspaceTab,\n isBrowserWorkspaceBridgeConfigured,\n listBrowserWorkspaceTabs,\n} from \"../workspace/browser-workspace.js\";\n\ninterface BrowserAutofillLoginParameters {\n domain?: string;\n username?: string;\n /** When true, attempt to submit the form after filling. Default: false. */\n submit?: boolean;\n}\n\nconst AUTOFILL_SUBACTION = \"autofill-login\";\n\nconst MAX_BROWSER_TAB_SCAN = 100;\nconst MAX_FILL_REASON_CHARS = 240;\n\nlet cachedVault: Vault | null = null;\n\nfunction sharedAutofillVault(): Vault {\n cachedVault ??= createManager().vault;\n return cachedVault;\n}\n\nfunction tabUrlMatchesDomain(tabUrl: string, domain: string): boolean {\n if (!tabUrl) return false;\n let hostname: string;\n try {\n hostname = new URL(tabUrl).hostname;\n } catch {\n return false;\n }\n return hostname.toLowerCase() === domain.toLowerCase();\n}\n\nfunction buildAutofillScript(args: {\n username: string;\n password: string;\n submit: boolean;\n}): string {\n // Inline snippet — runs in the OOPIF (the page's content world) via\n // electrobun tab eval. Uses setNativeInputValue to bypass React's\n // value-setter override.\n return `\n(() => {\n const USERNAME = ${JSON.stringify(args.username)};\n const PASSWORD = ${JSON.stringify(args.password)};\n const SUBMIT = ${args.submit ? \"true\" : \"false\"};\n\n function setNativeInputValue(input, value) {\n const proto = Object.getPrototypeOf(input);\n const desc = Object.getOwnPropertyDescriptor(proto, \"value\");\n if (desc && typeof desc.set === \"function\") {\n desc.set.call(input, value);\n } else {\n input.value = value;\n }\n input.dispatchEvent(new Event(\"input\", { bubbles: true }));\n input.dispatchEvent(new Event(\"change\", { bubbles: true }));\n }\n\n function findPrecedingTextInput(passwordInput) {\n const root = passwordInput.form || document.body;\n const candidates = root.querySelectorAll(\n 'input[type=\"text\"], input[type=\"email\"], input:not([type])'\n );\n let lastBefore = null;\n for (const el of candidates) {\n if (el.compareDocumentPosition(passwordInput) & Node.DOCUMENT_POSITION_FOLLOWING) {\n lastBefore = el;\n }\n }\n return lastBefore;\n }\n\n const password = document.querySelector('input[type=\"password\"]');\n if (!password) {\n return { ok: false, reason: \"no_password_input\" };\n }\n const form = password.form;\n const username =\n (form && form.querySelector(\n 'input[type=\"email\"], input[name*=\"user\" i], input[name*=\"email\" i], input[name*=\"login\" i]'\n )) || findPrecedingTextInput(password);\n\n if (username) setNativeInputValue(username, USERNAME);\n setNativeInputValue(password, PASSWORD);\n\n if (SUBMIT) {\n if (form && typeof form.requestSubmit === \"function\") {\n form.requestSubmit();\n } else if (form && typeof form.submit === \"function\") {\n form.submit();\n } else {\n const button =\n (form && form.querySelector('button[type=\"submit\"], input[type=\"submit\"]')) ||\n document.querySelector('button[type=\"submit\"], input[type=\"submit\"]');\n if (button) (button).click();\n }\n }\n\n return {\n ok: true,\n filled: { username: !!username, password: true },\n submitted: SUBMIT,\n };\n})();\n`;\n}\n\nfunction narrowSnippetResult(raw: unknown): {\n filled: boolean;\n fillReason: string | null;\n} {\n if (!raw || typeof raw !== \"object\") {\n return { filled: false, fillReason: null };\n }\n const obj = raw as { filled?: { username?: boolean; password?: boolean } };\n const hasFilledProp = \"filled\" in obj && Boolean(obj.filled);\n let fillReason: string | null = null;\n const reasonVal = \"reason\" in obj ? obj.reason : undefined;\n if (typeof reasonVal === \"string\") {\n fillReason = reasonVal.slice(0, MAX_FILL_REASON_CHARS);\n }\n return { filled: hasFilledProp, fillReason };\n}\n\n/**\n * Executes the vault-gated workspace autofill flow for {@link AUTOFILL_SUBACTION}.\n */\nexport async function executeBrowserAutofillLogin(\n _runtime: IAgentRuntime,\n _message: Memory | undefined,\n options: HandlerOptions | undefined,\n): Promise<ActionResult> {\n const params = options?.parameters as BrowserAutofillLoginParameters | undefined;\n const domain = params?.domain?.trim().toLowerCase() ?? \"\";\n const requestedUsername = params?.username?.trim();\n const submit = params?.submit === true;\n\n if (!domain) {\n return {\n text: `BROWSER requires subaction \"${AUTOFILL_SUBACTION}\" and a \\`domain\\` parameter.`,\n success: false,\n values: {\n success: false,\n error: \"BROWSER_AUTOFILL_BAD_PARAMS\",\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n\n if (!isBrowserWorkspaceBridgeConfigured(process.env)) {\n return {\n text: `BROWSER ${AUTOFILL_SUBACTION} requires the desktop browser workspace bridge.`,\n success: false,\n values: {\n success: false,\n error: \"BROWSER_BRIDGE_UNAVAILABLE\",\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n\n const vault = sharedAutofillVault();\n\n const allowed = await getAutofillAllowed(vault, domain);\n if (!allowed) {\n const text = `User has not pre-authorized agent autofill for ${domain}. Toggle \"Allow agent to autofill\" for this domain under Settings -> Vault -> Logins.`;\n return {\n text,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_NOT_AUTHORIZED\",\n domain,\n subaction: AUTOFILL_SUBACTION,\n },\n data: {\n actionName: \"BROWSER\",\n subaction: AUTOFILL_SUBACTION,\n domain,\n reason: text,\n },\n };\n }\n\n let savedLogin: Awaited<ReturnType<typeof getSavedLogin>> = null;\n if (requestedUsername) {\n savedLogin = await getSavedLogin(vault, domain, requestedUsername);\n if (!savedLogin) {\n return {\n text: `No saved login for ${requestedUsername} on ${domain}.`,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_NO_LOGIN\",\n domain,\n username: requestedUsername,\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n } else {\n const summaries = await listSavedLogins(vault, domain);\n if (summaries.length === 0) {\n return {\n text: `No saved logins for ${domain}.`,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_NO_LOGIN\",\n domain,\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n const sorted = [...summaries].sort(\n (a, b) => b.lastModified - a.lastModified,\n );\n const chosen = sorted[0];\n if (!chosen) {\n return {\n text: `No saved logins for ${domain}.`,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_NO_LOGIN\",\n domain,\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n savedLogin = await getSavedLogin(vault, domain, chosen.username);\n if (!savedLogin) {\n return {\n text: `Saved login ${chosen.username} on ${domain} disappeared between list and reveal.`,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_RACE\",\n domain,\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n }\n\n const tabs = await listBrowserWorkspaceTabs();\n const matchingTab = tabs\n .slice(0, MAX_BROWSER_TAB_SCAN)\n .find((t) => tabUrlMatchesDomain(t.url, domain));\n if (!matchingTab) {\n return {\n text: `No open browser tab on ${domain}. Open one with BROWSER (open/navigate) first.`,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_NO_TAB\",\n domain,\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n\n const script = buildAutofillScript({\n username: savedLogin.username,\n password: savedLogin.password,\n submit,\n });\n const rawResult = await evaluateBrowserWorkspaceTab({\n id: matchingTab.id,\n script,\n });\n const { filled, fillReason } = narrowSnippetResult(rawResult);\n\n logger.info(\n `[browser-autofill-login] domain=${domain} tabId=${matchingTab.id} submit=${submit} filled=${filled}`,\n );\n\n return {\n text: submit\n ? `Filled and submitted login on ${domain} (tab ${matchingTab.id}).`\n : `Filled login on ${domain} (tab ${matchingTab.id}). User must click submit.`,\n success: true,\n values: {\n success: true,\n domain,\n tabId: matchingTab.id,\n submitted: submit,\n filled,\n subaction: AUTOFILL_SUBACTION,\n ...(fillReason ? { fillReason } : {}),\n },\n data: {\n actionName: \"BROWSER\",\n subaction: AUTOFILL_SUBACTION,\n domain,\n tabId: matchingTab.id,\n filled,\n ...(fillReason ? { fillReason } : {}),\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/actions/browser-autofill-login.ts"],"sourcesContent":["/**\n * BROWSER autofill-login subaction — agent-driven browser login autofill.\n *\n * Invoked via BROWSER with `subaction: \"autofill-login\"` (canonical). The legacy\n * planner action name `BROWSER_AUTOFILL_LOGIN` normalizes to BROWSER with this\n * subaction in the planner dispatch pipeline.\n *\n * Lets the agent say \"log into github.com for me\" and have the saved\n * credentials filled into an open Eliza browser tab without a per-call\n * consent prompt.\n *\n * Authorization model (mirrors the user-driven autofill flow):\n * - The user must have set `creds.<domain>.:autoallow = \"1\"` on the\n * domain. This is the same vault key the React-side consent flow\n * uses; toggling it from Settings -> Vault -> Logins is the\n * SOLE way to let the agent autofill silently.\n * - Without that flag, this action returns\n * `{ ok: false, reason: \"user has not pre-authorized agent autofill for <domain>\" }`.\n * The agent should NOT fall back to the user-driven flow on its own\n * because the user-driven flow is gated by an interactive React\n * modal that an autonomous agent cannot consent to.\n *\n * Tab selection:\n * - Lists the live browser-workspace tabs and picks the first one\n * whose URL hostname matches `domain` (registrable hostname,\n * case-insensitive). Returns a clean error when no such tab exists\n * so the agent can decide whether to open one first via\n * BROWSER (open/navigate).\n *\n * Fill mechanism:\n * - Injects a small JS snippet that mirrors the same form-detection\n * and `setNativeInputValue` helpers the in-tab preload uses, so\n * React-controlled inputs see the change.\n * - When `submit: true`, the snippet also calls `form.submit()` (or\n * clicks a likely submit button) after filling. Off by default —\n * the safer behaviour is fill-only and let the user click submit.\n */\n\nimport type {\n ActionResult,\n HandlerOptions,\n IAgentRuntime,\n Memory,\n} from \"@elizaos/core\";\nimport { logger } from \"@elizaos/core\";\nimport {\n createManager,\n getAutofillAllowed,\n getSavedLogin,\n listSavedLogins,\n type Vault,\n} from \"@elizaos/vault\";\nimport {\n evaluateBrowserWorkspaceTab,\n isBrowserWorkspaceBridgeConfigured,\n listBrowserWorkspaceTabs,\n} from \"../workspace/browser-workspace.js\";\n\ninterface BrowserAutofillLoginParameters {\n domain?: string;\n username?: string;\n /** When true, attempt to submit the form after filling. Default: false. */\n submit?: boolean;\n}\n\nconst AUTOFILL_SUBACTION = \"autofill-login\";\n\nconst MAX_BROWSER_TAB_SCAN = 100;\nconst MAX_FILL_REASON_CHARS = 240;\n\nlet cachedVault: Vault | null = null;\n\nfunction sharedAutofillVault(): Vault {\n cachedVault ??= createManager().vault;\n return cachedVault;\n}\n\nfunction tabUrlMatchesDomain(tabUrl: string, domain: string): boolean {\n if (!tabUrl) return false;\n let hostname: string;\n try {\n hostname = new URL(tabUrl).hostname;\n } catch {\n return false;\n }\n return hostname.toLowerCase() === domain.toLowerCase();\n}\n\nfunction buildAutofillScript(args: {\n username: string;\n password: string;\n submit: boolean;\n}): string {\n // Inline snippet — runs in the OOPIF (the page's content world) via\n // electrobun tab eval. Uses setNativeInputValue to bypass React's\n // value-setter override.\n return `\n(() => {\n const USERNAME = ${JSON.stringify(args.username)};\n const PASSWORD = ${JSON.stringify(args.password)};\n const SUBMIT = ${args.submit ? \"true\" : \"false\"};\n\n function setNativeInputValue(input, value) {\n const proto = Object.getPrototypeOf(input);\n const desc = Object.getOwnPropertyDescriptor(proto, \"value\");\n if (desc && typeof desc.set === \"function\") {\n desc.set.call(input, value);\n } else {\n input.value = value;\n }\n input.dispatchEvent(new Event(\"input\", { bubbles: true }));\n input.dispatchEvent(new Event(\"change\", { bubbles: true }));\n }\n\n function findPrecedingTextInput(passwordInput) {\n const root = passwordInput.form || document.body;\n const candidates = root.querySelectorAll(\n 'input[type=\"text\"], input[type=\"email\"], input:not([type])'\n );\n let lastBefore = null;\n for (const el of candidates) {\n if (el.compareDocumentPosition(passwordInput) & Node.DOCUMENT_POSITION_FOLLOWING) {\n lastBefore = el;\n }\n }\n return lastBefore;\n }\n\n const password = document.querySelector('input[type=\"password\"]');\n if (!password) {\n return { ok: false, reason: \"no_password_input\" };\n }\n const form = password.form;\n const username =\n (form && form.querySelector(\n 'input[type=\"email\"], input[name*=\"user\" i], input[name*=\"email\" i], input[name*=\"login\" i]'\n )) || findPrecedingTextInput(password);\n\n if (username) setNativeInputValue(username, USERNAME);\n setNativeInputValue(password, PASSWORD);\n\n if (SUBMIT) {\n if (form && typeof form.requestSubmit === \"function\") {\n form.requestSubmit();\n } else if (form && typeof form.submit === \"function\") {\n form.submit();\n } else {\n const button =\n (form && form.querySelector('button[type=\"submit\"], input[type=\"submit\"]')) ||\n document.querySelector('button[type=\"submit\"], input[type=\"submit\"]');\n if (button) (button).click();\n }\n }\n\n return {\n ok: true,\n filled: { username: !!username, password: true },\n submitted: SUBMIT,\n };\n})();\n`;\n}\n\nfunction narrowSnippetResult(raw: unknown): {\n filled: boolean;\n fillReason: string | null;\n} {\n if (!raw || typeof raw !== \"object\") {\n return { filled: false, fillReason: null };\n }\n const obj = raw as { filled?: { username?: boolean; password?: boolean } };\n const hasFilledProp = \"filled\" in obj && Boolean(obj.filled);\n let fillReason: string | null = null;\n const reasonVal = \"reason\" in obj ? obj.reason : undefined;\n if (typeof reasonVal === \"string\") {\n fillReason = reasonVal.slice(0, MAX_FILL_REASON_CHARS);\n }\n return { filled: hasFilledProp, fillReason };\n}\n\n/**\n * Executes the vault-gated workspace autofill flow for {@link AUTOFILL_SUBACTION}.\n */\nexport async function executeBrowserAutofillLogin(\n _runtime: IAgentRuntime,\n _message: Memory | undefined,\n options: HandlerOptions | undefined,\n): Promise<ActionResult> {\n const params = options?.parameters as\n | BrowserAutofillLoginParameters\n | undefined;\n const domain = params?.domain?.trim().toLowerCase() ?? \"\";\n const requestedUsername = params?.username?.trim();\n const submit = params?.submit === true;\n\n if (!domain) {\n return {\n text: `BROWSER requires subaction \"${AUTOFILL_SUBACTION}\" and a \\`domain\\` parameter.`,\n success: false,\n values: {\n success: false,\n error: \"BROWSER_AUTOFILL_BAD_PARAMS\",\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n\n if (!isBrowserWorkspaceBridgeConfigured(process.env)) {\n return {\n text: `BROWSER ${AUTOFILL_SUBACTION} requires the desktop browser workspace bridge.`,\n success: false,\n values: {\n success: false,\n error: \"BROWSER_BRIDGE_UNAVAILABLE\",\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n\n const vault = sharedAutofillVault();\n\n const allowed = await getAutofillAllowed(vault, domain);\n if (!allowed) {\n const text = `User has not pre-authorized agent autofill for ${domain}. Toggle \"Allow agent to autofill\" for this domain under Settings -> Vault -> Logins.`;\n return {\n text,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_NOT_AUTHORIZED\",\n domain,\n subaction: AUTOFILL_SUBACTION,\n },\n data: {\n actionName: \"BROWSER\",\n subaction: AUTOFILL_SUBACTION,\n domain,\n reason: text,\n },\n };\n }\n\n let savedLogin: Awaited<ReturnType<typeof getSavedLogin>> = null;\n if (requestedUsername) {\n savedLogin = await getSavedLogin(vault, domain, requestedUsername);\n if (!savedLogin) {\n return {\n text: `No saved login for ${requestedUsername} on ${domain}.`,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_NO_LOGIN\",\n domain,\n username: requestedUsername,\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n } else {\n const summaries = await listSavedLogins(vault, domain);\n if (summaries.length === 0) {\n return {\n text: `No saved logins for ${domain}.`,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_NO_LOGIN\",\n domain,\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n const sorted = [...summaries].sort(\n (a, b) => b.lastModified - a.lastModified,\n );\n const chosen = sorted[0];\n if (!chosen) {\n return {\n text: `No saved logins for ${domain}.`,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_NO_LOGIN\",\n domain,\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n savedLogin = await getSavedLogin(vault, domain, chosen.username);\n if (!savedLogin) {\n return {\n text: `Saved login ${chosen.username} on ${domain} disappeared between list and reveal.`,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_RACE\",\n domain,\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n }\n\n const tabs = await listBrowserWorkspaceTabs();\n const matchingTab = tabs\n .slice(0, MAX_BROWSER_TAB_SCAN)\n .find((t) => tabUrlMatchesDomain(t.url, domain));\n if (!matchingTab) {\n return {\n text: `No open browser tab on ${domain}. Open one with BROWSER (open/navigate) first.`,\n success: false,\n values: {\n success: false,\n error: \"AGENT_AUTOFILL_NO_TAB\",\n domain,\n subaction: AUTOFILL_SUBACTION,\n },\n data: { actionName: \"BROWSER\", subaction: AUTOFILL_SUBACTION },\n };\n }\n\n const script = buildAutofillScript({\n username: savedLogin.username,\n password: savedLogin.password,\n submit,\n });\n const rawResult = await evaluateBrowserWorkspaceTab({\n id: matchingTab.id,\n script,\n });\n const { filled, fillReason } = narrowSnippetResult(rawResult);\n\n logger.info(\n `[browser-autofill-login] domain=${domain} tabId=${matchingTab.id} submit=${submit} filled=${filled}`,\n );\n\n return {\n text: submit\n ? `Filled and submitted login on ${domain} (tab ${matchingTab.id}).`\n : `Filled login on ${domain} (tab ${matchingTab.id}). User must click submit.`,\n success: true,\n values: {\n success: true,\n domain,\n tabId: matchingTab.id,\n submitted: submit,\n filled,\n subaction: AUTOFILL_SUBACTION,\n ...(fillReason ? { fillReason } : {}),\n },\n data: {\n actionName: \"BROWSER\",\n subaction: AUTOFILL_SUBACTION,\n domain,\n tabId: matchingTab.id,\n filled,\n ...(fillReason ? { fillReason } : {}),\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,12 +1,11 @@
1
1
  import type { Action } from "@elizaos/core";
2
2
  /**
3
3
  * Targets are the registered browser backends. The agent uses what is
4
- * available; specifying a target overrides the default. `workspace` is the
5
- * current default (electrobun-embedded BrowserView with JSDOM fallback).
6
- * `bridge` (Chrome/Safari companion) and `computeruse` (puppeteer Chromium)
7
- * are reserved for the BrowserService target-registry refactor — see
8
- * follow-up work.
4
+ * available; specifying a target overrides automatic routing. `workspace`
5
+ * is the app-owned browser surface, `bridge` is the paired Chrome/Safari
6
+ * companion, `stagehand` is the Playwright/Stagehand fallback, and other
7
+ * plugins may register additional target ids.
9
8
  */
10
- export type BrowserTarget = "workspace" | "bridge" | "computeruse";
9
+ export type BrowserTarget = string;
11
10
  export declare const browserAction: Action;
12
11
  //# sourceMappingURL=browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/actions/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EAIP,MAAM,eAAe,CAAC;AAavB;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,QAAQ,GAAG,aAAa,CAAC;AAyNnE,eAAO,MAAM,aAAa,EAAE,MAqU3B,CAAC"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/actions/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EAMP,MAAM,eAAe,CAAC;AAkBvB;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AAqlBnC,eAAO,MAAM,aAAa,EAAE,MA4W3B,CAAC"}