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.
This commit is contained in:
leeguooooo
2026-06-09 17:11:59 +09:00
parent 0d72e0d889
commit 6c0f5cbaa1
4 changed files with 87 additions and 6 deletions
+23 -5
View File
@@ -539,7 +539,7 @@ fn main() {
let args: Vec<String> = env::args().skip(1).collect();
let mut flags = parse_flags(&args);
let clean = clean_args(&args);
let mut clean = clean_args(&args);
// Loudly warn when launching a fresh browser with no profile: it gets a
// temporary EMPTY profile (no cookies / no login). For logged-in sites the
@@ -650,11 +650,29 @@ fn main() {
return;
}
// Handle extension (doesn't need daemon): native-messaging host install/status
// for the ab-connect extension. (`connect <port>` stays the CDP-attach command.)
// Handle extension: native-messaging host install/status, and
// `extension connect` which attaches to the live relay (auto-discovers the
// CDP url the host wrote) by rewriting into the normal `connect <url>` flow.
// (`connect <port>` stays the plain CDP-attach command.)
if clean.first().map(|s| s.as_str()) == Some("extension") {
connect::run_connect(&clean, flags.json);
return;
if clean.get(1).map(|s| s.as_str()) == Some("connect") {
match connect::relay_url() {
Some(url) => {
clean = vec!["connect".to_string(), url];
// fall through to the normal connect handling below
}
None => {
eprintln!(
"{} extension not connected. Run `agent-browser extension install`, load the\n ab-connect extension in Chrome (chrome://extensions → Developer mode →\n Load unpacked → extensions/ab-connect), then retry.",
color::error_indicator()
);
exit(1);
}
}
} else {
connect::run_connect(&clean, flags.json);
return;
}
}
// Handle session separately (doesn't need daemon)