feat(extension): auto-close automation window after command completes

- Add 'close-window' action to extension protocol and background.ts
- Add Page.closeWindow() method to send close-window command
- browserSession() now closes automation window in cleanup
- Remove domain pre-navigation + 2s wait from main.ts (CDP handles
  cross-domain cookies natively, no same-origin workaround needed)
- Net effect: commands run faster, no stale windows left behind
This commit is contained in:
jackwener committed 2026-03-19 23:59:31 +08:00
1 parent fde618063f
commit 2f6d28a3e9
7 files changed
+46 -13

No files matched your search

+14
View File
@@ -219,6 +219,7 @@ async function handleCommand(cmd) {
case "tabs": return await handleTabs(cmd);
case "cookies": return await handleCookies(cmd);
case "screenshot": return await handleScreenshot(cmd);
case "close-window": return await handleCloseWindow(cmd);
default: return {
id: cmd.id,
ok: false,
@@ -447,4 +448,17 @@ async function handleScreenshot(cmd) {
};
}
}
async function handleCloseWindow(cmd) {
if (automationWindowId !== null) {
try {
await chrome.windows.remove(automationWindowId);
} catch {}
automationWindowId = null;
}
return {
id: cmd.id,
ok: true,
data: { closed: true }
};
}
//#endregion
+14
View File
@@ -165,6 +165,8 @@ async function handleCommand(cmd: Command): Promise<Result> {
return await handleCookies(cmd);
case 'screenshot':
return await handleScreenshot(cmd);
case 'close-window':
return await handleCloseWindow(cmd);
default:
return { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
}
@@ -332,3 +334,15 @@ async function handleScreenshot(cmd: Command): Promise<Result> {
return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
}
}
async function handleCloseWindow(cmd: Command): Promise<Result> {
if (automationWindowId !== null) {
try {
await chrome.windows.remove(automationWindowId);
} catch {
// Window may already be closed
}
automationWindowId = null;
}
return { id: cmd.id, ok: true, data: { closed: true } };
}
+1 -1
View File
@@ -5,7 +5,7 @@
* Everything else is just JS code sent via 'exec'.
*/
export type Action = 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot';
export type Action = 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window';
export interface Command {
/** Unique request ID */
+1 -1
View File
@@ -15,7 +15,7 @@ function generateId(): string {
export interface DaemonCommand {
id: string;
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot';
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window';
tabId?: number;
code?: string;
url?: string;
+9
View File
@@ -37,6 +37,15 @@ export class Page implements IPage {
}
}
/** Close the automation window in the extension */
async closeWindow(): Promise<void> {
try {
await sendCommand('close-window', {});
} catch {
// Window may already be closed or daemon may be down
}
}
async evaluate(js: string): Promise<any> {
const code = wrapForEval(js);
return sendCommand('exec', { code, ...this._tabOpt() });
-6
View File
@@ -193,12 +193,6 @@ for (const [, cmd] of registry) {
let result: any;
if (cmd.browser) {
result = await browserSession(BrowserBridge, async (page) => {
// Cookie/header strategies require same-origin context for credentialed fetch.
// In CDP mode the active tab may be on an unrelated domain, causing CORS failures.
// Navigate to the command's domain first (mirrors cascade command behavior).
if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
try { await page.goto(`https://${cmd.domain}`); await page.wait(2); } catch {}
}
return runWithTimeout(executeCommand(cmd, page, kwargs, actionOpts.verbose), { timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT, label: fullName(cmd) });
});
} else { result = await executeCommand(cmd, null, kwargs, actionOpts.verbose); }
+7 -5
View File
@@ -1,8 +1,5 @@
/**
* Runtime utilities: timeouts and browser session management.
*/
import type { IPage } from './types.js';
import type { Page } from './browser/page.js';
export const DEFAULT_BROWSER_CONNECT_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_CONNECT_TIMEOUT ?? '30', 10);
export const DEFAULT_BROWSER_COMMAND_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_COMMAND_TIMEOUT ?? '60', 10);
@@ -45,7 +42,12 @@ export async function browserSession<T>(
const mcp = new BrowserFactory();
try {
const page = await mcp.connect({ timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT });
return await fn(page);
const result = await fn(page);
// Close the automation window after command completes
if (typeof (page as Page).closeWindow === 'function') {
await (page as Page).closeWindow();
}
return result;
} finally {
await mcp.close().catch(() => {});
}