Compare commits
1 Commits
main
...
browser-ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9aebafa7a |
@ -371,11 +371,7 @@ openclaw browser create-profile \
|
|||||||
--color "#00AA00"
|
--color "#00AA00"
|
||||||
```
|
```
|
||||||
|
|
||||||
Then in Chrome:
|
Then keep Chrome (v146+) running. OpenClaw auto-discovers it — no setup needed.
|
||||||
|
|
||||||
1. Open `chrome://inspect/#remote-debugging`
|
|
||||||
2. Enable remote debugging
|
|
||||||
3. Keep Chrome running and approve the connection prompt when OpenClaw attaches
|
|
||||||
|
|
||||||
Live attach smoke test:
|
Live attach smoke test:
|
||||||
|
|
||||||
@ -396,17 +392,15 @@ What success looks like:
|
|||||||
|
|
||||||
What to check if attach does not work:
|
What to check if attach does not work:
|
||||||
|
|
||||||
- Chrome is version `144+`
|
- Chrome is version `146+` and running
|
||||||
- remote debugging is enabled at `chrome://inspect/#remote-debugging`
|
- no other tool is already attached to the same Chrome session
|
||||||
- Chrome showed and you accepted the attach consent prompt
|
|
||||||
|
|
||||||
Agent use:
|
Agent use:
|
||||||
|
|
||||||
- Use `browserSession="user"` when you need the user’s logged-in browser state.
|
- Use `browserSession="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 know the profile name, pass `profile="chrome-live"` (or your custom
|
||||||
existing-session profile).
|
existing-session profile).
|
||||||
- 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.
|
||||||
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`
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|||||||
@ -311,10 +311,10 @@ describe("browser tool snapshot maxChars", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses the host user browser for browserSession="user"', async () => {
|
it('resolves sole user profile for browserSession="user"', async () => {
|
||||||
setResolvedBrowserProfiles({
|
setResolvedBrowserProfiles({
|
||||||
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
||||||
chrome: { driver: "extension", cdpUrl: "http://127.0.0.1:18792", color: "#0066CC" },
|
chrome: { 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", {
|
||||||
@ -331,31 +331,11 @@ describe("browser tool snapshot maxChars", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses a sole existing-session profile for browserSession="user"', async () => {
|
it('lists available profiles when browserSession="user" is ambiguous', async () => {
|
||||||
setResolvedBrowserProfiles({
|
setResolvedBrowserProfiles({
|
||||||
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
||||||
"chrome-live": { driver: "existing-session", attachOnly: true, color: "#00AA00" },
|
chrome: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
|
||||||
});
|
relay: { driver: "extension", cdpUrl: "http://127.0.0.1:18792", color: "#0066CC" },
|
||||||
const tool = createBrowserTool({ sandboxBridgeUrl: "http://127.0.0.1:9999" });
|
|
||||||
await tool.execute?.("call-1", {
|
|
||||||
action: "snapshot",
|
|
||||||
browserSession: "user",
|
|
||||||
snapshotFormat: "ai",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith(
|
|
||||||
undefined,
|
|
||||||
expect.objectContaining({
|
|
||||||
profile: "chrome-live",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fails when browserSession="user" is ambiguous', async () => {
|
|
||||||
setResolvedBrowserProfiles({
|
|
||||||
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
|
||||||
personal: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
|
|
||||||
work: { driver: "existing-session", attachOnly: true, color: "#0066CC" },
|
|
||||||
});
|
});
|
||||||
const tool = createBrowserTool();
|
const tool = createBrowserTool();
|
||||||
|
|
||||||
@ -365,12 +345,12 @@ describe("browser tool snapshot maxChars", () => {
|
|||||||
browserSession: "user",
|
browserSession: "user",
|
||||||
snapshotFormat: "ai",
|
snapshotFormat: "ai",
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(/Multiple user-browser profiles are configured/);
|
).rejects.toThrow(/Multiple user-browser profiles available.*profile=".*"/s);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects browserSession="user" with target="sandbox"', async () => {
|
it('rejects browserSession="user" with target="sandbox"', async () => {
|
||||||
setResolvedBrowserProfiles({
|
setResolvedBrowserProfiles({
|
||||||
chrome: { driver: "extension", cdpUrl: "http://127.0.0.1:18792", color: "#0066CC" },
|
chrome: { 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" });
|
||||||
|
|
||||||
|
|||||||
@ -280,18 +280,13 @@ function resolveBrowserBaseUrl(params: {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function listUserBrowserProfiles() {
|
/**
|
||||||
const cfg = loadConfig();
|
* Resolve which browser profile to use for a given browserSession value.
|
||||||
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
*
|
||||||
return Object.keys(resolved.profiles ?? {})
|
* For browserSession="user": if exactly one user-capable profile exists, use
|
||||||
.map((name) => resolveProfile(resolved, name))
|
* it. Otherwise return undefined and let the caller list available profiles so
|
||||||
.filter((profile): profile is NonNullable<typeof profile> => Boolean(profile))
|
* the model (or user) can pick.
|
||||||
.filter((profile) => {
|
*/
|
||||||
const capabilities = getBrowserProfileCapabilities(profile);
|
|
||||||
return capabilities.requiresRelay || capabilities.usesChromeMcp;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveBrowserToolProfile(params: {
|
function resolveBrowserToolProfile(params: {
|
||||||
profile?: string;
|
profile?: string;
|
||||||
browserSession?: "agent" | "user";
|
browserSession?: "agent" | "user";
|
||||||
@ -306,31 +301,24 @@ function resolveBrowserToolProfile(params: {
|
|||||||
return DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME;
|
return DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userProfiles = listUserBrowserProfiles();
|
// Find all profiles that connect to the user's real browser.
|
||||||
const defaultUserProfile = userProfiles.find(
|
const cfg = loadConfig();
|
||||||
(profile) => profile.name !== DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME,
|
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
||||||
);
|
const userProfiles = Object.keys(resolved.profiles ?? {})
|
||||||
if (defaultUserProfile?.name === "chrome") {
|
.map((name) => resolveProfile(resolved, name))
|
||||||
return defaultUserProfile.name;
|
.filter((profile): profile is NonNullable<typeof profile> => Boolean(profile))
|
||||||
}
|
.filter((profile) => getBrowserProfileCapabilities(profile).isUserBrowser);
|
||||||
const chromeRelay = userProfiles.find((profile) => profile.name === "chrome");
|
|
||||||
if (chromeRelay) {
|
|
||||||
return chromeRelay.name;
|
|
||||||
}
|
|
||||||
if (userProfiles.length === 1) {
|
if (userProfiles.length === 1) {
|
||||||
return userProfiles[0]?.name;
|
return userProfiles[0].name;
|
||||||
}
|
|
||||||
const chromeLive = userProfiles.find((profile) => profile.name === "chrome-live");
|
|
||||||
if (chromeLive) {
|
|
||||||
return chromeLive.name;
|
|
||||||
}
|
}
|
||||||
if (userProfiles.length === 0) {
|
if (userProfiles.length === 0) {
|
||||||
throw new Error(
|
throw new Error("No user-browser profile found. Set up an existing-session profile first.");
|
||||||
'No user-browser profile is configured. Use profile="chrome" for the extension relay or create an existing-session profile first.',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
// Multiple — tell the model what's available so it can pick or ask the user.
|
||||||
|
const descriptions = userProfiles.map((p) => ` - profile="${p.name}" (${p.driver})`).join("\n");
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Multiple user-browser profiles are configured (${userProfiles.map((profile) => profile.name).join(", ")}). Pass profile="<name>".`,
|
`Multiple user-browser profiles available. Pick one with profile="<name>" or ask the user:\n${descriptions}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,12 +335,11 @@ 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.',
|
'browserSession="agent" (default): isolated OpenClaw-managed browser — fresh profile, no cookies/logins.',
|
||||||
'browserSession="user" means the real local user browser on the host, not sandbox/node browsers. If user presence is unclear, ask first.',
|
'browserSession="user": the user\'s real local browser with their logged-in sessions. Use when you need their cookies/auth. Chrome (v146+) must be running. If unsure whether the user is present, ask first.',
|
||||||
'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.',
|
'If browserSession="user" can\'t auto-resolve (multiple profiles), the error lists available profiles — pick one with profile="<name>" or ask the user.',
|
||||||
'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).',
|
'profile is an explicit override and always takes precedence over browserSession. Use action="profiles" to discover available profiles.',
|
||||||
'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.",
|
|
||||||
"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.",
|
||||||
|
|||||||
@ -193,7 +193,7 @@ async function createRealSession(profileName: string): Promise<ChromeMcpSession>
|
|||||||
await client.close().catch(() => {});
|
await client.close().catch(() => {});
|
||||||
throw new BrowserProfileUnavailableError(
|
throw new BrowserProfileUnavailableError(
|
||||||
`Chrome MCP existing-session attach failed for profile "${profileName}". ` +
|
`Chrome MCP existing-session attach failed for profile "${profileName}". ` +
|
||||||
`Make sure Chrome is running, enable chrome://inspect/#remote-debugging, and approve the connection. ` +
|
`Make sure Chrome (v146+) is running. ` +
|
||||||
`Details: ${String(err)}`,
|
`Details: ${String(err)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,9 +23,10 @@ describe("browser config", () => {
|
|||||||
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 chrome = resolveProfile(resolved, "chrome");
|
||||||
expect(chrome?.driver).toBe("extension");
|
expect(chrome?.driver).toBe("existing-session");
|
||||||
expect(chrome?.cdpPort).toBe(18792);
|
expect(chrome?.cdpPort).toBe(0);
|
||||||
expect(chrome?.cdpUrl).toBe("http://127.0.0.1:18792");
|
expect(chrome?.cdpUrl).toBe("");
|
||||||
|
expect(chrome?.attachOnly).toBe(true);
|
||||||
expect(resolved.remoteCdpTimeoutMs).toBe(1500);
|
expect(resolved.remoteCdpTimeoutMs).toBe(1500);
|
||||||
expect(resolved.remoteCdpHandshakeTimeoutMs).toBe(3000);
|
expect(resolved.remoteCdpHandshakeTimeoutMs).toBe(3000);
|
||||||
});
|
});
|
||||||
@ -35,9 +36,7 @@ describe("browser config", () => {
|
|||||||
const resolved = resolveBrowserConfig(undefined);
|
const resolved = resolveBrowserConfig(undefined);
|
||||||
expect(resolved.controlPort).toBe(19003);
|
expect(resolved.controlPort).toBe(19003);
|
||||||
const chrome = resolveProfile(resolved, "chrome");
|
const chrome = resolveProfile(resolved, "chrome");
|
||||||
expect(chrome?.driver).toBe("extension");
|
expect(chrome?.driver).toBe("existing-session");
|
||||||
expect(chrome?.cdpPort).toBe(19004);
|
|
||||||
expect(chrome?.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);
|
||||||
@ -50,9 +49,7 @@ describe("browser config", () => {
|
|||||||
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 chrome = resolveProfile(resolved, "chrome");
|
||||||
expect(chrome?.driver).toBe("extension");
|
expect(chrome?.driver).toBe("existing-session");
|
||||||
expect(chrome?.cdpPort).toBe(19014);
|
|
||||||
expect(chrome?.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,14 +202,16 @@ 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 profile if the user already has one", () => {
|
||||||
const resolved = resolveBrowserConfig({
|
const resolved = resolveBrowserConfig({
|
||||||
profiles: {
|
profiles: {
|
||||||
openclaw: { cdpPort: 18792, color: "#FF4500" },
|
chrome: { driver: "extension", cdpUrl: "http://127.0.0.1:18792", color: "#0066CC" },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(resolveProfile(resolved, "chrome")).toBe(null);
|
// User's explicit chrome profile is preserved, not overwritten
|
||||||
expect(resolved.defaultProfile).toBe("openclaw");
|
const chrome = resolveProfile(resolved, "chrome");
|
||||||
|
expect(chrome?.driver).toBe("extension");
|
||||||
|
expect(chrome?.cdpUrl).toBe("http://127.0.0.1:18792");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("defaults extraArgs to empty array when not provided", () => {
|
it("defaults extraArgs to empty array when not provided", () => {
|
||||||
@ -303,6 +302,7 @@ describe("browser config", () => {
|
|||||||
const resolved = resolveBrowserConfig({
|
const resolved = resolveBrowserConfig({
|
||||||
profiles: {
|
profiles: {
|
||||||
"chrome-live": { driver: "existing-session", attachOnly: true, color: "#00AA00" },
|
"chrome-live": { driver: "existing-session", attachOnly: true, color: "#00AA00" },
|
||||||
|
relay: { driver: "extension", cdpUrl: "http://127.0.0.1:18792", color: "#0066CC" },
|
||||||
work: { cdpPort: 18801, color: "#0066CC" },
|
work: { cdpPort: 18801, color: "#0066CC" },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -313,7 +313,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, "relay")!;
|
||||||
expect(getBrowserProfileCapabilities(extension).usesChromeMcp).toBe(false);
|
expect(getBrowserProfileCapabilities(extension).usesChromeMcp).toBe(false);
|
||||||
|
|
||||||
const work = resolveProfile(resolved, "work")!;
|
const work = resolveProfile(resolved, "work")!;
|
||||||
|
|||||||
@ -8,13 +8,14 @@ import {
|
|||||||
import { isLoopbackHost } from "../gateway/net.js";
|
import { isLoopbackHost } from "../gateway/net.js";
|
||||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||||
import {
|
import {
|
||||||
|
MANAGED_BROWSER_DRIVERS,
|
||||||
DEFAULT_OPENCLAW_BROWSER_COLOR,
|
DEFAULT_OPENCLAW_BROWSER_COLOR,
|
||||||
DEFAULT_OPENCLAW_BROWSER_ENABLED,
|
DEFAULT_OPENCLAW_BROWSER_ENABLED,
|
||||||
DEFAULT_BROWSER_EVALUATE_ENABLED,
|
DEFAULT_BROWSER_EVALUATE_ENABLED,
|
||||||
DEFAULT_BROWSER_DEFAULT_PROFILE_NAME,
|
DEFAULT_BROWSER_DEFAULT_PROFILE_NAME,
|
||||||
DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME,
|
DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME,
|
||||||
} from "./constants.js";
|
} from "./constants.js";
|
||||||
import { CDP_PORT_RANGE_START, getUsedPorts } from "./profiles.js";
|
import { CDP_PORT_RANGE_START } from "./profiles.js";
|
||||||
|
|
||||||
export type ResolvedBrowserConfig = {
|
export type ResolvedBrowserConfig = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -180,31 +181,31 @@ function ensureDefaultProfile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure a built-in "chrome" profile exists for the Chrome extension relay.
|
* Ensure a built-in "chrome" profile exists for connecting to the user's
|
||||||
|
* real Chrome browser.
|
||||||
*
|
*
|
||||||
* Note: this is an OpenClaw browser profile (routing config), not a Chrome user profile.
|
* Uses the existing-session driver (Chrome MCP) which connects via
|
||||||
* It points at the local relay CDP endpoint (controlPort + 1).
|
* Chrome MCP auto-discovers a running Chrome (v146+) via its user data
|
||||||
|
* directory — no CDP port or remote-debugging toggle needed.
|
||||||
*/
|
*/
|
||||||
function ensureDefaultChromeExtensionProfile(
|
function ensureDefaultUserBrowserProfile(
|
||||||
profiles: Record<string, BrowserProfileConfig>,
|
profiles: Record<string, BrowserProfileConfig>,
|
||||||
controlPort: number,
|
|
||||||
): Record<string, BrowserProfileConfig> {
|
): Record<string, BrowserProfileConfig> {
|
||||||
const result = { ...profiles };
|
const result = { ...profiles };
|
||||||
if (result.chrome) {
|
if (result.chrome) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
const relayPort = controlPort + 1;
|
// Skip when the user already has a profile that connects to their real
|
||||||
if (!Number.isFinite(relayPort) || relayPort <= 0 || relayPort > 65535) {
|
// browser (any driver not in MANAGED_BROWSER_DRIVERS).
|
||||||
return result;
|
const hasUserProfile = Object.values(result).some(
|
||||||
}
|
(p) => p.driver && !MANAGED_BROWSER_DRIVERS.has(p.driver),
|
||||||
// Avoid adding the built-in profile if the derived relay port is already used by another profile
|
);
|
||||||
// (legacy single-profile configs may use controlPort+1 for openclaw/openclaw CDP).
|
if (hasUserProfile) {
|
||||||
if (getUsedPorts(result).has(relayPort)) {
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result.chrome = {
|
result.chrome = {
|
||||||
driver: "extension",
|
driver: "existing-session",
|
||||||
cdpUrl: `http://127.0.0.1:${relayPort}`,
|
attachOnly: true,
|
||||||
color: "#00AA00",
|
color: "#00AA00",
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
@ -268,7 +269,7 @@ 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 = ensureDefaultUserBrowserProfile(
|
||||||
ensureDefaultProfile(
|
ensureDefaultProfile(
|
||||||
cfg?.profiles,
|
cfg?.profiles,
|
||||||
defaultColor,
|
defaultColor,
|
||||||
@ -276,7 +277,6 @@ export function resolveBrowserConfig(
|
|||||||
cdpPortRangeStart,
|
cdpPortRangeStart,
|
||||||
legacyCdpUrl,
|
legacyCdpUrl,
|
||||||
),
|
),
|
||||||
controlPort,
|
|
||||||
);
|
);
|
||||||
const cdpProtocol = cdpInfo.parsed.protocol === "https:" ? "https" : "http";
|
const cdpProtocol = cdpInfo.parsed.protocol === "https:" ? "https" : "http";
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Drivers that launch/manage a browser instance (sandbox). Any driver not in
|
||||||
|
* this set attaches to the user's existing browser.
|
||||||
|
*/
|
||||||
|
export const MANAGED_BROWSER_DRIVERS = new Set(["openclaw", "clawd"]);
|
||||||
|
|
||||||
export const DEFAULT_OPENCLAW_BROWSER_ENABLED = true;
|
export const DEFAULT_OPENCLAW_BROWSER_ENABLED = true;
|
||||||
export const DEFAULT_BROWSER_EVALUATE_ENABLED = true;
|
export const DEFAULT_BROWSER_EVALUATE_ENABLED = true;
|
||||||
export const DEFAULT_OPENCLAW_BROWSER_COLOR = "#FF4500";
|
export const DEFAULT_OPENCLAW_BROWSER_COLOR = "#FF4500";
|
||||||
|
|||||||
@ -9,6 +9,8 @@ export type BrowserProfileMode =
|
|||||||
export type BrowserProfileCapabilities = {
|
export type BrowserProfileCapabilities = {
|
||||||
mode: BrowserProfileMode;
|
mode: BrowserProfileMode;
|
||||||
isRemote: boolean;
|
isRemote: boolean;
|
||||||
|
/** Profile attaches to the user's real browser (not a managed sandbox). */
|
||||||
|
isUserBrowser: boolean;
|
||||||
/** Profile uses the Chrome DevTools MCP server (existing-session driver). */
|
/** Profile uses the Chrome DevTools MCP server (existing-session driver). */
|
||||||
usesChromeMcp: boolean;
|
usesChromeMcp: boolean;
|
||||||
requiresRelay: boolean;
|
requiresRelay: boolean;
|
||||||
@ -27,6 +29,7 @@ export function getBrowserProfileCapabilities(
|
|||||||
return {
|
return {
|
||||||
mode: "local-extension-relay",
|
mode: "local-extension-relay",
|
||||||
isRemote: false,
|
isRemote: false,
|
||||||
|
isUserBrowser: true,
|
||||||
usesChromeMcp: false,
|
usesChromeMcp: false,
|
||||||
requiresRelay: true,
|
requiresRelay: true,
|
||||||
requiresAttachedTab: true,
|
requiresAttachedTab: true,
|
||||||
@ -42,6 +45,7 @@ export function getBrowserProfileCapabilities(
|
|||||||
return {
|
return {
|
||||||
mode: "local-existing-session",
|
mode: "local-existing-session",
|
||||||
isRemote: false,
|
isRemote: false,
|
||||||
|
isUserBrowser: true,
|
||||||
usesChromeMcp: true,
|
usesChromeMcp: true,
|
||||||
requiresRelay: false,
|
requiresRelay: false,
|
||||||
requiresAttachedTab: false,
|
requiresAttachedTab: false,
|
||||||
@ -57,6 +61,7 @@ export function getBrowserProfileCapabilities(
|
|||||||
return {
|
return {
|
||||||
mode: "remote-cdp",
|
mode: "remote-cdp",
|
||||||
isRemote: true,
|
isRemote: true,
|
||||||
|
isUserBrowser: false,
|
||||||
usesChromeMcp: false,
|
usesChromeMcp: false,
|
||||||
requiresRelay: false,
|
requiresRelay: false,
|
||||||
requiresAttachedTab: false,
|
requiresAttachedTab: false,
|
||||||
@ -71,6 +76,7 @@ export function getBrowserProfileCapabilities(
|
|||||||
return {
|
return {
|
||||||
mode: "local-managed",
|
mode: "local-managed",
|
||||||
isRemote: false,
|
isRemote: false,
|
||||||
|
isUserBrowser: false,
|
||||||
usesChromeMcp: false,
|
usesChromeMcp: false,
|
||||||
requiresRelay: false,
|
requiresRelay: false,
|
||||||
requiresAttachedTab: false,
|
requiresAttachedTab: false,
|
||||||
|
|||||||
@ -3,10 +3,11 @@ 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 existing-session snapshots to ai when format is omitted", () => {
|
||||||
const resolved = resolveBrowserConfig({});
|
const resolved = resolveBrowserConfig({});
|
||||||
const profile = resolveProfile(resolved, "chrome");
|
const profile = resolveProfile(resolved, "chrome");
|
||||||
expect(profile).toBeTruthy();
|
expect(profile).toBeTruthy();
|
||||||
|
expect(profile?.driver).toBe("existing-session");
|
||||||
|
|
||||||
const plan = resolveSnapshotPlan({
|
const plan = resolveSnapshotPlan({
|
||||||
profile: profile as NonNullable<typeof profile>,
|
profile: profile as NonNullable<typeof profile>,
|
||||||
@ -14,7 +15,7 @@ describe("resolveSnapshotPlan", () => {
|
|||||||
hasPlaywright: true,
|
hasPlaywright: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(plan.format).toBe("aria");
|
expect(plan.format).toBe("ai");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps ai snapshots for managed browsers when Playwright is available", () => {
|
it("keeps ai snapshots for managed browsers when Playwright is available", () => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user