pi-chrome 0.14.1 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Pi Chrome Connector",
|
|
4
|
-
"version": "0.14.
|
|
4
|
+
"version": "0.14.2",
|
|
5
5
|
"description": "Lets Pi control tabs in Chrome via a local connector at 127.0.0.1.",
|
|
6
6
|
"permissions": ["tabs", "scripting", "storage", "activeTab", "alarms", "webNavigation", "debugger"],
|
|
7
7
|
"host_permissions": ["<all_urls>", "http://127.0.0.1:17318/*"],
|
|
@@ -185,12 +185,72 @@ function cdpRaw(tabId, method, params) {
|
|
|
185
185
|
// chrome.debugger.attach can stay cached in attachedTabs even after Chrome killed
|
|
186
186
|
// the session (tab nav, devtools opened/closed, etc). Recover by detaching the
|
|
187
187
|
// stale entry and re-attaching, then retry the command once.
|
|
188
|
+
// Find foreign chrome-extension targets currently anchored to the tab. Password managers,
|
|
189
|
+
// autofill helpers, and other input-attached extensions create type:"other" CDP targets
|
|
190
|
+
// whose URL is chrome-extension://<otherId>/... When that target is in focus, CDP refuses
|
|
191
|
+
// our Input.dispatchMouseEvent calls with "Cannot access a chrome-extension:// URL of
|
|
192
|
+
// different extension" — surfacing a cryptic error to the user.
|
|
193
|
+
async function findForeignExtensionTargets() {
|
|
194
|
+
try {
|
|
195
|
+
const targets = await new Promise((resolve) => chrome.debugger.getTargets((t) => resolve(t || [])));
|
|
196
|
+
return targets.filter((t) => {
|
|
197
|
+
const url = String(t.url || "");
|
|
198
|
+
if (!url.startsWith("chrome-extension://")) return false;
|
|
199
|
+
if (t.extensionId === chrome.runtime.id) return false;
|
|
200
|
+
return true;
|
|
201
|
+
});
|
|
202
|
+
} catch {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function extractForeignExtId(targets) {
|
|
208
|
+
for (const t of targets) {
|
|
209
|
+
if (t.extensionId && t.extensionId !== chrome.runtime.id) return t.extensionId;
|
|
210
|
+
const m = String(t.url || "").match(/chrome-extension:\/\/([a-p]+)\//);
|
|
211
|
+
if (m && m[1] !== chrome.runtime.id) return m[1];
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function dismissOverlayViaEscape(tabId) {
|
|
217
|
+
// Esc routes through key dispatcher (target-by-focus), not by mouse coordinates, so it
|
|
218
|
+
// works even when a foreign chrome-extension popup is intercepting pointer events.
|
|
219
|
+
try {
|
|
220
|
+
await cdpRaw(tabId, "Input.dispatchKeyEvent", { type: "keyDown", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27 });
|
|
221
|
+
await cdpRaw(tabId, "Input.dispatchKeyEvent", { type: "keyUp", key: "Escape", code: "Escape", windowsVirtualKeyCode: 27 });
|
|
222
|
+
await sleep(120);
|
|
223
|
+
} catch {}
|
|
224
|
+
}
|
|
225
|
+
|
|
188
226
|
async function cdp(tabId, method, params) {
|
|
189
227
|
try {
|
|
190
228
|
return await cdpRaw(tabId, method, params);
|
|
191
229
|
} catch (error) {
|
|
192
230
|
const msg = String(error?.message || error);
|
|
193
231
|
const isStale = /Debugger is not attached|Detached while|Target closed|No tab with id/i.test(msg);
|
|
232
|
+
const isForeignExtBlock = /Cannot access a chrome-extension:\/\/ URL of different extension/i.test(msg);
|
|
233
|
+
if (isForeignExtBlock && /Input\./.test(method)) {
|
|
234
|
+
// Foreign chrome-extension popup (autofill, password manager) is hijacking input.
|
|
235
|
+
// Try once: dismiss via Esc, then retry.
|
|
236
|
+
const before = await findForeignExtensionTargets();
|
|
237
|
+
recordAttachEvent({ kind: "foreign-ext-detected", tabId, method, foreignExtId: extractForeignExtId(before), targetCount: before.length });
|
|
238
|
+
await dismissOverlayViaEscape(tabId);
|
|
239
|
+
try {
|
|
240
|
+
return await cdpRaw(tabId, method, params);
|
|
241
|
+
} catch (retryErr) {
|
|
242
|
+
const retryMsg = String(retryErr?.message || retryErr);
|
|
243
|
+
if (/Cannot access a chrome-extension:\/\/ URL of different extension/i.test(retryMsg)) {
|
|
244
|
+
const after = await findForeignExtensionTargets();
|
|
245
|
+
const id = extractForeignExtId(after) || extractForeignExtId(before) || "unknown";
|
|
246
|
+
throw new Error(
|
|
247
|
+
`Another Chrome extension (${id}) has an input overlay on this page (e.g. a password manager / autofill popup). \n` +
|
|
248
|
+
`pi-chrome tried to dismiss it with Escape but it reappeared. Disable that extension on this page, focus the field via Tab instead of clicking, or run /chrome quiet off so the agent uses synthetic input here.`,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
throw retryErr;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
194
254
|
if (!isStale) throw error;
|
|
195
255
|
attachedTabs.delete(tabId);
|
|
196
256
|
await chrome.debugger.attach({ tabId }, CDP_VERSION).catch(() => undefined);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { StringEnum } from "@earendil-works/pi-ai";
|
|
4
3
|
import { Container, type SettingItem, SettingsList, Text } from "@earendil-works/pi-tui";
|
|
5
4
|
import { Type } from "typebox";
|
|
6
5
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
@@ -381,6 +380,10 @@ const tabActionValues = ["list", "new", "activate", "close", "version"] as const
|
|
|
381
380
|
const imageFormatValues = ["png", "jpeg"] as const;
|
|
382
381
|
const waitForValues = ["selector", "expression"] as const;
|
|
383
382
|
|
|
383
|
+
function StringEnum<T extends readonly [string, ...string[]]>(values: T) {
|
|
384
|
+
return Type.Union(values.map((value) => Type.Literal(value)) as [ReturnType<typeof Type.Literal>, ...ReturnType<typeof Type.Literal>[]]);
|
|
385
|
+
}
|
|
386
|
+
|
|
384
387
|
export default function (pi: ExtensionAPI): void {
|
|
385
388
|
const globalState = globalThis as typeof globalThis & {
|
|
386
389
|
[PI_CHROME_GLOBAL_KEY]?: { version: string; root: string };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-chrome",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2",
|
|
4
4
|
"description": "Drive your existing logged-in Chrome from Pi — no re-login, no throwaway profile, watch the agent work in real time (or toggle quiet background mode).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -24,8 +24,15 @@
|
|
|
24
24
|
]
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@earendil-works/pi-ai": "*",
|
|
28
27
|
"@earendil-works/pi-coding-agent": "*",
|
|
29
28
|
"typebox": "*"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"@earendil-works/pi-coding-agent": {
|
|
32
|
+
"optional": true
|
|
33
|
+
},
|
|
34
|
+
"typebox": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
30
37
|
}
|
|
31
38
|
}
|