mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
fix(vom): bug fix
This commit is contained in:
@@ -344,6 +344,11 @@ export default defineContentScript({
|
|||||||
renderControlOverlay();
|
renderControlOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitForRenderedOverlayUpdate(): Promise<void> {
|
||||||
|
await new Promise<void>((resolve) => window.requestAnimationFrame(() => resolve()));
|
||||||
|
await new Promise<void>((resolve) => window.requestAnimationFrame(() => resolve()));
|
||||||
|
}
|
||||||
|
|
||||||
function clearCurrentAgentSession(): void {
|
function clearCurrentAgentSession(): void {
|
||||||
const sessionId = overlays.snapshot().activeSessionId;
|
const sessionId = overlays.snapshot().activeSessionId;
|
||||||
if (!sessionId) return;
|
if (!sessionId) return;
|
||||||
@@ -528,6 +533,7 @@ export default defineContentScript({
|
|||||||
};
|
};
|
||||||
overlays.clearAgentHelpRequest(requestId);
|
overlays.clearAgentHelpRequest(requestId);
|
||||||
renderAll();
|
renderAll();
|
||||||
|
await waitForRenderedOverlayUpdate();
|
||||||
await chrome.runtime.sendMessage(msg).catch((err) => {
|
await chrome.runtime.sendMessage(msg).catch((err) => {
|
||||||
console.debug("[bsk overlay] help finish failed", err);
|
console.debug("[bsk overlay] help finish failed", err);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -107,6 +107,30 @@ describe("handleRequestHelp", () => {
|
|||||||
expect(res).toMatchObject({ outcome: "continued", note: "ok", tab_id: 5 });
|
expect(res).toMatchObject({ outcome: "continued", note: "ok", tab_id: 5 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not refresh observations after the user returns control", async () => {
|
||||||
|
const cdpSend = vi.fn(async () => ({}));
|
||||||
|
const deps = baseDeps({
|
||||||
|
cdp: { send: cdpSend } as unknown as RequestHelpDeps["cdp"],
|
||||||
|
});
|
||||||
|
const res = await handleRequestHelp(
|
||||||
|
fakeManager("abcd", 99, 5),
|
||||||
|
baseParams({ tab_id: 5 }),
|
||||||
|
deps,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res).toMatchObject({ outcome: "continued", tab_id: 5 });
|
||||||
|
expect(cdpSend).not.toHaveBeenCalledWith(
|
||||||
|
expect.any(Number),
|
||||||
|
"Accessibility.getFullAXTree",
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
expect(cdpSend).not.toHaveBeenCalledWith(
|
||||||
|
expect.any(Number),
|
||||||
|
"DOMSnapshot.captureSnapshot",
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("forwards title into the help request message when provided", async () => {
|
it("forwards title into the help request message when provided", async () => {
|
||||||
const deps = baseDeps();
|
const deps = baseDeps();
|
||||||
await handleRequestHelp(
|
await handleRequestHelp(
|
||||||
|
|||||||
@@ -304,11 +304,12 @@ async function cleanupHelp(help: ActiveHelpRequest): Promise<void> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishHelp(
|
async function finishHelp(
|
||||||
help: ActiveHelpRequest,
|
help: ActiveHelpRequest,
|
||||||
value: RequestHelpResult | RpcError,
|
value: RequestHelpResult | RpcError,
|
||||||
notifyContent = true,
|
notifyContent = true,
|
||||||
): void {
|
waitForCleanup = false,
|
||||||
|
): Promise<void> {
|
||||||
if (help.settled) return;
|
if (help.settled) return;
|
||||||
help.settled = true;
|
help.settled = true;
|
||||||
activeHelpRequests.delete(help.requestId);
|
activeHelpRequests.delete(help.requestId);
|
||||||
@@ -317,7 +318,11 @@ function finishHelp(
|
|||||||
if (help.abortHandler) help.deps.signal?.removeEventListener("abort", help.abortHandler);
|
if (help.abortHandler) help.deps.signal?.removeEventListener("abort", help.abortHandler);
|
||||||
clearRearmTimer(help.primaryTabId);
|
clearRearmTimer(help.primaryTabId);
|
||||||
for (const tabId of help.overlayTabIds) clearRearmTimer(tabId);
|
for (const tabId of help.overlayTabIds) clearRearmTimer(tabId);
|
||||||
if (notifyContent) void cleanupHelp(help);
|
if (notifyContent) {
|
||||||
|
const cleanup = cleanupHelp(help);
|
||||||
|
if (waitForCleanup) await cleanup;
|
||||||
|
else void cleanup;
|
||||||
|
}
|
||||||
help.resolve(value);
|
help.resolve(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,12 +391,17 @@ async function rearmHelp(help: ActiveHelpRequest, tabId: number): Promise<boolea
|
|||||||
try {
|
try {
|
||||||
const legacyFinish = await refreshAndSendHelpOverlay(help, tabId);
|
const legacyFinish = await refreshAndSendHelpOverlay(help, tabId);
|
||||||
if (legacyFinish && !help.settled) {
|
if (legacyFinish && !help.settled) {
|
||||||
finishHelp(help, {
|
await finishHelp(
|
||||||
outcome: legacyFinish.outcome,
|
help,
|
||||||
...(legacyFinish.note ? { note: legacyFinish.note } : {}),
|
{
|
||||||
tab_id: help.primaryTabId,
|
outcome: legacyFinish.outcome,
|
||||||
resolved_targets: help.resolvedTargets,
|
...(legacyFinish.note ? { note: legacyFinish.note } : {}),
|
||||||
});
|
tab_id: help.primaryTabId,
|
||||||
|
resolved_targets: help.resolvedTargets,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -501,12 +511,17 @@ async function finishHelpFromContent(
|
|||||||
const match = await findHelpForTab(tabId, deps);
|
const match = await findHelpForTab(tabId, deps);
|
||||||
if (match !== help) return;
|
if (match !== help) return;
|
||||||
}
|
}
|
||||||
finishHelp(help, {
|
await finishHelp(
|
||||||
outcome: message.outcome,
|
help,
|
||||||
...(message.note ? { note: message.note } : {}),
|
{
|
||||||
tab_id: help.primaryTabId,
|
outcome: message.outcome,
|
||||||
resolved_targets: help.resolvedTargets,
|
...(message.note ? { note: message.note } : {}),
|
||||||
});
|
tab_id: help.primaryTabId,
|
||||||
|
resolved_targets: help.resolvedTargets,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachHelpTabListener(deps: RequestHelpDeps): () => void {
|
function attachHelpTabListener(deps: RequestHelpDeps): () => void {
|
||||||
@@ -639,7 +654,7 @@ function startCompletionPolling(help: ActiveHelpRequest): void {
|
|||||||
}
|
}
|
||||||
help.completionMatchedSince ??= now;
|
help.completionMatchedSince ??= now;
|
||||||
if (now - help.completionMatchedSince < stableMs) return;
|
if (now - help.completionMatchedSince < stableMs) return;
|
||||||
finishHelp(help, {
|
void finishHelp(help, {
|
||||||
outcome: "completed",
|
outcome: "completed",
|
||||||
completed_by: "system",
|
completed_by: "system",
|
||||||
tab_id: help.primaryTabId,
|
tab_id: help.primaryTabId,
|
||||||
@@ -698,7 +713,7 @@ export async function handleRequestHelp(
|
|||||||
|
|
||||||
return new Promise<RequestHelpResult | RpcError>((resolve) => {
|
return new Promise<RequestHelpResult | RpcError>((resolve) => {
|
||||||
const timeoutTimer = setTimeout(() => {
|
const timeoutTimer = setTimeout(() => {
|
||||||
finishHelp(help, {
|
void finishHelp(help, {
|
||||||
outcome: "timed_out",
|
outcome: "timed_out",
|
||||||
tab_id: help.primaryTabId,
|
tab_id: help.primaryTabId,
|
||||||
resolved_targets: help.resolvedTargets,
|
resolved_targets: help.resolvedTargets,
|
||||||
@@ -727,7 +742,8 @@ export async function handleRequestHelp(
|
|||||||
abortHandler: null,
|
abortHandler: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAbort = () => finishHelp(help, { code: "cancelled", message: "request_help aborted" });
|
const onAbort = () =>
|
||||||
|
void finishHelp(help, { code: "cancelled", message: "request_help aborted" });
|
||||||
help.abortHandler = onAbort;
|
help.abortHandler = onAbort;
|
||||||
activeHelpRequests.set(requestId, help);
|
activeHelpRequests.set(requestId, help);
|
||||||
if (deps.signal?.aborted) {
|
if (deps.signal?.aborted) {
|
||||||
@@ -738,14 +754,19 @@ export async function handleRequestHelp(
|
|||||||
startCompletionPolling(help);
|
startCompletionPolling(help);
|
||||||
|
|
||||||
void sendCurrentHelpOverlay(help)
|
void sendCurrentHelpOverlay(help)
|
||||||
.then((legacyFinish) => {
|
.then(async (legacyFinish) => {
|
||||||
if (!legacyFinish || help.settled) return;
|
if (!legacyFinish || help.settled) return;
|
||||||
finishHelp(help, {
|
await finishHelp(
|
||||||
outcome: legacyFinish.outcome,
|
help,
|
||||||
...(legacyFinish.note ? { note: legacyFinish.note } : {}),
|
{
|
||||||
tab_id: help.primaryTabId,
|
outcome: legacyFinish.outcome,
|
||||||
resolved_targets: help.resolvedTargets,
|
...(legacyFinish.note ? { note: legacyFinish.note } : {}),
|
||||||
});
|
tab_id: help.primaryTabId,
|
||||||
|
resolved_targets: help.resolvedTargets,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
scheduleRearmForTab(tabId, deps);
|
scheduleRearmForTab(tabId, deps);
|
||||||
|
|||||||
@@ -215,6 +215,11 @@ refreshes do not return control by themselves. The result `outcome` is one of:
|
|||||||
`note` carries any text the user typed back. `resolved_targets` reports
|
`note` carries any text the user typed back. `resolved_targets` reports
|
||||||
which refs/selectors matched a live element.
|
which refs/selectors matched a live element.
|
||||||
|
|
||||||
|
`request-help` does not refresh the page model after the user returns
|
||||||
|
control. After a `continued` or `completed` result, issue a separate
|
||||||
|
observation tool call (usually `bsk snapshot --session <id>`) before using
|
||||||
|
new refs or reasoning about the post-help page state.
|
||||||
|
|
||||||
### Recording — `bsk record`
|
### Recording — `bsk record`
|
||||||
|
|
||||||
Capture the user's own actions in the Agent Window to a `trace.json`, for later LLM-driven automation:
|
Capture the user's own actions in the Agent Window to a `trace.json`, for later LLM-driven automation:
|
||||||
|
|||||||
+14
-6
@@ -68,6 +68,7 @@ Write operations only affect tabs in the **Agent Window** (or tabs you **borrowe
|
|||||||
```
|
```
|
||||||
bsk navigate <url> --session <id>
|
bsk navigate <url> --session <id>
|
||||||
bsk snapshot --session <id> → aria tree with @e1, @e2, … refs
|
bsk snapshot --session <id> → aria tree with @e1, @e2, … refs
|
||||||
|
bsk observe --session <id> → semantic VOM view; may reveal hover/focus surfaces
|
||||||
bsk click @e3 --session <id> → or bsk fill, bsk select, bsk press
|
bsk click @e3 --session <id> → or bsk fill, bsk select, bsk press
|
||||||
bsk snapshot --session <id> → again after navigation / DOM change
|
bsk snapshot --session <id> → again after navigation / DOM change
|
||||||
```
|
```
|
||||||
@@ -78,11 +79,12 @@ Prefer `@eN` refs from the latest snapshot over raw CSS selectors. Use `--ref` /
|
|||||||
|
|
||||||
## Observation priority
|
## Observation priority
|
||||||
|
|
||||||
Start with `bsk snapshot` to understand page structure, text, controls, and element refs. Only escalate when the latest snapshot cannot answer the question:
|
Start with `bsk snapshot` to understand page structure, text, controls, and element refs. Use `bsk observe` when semantic VOM output or conditional hover/focus surfaces would materially help. Only escalate to raw HTML or screenshots when the latest observation cannot answer the question:
|
||||||
|
|
||||||
1. `bsk snapshot` — default for page understanding and interaction planning
|
1. `bsk snapshot` — strict static page understanding and interaction planning
|
||||||
2. `bsk get-html` — when hidden DOM, metadata, or markup details are required
|
2. `bsk observe` — semantic VOM observation; may run bounded perception probes such as hover-surface discovery
|
||||||
3. `bsk screenshot` — when visual layout, canvas/image content, or styling cannot be inferred from the snapshot. Use `--ref @eN` (from the latest snapshot) to crop to one element; omit `--ref` for the full visible tab.
|
3. `bsk get-html` — when hidden DOM, metadata, or markup details are required
|
||||||
|
4. `bsk screenshot` — when visual layout, canvas/image content, or styling cannot be inferred from the observation. Use `--ref @eN` (from the latest snapshot/observe) to crop to one element; omit `--ref` for the full visible tab.
|
||||||
|
|
||||||
Do **not** call `bsk get-html` or `bsk screenshot` first just to inspect a page.
|
Do **not** call `bsk get-html` or `bsk screenshot` first just to inspect a page.
|
||||||
|
|
||||||
@@ -141,7 +143,8 @@ Details and flags: **`bsk <cmd> --help`**
|
|||||||
|
|
||||||
| Command | Summary |
|
| Command | Summary |
|
||||||
|---------|---------|
|
|---------|---------|
|
||||||
| `bsk snapshot` | First-choice page understanding: accessibility tree with `@eN` element refs |
|
| `bsk snapshot` | First-choice static page understanding: accessibility tree with `@eN` element refs |
|
||||||
|
| `bsk observe` | Semantic VOM observation with bounded perception probes for conditional surfaces |
|
||||||
| `bsk get-html` | Raw HTML dump after snapshot is insufficient (high token cost) |
|
| `bsk get-html` | Raw HTML dump after snapshot is insufficient (high token cost) |
|
||||||
| `bsk screenshot` | PNG capture after snapshot is insufficient: full visible tab, or `--ref @eN` to crop to one element (`--out` path optional) |
|
| `bsk screenshot` | PNG capture after snapshot is insufficient: full visible tab, or `--ref @eN` to crop to one element (`--out` path optional) |
|
||||||
|
|
||||||
@@ -212,6 +215,11 @@ refreshes do not return control by themselves. The result `outcome` is one of:
|
|||||||
`note` carries any text the user typed back. `resolved_targets` reports
|
`note` carries any text the user typed back. `resolved_targets` reports
|
||||||
which refs/selectors matched a live element.
|
which refs/selectors matched a live element.
|
||||||
|
|
||||||
|
`request-help` does not refresh the page model after the user returns
|
||||||
|
control. After a `continued` or `completed` result, issue a separate
|
||||||
|
observation tool call (usually `bsk snapshot --session <id>`) before using
|
||||||
|
new refs or reasoning about the post-help page state.
|
||||||
|
|
||||||
### Recording — `bsk record`
|
### Recording — `bsk record`
|
||||||
|
|
||||||
Capture the user's own actions in the Agent Window to a `trace.json`, for later LLM-driven automation:
|
Capture the user's own actions in the Agent Window to a `trace.json`, for later LLM-driven automation:
|
||||||
@@ -260,7 +268,7 @@ Always **`bsk session stop <id>`** in a `finally`-style path so the Agent Window
|
|||||||
2. **No long borrow** — do not leave a user's personal tab in the Agent Window across unrelated tasks.
|
2. **No long borrow** — do not leave a user's personal tab in the Agent Window across unrelated tasks.
|
||||||
3. **No skip stop** — always `bsk session stop <id>`; never assume idle timeout will clean up.
|
3. **No skip stop** — always `bsk session stop <id>`; never assume idle timeout will clean up.
|
||||||
4. **No post-success control** — once the user’s goal (or last trace step) is met, do not keep operating the page; stop the session unless they asked to keep it open.
|
4. **No post-success control** — once the user’s goal (or last trace step) is met, do not keep operating the page; stop the session unless they asked to keep it open.
|
||||||
5. **No observe escalation before snapshot** — use `bsk snapshot` first; only use `bsk get-html` or `bsk screenshot` when the snapshot is insufficient. Element screenshots (`--ref @eN`) still require a fresh snapshot ref — never skip snapshot just to grab a visual.
|
5. **No raw observe escalation before snapshot/observe** — use `bsk snapshot` first; use `bsk observe` when VOM semantics or conditional surfaces help. Only use `bsk get-html` or `bsk screenshot` when snapshot/observe is insufficient. Element screenshots (`--ref @eN`) still require a fresh snapshot/observe ref — never skip observation just to grab a visual.
|
||||||
6. **`evaluate` is powerful and risky** — use only when snapshot + click/fill/select cannot suffice; never on credential surfaces.
|
6. **`evaluate` is powerful and risky** — use only when snapshot + click/fill/select cannot suffice; never on credential surfaces.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user