feat: dashboard provider support and session creation improvements (#1092)

Add provider icons and session creation from the dashboard UI.
Sessions can now be created with cloud providers (Browserbase,
Browserless, Browser Use, Kernel) in addition to local engines.

CLI changes:
- Track provider via .provider files alongside .engine files
- Add WaitUntil::None variant to skip lifecycle event waits for providers
- Auto-set waitUntil=none when --provider is used with navigate
- Fix Browser Use: use direct WSS connection (wss://connect.browser-use.com)
- Add connect_cdp_direct for providers with page-level CDP proxies
- Fix resolve_cdp_url to convert https:// provider URLs to wss://
- Treat empty CDP session_id as None (omit from protocol messages)
- Fix Browserbase: send explicit JSON body + Content-Type header
- Increase CDP connect timeout to 25s for remote providers
- Clean up .provider files on session close

Dashboard changes:
- Show provider or engine icon per session in sidebar
- New session dialog with unified engine/provider selector grid
- Async session creation with loading state and error display
- Kill zombie daemons on provider connection failure
- Parse CLI JSON error output for user-friendly messages
- Default new session URL to https://agent-browser.dev
This commit is contained in:
Chris Tate
2026-03-30 18:35:12 -05:00
committed by GitHub
parent f8bc8b368a
commit 40fdb4284d
22 changed files with 274 additions and 107 deletions
+34 -7
View File
@@ -4,7 +4,7 @@ import { atom } from "jotai";
import { useCallback, useEffect, useRef } from "react";
import { useAtomCallback } from "jotai/utils";
import type { SessionInfo } from "@/types";
import { execCommand, killSession, sessionArgs } from "@/lib/exec";
import { type ExecResult, execCommand, killSession, sessionArgs } from "@/lib/exec";
import { tabCacheAtom, engineCacheAtom } from "@/store/tabs";
import { streamTabsAtom, streamEngineAtom } from "@/store/stream";
@@ -35,7 +35,7 @@ export const activePortAtom = atom(getPort());
export const polledSessionsAtom = atom<SessionInfo[]>([]);
export const pendingSessionsAtom = atom<{ session: string; engine: string }[]>(
export const pendingSessionsAtom = atom<{ session: string; engine: string; provider?: string }[]>(
[],
);
@@ -57,6 +57,7 @@ export const sessionsAtom = atom((get) => {
session: p.session,
port: 0,
engine: p.engine,
provider: p.provider,
pending: true as const,
}));
const merged = polled.map((s) =>
@@ -88,16 +89,42 @@ export const activeExtensionsAtom = atom((get) => {
export const createSessionAtom = atom(
null,
(
async (
_get,
set,
{ name, engine }: { name: string; engine: string },
) => {
set(pendingSessionsAtom, (prev) => [...prev, { session: name, engine }]);
execCommand(["--session", name, "--engine", engine, "open", "about:blank"]);
{ name, engine, provider }: { name: string; engine: string; provider?: string },
): Promise<string | null> => {
set(pendingSessionsAtom, (prev) => [...prev, { session: name, engine, provider }]);
const args = ["--session", name];
if (provider) {
args.push("--provider", provider);
} else {
args.push("--engine", engine);
}
args.push("open", "https://agent-browser.dev");
const result = await execCommand(args);
if (!result.success) {
set(pendingSessionsAtom, (prev) => prev.filter((p) => p.session !== name));
killSession(name);
return parseExecError(result) || "Failed to create session";
}
return null;
},
);
function parseExecError(result: ExecResult): string {
if (result.stderr) return result.stderr;
if (result.stdout) {
try {
const json = JSON.parse(result.stdout);
if (json.error) return json.error;
} catch {
// stdout wasn't JSON
}
}
return "";
}
export const closeSessionAtom = atom(null, (get, set, port: number) => {
const sessions = get(sessionsAtom);
const s = sessions.find((x) => x.port === port)?.session;