From 99aa184cfea5eba63a2158a76a4abcd12a0e1b5c Mon Sep 17 00:00:00 2001 From: Derek Meegan Date: Sun, 29 Mar 2026 14:14:35 -0700 Subject: [PATCH 1/3] Add `browse dialog` command for auto-handling JS dialogs Adds dialog accept/dismiss/off modes via CDP Page.javascriptDialogOpening and Page.handleJavaScriptDialog. Handles alert, confirm, prompt, and beforeunload dialogs. Includes dialog_history command to inspect handled dialogs with type, message, and timestamp. Usage: browse dialog accept # auto-accept all dialogs browse dialog dismiss # auto-dismiss all dialogs browse dialog off # stop auto-handling browse dialog_history # show handled dialog log Co-Authored-By: Claude Opus 4.6 (1M context) --- .changeset/add-dialog-command.md | 5 ++ packages/cli/src/index.ts | 88 ++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 .changeset/add-dialog-command.md diff --git a/.changeset/add-dialog-command.md b/.changeset/add-dialog-command.md new file mode 100644 index 000000000..50a23a36c --- /dev/null +++ b/.changeset/add-dialog-command.md @@ -0,0 +1,5 @@ +--- +"@browserbasehq/browse-cli": minor +--- + +Add `browse dialog ` command to auto-handle JavaScript dialogs (alert, confirm, prompt). Modes: `accept`, `dismiss`, `off`. Also adds `browse dialog_history` to inspect handled dialogs. diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index be6081fb0..3581a24c2 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -854,6 +854,18 @@ let networkCounter = 0; let networkSession: string | null = null; const pendingRequests = new Map(); +// ==================== DIALOG HANDLING STATE ==================== + +type DialogMode = "accept" | "dismiss" | "off"; +let dialogMode: DialogMode = "off"; +let dialogHandlerInstalled = false; +const dialogHistory: Array<{ + type: string; + message: string; + handled: DialogMode; + timestamp: string; +}> = []; + /** Sanitize a string for use in a filename */ function sanitizeForFilename(str: string, maxLen: number = 30): string { return str @@ -1582,6 +1594,45 @@ async function executeCommand( } } + // Dialog handling + case "dialog": { + const [mode] = args as [DialogMode]; + dialogMode = mode; + + if (mode !== "off" && !dialogHandlerInstalled && page) { + const cdpSession = page.mainFrame().session; + cdpSession.on( + "Page.javascriptDialogOpening", + async (params: { + type: string; + message: string; + defaultPrompt?: string; + }) => { + if (dialogMode === "off") return; + dialogHistory.push({ + type: params.type, + message: params.message, + handled: dialogMode, + timestamp: new Date().toISOString(), + }); + await cdpSession.send("Page.handleJavaScriptDialog", { + accept: dialogMode === "accept", + promptText: + dialogMode === "accept" + ? (params.defaultPrompt ?? "") + : undefined, + }); + }, + ); + dialogHandlerInstalled = true; + } + + return { mode: dialogMode, handlerInstalled: dialogHandlerInstalled }; + } + case "dialog_history": { + return { dialogs: dialogHistory }; + } + // Daemon control case "stop": { process.nextTick(() => { @@ -2756,6 +2807,43 @@ networkCmd } }); +// ==================== DIALOG HANDLING ==================== + +program + .command("dialog ") + .description( + "Set dialog handling mode: accept, dismiss, or off.\n" + + "When enabled, alert/confirm/prompt dialogs are auto-handled.", + ) + .action(async (mode: string) => { + const opts = program.opts(); + if (mode !== "accept" && mode !== "dismiss" && mode !== "off") { + console.error("Error: mode must be accept, dismiss, or off"); + process.exit(1); + } + try { + const result = await runCommand("dialog", [mode]); + output(result, opts.json ?? false); + } catch (e) { + console.error("Error:", e instanceof Error ? e.message : e); + process.exit(1); + } + }); + +program + .command("dialog_history") + .description("Show history of handled dialogs") + .action(async () => { + const opts = program.opts(); + try { + const result = await runCommand("dialog_history", []); + output(result, opts.json ?? false); + } catch (e) { + console.error("Error:", e instanceof Error ? e.message : e); + process.exit(1); + } + }); + // ==================== RUN ==================== program.parse(); From deae45cdbfb10d62e323d52aa327380d37e784a3 Mon Sep 17 00:00:00 2001 From: Derek Meegan Date: Sun, 29 Mar 2026 14:37:38 -0700 Subject: [PATCH 2/3] fix: add error isolation and per-session listener tracking for dialogs - Wrap Page.handleJavaScriptDialog in try/catch to prevent unhandled rejections from crashing the daemon when dialogs close externally or navigation occurs before the response resolves. - Replace global boolean with WeakSet to track listener installation per CDP session, so dialog handling works correctly across tab switches and new pages. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cli/src/index.ts | 60 +++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 3581a24c2..64953d204 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -858,7 +858,7 @@ const pendingRequests = new Map(); type DialogMode = "accept" | "dismiss" | "off"; let dialogMode: DialogMode = "off"; -let dialogHandlerInstalled = false; +const dialogInstalledSessions = new WeakSet(); const dialogHistory: Array<{ type: string; message: string; @@ -1599,35 +1599,41 @@ async function executeCommand( const [mode] = args as [DialogMode]; dialogMode = mode; - if (mode !== "off" && !dialogHandlerInstalled && page) { + if (mode !== "off" && page) { const cdpSession = page.mainFrame().session; - cdpSession.on( - "Page.javascriptDialogOpening", - async (params: { - type: string; - message: string; - defaultPrompt?: string; - }) => { - if (dialogMode === "off") return; - dialogHistory.push({ - type: params.type, - message: params.message, - handled: dialogMode, - timestamp: new Date().toISOString(), - }); - await cdpSession.send("Page.handleJavaScriptDialog", { - accept: dialogMode === "accept", - promptText: - dialogMode === "accept" - ? (params.defaultPrompt ?? "") - : undefined, - }); - }, - ); - dialogHandlerInstalled = true; + if (!dialogInstalledSessions.has(cdpSession)) { + cdpSession.on( + "Page.javascriptDialogOpening", + async (params: { + type: string; + message: string; + defaultPrompt?: string; + }) => { + if (dialogMode === "off") return; + dialogHistory.push({ + type: params.type, + message: params.message, + handled: dialogMode, + timestamp: new Date().toISOString(), + }); + try { + await cdpSession.send("Page.handleJavaScriptDialog", { + accept: dialogMode === "accept", + promptText: + dialogMode === "accept" + ? (params.defaultPrompt ?? "") + : undefined, + }); + } catch { + // Dialog may have been closed by navigation or externally + } + }, + ); + dialogInstalledSessions.add(cdpSession); + } } - return { mode: dialogMode, handlerInstalled: dialogHandlerInstalled }; + return { mode: dialogMode }; } case "dialog_history": { return { dialogs: dialogHistory }; From 03e5aca386cf3d7146f6223d2bff66c04358e94e Mon Sep 17 00:00:00 2001 From: Derek Meegan Date: Sun, 29 Mar 2026 16:11:37 -0700 Subject: [PATCH 3/3] refactor: consolidate dialog into subcommand group Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cli/src/index.ts | 49 +++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 64953d204..595f47904 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -2815,20 +2815,31 @@ networkCmd // ==================== DIALOG HANDLING ==================== -program - .command("dialog ") - .description( - "Set dialog handling mode: accept, dismiss, or off.\n" + - "When enabled, alert/confirm/prompt dialogs are auto-handled.", - ) - .action(async (mode: string) => { +const dialogCmd = program + .command("dialog") + .description("Dialog handling commands"); + +dialogCmd + .command("accept") + .description("Auto-accept all dialogs (alerts, confirms, prompts)") + .action(async () => { const opts = program.opts(); - if (mode !== "accept" && mode !== "dismiss" && mode !== "off") { - console.error("Error: mode must be accept, dismiss, or off"); + try { + const result = await runCommand("dialog", ["accept"]); + output(result, opts.json ?? false); + } catch (e) { + console.error("Error:", e instanceof Error ? e.message : e); process.exit(1); } + }); + +dialogCmd + .command("dismiss") + .description("Auto-dismiss all dialogs") + .action(async () => { + const opts = program.opts(); try { - const result = await runCommand("dialog", [mode]); + const result = await runCommand("dialog", ["dismiss"]); output(result, opts.json ?? false); } catch (e) { console.error("Error:", e instanceof Error ? e.message : e); @@ -2836,8 +2847,22 @@ program } }); -program - .command("dialog_history") +dialogCmd + .command("off") + .description("Disable automatic dialog handling") + .action(async () => { + const opts = program.opts(); + try { + const result = await runCommand("dialog", ["off"]); + output(result, opts.json ?? false); + } catch (e) { + console.error("Error:", e instanceof Error ? e.message : e); + process.exit(1); + } + }); + +dialogCmd + .command("history") .description("Show history of handled dialogs") .action(async () => { const opts = program.opts();