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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) 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 +54 -59
  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/bridge-policy.d.ts +10 -0
  13. package/dist/bridge-policy.d.ts.map +1 -0
  14. package/dist/bridge-policy.js +37 -0
  15. package/dist/bridge-policy.js.map +1 -0
  16. package/dist/bridge-readiness.d.ts +16 -0
  17. package/dist/bridge-readiness.d.ts.map +1 -0
  18. package/dist/bridge-readiness.js +82 -0
  19. package/dist/bridge-readiness.js.map +1 -0
  20. package/dist/bridge-records.d.ts +9 -0
  21. package/dist/bridge-records.d.ts.map +1 -0
  22. package/dist/bridge-records.js +37 -0
  23. package/dist/bridge-records.js.map +1 -0
  24. package/dist/browser-capture-hooks.d.ts +9 -0
  25. package/dist/browser-capture-hooks.d.ts.map +1 -0
  26. package/dist/browser-capture-hooks.js +15 -0
  27. package/dist/browser-capture-hooks.js.map +1 -0
  28. package/dist/browser-service.d.ts +22 -4
  29. package/dist/browser-service.d.ts.map +1 -1
  30. package/dist/browser-service.js +63 -15
  31. package/dist/browser-service.js.map +1 -1
  32. package/dist/browser-workspace-hooks.d.ts +14 -0
  33. package/dist/browser-workspace-hooks.d.ts.map +1 -0
  34. package/dist/browser-workspace-hooks.js +15 -0
  35. package/dist/browser-workspace-hooks.js.map +1 -0
  36. package/dist/companion-auth.d.ts +34 -0
  37. package/dist/companion-auth.d.ts.map +1 -0
  38. package/dist/companion-auth.js +98 -0
  39. package/dist/companion-auth.js.map +1 -0
  40. package/dist/index.d.ts +9 -3
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +46 -11
  43. package/dist/index.js.map +1 -1
  44. package/dist/message-adapter.d.ts +9 -0
  45. package/dist/message-adapter.d.ts.map +1 -0
  46. package/dist/message-adapter.js +104 -0
  47. package/dist/message-adapter.js.map +1 -0
  48. package/dist/packaging.d.ts.map +1 -1
  49. package/dist/packaging.js +2 -0
  50. package/dist/packaging.js.map +1 -1
  51. package/dist/password-manager-bridge.d.ts +50 -0
  52. package/dist/password-manager-bridge.d.ts.map +1 -0
  53. package/dist/password-manager-bridge.js +437 -0
  54. package/dist/password-manager-bridge.js.map +1 -0
  55. package/dist/plugin.d.ts.map +1 -1
  56. package/dist/plugin.js +8 -4
  57. package/dist/plugin.js.map +1 -1
  58. package/dist/providers/workspace.d.ts +1 -1
  59. package/dist/providers/workspace.js.map +1 -1
  60. package/dist/routes/bridge.d.ts.map +1 -1
  61. package/dist/routes/bridge.js +63 -14
  62. package/dist/routes/bridge.js.map +1 -1
  63. package/dist/routes/workspace-setup.d.ts.map +1 -1
  64. package/dist/routes/workspace-setup.js +1 -1
  65. package/dist/routes/workspace-setup.js.map +1 -1
  66. package/dist/routes/workspace.d.ts +1 -2
  67. package/dist/routes/workspace.d.ts.map +1 -1
  68. package/dist/routes/workspace.js +63 -3
  69. package/dist/routes/workspace.js.map +1 -1
  70. package/dist/schema.d.ts +2 -2
  71. package/dist/schema.js.map +1 -1
  72. package/dist/service.d.ts +1 -1
  73. package/dist/service.d.ts.map +1 -1
  74. package/dist/service.js.map +1 -1
  75. package/dist/targets/bridge-target.d.ts +1 -1
  76. package/dist/targets/bridge-target.d.ts.map +1 -1
  77. package/dist/targets/bridge-target.js.map +1 -1
  78. package/dist/targets/stagehand-target.d.ts +3 -0
  79. package/dist/targets/stagehand-target.d.ts.map +1 -0
  80. package/dist/targets/stagehand-target.js +187 -0
  81. package/dist/targets/stagehand-target.js.map +1 -0
  82. package/dist/workspace/browser-capture.d.ts +1 -1
  83. package/dist/workspace/browser-capture.js.map +1 -1
  84. package/dist/workspace/browser-workspace-desktop.d.ts +1 -1
  85. package/dist/workspace/browser-workspace-desktop.d.ts.map +1 -1
  86. package/dist/workspace/browser-workspace-desktop.js +47 -25
  87. package/dist/workspace/browser-workspace-desktop.js.map +1 -1
  88. package/dist/workspace/browser-workspace-forms.d.ts.map +1 -1
  89. package/dist/workspace/browser-workspace-forms.js +1 -1
  90. package/dist/workspace/browser-workspace-forms.js.map +1 -1
  91. package/dist/workspace/browser-workspace-helpers.d.ts +7 -0
  92. package/dist/workspace/browser-workspace-helpers.d.ts.map +1 -1
  93. package/dist/workspace/browser-workspace-helpers.js +37 -0
  94. package/dist/workspace/browser-workspace-helpers.js.map +1 -1
  95. package/dist/workspace/browser-workspace-network.d.ts +1 -1
  96. package/dist/workspace/browser-workspace-network.d.ts.map +1 -1
  97. package/dist/workspace/browser-workspace-types.d.ts +15 -0
  98. package/dist/workspace/browser-workspace-types.d.ts.map +1 -1
  99. package/dist/workspace/browser-workspace-types.js.map +1 -1
  100. package/dist/workspace/browser-workspace-web.d.ts.map +1 -1
  101. package/dist/workspace/browser-workspace-web.js +15 -88
  102. package/dist/workspace/browser-workspace-web.js.map +1 -1
  103. package/dist/workspace/browser-workspace.d.ts +1 -1
  104. package/dist/workspace/browser-workspace.d.ts.map +1 -1
  105. package/dist/workspace/browser-workspace.js +9 -4
  106. package/dist/workspace/browser-workspace.js.map +1 -1
  107. package/package.json +28 -7
  108. package/dist/actions/browser-autofill-login.d.js +0 -1
  109. package/dist/actions/browser-autofill-login.d.js.map +0 -1
  110. package/dist/actions/browser.d.js +0 -1
  111. package/dist/actions/browser.d.js.map +0 -1
  112. package/dist/actions/manage-browser-bridge.d.js +0 -1
  113. package/dist/actions/manage-browser-bridge.d.js.map +0 -1
  114. package/dist/ambient-jsdom.d.js +0 -1
  115. package/dist/ambient-jsdom.d.js.map +0 -1
  116. package/dist/browser-service.d.js +0 -1
  117. package/dist/browser-service.d.js.map +0 -1
  118. package/dist/contracts.d.js +0 -1
  119. package/dist/contracts.d.js.map +0 -1
  120. package/dist/index.d.js +0 -21
  121. package/dist/index.d.js.map +0 -1
  122. package/dist/lifeops-session-contracts.d.js +0 -1
  123. package/dist/lifeops-session-contracts.d.js.map +0 -1
  124. package/dist/packaging.d.js +0 -1
  125. package/dist/packaging.d.js.map +0 -1
  126. package/dist/plugin.d.js +0 -1
  127. package/dist/plugin.d.js.map +0 -1
  128. package/dist/providers/workspace.d.js +0 -1
  129. package/dist/providers/workspace.d.js.map +0 -1
  130. package/dist/routes/bridge.d.js +0 -1
  131. package/dist/routes/bridge.d.js.map +0 -1
  132. package/dist/routes/workspace-account-gate.d.js +0 -1
  133. package/dist/routes/workspace-account-gate.d.js.map +0 -1
  134. package/dist/routes/workspace-setup.d.js +0 -1
  135. package/dist/routes/workspace-setup.d.js.map +0 -1
  136. package/dist/routes/workspace.d.js +0 -1
  137. package/dist/routes/workspace.d.js.map +0 -1
  138. package/dist/schema.d.js +0 -1
  139. package/dist/schema.d.js.map +0 -1
  140. package/dist/service.d.js +0 -1
  141. package/dist/service.d.js.map +0 -1
  142. package/dist/targets/bridge-target.d.js +0 -1
  143. package/dist/targets/bridge-target.d.js.map +0 -1
  144. package/dist/workspace/browser-capture.d.js +0 -1
  145. package/dist/workspace/browser-capture.d.js.map +0 -1
  146. package/dist/workspace/browser-workspace-desktop.d.js +0 -1
  147. package/dist/workspace/browser-workspace-desktop.d.js.map +0 -1
  148. package/dist/workspace/browser-workspace-elements.d.js +0 -1
  149. package/dist/workspace/browser-workspace-elements.d.js.map +0 -1
  150. package/dist/workspace/browser-workspace-forms.d.js +0 -1
  151. package/dist/workspace/browser-workspace-forms.d.js.map +0 -1
  152. package/dist/workspace/browser-workspace-helpers.d.js +0 -1
  153. package/dist/workspace/browser-workspace-helpers.d.js.map +0 -1
  154. package/dist/workspace/browser-workspace-jsdom.d.js +0 -1
  155. package/dist/workspace/browser-workspace-jsdom.d.js.map +0 -1
  156. package/dist/workspace/browser-workspace-network.d.js +0 -1
  157. package/dist/workspace/browser-workspace-network.d.js.map +0 -1
  158. package/dist/workspace/browser-workspace-snapshots.d.js +0 -1
  159. package/dist/workspace/browser-workspace-snapshots.d.js.map +0 -1
  160. package/dist/workspace/browser-workspace-state.d.js +0 -1
  161. package/dist/workspace/browser-workspace-state.d.js.map +0 -1
  162. package/dist/workspace/browser-workspace-types.d.js +0 -1
  163. package/dist/workspace/browser-workspace-types.d.js.map +0 -1
  164. package/dist/workspace/browser-workspace-web.d.js +0 -1
  165. package/dist/workspace/browser-workspace-web.d.js.map +0 -1
  166. package/dist/workspace/browser-workspace.d.js +0 -11
  167. package/dist/workspace/browser-workspace.d.js.map +0 -1
  168. package/dist/workspace/index.d.js +0 -3
  169. 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,EAIP,MAAM,eAAe,CAAC;AAYvB;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AA2SnC,eAAO,MAAM,aAAa,EAAE,MAyS3B,CAAC"}
@@ -2,7 +2,6 @@ import { logger } from "@elizaos/core";
2
2
  import {
3
3
  BROWSER_SERVICE_TYPE
4
4
  } from "../browser-service.js";
5
- import { executeBrowserAutofillLogin } from "./browser-autofill-login.js";
6
5
  import {
7
6
  executeBrowserWorkspaceCommand,
8
7
  getBrowserWorkspaceMode
@@ -19,10 +18,11 @@ function extractFirstUrl(value) {
19
18
  return match?.[0] ?? null;
20
19
  }
21
20
  function inferBrowserSubaction(params, messageText) {
22
- if (params?.action === "autofill-login" || params?.subaction === "autofill-login") {
21
+ const normalizedAction = normalizeBrowserAction(params?.action);
22
+ if (normalizedAction === "autofill-login" || params?.subaction === "autofill-login") {
23
23
  return "autofill-login";
24
24
  }
25
- const legacySubaction = normalizeLegacyBrowserAction(params?.action);
25
+ const legacySubaction = normalizeLegacyBrowserAction(normalizedAction);
26
26
  if (legacySubaction) {
27
27
  return legacySubaction;
28
28
  }
@@ -44,8 +44,29 @@ function inferBrowserSubaction(params, messageText) {
44
44
  }
45
45
  return "state";
46
46
  }
47
- function normalizeLegacyBrowserAction(action) {
47
+ function normalizeBrowserAction(action) {
48
48
  switch (action) {
49
+ case "realistic_click":
50
+ return "realistic-click";
51
+ case "realistic_fill":
52
+ return "realistic-fill";
53
+ case "realistic_type":
54
+ return "realistic-type";
55
+ case "realistic_press":
56
+ return "realistic-press";
57
+ case "cursor_move":
58
+ return "cursor-move";
59
+ case "cursor_hide":
60
+ return "cursor-hide";
61
+ case "autofill_login":
62
+ return "autofill-login";
63
+ default:
64
+ return action;
65
+ }
66
+ }
67
+ function normalizeLegacyBrowserAction(action) {
68
+ const normalizedAction = normalizeBrowserAction(action);
69
+ switch (normalizedAction) {
49
70
  case "info":
50
71
  case "context":
51
72
  case "get_context":
@@ -60,11 +81,14 @@ function normalizeLegacyBrowserAction(action) {
60
81
  case void 0:
61
82
  return void 0;
62
83
  default:
63
- return action;
84
+ return isWorkspaceSubaction(normalizedAction) ? normalizedAction : void 0;
64
85
  }
65
86
  }
87
+ function isWorkspaceSubaction(action) {
88
+ return action === "back" || action === "click" || action === "close" || action === "forward" || action === "get" || action === "hide" || action === "navigate" || action === "open" || action === "press" || action === "reload" || action === "screenshot" || action === "show" || action === "snapshot" || action === "state" || action === "tab" || action === "type" || action === "wait" || action === "realistic-click" || action === "realistic-fill" || action === "realistic-type" || action === "realistic-press" || action === "cursor-move" || action === "cursor-hide";
89
+ }
66
90
  function normalizeLegacyTabAction(action) {
67
- switch (action) {
91
+ switch (normalizeBrowserAction(action)) {
68
92
  case "list_tabs":
69
93
  return "list";
70
94
  case "open_tab":
@@ -115,7 +139,6 @@ const browserAction = {
115
139
  "CONTROL_BROWSER",
116
140
  "CONTROL_BROWSER_SESSION",
117
141
  "MANAGE_ELIZA_BROWSER_WORKSPACE",
118
- "MANAGE_LIFEOPS_BROWSER",
119
142
  "NAVIGATE_SITE",
120
143
  "OPEN_SITE",
121
144
  "USE_BROWSER",
@@ -128,14 +151,15 @@ const browserAction = {
128
151
  "LOG_INTO_SITE",
129
152
  "SIGN_IN_TO_SITE"
130
153
  ],
131
- description: "Single BROWSER action \u2014 control whichever browser target is registered. Targets are pluggable: `workspace` (electrobun-embedded BrowserView, the default; falls back to a JSDOM web mode when the desktop bridge isn't configured), `bridge` (the user's real Chrome/Safari via the Agent Browser Bridge companion extension), and `computeruse` (a local puppeteer-driven Chromium via plugin-computeruse). The agent uses what is available \u2014 the BrowserService picks the active target when none is specified. Use `subaction: \"autofill-login\"` with `domain` (and optional `username`, `submit`) to vault-gated autofill into an open workspace tab.",
132
- descriptionCompressed: "Browser tab/page control: open/navigate/click/type/screenshot/state; subaction autofill-login + domain autofill vault-gated credential into workspace tab pre-authorized in Settings Vault Logins. Bridge settings/status use MANAGE_BROWSER_BRIDGE.",
154
+ description: "BROWSER action. Control registered browser target: app workspace, bridge Chrome/Safari companion, computeruse Chromium, or Stagehand fallback. BrowserService picks target if omitted. action=autofill_login + domain vault-gated autofills open workspace tab.",
155
+ descriptionCompressed: "Browser open|navigate|click|type|screenshot|state|autofill_login; bridge status elsewhere",
133
156
  validate: async () => true,
134
157
  handler: async (runtime, message, _state, options) => {
135
158
  const params = options?.parameters;
136
159
  const messageText = getMessageText(message);
137
160
  const subaction = inferBrowserSubaction(params, messageText);
138
161
  if (subaction === "autofill-login") {
162
+ const { executeBrowserAutofillLogin } = await import("./browser-autofill-login.js");
139
163
  return executeBrowserAutofillLogin(runtime, message, options);
140
164
  }
141
165
  const url = params?.url?.trim() || extractFirstUrl(messageText) || void 0;
@@ -148,6 +172,7 @@ const browserAction = {
148
172
  subaction,
149
173
  tabAction: params?.tabAction ?? normalizeLegacyTabAction(params?.action),
150
174
  text: params?.text,
175
+ value: params?.text,
151
176
  timeoutMs: params?.timeoutMs,
152
177
  url,
153
178
  cursorDurationMs: params?.cursorDurationMs,
@@ -156,9 +181,7 @@ const browserAction = {
156
181
  x: params?.x,
157
182
  y: params?.y
158
183
  };
159
- const browserService = runtime.getService(
160
- BROWSER_SERVICE_TYPE
161
- );
184
+ const browserService = runtime.getService(BROWSER_SERVICE_TYPE);
162
185
  try {
163
186
  logger.info(
164
187
  `[BROWSER] ${command.subaction} via target=${params?.target ?? "auto"} (workspace mode=${getBrowserWorkspaceMode(process.env)})`
@@ -193,9 +216,15 @@ const browserAction = {
193
216
  }
194
217
  },
195
218
  parameters: [
219
+ {
220
+ name: "target",
221
+ description: "Optional browser target id. Common values: workspace, bridge, computeruse, stagehand.",
222
+ required: false,
223
+ schema: { type: "string" }
224
+ },
196
225
  {
197
226
  name: "action",
198
- description: "Browser action to perform. Legacy subaction is also accepted.",
227
+ description: "Browser action. Snake_case canonical; legacy kebab-case and subaction accepted.",
199
228
  required: false,
200
229
  schema: {
201
230
  type: "string",
@@ -224,47 +253,13 @@ const browserAction = {
224
253
  "wait",
225
254
  "close_tab",
226
255
  "switch_tab",
227
- "realistic-click",
228
- "realistic-fill",
229
- "realistic-type",
230
- "realistic-press",
231
- "cursor-move",
232
- "cursor-hide",
233
- "autofill-login"
234
- ]
235
- }
236
- },
237
- {
238
- name: "subaction",
239
- description: "Legacy alias for action.",
240
- required: false,
241
- schema: {
242
- type: "string",
243
- enum: [
244
- "back",
245
- "click",
246
- "close",
247
- "forward",
248
- "get",
249
- "hide",
250
- "navigate",
251
- "open",
252
- "press",
253
- "reload",
254
- "screenshot",
255
- "show",
256
- "snapshot",
257
- "state",
258
- "tab",
259
- "type",
260
- "wait",
261
- "realistic-click",
262
- "realistic-fill",
263
- "realistic-type",
264
- "realistic-press",
265
- "cursor-move",
266
- "cursor-hide",
267
- "autofill-login"
256
+ "realistic_click",
257
+ "realistic_fill",
258
+ "realistic_type",
259
+ "realistic_press",
260
+ "cursor_move",
261
+ "cursor_hide",
262
+ "autofill_login"
268
263
  ]
269
264
  }
270
265
  },
@@ -279,19 +274,19 @@ const browserAction = {
279
274
  },
280
275
  {
281
276
  name: "domain",
282
- description: "Required when subaction is autofill-login: registrable hostname (e.g. `github.com`).",
277
+ description: "Required for action=autofill_login: registrable hostname, e.g. github.com.",
283
278
  required: false,
284
279
  schema: { type: "string" }
285
280
  },
286
281
  {
287
282
  name: "username",
288
- description: "When using autofill-login: specific saved login; omit for most recently modified.",
283
+ description: "For autofill-login: saved login username; omit for latest.",
289
284
  required: false,
290
285
  schema: { type: "string" }
291
286
  },
292
287
  {
293
288
  name: "submit",
294
- description: "When using autofill-login: submit the form after filling (default false).",
289
+ description: "For autofill-login: submit after filling. Default false.",
295
290
  required: false,
296
291
  schema: { type: "boolean" }
297
292
  },
@@ -345,7 +340,7 @@ const browserAction = {
345
340
  },
346
341
  {
347
342
  name: "watchMode",
348
- description: "Hint that the user is watching; prefers realistic-* subactions for click/fill so the cursor moves visibly and pointer events fire faithfully.",
343
+ description: "User watching hint; prefer realistic-* click/fill, visible cursor, pointer events.",
349
344
  required: false,
350
345
  schema: { type: "boolean" }
351
346
  },
@@ -363,7 +358,7 @@ const browserAction = {
363
358
  },
364
359
  {
365
360
  name: "replace",
366
- description: "Replace existing input value when filling (vs append) \u2014 applies to realistic-fill",
361
+ description: "For realistic-fill: replace existing input, not append.",
367
362
  required: false,
368
363
  schema: { type: "boolean" }
369
364
  },