mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
feat(browser): support per-profile headless
Co-authored-by: nakamotoliu <nakamotoliu2026@gmail.com> Co-authored-by: Nakamoto <nakamoto@claude.ai>
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Browser/config: support per-profile `browser.profiles.<name>.headless` overrides for locally launched browser profiles, so one profile can run headless without forcing all browser profiles headless. Thanks @nakamotoliu.
|
||||
- Plugins/PDF: move local PDF extraction into a bundled `document-extract` plugin so core no longer owns `pdfjs-dist` or PDF image-rendering dependencies. Thanks @vincentkoc.
|
||||
- Matrix: require full cross-signing identity trust for self-device verification and add `openclaw matrix verify self` so operators can establish that trust from the CLI. (#70401) Thanks @gumadeiras.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
52af51e35e05d0cbaa1a79fb415f2c2fe56ad5d52a62efa9cbb9c32489d517f5 config-baseline.json
|
||||
642b4e2c9891e710790313df097b4e0db75a197ec0908e9c03bdc76f5bbdf9b0 config-baseline.core.json
|
||||
8f23e853ccde6cd021b84b32fe205f456f8516667683d16c9b56d6598f608989 config-baseline.json
|
||||
037bf4a873587adb8349f531c0ad79cd4f90e01712f5aa5d8b4387be73538a7f config-baseline.core.json
|
||||
22d7cd6d8279146b2d79c9531a55b80b52a2c99c81338c508104729154fdd02d config-baseline.channel.json
|
||||
d47a574045a47356e513ab308d7dcad9fa0b389f50e93c5cf0f820fab858e70e config-baseline.plugin.json
|
||||
86f615b7d267b03888af0af7ccb3f8232a6b636f8a741d522ff425e46729ba81 config-baseline.plugin.json
|
||||
|
||||
@@ -137,7 +137,7 @@ Browser settings live in `~/.openclaw/openclaw.json`.
|
||||
executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
||||
profiles: {
|
||||
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
||||
work: { cdpPort: 18801, color: "#0066CC" },
|
||||
work: { cdpPort: 18801, color: "#0066CC", headless: true },
|
||||
user: {
|
||||
driver: "existing-session",
|
||||
attachOnly: true,
|
||||
@@ -177,6 +177,7 @@ Browser settings live in `~/.openclaw/openclaw.json`.
|
||||
<Accordion title="Profile behavior">
|
||||
|
||||
- `attachOnly: true` means never launch a local browser; only attach if one is already running.
|
||||
- `headless` can be set globally or per local managed profile. Per-profile values override `browser.headless`, so one locally launched profile can stay headless while another remains visible.
|
||||
- `color` (top-level and per-profile) tints the browser UI so you can see which profile is active.
|
||||
- Default profile is `openclaw` (managed standalone). Use `defaultProfile: "user"` to opt into the signed-in user browser.
|
||||
- Auto-detect order: system default browser if Chromium-based; otherwise Chrome → Brave → Edge → Chromium → Chrome Canary.
|
||||
@@ -235,6 +236,7 @@ Or set it in config, per platform:
|
||||
- **Remote control (node host):** run a node host on the machine that has the browser; the Gateway proxies browser actions to it.
|
||||
- **Remote CDP:** set `browser.profiles.<name>.cdpUrl` (or `browser.cdpUrl`) to
|
||||
attach to a remote Chromium-based browser. In this case, OpenClaw will not launch a local browser.
|
||||
- `headless` only affects local managed profiles that OpenClaw launches. It does not restart or change existing-session or remote CDP browsers.
|
||||
|
||||
Stopping behavior differs by profile mode:
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ function createProfile(overrides: Partial<ResolvedBrowserProfile>): ResolvedBrow
|
||||
driver: "openclaw",
|
||||
attachOnly: false,
|
||||
...overrides,
|
||||
headless: overrides.headless ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ const localProfile: ResolvedBrowserProfile = {
|
||||
cdpIsLoopback: true,
|
||||
color: "#FF4500",
|
||||
driver: "openclaw",
|
||||
headless: false,
|
||||
attachOnly: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -166,18 +166,29 @@ describe("chrome.ts internal", () => {
|
||||
cdpPort: 19222,
|
||||
cdpUrl: "http://127.0.0.1:19222",
|
||||
cdpIsLoopback: true,
|
||||
headless: false,
|
||||
} as unknown as ResolvedBrowserProfile;
|
||||
|
||||
it("toggles headless args", () => {
|
||||
const args = buildOpenClawChromeLaunchArgs({
|
||||
resolved: baseResolved({ headless: true }),
|
||||
profile: baseProfile,
|
||||
resolved: baseResolved({ headless: false }),
|
||||
profile: { ...baseProfile, headless: true },
|
||||
userDataDir: "/tmp/foo",
|
||||
});
|
||||
expect(args).toContain("--headless=new");
|
||||
expect(args).toContain("--disable-gpu");
|
||||
});
|
||||
|
||||
it("lets profile headless=false override global headless=true", () => {
|
||||
const args = buildOpenClawChromeLaunchArgs({
|
||||
resolved: baseResolved({ headless: true }),
|
||||
profile: { ...baseProfile, headless: false },
|
||||
userDataDir: "/tmp/foo",
|
||||
});
|
||||
expect(args).not.toContain("--headless=new");
|
||||
expect(args).not.toContain("--disable-gpu");
|
||||
});
|
||||
|
||||
it("toggles no-sandbox args", () => {
|
||||
const args = buildOpenClawChromeLaunchArgs({
|
||||
resolved: baseResolved({ noSandbox: true }),
|
||||
|
||||
@@ -650,6 +650,7 @@ describe("browser chrome launch args", () => {
|
||||
cdpIsLoopback: true,
|
||||
color: "#FF4500",
|
||||
driver: "openclaw",
|
||||
headless: false,
|
||||
attachOnly: false,
|
||||
},
|
||||
userDataDir: "/tmp/openclaw-test-user-data",
|
||||
|
||||
@@ -121,7 +121,7 @@ export function buildOpenClawChromeLaunchArgs(params: {
|
||||
"--password-store=basic",
|
||||
];
|
||||
|
||||
if (resolved.headless) {
|
||||
if (profile.headless) {
|
||||
args.push("--headless=new");
|
||||
args.push("--disable-gpu");
|
||||
}
|
||||
|
||||
@@ -178,6 +178,42 @@ describe("browser config", () => {
|
||||
expect(remote?.attachOnly).toBe(true);
|
||||
});
|
||||
|
||||
it("inherits headless from global browser config when profile override is not set", () => {
|
||||
const resolved = resolveBrowserConfig({
|
||||
headless: true,
|
||||
profiles: {
|
||||
remote: { cdpUrl: "http://127.0.0.1:9222", color: "#0066CC" },
|
||||
},
|
||||
});
|
||||
|
||||
const remote = resolveProfile(resolved, "remote");
|
||||
expect(remote?.headless).toBe(true);
|
||||
});
|
||||
|
||||
it("allows profile headless to override global browser headless", () => {
|
||||
const resolved = resolveBrowserConfig({
|
||||
headless: false,
|
||||
profiles: {
|
||||
remote: { cdpUrl: "http://127.0.0.1:9222", headless: true, color: "#0066CC" },
|
||||
},
|
||||
});
|
||||
|
||||
const remote = resolveProfile(resolved, "remote");
|
||||
expect(remote?.headless).toBe(true);
|
||||
});
|
||||
|
||||
it("allows profile headless=false to override global browser headless=true", () => {
|
||||
const resolved = resolveBrowserConfig({
|
||||
headless: true,
|
||||
profiles: {
|
||||
remote: { cdpUrl: "http://127.0.0.1:9222", headless: false, color: "#0066CC" },
|
||||
},
|
||||
});
|
||||
|
||||
const remote = resolveProfile(resolved, "remote");
|
||||
expect(remote?.headless).toBe(false);
|
||||
});
|
||||
|
||||
it("uses base protocol for profiles with only cdpPort", () => {
|
||||
const resolved = resolveBrowserConfig({
|
||||
cdpUrl: "https://example.com:9443",
|
||||
|
||||
@@ -81,6 +81,7 @@ export type ResolvedBrowserProfile = {
|
||||
userDataDir?: string;
|
||||
color: string;
|
||||
driver: "openclaw" | "existing-session";
|
||||
headless: boolean;
|
||||
attachOnly: boolean;
|
||||
};
|
||||
|
||||
@@ -312,6 +313,7 @@ export function resolveProfile(
|
||||
let cdpPort = profile.cdpPort ?? 0;
|
||||
let cdpUrl = "";
|
||||
const driver = profile.driver === "existing-session" ? "existing-session" : "openclaw";
|
||||
const headless = profile.headless ?? resolved.headless;
|
||||
|
||||
if (driver === "existing-session") {
|
||||
return {
|
||||
@@ -323,6 +325,7 @@ export function resolveProfile(
|
||||
userDataDir: resolveUserPath(profile.userDataDir?.trim() || "") || undefined,
|
||||
color: profile.color,
|
||||
driver,
|
||||
headless,
|
||||
attachOnly: true,
|
||||
};
|
||||
}
|
||||
@@ -356,6 +359,7 @@ export function resolveProfile(
|
||||
cdpIsLoopback: isLoopbackHost(cdpHost),
|
||||
color: profile.color,
|
||||
driver,
|
||||
headless,
|
||||
attachOnly: profile.attachOnly ?? resolved.attachOnly,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ function changedProfileInvariants(
|
||||
next: ResolvedBrowserProfile,
|
||||
): string[] {
|
||||
const changed: string[] = [];
|
||||
const currentUsesLocalManagedLaunch =
|
||||
current.driver === "openclaw" && !current.attachOnly && current.cdpIsLoopback;
|
||||
const nextUsesLocalManagedLaunch =
|
||||
next.driver === "openclaw" && !next.attachOnly && next.cdpIsLoopback;
|
||||
if (current.cdpUrl !== next.cdpUrl) {
|
||||
changed.push("cdpUrl");
|
||||
}
|
||||
@@ -16,6 +20,13 @@ function changedProfileInvariants(
|
||||
if (current.driver !== next.driver) {
|
||||
changed.push("driver");
|
||||
}
|
||||
if (
|
||||
currentUsesLocalManagedLaunch &&
|
||||
nextUsesLocalManagedLaunch &&
|
||||
current.headless !== next.headless
|
||||
) {
|
||||
changed.push("headless");
|
||||
}
|
||||
if (current.attachOnly !== next.attachOnly) {
|
||||
changed.push("attachOnly");
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ function profile(driver: "existing-session" | "openclaw"): ResolvedBrowserProfil
|
||||
cdpHost: "127.0.0.1",
|
||||
cdpIsLoopback: true,
|
||||
color: "#00AA00",
|
||||
headless: false,
|
||||
attachOnly: driver === "existing-session",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ async function buildBrowserStatus(req: BrowserRequest, ctx: BrowserRouteContext)
|
||||
detectError,
|
||||
userDataDir: profileState?.running?.userDataDir ?? profileCtx.profile.userDataDir ?? null,
|
||||
color: profileCtx.profile.color,
|
||||
headless: current.resolved.headless,
|
||||
headless: profileCtx.profile.headless,
|
||||
noSandbox: current.resolved.noSandbox,
|
||||
executablePath: current.resolved.executablePath ?? null,
|
||||
attachOnly: profileCtx.profile.attachOnly,
|
||||
|
||||
@@ -23,6 +23,7 @@ describe("browser tab routes attachOnly loopback profiles", () => {
|
||||
cdpPort: 9222,
|
||||
color: "#00AA00",
|
||||
driver: "openclaw",
|
||||
headless: false,
|
||||
attachOnly: true,
|
||||
},
|
||||
resolvedOverrides: {
|
||||
|
||||
@@ -35,6 +35,7 @@ function createAttachOnlyLoopbackProfile(cdpUrl: string) {
|
||||
cdpPort: 9222,
|
||||
color: "#00AA00",
|
||||
driver: "openclaw",
|
||||
headless: false,
|
||||
attachOnly: true,
|
||||
},
|
||||
resolvedOverrides: {
|
||||
@@ -236,6 +237,7 @@ describe("browser server-context ensureBrowserAvailable", () => {
|
||||
cdpPort: 443,
|
||||
color: "#00AA00",
|
||||
driver: "openclaw",
|
||||
headless: false,
|
||||
attachOnly: false,
|
||||
},
|
||||
resolvedOverrides: {
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { BrowserServerState } from "./server-context.types.js";
|
||||
|
||||
type TestProfileConfig = { cdpPort?: number; cdpUrl?: string; color?: string };
|
||||
type TestProfileConfig = {
|
||||
cdpPort?: number;
|
||||
cdpUrl?: string;
|
||||
color?: string;
|
||||
headless?: boolean;
|
||||
driver?: "openclaw" | "existing-session";
|
||||
};
|
||||
type TestConfig = {
|
||||
browser: {
|
||||
enabled: true;
|
||||
@@ -225,4 +231,157 @@ describe("server-context hot-reload profiles", () => {
|
||||
expect(runtime?.lastTargetId).toBeNull();
|
||||
expect(runtime?.reconcile?.reason).toContain("cdpPort");
|
||||
});
|
||||
|
||||
it("marks local managed runtime state for reconcile when profile headless changes", async () => {
|
||||
const cfg = loadConfig();
|
||||
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
||||
const openclawProfile = resolveProfile(resolved, "openclaw");
|
||||
expect(openclawProfile).toBeTruthy();
|
||||
expect(openclawProfile?.headless).toBe(true);
|
||||
const state: BrowserServerState = {
|
||||
server: null,
|
||||
port: 18791,
|
||||
resolved,
|
||||
profiles: new Map([
|
||||
[
|
||||
"openclaw",
|
||||
{
|
||||
profile: openclawProfile!,
|
||||
running: { pid: 123 } as never,
|
||||
lastTargetId: "tab-1",
|
||||
reconcile: null,
|
||||
},
|
||||
],
|
||||
]),
|
||||
};
|
||||
|
||||
mockState.cfgProfiles.openclaw = {
|
||||
cdpPort: 18800,
|
||||
color: "#FF4500",
|
||||
headless: false,
|
||||
};
|
||||
mockState.cachedConfig = null;
|
||||
|
||||
refreshResolvedBrowserConfigFromDisk({
|
||||
current: state,
|
||||
refreshConfigFromDisk: true,
|
||||
mode: "cached",
|
||||
});
|
||||
|
||||
const runtime = state.profiles.get("openclaw");
|
||||
expect(runtime).toBeTruthy();
|
||||
expect(runtime?.profile.headless).toBe(false);
|
||||
expect(runtime?.lastTargetId).toBeNull();
|
||||
expect(runtime?.reconcile?.reason).toContain("headless");
|
||||
});
|
||||
|
||||
it("does not reconcile existing-session runtime when only headless changes", async () => {
|
||||
mockState.cfgProfiles.remote = {
|
||||
cdpUrl: "http://127.0.0.1:9222",
|
||||
color: "#0066CC",
|
||||
headless: true,
|
||||
driver: "existing-session",
|
||||
};
|
||||
|
||||
const cfg = loadConfig();
|
||||
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
||||
const remoteProfile = resolveProfile(resolved, "remote");
|
||||
expect(remoteProfile).toBeTruthy();
|
||||
expect(remoteProfile?.driver).toBe("existing-session");
|
||||
expect(remoteProfile?.attachOnly).toBe(true);
|
||||
expect(remoteProfile?.headless).toBe(true);
|
||||
|
||||
const state: BrowserServerState = {
|
||||
server: null,
|
||||
port: 18791,
|
||||
resolved,
|
||||
profiles: new Map([
|
||||
[
|
||||
"remote",
|
||||
{
|
||||
profile: remoteProfile!,
|
||||
running: { pid: 456 } as never,
|
||||
lastTargetId: "tab-remote",
|
||||
reconcile: null,
|
||||
},
|
||||
],
|
||||
]),
|
||||
};
|
||||
|
||||
mockState.cfgProfiles.remote = {
|
||||
cdpUrl: "http://127.0.0.1:9222",
|
||||
color: "#0066CC",
|
||||
headless: false,
|
||||
driver: "existing-session",
|
||||
};
|
||||
mockState.cachedConfig = null;
|
||||
|
||||
refreshResolvedBrowserConfigFromDisk({
|
||||
current: state,
|
||||
refreshConfigFromDisk: true,
|
||||
mode: "cached",
|
||||
});
|
||||
|
||||
const runtime = state.profiles.get("remote");
|
||||
expect(runtime).toBeTruthy();
|
||||
expect(runtime?.profile.driver).toBe("existing-session");
|
||||
expect(runtime?.profile.headless).toBe(false);
|
||||
expect(runtime?.lastTargetId).toBe("tab-remote");
|
||||
expect(runtime?.reconcile).toBeNull();
|
||||
});
|
||||
|
||||
it("does not reconcile remote cdp runtime when only headless changes", async () => {
|
||||
mockState.cfgProfiles.remote = {
|
||||
cdpUrl: "http://10.0.0.42:9222",
|
||||
color: "#0066CC",
|
||||
headless: true,
|
||||
};
|
||||
|
||||
const cfg = loadConfig();
|
||||
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
||||
const remoteProfile = resolveProfile(resolved, "remote");
|
||||
expect(remoteProfile).toBeTruthy();
|
||||
expect(remoteProfile?.driver).toBe("openclaw");
|
||||
expect(remoteProfile?.attachOnly).toBe(false);
|
||||
expect(remoteProfile?.cdpIsLoopback).toBe(false);
|
||||
expect(remoteProfile?.headless).toBe(true);
|
||||
|
||||
const state: BrowserServerState = {
|
||||
server: null,
|
||||
port: 18791,
|
||||
resolved,
|
||||
profiles: new Map([
|
||||
[
|
||||
"remote",
|
||||
{
|
||||
profile: remoteProfile!,
|
||||
running: { pid: 789 } as never,
|
||||
lastTargetId: "tab-remote-cdp",
|
||||
reconcile: null,
|
||||
},
|
||||
],
|
||||
]),
|
||||
};
|
||||
|
||||
mockState.cfgProfiles.remote = {
|
||||
cdpUrl: "http://10.0.0.42:9222",
|
||||
color: "#0066CC",
|
||||
headless: false,
|
||||
};
|
||||
mockState.cachedConfig = null;
|
||||
|
||||
refreshResolvedBrowserConfigFromDisk({
|
||||
current: state,
|
||||
refreshConfigFromDisk: true,
|
||||
mode: "cached",
|
||||
});
|
||||
|
||||
const runtime = state.profiles.get("remote");
|
||||
expect(runtime).toBeTruthy();
|
||||
expect(runtime?.profile.driver).toBe("openclaw");
|
||||
expect(runtime?.profile.cdpIsLoopback).toBe(false);
|
||||
expect(runtime?.profile.headless).toBe(false);
|
||||
expect(runtime?.lastTargetId).toBe("tab-remote-cdp");
|
||||
expect(runtime?.reconcile).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,7 @@ describe("browser server-context listProfiles", () => {
|
||||
cdpPort: 9222,
|
||||
color: "#00AA00",
|
||||
driver: "openclaw",
|
||||
headless: false,
|
||||
attachOnly: true,
|
||||
},
|
||||
resolvedOverrides: {
|
||||
|
||||
@@ -77,6 +77,7 @@ function resolveProfileForTest(
|
||||
cdpIsLoopback,
|
||||
color: rawProfile.color ?? state.resolved.color,
|
||||
driver: rawProfile.driver === "existing-session" ? "existing-session" : "openclaw",
|
||||
headless: rawProfile.headless ?? state.resolved.headless,
|
||||
attachOnly: rawProfile.attachOnly ?? state.resolved.attachOnly,
|
||||
userDataDir: rawProfile.userDataDir,
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@ function localOpenClawProfile(): Parameters<typeof createProfileResetOps>[0]["pr
|
||||
cdpPort: 18800,
|
||||
color: "#f60",
|
||||
driver: "openclaw",
|
||||
headless: false,
|
||||
attachOnly: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export function makeBrowserProfile(
|
||||
cdpPort: 18800,
|
||||
color: "#FF4500",
|
||||
driver: "openclaw",
|
||||
headless: false,
|
||||
attachOnly: false,
|
||||
...overrides,
|
||||
};
|
||||
|
||||
@@ -744,6 +744,12 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
description:
|
||||
'Per-profile browser driver mode. Use "openclaw" (or legacy "clawd") for CDP-based profiles, or use "existing-session" for Chrome DevTools MCP attachment on the selected host or browser node.',
|
||||
},
|
||||
headless: {
|
||||
type: "boolean",
|
||||
title: "Browser Profile Headless Mode",
|
||||
description:
|
||||
"Per-profile headless override for locally launched browser instances. Use this when one profile should stay headless without forcing browser.headless for every other profile.",
|
||||
},
|
||||
attachOnly: {
|
||||
type: "boolean",
|
||||
title: "Browser Profile Attach-only Mode",
|
||||
@@ -23950,6 +23956,11 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
help: 'Per-profile browser driver mode. Use "openclaw" (or legacy "clawd") for CDP-based profiles, or use "existing-session" for Chrome DevTools MCP attachment on the selected host or browser node.',
|
||||
tags: ["storage"],
|
||||
},
|
||||
"browser.profiles.*.headless": {
|
||||
label: "Browser Profile Headless Mode",
|
||||
help: "Per-profile headless override for locally launched browser instances. Use this when one profile should stay headless without forcing browser.headless for every other profile.",
|
||||
tags: ["storage"],
|
||||
},
|
||||
"browser.profiles.*.attachOnly": {
|
||||
label: "Browser Profile Attach-only Mode",
|
||||
help: "Per-profile attach-only override that skips local browser launch and only attaches to an existing CDP endpoint. Useful when one profile is externally managed but others are locally launched.",
|
||||
|
||||
@@ -282,6 +282,8 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"Per-profile Chromium user data directory for existing-session attachment through Chrome DevTools MCP. Use this for Brave, Edge, Chromium, or non-default Chrome profiles when the built-in auto-connect path would pick the wrong browser data directory on the selected host or browser node.",
|
||||
"browser.profiles.*.driver":
|
||||
'Per-profile browser driver mode. Use "openclaw" (or legacy "clawd") for CDP-based profiles, or use "existing-session" for Chrome DevTools MCP attachment on the selected host or browser node.',
|
||||
"browser.profiles.*.headless":
|
||||
"Per-profile headless override for locally launched browser instances. Use this when one profile should stay headless without forcing browser.headless for every other profile.",
|
||||
"browser.profiles.*.attachOnly":
|
||||
"Per-profile attach-only override that skips local browser launch and only attaches to an existing CDP endpoint. Useful when one profile is externally managed but others are locally launched.",
|
||||
"browser.profiles.*.color":
|
||||
|
||||
@@ -152,6 +152,7 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"browser.profiles.*.cdpUrl": "Browser Profile CDP URL",
|
||||
"browser.profiles.*.userDataDir": "Browser Profile User Data Dir",
|
||||
"browser.profiles.*.driver": "Browser Profile Driver",
|
||||
"browser.profiles.*.headless": "Browser Profile Headless Mode",
|
||||
"browser.profiles.*.attachOnly": "Browser Profile Attach-only Mode",
|
||||
"browser.profiles.*.color": "Browser Profile Accent Color",
|
||||
tools: "Tools",
|
||||
|
||||
@@ -7,6 +7,8 @@ export type BrowserProfileConfig = {
|
||||
userDataDir?: string;
|
||||
/** Profile driver (default: openclaw). */
|
||||
driver?: "openclaw" | "clawd" | "existing-session";
|
||||
/** If true, launch this profile in headless mode. Falls back to browser.headless. */
|
||||
headless?: boolean;
|
||||
/** If true, never launch a browser for this profile; only attach. Falls back to browser.attachOnly. */
|
||||
attachOnly?: boolean;
|
||||
/** Profile color (hex). Auto-assigned at creation. */
|
||||
|
||||
@@ -409,6 +409,7 @@ export const OpenClawSchema = z
|
||||
driver: z
|
||||
.union([z.literal("openclaw"), z.literal("clawd"), z.literal("existing-session")])
|
||||
.optional(),
|
||||
headless: z.boolean().optional(),
|
||||
attachOnly: z.boolean().optional(),
|
||||
color: HexColorSchema,
|
||||
})
|
||||
|
||||
@@ -43,6 +43,7 @@ export type ResolvedBrowserProfile = {
|
||||
userDataDir?: string;
|
||||
color: string;
|
||||
driver: "openclaw" | "existing-session";
|
||||
headless?: boolean;
|
||||
attachOnly: boolean;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user