pi-chrome 0.15.20 → 0.15.23
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.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
All notable user-facing changes to `pi-chrome`.
|
|
4
4
|
|
|
5
|
+
## 0.15.23 — 2026-05-16
|
|
6
|
+
|
|
7
|
+
- **Attribution.** The 0.15.22 features below are pulled from Dani Bednarski's fork (`DaniBedz/pi-chrome`). Thank you, Dani.
|
|
8
|
+
|
|
9
|
+
## 0.15.22 — 2026-05-16
|
|
10
|
+
|
|
11
|
+
Features in this release are pulled from Dani Bednarski's fork (`DaniBedz/pi-chrome`). Thank you, Dani.
|
|
12
|
+
|
|
13
|
+
- **Earlier page-load capture.** Companion extension now injects console/network instrumentation at `document_start`, so initial React render errors and early API calls show up in `chrome_list_console_messages` / `chrome_list_network_requests`.
|
|
14
|
+
- **Quieter locked state.** Startup no longer shows a persistent Chrome bridge notification/status item before authorization; status bar appears only when Chrome control is authorized.
|
|
15
|
+
- **Lazy tool registration.** `chrome_*` tools and primer are registered only after `/chrome authorize`, reducing prompt/tool overhead while Chrome control is locked.
|
|
16
|
+
|
|
17
|
+
## 0.15.21 — 2026-05-16
|
|
18
|
+
|
|
19
|
+
### Reverted 0.16.x and 0.17.x lines
|
|
20
|
+
|
|
21
|
+
- Versions 0.16.0 through 0.17.2 were published to npm and subsequently unpublished. 0.17.3 was prepared locally but never published. The work introduced in those versions — mandatory pairing, signed-envelope auth, standalone bridge daemon, idempotent onboard, etc. — is reachable only via git tags (`v0.16.0` … `v0.17.3`) and is not in the current main branch.
|
|
22
|
+
- This release is **tree-equivalent to 0.15.20** with a version-only bump so future patch releases can ship cleanly.
|
|
23
|
+
|
|
5
24
|
## 0.15.20 — 2026-05-15
|
|
6
25
|
|
|
7
26
|
- **Interruptible `chrome_*` tools.** All `chrome_*` tools now honor the agent harness `AbortSignal`, so pressing Esc aborts in-flight bridge calls (including the long-polling `chrome_wait_for`) immediately instead of waiting out the full `timeoutMs`.
|
|
@@ -966,6 +966,24 @@ if (chrome.webNavigation && chrome.webNavigation.onCommitted) {
|
|
|
966
966
|
});
|
|
967
967
|
}
|
|
968
968
|
|
|
969
|
+
// Always inject early console/network capture at document_start on every navigation.
|
|
970
|
+
// Catches console messages, errors, and network requests that fire during page load,
|
|
971
|
+
// before chrome_snapshot or chrome_evaluate install the instrumentation normally.
|
|
972
|
+
// The function installEarlyCapture sets __piChromeWrapped flags so the post-hoc
|
|
973
|
+
// installPiChromeInstrumentation() call is idempotent.
|
|
974
|
+
if (chrome.webNavigation && chrome.webNavigation.onCommitted) {
|
|
975
|
+
chrome.webNavigation.onCommitted.addListener((details) => {
|
|
976
|
+
if (details.frameId !== 0) return;
|
|
977
|
+
chrome.scripting.executeScript({
|
|
978
|
+
target: { tabId: details.tabId, frameIds: [0] },
|
|
979
|
+
world: "MAIN",
|
|
980
|
+
injectImmediately: true,
|
|
981
|
+
func: installEarlyCapture,
|
|
982
|
+
args: [],
|
|
983
|
+
}).catch(() => undefined);
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
|
|
969
987
|
async function bringToFront(tab) {
|
|
970
988
|
await chrome.windows.update(tab.windowId, { focused: true });
|
|
971
989
|
await chrome.tabs.update(tab.id, { active: true });
|
|
@@ -1310,6 +1328,141 @@ function installPiChromeInstrumentation() {
|
|
|
1310
1328
|
}
|
|
1311
1329
|
}
|
|
1312
1330
|
|
|
1331
|
+
// Early-capture version of installPiChromeInstrumentation, designed to be injected
|
|
1332
|
+
// at document_start via webNavigation.onCommitted. Wraps console, fetch, and XHR
|
|
1333
|
+
// before the page's own JavaScript runs, so page-load errors are captured.
|
|
1334
|
+
// Sets __piChromeWrapped flags so the post-hoc installPiChromeInstrumentation()
|
|
1335
|
+
// sees them and skips (idempotent).
|
|
1336
|
+
// NOTE: This function is self-contained — it does NOT close over any outer scope
|
|
1337
|
+
// because it gets serialized by chrome.scripting.executeScript({func: ...}).
|
|
1338
|
+
function installEarlyCapture() {
|
|
1339
|
+
if (window.__piChromeEarlyCaptureInstalled) return;
|
|
1340
|
+
window.__piChromeEarlyCaptureInstalled = true;
|
|
1341
|
+
var state = window.__PI_CHROME_STATE__;
|
|
1342
|
+
if (!state) {
|
|
1343
|
+
state = {
|
|
1344
|
+
nextElementUid: 1,
|
|
1345
|
+
elements: {},
|
|
1346
|
+
console: [],
|
|
1347
|
+
network: [],
|
|
1348
|
+
nextRequestId: 1,
|
|
1349
|
+
instrumentationInstalled: false,
|
|
1350
|
+
};
|
|
1351
|
+
window.__PI_CHROME_STATE__ = state;
|
|
1352
|
+
}
|
|
1353
|
+
function pushConsole(level, args) {
|
|
1354
|
+
state.console.push({
|
|
1355
|
+
id: state.console.length + 1,
|
|
1356
|
+
level: level,
|
|
1357
|
+
timestamp: Date.now(),
|
|
1358
|
+
url: location.href,
|
|
1359
|
+
args: Array.from(args).map(function(arg) {
|
|
1360
|
+
try {
|
|
1361
|
+
if (typeof arg === "string") return arg;
|
|
1362
|
+
if (arg instanceof Error) return { name: arg.name, message: arg.message, stack: arg.stack };
|
|
1363
|
+
return JSON.parse(JSON.stringify(arg));
|
|
1364
|
+
} catch (e) {
|
|
1365
|
+
return String(arg);
|
|
1366
|
+
}
|
|
1367
|
+
}),
|
|
1368
|
+
});
|
|
1369
|
+
if (state.console.length > 500) state.console.splice(0, state.console.length - 500);
|
|
1370
|
+
}
|
|
1371
|
+
for (var i = 0; i < 5; i++) {
|
|
1372
|
+
var levels = ["debug", "log", "info", "warn", "error"];
|
|
1373
|
+
var level = levels[i];
|
|
1374
|
+
var original = console[level];
|
|
1375
|
+
if (typeof original !== "function" || original.__piChromeWrapped) continue;
|
|
1376
|
+
var wrapped = function(lvl, orig) {
|
|
1377
|
+
return function() {
|
|
1378
|
+
pushConsole(lvl, arguments);
|
|
1379
|
+
return orig.apply(this, arguments);
|
|
1380
|
+
};
|
|
1381
|
+
}(level, original);
|
|
1382
|
+
wrapped.__piChromeWrapped = true;
|
|
1383
|
+
console[level] = wrapped;
|
|
1384
|
+
}
|
|
1385
|
+
window.addEventListener("error", function(event) {
|
|
1386
|
+
pushConsole("pageerror", [event.message, event.filename + ":" + event.lineno + ":" + event.colno]);
|
|
1387
|
+
});
|
|
1388
|
+
window.addEventListener("unhandledrejection", function(event) {
|
|
1389
|
+
pushConsole("unhandledrejection", [event.reason]);
|
|
1390
|
+
});
|
|
1391
|
+
var trimBody = function(text) {
|
|
1392
|
+
return typeof text === "string" && text.length > 200000 ? text.slice(0, 200000) + "\n[truncated " + (text.length - 200000) + " chars]" : text;
|
|
1393
|
+
};
|
|
1394
|
+
var record = function(entry) {
|
|
1395
|
+
state.network.push(entry);
|
|
1396
|
+
if (state.network.length > 1000) state.network.splice(0, state.network.length - 1000);
|
|
1397
|
+
return entry;
|
|
1398
|
+
};
|
|
1399
|
+
if (window.fetch && !window.fetch.__piChromeWrapped) {
|
|
1400
|
+
var originalFetch = window.fetch.bind(window);
|
|
1401
|
+
var wrappedFetch = async function() {
|
|
1402
|
+
var args = [];
|
|
1403
|
+
for (var k = 0; k < arguments.length; k++) args.push(arguments[k]);
|
|
1404
|
+
var id = "req-" + state.nextRequestId++;
|
|
1405
|
+
var startedAt = Date.now();
|
|
1406
|
+
var input = args[0];
|
|
1407
|
+
var init = args[1] || {};
|
|
1408
|
+
var url = typeof input === "string" ? input : (input ? input.url : "");
|
|
1409
|
+
var method = (init.method || (input ? input.method : null) || "GET").toUpperCase();
|
|
1410
|
+
var entry = record({ id: id, type: "fetch", method: method, url: String(url || ""), startedAt: startedAt, pageUrl: location.href, status: "pending" });
|
|
1411
|
+
try {
|
|
1412
|
+
var response = await originalFetch.apply(window, args);
|
|
1413
|
+
entry.status = response.status;
|
|
1414
|
+
entry.statusText = response.statusText;
|
|
1415
|
+
entry.ok = response.ok;
|
|
1416
|
+
entry.responseUrl = response.url;
|
|
1417
|
+
entry.durationMs = Date.now() - startedAt;
|
|
1418
|
+
entry.responseHeaders = Array.from(response.headers.entries());
|
|
1419
|
+
response.clone().text().then(function(text) {
|
|
1420
|
+
entry.responseBody = trimBody(text);
|
|
1421
|
+
entry.responseBodyTruncated = typeof text === "string" && text.length > 200000;
|
|
1422
|
+
}).catch(function(error) { entry.responseBodyError = error ? error.message : String(error); });
|
|
1423
|
+
return response;
|
|
1424
|
+
} catch (error) {
|
|
1425
|
+
entry.error = error ? error.message : String(error);
|
|
1426
|
+
entry.durationMs = Date.now() - startedAt;
|
|
1427
|
+
throw error;
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
wrappedFetch.__piChromeWrapped = true;
|
|
1431
|
+
window.fetch = wrappedFetch;
|
|
1432
|
+
}
|
|
1433
|
+
if (window.XMLHttpRequest && !XMLHttpRequest.prototype.open.__piChromeWrapped) {
|
|
1434
|
+
var originalOpen = XMLHttpRequest.prototype.open;
|
|
1435
|
+
var originalSend = XMLHttpRequest.prototype.send;
|
|
1436
|
+
XMLHttpRequest.prototype.open = function(method, url) {
|
|
1437
|
+
this.__piChromeRequest = { method: String(method || "GET").toUpperCase(), url: String(url || "") };
|
|
1438
|
+
return originalOpen.apply(this, arguments);
|
|
1439
|
+
};
|
|
1440
|
+
XMLHttpRequest.prototype.open.__piChromeWrapped = true;
|
|
1441
|
+
XMLHttpRequest.prototype.send = function(body) {
|
|
1442
|
+
var id = "req-" + state.nextRequestId++;
|
|
1443
|
+
var startedAt = Date.now();
|
|
1444
|
+
var info = this.__piChromeRequest || {};
|
|
1445
|
+
var entry = record({ id: id, type: "xhr", method: info.method || "GET", url: info.url || "", startedAt: startedAt, pageUrl: location.href, status: "pending" });
|
|
1446
|
+
this.addEventListener("loadend", function() {
|
|
1447
|
+
entry.status = this.status;
|
|
1448
|
+
entry.statusText = this.statusText;
|
|
1449
|
+
entry.responseUrl = this.responseURL;
|
|
1450
|
+
entry.durationMs = Date.now() - startedAt;
|
|
1451
|
+
try { entry.responseHeadersText = this.getAllResponseHeaders(); } catch (e) {}
|
|
1452
|
+
try {
|
|
1453
|
+
if (typeof this.responseText === "string") {
|
|
1454
|
+
entry.responseBody = trimBody(this.responseText);
|
|
1455
|
+
entry.responseBodyTruncated = this.responseText.length > 200000;
|
|
1456
|
+
}
|
|
1457
|
+
} catch (error) { entry.responseBodyError = error ? error.message : String(error); }
|
|
1458
|
+
});
|
|
1459
|
+
this.addEventListener("error", function() { entry.error = "XMLHttpRequest error"; entry.durationMs = Date.now() - startedAt; });
|
|
1460
|
+
return originalSend.apply(this, arguments);
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
state.instrumentationInstalled = true;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1313
1466
|
function snapshotPage(maxElements, containingText, roleFilter, nearUid) {
|
|
1314
1467
|
installPiChromeInstrumentation();
|
|
1315
1468
|
const unique = (selector) => {
|
|
@@ -463,6 +463,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
463
463
|
const bridge = new ChromeProfileBridge(DEFAULT_HOST, DEFAULT_PORT);
|
|
464
464
|
let backgroundDefault = false;
|
|
465
465
|
let chromeAuthorizedUntil: number | "indefinite" | undefined;
|
|
466
|
+
let chromeToolsRegistered = false;
|
|
466
467
|
|
|
467
468
|
const authSummary = (): string => {
|
|
468
469
|
if (chromeAuthorizedUntil === "indefinite") return "authorized indefinitely";
|
|
@@ -486,6 +487,14 @@ export default function (pi: ExtensionAPI): void {
|
|
|
486
487
|
}
|
|
487
488
|
};
|
|
488
489
|
|
|
490
|
+
const updateChromeStatus = (ctx: ExtensionContext): void => {
|
|
491
|
+
if (chromeControlAuthorized()) {
|
|
492
|
+
ctx.ui.setStatus("chrome", ctx.ui.theme.fg("success", "●") + " Chrome Bridge");
|
|
493
|
+
} else {
|
|
494
|
+
ctx.ui.setStatus("chrome", undefined);
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
|
|
489
498
|
const authorizedBridgeSend = (action: string, params: Record<string, unknown>, timeoutMs = DEFAULT_TIMEOUT_MS, signal?: AbortSignal): Promise<unknown> => {
|
|
490
499
|
requireChromeControlAuthorized();
|
|
491
500
|
return bridge.send(action, params, timeoutMs, signal);
|
|
@@ -507,14 +516,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
507
516
|
|
|
508
517
|
pi.on("session_start", async (_event, ctx) => {
|
|
509
518
|
await bridge.start();
|
|
510
|
-
|
|
511
|
-
ctx.ui.setStatus("chrome", `Chrome bridge :${DEFAULT_PORT}`);
|
|
512
|
-
ctx.ui.notify(
|
|
513
|
-
status.mode === "client"
|
|
514
|
-
? `pi-chrome connected (sharing the Chrome connection an earlier pi session opened). Run /chrome authorize before using chrome_* tools.`
|
|
515
|
-
: `pi-chrome is ready and waiting for the Chrome companion to connect. Run /chrome onboard to install it, then /chrome authorize to allow chrome_* tools.`,
|
|
516
|
-
"info",
|
|
517
|
-
);
|
|
519
|
+
updateChromeStatus(ctx);
|
|
518
520
|
});
|
|
519
521
|
|
|
520
522
|
pi.on("session_shutdown", () => {
|
|
@@ -525,6 +527,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
525
527
|
});
|
|
526
528
|
|
|
527
529
|
pi.on("before_agent_start", (event) => {
|
|
530
|
+
if (!chromeToolsRegistered || !chromeControlAuthorized()) {
|
|
531
|
+
return { systemPrompt: event.systemPrompt };
|
|
532
|
+
}
|
|
528
533
|
const primer = `
|
|
529
534
|
<chrome-profile-bridge>
|
|
530
535
|
Chrome control is available through the chrome_* tools via a companion Chrome extension installed in the user's normal Chrome profile. Tools target the existing signed-in profile: no remote-debug port, no throwaway profile.
|
|
@@ -648,8 +653,10 @@ Usage rules:
|
|
|
648
653
|
ctx.ui.notify("Chrome control remains locked.", "info");
|
|
649
654
|
return;
|
|
650
655
|
}
|
|
656
|
+
registerChromeTools(pi);
|
|
651
657
|
chromeAuthorizedUntil = until;
|
|
652
658
|
ctx.ui.notify(`Chrome control authorized for ${label}.`, "info");
|
|
659
|
+
updateChromeStatus(ctx);
|
|
653
660
|
};
|
|
654
661
|
|
|
655
662
|
const parseAuthorizeArg = (arg: string): { label: string; until: number | "indefinite" } | undefined => {
|
|
@@ -672,6 +679,7 @@ Usage rules:
|
|
|
672
679
|
const revokeHandler = (ctx: ExtensionContext) => {
|
|
673
680
|
chromeAuthorizedUntil = undefined;
|
|
674
681
|
ctx.ui.notify("Chrome control locked. Run /chrome authorize to allow chrome_* tools again.", "info");
|
|
682
|
+
updateChromeStatus(ctx);
|
|
675
683
|
};
|
|
676
684
|
|
|
677
685
|
const onboardHandler = async (ctx: ExtensionContext) => {
|
|
@@ -848,6 +856,10 @@ Usage rules:
|
|
|
848
856
|
},
|
|
849
857
|
});
|
|
850
858
|
|
|
859
|
+
function registerChromeTools(pi: ExtensionAPI): void {
|
|
860
|
+
if (chromeToolsRegistered) return;
|
|
861
|
+
chromeToolsRegistered = true;
|
|
862
|
+
|
|
851
863
|
pi.registerTool({
|
|
852
864
|
name: "chrome_launch",
|
|
853
865
|
label: "Chrome Bridge Setup",
|
|
@@ -1381,4 +1393,6 @@ Usage rules:
|
|
|
1381
1393
|
return { content: [{ type: "text", text: `Uploaded ${paths.length} file(s) to ${params.uid ?? params.selector}` }], details: { result: result as Json } };
|
|
1382
1394
|
},
|
|
1383
1395
|
});
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1384
1398
|
}
|