fix(coding-agent/tools): fixed resource leak and async support in browser query handler

- 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.
- Removed unused maxLines parameter from buildMutationPreviewAgainstOriginal function and simplified preview generation logic.
This commit is contained in:
can1357
2026-02-11 09:56:10 +01:00
parent 209a5c464f
commit fce033cbed
3 changed files with 24 additions and 11 deletions
+21 -7
View File
@@ -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<ElementHandle | null> {
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<ActionabilityResult> {
@@ -1126,11 +1140,11 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
const script = ensureParam(params.script, "script", params.action);
const page = await this.#ensurePage(params);
const value = (await untilAborted(signal, () =>
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;