mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:20:44 +00:00
fix(browser): report attach-only profile transport truthfully
# Conflicts: # extensions/browser/src/browser/routes/basic.ts
This commit is contained in:
@@ -8,7 +8,11 @@ vi.mock("../chrome-mcp.js", () => ({
|
||||
const { BrowserProfileUnavailableError } = await import("../errors.js");
|
||||
const { registerBrowserBasicRoutes } = await import("./basic.js");
|
||||
|
||||
function createExistingSessionProfileState(params?: { isHttpReachable?: () => Promise<boolean> }) {
|
||||
function createExistingSessionProfileState(params?: {
|
||||
isHttpReachable?: () => Promise<boolean>;
|
||||
isTransportAvailable?: () => Promise<boolean>;
|
||||
isReachable?: () => Promise<boolean>;
|
||||
}) {
|
||||
return {
|
||||
resolved: {
|
||||
enabled: true,
|
||||
@@ -31,7 +35,8 @@ function createExistingSessionProfileState(params?: { isHttpReachable?: () => Pr
|
||||
attachOnly: true,
|
||||
},
|
||||
isHttpReachable: params?.isHttpReachable ?? (async () => true),
|
||||
isReachable: async () => true,
|
||||
isTransportAvailable: params?.isTransportAvailable ?? (async () => true),
|
||||
isReachable: params?.isReachable ?? (async () => true),
|
||||
}) as never,
|
||||
};
|
||||
}
|
||||
@@ -86,4 +91,22 @@ describe("basic browser routes", () => {
|
||||
pid: 4321,
|
||||
});
|
||||
});
|
||||
|
||||
it("treats attach-only profiles as running when transport is available even if page reachability is false", async () => {
|
||||
const response = await callBasicRouteWithState({
|
||||
state: createExistingSessionProfileState({
|
||||
isTransportAvailable: async () => true,
|
||||
isReachable: async () => false,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toMatchObject({
|
||||
profile: "chrome-live",
|
||||
driver: "existing-session",
|
||||
transport: "chrome-mcp",
|
||||
running: true,
|
||||
cdpReady: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,13 +61,15 @@ async function buildBrowserStatus(req: BrowserRequest, ctx: BrowserRouteContext)
|
||||
throw new BrowserError(profileCtx.error, profileCtx.status);
|
||||
}
|
||||
|
||||
const [cdpHttp, cdpReady] = await Promise.all([
|
||||
profileCtx.isHttpReachable(300),
|
||||
profileCtx.isReachable(600),
|
||||
]);
|
||||
const capabilities = getBrowserProfileCapabilities(profileCtx.profile);
|
||||
const [cdpHttp, cdpReady] = capabilities.usesChromeMcp
|
||||
? await (async () => {
|
||||
const ready = await profileCtx.isTransportAvailable(600);
|
||||
return [ready, ready] as const;
|
||||
})()
|
||||
: await Promise.all([profileCtx.isHttpReachable(300), profileCtx.isTransportAvailable(600)]);
|
||||
|
||||
const profileState = current.profiles.get(profileCtx.profile.name);
|
||||
const capabilities = getBrowserProfileCapabilities(profileCtx.profile);
|
||||
let detectedBrowser: string | null = null;
|
||||
let detectedExecutablePath: string | null = null;
|
||||
let detectError: string | null = null;
|
||||
|
||||
@@ -46,6 +46,7 @@ type AvailabilityDeps = {
|
||||
|
||||
type AvailabilityOps = {
|
||||
isHttpReachable: (timeoutMs?: number) => Promise<boolean>;
|
||||
isTransportAvailable: (timeoutMs?: number) => Promise<boolean>;
|
||||
isReachable: (timeoutMs?: number) => Promise<boolean>;
|
||||
ensureBrowserAvailable: () => Promise<void>;
|
||||
stopRunningBrowser: () => Promise<{ stopped: boolean }>;
|
||||
@@ -87,9 +88,17 @@ export function createProfileAvailability({
|
||||
);
|
||||
};
|
||||
|
||||
const isTransportAvailable = async (timeoutMs?: number) => {
|
||||
if (capabilities.usesChromeMcp) {
|
||||
await ensureChromeMcpAvailable(profile.name, profile.userDataDir);
|
||||
return true;
|
||||
}
|
||||
return await isReachable(timeoutMs);
|
||||
};
|
||||
|
||||
const isHttpReachable = async (timeoutMs?: number) => {
|
||||
if (capabilities.usesChromeMcp) {
|
||||
return await isReachable(timeoutMs);
|
||||
return await isTransportAvailable(timeoutMs);
|
||||
}
|
||||
const { httpTimeoutMs } = resolveTimeouts(timeoutMs);
|
||||
return await isChromeReachable(profile.cdpUrl, httpTimeoutMs, getCdpReachabilityPolicy());
|
||||
@@ -341,6 +350,7 @@ export function createProfileAvailability({
|
||||
|
||||
return {
|
||||
isHttpReachable,
|
||||
isTransportAvailable,
|
||||
isReachable,
|
||||
ensureBrowserAvailable,
|
||||
stopRunningBrowser,
|
||||
|
||||
@@ -85,6 +85,24 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("browser server-context existing-session profile", () => {
|
||||
it("reports attach-only profiles as running when the MCP session is available but no page is selected", async () => {
|
||||
fs.mkdirSync("/tmp/brave-profile", { recursive: true });
|
||||
const state = makeState();
|
||||
const ctx = createBrowserRouteContext({ getState: () => state });
|
||||
|
||||
vi.mocked(chromeMcp.ensureChromeMcpAvailable).mockResolvedValueOnce();
|
||||
vi.mocked(chromeMcp.listChromeMcpTabs).mockRejectedValueOnce(new Error("No page selected"));
|
||||
|
||||
await expect(ctx.listProfiles()).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
name: "chrome-live",
|
||||
transport: "chrome-mcp",
|
||||
running: true,
|
||||
tabCount: 0,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("routes tab operations through the Chrome MCP backend", async () => {
|
||||
fs.mkdirSync("/tmp/brave-profile", { recursive: true });
|
||||
const state = makeState();
|
||||
|
||||
@@ -79,14 +79,19 @@ function createProfileContext(
|
||||
getProfileState,
|
||||
});
|
||||
|
||||
const { ensureBrowserAvailable, isHttpReachable, isReachable, stopRunningBrowser } =
|
||||
createProfileAvailability({
|
||||
opts,
|
||||
profile,
|
||||
state,
|
||||
getProfileState,
|
||||
setProfileRunning,
|
||||
});
|
||||
const {
|
||||
ensureBrowserAvailable,
|
||||
isHttpReachable,
|
||||
isTransportAvailable,
|
||||
isReachable,
|
||||
stopRunningBrowser,
|
||||
} = createProfileAvailability({
|
||||
opts,
|
||||
profile,
|
||||
state,
|
||||
getProfileState,
|
||||
setProfileRunning,
|
||||
});
|
||||
|
||||
const { ensureTabAvailable, focusTab, closeTab } = createProfileSelectionOps({
|
||||
profile,
|
||||
@@ -110,6 +115,7 @@ function createProfileContext(
|
||||
ensureBrowserAvailable,
|
||||
ensureTabAvailable,
|
||||
isHttpReachable,
|
||||
isTransportAvailable,
|
||||
isReachable,
|
||||
listTabs,
|
||||
openTab,
|
||||
@@ -173,9 +179,9 @@ export function createBrowserRouteContext(opts: ContextOptions): BrowserRouteCon
|
||||
|
||||
if (capabilities.usesChromeMcp) {
|
||||
try {
|
||||
running = await profileCtx.isReachable(300);
|
||||
running = await profileCtx.isTransportAvailable(300);
|
||||
if (running) {
|
||||
const tabs = await profileCtx.listTabs();
|
||||
const tabs = await profileCtx.listTabs().catch(() => [] as BrowserTab[]);
|
||||
tabCount = tabs.filter((t) => t.type === "page").length;
|
||||
}
|
||||
} catch {
|
||||
@@ -251,6 +257,7 @@ export function createBrowserRouteContext(opts: ContextOptions): BrowserRouteCon
|
||||
ensureBrowserAvailable: () => getDefaultContext().ensureBrowserAvailable(),
|
||||
ensureTabAvailable: (targetId) => getDefaultContext().ensureTabAvailable(targetId),
|
||||
isHttpReachable: (timeoutMs) => getDefaultContext().isHttpReachable(timeoutMs),
|
||||
isTransportAvailable: (timeoutMs) => getDefaultContext().isTransportAvailable(timeoutMs),
|
||||
isReachable: (timeoutMs) => getDefaultContext().isReachable(timeoutMs),
|
||||
listTabs: () => getDefaultContext().listTabs(),
|
||||
openTab: (url, opts) => getDefaultContext().openTab(url, opts),
|
||||
|
||||
@@ -36,6 +36,7 @@ type BrowserProfileActions = {
|
||||
ensureBrowserAvailable: () => Promise<void>;
|
||||
ensureTabAvailable: (targetId?: string) => Promise<BrowserTab>;
|
||||
isHttpReachable: (timeoutMs?: number) => Promise<boolean>;
|
||||
isTransportAvailable: (timeoutMs?: number) => Promise<boolean>;
|
||||
isReachable: (timeoutMs?: number) => Promise<boolean>;
|
||||
listTabs: () => Promise<BrowserTab[]>;
|
||||
openTab: (url: string, opts?: { label?: string }) => Promise<BrowserTab>;
|
||||
|
||||
Reference in New Issue
Block a user