@elizaos/plugin-browser 2.0.0-beta.1 → 2.0.11-beta.7
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.
- package/LICENSE +21 -0
- package/README.md +106 -64
- package/dist/actions/browser-autofill-login.d.ts.map +1 -1
- package/dist/actions/browser-autofill-login.js.map +1 -1
- package/dist/actions/browser.d.ts +5 -6
- package/dist/actions/browser.d.ts.map +1 -1
- package/dist/actions/browser.js +54 -59
- package/dist/actions/browser.js.map +1 -1
- package/dist/actions/manage-browser-bridge.d.ts.map +1 -1
- package/dist/actions/manage-browser-bridge.js +10 -14
- package/dist/actions/manage-browser-bridge.js.map +1 -1
- package/dist/bridge-policy.d.ts +10 -0
- package/dist/bridge-policy.d.ts.map +1 -0
- package/dist/bridge-policy.js +37 -0
- package/dist/bridge-policy.js.map +1 -0
- package/dist/bridge-readiness.d.ts +16 -0
- package/dist/bridge-readiness.d.ts.map +1 -0
- package/dist/bridge-readiness.js +82 -0
- package/dist/bridge-readiness.js.map +1 -0
- package/dist/bridge-records.d.ts +9 -0
- package/dist/bridge-records.d.ts.map +1 -0
- package/dist/bridge-records.js +37 -0
- package/dist/bridge-records.js.map +1 -0
- package/dist/browser-capture-hooks.d.ts +9 -0
- package/dist/browser-capture-hooks.d.ts.map +1 -0
- package/dist/browser-capture-hooks.js +15 -0
- package/dist/browser-capture-hooks.js.map +1 -0
- package/dist/browser-service.d.ts +22 -4
- package/dist/browser-service.d.ts.map +1 -1
- package/dist/browser-service.js +63 -15
- package/dist/browser-service.js.map +1 -1
- package/dist/browser-workspace-hooks.d.ts +14 -0
- package/dist/browser-workspace-hooks.d.ts.map +1 -0
- package/dist/browser-workspace-hooks.js +15 -0
- package/dist/browser-workspace-hooks.js.map +1 -0
- package/dist/companion-auth.d.ts +34 -0
- package/dist/companion-auth.d.ts.map +1 -0
- package/dist/companion-auth.js +98 -0
- package/dist/companion-auth.js.map +1 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -11
- package/dist/index.js.map +1 -1
- package/dist/message-adapter.d.ts +9 -0
- package/dist/message-adapter.d.ts.map +1 -0
- package/dist/message-adapter.js +104 -0
- package/dist/message-adapter.js.map +1 -0
- package/dist/packaging.d.ts.map +1 -1
- package/dist/packaging.js +2 -0
- package/dist/packaging.js.map +1 -1
- package/dist/password-manager-bridge.d.ts +50 -0
- package/dist/password-manager-bridge.d.ts.map +1 -0
- package/dist/password-manager-bridge.js +437 -0
- package/dist/password-manager-bridge.js.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +8 -4
- package/dist/plugin.js.map +1 -1
- package/dist/providers/workspace.d.ts +1 -1
- package/dist/providers/workspace.js.map +1 -1
- package/dist/routes/bridge.d.ts.map +1 -1
- package/dist/routes/bridge.js +63 -14
- package/dist/routes/bridge.js.map +1 -1
- package/dist/routes/workspace-setup.d.ts.map +1 -1
- package/dist/routes/workspace-setup.js +1 -1
- package/dist/routes/workspace-setup.js.map +1 -1
- package/dist/routes/workspace.d.ts +1 -2
- package/dist/routes/workspace.d.ts.map +1 -1
- package/dist/routes/workspace.js +63 -3
- package/dist/routes/workspace.js.map +1 -1
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js.map +1 -1
- package/dist/service.d.ts +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js.map +1 -1
- package/dist/targets/bridge-target.d.ts +1 -1
- package/dist/targets/bridge-target.d.ts.map +1 -1
- package/dist/targets/bridge-target.js.map +1 -1
- package/dist/targets/stagehand-target.d.ts +3 -0
- package/dist/targets/stagehand-target.d.ts.map +1 -0
- package/dist/targets/stagehand-target.js +187 -0
- package/dist/targets/stagehand-target.js.map +1 -0
- package/dist/workspace/browser-capture.d.ts +1 -1
- package/dist/workspace/browser-capture.js.map +1 -1
- package/dist/workspace/browser-workspace-desktop.d.ts +1 -1
- package/dist/workspace/browser-workspace-desktop.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-desktop.js +47 -25
- package/dist/workspace/browser-workspace-desktop.js.map +1 -1
- package/dist/workspace/browser-workspace-forms.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-forms.js +1 -1
- package/dist/workspace/browser-workspace-forms.js.map +1 -1
- package/dist/workspace/browser-workspace-helpers.d.ts +7 -0
- package/dist/workspace/browser-workspace-helpers.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-helpers.js +37 -0
- package/dist/workspace/browser-workspace-helpers.js.map +1 -1
- package/dist/workspace/browser-workspace-network.d.ts +1 -1
- package/dist/workspace/browser-workspace-network.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-types.d.ts +15 -0
- package/dist/workspace/browser-workspace-types.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-types.js.map +1 -1
- package/dist/workspace/browser-workspace-web.d.ts.map +1 -1
- package/dist/workspace/browser-workspace-web.js +15 -88
- package/dist/workspace/browser-workspace-web.js.map +1 -1
- package/dist/workspace/browser-workspace.d.ts +1 -1
- package/dist/workspace/browser-workspace.d.ts.map +1 -1
- package/dist/workspace/browser-workspace.js +9 -4
- package/dist/workspace/browser-workspace.js.map +1 -1
- package/package.json +28 -7
- package/dist/actions/browser-autofill-login.d.js +0 -1
- package/dist/actions/browser-autofill-login.d.js.map +0 -1
- package/dist/actions/browser.d.js +0 -1
- package/dist/actions/browser.d.js.map +0 -1
- package/dist/actions/manage-browser-bridge.d.js +0 -1
- package/dist/actions/manage-browser-bridge.d.js.map +0 -1
- package/dist/ambient-jsdom.d.js +0 -1
- package/dist/ambient-jsdom.d.js.map +0 -1
- package/dist/browser-service.d.js +0 -1
- package/dist/browser-service.d.js.map +0 -1
- package/dist/contracts.d.js +0 -1
- package/dist/contracts.d.js.map +0 -1
- package/dist/index.d.js +0 -21
- package/dist/index.d.js.map +0 -1
- package/dist/lifeops-session-contracts.d.js +0 -1
- package/dist/lifeops-session-contracts.d.js.map +0 -1
- package/dist/packaging.d.js +0 -1
- package/dist/packaging.d.js.map +0 -1
- package/dist/plugin.d.js +0 -1
- package/dist/plugin.d.js.map +0 -1
- package/dist/providers/workspace.d.js +0 -1
- package/dist/providers/workspace.d.js.map +0 -1
- package/dist/routes/bridge.d.js +0 -1
- package/dist/routes/bridge.d.js.map +0 -1
- package/dist/routes/workspace-account-gate.d.js +0 -1
- package/dist/routes/workspace-account-gate.d.js.map +0 -1
- package/dist/routes/workspace-setup.d.js +0 -1
- package/dist/routes/workspace-setup.d.js.map +0 -1
- package/dist/routes/workspace.d.js +0 -1
- package/dist/routes/workspace.d.js.map +0 -1
- package/dist/schema.d.js +0 -1
- package/dist/schema.d.js.map +0 -1
- package/dist/service.d.js +0 -1
- package/dist/service.d.js.map +0 -1
- package/dist/targets/bridge-target.d.js +0 -1
- package/dist/targets/bridge-target.d.js.map +0 -1
- package/dist/workspace/browser-capture.d.js +0 -1
- package/dist/workspace/browser-capture.d.js.map +0 -1
- package/dist/workspace/browser-workspace-desktop.d.js +0 -1
- package/dist/workspace/browser-workspace-desktop.d.js.map +0 -1
- package/dist/workspace/browser-workspace-elements.d.js +0 -1
- package/dist/workspace/browser-workspace-elements.d.js.map +0 -1
- package/dist/workspace/browser-workspace-forms.d.js +0 -1
- package/dist/workspace/browser-workspace-forms.d.js.map +0 -1
- package/dist/workspace/browser-workspace-helpers.d.js +0 -1
- package/dist/workspace/browser-workspace-helpers.d.js.map +0 -1
- package/dist/workspace/browser-workspace-jsdom.d.js +0 -1
- package/dist/workspace/browser-workspace-jsdom.d.js.map +0 -1
- package/dist/workspace/browser-workspace-network.d.js +0 -1
- package/dist/workspace/browser-workspace-network.d.js.map +0 -1
- package/dist/workspace/browser-workspace-snapshots.d.js +0 -1
- package/dist/workspace/browser-workspace-snapshots.d.js.map +0 -1
- package/dist/workspace/browser-workspace-state.d.js +0 -1
- package/dist/workspace/browser-workspace-state.d.js.map +0 -1
- package/dist/workspace/browser-workspace-types.d.js +0 -1
- package/dist/workspace/browser-workspace-types.d.js.map +0 -1
- package/dist/workspace/browser-workspace-web.d.js +0 -1
- package/dist/workspace/browser-workspace-web.d.js.map +0 -1
- package/dist/workspace/browser-workspace.d.js +0 -11
- package/dist/workspace/browser-workspace.d.js.map +0 -1
- package/dist/workspace/index.d.js +0 -3
- 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
|
-
|
|
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
|
-
##
|
|
5
|
+
## What this plugin provides
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
### Actions
|
|
9
8
|
|
|
10
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
`
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
48
|
+
External plugins can register additional targets by calling `BrowserService.registerTarget(target)`.
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
Workspace UI, agent actions, and companion extension use the same API surface.
|
|
50
|
+
### Provider
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
72
|
+
### Environment variables
|
|
50
73
|
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
- `Authorization: Bearer <pairing token>`
|
|
83
|
+
### Vault keys (set by the user, not env vars)
|
|
56
84
|
|
|
57
|
-
|
|
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
|
-
|
|
87
|
+
- `creds.<domain>.:autoallow = "1"` — set via Settings → Vault → Logins.
|
|
62
88
|
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
106
|
+
- `browser_bridge_companions`
|
|
107
|
+
- `browser_bridge_settings`
|
|
108
|
+
- `browser_bridge_tabs`
|
|
109
|
+
- `browser_bridge_page_contexts`
|
|
84
110
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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,
|
|
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
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
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 =
|
|
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;
|
|
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"}
|
package/dist/actions/browser.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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: "
|
|
132
|
-
descriptionCompressed: "Browser
|
|
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
|
|
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
|
-
"
|
|
228
|
-
"
|
|
229
|
-
"
|
|
230
|
-
"
|
|
231
|
-
"
|
|
232
|
-
"
|
|
233
|
-
"
|
|
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
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
361
|
+
description: "For realistic-fill: replace existing input, not append.",
|
|
367
362
|
required: false,
|
|
368
363
|
schema: { type: "boolean" }
|
|
369
364
|
},
|