all repos — sz-chrome-remote-extension @ f133011c8ecda06ae63068649a9714c88a7b901e

pi-chrome, patched to drive a signed-in browser on another host through a broker, with an MCP server

0.15.24: unload Chrome tools when locked
Tianren Dong tianrendong.dtr@gmail.com
Sat, 16 May 2026 13:33:57 -0400
commit

f133011c8ecda06ae63068649a9714c88a7b901e

parent

cd8195c82324b815001c0fe8f1ed3fca61a08ebe

M CHANGELOG.md → CHANGELOG.md

@@ -2,6 +2,10 @@ # Changelog

All notable user-facing changes to `pi-chrome`. +## 0.15.24 — 2026-05-16 + +- **Unload Chrome tools on lock.** `chrome_*` tools now deactivate when `/chrome revoke` runs or timed authorization expires, keeping the prompt/tool list small after Chrome control locks again. + ## 0.15.23 — 2026-05-16 - **Attribution.** The 0.15.22 features below are pulled from Dani Bednarski's fork (`DaniBedz/pi-chrome`). Thank you, Dani.
M extensions/chrome-profile-bridge/browser-extension/manifest.json → extensions/chrome-profile-bridge/browser-extension/manifest.json

@@ -1,7 +1,7 @@

{ "manifest_version": 3, "name": "Pi Chrome Connector", - "version": "0.15.23", + "version": "0.15.24", "description": "Lets Pi control tabs in Chrome via a local connector at 127.0.0.1.", "permissions": [ "tabs",
M extensions/chrome-profile-bridge/index.ts → extensions/chrome-profile-bridge/index.ts

@@ -441,6 +441,28 @@

const tabActionValues = ["list", "new", "activate", "close", "version"] as const; const imageFormatValues = ["png", "jpeg"] as const; const waitForValues = ["selector", "expression"] as const; +const CHROME_TOOL_NAMES = [ + "chrome_launch", + "chrome_tab", + "chrome_snapshot", + "chrome_navigate", + "chrome_evaluate", + "chrome_click", + "chrome_type", + "chrome_fill", + "chrome_key", + "chrome_wait_for", + "chrome_list_console_messages", + "chrome_list_network_requests", + "chrome_get_network_request", + "chrome_screenshot", + "chrome_hover", + "chrome_drag", + "chrome_tap", + "chrome_scroll", + "chrome_upload_file", +] as const; +const CHROME_TOOL_NAME_SET = new Set<string>(CHROME_TOOL_NAMES); function StringEnum<T extends readonly [string, ...string[]]>(values: T) { return Type.Union(values.map((value) => Type.Literal(value)) as [ReturnType<typeof Type.Literal>, ...ReturnType<typeof Type.Literal>[]]);

@@ -464,6 +486,30 @@ const bridge = new ChromeProfileBridge(DEFAULT_HOST, DEFAULT_PORT);

let backgroundDefault = false; let chromeAuthorizedUntil: number | "indefinite" | undefined; let chromeToolsRegistered = false; + let authExpiryTimer: NodeJS.Timeout | undefined; + + const clearAuthExpiryTimer = (): void => { + if (!authExpiryTimer) return; + clearTimeout(authExpiryTimer); + authExpiryTimer = undefined; + }; + + const activeToolNamesWithoutChrome = (): string[] => pi.getActiveTools().filter((name) => !CHROME_TOOL_NAME_SET.has(name)); + + const activateChromeTools = (): void => { + registerChromeTools(pi); + pi.setActiveTools([...new Set([...pi.getActiveTools(), ...CHROME_TOOL_NAMES])]); + }; + + const deactivateChromeTools = (): void => { + pi.setActiveTools(activeToolNamesWithoutChrome()); + }; + + const lockChromeControl = (): void => { + clearAuthExpiryTimer(); + chromeAuthorizedUntil = undefined; + deactivateChromeTools(); + }; const authSummary = (): string => { if (chromeAuthorizedUntil === "indefinite") return "authorized indefinitely";

@@ -477,7 +523,7 @@

const chromeControlAuthorized = (): boolean => { if (chromeAuthorizedUntil === "indefinite") return true; if (typeof chromeAuthorizedUntil === "number" && chromeAuthorizedUntil > Date.now()) return true; - chromeAuthorizedUntil = undefined; + if (chromeAuthorizedUntil !== undefined) lockChromeControl(); return false; };

@@ -495,6 +541,21 @@ ctx.ui.setStatus("chrome", undefined);

} }; + const scheduleAuthExpiry = (ctx: ExtensionContext, until: number | "indefinite"): void => { + clearAuthExpiryTimer(); + if (until === "indefinite") return; + authExpiryTimer = setTimeout(() => { + if (chromeAuthorizedUntil !== until) return; + try { + lockChromeControl(); + ctx.ui.notify("Chrome control authorization expired. Run /chrome authorize to allow chrome_* tools again.", "info"); + updateChromeStatus(ctx); + } catch (error) { + console.warn(`Failed to expire pi-chrome authorization cleanly: ${(error as Error).message}`); + } + }, Math.max(0, until - Date.now())); + }; + const authorizedBridgeSend = (action: string, params: Record<string, unknown>, timeoutMs = DEFAULT_TIMEOUT_MS, signal?: AbortSignal): Promise<unknown> => { requireChromeControlAuthorized(); return bridge.send(action, params, timeoutMs, signal);

@@ -520,6 +581,7 @@ updateChromeStatus(ctx);

}); pi.on("session_shutdown", () => { + clearAuthExpiryTimer(); bridge.stop(); if (globalState[PI_CHROME_GLOBAL_KEY]?.token === instanceToken) { delete globalState[PI_CHROME_GLOBAL_KEY];

@@ -653,8 +715,9 @@ if (!ok) {

ctx.ui.notify("Chrome control remains locked.", "info"); return; } - registerChromeTools(pi); chromeAuthorizedUntil = until; + activateChromeTools(); + scheduleAuthExpiry(ctx, until); ctx.ui.notify(`Chrome control authorized for ${label}.`, "info"); updateChromeStatus(ctx); };

@@ -677,7 +740,7 @@ return authorizeFor(ctx, grant.label, grant.until);

}; const revokeHandler = (ctx: ExtensionContext) => { - chromeAuthorizedUntil = undefined; + lockChromeControl(); ctx.ui.notify("Chrome control locked. Run /chrome authorize to allow chrome_* tools again.", "info"); updateChromeStatus(ctx); };
M package-lock.json → package-lock.json

@@ -1,12 +1,12 @@

{ "name": "pi-chrome", - "version": "0.15.23", + "version": "0.15.24", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-chrome", - "version": "0.15.23", + "version": "0.15.24", "license": "MIT", "peerDependencies": { "@earendil-works/pi-coding-agent": "*",
M package.json → package.json

@@ -1,6 +1,6 @@

{ "name": "pi-chrome", - "version": "0.15.23", + "version": "0.15.24", "scripts": { "version": "node scripts/sync-manifest-version.js", "prepublishOnly": "node scripts/sync-manifest-version.js"