mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
feat(vom): update vom algorithm, fix overlay problem with background state machine
This commit is contained in:
@@ -32,6 +32,13 @@ export const chromeTabsApi: ChromeTabsApi = {
|
||||
query: (q) => chrome.tabs.query(q),
|
||||
};
|
||||
|
||||
export interface ResolvedTargetTab {
|
||||
tabId: number;
|
||||
windowId: number;
|
||||
active: boolean;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export type { DialogCursor };
|
||||
|
||||
/**
|
||||
@@ -44,6 +51,7 @@ export type { DialogCursor };
|
||||
*/
|
||||
export interface CdpRunner {
|
||||
send<T = unknown>(tabId: number, method: string, params?: object): Promise<T>;
|
||||
ensureAttachedToUrl?(tabId: number, expectedUrl: string | undefined): Promise<void>;
|
||||
trackSessionTab?(sessionId: string, tabId: number): void;
|
||||
onEvent?(handler: (source: chrome.debugger.Debuggee, method: string, params: unknown) => void): {
|
||||
dispose(): void;
|
||||
@@ -146,7 +154,7 @@ export async function resolveTargetTab(
|
||||
ctx: SessionContext,
|
||||
tabId: number | undefined,
|
||||
api: ChromeTabsApi,
|
||||
): Promise<{ tabId: number; windowId: number; active: boolean } | RpcError> {
|
||||
): Promise<ResolvedTargetTab | RpcError> {
|
||||
if (tabId !== undefined) {
|
||||
if (!Number.isSafeInteger(tabId) || tabId <= 0) {
|
||||
return {
|
||||
@@ -176,7 +184,7 @@ export async function resolveTargetTab(
|
||||
message: `tab ${tabId} not found in session scope`,
|
||||
};
|
||||
}
|
||||
return { tabId: tab.id, windowId: tab.windowId, active: tab.active === true };
|
||||
return { tabId: tab.id, windowId: tab.windowId, active: tab.active === true, url: tab.url };
|
||||
}
|
||||
const tabs = await api.query({ active: true, windowId: ctx.agentWindowId });
|
||||
const first = tabs.find((t) => typeof t.id === "number");
|
||||
@@ -186,7 +194,12 @@ export async function resolveTargetTab(
|
||||
message: `no active tab in Agent Window ${ctx.agentWindowId}`,
|
||||
};
|
||||
}
|
||||
return { tabId: first.id, windowId: ctx.agentWindowId, active: first.active === true };
|
||||
return {
|
||||
tabId: first.id,
|
||||
windowId: ctx.agentWindowId,
|
||||
active: first.active === true,
|
||||
url: first.url,
|
||||
};
|
||||
}
|
||||
|
||||
export function isRpcError(v: unknown): v is RpcError {
|
||||
@@ -202,6 +215,91 @@ export function isRpcError(v: unknown): v is RpcError {
|
||||
/** Re-export so M6/M7 tools keep a stable import path. */
|
||||
export { normaliseRef };
|
||||
|
||||
export type ToolEffect = "passive_read" | "transient_input" | "browser_mutation";
|
||||
|
||||
const CDP_BLOCKED_PROTOCOLS = new Set([
|
||||
"chrome:",
|
||||
"chrome-extension:",
|
||||
"devtools:",
|
||||
"edge:",
|
||||
"brave:",
|
||||
"vivaldi:",
|
||||
"opera:",
|
||||
]);
|
||||
|
||||
export function cdpBlockedUrlReason(url: string | undefined): string | null {
|
||||
if (!url) return null;
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(url);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (CDP_BLOCKED_PROTOCOLS.has(parsed.protocol)) return parsed.protocol;
|
||||
if (parsed.protocol === "about:" && parsed.pathname !== "blank") return "about:";
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page CDP tools cannot inspect browser/extension internal pages. Check this
|
||||
* at the tool boundary so Chrome's low-level "Cannot access..." errors do not
|
||||
* leak as ambiguous page-read failures.
|
||||
*/
|
||||
export function enforceCdpAccessibleTarget(
|
||||
target: ResolvedTargetTab,
|
||||
toolName: string,
|
||||
): RpcError | null {
|
||||
const reason = cdpBlockedUrlReason(target.url);
|
||||
if (!reason) return null;
|
||||
return rpcError(
|
||||
"permission_denied",
|
||||
"restricted_tab_url",
|
||||
`${toolName} cannot access tab ${target.tabId} because its URL is ${target.url}; navigate the Agent Window to a web page first`,
|
||||
);
|
||||
}
|
||||
|
||||
function resolvedTargetFromChromeTab(
|
||||
tab: chrome.tabs.Tab,
|
||||
fallbackWindowId: number,
|
||||
): ResolvedTargetTab | null {
|
||||
if (typeof tab.id !== "number") return null;
|
||||
return {
|
||||
tabId: tab.id,
|
||||
windowId: typeof tab.windowId === "number" ? tab.windowId : fallbackWindowId,
|
||||
active: tab.active === true,
|
||||
url: tab.url,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a target for page CDP reads. Explicit tab_ids stay exact and are
|
||||
* rejected when browser security blocks the URL. For default targeting, skip a
|
||||
* restricted Agent Window active tab if another CDP-accessible tab exists in
|
||||
* the same Agent Window.
|
||||
*/
|
||||
export async function resolveCdpAccessibleTargetTab(
|
||||
manager: SessionManager,
|
||||
ctx: SessionContext,
|
||||
tabId: number | undefined,
|
||||
api: ChromeTabsApi,
|
||||
toolName: string,
|
||||
): Promise<ResolvedTargetTab | RpcError> {
|
||||
const target = await resolveTargetTab(manager, ctx, tabId, api);
|
||||
if (isRpcError(target)) return target;
|
||||
|
||||
const restricted = enforceCdpAccessibleTarget(target, toolName);
|
||||
if (!restricted) return target;
|
||||
if (tabId !== undefined) return restricted;
|
||||
|
||||
const tabs = await api.query({ windowId: ctx.agentWindowId });
|
||||
for (const tab of tabs) {
|
||||
const candidate = resolvedTargetFromChromeTab(tab, ctx.agentWindowId);
|
||||
if (!candidate) continue;
|
||||
if (!enforceCdpAccessibleTarget(candidate, toolName)) return candidate;
|
||||
}
|
||||
return restricted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sandbox guard: M7 write tools (click / fill / press / navigate*)
|
||||
* MUST refuse to touch a tab outside the session's Agent Window
|
||||
@@ -224,3 +322,17 @@ export function enforceAgentWindow(
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified target-scope policy by tool effect. Passive reads may inspect user
|
||||
* tabs; any tool that dispatches page input must stay inside the Agent Window.
|
||||
*/
|
||||
export function enforceToolTargetScope(
|
||||
ctx: SessionContext,
|
||||
target: { tabId: number; windowId: number },
|
||||
effect: ToolEffect,
|
||||
toolName: string,
|
||||
): RpcError | null {
|
||||
if (effect === "passive_read") return null;
|
||||
return enforceAgentWindow(ctx, target, toolName);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user