mirror of https://github.com/openclaw/openclaw.git
fix(browser): prefer user profile over chrome relay
This commit is contained in:
parent
1f9cc647f8
commit
b6d1d0d72d
|
|
@ -11,7 +11,7 @@ Docs: https://docs.openclaw.ai
|
||||||
- Android/chat settings: redesign the chat settings sheet with grouped device and media sections, refresh the Connect and Voice tabs, and tighten the chat composer/session header for a denser mobile layout. (#44894) Thanks @obviyus.
|
- Android/chat settings: redesign the chat settings sheet with grouped device and media sections, refresh the Connect and Voice tabs, and tighten the chat composer/session header for a denser mobile layout. (#44894) Thanks @obviyus.
|
||||||
- iOS/onboarding: add a first-run welcome pager before gateway setup, stop auto-opening the QR scanner, and show `/pair qr` instructions on the connect step. (#45054) Thanks @ngutman.
|
- iOS/onboarding: add a first-run welcome pager before gateway setup, stop auto-opening the QR scanner, and show `/pair qr` instructions on the connect step. (#45054) Thanks @ngutman.
|
||||||
- Docker/timezone override: add `OPENCLAW_TZ` so `docker-setup.sh` can pin gateway and CLI containers to a chosen IANA timezone instead of inheriting the daemon default. (#34119) Thanks @Lanfei.
|
- Docker/timezone override: add `OPENCLAW_TZ` so `docker-setup.sh` can pin gateway and CLI containers to a chosen IANA timezone instead of inheriting the daemon default. (#34119) Thanks @Lanfei.
|
||||||
- Browser/agents: add `browserSession="agent" | "user"` so agent browser calls can explicitly choose the isolated OpenClaw browser or a logged-in user browser, with docs for when user presence and attach approval are required.
|
- Browser/agents: add built-in `profile="user"` for the logged-in host browser and `profile="chrome-relay"` for the extension relay, so agent browser calls can prefer the real signed-in browser without the extra `browserSession` selector.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ Related:
|
||||||
## Quick start (local)
|
## Quick start (local)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw browser --browser-profile chrome tabs
|
openclaw browser profiles
|
||||||
openclaw browser --browser-profile openclaw start
|
openclaw browser --browser-profile openclaw start
|
||||||
openclaw browser --browser-profile openclaw open https://example.com
|
openclaw browser --browser-profile openclaw open https://example.com
|
||||||
openclaw browser --browser-profile openclaw snapshot
|
openclaw browser --browser-profile openclaw snapshot
|
||||||
|
|
@ -38,7 +38,8 @@ openclaw browser --browser-profile openclaw snapshot
|
||||||
Profiles are named browser routing configs. In practice:
|
Profiles are named browser routing configs. In practice:
|
||||||
|
|
||||||
- `openclaw`: launches/attaches to a dedicated OpenClaw-managed Chrome instance (isolated user data dir).
|
- `openclaw`: launches/attaches to a dedicated OpenClaw-managed Chrome instance (isolated user data dir).
|
||||||
- `chrome`: controls your existing Chrome tab(s) via the Chrome extension relay.
|
- `user`: controls your existing signed-in Chrome session via Chrome DevTools MCP.
|
||||||
|
- `chrome-relay`: controls your existing Chrome tab(s) via the Chrome extension relay.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw browser profiles
|
openclaw browser profiles
|
||||||
|
|
|
||||||
|
|
@ -2342,7 +2342,7 @@ See [Plugins](/tools/plugin).
|
||||||
browser: {
|
browser: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
evaluateEnabled: true,
|
evaluateEnabled: true,
|
||||||
defaultProfile: "chrome",
|
defaultProfile: "user",
|
||||||
ssrfPolicy: {
|
ssrfPolicy: {
|
||||||
dangerouslyAllowPrivateNetwork: true, // default trusted-network mode
|
dangerouslyAllowPrivateNetwork: true, // default trusted-network mode
|
||||||
// allowPrivateNetwork: true, // legacy alias
|
// allowPrivateNetwork: true, // legacy alias
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ Look for:
|
||||||
|
|
||||||
- Valid browser executable path.
|
- Valid browser executable path.
|
||||||
- CDP profile reachability.
|
- CDP profile reachability.
|
||||||
- Extension relay tab attachment for `profile="chrome"`.
|
- Extension relay tab attachment for `profile="chrome-relay"`.
|
||||||
|
|
||||||
Common signatures:
|
Common signatures:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ curl -s http://127.0.0.1:18791/tabs
|
||||||
|
|
||||||
### Problem: "Chrome extension relay is running, but no tab is connected"
|
### Problem: "Chrome extension relay is running, but no tab is connected"
|
||||||
|
|
||||||
You’re using the `chrome` profile (extension relay). It expects the OpenClaw
|
You’re using the `chrome-relay` profile (extension relay). It expects the OpenClaw
|
||||||
browser extension to be attached to a live tab.
|
browser extension to be attached to a live tab.
|
||||||
|
|
||||||
Fix options:
|
Fix options:
|
||||||
|
|
@ -135,5 +135,5 @@ Fix options:
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- The `chrome` profile uses your **system default Chromium browser** when possible.
|
- The `chrome-relay` profile uses your **system default Chromium browser** when possible.
|
||||||
- Local `openclaw` profiles auto-assign `cdpPort`/`cdpUrl`; only set those for remote CDP.
|
- Local `openclaw` profiles auto-assign `cdpPort`/`cdpUrl`; only set those for remote CDP.
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ OpenClaw controls a **dedicated Chrome profile** (named `openclaw`, orange‑tin
|
||||||
For agent browser tool calls:
|
For agent browser tool calls:
|
||||||
|
|
||||||
- Default choice: the agent should use its isolated `openclaw` browser.
|
- Default choice: the agent should use its isolated `openclaw` browser.
|
||||||
- Use the **user browser** only when existing logged-in sessions matter and the user is at the computer to click/approve any attach prompt.
|
- Use `profile="user"` only when existing logged-in sessions matter and the user is at the computer to click/approve any attach prompt.
|
||||||
- If you need to force the choice, use `browserSession="agent"` or `browserSession="user"`.
|
- Use `profile="chrome-relay"` only for the Chrome extension / toolbar-button attach flow.
|
||||||
- If you have multiple user-browser profiles, specify the profile explicitly instead of guessing.
|
- If you have multiple user-browser profiles, specify the profile explicitly instead of guessing.
|
||||||
|
|
||||||
Two easy ways to access it:
|
Two easy ways to access it:
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ Choose this when:
|
||||||
|
|
||||||
### Option 2: Chrome extension relay
|
### Option 2: Chrome extension relay
|
||||||
|
|
||||||
Use the built-in `chrome` profile plus the OpenClaw Chrome extension.
|
Use the built-in `chrome-relay` profile plus the OpenClaw Chrome extension.
|
||||||
|
|
||||||
Choose this when:
|
Choose this when:
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ Example:
|
||||||
{
|
{
|
||||||
browser: {
|
browser: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
defaultProfile: "chrome",
|
defaultProfile: "chrome-relay",
|
||||||
relayBindHost: "0.0.0.0",
|
relayBindHost: "0.0.0.0",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +197,7 @@ openclaw browser tabs --browser-profile remote
|
||||||
For the extension relay:
|
For the extension relay:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw browser tabs --browser-profile chrome
|
openclaw browser tabs --browser-profile chrome-relay
|
||||||
```
|
```
|
||||||
|
|
||||||
Good result:
|
Good result:
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ Beginner view:
|
||||||
- Think of it as a **separate, agent-only browser**.
|
- Think of it as a **separate, agent-only browser**.
|
||||||
- The `openclaw` profile does **not** touch your personal browser profile.
|
- The `openclaw` profile does **not** touch your personal browser profile.
|
||||||
- The agent can **open tabs, read pages, click, and type** in a safe lane.
|
- The agent can **open tabs, read pages, click, and type** in a safe lane.
|
||||||
- The default `chrome` profile uses the **system default Chromium browser** via the
|
- The built-in `user` profile attaches to your real signed-in Chrome session;
|
||||||
extension relay; switch to `openclaw` for the isolated managed browser.
|
`chrome-relay` is the explicit extension-relay profile.
|
||||||
|
|
||||||
## What you get
|
## What you get
|
||||||
|
|
||||||
|
|
@ -43,22 +43,22 @@ openclaw browser --browser-profile openclaw snapshot
|
||||||
If you get “Browser disabled”, enable it in config (see below) and restart the
|
If you get “Browser disabled”, enable it in config (see below) and restart the
|
||||||
Gateway.
|
Gateway.
|
||||||
|
|
||||||
## Profiles: `openclaw` vs `chrome`
|
## Profiles: `openclaw` vs `user` vs `chrome-relay`
|
||||||
|
|
||||||
- `openclaw`: managed, isolated browser (no extension required).
|
- `openclaw`: managed, isolated browser (no extension required).
|
||||||
- `chrome`: extension relay to your **system browser** (requires the OpenClaw
|
- `user`: built-in Chrome MCP attach profile for your **real signed-in Chrome**
|
||||||
extension to be attached to a tab).
|
session.
|
||||||
- `existing-session`: official Chrome MCP attach flow for a running Chrome
|
- `chrome-relay`: extension relay to your **system browser** (requires the
|
||||||
profile.
|
OpenClaw extension to be attached to a tab).
|
||||||
|
|
||||||
For agent browser tool calls:
|
For agent browser tool calls:
|
||||||
|
|
||||||
- Default: use the isolated `openclaw` browser.
|
- Default: use the isolated `openclaw` browser.
|
||||||
- Use the **user browser** only when existing logged-in sessions matter and the
|
- Prefer `profile="user"` when existing logged-in sessions matter and the user
|
||||||
user is at the computer to click/approve any attach prompt.
|
is at the computer to click/approve any attach prompt.
|
||||||
- If you need to force the choice, use `browserSession="agent"` or
|
- Use `profile="chrome-relay"` only when the user explicitly wants the Chrome
|
||||||
`browserSession="user"`.
|
extension / toolbar-button attach flow.
|
||||||
- `profile` is the explicit override when you already know which profile to use.
|
- `profile` is the explicit override when you want a specific browser mode.
|
||||||
|
|
||||||
Set `browser.defaultProfile: "openclaw"` if you want managed mode by default.
|
Set `browser.defaultProfile: "openclaw"` if you want managed mode by default.
|
||||||
|
|
||||||
|
|
@ -88,11 +88,16 @@ Browser settings live in `~/.openclaw/openclaw.json`.
|
||||||
profiles: {
|
profiles: {
|
||||||
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
||||||
work: { cdpPort: 18801, color: "#0066CC" },
|
work: { cdpPort: 18801, color: "#0066CC" },
|
||||||
"chrome-live": {
|
user: {
|
||||||
driver: "existing-session",
|
driver: "existing-session",
|
||||||
attachOnly: true,
|
attachOnly: true,
|
||||||
color: "#00AA00",
|
color: "#00AA00",
|
||||||
},
|
},
|
||||||
|
"chrome-relay": {
|
||||||
|
driver: "extension",
|
||||||
|
cdpUrl: "http://127.0.0.1:18792",
|
||||||
|
color: "#00AA00",
|
||||||
|
},
|
||||||
remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" },
|
remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -113,7 +118,7 @@ Notes:
|
||||||
- `browser.ssrfPolicy.allowPrivateNetwork` remains supported as a legacy alias for compatibility.
|
- `browser.ssrfPolicy.allowPrivateNetwork` remains supported as a legacy alias for compatibility.
|
||||||
- `attachOnly: true` means “never launch a local browser; only attach if it is already running.”
|
- `attachOnly: true` means “never launch a local browser; only attach if it is already running.”
|
||||||
- `color` + per-profile `color` tint the browser UI so you can see which profile is active.
|
- `color` + per-profile `color` tint the browser UI so you can see which profile is active.
|
||||||
- Default profile is `openclaw` (OpenClaw-managed standalone browser). Use `defaultProfile: "chrome"` to opt into the Chrome extension relay.
|
- Default profile is `openclaw` (OpenClaw-managed standalone browser). Use `defaultProfile: "user"` to opt into the signed-in user browser, or `defaultProfile: "chrome-relay"` for the extension relay.
|
||||||
- Auto-detect order: system default browser if Chromium-based; otherwise Chrome → Brave → Edge → Chromium → Chrome Canary.
|
- Auto-detect order: system default browser if Chromium-based; otherwise Chrome → Brave → Edge → Chromium → Chrome Canary.
|
||||||
- Local `openclaw` profiles auto-assign `cdpPort`/`cdpUrl` — set those only for remote CDP.
|
- Local `openclaw` profiles auto-assign `cdpPort`/`cdpUrl` — set those only for remote CDP.
|
||||||
- `driver: "existing-session"` uses Chrome DevTools MCP instead of raw CDP. Do
|
- `driver: "existing-session"` uses Chrome DevTools MCP instead of raw CDP. Do
|
||||||
|
|
@ -287,7 +292,7 @@ OpenClaw supports multiple named profiles (routing configs). Profiles can be:
|
||||||
Defaults:
|
Defaults:
|
||||||
|
|
||||||
- The `openclaw` profile is auto-created if missing.
|
- The `openclaw` profile is auto-created if missing.
|
||||||
- The `chrome` profile is built-in for the Chrome extension relay (points at `http://127.0.0.1:18792` by default).
|
- The `chrome-relay` profile is built-in for the Chrome extension relay (points at `http://127.0.0.1:18792` by default).
|
||||||
- Existing-session profiles are opt-in; create them with `--driver existing-session`.
|
- Existing-session profiles are opt-in; create them with `--driver existing-session`.
|
||||||
- Local CDP ports allocate from **18800–18899** by default.
|
- Local CDP ports allocate from **18800–18899** by default.
|
||||||
- Deleting a profile moves its local data directory to Trash.
|
- Deleting a profile moves its local data directory to Trash.
|
||||||
|
|
@ -331,8 +336,8 @@ openclaw browser extension install
|
||||||
|
|
||||||
2. Use it:
|
2. Use it:
|
||||||
|
|
||||||
- CLI: `openclaw browser --browser-profile chrome tabs`
|
- CLI: `openclaw browser --browser-profile chrome-relay tabs`
|
||||||
- Agent tool: `browser` with `browserSession="user"` (or `profile="chrome"`)
|
- Agent tool: `browser` with `profile="chrome-relay"`
|
||||||
|
|
||||||
Optional: if you want a different name or relay port, create your own profile:
|
Optional: if you want a different name or relay port, create your own profile:
|
||||||
|
|
||||||
|
|
@ -348,8 +353,9 @@ Notes:
|
||||||
|
|
||||||
- This mode relies on Playwright-on-CDP for most operations (screenshots/snapshots/actions).
|
- This mode relies on Playwright-on-CDP for most operations (screenshots/snapshots/actions).
|
||||||
- Detach by clicking the extension icon again.
|
- Detach by clicking the extension icon again.
|
||||||
- Agent use: prefer `browserSession="user"` for logged-in sites. The user must be
|
- Agent use: prefer `profile="user"` for logged-in sites. Use `profile="chrome-relay"`
|
||||||
present to click the extension and attach the tab.
|
only when you specifically want the extension flow. The user must be present
|
||||||
|
to click the extension and attach the tab.
|
||||||
|
|
||||||
## Chrome existing-session via MCP
|
## Chrome existing-session via MCP
|
||||||
|
|
||||||
|
|
@ -362,14 +368,12 @@ Official background and setup references:
|
||||||
- [Chrome for Developers: Use Chrome DevTools MCP with your browser session](https://developer.chrome.com/blog/chrome-devtools-mcp-debug-your-browser-session)
|
- [Chrome for Developers: Use Chrome DevTools MCP with your browser session](https://developer.chrome.com/blog/chrome-devtools-mcp-debug-your-browser-session)
|
||||||
- [Chrome DevTools MCP README](https://github.com/ChromeDevTools/chrome-devtools-mcp)
|
- [Chrome DevTools MCP README](https://github.com/ChromeDevTools/chrome-devtools-mcp)
|
||||||
|
|
||||||
Create a profile:
|
Built-in profile:
|
||||||
|
|
||||||
```bash
|
- `user`
|
||||||
openclaw browser create-profile \
|
|
||||||
--name chrome-live \
|
Optional: create your own custom existing-session profile if you want a
|
||||||
--driver existing-session \
|
different name or color.
|
||||||
--color "#00AA00"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then in Chrome:
|
Then in Chrome:
|
||||||
|
|
||||||
|
|
@ -380,10 +384,10 @@ Then in Chrome:
|
||||||
Live attach smoke test:
|
Live attach smoke test:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw browser --browser-profile chrome-live start
|
openclaw browser --browser-profile user start
|
||||||
openclaw browser --browser-profile chrome-live status
|
openclaw browser --browser-profile user status
|
||||||
openclaw browser --browser-profile chrome-live tabs
|
openclaw browser --browser-profile user tabs
|
||||||
openclaw browser --browser-profile chrome-live snapshot --format ai
|
openclaw browser --browser-profile user snapshot --format ai
|
||||||
```
|
```
|
||||||
|
|
||||||
What success looks like:
|
What success looks like:
|
||||||
|
|
@ -402,9 +406,10 @@ What to check if attach does not work:
|
||||||
|
|
||||||
Agent use:
|
Agent use:
|
||||||
|
|
||||||
- Use `browserSession="user"` when you need the user’s logged-in browser state.
|
- Use `profile="user"` when you need the user’s logged-in browser state.
|
||||||
- If you know the profile name, pass `profile="chrome-live"` (or your custom
|
- If you use a custom existing-session profile, pass that explicit profile name.
|
||||||
existing-session profile).
|
- Prefer `profile="user"` over `profile="chrome-relay"` unless the user
|
||||||
|
explicitly wants the extension / attach-tab flow.
|
||||||
- Only choose this mode when the user is at the computer to approve the attach
|
- Only choose this mode when the user is at the computer to approve the attach
|
||||||
prompt.
|
prompt.
|
||||||
- the Gateway or node host can spawn `npx chrome-devtools-mcp@latest --autoConnect`
|
- the Gateway or node host can spawn `npx chrome-devtools-mcp@latest --autoConnect`
|
||||||
|
|
@ -432,7 +437,7 @@ WSL2 / cross-namespace example:
|
||||||
browser: {
|
browser: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
relayBindHost: "0.0.0.0",
|
relayBindHost: "0.0.0.0",
|
||||||
defaultProfile: "chrome",
|
defaultProfile: "chrome-relay",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ After upgrading OpenClaw:
|
||||||
|
|
||||||
## Use it (set gateway token once)
|
## Use it (set gateway token once)
|
||||||
|
|
||||||
OpenClaw ships with a built-in browser profile named `chrome` that targets the extension relay on the default port.
|
OpenClaw ships with a built-in browser profile named `chrome-relay` that targets the extension relay on the default port.
|
||||||
|
|
||||||
Before first attach, open extension Options and set:
|
Before first attach, open extension Options and set:
|
||||||
|
|
||||||
|
|
@ -71,8 +71,8 @@ Before first attach, open extension Options and set:
|
||||||
|
|
||||||
Use it:
|
Use it:
|
||||||
|
|
||||||
- CLI: `openclaw browser --browser-profile chrome tabs`
|
- CLI: `openclaw browser --browser-profile chrome-relay tabs`
|
||||||
- Agent tool: `browser` with `profile="chrome"`
|
- Agent tool: `browser` with `profile="chrome-relay"`
|
||||||
|
|
||||||
If you want a different name or a different relay port, create your own profile:
|
If you want a different name or a different relay port, create your own profile:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -310,17 +310,16 @@ Profile management:
|
||||||
|
|
||||||
Common parameters:
|
Common parameters:
|
||||||
|
|
||||||
- `browserSession` (`agent` | `user`)
|
|
||||||
- `profile` (optional; defaults to `browser.defaultProfile`)
|
- `profile` (optional; defaults to `browser.defaultProfile`)
|
||||||
- `target` (`sandbox` | `host` | `node`)
|
- `target` (`sandbox` | `host` | `node`)
|
||||||
- `node` (optional; picks a specific node id/name)
|
- `node` (optional; picks a specific node id/name)
|
||||||
Notes:
|
Notes:
|
||||||
- Requires `browser.enabled=true` (default is `true`; set `false` to disable).
|
- Requires `browser.enabled=true` (default is `true`; set `false` to disable).
|
||||||
- `browserSession="agent"` is the safe default: isolated OpenClaw-managed browser.
|
|
||||||
- `browserSession="user"` means the real local host browser. Use it only when existing logins/cookies matter and the user is present to click/approve any attach prompt.
|
|
||||||
- `browserSession="user"` is host-only; do not combine it with sandbox/node targets.
|
|
||||||
- All actions accept optional `profile` parameter for multi-instance support.
|
- All actions accept optional `profile` parameter for multi-instance support.
|
||||||
- `profile` overrides `browserSession` when both are supplied.
|
- Omit `profile` for the safe default: isolated OpenClaw-managed browser (`openclaw`).
|
||||||
|
- Use `profile="user"` for the real local host browser when existing logins/cookies matter and the user is present to click/approve any attach prompt.
|
||||||
|
- Use `profile="chrome-relay"` only for the Chrome extension / toolbar-button attach flow.
|
||||||
|
- `profile="user"` and `profile="chrome-relay"` are host-only; do not combine them with sandbox/node targets.
|
||||||
- When `profile` is omitted, uses `browser.defaultProfile` (defaults to `openclaw`).
|
- When `profile` is omitted, uses `browser.defaultProfile` (defaults to `openclaw`).
|
||||||
- Profile names: lowercase alphanumeric + hyphens only (max 64 chars).
|
- Profile names: lowercase alphanumeric + hyphens only (max 64 chars).
|
||||||
- Port range: 18800-18899 (~100 profiles max).
|
- Port range: 18800-18899 (~100 profiles max).
|
||||||
|
|
|
||||||
|
|
@ -160,9 +160,8 @@ describe("createOpenClawCodingTools", () => {
|
||||||
it("mentions Chrome extension relay in browser tool description", () => {
|
it("mentions Chrome extension relay in browser tool description", () => {
|
||||||
const browser = createBrowserTool();
|
const browser = createBrowserTool();
|
||||||
expect(browser.description).toMatch(/Chrome extension/i);
|
expect(browser.description).toMatch(/Chrome extension/i);
|
||||||
expect(browser.description).toMatch(/browserSession="agent"/i);
|
expect(browser.description).toMatch(/profile="user"/i);
|
||||||
expect(browser.description).toMatch(/browserSession="user"/i);
|
expect(browser.description).toMatch(/profile="chrome-relay"/i);
|
||||||
expect(browser.description).toMatch(/profile="chrome"/i);
|
|
||||||
});
|
});
|
||||||
it("keeps browser tool schema properties after normalization", () => {
|
it("keeps browser tool schema properties after normalization", () => {
|
||||||
const browser = defaultTools.find((tool) => tool.name === "browser");
|
const browser = defaultTools.find((tool) => tool.name === "browser");
|
||||||
|
|
@ -174,7 +173,6 @@ describe("createOpenClawCodingTools", () => {
|
||||||
};
|
};
|
||||||
expect(parameters.properties?.action).toBeDefined();
|
expect(parameters.properties?.action).toBeDefined();
|
||||||
expect(parameters.properties?.target).toBeDefined();
|
expect(parameters.properties?.target).toBeDefined();
|
||||||
expect(parameters.properties?.browserSession).toBeDefined();
|
|
||||||
expect(parameters.properties?.targetUrl).toBeDefined();
|
expect(parameters.properties?.targetUrl).toBeDefined();
|
||||||
expect(parameters.properties?.request).toBeDefined();
|
expect(parameters.properties?.request).toBeDefined();
|
||||||
expect(parameters.required ?? []).toContain("action");
|
expect(parameters.required ?? []).toContain("action");
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ function formatConsoleToolResult(result: {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isChromeStaleTargetError(profile: string | undefined, err: unknown): boolean {
|
function isChromeStaleTargetError(profile: string | undefined, err: unknown): boolean {
|
||||||
if (profile !== "chrome") {
|
if (profile !== "chrome-relay" && profile !== "chrome") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const msg = String(err);
|
const msg = String(err);
|
||||||
|
|
@ -340,7 +340,7 @@ export async function executeActAction(params: {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Chrome tab not found (stale targetId?). Run action=tabs profile="chrome" and use one of the returned targetIds.`,
|
`Chrome tab not found (stale targetId?). Run action=tabs profile="chrome-relay" and use one of the returned targetIds.`,
|
||||||
{ cause: err },
|
{ cause: err },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ const BROWSER_TOOL_ACTIONS = [
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const BROWSER_TARGETS = ["sandbox", "host", "node"] as const;
|
const BROWSER_TARGETS = ["sandbox", "host", "node"] as const;
|
||||||
const BROWSER_SESSION_CHOICES = ["agent", "user"] as const;
|
|
||||||
|
|
||||||
const BROWSER_SNAPSHOT_FORMATS = ["aria", "ai"] as const;
|
const BROWSER_SNAPSHOT_FORMATS = ["aria", "ai"] as const;
|
||||||
const BROWSER_SNAPSHOT_MODES = ["efficient"] as const;
|
const BROWSER_SNAPSHOT_MODES = ["efficient"] as const;
|
||||||
|
|
@ -89,7 +88,6 @@ const BrowserActSchema = Type.Object({
|
||||||
export const BrowserToolSchema = Type.Object({
|
export const BrowserToolSchema = Type.Object({
|
||||||
action: stringEnum(BROWSER_TOOL_ACTIONS),
|
action: stringEnum(BROWSER_TOOL_ACTIONS),
|
||||||
target: optionalStringEnum(BROWSER_TARGETS),
|
target: optionalStringEnum(BROWSER_TARGETS),
|
||||||
browserSession: optionalStringEnum(BROWSER_SESSION_CHOICES),
|
|
||||||
node: Type.Optional(Type.String()),
|
node: Type.Optional(Type.String()),
|
||||||
profile: Type.Optional(Type.String()),
|
profile: Type.Optional(Type.String()),
|
||||||
targetUrl: Type.Optional(Type.String()),
|
targetUrl: Type.Optional(Type.String()),
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,6 @@ async function runSnapshotToolCall(params: {
|
||||||
refs?: "aria" | "dom";
|
refs?: "aria" | "dom";
|
||||||
maxChars?: number;
|
maxChars?: number;
|
||||||
profile?: string;
|
profile?: string;
|
||||||
browserSession?: "agent" | "user";
|
|
||||||
}) {
|
}) {
|
||||||
const tool = createBrowserTool();
|
const tool = createBrowserTool();
|
||||||
await tool.execute?.("call-1", { action: "snapshot", ...params });
|
await tool.execute?.("call-1", { action: "snapshot", ...params });
|
||||||
|
|
@ -288,58 +287,56 @@ describe("browser tool snapshot maxChars", () => {
|
||||||
expect(opts?.mode).toBeUndefined();
|
expect(opts?.mode).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("defaults to host when using profile=chrome (even in sandboxed sessions)", async () => {
|
it("defaults to host when using profile=chrome-relay (even in sandboxed sessions)", async () => {
|
||||||
const tool = createBrowserTool({ sandboxBridgeUrl: "http://127.0.0.1:9999" });
|
|
||||||
await tool.execute?.("call-1", { action: "snapshot", profile: "chrome", snapshotFormat: "ai" });
|
|
||||||
|
|
||||||
expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith(
|
|
||||||
undefined,
|
|
||||||
expect.objectContaining({
|
|
||||||
profile: "chrome",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('uses the isolated openclaw profile for browserSession="agent"', async () => {
|
|
||||||
await runSnapshotToolCall({ browserSession: "agent", snapshotFormat: "ai" });
|
|
||||||
|
|
||||||
expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith(
|
|
||||||
undefined,
|
|
||||||
expect.objectContaining({
|
|
||||||
profile: "openclaw",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('uses the host user browser for browserSession="user"', async () => {
|
|
||||||
setResolvedBrowserProfiles({
|
setResolvedBrowserProfiles({
|
||||||
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
"chrome-relay": {
|
||||||
chrome: { driver: "extension", cdpUrl: "http://127.0.0.1:18792", color: "#0066CC" },
|
driver: "extension",
|
||||||
|
cdpUrl: "http://127.0.0.1:18792",
|
||||||
|
color: "#0066CC",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const tool = createBrowserTool({ sandboxBridgeUrl: "http://127.0.0.1:9999" });
|
const tool = createBrowserTool({ sandboxBridgeUrl: "http://127.0.0.1:9999" });
|
||||||
await tool.execute?.("call-1", {
|
await tool.execute?.("call-1", {
|
||||||
action: "snapshot",
|
action: "snapshot",
|
||||||
browserSession: "user",
|
profile: "chrome-relay",
|
||||||
snapshotFormat: "ai",
|
snapshotFormat: "ai",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith(
|
expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith(
|
||||||
undefined,
|
undefined,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
profile: "chrome",
|
profile: "chrome-relay",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses a sole existing-session profile for browserSession="user"', async () => {
|
it("defaults to host when using profile=user (even in sandboxed sessions)", async () => {
|
||||||
|
setResolvedBrowserProfiles({
|
||||||
|
user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
|
||||||
|
});
|
||||||
|
const tool = createBrowserTool({ sandboxBridgeUrl: "http://127.0.0.1:9999" });
|
||||||
|
await tool.execute?.("call-1", {
|
||||||
|
action: "snapshot",
|
||||||
|
profile: "user",
|
||||||
|
snapshotFormat: "ai",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith(
|
||||||
|
undefined,
|
||||||
|
expect.objectContaining({
|
||||||
|
profile: "user",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defaults to host for custom existing-session profiles too", async () => {
|
||||||
setResolvedBrowserProfiles({
|
setResolvedBrowserProfiles({
|
||||||
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
|
||||||
"chrome-live": { driver: "existing-session", attachOnly: true, color: "#00AA00" },
|
"chrome-live": { driver: "existing-session", attachOnly: true, color: "#00AA00" },
|
||||||
});
|
});
|
||||||
const tool = createBrowserTool({ sandboxBridgeUrl: "http://127.0.0.1:9999" });
|
const tool = createBrowserTool({ sandboxBridgeUrl: "http://127.0.0.1:9999" });
|
||||||
await tool.execute?.("call-1", {
|
await tool.execute?.("call-1", {
|
||||||
action: "snapshot",
|
action: "snapshot",
|
||||||
browserSession: "user",
|
profile: "chrome-live",
|
||||||
snapshotFormat: "ai",
|
snapshotFormat: "ai",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -351,47 +348,30 @@ describe("browser tool snapshot maxChars", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails when browserSession="user" is ambiguous', async () => {
|
it('rejects profile="user" with target="sandbox"', async () => {
|
||||||
setResolvedBrowserProfiles({
|
setResolvedBrowserProfiles({
|
||||||
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
|
||||||
personal: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
|
|
||||||
work: { driver: "existing-session", attachOnly: true, color: "#0066CC" },
|
|
||||||
});
|
|
||||||
const tool = createBrowserTool();
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
tool.execute?.("call-1", {
|
|
||||||
action: "snapshot",
|
|
||||||
browserSession: "user",
|
|
||||||
snapshotFormat: "ai",
|
|
||||||
}),
|
|
||||||
).rejects.toThrow(/Multiple user-browser profiles are configured/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('rejects browserSession="user" with target="sandbox"', async () => {
|
|
||||||
setResolvedBrowserProfiles({
|
|
||||||
chrome: { driver: "extension", cdpUrl: "http://127.0.0.1:18792", color: "#0066CC" },
|
|
||||||
});
|
});
|
||||||
const tool = createBrowserTool({ sandboxBridgeUrl: "http://127.0.0.1:9999" });
|
const tool = createBrowserTool({ sandboxBridgeUrl: "http://127.0.0.1:9999" });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
tool.execute?.("call-1", {
|
tool.execute?.("call-1", {
|
||||||
action: "snapshot",
|
action: "snapshot",
|
||||||
browserSession: "user",
|
profile: "user",
|
||||||
target: "sandbox",
|
target: "sandbox",
|
||||||
snapshotFormat: "ai",
|
snapshotFormat: "ai",
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(/cannot use the sandbox browser/);
|
).rejects.toThrow(/profile="user" cannot use the sandbox browser/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("lets the server choose snapshot format when the user does not request one", async () => {
|
it("lets the server choose snapshot format when the user does not request one", async () => {
|
||||||
const tool = createBrowserTool();
|
const tool = createBrowserTool();
|
||||||
await tool.execute?.("call-1", { action: "snapshot", profile: "chrome" });
|
await tool.execute?.("call-1", { action: "snapshot", profile: "chrome-relay" });
|
||||||
|
|
||||||
expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith(
|
expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith(
|
||||||
undefined,
|
undefined,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
profile: "chrome",
|
profile: "chrome-relay",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const opts = browserClientMocks.browserSnapshot.mock.calls.at(-1)?.[1] as
|
const opts = browserClientMocks.browserSnapshot.mock.calls.at(-1)?.[1] as
|
||||||
|
|
@ -458,14 +438,21 @@ describe("browser tool snapshot maxChars", () => {
|
||||||
expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled();
|
expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps chrome profile on host when node proxy is available", async () => {
|
it("keeps chrome-relay profile on host when node proxy is available", async () => {
|
||||||
mockSingleBrowserProxyNode();
|
mockSingleBrowserProxyNode();
|
||||||
|
setResolvedBrowserProfiles({
|
||||||
|
"chrome-relay": {
|
||||||
|
driver: "extension",
|
||||||
|
cdpUrl: "http://127.0.0.1:18792",
|
||||||
|
color: "#0066CC",
|
||||||
|
},
|
||||||
|
});
|
||||||
const tool = createBrowserTool();
|
const tool = createBrowserTool();
|
||||||
await tool.execute?.("call-1", { action: "status", profile: "chrome" });
|
await tool.execute?.("call-1", { action: "status", profile: "chrome-relay" });
|
||||||
|
|
||||||
expect(browserClientMocks.browserStatus).toHaveBeenCalledWith(
|
expect(browserClientMocks.browserStatus).toHaveBeenCalledWith(
|
||||||
undefined,
|
undefined,
|
||||||
expect.objectContaining({ profile: "chrome" }),
|
expect.objectContaining({ profile: "chrome-relay" }),
|
||||||
);
|
);
|
||||||
expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled();
|
expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
@ -758,7 +745,7 @@ describe("browser tool external content wrapping", () => {
|
||||||
describe("browser tool act stale target recovery", () => {
|
describe("browser tool act stale target recovery", () => {
|
||||||
registerBrowserToolAfterEachReset();
|
registerBrowserToolAfterEachReset();
|
||||||
|
|
||||||
it("retries safe chrome act once without targetId when exactly one tab remains", async () => {
|
it("retries safe chrome-relay act once without targetId when exactly one tab remains", async () => {
|
||||||
browserActionsMocks.browserAct
|
browserActionsMocks.browserAct
|
||||||
.mockRejectedValueOnce(new Error("404: tab not found"))
|
.mockRejectedValueOnce(new Error("404: tab not found"))
|
||||||
.mockResolvedValueOnce({ ok: true });
|
.mockResolvedValueOnce({ ok: true });
|
||||||
|
|
@ -767,7 +754,7 @@ describe("browser tool act stale target recovery", () => {
|
||||||
const tool = createBrowserTool();
|
const tool = createBrowserTool();
|
||||||
const result = await tool.execute?.("call-1", {
|
const result = await tool.execute?.("call-1", {
|
||||||
action: "act",
|
action: "act",
|
||||||
profile: "chrome",
|
profile: "chrome-relay",
|
||||||
request: {
|
request: {
|
||||||
kind: "hover",
|
kind: "hover",
|
||||||
targetId: "stale-tab",
|
targetId: "stale-tab",
|
||||||
|
|
@ -780,18 +767,18 @@ describe("browser tool act stale target recovery", () => {
|
||||||
1,
|
1,
|
||||||
undefined,
|
undefined,
|
||||||
expect.objectContaining({ targetId: "stale-tab", kind: "hover", ref: "btn-1" }),
|
expect.objectContaining({ targetId: "stale-tab", kind: "hover", ref: "btn-1" }),
|
||||||
expect.objectContaining({ profile: "chrome" }),
|
expect.objectContaining({ profile: "chrome-relay" }),
|
||||||
);
|
);
|
||||||
expect(browserActionsMocks.browserAct).toHaveBeenNthCalledWith(
|
expect(browserActionsMocks.browserAct).toHaveBeenNthCalledWith(
|
||||||
2,
|
2,
|
||||||
undefined,
|
undefined,
|
||||||
expect.not.objectContaining({ targetId: expect.anything() }),
|
expect.not.objectContaining({ targetId: expect.anything() }),
|
||||||
expect.objectContaining({ profile: "chrome" }),
|
expect.objectContaining({ profile: "chrome-relay" }),
|
||||||
);
|
);
|
||||||
expect(result?.details).toMatchObject({ ok: true });
|
expect(result?.details).toMatchObject({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not retry mutating chrome act requests without targetId", async () => {
|
it("does not retry mutating chrome-relay act requests without targetId", async () => {
|
||||||
browserActionsMocks.browserAct.mockRejectedValueOnce(new Error("404: tab not found"));
|
browserActionsMocks.browserAct.mockRejectedValueOnce(new Error("404: tab not found"));
|
||||||
browserClientMocks.browserTabs.mockResolvedValueOnce([{ targetId: "only-tab" }]);
|
browserClientMocks.browserTabs.mockResolvedValueOnce([{ targetId: "only-tab" }]);
|
||||||
|
|
||||||
|
|
@ -799,14 +786,14 @@ describe("browser tool act stale target recovery", () => {
|
||||||
await expect(
|
await expect(
|
||||||
tool.execute?.("call-1", {
|
tool.execute?.("call-1", {
|
||||||
action: "act",
|
action: "act",
|
||||||
profile: "chrome",
|
profile: "chrome-relay",
|
||||||
request: {
|
request: {
|
||||||
kind: "click",
|
kind: "click",
|
||||||
targetId: "stale-tab",
|
targetId: "stale-tab",
|
||||||
ref: "btn-1",
|
ref: "btn-1",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(/Run action=tabs profile="chrome"/i);
|
).rejects.toThrow(/Run action=tabs profile="chrome-relay"/i);
|
||||||
|
|
||||||
expect(browserActionsMocks.browserAct).toHaveBeenCalledTimes(1);
|
expect(browserActionsMocks.browserAct).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import {
|
||||||
browserStop,
|
browserStop,
|
||||||
} from "../../browser/client.js";
|
} from "../../browser/client.js";
|
||||||
import { resolveBrowserConfig, resolveProfile } from "../../browser/config.js";
|
import { resolveBrowserConfig, resolveProfile } from "../../browser/config.js";
|
||||||
import { DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME } from "../../browser/constants.js";
|
|
||||||
import { DEFAULT_UPLOAD_DIR, resolveExistingPathsWithinRoot } from "../../browser/paths.js";
|
import { DEFAULT_UPLOAD_DIR, resolveExistingPathsWithinRoot } from "../../browser/paths.js";
|
||||||
import { getBrowserProfileCapabilities } from "../../browser/profile-capabilities.js";
|
import { getBrowserProfileCapabilities } from "../../browser/profile-capabilities.js";
|
||||||
import { applyBrowserProxyPaths, persistBrowserProxyFiles } from "../../browser/proxy-files.js";
|
import { applyBrowserProxyPaths, persistBrowserProxyFiles } from "../../browser/proxy-files.js";
|
||||||
|
|
@ -280,58 +279,22 @@ function resolveBrowserBaseUrl(params: {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function listUserBrowserProfiles() {
|
function shouldPreferHostForProfile(profileName: string | undefined) {
|
||||||
|
if (!profileName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
||||||
return Object.keys(resolved.profiles ?? {})
|
const profile = resolveProfile(resolved, profileName);
|
||||||
.map((name) => resolveProfile(resolved, name))
|
if (!profile) {
|
||||||
.filter((profile): profile is NonNullable<typeof profile> => Boolean(profile))
|
return false;
|
||||||
.filter((profile) => {
|
}
|
||||||
const capabilities = getBrowserProfileCapabilities(profile);
|
const capabilities = getBrowserProfileCapabilities(profile);
|
||||||
return capabilities.requiresRelay || capabilities.usesChromeMcp;
|
return capabilities.requiresRelay || capabilities.usesChromeMcp;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveBrowserToolProfile(params: {
|
function isHostOnlyProfileName(profileName: string | undefined) {
|
||||||
profile?: string;
|
return profileName === "user" || profileName === "chrome-relay";
|
||||||
browserSession?: "agent" | "user";
|
|
||||||
}): string | undefined {
|
|
||||||
if (params.profile) {
|
|
||||||
return params.profile;
|
|
||||||
}
|
|
||||||
if (!params.browserSession) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (params.browserSession === "agent") {
|
|
||||||
return DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userProfiles = listUserBrowserProfiles();
|
|
||||||
const defaultUserProfile = userProfiles.find(
|
|
||||||
(profile) => profile.name !== DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME,
|
|
||||||
);
|
|
||||||
if (defaultUserProfile?.name === "chrome") {
|
|
||||||
return defaultUserProfile.name;
|
|
||||||
}
|
|
||||||
const chromeRelay = userProfiles.find((profile) => profile.name === "chrome");
|
|
||||||
if (chromeRelay) {
|
|
||||||
return chromeRelay.name;
|
|
||||||
}
|
|
||||||
if (userProfiles.length === 1) {
|
|
||||||
return userProfiles[0]?.name;
|
|
||||||
}
|
|
||||||
const chromeLive = userProfiles.find((profile) => profile.name === "chrome-live");
|
|
||||||
if (chromeLive) {
|
|
||||||
return chromeLive.name;
|
|
||||||
}
|
|
||||||
if (userProfiles.length === 0) {
|
|
||||||
throw new Error(
|
|
||||||
'No user-browser profile is configured. Use profile="chrome" for the extension relay or create an existing-session profile first.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw new Error(
|
|
||||||
`Multiple user-browser profiles are configured (${userProfiles.map((profile) => profile.name).join(", ")}). Pass profile="<name>".`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createBrowserTool(opts?: {
|
export function createBrowserTool(opts?: {
|
||||||
|
|
@ -347,12 +310,12 @@ export function createBrowserTool(opts?: {
|
||||||
name: "browser",
|
name: "browser",
|
||||||
description: [
|
description: [
|
||||||
"Control the browser via OpenClaw's browser control server (status/start/stop/profiles/tabs/open/snapshot/screenshot/actions).",
|
"Control the browser via OpenClaw's browser control server (status/start/stop/profiles/tabs/open/snapshot/screenshot/actions).",
|
||||||
'Browser choice: use browserSession="agent" by default for the isolated OpenClaw browser. Use browserSession="user" only when logged-in browser state matters and the user is present to click/approve browser attach prompts.',
|
"Browser choice: omit profile by default for the isolated OpenClaw-managed browser (`openclaw`).",
|
||||||
'browserSession="user" means the real local user browser on the host, not sandbox/node browsers. If user presence is unclear, ask first.',
|
'For the logged-in user browser on the local host, prefer profile="user". Use it only when existing logins/cookies matter and the user is present to click/approve any browser attach prompt.',
|
||||||
'profile remains the explicit override. Use profile="chrome" for Chrome extension relay takeover (existing Chrome tabs). Use profile="openclaw" for the isolated OpenClaw-managed browser.',
|
'Use profile="chrome-relay" only for the Chrome extension / Browser Relay / toolbar-button attach-tab flow, or when the user explicitly asks for the extension relay.',
|
||||||
'If the user mentions the Chrome extension / Browser Relay / toolbar button / “attach tab”, ALWAYS use browserSession="user" and prefer profile="chrome" (do not ask which profile unless ambiguous).',
|
'If the user mentions the Chrome extension / Browser Relay / toolbar button / “attach tab”, ALWAYS prefer profile="chrome-relay". Otherwise prefer profile="user" over the extension relay for user-browser work.',
|
||||||
'When a node-hosted browser proxy is available, the tool may auto-route to it. Pin a node with node=<id|name> or target="node".',
|
'When a node-hosted browser proxy is available, the tool may auto-route to it. Pin a node with node=<id|name> or target="node".',
|
||||||
"User-browser flows need user interaction: Chrome extension relay needs the user to click the OpenClaw Browser Relay toolbar icon on the tab (badge ON); existing-session may require approving a browser attach prompt.",
|
'User-browser flows need user interaction: profile="user" may require approving a browser attach prompt; profile="chrome-relay" needs the user to click the OpenClaw Browser Relay toolbar icon on the tab (badge ON). If user presence is unclear, ask first.',
|
||||||
"When using refs from snapshot (e.g. e12), keep the same tab: prefer passing targetId from the snapshot response into subsequent actions (act/click/type/etc).",
|
"When using refs from snapshot (e.g. e12), keep the same tab: prefer passing targetId from the snapshot response into subsequent actions (act/click/type/etc).",
|
||||||
'For stable, self-resolving refs across calls, use snapshot with refs="aria" (Playwright aria-ref ids). Default refs="role" are role+name-based.',
|
'For stable, self-resolving refs across calls, use snapshot with refs="aria" (Playwright aria-ref ids). Default refs="role" are role+name-based.',
|
||||||
"Use snapshot+act for UI automation. Avoid act:wait by default; use only in exceptional cases when no reliable UI state exists.",
|
"Use snapshot+act for UI automation. Avoid act:wait by default; use only in exceptional cases when no reliable UI state exists.",
|
||||||
|
|
@ -363,36 +326,25 @@ export function createBrowserTool(opts?: {
|
||||||
execute: async (_toolCallId, args) => {
|
execute: async (_toolCallId, args) => {
|
||||||
const params = args as Record<string, unknown>;
|
const params = args as Record<string, unknown>;
|
||||||
const action = readStringParam(params, "action", { required: true });
|
const action = readStringParam(params, "action", { required: true });
|
||||||
const browserSession = readStringParam(params, "browserSession") as
|
const profile = readStringParam(params, "profile");
|
||||||
| "agent"
|
|
||||||
| "user"
|
|
||||||
| undefined;
|
|
||||||
const profile = resolveBrowserToolProfile({
|
|
||||||
profile: readStringParam(params, "profile"),
|
|
||||||
browserSession,
|
|
||||||
});
|
|
||||||
const requestedNode = readStringParam(params, "node");
|
const requestedNode = readStringParam(params, "node");
|
||||||
let target = readStringParam(params, "target") as "sandbox" | "host" | "node" | undefined;
|
let target = readStringParam(params, "target") as "sandbox" | "host" | "node" | undefined;
|
||||||
|
|
||||||
if (requestedNode && target && target !== "node") {
|
if (requestedNode && target && target !== "node") {
|
||||||
throw new Error('node is only supported with target="node".');
|
throw new Error('node is only supported with target="node".');
|
||||||
}
|
}
|
||||||
if (browserSession === "user") {
|
if (isHostOnlyProfileName(profile)) {
|
||||||
if (requestedNode || target === "node") {
|
if (requestedNode || target === "node") {
|
||||||
throw new Error('browserSession="user" only supports the local host browser.');
|
throw new Error(`profile="${profile}" only supports the local host browser.`);
|
||||||
}
|
}
|
||||||
if (target === "sandbox") {
|
if (target === "sandbox") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'browserSession="user" cannot use the sandbox browser; use target="host" or omit target.',
|
`profile="${profile}" cannot use the sandbox browser; use target="host" or omit target.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!target && !requestedNode && browserSession === "user") {
|
if (!target && !requestedNode && shouldPreferHostForProfile(profile)) {
|
||||||
target = "host";
|
// Local host user-browser profiles should not silently bind to sandbox/node browsers.
|
||||||
}
|
|
||||||
|
|
||||||
if (!target && !requestedNode && profile === "chrome") {
|
|
||||||
// Chrome extension relay takeover is a host Chrome feature; prefer host unless explicitly targeting a node.
|
|
||||||
target = "host";
|
target = "host";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,14 @@ describe("browser config", () => {
|
||||||
expect(openclaw?.driver).toBe("openclaw");
|
expect(openclaw?.driver).toBe("openclaw");
|
||||||
expect(openclaw?.cdpPort).toBe(18800);
|
expect(openclaw?.cdpPort).toBe(18800);
|
||||||
expect(openclaw?.cdpUrl).toBe("http://127.0.0.1:18800");
|
expect(openclaw?.cdpUrl).toBe("http://127.0.0.1:18800");
|
||||||
const chrome = resolveProfile(resolved, "chrome");
|
const user = resolveProfile(resolved, "user");
|
||||||
expect(chrome?.driver).toBe("extension");
|
expect(user?.driver).toBe("existing-session");
|
||||||
expect(chrome?.cdpPort).toBe(18792);
|
expect(user?.cdpPort).toBe(0);
|
||||||
expect(chrome?.cdpUrl).toBe("http://127.0.0.1:18792");
|
expect(user?.cdpUrl).toBe("");
|
||||||
|
const chromeRelay = resolveProfile(resolved, "chrome-relay");
|
||||||
|
expect(chromeRelay?.driver).toBe("extension");
|
||||||
|
expect(chromeRelay?.cdpPort).toBe(18792);
|
||||||
|
expect(chromeRelay?.cdpUrl).toBe("http://127.0.0.1:18792");
|
||||||
expect(resolved.remoteCdpTimeoutMs).toBe(1500);
|
expect(resolved.remoteCdpTimeoutMs).toBe(1500);
|
||||||
expect(resolved.remoteCdpHandshakeTimeoutMs).toBe(3000);
|
expect(resolved.remoteCdpHandshakeTimeoutMs).toBe(3000);
|
||||||
});
|
});
|
||||||
|
|
@ -34,10 +38,10 @@ describe("browser config", () => {
|
||||||
withEnv({ OPENCLAW_GATEWAY_PORT: "19001" }, () => {
|
withEnv({ OPENCLAW_GATEWAY_PORT: "19001" }, () => {
|
||||||
const resolved = resolveBrowserConfig(undefined);
|
const resolved = resolveBrowserConfig(undefined);
|
||||||
expect(resolved.controlPort).toBe(19003);
|
expect(resolved.controlPort).toBe(19003);
|
||||||
const chrome = resolveProfile(resolved, "chrome");
|
const chromeRelay = resolveProfile(resolved, "chrome-relay");
|
||||||
expect(chrome?.driver).toBe("extension");
|
expect(chromeRelay?.driver).toBe("extension");
|
||||||
expect(chrome?.cdpPort).toBe(19004);
|
expect(chromeRelay?.cdpPort).toBe(19004);
|
||||||
expect(chrome?.cdpUrl).toBe("http://127.0.0.1:19004");
|
expect(chromeRelay?.cdpUrl).toBe("http://127.0.0.1:19004");
|
||||||
|
|
||||||
const openclaw = resolveProfile(resolved, "openclaw");
|
const openclaw = resolveProfile(resolved, "openclaw");
|
||||||
expect(openclaw?.cdpPort).toBe(19012);
|
expect(openclaw?.cdpPort).toBe(19012);
|
||||||
|
|
@ -49,10 +53,10 @@ describe("browser config", () => {
|
||||||
withEnv({ OPENCLAW_GATEWAY_PORT: undefined }, () => {
|
withEnv({ OPENCLAW_GATEWAY_PORT: undefined }, () => {
|
||||||
const resolved = resolveBrowserConfig(undefined, { gateway: { port: 19011 } });
|
const resolved = resolveBrowserConfig(undefined, { gateway: { port: 19011 } });
|
||||||
expect(resolved.controlPort).toBe(19013);
|
expect(resolved.controlPort).toBe(19013);
|
||||||
const chrome = resolveProfile(resolved, "chrome");
|
const chromeRelay = resolveProfile(resolved, "chrome-relay");
|
||||||
expect(chrome?.driver).toBe("extension");
|
expect(chromeRelay?.driver).toBe("extension");
|
||||||
expect(chrome?.cdpPort).toBe(19014);
|
expect(chromeRelay?.cdpPort).toBe(19014);
|
||||||
expect(chrome?.cdpUrl).toBe("http://127.0.0.1:19014");
|
expect(chromeRelay?.cdpUrl).toBe("http://127.0.0.1:19014");
|
||||||
|
|
||||||
const openclaw = resolveProfile(resolved, "openclaw");
|
const openclaw = resolveProfile(resolved, "openclaw");
|
||||||
expect(openclaw?.cdpPort).toBe(19022);
|
expect(openclaw?.cdpPort).toBe(19022);
|
||||||
|
|
@ -205,13 +209,13 @@ describe("browser config", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not add the built-in chrome extension profile if the derived relay port is already used", () => {
|
it("does not add the built-in chrome-relay profile if the derived relay port is already used", () => {
|
||||||
const resolved = resolveBrowserConfig({
|
const resolved = resolveBrowserConfig({
|
||||||
profiles: {
|
profiles: {
|
||||||
openclaw: { cdpPort: 18792, color: "#FF4500" },
|
openclaw: { cdpPort: 18792, color: "#FF4500" },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(resolveProfile(resolved, "chrome")).toBe(null);
|
expect(resolveProfile(resolved, "chrome-relay")).toBe(null);
|
||||||
expect(resolved.defaultProfile).toBe("openclaw");
|
expect(resolved.defaultProfile).toBe("openclaw");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -313,7 +317,7 @@ describe("browser config", () => {
|
||||||
const managed = resolveProfile(resolved, "openclaw")!;
|
const managed = resolveProfile(resolved, "openclaw")!;
|
||||||
expect(getBrowserProfileCapabilities(managed).usesChromeMcp).toBe(false);
|
expect(getBrowserProfileCapabilities(managed).usesChromeMcp).toBe(false);
|
||||||
|
|
||||||
const extension = resolveProfile(resolved, "chrome")!;
|
const extension = resolveProfile(resolved, "chrome-relay")!;
|
||||||
expect(getBrowserProfileCapabilities(extension).usesChromeMcp).toBe(false);
|
expect(getBrowserProfileCapabilities(extension).usesChromeMcp).toBe(false);
|
||||||
|
|
||||||
const work = resolveProfile(resolved, "work")!;
|
const work = resolveProfile(resolved, "work")!;
|
||||||
|
|
@ -354,17 +358,17 @@ describe("browser config", () => {
|
||||||
it("explicit defaultProfile config overrides defaults in headless mode", () => {
|
it("explicit defaultProfile config overrides defaults in headless mode", () => {
|
||||||
const resolved = resolveBrowserConfig({
|
const resolved = resolveBrowserConfig({
|
||||||
headless: true,
|
headless: true,
|
||||||
defaultProfile: "chrome",
|
defaultProfile: "chrome-relay",
|
||||||
});
|
});
|
||||||
expect(resolved.defaultProfile).toBe("chrome");
|
expect(resolved.defaultProfile).toBe("chrome-relay");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("explicit defaultProfile config overrides defaults in noSandbox mode", () => {
|
it("explicit defaultProfile config overrides defaults in noSandbox mode", () => {
|
||||||
const resolved = resolveBrowserConfig({
|
const resolved = resolveBrowserConfig({
|
||||||
noSandbox: true,
|
noSandbox: true,
|
||||||
defaultProfile: "chrome",
|
defaultProfile: "chrome-relay",
|
||||||
});
|
});
|
||||||
expect(resolved.defaultProfile).toBe("chrome");
|
expect(resolved.defaultProfile).toBe("chrome-relay");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows custom profile as default even in headless mode", () => {
|
it("allows custom profile as default even in headless mode", () => {
|
||||||
|
|
|
||||||
|
|
@ -180,17 +180,35 @@ function ensureDefaultProfile(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure a built-in "chrome" profile exists for the Chrome extension relay.
|
* Ensure a built-in "user" profile exists for Chrome's existing-session attach flow.
|
||||||
|
*/
|
||||||
|
function ensureDefaultUserBrowserProfile(
|
||||||
|
profiles: Record<string, BrowserProfileConfig>,
|
||||||
|
): Record<string, BrowserProfileConfig> {
|
||||||
|
const result = { ...profiles };
|
||||||
|
if (result.user) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.user = {
|
||||||
|
driver: "existing-session",
|
||||||
|
attachOnly: true,
|
||||||
|
color: "#00AA00",
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure a built-in "chrome-relay" profile exists for the Chrome extension relay.
|
||||||
*
|
*
|
||||||
* Note: this is an OpenClaw browser profile (routing config), not a Chrome user profile.
|
* Note: this is an OpenClaw browser profile (routing config), not a Chrome user profile.
|
||||||
* It points at the local relay CDP endpoint (controlPort + 1).
|
* It points at the local relay CDP endpoint (controlPort + 1).
|
||||||
*/
|
*/
|
||||||
function ensureDefaultChromeExtensionProfile(
|
function ensureDefaultChromeRelayProfile(
|
||||||
profiles: Record<string, BrowserProfileConfig>,
|
profiles: Record<string, BrowserProfileConfig>,
|
||||||
controlPort: number,
|
controlPort: number,
|
||||||
): Record<string, BrowserProfileConfig> {
|
): Record<string, BrowserProfileConfig> {
|
||||||
const result = { ...profiles };
|
const result = { ...profiles };
|
||||||
if (result.chrome) {
|
if (result["chrome-relay"]) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
const relayPort = controlPort + 1;
|
const relayPort = controlPort + 1;
|
||||||
|
|
@ -202,7 +220,7 @@ function ensureDefaultChromeExtensionProfile(
|
||||||
if (getUsedPorts(result).has(relayPort)) {
|
if (getUsedPorts(result).has(relayPort)) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result.chrome = {
|
result["chrome-relay"] = {
|
||||||
driver: "extension",
|
driver: "extension",
|
||||||
cdpUrl: `http://127.0.0.1:${relayPort}`,
|
cdpUrl: `http://127.0.0.1:${relayPort}`,
|
||||||
color: "#00AA00",
|
color: "#00AA00",
|
||||||
|
|
@ -268,13 +286,15 @@ export function resolveBrowserConfig(
|
||||||
const legacyCdpPort = rawCdpUrl ? cdpInfo.port : undefined;
|
const legacyCdpPort = rawCdpUrl ? cdpInfo.port : undefined;
|
||||||
const isWsUrl = cdpInfo.parsed.protocol === "ws:" || cdpInfo.parsed.protocol === "wss:";
|
const isWsUrl = cdpInfo.parsed.protocol === "ws:" || cdpInfo.parsed.protocol === "wss:";
|
||||||
const legacyCdpUrl = rawCdpUrl && isWsUrl ? cdpInfo.normalized : undefined;
|
const legacyCdpUrl = rawCdpUrl && isWsUrl ? cdpInfo.normalized : undefined;
|
||||||
const profiles = ensureDefaultChromeExtensionProfile(
|
const profiles = ensureDefaultChromeRelayProfile(
|
||||||
ensureDefaultProfile(
|
ensureDefaultUserBrowserProfile(
|
||||||
cfg?.profiles,
|
ensureDefaultProfile(
|
||||||
defaultColor,
|
cfg?.profiles,
|
||||||
legacyCdpPort,
|
defaultColor,
|
||||||
cdpPortRangeStart,
|
legacyCdpPort,
|
||||||
legacyCdpUrl,
|
cdpPortRangeStart,
|
||||||
|
legacyCdpUrl,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
controlPort,
|
controlPort,
|
||||||
);
|
);
|
||||||
|
|
@ -286,7 +306,7 @@ export function resolveBrowserConfig(
|
||||||
? DEFAULT_BROWSER_DEFAULT_PROFILE_NAME
|
? DEFAULT_BROWSER_DEFAULT_PROFILE_NAME
|
||||||
: profiles[DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME]
|
: profiles[DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME]
|
||||||
? DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME
|
? DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME
|
||||||
: "chrome");
|
: "user");
|
||||||
|
|
||||||
const extraArgs = Array.isArray(cfg?.extraArgs)
|
const extraArgs = Array.isArray(cfg?.extraArgs)
|
||||||
? cfg.extraArgs.filter((a): a is string => typeof a === "string" && a.trim().length > 0)
|
? cfg.extraArgs.filter((a): a is string => typeof a === "string" && a.trim().length > 0)
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import { resolveBrowserConfig, resolveProfile } from "../config.js";
|
||||||
import { resolveSnapshotPlan } from "./agent.snapshot.plan.js";
|
import { resolveSnapshotPlan } from "./agent.snapshot.plan.js";
|
||||||
|
|
||||||
describe("resolveSnapshotPlan", () => {
|
describe("resolveSnapshotPlan", () => {
|
||||||
it("defaults chrome extension relay snapshots to aria when format is omitted", () => {
|
it("defaults chrome-relay snapshots to aria when format is omitted", () => {
|
||||||
const resolved = resolveBrowserConfig({});
|
const resolved = resolveBrowserConfig({});
|
||||||
const profile = resolveProfile(resolved, "chrome");
|
const profile = resolveProfile(resolved, "chrome-relay");
|
||||||
expect(profile).toBeTruthy();
|
expect(profile).toBeTruthy();
|
||||||
|
|
||||||
const plan = resolveSnapshotPlan({
|
const plan = resolveSnapshotPlan({
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ function makeBrowserState(): BrowserServerState {
|
||||||
headless: true,
|
headless: true,
|
||||||
noSandbox: false,
|
noSandbox: false,
|
||||||
attachOnly: false,
|
attachOnly: false,
|
||||||
defaultProfile: "chrome",
|
defaultProfile: "chrome-relay",
|
||||||
profiles: {
|
profiles: {
|
||||||
chrome: {
|
"chrome-relay": {
|
||||||
driver: "extension",
|
driver: "extension",
|
||||||
cdpUrl: "http://127.0.0.1:18792",
|
cdpUrl: "http://127.0.0.1:18792",
|
||||||
cdpPort: 18792,
|
cdpPort: 18792,
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ describe("ensureExtensionRelayForProfiles", () => {
|
||||||
|
|
||||||
it("starts relay only for extension profiles", async () => {
|
it("starts relay only for extension profiles", async () => {
|
||||||
resolveProfileMock.mockImplementation((_resolved: unknown, name: string) => {
|
resolveProfileMock.mockImplementation((_resolved: unknown, name: string) => {
|
||||||
if (name === "chrome") {
|
if (name === "chrome-relay") {
|
||||||
return { driver: "extension", cdpUrl: "http://127.0.0.1:18888" };
|
return { driver: "extension", cdpUrl: "http://127.0.0.1:18888" };
|
||||||
}
|
}
|
||||||
return { driver: "openclaw", cdpUrl: "http://127.0.0.1:18889" };
|
return { driver: "openclaw", cdpUrl: "http://127.0.0.1:18889" };
|
||||||
|
|
@ -53,7 +53,7 @@ describe("ensureExtensionRelayForProfiles", () => {
|
||||||
await ensureExtensionRelayForProfiles({
|
await ensureExtensionRelayForProfiles({
|
||||||
resolved: {
|
resolved: {
|
||||||
profiles: {
|
profiles: {
|
||||||
chrome: {},
|
"chrome-relay": {},
|
||||||
openclaw: {},
|
openclaw: {},
|
||||||
},
|
},
|
||||||
} as never,
|
} as never,
|
||||||
|
|
@ -72,12 +72,12 @@ describe("ensureExtensionRelayForProfiles", () => {
|
||||||
const onWarn = vi.fn();
|
const onWarn = vi.fn();
|
||||||
|
|
||||||
await ensureExtensionRelayForProfiles({
|
await ensureExtensionRelayForProfiles({
|
||||||
resolved: { profiles: { chrome: {} } } as never,
|
resolved: { profiles: { "chrome-relay": {} } } as never,
|
||||||
onWarn,
|
onWarn,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(onWarn).toHaveBeenCalledWith(
|
expect(onWarn).toHaveBeenCalledWith(
|
||||||
'Chrome extension relay init failed for profile "chrome": Error: boom',
|
'Chrome extension relay init failed for profile "chrome-relay": Error: boom',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -91,10 +91,10 @@ describe("stopKnownBrowserProfiles", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("stops all known profiles and ignores per-profile failures", async () => {
|
it("stops all known profiles and ignores per-profile failures", async () => {
|
||||||
listKnownProfileNamesMock.mockReturnValue(["openclaw", "chrome"]);
|
listKnownProfileNamesMock.mockReturnValue(["openclaw", "chrome-relay"]);
|
||||||
const stopMap: Record<string, ReturnType<typeof vi.fn>> = {
|
const stopMap: Record<string, ReturnType<typeof vi.fn>> = {
|
||||||
openclaw: vi.fn(async () => {}),
|
openclaw: vi.fn(async () => {}),
|
||||||
chrome: vi.fn(async () => {
|
"chrome-relay": vi.fn(async () => {
|
||||||
throw new Error("profile stop failed");
|
throw new Error("profile stop failed");
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
@ -112,7 +112,7 @@ describe("stopKnownBrowserProfiles", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stopMap.openclaw).toHaveBeenCalledTimes(1);
|
expect(stopMap.openclaw).toHaveBeenCalledTimes(1);
|
||||||
expect(stopMap.chrome).toHaveBeenCalledTimes(1);
|
expect(stopMap["chrome-relay"]).toHaveBeenCalledTimes(1);
|
||||||
expect(onWarn).not.toHaveBeenCalled();
|
expect(onWarn).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ const configMocks = vi.hoisted(() => ({
|
||||||
const browserConfigMocks = vi.hoisted(() => ({
|
const browserConfigMocks = vi.hoisted(() => ({
|
||||||
resolveBrowserConfig: vi.fn(() => ({
|
resolveBrowserConfig: vi.fn(() => ({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
defaultProfile: "chrome",
|
defaultProfile: "openclaw",
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ describe("runBrowserProxyCommand", () => {
|
||||||
});
|
});
|
||||||
browserConfigMocks.resolveBrowserConfig.mockReturnValue({
|
browserConfigMocks.resolveBrowserConfig.mockReturnValue({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
defaultProfile: "chrome",
|
defaultProfile: "openclaw",
|
||||||
});
|
});
|
||||||
controlServiceMocks.startBrowserControlServiceFromConfig.mockResolvedValue(true);
|
controlServiceMocks.startBrowserControlServiceFromConfig.mockResolvedValue(true);
|
||||||
});
|
});
|
||||||
|
|
@ -70,12 +70,12 @@ describe("runBrowserProxyCommand", () => {
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: "/snapshot",
|
path: "/snapshot",
|
||||||
profile: "chrome",
|
profile: "chrome-relay",
|
||||||
timeoutMs: 5,
|
timeoutMs: 5,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
/browser proxy timed out for GET \/snapshot after 5ms; ws-backed browser action; profile=chrome; status\(running=true, cdpHttp=true, cdpReady=false, cdpUrl=http:\/\/127\.0\.0\.1:18792\)/,
|
/browser proxy timed out for GET \/snapshot after 5ms; ws-backed browser action; profile=chrome-relay; status\(running=true, cdpHttp=true, cdpReady=false, cdpUrl=http:\/\/127\.0\.0\.1:18792\)/,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -100,12 +100,12 @@ describe("runBrowserProxyCommand", () => {
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: "/snapshot",
|
path: "/snapshot",
|
||||||
profile: "chrome-live",
|
profile: "user",
|
||||||
timeoutMs: 5,
|
timeoutMs: 5,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
/browser proxy timed out for GET \/snapshot after 5ms; ws-backed browser action; profile=chrome-live; status\(running=true, cdpHttp=true, cdpReady=false, transport=chrome-mcp\)/,
|
/browser proxy timed out for GET \/snapshot after 5ms; ws-backed browser action; profile=user; status\(running=true, cdpHttp=true, cdpReady=false, transport=chrome-mcp\)/,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -120,7 +120,7 @@ describe("runBrowserProxyCommand", () => {
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
path: "/act",
|
path: "/act",
|
||||||
profile: "chrome",
|
profile: "chrome-relay",
|
||||||
timeoutMs: 50,
|
timeoutMs: 50,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue