pi-chrome 0.11.0 → 0.11.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.11.
|
|
4
|
+
"version": "0.11.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/*"],
|
|
@@ -103,7 +103,7 @@ setInterval(() => {
|
|
|
103
103
|
}
|
|
104
104
|
}, 5000);
|
|
105
105
|
|
|
106
|
-
function
|
|
106
|
+
function cdpRaw(tabId, method, params) {
|
|
107
107
|
return new Promise((resolve, reject) => {
|
|
108
108
|
chrome.debugger.sendCommand({ tabId }, method, params || {}, (result) => {
|
|
109
109
|
if (chrome.runtime.lastError) reject(new Error(`${method}: ${chrome.runtime.lastError.message}`));
|
|
@@ -112,6 +112,24 @@ function cdp(tabId, method, params) {
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// Wraps cdpRaw with one auto-recover on detached/closed sessions:
|
|
116
|
+
// chrome.debugger.attach can stay cached in attachedTabs even after Chrome killed
|
|
117
|
+
// the session (tab nav, devtools opened/closed, etc). Recover by detaching the
|
|
118
|
+
// stale entry and re-attaching, then retry the command once.
|
|
119
|
+
async function cdp(tabId, method, params) {
|
|
120
|
+
try {
|
|
121
|
+
return await cdpRaw(tabId, method, params);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const msg = String(error?.message || error);
|
|
124
|
+
const isStale = /Debugger is not attached|Detached while|Target closed|No tab with id/i.test(msg);
|
|
125
|
+
if (!isStale) throw error;
|
|
126
|
+
attachedTabs.delete(tabId);
|
|
127
|
+
await chrome.debugger.attach({ tabId }, CDP_VERSION).catch(() => undefined);
|
|
128
|
+
attachedTabs.set(tabId, { detachAt: Date.now() + TRUSTED_IDLE_DETACH_MS, pointer: { x: 120 + Math.random() * 200, y: 80 + Math.random() * 120 } });
|
|
129
|
+
return cdpRaw(tabId, method, params);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
// Resolve target -> {x, y, rect} in viewport coords by running tiny script in tab.
|
|
116
134
|
async function resolveTargetInTab(tabId, params) {
|
|
117
135
|
const results = await chrome.scripting.executeScript({
|
|
@@ -349,20 +367,49 @@ async function trustedScroll(params) {
|
|
|
349
367
|
const x = resolved.rect ? resolved.rect.left + Math.min(resolved.rect.width, 800) / 2 : resolved.x;
|
|
350
368
|
const y = resolved.rect ? resolved.rect.top + Math.min(resolved.rect.height, 600) / 2 : resolved.y;
|
|
351
369
|
const totalY = params.deltaY || 0, totalX = params.deltaX || 0;
|
|
352
|
-
|
|
353
|
-
//
|
|
354
|
-
const
|
|
370
|
+
// Per-event delta cap so IntersectionObserver / scroll-driven animations get gradient samples.
|
|
371
|
+
// A real wheel notch is ~50-120px; we aim for the lower end so visibility transitions are visible.
|
|
372
|
+
const MAX_STEP = 60;
|
|
373
|
+
const peak = Math.max(Math.abs(totalY), Math.abs(totalX));
|
|
374
|
+
// We weight events with mild front-loading. The peak weight = 1.5 / n (vs uniform 1/n), so the
|
|
375
|
+
// first event is ~1.5× average. Choose n so even the peak event stays under MAX_STEP.
|
|
376
|
+
const minN = Math.ceil(peak * 1.5 / MAX_STEP);
|
|
377
|
+
const n = Math.max(6, Math.min(80, params.steps || Math.max(minN, 12)));
|
|
378
|
+
// Front-loaded but smooth weights: w[i] = 1 + 0.5 * (1 - i/(n-1)) so the first event has
|
|
379
|
+
// weight 1.5, the last has 1.0, average ~1.25; redistribution stays predictable.
|
|
380
|
+
const w = [];
|
|
381
|
+
for (let i = 0; i < n; i++) {
|
|
382
|
+
const t = i / Math.max(1, n - 1);
|
|
383
|
+
w.push(1 + 0.5 * (1 - t));
|
|
384
|
+
}
|
|
355
385
|
const sumW = w.reduce((a, b) => a + b, 0);
|
|
356
386
|
for (let i = 0; i < n; i++) {
|
|
357
387
|
const dy = totalY * (w[i] / sumW), dx = totalX * (w[i] / sumW);
|
|
358
388
|
await cdp(tab.id, "Input.dispatchMouseEvent", {
|
|
359
389
|
type: "mouseWheel", x, y, deltaX: dx, deltaY: dy, pointerType: "mouse",
|
|
360
390
|
});
|
|
361
|
-
|
|
391
|
+
// Sleep one+ frame so IntersectionObserver / rAF samples can run between events.
|
|
392
|
+
await sleep(rng(22, 48));
|
|
362
393
|
}
|
|
363
394
|
return { trusted: true, deltaX: totalX, deltaY: totalY, steps: n };
|
|
364
395
|
}
|
|
365
396
|
|
|
397
|
+
async function trustedTap(params) {
|
|
398
|
+
const tab = await getTabByParams(params);
|
|
399
|
+
if (params.foreground) await bringToFront(tab);
|
|
400
|
+
await attachDebugger(tab.id);
|
|
401
|
+
const resolved = (params.selector || params.uid || (typeof params.x === "number" && typeof params.y === "number"))
|
|
402
|
+
? await resolveTargetInTab(tab.id, params)
|
|
403
|
+
: null;
|
|
404
|
+
if (!resolved || !resolved.found) throw new Error("trusted.tap: target not found");
|
|
405
|
+
const point = resolved.rect ? pickInsideRect(resolved.rect) : { x: resolved.x, y: resolved.y };
|
|
406
|
+
const tp = { x: point.x, y: point.y, radiusX: 8, radiusY: 8, rotationAngle: 0, force: 0.5, id: 1 };
|
|
407
|
+
await cdp(tab.id, "Input.dispatchTouchEvent", { type: "touchStart", touchPoints: [tp] });
|
|
408
|
+
await sleep(rng(40, 110));
|
|
409
|
+
await cdp(tab.id, "Input.dispatchTouchEvent", { type: "touchEnd", touchPoints: [] });
|
|
410
|
+
return { trusted: true, x: point.x, y: point.y, tag: resolved.tag };
|
|
411
|
+
}
|
|
412
|
+
|
|
366
413
|
async function trustedDrag(params) {
|
|
367
414
|
const tab = await getTabByParams(params);
|
|
368
415
|
if (params.foreground) await bringToFront(tab);
|
|
@@ -537,6 +584,8 @@ async function dispatch(action, params) {
|
|
|
537
584
|
case "page.scroll":
|
|
538
585
|
if (await wantsTrusted(params)) return trustedScroll(params);
|
|
539
586
|
return executeActionInTab(params, scrollPage, [params.selector ?? null, params.uid ?? null, params.deltaY ?? 0, params.deltaX ?? 0, params.steps ?? null]);
|
|
587
|
+
case "page.tap":
|
|
588
|
+
return trustedTap(params);
|
|
540
589
|
case "trusted.mode":
|
|
541
590
|
return setTrustedMode(params.mode);
|
|
542
591
|
case "trusted.status":
|
|
@@ -1165,6 +1165,29 @@ Usage rules:
|
|
|
1165
1165
|
},
|
|
1166
1166
|
});
|
|
1167
1167
|
|
|
1168
|
+
pi.registerTool({
|
|
1169
|
+
name: "chrome_tap",
|
|
1170
|
+
label: "Chrome Tap (Touch)",
|
|
1171
|
+
description:
|
|
1172
|
+
"Dispatch a real browser-trusted touchstart/touchend tap via chrome.debugger (CDP Input.dispatchTouchEvent). Use for sites that gate on TouchEvent rather than MouseEvent (mobile-first PWAs, swipe carousels). Always uses the trusted CDP path — the yellow debugger banner appears.",
|
|
1173
|
+
promptSnippet: "Tap (real touch) a Chrome element by snapshot uid, selector, or coordinate.",
|
|
1174
|
+
parameters: Type.Object({
|
|
1175
|
+
uid: Type.Optional(Type.String()),
|
|
1176
|
+
selector: Type.Optional(Type.String()),
|
|
1177
|
+
x: Type.Optional(Type.Number()),
|
|
1178
|
+
y: Type.Optional(Type.Number()),
|
|
1179
|
+
targetId: Type.Optional(Type.String()),
|
|
1180
|
+
urlIncludes: Type.Optional(Type.String()),
|
|
1181
|
+
titleIncludes: Type.Optional(Type.String()),
|
|
1182
|
+
background: Type.Optional(Type.Boolean()),
|
|
1183
|
+
}),
|
|
1184
|
+
async execute(_id, params): Promise<ToolTextResult> {
|
|
1185
|
+
const result = await bridge.send("page.tap", withBackground(params), DEFAULT_TIMEOUT_MS);
|
|
1186
|
+
const target = params.uid ?? params.selector ?? `${params.x},${params.y}`;
|
|
1187
|
+
return { content: [{ type: "text", text: `Tapped ${target} (touch)` }], details: { result: result as Json } };
|
|
1188
|
+
},
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1168
1191
|
pi.registerTool({
|
|
1169
1192
|
name: "chrome_scroll",
|
|
1170
1193
|
label: "Chrome Scroll",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-chrome",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.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",
|