feat(browser): include current page URL in all tab-targeting responses
Enrich every browser action response with the resolved page URL so downstream consumers (security plugins, audit loggers) know which page was targeted without a separate tabs query. - Add shared withRouteTabContext URL enrichment wrapper (agent.shared.ts) - Resolve live URL via Playwright, fall back to tab list URL - Include url field in browser-tool console message results - Push URL changes from Chrome extension background script Co-authored-by: Eddie Abrams <eddie@bighatbio.com>
This commit is contained in:
parent
91d37ccfc3
commit
a66693b025
@ -288,11 +288,37 @@ export async function executeConsoleAction(params: {
|
||||
level,
|
||||
targetId,
|
||||
},
|
||||
})) as { ok?: boolean; targetId?: string; messages?: unknown[] };
|
||||
return formatConsoleToolResult(result);
|
||||
})) as { ok?: boolean; targetId?: string; url?: string; messages?: unknown[] };
|
||||
const wrapped = wrapBrowserExternalJson({
|
||||
kind: "console",
|
||||
payload: result,
|
||||
includeWarning: false,
|
||||
});
|
||||
return {
|
||||
content: [{ type: "text" as const, text: wrapped.wrappedText }],
|
||||
details: {
|
||||
...wrapped.safeDetails,
|
||||
targetId: typeof result.targetId === "string" ? result.targetId : undefined,
|
||||
url: typeof result.url === "string" ? result.url : undefined,
|
||||
messageCount: Array.isArray(result.messages) ? result.messages.length : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
const result = await browserConsoleMessages(baseUrl, { level, targetId, profile });
|
||||
return formatConsoleToolResult(result);
|
||||
const wrapped = wrapBrowserExternalJson({
|
||||
kind: "console",
|
||||
payload: result,
|
||||
includeWarning: false,
|
||||
});
|
||||
return {
|
||||
content: [{ type: "text" as const, text: wrapped.wrappedText }],
|
||||
details: {
|
||||
...wrapped.safeDetails,
|
||||
targetId: result.targetId,
|
||||
url: result.url,
|
||||
messageCount: result.messages.length,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function executeActAction(params: {
|
||||
|
||||
@ -25,7 +25,7 @@ function buildQuerySuffix(params: Array<[string, string | boolean | undefined]>)
|
||||
export async function browserConsoleMessages(
|
||||
baseUrl: string | undefined,
|
||||
opts: { level?: string; targetId?: string; profile?: string } = {},
|
||||
): Promise<{ ok: true; messages: BrowserConsoleMessage[]; targetId: string }> {
|
||||
): Promise<{ ok: true; messages: BrowserConsoleMessage[]; targetId: string; url?: string }> {
|
||||
const suffix = buildQuerySuffix([
|
||||
["level", opts.level],
|
||||
["targetId", opts.targetId],
|
||||
@ -35,6 +35,7 @@ export async function browserConsoleMessages(
|
||||
ok: true;
|
||||
messages: BrowserConsoleMessage[];
|
||||
targetId: string;
|
||||
url?: string;
|
||||
}>(withBaseUrl(baseUrl, `/console${suffix}`), { timeoutMs: 20000 });
|
||||
}
|
||||
|
||||
|
||||
@ -626,7 +626,7 @@ export function registerBrowserAgentActRoutes(
|
||||
typeRequest.timeoutMs = timeoutMs;
|
||||
}
|
||||
await pw.typeViaPlaywright(typeRequest);
|
||||
return res.json({ ok: true, targetId: tab.targetId });
|
||||
return res.json({ ok: true, targetId: tab.targetId, url: tab.url });
|
||||
}
|
||||
case "press": {
|
||||
const key = toStringOrEmpty(body.key);
|
||||
@ -656,7 +656,7 @@ export function registerBrowserAgentActRoutes(
|
||||
key,
|
||||
delayMs: delayMs ?? undefined,
|
||||
});
|
||||
return res.json({ ok: true, targetId: tab.targetId });
|
||||
return res.json({ ok: true, targetId: tab.targetId, url: tab.url });
|
||||
}
|
||||
case "hover": {
|
||||
const ref = toStringOrEmpty(body.ref) || undefined;
|
||||
@ -699,7 +699,7 @@ export function registerBrowserAgentActRoutes(
|
||||
selector,
|
||||
timeoutMs: timeoutMs ?? undefined,
|
||||
});
|
||||
return res.json({ ok: true, targetId: tab.targetId });
|
||||
return res.json({ ok: true, targetId: tab.targetId, url: tab.url });
|
||||
}
|
||||
case "scrollIntoView": {
|
||||
const ref = toStringOrEmpty(body.ref) || undefined;
|
||||
@ -750,7 +750,7 @@ export function registerBrowserAgentActRoutes(
|
||||
scrollRequest.timeoutMs = timeoutMs;
|
||||
}
|
||||
await pw.scrollIntoViewViaPlaywright(scrollRequest);
|
||||
return res.json({ ok: true, targetId: tab.targetId });
|
||||
return res.json({ ok: true, targetId: tab.targetId, url: tab.url });
|
||||
}
|
||||
case "drag": {
|
||||
const startRef = toStringOrEmpty(body.startRef) || undefined;
|
||||
@ -801,7 +801,7 @@ export function registerBrowserAgentActRoutes(
|
||||
endSelector,
|
||||
timeoutMs: timeoutMs ?? undefined,
|
||||
});
|
||||
return res.json({ ok: true, targetId: tab.targetId });
|
||||
return res.json({ ok: true, targetId: tab.targetId, url: tab.url });
|
||||
}
|
||||
case "select": {
|
||||
const ref = toStringOrEmpty(body.ref) || undefined;
|
||||
@ -854,7 +854,7 @@ export function registerBrowserAgentActRoutes(
|
||||
values,
|
||||
timeoutMs: timeoutMs ?? undefined,
|
||||
});
|
||||
return res.json({ ok: true, targetId: tab.targetId });
|
||||
return res.json({ ok: true, targetId: tab.targetId, url: tab.url });
|
||||
}
|
||||
case "fill": {
|
||||
const rawFields = Array.isArray(body.fields) ? body.fields : [];
|
||||
@ -899,7 +899,7 @@ export function registerBrowserAgentActRoutes(
|
||||
fields,
|
||||
timeoutMs: timeoutMs ?? undefined,
|
||||
});
|
||||
return res.json({ ok: true, targetId: tab.targetId });
|
||||
return res.json({ ok: true, targetId: tab.targetId, url: tab.url });
|
||||
}
|
||||
case "resize": {
|
||||
const width = toNumber(body.width);
|
||||
@ -1001,7 +1001,7 @@ export function registerBrowserAgentActRoutes(
|
||||
fn,
|
||||
timeoutMs,
|
||||
});
|
||||
return res.json({ ok: true, targetId: tab.targetId });
|
||||
return res.json({ ok: true, targetId: tab.targetId, url: tab.url });
|
||||
}
|
||||
case "evaluate": {
|
||||
if (!evaluateEnabled) {
|
||||
@ -1152,7 +1152,7 @@ export function registerBrowserAgentActRoutes(
|
||||
timeoutMs: timeoutMs ?? undefined,
|
||||
maxChars: maxChars ?? undefined,
|
||||
});
|
||||
res.json({ ok: true, targetId: tab.targetId, response: result });
|
||||
res.json({ ok: true, targetId: tab.targetId, url: tab.url, response: result });
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -1204,7 +1204,7 @@ export function registerBrowserAgentActRoutes(
|
||||
targetId: tab.targetId,
|
||||
ref,
|
||||
});
|
||||
res.json({ ok: true, targetId: tab.targetId });
|
||||
res.json({ ok: true, targetId: tab.targetId, url: tab.url });
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@ -32,7 +32,7 @@ export function registerBrowserAgentDebugRoutes(
|
||||
targetId: tab.targetId,
|
||||
level: level.trim() || undefined,
|
||||
});
|
||||
res.json({ ok: true, messages, targetId: tab.targetId });
|
||||
res.json({ ok: true, messages, targetId: tab.targetId, url: tab.url });
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@ -109,6 +109,56 @@ export async function withRouteTabContext<T>(
|
||||
}
|
||||
try {
|
||||
const tab = await profileCtx.ensureTabAvailable(params.targetId);
|
||||
|
||||
// Enrich every successful tab-targeting response with the resolved tab's
|
||||
// current page URL. This gives downstream consumers (security plugins,
|
||||
// audit loggers, etc.) a consistent way to know which page was targeted
|
||||
// without issuing a separate tabs query. Existing explicit values win;
|
||||
// the wrapper only fills in missing fields.
|
||||
//
|
||||
// We attempt to resolve the *live* page URL via Playwright (which queries
|
||||
// the actual browser page), falling back to the tab list URL if
|
||||
// Playwright is unavailable. This corrects stale URL caches when the
|
||||
// user navigates in the browser without triggering a relay metadata
|
||||
// refresh (e.g. Chrome extension relay).
|
||||
let liveUrl: string | undefined;
|
||||
try {
|
||||
const pwMod = await getPwAiModuleBase({ mode: "soft" });
|
||||
if (pwMod?.getPageForTargetId) {
|
||||
const page = await pwMod.getPageForTargetId({
|
||||
cdpUrl: profileCtx.profile.cdpUrl,
|
||||
targetId: tab.targetId,
|
||||
});
|
||||
if (page) {
|
||||
liveUrl = page.url();
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Playwright not available or page not found — fall back to tab.url
|
||||
}
|
||||
const resolvedUrl = liveUrl || tab.url;
|
||||
|
||||
const originalJson = params.res.json.bind(params.res);
|
||||
params.res.json = (body: unknown) => {
|
||||
if (
|
||||
body &&
|
||||
typeof body === "object" &&
|
||||
!Array.isArray(body) &&
|
||||
(body as Record<string, unknown>).ok === true
|
||||
) {
|
||||
const record = body as Record<string, unknown>;
|
||||
if (record.targetId === undefined) {
|
||||
record.targetId = tab.targetId;
|
||||
}
|
||||
if (resolvedUrl) {
|
||||
// Always override url with live value — the route may have used a
|
||||
// stale tab.url from the relay cache.
|
||||
record.url = resolvedUrl;
|
||||
}
|
||||
}
|
||||
return originalJson(body);
|
||||
};
|
||||
|
||||
return await params.run({
|
||||
profileCtx,
|
||||
tab,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user