fix(extension): use idle-timeout for automation window lifecycle
Replace eager close-window (which caused race conditions when parallel commands shared the window) with an idle-based timer: - Window auto-closes 30s after the last command completes - Each incoming command resets the idle timer - Consecutive commands reuse the same window (faster) - No race conditions with parallel execution - Close-window action kept for explicit cleanup if needed
This commit is contained in:
1 parent
2f6d28a3e9
commit
b5c1b242e2
3 files changed
+43
-7
No files matched your search
Vendored
+20
@@ -169,6 +169,21 @@ function scheduleReconnect() {
|
||||
}, delay);
|
||||
}
|
||||
var automationWindowId = null;
|
||||
var windowIdleTimer = null;
|
||||
var WINDOW_IDLE_TIMEOUT = 3e4;
|
||||
function resetWindowIdleTimer() {
|
||||
if (windowIdleTimer) clearTimeout(windowIdleTimer);
|
||||
windowIdleTimer = setTimeout(async () => {
|
||||
if (automationWindowId !== null) {
|
||||
try {
|
||||
await chrome.windows.remove(automationWindowId);
|
||||
console.log(`[opencli] Automation window ${automationWindowId} closed (idle timeout)`);
|
||||
} catch {}
|
||||
automationWindowId = null;
|
||||
}
|
||||
windowIdleTimer = null;
|
||||
}, WINDOW_IDLE_TIMEOUT);
|
||||
}
|
||||
/** Get or create the dedicated automation window. */
|
||||
async function getAutomationWindow() {
|
||||
if (automationWindowId !== null) try {
|
||||
@@ -191,6 +206,10 @@ chrome.windows.onRemoved.addListener((windowId) => {
|
||||
if (windowId === automationWindowId) {
|
||||
console.log("[opencli] Automation window closed");
|
||||
automationWindowId = null;
|
||||
if (windowIdleTimer) {
|
||||
clearTimeout(windowIdleTimer);
|
||||
windowIdleTimer = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
var initialized = false;
|
||||
@@ -212,6 +231,7 @@ chrome.alarms.onAlarm.addListener((alarm) => {
|
||||
if (alarm.name === "keepalive") connect();
|
||||
});
|
||||
async function handleCommand(cmd) {
|
||||
resetWindowIdleTimer();
|
||||
try {
|
||||
switch (cmd.action) {
|
||||
case "exec": return await handleExec(cmd);
|
||||
|
||||
@@ -88,8 +88,27 @@ function scheduleReconnect(): void {
|
||||
// ─── Automation window isolation ─────────────────────────────────────
|
||||
// All opencli operations happen in a dedicated Chrome window so the
|
||||
// user's active browsing session is never touched.
|
||||
// The window auto-closes after 30s of idle (no commands).
|
||||
|
||||
let automationWindowId: number | null = null;
|
||||
let windowIdleTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
const WINDOW_IDLE_TIMEOUT = 30000; // 30s
|
||||
|
||||
function resetWindowIdleTimer(): void {
|
||||
if (windowIdleTimer) clearTimeout(windowIdleTimer);
|
||||
windowIdleTimer = setTimeout(async () => {
|
||||
if (automationWindowId !== null) {
|
||||
try {
|
||||
await chrome.windows.remove(automationWindowId);
|
||||
console.log(`[opencli] Automation window ${automationWindowId} closed (idle timeout)`);
|
||||
} catch {
|
||||
// Already gone
|
||||
}
|
||||
automationWindowId = null;
|
||||
}
|
||||
windowIdleTimer = null;
|
||||
}, WINDOW_IDLE_TIMEOUT);
|
||||
}
|
||||
|
||||
/** Get or create the dedicated automation window. */
|
||||
async function getAutomationWindow(): Promise<number> {
|
||||
@@ -122,6 +141,7 @@ chrome.windows.onRemoved.addListener((windowId) => {
|
||||
if (windowId === automationWindowId) {
|
||||
console.log('[opencli] Automation window closed');
|
||||
automationWindowId = null;
|
||||
if (windowIdleTimer) { clearTimeout(windowIdleTimer); windowIdleTimer = null; }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -153,6 +173,8 @@ chrome.alarms.onAlarm.addListener((alarm) => {
|
||||
// ─── Command dispatcher ─────────────────────────────────────────────
|
||||
|
||||
async function handleCommand(cmd: Command): Promise<Result> {
|
||||
// Reset idle timer on every command (window stays alive while active)
|
||||
resetWindowIdleTimer();
|
||||
try {
|
||||
switch (cmd.action) {
|
||||
case 'exec':
|
||||
|
||||
+1
-7
@@ -1,5 +1,4 @@
|
||||
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);
|
||||
@@ -42,12 +41,7 @@ export async function browserSession<T>(
|
||||
const mcp = new BrowserFactory();
|
||||
try {
|
||||
const page = await mcp.connect({ timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT });
|
||||
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;
|
||||
return await fn(page);
|
||||
} finally {
|
||||
await mcp.close().catch(() => {});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user