From 1a100a2a239a74af03c2bf32abb370419b06416a Mon Sep 17 00:00:00 2001 From: xianren Date: Tue, 17 Mar 2026 22:05:02 +0800 Subject: [PATCH] fix: eliminate duplicate command sets in chain, improve flush perf and type safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove duplicate CHAIN_READ/CHAIN_WRITE/CHAIN_META sets from meta-commands.ts and import from commands.ts (single source of truth). The duplicated sets would silently fail to route new commands added to commands.ts. - Replace read+concat+write log flush with fs.appendFileSync — O(new entries) instead of O(total log size) per flush cycle. - Replace `any` types for contextOptions with Playwright's BrowserContextOptions and add proper types for storage state in recreateContext(). Co-Authored-By: Claude Opus 4.6 (1M context) --- browse/src/browser-manager.ts | 20 ++++++++++---------- browse/src/meta-commands.ts | 29 ++++------------------------- browse/src/server.ts | 6 +++--- 3 files changed, 17 insertions(+), 38 deletions(-) diff --git a/browse/src/browser-manager.ts b/browse/src/browser-manager.ts index 260c8219c28c1bdabed3ff966cf15c9166f280e4..e094f3a5e78dc3cf77e92341df8bd30d8731c4ed 100644 --- a/browse/src/browser-manager.ts +++ b/browse/src/browser-manager.ts @@ -15,7 +15,7 @@ * restores state. Falls back to clean slate on any failure. */ -import { chromium, type Browser, type BrowserContext, type Page, type Locator } from 'playwright'; +import { chromium, type Browser, type BrowserContext, type BrowserContextOptions, type Page, type Locator } from 'playwright'; import { addConsoleEntry, addNetworkEntry, addDialogEntry, networkBuffer, type DialogEntry } from './buffers'; export interface RefEntry { @@ -57,7 +57,7 @@ export class BrowserManager { process.exit(1); }); - const contextOptions: any = { + const contextOptions: BrowserContextOptions = { viewport: { width: 1280, height: 720 }, }; if (this.customUserAgent) { @@ -282,7 +282,7 @@ export class BrowserManager { try { // 1. Save state from current context const savedCookies = await this.context.cookies(); - const savedPages: Array<{ url: string; isActive: boolean; storage: any }> = []; + const savedPages: Array<{ url: string; isActive: boolean; storage: { localStorage: Record; sessionStorage: Record } | null }> = []; for (const [id, page] of this.pages) { const url = page.url(); @@ -308,7 +308,7 @@ export class BrowserManager { await this.context.close().catch(() => {}); // 3. Create new context with updated settings - const contextOptions: any = { + const contextOptions: BrowserContextOptions = { viewport: { width: 1280, height: 720 }, }; if (this.customUserAgent) { @@ -340,15 +340,15 @@ export class BrowserManager { // 6. Restore storage if (saved.storage) { try { - await page.evaluate((s: any) => { + await page.evaluate((s: { localStorage: Record; sessionStorage: Record }) => { if (s.localStorage) { for (const [k, v] of Object.entries(s.localStorage)) { - localStorage.setItem(k, v as string); + localStorage.setItem(k, v); } } if (s.sessionStorage) { for (const [k, v] of Object.entries(s.sessionStorage)) { - sessionStorage.setItem(k, v as string); + sessionStorage.setItem(k, v); } } }, saved.storage); @@ -369,13 +369,13 @@ export class BrowserManager { this.clearRefs(); return null; // success - } catch (err: any) { + } catch (err: unknown) { // Fallback: create a clean context + blank tab try { this.pages.clear(); if (this.context) await this.context.close().catch(() => {}); - const contextOptions: any = { + const contextOptions: BrowserContextOptions = { viewport: { width: 1280, height: 720 }, }; if (this.customUserAgent) { @@ -387,7 +387,7 @@ export class BrowserManager { } catch { // If even the fallback fails, we're in trouble — but browser is still alive } - return `Context recreation failed: ${err.message}. Browser reset to blank tab.`; + return `Context recreation failed: ${err instanceof Error ? err.message : String(err)}. Browser reset to blank tab.`; } } diff --git a/browse/src/meta-commands.ts b/browse/src/meta-commands.ts index c17930b38e4924efe23a435e79d4c896729af662..3c622db9e1e9de4e38c3c51fd1a7477de98e33b2 100644 --- a/browse/src/meta-commands.ts +++ b/browse/src/meta-commands.ts @@ -5,6 +5,7 @@ import type { BrowserManager } from './browser-manager'; import { handleSnapshot } from './snapshot'; import { getCleanText } from './read-commands'; +import { READ_COMMANDS, WRITE_COMMANDS, META_COMMANDS } from './commands'; import * as Diff from 'diff'; import * as fs from 'fs'; import * as path from 'path'; @@ -20,28 +21,6 @@ function validateOutputPath(filePath: string): void { } } -// Command sets for chain routing (mirrors server.ts — kept local to avoid circular import) -const CHAIN_READ = new Set([ - 'text', 'html', 'links', 'forms', 'accessibility', - 'js', 'eval', 'css', 'attrs', - 'console', 'network', 'cookies', 'storage', 'perf', - 'dialog', 'is', -]); -const CHAIN_WRITE = new Set([ - 'goto', 'back', 'forward', 'reload', - 'click', 'fill', 'select', 'hover', 'type', 'press', 'scroll', 'wait', - 'viewport', 'cookie', 'cookie-import', 'header', 'useragent', - 'upload', 'dialog-accept', 'dialog-dismiss', - 'cookie-import-browser', -]); -const CHAIN_META = new Set([ - 'tabs', 'tab', 'newtab', 'closetab', - 'status', 'stop', 'restart', - 'screenshot', 'pdf', 'responsive', - 'chain', 'diff', - 'url', 'snapshot', -]); - export async function handleMetaCommand( command: string, args: string[], @@ -223,9 +202,9 @@ export async function handleMetaCommand( const [name, ...cmdArgs] = cmd; try { let result: string; - if (CHAIN_WRITE.has(name)) result = await handleWriteCommand(name, cmdArgs, bm); - else if (CHAIN_READ.has(name)) result = await handleReadCommand(name, cmdArgs, bm); - else if (CHAIN_META.has(name)) result = await handleMetaCommand(name, cmdArgs, bm, shutdown); + if (WRITE_COMMANDS.has(name)) result = await handleWriteCommand(name, cmdArgs, bm); + else if (READ_COMMANDS.has(name)) result = await handleReadCommand(name, cmdArgs, bm); + else if (META_COMMANDS.has(name)) result = await handleMetaCommand(name, cmdArgs, bm, shutdown); else throw new Error(`Unknown command: ${name}`); results.push(`[${name}] ${result}`); } catch (err: any) { diff --git a/browse/src/server.ts b/browse/src/server.ts index 5e76f4214df06d93fa822d0d7f98258818eb7c90..f30a4881f8435aa0701975907466493c5807b458 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -104,7 +104,7 @@ async function flushBuffers() { const lines = entries.map(e => `[${new Date(e.timestamp).toISOString()}] [${e.level}] ${e.text}` ).join('\n') + '\n'; - await Bun.write(CONSOLE_LOG_PATH, (await Bun.file(CONSOLE_LOG_PATH).text().catch(() => '')) + lines); + fs.appendFileSync(CONSOLE_LOG_PATH, lines); lastConsoleFlushed = consoleBuffer.totalAdded; } @@ -115,7 +115,7 @@ async function flushBuffers() { const lines = entries.map(e => `[${new Date(e.timestamp).toISOString()}] ${e.method} ${e.url} → ${e.status || 'pending'} (${e.duration || '?'}ms, ${e.size || '?'}B)` ).join('\n') + '\n'; - await Bun.write(NETWORK_LOG_PATH, (await Bun.file(NETWORK_LOG_PATH).text().catch(() => '')) + lines); + fs.appendFileSync(NETWORK_LOG_PATH, lines); lastNetworkFlushed = networkBuffer.totalAdded; } @@ -126,7 +126,7 @@ async function flushBuffers() { const lines = entries.map(e => `[${new Date(e.timestamp).toISOString()}] [${e.type}] "${e.message}" → ${e.action}${e.response ? ` "${e.response}"` : ''}` ).join('\n') + '\n'; - await Bun.write(DIALOG_LOG_PATH, (await Bun.file(DIALOG_LOG_PATH).text().catch(() => '')) + lines); + fs.appendFileSync(DIALOG_LOG_PATH, lines); lastDialogFlushed = dialogBuffer.totalAdded; } } catch {