aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--extensions/chrome-profile-bridge/browser-extension/service_worker.js10
-rw-r--r--extensions/chrome-profile-bridge/index.ts8
-rw-r--r--test-suite/unit/overlay.test.mjs17
3 files changed, 18 insertions, 17 deletions
diff --git a/extensions/chrome-profile-bridge/browser-extension/service_worker.js b/extensions/chrome-profile-bridge/browser-extension/service_worker.js
index 4aecf68..73aabc5 100644
--- a/extensions/chrome-profile-bridge/browser-extension/service_worker.js
+++ b/extensions/chrome-profile-bridge/browser-extension/service_worker.js
@@ -694,17 +694,17 @@ function extractForeignExtId(targets) {
const FOREIGN_EXTENSION_BLOCK = /Cannot access a chrome-extension:\/\/ URL of different extension/i;
// Another extension's frame on the page (a password manager's inline prompt, an autofill menu)
-// makes Chrome refuse the debugger on that tab. The user may be using that prompt this very
-// moment, so we never close it on our own: the error tells the agent to ask, and the agent can
-// then call overlay.clear (chrome_clear_overlay).
+// makes Chrome refuse the debugger on that tab. Nothing is closed automatically: the user may be
+// using the prompt (signing in, approving something for the agent). The error gives the agent the
+// facts and lets it decide, with overlay.clear (chrome_clear_overlay) to close it when it is only
+// in the way.
async function overlayBlockedError(tabId) {
const targets = (await findForeignExtensionTargets()).filter((t) => t.tabId === tabId);
const url = String(targets[0]?.url || "").replace(/[?#].*$/, "");
const id = extractForeignExtId(targets) || "unknown";
return new Error(
`Another extension (${id}${url ? `, ${url}` : ""}) has a prompt open on this tab, probably a password manager such as 1Password, and Chrome will not let the debugger work on the tab while it is there. ` +
- `The user may be using it right now, so do not close it on your own: ask the user, or wait for them to finish. ` +
- `When the user says to go ahead, or the tab is yours and idle, call chrome_clear_overlay for this tab. Navigating the tab away also works.`,
+ `If it is only in your way, call chrome_clear_overlay for this tab. If you are waiting for the user to sign in or approve something with it, leave it alone and tell the user. Navigating the tab away also frees it.`,
);
}
diff --git a/extensions/chrome-profile-bridge/index.ts b/extensions/chrome-profile-bridge/index.ts
index 1b770dd..3d6fa66 100644
--- a/extensions/chrome-profile-bridge/index.ts
+++ b/extensions/chrome-profile-bridge/index.ts
@@ -1266,7 +1266,7 @@ Capability model (important):
- Input tools return structured details and support \`includeSnapshot=true\` on click/type/fill/key. Use the fresh snapshot to verify state instead of repeating blindly. If the action started a navigation, the snapshot waits (up to 5s) for the new page.
- \`chrome_type\` never replaces: with a uid/selector it appends at the end of the field; without one it types at the current caret. Check its before/after report. Use \`chrome_fill\` to replace a field.
- Chrome ignores real input to hidden pages (inactive tabs, minimized windows, windows behind a macOS full-screen window). Input tools report this instead of silently doing nothing; retry with background:false only when bringing the tab forward is acceptable.
-- \`chrome_cdp\` runs any raw CDP method on a tab (emulation, cookies, PDF, accessibility tree, etc.) when no dedicated chrome_* tool fits; \`chrome_cdp_targets\` diagnoses debugger/overlay conflicts, and \`chrome_clear_overlay\` closes a password manager prompt that blocks a tab (ask the user first).
+- \`chrome_cdp\` runs any raw CDP method on a tab (emulation, cookies, PDF, accessibility tree, etc.) when no dedicated chrome_* tool fits; \`chrome_cdp_targets\` diagnoses debugger/overlay conflicts, and \`chrome_clear_overlay\` closes a password manager prompt that blocks a tab (unless you are waiting for the user to use it).
Usage rules:
1. If a chrome_* tool says Chrome control is locked, ask the user to run \`/chrome authorize\` before retrying.
@@ -1279,7 +1279,7 @@ Usage rules:
8. Run /chrome doctor when in doubt about connectivity or capabilities.
9. A page can open dialogs you cannot see: alert, confirm, prompt, and the \"Leave site? Changes you made may not be saved\" prompt of a page with unsaved changes. They are dismissed for you (stay, cancel). \`chrome_navigate\` reports one by failing, and \`leaveIfAsked: true\` leaves anyway; use that only when you know nothing unsaved matters, and ask the user when unsure. If a click or key press that should have navigated did nothing, a Leave-site prompt may have stopped it: check the page, then ask the user before discarding anything.
10. All agent sessions share one browser window; your tabs sit in your own colored tab group inside it. Reuse a tab you already have (navigate it) rather than opening new ones, and open a new tab only when you need two pages at once. Do not open new windows unless the user asks.
-11. If a tool fails saying another extension (a password manager such as 1Password) has a prompt open on the tab, the user may be using it right now, perhaps logging in or approving something for you. Do not close it on your own: tell the user and wait, or ask. Only when they say to go ahead, or the tab is yours and idle, call \`chrome_clear_overlay\`. Navigating the tab away also frees it.`;
+11. If a tool fails saying another extension (a password manager such as 1Password) has a prompt open on the tab, decide whether it is in your way or part of the task. If it is just in your way, call \`chrome_clear_overlay\` for the tab. If you need the user to sign in, authorize or approve something with it, leave it alone and tell the user what you are waiting for. Navigating the tab away also frees it.`;
// Shared handlers, dispatched by the unified /chrome command below.
const doctorHandler = async (ctx: ExtensionContext) => {
@@ -2286,8 +2286,8 @@ Usage rules:
name: "chrome_clear_overlay",
label: "Chrome Clear Overlay",
description:
- "Close another extension's prompt that is blocking a tab, usually a password manager's (1Password) inline sign-in or autofill prompt. While such a prompt is open Chrome refuses the debugger on that tab, so screenshots, clicks, typing and evaluate fail with \"Another extension ... has a prompt open on this tab\". The prompt may belong to the user, who is often using 1Password on the page right now (logging in, approving something). So never call this on your own: ask the user first, or call it only when they say to go ahead, or when the tab is yours and nobody is working in it. It removes the other extension's frame, blurs the focused field and sends Escape, then reports whether the tab is usable again. It does not touch the page's own content, and it cannot close 1Password's browser-toolbar popup or the 1Password desktop app (if the result says the tab is still blocked, tell the user). Navigating the tab away also frees it.",
- promptSnippet: "Close a password manager prompt that blocks a tab, after asking the user.",
+ "Close another extension's prompt that is blocking a tab, usually a password manager's (1Password) inline sign-in or autofill prompt. While such a prompt is open Chrome refuses the debugger on that tab, so screenshots, clicks, typing and evaluate fail with \"Another extension ... has a prompt open on this tab\". Decide for yourself whether to use it: if the prompt is just in your way (it popped up on a field you are filling, or you do not need anything from it), clear it. If you need the user to sign in, authorize or approve something with it, or the user may be in the middle of using it, do not clear it: tell the user what you are waiting for. It removes the other extension's frame, blurs the focused field and sends Escape, then reports whether the tab is usable again. It does not touch the page's own content, and it cannot close 1Password's browser-toolbar popup or the 1Password desktop app (if the result says the tab is still blocked, tell the user). Navigating the tab away also frees it.",
+ promptSnippet: "Close a password manager prompt that blocks a tab, unless you are waiting for the user to use it.",
parameters: Type.Object({
targetId: Type.Optional(Type.String()),
urlIncludes: Type.Optional(Type.String()),
diff --git a/test-suite/unit/overlay.test.mjs b/test-suite/unit/overlay.test.mjs
index de18da8..cd60522 100644
--- a/test-suite/unit/overlay.test.mjs
+++ b/test-suite/unit/overlay.test.mjs
@@ -1,6 +1,6 @@
-// Another extension's prompt (1Password) blocks the debugger on a tab. We never close it on our
-// own, because the user may be using it; the agent is told, and chrome_clear_overlay closes it
-// when asked.
+// Another extension's prompt (1Password) blocks the debugger on a tab. Nothing closes it
+// automatically, because the user may be using it; the agent is told what is blocking and decides:
+// chrome_clear_overlay when it is only in the way, leave it when it is waiting for the user.
import assert from "node:assert/strict";
import fs from "node:fs";
import vm from "node:vm";
@@ -35,12 +35,12 @@ function worker({ targets = [frame], clears = true, scriptResult = { removed: ["
return { w, calls, isCleared: () => cleared };
}
-test("a blocked input is not dismissed with Escape: the agent is told to ask the user", async () => {
+test("a blocked input is not dismissed with Escape: the agent is told and decides", async () => {
const { w, calls } = worker();
w.cdpRaw = async (_tab, method) => { calls.push({ method }); throw new Error(`${method}: ${BLOCK}`); };
await assert.rejects(
() => w.cdp(2, "Input.dispatchMouseEvent", {}),
- (e) => /may be using it right now/.test(e.message) && /ask the user/.test(e.message) && /chrome_clear_overlay/.test(e.message) && /aeblf/.test(e.message),
+ (e) => /only in your way/.test(e.message) && /waiting for the user to sign in or approve/.test(e.message) && /leave it alone/.test(e.message) && /chrome_clear_overlay/.test(e.message) && /aeblf/.test(e.message),
);
assert.deepEqual(calls.map((c) => c.method), ["Input.dispatchMouseEvent"]);
});
@@ -83,9 +83,10 @@ test("the injected clean-up removes foreign extension iframes, not ours, and blu
assert.ok(blurred && escaped);
});
-test("the tool tells the agent not to call it on its own, and the primer says the same", () => {
+test("the tool and the primer let the agent decide: clear it when in the way, leave it when waiting for the user", () => {
assert.match(indexSource, /name: "chrome_clear_overlay"/);
- assert.match(indexSource, /So never call this on your own: ask the user first/);
- assert.match(indexSource, /11\. If a tool fails saying another extension/);
+ assert.match(indexSource, /Decide for yourself whether to use it/);
+ assert.match(indexSource, /do not clear it: tell the user what you are waiting for/);
+ assert.match(indexSource, /11\. If a tool fails saying another extension[^`]*decide whether it is in your way or part of the task/);
assert.match(indexSource, /"chrome_clear_overlay",\n\] as const/);
});