diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a49d1c64e..aadb5227e 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,7 +1,6 @@ # Changelog ## [Unreleased] - ### Added - Added `replace` hashline edit operation for substr-style fuzzy text replacement without line references, with optional `all` flag for replace-all behavior @@ -58,6 +57,8 @@ ### Fixed +- Fixed resource leak in browser query handler by properly disposing owned proxy elements for non-winning candidates +- Fixed script evaluation to support async functions and await expressions in browser evaluate operations - Fixed `range` edits with missing `end` field to gracefully degrade to single-line edits instead of crashing - Fixed `insertAfter` operations to accept both `content` and `replacement` field names for consistency with other edit types - Fixed deduplication logic to correctly identify and remove identical hashline edits targeting the same line(s) in a single call diff --git a/packages/coding-agent/src/tools/browser.ts b/packages/coding-agent/src/tools/browser.ts index 0f48091c2..0c9d774a1 100644 --- a/packages/coding-agent/src/tools/browser.ts +++ b/packages/coding-agent/src/tools/browser.ts @@ -93,7 +93,11 @@ function normalizeSelector(selector: string): string { type ActionabilityResult = { ok: true; x: number; y: number } | { ok: false; reason: string }; async function resolveActionableQueryHandlerClickTarget(handles: ElementHandle[]): Promise { - const candidates: Array<{ handle: ElementHandle; rect: { x: number; y: number; w: number; h: number } }> = []; + const candidates: Array<{ + handle: ElementHandle; + rect: { x: number; y: number; w: number; h: number }; + ownedProxy?: ElementHandle; + }> = []; for (const handle of handles) { let clickable: ElementHandle = handle; @@ -123,11 +127,11 @@ async function resolveActionableQueryHandlerClickTarget(handles: ElementHandle[] return { x: r.left, y: r.top, w: r.width, h: r.height }; })) as { x: number; y: number; w: number; h: number }; if (rect.w < 1 || rect.h < 1) continue; - candidates.push({ handle: clickable, rect }); + candidates.push({ handle: clickable, rect, ownedProxy: clickableProxy ?? undefined }); } catch { // ignore } finally { - if (clickableProxy && clickableProxy !== handle) { + if (clickableProxy && clickableProxy !== handle && clickable !== clickableProxy) { try { await clickableProxy.dispose(); } catch {} @@ -139,7 +143,17 @@ async function resolveActionableQueryHandlerClickTarget(handles: ElementHandle[] // Prefer top-most visible element (nav/header usually wins), tie-break by left-most. candidates.sort((a, b) => a.rect.y - b.rect.y || a.rect.x - b.rect.x); - return candidates[0]?.handle ?? null; + const winner = candidates[0]?.handle ?? null; + // Dispose owned proxies for non-winning candidates + for (let i = 1; i < candidates.length; i++) { + const c = candidates[i]!; + if (c.ownedProxy) { + try { + await c.ownedProxy.dispose(); + } catch {} + } + } + return winner; } async function isClickActionable(handle: ElementHandle): Promise { @@ -1126,11 +1140,11 @@ export class BrowserTool implements AgentTool - page.evaluate((source: string) => { + page.evaluate(async (source: string) => { try { - return new Function(`return (${source});`)(); + return await new Function(`return (async () => (${source}))();`)(); } catch { - return new Function(source)(); + return await new Function(`return (async () => { ${source} })();`)(); } }, script), )) as unknown; diff --git a/packages/react-edit-benchmark/runner.ts b/packages/react-edit-benchmark/runner.ts index 86aa26cb9..0e641fb0c 100644 --- a/packages/react-edit-benchmark/runner.ts +++ b/packages/react-edit-benchmark/runner.ts @@ -83,7 +83,7 @@ async function collectOriginalFileContents(cwd: string, files: string[]): Promis return originals; } -function buildMutationPreviewAgainstOriginal(original: string, current: string, maxLines = 8): string | null { +function buildMutationPreviewAgainstOriginal(original: string, current: string): string | null { if (original === current) return null; const changes = diffLines(original, current); @@ -102,7 +102,6 @@ function buildMutationPreviewAgainstOriginal(original: string, current: string, const hash = computeLineHash(lineNum, line); preview.push(`${lineNum}:${hash}| -${line}`); lineNum += 1; - if (preview.length >= maxLines) return preview.join("\n"); } continue; } @@ -110,7 +109,6 @@ function buildMutationPreviewAgainstOriginal(original: string, current: string, for (const line of lines) { const hash = computeLineHash(lineNum, line); preview.push(`${lineNum}:${hash}| +${line}`); - if (preview.length >= maxLines) return preview.join("\n"); } }