| Age | Commit message (Collapse) | Author |
|
The grant lives on globalThis so /reload keeps it, but nothing cleared it
when pi replaced the session in the same process. After /new, /resume or
/fork the next session started authorized and announced it in the footer.
Drop the saved grant on session_shutdown for new, resume and fork. Reload
keeps it. Quit leaves it alone, so a subagent quitting in the same process
does not take its parent's grant with it.
grant-scope.test.mjs loads the shipped module once per runtime in one
shared context, the way pi keeps globalThis across reloads and switches,
and checks the tools and the footer after each. It fails on 85b4988, and
its /chrome authorize cases fail with only the PI_CHROME_AUTHORIZE fix.
|
|
The env grant was saved to globalThis like a /chrome authorize grant. After
a /reload or a session switch (/resume, /fork, /new) the new runtime restored
it, skipped the env branch, and left envGranted false, so the footer
announced a grant the user never gave.
Read the variable again on each session_start instead of persisting it.
A restored grant now always comes from /chrome authorize. A timed env grant
restarts its countdown on each reload or switch.
|
|
A standing PI_CHROME_AUTHORIZE is not session state worth a line at the
bottom of every session that has it; announce only access the user granted
through /chrome authorize.
|
|
PI_CHROME_REMOTE=host:port sends every command to a broker/broker.mjs there,
with its bearer token, instead of binding a bridge on this machine. Unset, the
extension behaves as upstream. A remote session never binds or takes over a
port: when the broker is unreachable the tools fail rather than drive some
other browser.
A pi-chrome-remote prompt section says which browser chrome_* drives and how
files move to and from its host; chrome_upload_file wants absolute paths there.
PI_CHROME_AUTHORIZE grants every session up front.
|
|
Chrome ignores clicks and typing in hidden pages. Doctor now asks the worker
whether this session's own tab is visible (automation.status includeVisibility,
which never creates or activates a tab) and reports the reason and fix when it
is hidden.
|
|
- Contribute the primer as a structured prompt section instead of forcing systemPrompt.
- Log pi-chrome-tool-change as a rendered custom entry that is not sent to the model.
|
|
screenshots; release 0.15.54
- Check page visibility before trusted input: Chrome drops input to hidden pages (inactive tabs,
covered/minimized windows, macOS window tabs behind full screen). Type/key/hover/scroll/tap/drag
fail fast with the reason; click/fill use their DOM fallback immediately (or reject with
domFallback:false).
- Use an inactive tab instead of a new window for the automation target while the user's window is
full screen.
- chrome_type with uid/selector moves the caret to the end (moveToEndOfDocument) after its focus click.
- chrome_upload_file: labels/wrappers resolve to their file input, same-origin iframe search, and
intercepted file chooser for upload buttons (trusted click when visible, user-gesture click when
hidden); reject multiple paths for single-file inputs; no duplicate change event.
- Prune default-folder chrome_screenshot captures (7 days, newest 20 kept, retentionDays override).
- input.debug without a target no longer calls tabs.get(-1).
- New hidden-input unit suite.
|
|
Adapted from community forks (ardhiqii, kkunkunya, nihar-oracle, steimerbyte):
- Abort stalled /next long polls after 45s; time out, retry, and single-post results.
- Fall back to CDP Runtime.evaluate when chrome.scripting is refused (about:blank automation tabs).
- Load pi-chrome in same-process subagent sessions as a bridge client; client chrome_launch reports owner status.
- Add chrome_cdp (validated raw CDP passthrough, background-guarded, payload summarization) and chrome_cdp_targets.
- chrome_type reports before/after values, insertedAt, splice/unchanged warnings; add replace=true.
- includeSnapshot waits for navigations started by the action; tab.new reports the settled tab.
- CDP timeouts surface as timeouts and are not re-sent after the cleanup detach.
- Activate/deactivate chrome_find and chrome_inspect with the rest of the chrome_* tools.
- New unit suites: bridge-resilience, cdp-passthrough, type-evidence.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Stop pi-chrome from hijacking the user's active tab. Page/navigation actions
with no explicit target now route to a dedicated automation window owned by the
calling Pi session (falling back to a dedicated tab if a window can't be made),
instead of overwriting whatever the user has open.
- Session-scoped ownership: Map<sessionKey, target> in the service worker, keyed
by a sessionKey forwarded from index.ts, so concurrent sessions (one shared
extension) never fight over a window or close each other's tabs.
- Persistence: ownership mirrored to chrome.storage.session so a service-worker
restart re-hydrates and reuses the window instead of orphaning it. Cleared on
full browser restart; restored windows are then left alone (never reclaimed).
- getTabByParams default branch resolves the owned target; tab.activate/close/
group/ungroup pass createOwnedTarget:false so management never auto-creates a
throwaway tab or falls back to the user's active tab.
- Session-group integration: the dedicated-window tab still joins this session's
tab group, in its own window; grouping stays best-effort.
- Lifecycle: cleanup on /chrome revoke and real session end (not /reload);
fire-and-forget so it never blocks /quit, /reload, or shutdown.
- Tests: new test-suite/unit/automation-target.test.mjs (isolation, reuse,
concurrency, restart/reconnect re-hydration, guarded tab.*, group integration,
window->tab fallback, robust cleanup). Docs + CHANGELOG updated.
|
|
* 0.15.36: richer chrome_snapshot + chrome_find/chrome_inspect (ported from foxfirecodes fork)
- chrome_snapshot returns a concise observation (layout, hints, actions, forms,
page map, query matches, diff) with mode/query/maxTextChars params
- new chrome_find (query -> ranked uids) and chrome_inspect (deep element context,
with page.inspect fallback)
- includeSnapshot on click/type/fill/key now embeds the formatted snapshot
- snapshot/inspect logic shipped as eval-free packaged snapshot_injected.js,
sharing window.__PI_CHROME_STATE__ (el- uid scheme) with existing input helpers
* feat: show remaining auth time in Chrome Bridge status bar
Before: ● Chrome Bridge
After: ● Chrome Bridge (14m)
● Chrome Bridge (indefinite)
Changes:
- authCountdownLabel() computes remaining minutes from chromeAuthorizedUntil
- updateChromeStatus appends the label to the status line
- startCountdownTicker() runs setInterval every 60s to refresh the display
while a timed authorization is active; no-ops for indefinite auth
- clearCountdownInterval() called in lockChromeControl and session_shutdown
so no interval leaks on revoke or reload
- scheduleAuthExpiry calls startCountdownTicker so the ticker starts
whenever authorization is granted or restored after /reload
---------
Co-authored-by: Tianren Dong <tianrendong.dtr@gmail.com>
|
|
|
|
|
|
- chrome_snapshot returns a concise observation (layout, hints, actions, forms,
page map, query matches, diff) with mode/query/maxTextChars params
- new chrome_find (query -> ranked uids) and chrome_inspect (deep element context,
with page.inspect fallback)
- includeSnapshot on click/type/fill/key now embeds the formatted snapshot
- snapshot/inspect logic shipped as eval-free packaged snapshot_injected.js,
sharing window.__PI_CHROME_STATE__ (el- uid scheme) with existing input helpers
|
|
chrome_* tools on session_start)
|
|
chrome_type/chrome_key (US-layout keymap)
|
|
|
|
|
|
|
|
merge by title
|
|
evaluate/snapshot/waitFor/initScript
- evaluateInTab/executeInTab now run through CDP Runtime.evaluate instead of
new Function() in MAIN world, so chrome_evaluate/chrome_snapshot work on pages
that block unsafe-eval (github.com, many bank/SaaS apps). Preserves value
markers (undefined/function/symbol/bigint/Error), DOMRect expansion, and the
expression/statement fallback; keeps {ok,value} error envelope.
- page.waitFor polls via service-worker-side CDP evaluation.
- chrome_navigate initScript uses Page.addScriptToEvaluateOnNewDocument.
- Add npm test (test-suite/unit/csp-eval.test.mjs) and in-browser regression
challenge 42 strict-csp-evaluate; update challenge 39 notes + docs/primer.
|
|
DOMRect serialization, stale-target errors, pageMutated wording
|
|
|
|
|
|
Defer all pi.registerTool() calls until /chrome authorize fires.
Previously, 18 chrome_* tool definitions + chrome-profile-bridge
primer consumed ~5K tokens/turn even when Chrome control locked.
Changes:
- Add chromeToolsRegistered flag (default false)
- Wrap all pi.registerTool() calls in registerChromeTools(pi)
- Only register tools when authorizeFor() runs
- Make before_agent_start conditional: primer injected only when
tools registered AND authorized
- No tools exist in context until user explicitly allows it
Saves ~5,000 tokens/turn when Chrome control is locked.
|
|
- Remove startup notification about companion extension
- Remove static 'Chrome bridge :17318' status bar entry
- Add updateChromeStatus() helper: shows green dot + 'Chrome Bridge'
only when authorized, clears otherwise
- Call from session_start, authorizeFor, revokeHandler
|
|
Reverts the pairing + signed-envelopes work (0.16.0-0.16.1), the
standalone daemon refactor (0.17.0-0.17.3), and assorted patches.
These were unpublished from npm; this commit restores the working
tree to match the v0.15.20 release so the branch tip and the npm
'latest' dist-tag are consistent again.
Tags v0.16.0 through v0.17.3 are preserved as recovery anchors;
the work is reachable via 'git checkout v0.17.3' if ever needed.
Tree-equivalent to v0.15.20.
|
|
In 0.17.x the daemon is a separate process that can idle-shut-down
(10 min idle) or be killed externally while Pi sessions are running.
Without self-heal, the next /chrome onboard or chrome_* call surfaces
'fetch failed' instead of recovering.
bridge.admin() now catches the failed fetch, re-runs ensureDaemonRunning
(probe -> spawn if missing), and retries once.
bridge.sendViaOwner() pre-probes /status and spawns the daemon if missing
before issuing the signed /command.
Net effect: a missing daemon transparently respawns instead of bubbling
fetch failed up to the user.
|
|
Bugs surfaced in real-world 0.17.0/0.17.1 testing:
1. Daemon /next only recorded lastSeenAt AFTER the auth/pair checks. Pre-pair
the daemon returns needsPairing early without recording the poll, so
connected stayed false even though the extension was polling. /chrome
onboard's wait-for-poll loop spun forever on a healthy extension.
Fix: record lastSeenAt at the top of /next for any chrome-extension
origin, before auth checks. Onboard now correctly detects 'extension
alive but unpaired' which is the precise state during pair step.
2. Daemon compatibility was checked at major.minor match. Result: a patch
release fixing a daemon bug (like #1) couldn't replace the running
daemon — new Pi client joins old buggy daemon as client.
Fix: exact version match. Patch releases auto-kill+respawn the daemon.
|
|
0.17.0 made the daemon own the HTTP listener, but the Pi client's
bridge.status().connected still reflected the local Pi's lastSeenAt
(which is never updated in the daemon model). Onboard's connect-wait
loop spun forever even when the extension was polling the daemon.
Fix: new ChromeProfileBridge.daemonStatus() async helper that fetches
the daemon's /status. onboard, doctor, and status now query that for
the 'connected' flag instead of reading stale Pi-side state.
|
|
The Chrome bridge is now a separate detached daemon at
~/.cache/pi-chrome/<version>/daemon.cjs. Every Pi session is a uniform client;
daemon lifecycle is decoupled from any Pi session. Killing a Pi never breaks
Chrome control, and pi update followed by any new Pi session auto-replaces
an incompatible daemon.
Architecture:
- New extensions/chrome-profile-bridge/daemon.cjs: ~700 LOC, zero deps, runs
detached. Owns the HTTP listener, BridgeAuth state, command queue, /pair
handshake. Idle-shuts-down after 10 min of no activity.
- ChromeProfileBridge on Pi side becomes a pure client: start() probes /status,
spawns daemon if missing/incompatible, kills+respawns if version mismatch.
send() always uses signed /command. No more server-mode branch.
- New signed /admin RPC for pair-window/reset/status state mutations the
daemon owns. Pi clients sign with broker key (same contract as /command).
- New /heartbeat for daemon idle-timer keepalive.
- daemon.log at ~/.cache/pi-chrome/<version>/daemon.log.
/chrome pair, /chrome unpair, /chrome onboard now go through /admin RPCs
instead of mutating in-process bridge state.
Removed the foreign-owner takeover dance from /chrome onboard (auto-handled
at session_start now).
Wire protocol is byte-identical to 0.16.x; existing paired extensions don't
need to re-pair. See docs/DESIGN-0.17-daemon.md for the full write-up.
|
|
User pain: machines stuck in broken state after pi-chrome upgrades because an old
pi-chrome process kept holding :17318 and /chrome onboard trusted confirm() clicks
without verifying anything. Both fixed.
Idempotent /chrome onboard recovery flow:
- Always resets Pi-side pairing state first (kills half-paired ghosts).
- Step 1 covers both first-install AND v0.15->v0.16 reload (extension service worker
must reload to gain the /pair logic).
- Actively verifies progress at each step rather than trusting clicks:
- polls bridge.status().connected (15s window) to confirm the extension polls /next,
- polls bridge.bridgeAuth.paired to confirm the /pair handshake completed,
- round-trips a real tab.version command to confirm the signed-envelope path works.
- /chrome install is now an alias of /chrome onboard.
Automatic stale-owner takeover at bridge.start():
- /status now includes bridgeVersion.
- On EADDRINUSE, probe /status with 1s timeout. If unresponsive OR major.minor
mismatches, locate the owner PID via lsof and kill it (SIGTERM -> SIGKILL),
then re-bind. Same-version owners are joined as clients (multi-Pi sharing).
- Eliminates the 'stale Pi session holding bridge' class of bugs that broke
upgrades from 0.15.x.
/chrome status / doctor / onboard no longer hang:
- New probeExtension() short-circuits before bridge.send when the bridge can't
round-trip (unpaired, or extension hasn't polled recently). Returns immediately
with an actionable message instead of waiting 5-35s.
|
|
Major security hardening of the Chrome bridge driven by red-team findings.
- Mandatory pairing via /chrome onboard (one-time per machine). Bridge pins exact
chrome-extension:// origin and derives HMAC keys via HKDF. Until paired, the
bridge returns idle responses and refuses to deliver commands.
- Signed envelopes on /next, /result, /command. HMAC-SHA256 over protocol version,
direction, method, path, extension ID, bridge ID, timestamp, fresh nonce, and
SHA-256 of the body. +/-30s clock window; 5-min LRU nonce replay defense on
both request and response directions; constant-time MAC compare.
- Peer Pi sessions sharing the bridge sign each /command with a broker HKDF key
(no bearer token transmitted) and verify the owner's signed response.
- Global loopback enforcement. PI_CHROME_BRIDGE_HOST=0.0.0.0 ignored unless
PI_CHROME_BRIDGE_DANGEROUS_REMOTE=1; loopback check still applies per-endpoint.
- Cross-process state coherence: fs.watch + per-request mtime check on
~/.config/pi/chrome-bridge.json. A peer Pi's unpair (or any external rm/edit)
propagates to the owner's in-memory state before the next privileged verify;
replay caches reset on key rotation.
- Body cap 1 MiB -> 413. Queue/pending/waiter caps. Malformed JSON -> 400.
Non-object JSON bodies -> 400 instead of being trusted.
- page.navigate initScript: try/finally around tabs.update, per-call onCommitted
listener with bounded commit-wait window, one-shot delete in global onCommitted.
No more stale init scripts firing on later unrelated navigations.
- crypto.randomUUID() command IDs.
- Manifest diet (dropped activeTab); added popup.html/popup.js for the pair UI.
UX:
- /chrome onboard runs install + pair in one interactive flow.
- /chrome help reorganized into First-time / Day-to-day / Maintenance.
- Status bar lights up as green dot only when authorized.
- Early console/network capture at document_start so boot-time React errors and
initial API calls aren't dropped (ported from DaniBedz/pi-chrome fork).
Docs:
- SECURITY.md threat model rewritten with in-scope / out-of-scope sections.
- README rewritten with the install-then-pair flow front and center.
- CHANGELOG 0.16.0 entry with full patch list.
|
|
Thread the harness AbortSignal through authorizedBridgeSend, the
ChromeProfileBridge send/sendLocal/sendViaOwner paths, and every
chrome_* tool's execute(). Pressing Esc now rejects the in-flight
bridge call immediately (most visibly fixing chrome_wait_for, which
would otherwise block for the full timeoutMs).
|
|
|
|
|
|
|
|
|
|
- Primer teaches agents the trusted:true escape hatch for CSP-locked pages
(github.com, banks, many SaaS) where chrome_evaluate/chrome_snapshot throw
EvalError due to missing 'unsafe-eval'. Agents now self-escalate instead
of giving up and asking the user.
- Corrects stale primer claim that chrome_evaluate works without 'unsafe-eval'.
- scripts/sync-manifest-version.js wired to npm version + prepublishOnly
lifecycle hooks; kills the recurring browser-extension manifest version
drift (cf. 0.14.4, 0.14.8).
|
|
When a different Chrome extension (password manager, autofill popup,
etc.) anchors an overlay to the focused input, Chrome's CDP refuses
our Input.dispatchMouseEvent with 'Cannot access a chrome-extension://
URL of different extension'. The CDP target list shows the foreign
chrome-extension:// URL as type:'other' attached to the same tab.
New behaviour in cdp() wrapper:
1. On that specific error for any Input.* call, snapshot CDP targets
to identify the offending extension id and log it (foreign-ext-
detected event added to ring buffer).
2. Send Esc via Input.dispatchKeyEvent (key dispatch routes by focus,
not coordinates, so it bypasses the overlay) to dismiss the popup.
3. Retry the original Input.* call once.
4. If retry still fails, throw a clear error naming the foreign
extension id and listing three concrete workarounds: disable that
extension on the page, use Tab-to-focus instead of clicking, or
run /chrome quiet off.
|
|
enter=save)
Bare /chrome no longer opens a sequential picker. It now opens an inline
SettingsList from pi-tui with two rows:
Click realism auto / on / off
Quiet mode on / off
Up/Down navigate, Space or Enter cycles the value of the focused row,
Esc closes. Each row's description updates live to match the current
value, so users always see what the active setting means.
When the Chrome extension hasn't been granted the debugger permission
(real-looking clicks unavailable), the Click realism row stays locked
on its current value and the description points to the fix.
Direct subcommands unchanged:
/chrome status / doctor / onboard
/chrome clicks [auto|off|on|status] (no-args still cycles)
/chrome quiet [on|off|toggle|status] (no-args still toggles)
|
|
status header
Reorganises the /chrome command surface:
/chrome status - one-line snapshot of connection + click mode + quiet mode
/chrome doctor - full health check (unchanged)
/chrome onboard - install extension (unchanged)
/chrome clicks - cycles auto -> on -> off -> auto when called with no args.
Explicit args (auto|off|on|status) still work.
/chrome quiet - toggles on/off when called with no args.
Explicit args (on|off|toggle|status) still work.
Removes the nested 'settings' wrapper. /chrome settings background/trusted
still functions as a hidden legacy alias but is no longer advertised.
Bare /chrome opens a picker with a one-line status header and cycle-target
labels: 'clicks: auto -> switch to on' / 'quiet: off -> switch to on'. No
second picker; one keypress flips the setting.
Argument autocomplete now returns full-path values (e.g. 'clicks auto') so
pi-tui's whole-argument replacement applies correctly. Top-level suggestions
show clicks / quiet / status / doctor / onboard with descriptions.
User-visible names match the descriptive copy:
- 'trusted' -> 'clicks' (matches 'real-looking clicks' / 'quiet clicks')
- 'background' -> 'quiet' (matches 'quiet mode' / 'watch mode')
README + agent prompt primer + internal hint strings updated.
|
|
Replaces /chrome-doctor, /chrome-onboard, /chrome-background,
/chrome-trusted with a single /chrome command that takes subcommands:
/chrome doctor (was /chrome-doctor)
/chrome onboard (was /chrome-onboard)
/chrome settings background [on|off] (was /chrome-background)
/chrome settings trusted [auto|off|on|status] (was /chrome-trusted)
Running /chrome with no arguments opens a picker. Argument auto-
completion walks the tree (e.g. type '/chrome se<Tab>' to land on
'settings', then '/chrome settings t<Tab>' to land on 'trusted').
All user-visible references (notify messages, doctor output, agent
prompt primer, README) updated to the new paths.
|