Commit Graph
825 Commits
Author SHA1 Message Date
leeguooooo 1a4c440d9e ci: fix long-broken CI (version-sync, dead dashboard job, fmt, clippy, flaky test)
The fork's CI had never been green. Pre-existing failures:
- version-sync: check-version-sync.js read packages/dashboard/package.json,
  which doesn't exist in this fork (workspace is just "."). Drop the dashboard
  comparison; check package.json vs cli/Cargo.toml only.
- Dashboard job: `pnpm install --filter dashboard` for a non-existent package.
  Remove the job.
- Format check: repo was never `cargo fmt`-clean. Ran cargo fmt (mechanical).
- Clippy -D warnings (newly enforced on Rust 1.94 stable): manual_contains in
  commands.rs (.iter().any()->.contains()), question_mark in element.rs
  (if-let-Err -> ?), result_large_err on the tungstenite handshake callback in
  connect.rs (allow — the Result type is fixed by the accept_hdr_async contract).
- rust-cross: lightpanda::waits_for_ready_without_logs spawns a real process +
  binds a socket with timing assumptions; flaky in CI. Marked #[ignore].

Also: skill docs note fork.30's relay-preferred auto-connect (plain
`agent-browser open` is dialog-free once the ab-connect extension is loaded) and
the extension's new "agent-browser-stealth" display name.
2026-06-10 11:49:11 +09:00
leeguooooo d1fbdaadeb chore(release): 0.27.0-fork.30 — stealth: navigator overrides on prototype, not instance
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
rebrowser's navigatorWebdriver probe checks Object.getOwnPropertyNames(navigator)
== [] (real Chrome keeps navigator members on Navigator.prototype). The launch-mode
stealth script defined language/languages/userAgentData/contacts as instance
own-properties, leaking them as an automation tell.

- add __abRedefineNavProto(name, getterImpl): redefines a navigator member on the
  PROTOTYPE with a native-masked getter toString, then deletes any instance shadow
  (mirrors the existing vendor patch). Falls back to instance only if proto is locked.
- convert language/languages/userAgentData to it; make the contacts block prototype-first.

After: Object.getOwnPropertyNames(navigator) == [], values intact, getters native,
rebrowser navigatorWebdriver 🟢, runtimeEnableLeak/pwInitScripts 🟢, sannysoft 0 fails.
v0.27.0-fork.30
2026-06-10 11:35:35 +09:00
leeguooooo 839aaa5586 chore(release): 0.27.0-fork.29 — plugin overflowTest fix, popup-free auto-connect, ab-connect rebrand+icon, README
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
- stealth(plugins): stop overwriting real native navigator.plugins in headed
  mode (the JS fake had a non-native item(), broken uint32 wrap → incolumitas
  overflowTest FAIL, and an anachronistic Native Client plugin). Leave native
  plugins untouched when present; modernize the headless-escape fallback to the
  real 5 PDF-viewer set with masked-native item()/namedItem().
- connect: auto_connect_cdp() now prefers the dialog-free ab-connect relay over
  the raw :9222 CDP port, so Chrome 136+'s "Allow remote debugging?" consent
  modal no longer fires when the extension relay is live. Gated by a bare-TCP
  relay_is_live() probe (+3 unit tests).
- extension: rename ab-connect to "agent-browser-stealth" + new stealth icon set
  (16/32/48/128).
- docs(README): hero/shield/fingerprint images, expanded detector results
  (CreepJS 0% stealth, incolumitas all-OK, BrowserScan CDP-clean), and a
  "Verify it yourself" section. .gitignore: allow assets/ + extension icons.
v0.27.0-fork.29
2026-06-10 11:17:41 +09:00
leeguooooo a7f9c24fdb chore(release): 0.27.0-fork.28 — skill docs (headed default, tab groups) embedded
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.28
2026-06-10 09:58:35 +09:00
leeguooooo 42ade7b4e8 docs(skill): headed-default/headless-forbidden + per-session tab groups + stealth ranking
Update the served skill (skill-data/core, embedded into the binary) for tonight's
changes: --headed is the default and headless is FORBIDDEN (was wrongly 'default
is headless'); each --session on the extension-connect path gets its own colored
tab group with no cross-talk; anti-detection ranking real-Chrome(extension) >
headed-launch > headless(forbidden). Needs a rebuild so standalone installs'
embedded skill reflects it.
2026-06-10 09:58:33 +09:00
leeguooooo 2dabed973e chore(release): 0.27.0-fork.27 — forbid headless (always headed for stealth)
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.27
2026-06-10 09:52:40 +09:00
leeguooooo dd2deff06c feat(stealth): forbid headless — always launch headed
Headless Chrome is a bot-detection tell: creepjs scores ~33% headless even with
--headless=new, while a headed window with a real GPU scores 0%. Since this is a
stealth fork, headless is now forbidden — build_chrome_args ignores the headless
LaunchOption and never emits --headless/--enable-unsafe-swiftshader/forced
--window-size. The only escape is AGENT_BROWSER_ALLOW_HEADLESS=1 for genuinely
display-less servers (discouraged — forfeits stealth).

Verified locally: default launch (no env) is headed (webdriver=false,
platform=MacIntel, no --headless flag); creepjs headed = 0% headless vs 33%
headless. chrome.rs: 48 tests pass incl. forbids-headless + escape.
2026-06-10 09:52:39 +09:00
leeguooooo 340886293a chore(release): 0.27.0-fork.26 — stealth navigator.platform=MacIntel (anti-detection fix)
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.26
2026-06-10 08:46:43 +09:00
leeguooooo fc1699a526 fix(stealth): navigator.platform = MacIntel/Win32/Linux x86_64 (was UA-CH value)
platform_string() feeds the CDP Emulation.setUserAgentOverride 'platform' field,
which sets the LEGACY navigator.platform. It was returning the UA-CH form
("macOS"/"Linux") — but real Chrome reports navigator.platform = "MacIntel" on
macOS and "Linux x86_64" on Linux. "macOS" contradicts the UA's "Intel Mac OS X"
and is a trivial bot-detection tell (platform vs UA mismatch). UA-CH
(navigator.userAgentData.platform via platform_hint) stays "macOS"/"Windows"/
"Linux" — that form is correct there.

Verified locally on bot.sannysoft.com (all rows green incl. navigator.platform=
MacIntel) + eval probes: webdriver false, no Headless in UA, real WebGL
(Apple M3 Metal, not SwiftShader), plugins/permissions consistent.
2026-06-10 08:46:42 +09:00
leeguooooo 4bcfe74514 chore(release): 0.27.0-fork.25 — relay liveness fix (Browser.getVersion local) stops reconnect-storm drift
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.25
2026-06-10 01:38:19 +09:00
leeguooooo bb41c24c08 fix(connect): relay answers Browser.getVersion locally (stops reconnect storm)
ROOT CAUSE of per-session command drift on the extension path: the daemon's
liveness check (`is_connection_alive` → `Browser.getVersion`) is a BROWSER-level
command. The relay only answered Target.* locally and forwarded the rest, so
Browser.getVersion went to the extension, which can only do per-tab
chrome.debugger → it errored → CdpClient saw TransportError → connection deemed
DEAD → the daemon closed + reconnected + re-ran discover_and_attach_targets on
EVERY command. Each re-discover rebuilds pages from the relay's minimal
targetInfo and resets active_page_index=0, so eval/get-title/screenshot drifted
to the first tab (about:blank / a foreign focused tab).

Reproduced locally (throwaway Chrome + Extensions.loadUnpacked + fork.24 nm-host):
trace showed discover_and_attach_targets running on every command (pages
before=0) and [ev] active_idx reset to 0.

Fix: relay answers Browser.getVersion locally with a stub version (like
getTargets), so the liveness probe succeeds → connection stays alive → no
reconnect/re-discover → the session's active tab is preserved. Pairs with
fork.24's add_background_page. relay.rs: 10 unit tests.
2026-06-10 01:38:18 +09:00
leeguooooo 75bd1d21a7 chore(release): 0.27.0-fork.24 — passive tab discovery no longer hijacks active tab (per-session control)
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.24
2026-06-10 00:42:16 +09:00
leeguooooo 06c75af46a fix(connect): passively-discovered tabs no longer steal the active tab
After connect+grouping worked, follow-up eval/get-title/screenshot drifted to a
foreign tab: on a shared browser, Target.targetCreated events for tabs the user
or OTHER agent sessions open stream in and are drained on every command. The
drain path routed them through add_page(), which sets active_page_index to the
new page — so the session's active tab silently jumped to a foreign tab and its
commands landed there.

Add BrowserManager::add_background_page() (push without touching active, dedup by
target_id) and use it in the event-drain path. Explicit opens (tab new, the
add-and-switch paths) keep using add_page() and still focus the new tab.

Closes the last gap in concurrent multi-agent: each session now drives its OWN
tab regardless of other sessions'/the user's tab activity.
2026-06-10 00:42:16 +09:00
leeguooooo 312bb0d65b chore(release): 0.27.0-fork.23 — tolerate minimal targetInfo from relay (extension connect getTargets)
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.23
2026-06-10 00:07:59 +09:00
leeguooooo cff003c333 fix(connect): tolerate minimal targetInfo (relay re-announce omits title/url)
After the connect fix, extension connect reached the relay but Target.getTargets
failed: 'missing field title'. The ab-connect relay builds targets from the
extension's synthesized Target.attachedToTarget; the re-announce path
(reannounceAttachedTabs) emits a minimal targetInfo {targetId,type,attached}
with no title/url, so strict deserialize of TargetInfo blew up the whole
getTargets response.

Make TargetInfo.title/url #[serde(default)] (empty) — tolerant of minimal CDP
targetInfo from the relay (and the occasional real-CDP omission). Titles
re-populate from Target.targetInfoChanged / page events after attach.
2026-06-10 00:07:58 +09:00
leeguooooo f2b0c2ea9b chore(release): 0.27.0-fork.22 — extension connect uses relay URL (fixes --session connect hang)
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.22
2026-06-09 23:39:42 +09:00
leeguooooo ea58bce19e fix(connect): extension connect now uses the relay URL (was falling through to auto-connect)
`extension connect` rewrote argv to ["connect", <relay-url>] but the connect path
reads flags.cdp — parsed earlier from the original argv ("extension connect" →
None). So the relay URL was dropped and the daemon ran AUTO-CONNECT, grabbing
whatever Chrome it could discover: a stale remote-debugging Chrome on :9222
(indefinite hang), or triggering Chrome's "Allow remote debugging?" prompt on
machines without one. This is the EAGAIN/hang hermes hit on --session connect.

Fix: set flags.cdp = Some(relay_url) (+ disable auto_connect) in the
extension-connect branch so the daemon connects to the live relay endpoint.
Diagnosed via local repro (trace showed connect_cdp resolving ws://...:9222/
devtools/browser/... instead of the relay's ws://...:<port>/<guid>).
2026-06-09 23:39:41 +09:00
leeguooooo afb68ded93 chore(release): 0.27.0-fork.21 — multi-client relay (concurrent agents)
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.21
2026-06-09 22:47:30 +09:00
leeguooooo a6631cd7d8 fix(connect): multi-client relay — concurrent agents no longer cross-talk
The nm-host fanned extension→client messages over a broadcast channel and
forwarded commands under the client's own id, so two sessions connected to one
relay collided: command replies went to every client and ids overlapped → the
2nd session's connect hung (EAGAIN after 30s×5) and responses cross-talked.

Now the relay demultiplexes:
- each forwarded command is re-keyed to a relay-global id mapped to (client,
  original_id); the extension's reply routes back to ONLY that client with its
  original id restored (relay.rs: pending map + ClientId)
- CDP events fan out to all clients (they ignore unknown sessions)
- nm-host keeps a client_id -> sender registry instead of a broadcast; clients
  are unregistered + their pending dropped on disconnect

Unblocks concurrent multi-agent on one shared Chrome (each --session its own tab
group from fork.20). relay.rs: 9 unit tests incl. cross-client id isolation.
2026-06-09 22:47:30 +09:00
leeguooooo 4f630e29ad chore(release): 0.27.0-fork.20 — per-session tab groups (ab-connect 0.4.0)
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.20
2026-06-09 21:17:54 +09:00
leeguooooo d232763ff7 feat(connect): per-session Chrome tab groups on the shared real browser
Shared browser, separate tab groups: when an agent drives the user's real Chrome
via ab-connect, every tab it opens lands in a Chrome tab group named after its
--session (stable color per name). Each agent's tabs stay visually separated from
other agents' and from the user's own (ungrouped) tabs. Visibility is NOT
restricted — all agents still see all tabs (per design).

- CreateTargetParams gains an optional non-CDP `agentGroup` hint (skip-if-none),
  so a strict real-Chrome endpoint never receives it
- BrowserManager.agent_group(): Some(session) only when ws_url == the live
  ab-connect relay URL (never on launched/direct CDP); DAEMON_SESSION set at
  daemon start supplies the name; emitted at all createTarget sites (transient
  storage target stays None)
- ab-connect: +tabGroups permission; Target.createTarget reads agentGroup and
  groups the new tab (create/reuse by title, deterministic color), best-effort
- extension 0.3.0 -> 0.4.0; re-signed crx + zip (id unchanged)

Needs the v0.4.0 extension reloaded + a build with this change to take effect.
2026-06-09 21:17:53 +09:00
leeguooooo 85f4635358 chore(release): 0.27.0-fork.19 — new extension id (Web Store signing key) + store-aware install
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
Transport (native messaging + extension connect) works today via Load unpacked.
Silent force-install is pending the Chrome Web Store listing going live (off-store
force-install is [BLOCKED] on unmanaged Chrome 149).
v0.27.0-fork.19
2026-06-09 19:20:45 +09:00
leeguooooo 726e9d4ea3 chore(store): add listing screenshot (force-add past png ignore) 2026-06-09 19:16:04 +09:00
leeguooooo ff8a340269 chore(store): add 1280x800 listing screenshot + bake in GitHub Pages privacy URL 2026-06-09 19:15:49 +09:00
leeguooooo c1fa237183 chore: add .nojekyll for GitHub Pages (serve privacy policy as-is) 2026-06-09 19:10:32 +09:00
leeguooooo f6b21461e9 feat(connect): pivot extension install to Chrome Web Store path
Verified on Chrome 149 (unmanaged macOS): a force-install policy pointing at a
SELF-HOSTED crx is tagged [BLOCKED] in chrome://policy ("Error, Warning") — Chrome
refuses off-Web-Store force-installs on non-cloud-managed browsers. So the
self-hosted-crx approach cannot work on consumer Chrome; the extension must ship
via the Chrome Web Store (same reason codex/claude do).

- UPDATE_URL -> Chrome Web Store update endpoint; add STORE_URL (one-click Add to
  Chrome) as the guaranteed path + headless fallback
- install instructions now offer: A) one-click store link, B) silent profile
  force-install (works once published), with Load-unpacked as the pre-publish stopgap
- build extensions/ab-connect.zip (CWS upload package; manifest "key" kept so the
  published id stays ciiljdlhdpfckdcfkphgmfalanpdejep)
- extensions/store/{SUBMISSION.html,privacy.html}: full listing copy, permission
  justifications (debugger is the review-sensitive one), privacy policy
- drop dead self-hosted extensions/updates.xml; pack-extension.sh now builds the zip

Not released yet — force-install only works after the store listing is Published.
2026-06-09 19:03:10 +09:00
leeguooooo e8ef57bf00 feat(connect): force-install ab-connect via Chrome config profile (no Load-unpacked GUI)
Chrome 149 killed every GUI-free way to load an *unpacked* extension into the
real profile: --load-extension removed in Chrome 142 (incl. the
--disable-features workaround), local-.crx external install blocked on macOS
since Chrome 44, remote-debugging-port killed in Chrome 136. So agents were
stuck automating the chrome://extensions Load-unpacked native file dialog —
unworkable.

`extension install` now writes a macOS configuration profile that force-installs
the signed .crx from a hosted update_url (ExtensionInstallForcelist policy). One
approval in System Settings (a single fixed Install button — cua-driver-friendly,
unlike a file dialog) → Chrome force-installs + auto-updates the extension on next
launch. No token, no per-use confirmation, and binary-install users no longer
need the extensions/ folder (crx is fetched from the URL).

- pin a stable signing key; new extension id ciiljdlhdpfckdcfkphgmfalanpdejep
- ship signed extensions/ab-connect.crx + extensions/updates.xml (raw GH host)
- scripts/pack-extension.sh re-signs with the stable key; .secrets/*.pem ignored
- uninstall removes the profile file + prints `profiles remove` hint
2026-06-09 18:27:51 +09:00
leeguooooo 9efcb56651 chore(release): bump to 0.27.0-fork.18 — extension connect (zero-token native-messaging control of real Chrome) + click reliability + eval-first/find-url/site-notes
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.18
2026-06-09 17:32:44 +09:00
leeguooooo 091a4ec02e docs(skills): teach agents the extension-connect flow + computer-use for setup
So an agent can operate the zero-confirmation real-Chrome feature itself:
- SKILL.md: tool matrix gains "the user's own already-open, logged-in window →
  extension connect", plus a short section pointing at the flow.
- commands.md: the one-time "Load unpacked" is a privileged GUI step the CLI
  can't do — call it out that the agent can perform it with a computer-use /
  GUI-automation tool (cua-driver), with the live gotchas (synthetic-keystroke
  tools like peekaboo don't reach Chrome; cua-driver does; the native file
  dialog may need the user to pick the folder).
2026-06-09 17:14:50 +09:00
leeguooooo 6c0f5cbaa1 feat(connect): attach existing tabs + extension connect one-command UX
Completes the zero-confirmation real-Chrome feature.

- Drive the user's EXISTING logged-in tabs (not just newly-created ones):
  extension attachTab now treats "already attached" (a lingering chrome.debugger
  binding after a service-worker restart) as success and announces the tab
  anyway, instead of skipping it. The nm-host also sends {method:"attachAll"}
  when an agent-browser CDP client connects, so the daemon doesn't race an empty
  target list.
- `agent-browser extension connect` auto-discovers the relay's CDP url
  (~/.agent-browser/relay-cdp-url) and attaches — no copying a ws URL. Rewrites
  into the normal `connect <url>` flow; `extension install/status/uninstall`
  unchanged.
- Skill docs: a "drive your real, logged-in Chrome (extension)" section.

Verified end-to-end: `extension connect` listed the user's real tabs (Lark,
LINUX DO, Rakuten, Discord) and read a logged-in Lark doc's title — zero token,
zero confirmation. Full suite 768 passed.
2026-06-09 17:11:59 +09:00
leeguooooo 0d72e0d889 feat(connect): bridge native-messaging host to a CDP endpoint — end-to-end works
The __nm-host now exposes a Chrome-compatible CDP WebSocket endpoint and bridges
it to the extension over native messaging via the relay translation core
(relay.rs): incoming raw CDP commands are answered locally for browser-level
Target discovery or forwarded to the extension as forwardCDPCommand; the
extension's forwardCDPEvent/results are relayed back as raw CDP.

Security without a token or user interaction: the ws URL carries an unguessable
guid and is written to ~/.agent-browser/relay-cdp-url (perms 600), so only this
user's agent-browser can drive the browser — mirroring how Chrome guards its own
remote-debugging URL.

Verified end-to-end on real Chrome: `agent-browser connect <relay-url>` then an
eval navigated a tab and read back "Example Domain | https://example.com/" —
abs → CDP → relay → native messaging → extension → chrome.debugger → real tab,
zero token, zero confirmation. Adds the tokio io-std feature for the host's
stdio.

Remaining polish: re-attach the user's EXISTING tabs after a service-worker
restart (currently attaches new tabs cleanly; existing ones need detach+reattach
since chrome.debugger may still be bound), and an `open --extension` UX that
reads relay-cdp-url so the URL isn't passed by hand.
2026-06-09 16:54:29 +09:00
leeguooooo 528de4230f feat(connect): native-messaging transport — zero-token connect to real Chrome
Optimal architecture (chosen over the WS+token copy): the ab-connect extension
talks to a local agent-browser native-messaging host. No localhost port, no
token — Chrome authenticates the extension to the host by id. This is the
codex/claude-style "install once, no per-use confirmation" model.

- extensions/ab-connect: rewritten transport WebSocket+token → native messaging
  (chrome.runtime.connectNative). Pinned the extension id via a manifest `key`
  (→ bdoiejojpjogcjojeladhioioijhgade) so the host manifest can authorize it.
  Kept the proven chrome.debugger attach + Target.attachedToTarget emulation;
  dropped WS/token/options. Rebranded to "agent-browser connect".
- cli connect.rs: `agent-browser extension install` writes the native-messaging
  host manifest (Chrome/Chromium/Edge/Brave) + a launcher; hidden `__nm-host`
  speaks the 4-byte-length native-messaging framing.

Validated end-to-end on real Chrome: Chrome spawned the host (origin matched the
pinned id) and the extension attached the user's real logged-in tabs, streaming
Target.attachedToTarget over native messaging — zero token, zero port.

Next: bridge the host to the daemon relay (relay.rs) + CdpClient so
`agent-browser click/eval/...` drives those tabs.
2026-06-09 16:39:38 +09:00
leeguooooo 7f672494c1 feat(connect): relay translation core (envelope <-> raw CDP + Target emulation)
Pure, unit-tested core of the daemon-side relay that bridges the ab-connect
extension to the existing CdpClient. The extension exposes per-tab
chrome.debugger + synthesized Target events; CdpClient expects a browser-level
endpoint. So RelayState:

- answers Target.getTargets / attachToTarget / setDiscoverTargets LOCALLY from
  targets learned via the extension's forwardCDPEvent(Target.attachedToTarget),
  returning the extension's cb-tab-N sessionId (consumes those synth events
  rather than double-forwarding them);
- forwards every other command as a forwardCDPCommand envelope (carrying
  method/params/sessionId);
- maps forwardCDPCommand responses and forwardCDPEvent events back to raw CDP;
- validates the connect-handshake token; emits challenge/ping.

Keeps CdpClient and browser.rs unchanged. 8 unit tests; clippy clean. Still
inert — the tokio WS server + `connect` command wire it next.
2026-06-09 14:59:54 +09:00
leeguooooo 8a8106ad75 feat(connect): vendor MV3 connect extension (adapted from openclaw-browser-relay)
First step toward zero-confirmation direct connect to the user's real Chrome:
Chrome 136 killed --remote-debugging-port on the default profile, so the only
sanctioned way to drive the user's live logged-in window is an extension using
chrome.debugger (same approach as Codex/Claude, whose extensions are closed).

Vendors the MIT-licensed openclaw-browser-relay extension into
extensions/ab-connect/, rebranded to "agent-browser connect" (NOTICE.md keeps
attribution). It already handles the hard parts: chrome.debugger auto-attach all
tabs, new-tab auto-attach, MV3 service-worker keepalive (alarms) + reconnect,
sessionId↔tab mapping, token auth, and a CDP-over-WebSocket envelope
(connect handshake / forwardCDPCommand / forwardCDPEvent / ping-pong).

Inert for now — not wired. Next: an abs-daemon relay that speaks this envelope
and bridges it to the existing CdpClient (raw CDP), then a `connect` command.
2026-06-09 14:54:03 +09:00
leeguooooo f9cc31d003 chore(release): bump to 0.27.0-fork.17 — eval-first skill + find-url (local bookmark search) + site-notes convention
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.17
2026-06-05 17:15:49 +09:00
leeguooooo a7a3f924b0 docs(skills): site-notes convention for remembering site quirks
Borrow web-access's site-experience persistence as an agent-workflow convention
(no CLI code): keep one markdown file per domain under
~/.agent-browser/site-patterns/<domain>.md. Read it before working a domain
(hints, not guarantees); update it after learning something durable — working
selectors, required hidden fields, anti-bot traps, login needs. Makes repeat
visits fast instead of re-solving the same page every run.
2026-06-05 16:55:47 +09:00
leeguooooo 7572c34229 feat(find-url): search local Chrome/Edge bookmarks by keyword
Borrow web-access's find-url: locate an internal system or a previously-saved
page that public search can't reach, without opening a browser.

- `agent-browser find-url <keywords> [--browser chrome|edge] [--profile X]
  [--limit N] [--json]` — local command, no daemon. All keywords must match a
  bookmark's name or url; results are most-recently-added first.
- Cross-platform Bookmarks JSON paths (macOS / Linux / Windows), zero new deps
  (serde_json). Skips javascript:/data: bookmarklets.
- Skill docs: "pick the cheapest tool" matrix now points at find-url, plus a
  commands.md section.

Bookmarks only for now — visited-history is a locked SQLite DB and would need a
SQLite dependency (deferred to avoid C-dep cross-compile risk in the release
pipeline).
2026-06-05 16:54:46 +09:00
leeguooooo 06f5f9e8f1 docs(skills): lead with eval-first + tool-choice matrix
Real dogfooding showed the skill pushed agents straight into the fragile
snapshot/@ref path. Reframe the core guidance toward how a developer actually
drives a real browser:

- "Pick the cheapest tool" matrix: WebSearch / WebFetch+curl for static, reach
  for agent-browser only when you need a real logged-in / interactive / dynamic
  browser. Plus: don't hand-build deep URLs — use links found by interacting.
- "Two ways to drive a page": structured (@ref/find) is convenient but lossy &
  fragile; eval-first (`eval "<js>"`) is the real DOM — read hidden inputs,
  Shadow DOM, form.elements/.validity, or el.click() directly. Drop to eval the
  moment the structured path fights you, instead of retrying it.
- Escalation ladder rewritten (refs → find → CSS → eval) and a note to retry a
  no-op click with AGENT_BROWSER_CLICK_MODE=dom.

Doc-only; closes the biggest part of the "abs feels worse than web-access" gap.
2026-06-05 16:44:29 +09:00
leeguooooo 5f50ca075c chore(release): bump to 0.27.0-fork.16 — click reliability (scroll-into-view + DOM fallback) + skill docs (console opt-in, CLICK_MODE, form/hidden-input eval)
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.16
2026-06-05 11:00:57 +09:00
leeguooooo e7548c3eb5 fix(click): scroll into view + DOM-dispatch fallback for reliable clicks
Real-world dogfooding surfaced clicks that resolve a valid @ref but still miss:

- Scroll the target into view before computing click coordinates
  (scrollIntoViewIfNeeded). Without it, an element below the fold — or revealed
  after a scroll/popup — yields off-viewport coordinates and the click lands on
  whatever occupies that screen point.
- Fall back to a DOM-dispatched `.click()` when the coordinate path fails (a
  persistent floating layer failing the occlusion guard, or coordinates that
  won't resolve). The DOM dispatch targets the intended element directly instead
  of a screen point, so an overlay or portal can't divert it.
- AGENT_BROWSER_CLICK_MODE: "" (default: scroll + coordinate + DOM fallback),
  "coord" (strict coordinate, hard-fail on occlusion), "dom" (always
  element.click() — best for autocomplete/menu <li> that close on input blur).

Fallback is limited to left single-clicks (DOM .click() can't express
right/middle/double). Non-left/multi and "coord" mode keep the original error.

Docs: README knob table + skill commands.md gain CLICK_MODE, a click-reliability
note, and a "debug forms/hidden inputs with eval" section (snapshot doesn't show
hidden inputs — the fast path to bugs like a hidden point_choice=none).

6 click/interaction e2e green; full suite 760 passed.
2026-06-05 10:58:28 +09:00
leeguooooo b77a1e4568 docs(skills): document console-capture opt-in + stealth env knobs
console/errors capture is off by default in this fork (Runtime.enable is a
detectable CDP signal). Update the agent-facing skill docs so agents don't
treat empty console output as a bug:

- commands.md: new "Stealth / anti-detection knobs" env-var block
  (CAPTURE_CONSOLE, TIMEZONE, BLOCK_WEBRTC, HIDE_CANVAS, ADAPTIVE_REF) plus a
  heads-up note; annotate the console/errors lines.
- dogfood/slack SKILL.md: note that console/errors need
  AGENT_BROWSER_CAPTURE_CONSOLE=1.
2026-06-04 17:08:14 +09:00
leeguooooo 7c499885e5 chore(release): bump to 0.27.0-fork.15 — stealth hardening (lazy Runtime.enable, native timezone/WebRTC, opt-in canvas noise) + adaptive @ref relocation
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
v0.27.0-fork.15
2026-06-04 16:26:51 +09:00
leeguooooo fc2621559b style: clear clippy warnings from the stealth/adaptive work
- snapshot: make collect_fingerprints private (TreeNode is private, so a
  pub(super) fn leaked a more-private type)
- adaptive: if-let instead of single-arm match in attr_score
- stealth: move timezone test module to end of file (items-after-test-module)

No behavior change. Pre-release cleanup.
2026-06-04 14:26:08 +09:00
leeguooooo 8b55c553e6 feat(adaptive): relocate stale @refs by AX fingerprint similarity
Borrow Scrapling's adaptive element finding, adapted to this project's
in-session AX-ref model. When a saved @ref's node is gone (or its identity
no longer matches) and the role/name/nth re-query also fails, score the
current page's candidate elements against an AX fingerprint captured at
snapshot time and relocate to the best match.

- New `adaptive` module: pure, browser-free scoring (role, accessible name
  via Levenshtein, AX properties, ancestor-role LCS, parent/sibling) plus
  pick_best with a high absolute threshold (0.70) AND a clear margin (0.15)
  over the runner-up — so ambiguous twins are refused rather than mis-clicked,
  matching the existing "fail loudly over wrong click" posture.
- Fingerprint captured during the existing AX-tree snapshot walk — no extra
  CDP round-trips. TreeNode is AX-only (no DOM tag/attrs), so we use AX role
  as the type and a few discriminating AX properties (value/url/level/checked);
  DOM id/class would have cost an N×describeNode storm per snapshot.
- Wired into both resolve_element_center and resolve_element_object_id: on a
  verify-identity mismatch or a stale-node fallback miss, relocation is tried
  before erroring. A confident match overrides the identity guard; otherwise
  the original error is surfaced. Opt out with AGENT_BROWSER_ADAPTIVE_REF=0.

README documents the new tuning knobs. Adds 9 unit tests; full suite 760 passed.
2026-06-04 14:10:50 +09:00
leeguooooo 6b99d304b1 feat(stealth): shrink detectable surface — lazy Runtime.enable, native timezone/WebRTC, opt-in canvas noise
Borrow anti-detection hardening from Scrapling/patchright, preferring native
CDP/Chrome overrides over JS lies:

- Runtime.enable is now opt-in via AGENT_BROWSER_CAPTURE_CONSOLE (default off).
  It was called on every session INCLUDING CdpAttach (the user's real Chrome),
  leaking the patchright/rebrowser "runtime" CDP signal and undermining the
  "real browser, no lies" guarantee. Runtime.evaluate/callFunctionOn and
  runIfWaitingForDebugger work without it; only console/error capture needs it.
  The console/errors commands now return a hint when capture is disabled.
- Timezone alignment via native Emulation.setTimezoneOverride, opt-in with
  AGENT_BROWSER_TIMEZONE=<IANA>|auto (FullLaunch only). Intl and Date both
  follow with no JS artifact.
- WebRTC IP-leak handling via the --force-webrtc-ip-handling-policy Chrome
  flag: auto disable_non_proxied_udp when a proxy is set (so the real IP can't
  leak past the proxy); AGENT_BROWSER_BLOCK_WEBRTC=1 hides the local IP when
  there is no proxy; =0 opts out.
- Opt-in canvas/audio fingerprint noise via AGENT_BROWSER_HIDE_CANVAS=1
  (FullLaunch only). Session-stable seed so reads stay consistent within a
  session while differing from the headless-stable hash.

Adds 5 unit tests; full suite 751 passed, 0 failed.
2026-06-04 13:28:34 +09:00
leeguooooo 900a5b5cde fix(install): also create the agent-browser-stealth command name
install.sh created `agent-browser` + `abs` but not `agent-browser-stealth`, so
users who invoke `agent-browser-stealth` (the fork's package name) weren't
getting it updated on curl-install/upgrade. Now all three names — agent-browser,
agent-browser-stealth, abs — symlink to the same binary, so an upgrade refreshes
whichever name you actually run.
2026-06-01 18:55:58 +09:00
leeguooooo ab9b8d96ca chore(release): bump to 0.27.0-fork.14 — fix upgrade footgun + CI Node 24
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
`agent-browser-stealth upgrade` no longer installs the wrong upstream npm
package; it re-runs the GitHub-Release install.sh in place. CI actions bumped
off Node 20.
v0.27.0-fork.14
2026-06-01 18:46:27 +09:00
leeguooooo 5c734c51b6 fix(upgrade): re-run install.sh instead of installing the wrong npm package
`agent-browser-stealth upgrade` (inherited from upstream) queried
registry.npmjs.org/agent-browser and ran `npm/pnpm install -g
agent-browser@latest` — installing the UNRELATED upstream `agent-browser`
package and clobbering the user's stealth install (reported in testing).

The stealth fork ships via GitHub Releases, so `upgrade` now just re-runs
install.sh into the same directory as the current binary — identical to the
install path, always tracking the freshest Release. (Windows prints manual
download instructions.)

Also bump CI actions off the deprecated Node 20 runtime (GitHub forces Node 24
on 2026-06-16): checkout v4->v6, upload-artifact v4->v7, download-artifact
v4->v8, action-gh-release v2->v3.
2026-06-01 18:46:16 +09:00
leeguooooo dc54855784 chore(release): bump to 0.27.0-fork.13 — --profile auto + temp-profile warnings
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
Stops agents from silently launching a temporary empty profile (no login).
Adds --profile auto, warns on bare --launch, and recommends --profile auto in
connect-failure errors. Addresses issue #1 follow-up.
v0.27.0-fork.13
2026-06-01 18:30:28 +09:00
leeguooooo ed61be3359 feat(profile): --profile auto + stop steering users into temp-profile launches
Addresses the footgun raised in issue #1 follow-up: plain `--launch` silently
uses a temporary EMPTY profile (no cookies/login), and the connect-failure
error even recommended it — trapping agents into thinking they reused the
logged-in browser when they didn't.

- `--profile auto`: resolves to the Chrome profile last used (from Local State
  `profile.last_used`), falling back to "Default", then the first profile. So
  `--launch --profile auto open <url>` reuses real login state without naming
  the profile. (--profile <name>/Default already worked.)
- connect-failure error now recommends `--launch --profile auto` and states
  plainly that bare `--launch` is a temporary EMPTY profile — no cookies/login.
- bare `--launch` (no --profile, not CI) now prints a warning to that effect.
- README: fix Setup (relaunch with --remote-debugging-port, not chrome://inspect)
  and split Standalone mode into throwaway vs. keep-your-login (`--profile auto`).

Tests: resolve_chrome_profile("auto") prefers last_used, falls back to Default.
2026-06-01 18:30:11 +09:00