fix: close window tool (#262)

* fix: close window tool

* fix: close window tool
This commit is contained in:
shivammittal274
2026-01-23 22:35:10 +05:30
committed by GitHub
parent aeb1fb37e7
commit 8354ad5ab5
6 changed files with 88 additions and 31 deletions

View File

@@ -21,6 +21,8 @@ if (useBrowserOS) {
}
if (env.BROWSEROS_CDP_PORT) {
chromiumArgs.push(`--browseros-cdp-port=${env.BROWSEROS_CDP_PORT}`)
// Enable HTTP-based CDP so the server can connect
chromiumArgs.push(`--remote-debugging-port=${env.BROWSEROS_CDP_PORT}`)
}
if (env.BROWSEROS_SERVER_PORT) {
chromiumArgs.push(`--browseros-mcp-port=${env.BROWSEROS_SERVER_PORT}`)

View File

@@ -228,3 +228,50 @@ export const handleDialog = defineTool({
response.setIncludePages(true)
},
})
export const closeWindow = defineTool({
name: 'browser_close_window',
description: `Close a browser window by its windowId. Bypasses beforeunload dialogs.`,
annotations: {
category: ToolCategories.TAB_MANAGEMENT,
readOnlyHint: false,
},
schema: {
windowId: z.number().describe('The ID of the window to close'),
},
handler: async (request, response, context) => {
const { windowId } = request.params
const targets = context.browser.targets()
let closedCount = 0
for (const target of targets) {
try {
const targetId = (target as unknown as { _targetId?: string })._targetId
if (!targetId) continue
const session = await target.createCDPSession()
try {
const result = await session.send('Browser.getWindowForTarget', {
targetId,
})
if (result.windowId === windowId) {
await session.send('Target.closeTarget', { targetId })
closedCount++
}
} finally {
await session.detach().catch(() => {})
}
} catch {
// Target may already be closed or not support CDP session
}
}
if (closedCount === 0) {
throw new Error(`No targets found for window ${windowId}`)
}
response.appendResponseLine(`Closed window ${windowId}`)
response.setIncludePages(true)
},
})

View File

@@ -6,13 +6,13 @@ import type { ToolDefinition } from '../types/tool-definition'
import * as consoleTools from './console'
import * as networkTools from './network'
import { closeWindow } from './pages'
/**
* All available CDP-based browser automation tools
*/
// biome-ignore lint/suspicious/noExplicitAny: heterogeneous tool collection requires any
export const allCdpTools: Array<ToolDefinition<any>> = [
//FIXME: nikhil - figure out the better wway to enable/disable tools
...Object.values(consoleTools),
// ...Object.values(emulationTools),
// ...Object.values(inputTools),
@@ -23,6 +23,8 @@ export const allCdpTools: Array<ToolDefinition<any>> = [
// ...Object.values(screenshotTools),
// ...Object.values(scriptTools),
// ...Object.values(snapshotTools),
// CDP-based window close (bypasses beforeunload)
closeWindow,
]
// Re-export individual tool modules for selective imports

View File

@@ -55,8 +55,8 @@ export {
ungroupTabs,
updateTabGroup,
} from './tools/tab-management'
// Window Management
export { closeWindow, createWindow } from './tools/window-management'
// Window Management (createWindow uses chrome.windows.create for actual windows)
export { createWindow } from './tools/window-management'
// Types
export type { Context } from './types/context'
export type { ImageContentData, Response } from './types/response'
@@ -104,9 +104,9 @@ import {
ungroupTabs,
updateTabGroup,
} from './tools/tab-management'
import { closeWindow, createWindow } from './tools/window-management'
import { createWindow } from './tools/window-management'
// Array export for convenience (37 tools total)
// Array export for convenience (36 tools total)
export const allControllerTools = [
getActiveTab,
listTabs,
@@ -145,5 +145,4 @@ export const allControllerTools = [
searchHistory,
getRecentHistory,
createWindow,
closeWindow,
]

View File

@@ -49,22 +49,3 @@ export const createWindow = defineTool<z.ZodRawShape, Context, Response>({
response.addStructuredContent('tabId', data.tabId)
},
})
export const closeWindow = defineTool<z.ZodRawShape, Context, Response>({
name: 'browser_close_window',
description: 'Close a browser window by its windowId.',
annotations: {
category: ToolCategories.TAB_MANAGEMENT,
readOnlyHint: false,
},
schema: {
windowId: z.coerce.number().describe('The ID of the window to close'),
},
handler: async (request, response, context) => {
const { windowId } = request.params as { windowId: number }
await context.executeAction('closeWindow', { windowId })
response.appendResponseLine(`Closed window ${windowId}`)
},
})

View File

@@ -53,6 +53,27 @@ function killPort(port: number): void {
})
}
async function waitForCdp(cdpPort: number, maxAttempts = 60): Promise<void> {
for (let i = 0; i < maxAttempts; i++) {
try {
const response = await fetch(`http://127.0.0.1:${cdpPort}/json/version`, {
signal: AbortSignal.timeout(1000),
})
if (response.ok) {
return
}
} catch {
// CDP not ready yet
}
await new Promise((resolve) => setTimeout(resolve, 500))
}
log(
'server',
COLORS.server,
`Warning: CDP not available after ${maxAttempts * 0.5}s, starting server anyway`,
)
}
function isPortAvailable(port: number): Promise<boolean> {
return new Promise((resolve) => {
const server = createNetServer()
@@ -185,19 +206,24 @@ async function main() {
const env = createEnvWithMutablePorts(ports, userDataDir)
log('server', COLORS.server, 'Starting server...')
log('agent', COLORS.agent, 'Starting agent...\n')
// Start agent first (launches browser)
log('agent', COLORS.agent, 'Starting agent (browser)...\n')
const serverProc = spawn({
cmd: ['bun', 'run', '--filter', '@browseros/server', 'start'],
const agentProc = spawn({
cmd: ['bun', 'run', '--filter', '@browseros/agent', 'dev'],
cwd: MONOREPO_ROOT,
stdout: 'pipe',
stderr: 'pipe',
env,
})
const agentProc = spawn({
cmd: ['bun', 'run', '--filter', '@browseros/agent', 'dev'],
// Wait for CDP to be available before starting server
log('server', COLORS.server, 'Waiting for CDP to be ready...')
await waitForCdp(ports.cdp)
log('server', COLORS.server, 'CDP ready, starting server...\n')
const serverProc = spawn({
cmd: ['bun', 'run', '--filter', '@browseros/server', 'start'],
cwd: MONOREPO_ROOT,
stdout: 'pipe',
stderr: 'pipe',