

























@@ -1,16 +1,21 @@
1+import fs from "node:fs";
2+import path from "node:path";
13import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
24import {
35parseBrowserMajorVersion,
46readBrowserVersion,
57resolveBrowserExecutableForPlatform,
68resolveGoogleChromeExecutableForPlatform,
79} from "./browser/chrome.executables.js";
8-import { resolveBrowserConfig } from "./browser/config.js";
10+import { DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME, resolveBrowserConfig } from "./browser/config.js";
11+import { movePathToTrash } from "./browser/trash.js";
912import type { OpenClawConfig } from "./config/config.js";
1013import { asRecord } from "./record-shared.js";
11-import { note } from "./sdk-setup-tools.js";
14+import { formatCliCommand, note } from "./sdk-setup-tools.js";
15+import { CONFIG_DIR, resolveUserPath } from "./utils.js";
12161317const CHROME_MCP_MIN_MAJOR = 144;
18+const LEGACY_CLAWD_BROWSER_PROFILE_NAME = "clawd";
1419const REMOTE_DEBUGGING_PAGES = [
1520"chrome://inspect/#remote-debugging",
1621"brave://inspect/#remote-debugging",
@@ -26,6 +31,18 @@ type ManagedProfile = {
2631name: string;
2732};
283334+export type LegacyClawdBrowserProfileResidue = {
35+legacyProfileDir: string;
36+legacyUserDataDir: string;
37+canonicalUserDataDir: string;
38+};
39+40+type BrowserDoctorFilesystemDeps = {
41+configDir?: string;
42+pathExists?: (targetPath: string) => boolean;
43+movePathToTrash?: (targetPath: string) => Promise<string>;
44+};
45+2946function collectChromeMcpProfiles(cfg: OpenClawConfig): ExistingSessionProfile[] {
3047const browser = asRecord(cfg.browser);
3148if (!browser) {
@@ -85,6 +102,102 @@ function collectManagedProfiles(cfg: OpenClawConfig): ManagedProfile[] {
85102return [...profiles.values()].toSorted((a, b) => a.name.localeCompare(b.name));
86103}
87104105+function resolveManagedBrowserProfileDir(configDir: string, profileName: string): string {
106+return path.join(configDir, "browser", profileName);
107+}
108+109+function resolveManagedBrowserUserDataDir(configDir: string, profileName: string): string {
110+return path.join(resolveManagedBrowserProfileDir(configDir, profileName), "user-data");
111+}
112+113+function normalizeComparablePath(targetPath: string): string {
114+return path.resolve(targetPath);
115+}
116+117+function isSameOrChildPath(candidatePath: string, parentPath: string): boolean {
118+const candidate = normalizeComparablePath(candidatePath);
119+const parent = normalizeComparablePath(parentPath);
120+return candidate === parent || candidate.startsWith(`${parent}${path.sep}`);
121+}
122+123+function isLegacyClawdProfileConfigured(cfg: OpenClawConfig, legacyProfileDir: string): boolean {
124+const browser = asRecord(cfg.browser);
125+if (!browser) {
126+return false;
127+}
128+if (normalizeOptionalString(browser.defaultProfile) === LEGACY_CLAWD_BROWSER_PROFILE_NAME) {
129+return true;
130+}
131+132+const configuredProfiles = asRecord(browser.profiles);
133+if (!configuredProfiles) {
134+return false;
135+}
136+if (Object.prototype.hasOwnProperty.call(configuredProfiles, LEGACY_CLAWD_BROWSER_PROFILE_NAME)) {
137+return true;
138+}
139+140+for (const rawProfile of Object.values(configuredProfiles)) {
141+const profile = asRecord(rawProfile);
142+const userDataDir = normalizeOptionalString(profile?.userDataDir);
143+if (userDataDir && isSameOrChildPath(resolveUserPath(userDataDir), legacyProfileDir)) {
144+return true;
145+}
146+}
147+return false;
148+}
149+150+export function detectLegacyClawdBrowserProfileResidue(
151+cfg: OpenClawConfig,
152+deps?: BrowserDoctorFilesystemDeps,
153+): LegacyClawdBrowserProfileResidue | null {
154+const configDir = deps?.configDir ?? CONFIG_DIR;
155+const legacyProfileDir = resolveManagedBrowserProfileDir(
156+configDir,
157+LEGACY_CLAWD_BROWSER_PROFILE_NAME,
158+);
159+const legacyUserDataDir = resolveManagedBrowserUserDataDir(
160+configDir,
161+LEGACY_CLAWD_BROWSER_PROFILE_NAME,
162+);
163+const pathExists = deps?.pathExists ?? fs.existsSync;
164+if (!pathExists(legacyProfileDir) && !pathExists(legacyUserDataDir)) {
165+return null;
166+}
167+168+if (isLegacyClawdProfileConfigured(cfg, legacyProfileDir)) {
169+return null;
170+}
171+172+const resolved = resolveBrowserConfig(cfg.browser, cfg);
173+const defaultProfile = resolved.profiles[resolved.defaultProfile];
174+if (
175+resolved.defaultProfile !== DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME ||
176+defaultProfile?.driver === "existing-session"
177+) {
178+return null;
179+}
180+181+return {
182+ legacyProfileDir,
183+ legacyUserDataDir,
184+canonicalUserDataDir: resolveManagedBrowserUserDataDir(
185+configDir,
186+DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME,
187+),
188+};
189+}
190+191+function formatLegacyClawdBrowserProfileResidueNote(
192+residue: LegacyClawdBrowserProfileResidue,
193+): string {
194+return [
195+`- Legacy managed browser profile residue was found at ${residue.legacyProfileDir}.`,
196+`- The canonical OpenClaw-managed browser profile is ${residue.canonicalUserDataDir}.`,
197+`- If no browser is using the legacy profile, run ${formatCliCommand("openclaw doctor --fix")} to archive it safely instead of deleting it in place.`,
198+].join("\n");
199+}
200+88201export async function noteChromeMcpBrowserReadiness(
89202cfg: OpenClawConfig,
90203deps?: {
@@ -95,6 +208,8 @@ export async function noteChromeMcpBrowserReadiness(
95208resolveManagedExecutable?: typeof resolveBrowserExecutableForPlatform;
96209resolveChromeExecutable?: (platform: NodeJS.Platform) => { path: string } | null;
97210readVersion?: (executablePath: string) => string | null;
211+configDir?: string;
212+pathExists?: (targetPath: string) => boolean;
98213},
99214) {
100215const noteFn = deps?.noteFn ?? note;
@@ -109,6 +224,13 @@ export async function noteChromeMcpBrowserReadiness(
109224const managedProfiles = collectManagedProfiles(cfg);
110225const managedProfileLabel = managedProfiles.map((profile) => profile.name).join(", ");
111226const resolved = resolveBrowserConfig(cfg.browser, cfg);
227+const legacyClawdResidue = detectLegacyClawdBrowserProfileResidue(cfg, {
228+configDir: deps?.configDir,
229+pathExists: deps?.pathExists,
230+});
231+if (legacyClawdResidue) {
232+noteFn(formatLegacyClawdBrowserProfileResidueNote(legacyClawdResidue), "Browser");
233+}
112234const browserExecutable =
113235managedProfiles.length > 0 ? resolveManagedExecutable(resolved, platform) : null;
114236const missingDisplay =
@@ -225,3 +347,35 @@ export async function noteChromeMcpBrowserReadiness(
225347226348noteFn(lines.join("\n"), "Browser");
227349}
350+351+export async function maybeArchiveLegacyClawdBrowserProfileResidue(
352+cfg: OpenClawConfig,
353+deps?: BrowserDoctorFilesystemDeps,
354+): Promise<{ changes: string[]; warnings: string[] }> {
355+const residue = detectLegacyClawdBrowserProfileResidue(cfg, deps);
356+if (!residue) {
357+return { changes: [], warnings: [] };
358+}
359+360+const move = deps?.movePathToTrash ?? movePathToTrash;
361+try {
362+const archivedPath = await move(residue.legacyProfileDir);
363+return {
364+changes: [
365+[
366+"Archived legacy clawd managed browser profile residue.",
367+`- legacy profile: ${residue.legacyProfileDir}`,
368+`- canonical profile: ${residue.canonicalUserDataDir}`,
369+`- archived at: ${archivedPath}`,
370+].join("\n"),
371+],
372+warnings: [],
373+};
374+} catch (error) {
375+const message = error instanceof Error ? error.message : String(error);
376+return {
377+changes: [],
378+warnings: [`Legacy clawd browser profile residue could not be archived: ${message}`],
379+};
380+}
381+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。