Skip to content

feat(demo): auto-approve Google Drive mount consent (--auto-drive) - #26

Merged
henrik253 merged 1 commit into
mainfrom
feat/auto-drive-mount-main
Jul 21, 2026
Merged

feat(demo): auto-approve Google Drive mount consent (--auto-drive)#26
henrik253 merged 1 commit into
mainfrom
feat/auto-drive-mount-main

Conversation

@henrik253

Copy link
Copy Markdown
Owner

Re-lands #24 onto main: #24 was merged into the #21 feature branch, but #21 was squash-merged, so the Drive-mount work never reached main. This is a clean cherry-pick of that commit (0724650) onto current main; suite green there (395 passed).

What this adds

Notebook cells calling drive.mount(...) now work unattended: a new --auto-drive flag auto-approves the whole consent flow — Colab's Drive permission dialog and the accounts.google.com popup with its consent screens.

Design and verified UI contracts are documented in #24 and in drive_mount.py's docstring. Highlights:

  • Watcher, not one-shot: the first mount raises dialog + popup; consented accounts show neither. A background watcher per managed tab approves each surface if and when it appears — necessary because drive.mount itself times out after ~2 minutes.
  • Locale-independent selectors: footer buttons jsname="LgbsSe" (Cancel first, Confirm last in DOM order), scroll affordance jsname="NakZHc"; the Colab dialog is identified by its "Google Drive" text. The popup closing itself is the success signal.
  • Opt-in: off by default and separate from --auto-approve, since the consent lets notebook code modify the user's Drive files.
  • Demo cells added to cpu-a.ipynb / t4.ipynb.

Verified live on the 2-notebook config: both notebooks report drive-mounted: True; the T4 ran on a fresh VM and exercised the full dialog + popup flow.

🤖 Generated with Claude Code

A cell calling drive.mount raises Colab's Drive permission dialog and then a
real accounts.google.com popup with consent screens; drive.mount itself gives
up after about two minutes, so on a server the flow has to be automated. Once
consent is granted, later mounts show neither surface — so the automation is
a background watcher per managed tab, not a one-shot flow: it approves each
surface if and when it appears and idles otherwise.

- browser/adapters/colab/drive_mount.py (new): the watcher. The Colab dialog
  is a generic yes-no mwc-dialog identified by its "Google Drive" text; the
  popup consent screens are locale-dependent in text but structurally stable
  (footer buttons jsname="LgbsSe", Cancel first / Confirm last in DOM order;
  jsname="NakZHc" is a scroll affordance dismissed first). The popup closing
  itself is the success signal, never the click.
- browser/controller.py: BrowserController(auto_drive=...) spawns one watcher
  per tab after the MCP dialog is approved, and cancels it on close/aclose.
  Off by default: the consent lets notebook code modify the user's Drive
  files, so it needs an explicit opt-in.
- run_demo.py: --auto-drive flag, threaded through live_phase; separate from
  --auto-approve on purpose.
- cpu-a.ipynb / t4.ipynb: drive-mount demo cells.

Verified live: run-notebooks with --auto-drive on the 2-notebook config; both
notebooks report drive-mounted: True (T4 on a fresh VM went through the full
dialog + popup flow). Suite green: 383 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@henrik253
henrik253 merged commit 0c38c1c into main Jul 21, 2026
1 check failed
henrik253 added a commit that referenced this pull request Jul 21, 2026
PR #26 merged with `import os` placed after the drive.mount call in the two
drive-mount demo cells, which fails ruff E402 and turned CI red on main.
Move the import to the top of the cell. Roadmap: record PR #26 (merged
directly to main without a roadmap update) in the wave status.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
henrik253 added a commit that referenced this pull request Jul 21, 2026
* docs: require roadmap updates in completing commits

* feat: wire managed-browser auto-approval into the MCP server CLI (#18)

* docs: require roadmap updates in completing commits

* feat: auto-approve Colab MCP connect dialog via managed Chromium

Establishes the Phase 2 browser layer (plan.md §10/§11).

The popup cannot be bypassed: Colab's connect-local-mcp command always awaits
the dialog, it has no remember option, and the only dialog-free entry point is a
private TEST_ONLY.connect() hook that would bypass a consent gate (plan.md §14).
So we click it — only after proving the dialog is ours.

Two gates exist, not one. Beyond Colab's dialog, Chrome blocks a public origin
from reaching localhost (ERR_BLOCKED_BY_LOCAL_NETWORK). Modern Chrome uses the
Local Network Access permission; the older Private Network Access response
headers do not satisfy it. The controller grants local-network-access scoped to
the Colab origin.

Verification uses the dialog's own readonly token field, which Colab prefills
with exactly <token>&<port>: compare against the session, refuse on mismatch,
never leak the token. Success is the server-side connection, not the click.

Mechanism verified by frontend-bundle analysis and a headless end-to-end spike
against the real Colab UI plus a real ColabWebSocketServer; documented in
plan.md §11 so a Colab UI change can be re-verified cheaply.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(demo): unattended MCP approval via managed browser

Wires the auto-approval into the demo so its phases stop needing three
manual popup clicks each (demo README 'one MCP approval per tab').

- SessionManager takes an optional BrowserController; open_connection uses it
  to open and approve the tab, else falls back to webbrowser.open_new. The
  manager already owns one session per notebook_id and the controller owns one
  tab per notebook_id, so no new global or signature churn is needed.
- run_demo.py gains --auto-approve/--headless and a 'login' command that opens
  the managed browser for the operator's one-time Google sign-in. Credentials
  are only ever typed by the operator and live in the gitignored profile.
- Approval failures surface as structured errors, not hangs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(demo): attach to the operator's own Chrome for auto-approval

Google refuses sign-in to automated browsers: even launching the real Chrome
binary, navigator.webdriver stays true and sign-in is blocked, so a
Playwright-launched profile can never hold a usable session. Spoofing that
signal would defeat a security check (plan.md §14), so instead the operator
signs in to a normally-launched Chrome and the demo attaches to it.

- BrowserController gains cdp_url (attach over CDP; never closes a browser it
  does not own) and use_chrome (real Chrome channel, automation flag dropped).
- run_demo.py gains a 'chrome' command that launches Chrome with a debug port
  and its own profile (Chrome refuses remote debugging on the default profile),
  plus --cdp-url for the live phases.
- Sign-in detection now waits for Colab's app shell before judging: a blank page
  trivially lacks the sign-in prompt and used to report success, closing the
  window before the operator could sign in. Regression covered by login_test.py.

Verified live: three notebooks opened and auto-approved with no manual clicks;
each runtime returned {connected: true, accelerator: CPU} from executed code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(demo): headless operation via exported browser session

A terminal-only server can now run signed in without a display, Chrome, Xvfb
or a desktop. Google only checks for automation at sign-in, so a headless
browser replaying an exported session is already authenticated — verified
live: three notebooks connected headless from the session file alone, each
runtime returning {connected: true, accelerator: CPU} from executed code.

- BrowserController gains session_file (launch headless + storage_state) and
  export_session(), which writes the state 0600 before any bytes reach disk.
  A missing session returns user_action_required naming the fix.
- run_demo.py gains export-session, session-check (exit 0/1, so a scheduler
  alerts instead of hanging) and --session-file.
- CDP_URL now uses 127.0.0.1: Chrome binds the debug port on IPv4 while
  localhost may resolve to ::1 first and refuse the connection.

The session file authenticates as the user with no password or 2FA, so it is
treated as a credential throughout: 0600, gitignored, never logged, and
documented as such. It is not interchangeable with the OAuth client JSON —
that identifies the application, this identifies the user.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: wire managed-browser auto-approval into the MCP server CLI

The auto-approval flow existed in the library and demo (BrowserController +
SessionManager(browser=...) + server.open_connection) but the shipped entry
point always built SessionManager() bare, so MCP clients still got the manual
popup. New CLI flags close the gap:

- --auto-approve: build + start a BrowserController, pass it to
  SessionManager, close it on shutdown (the manager never owns its lifecycle;
  a nested finally closes it even when a session refuses to close)
- --cdp-url / --headless / --session-file configure the controller; they are
  rejected without --auto-approve, and --cdp-url rejects --headless /
  --session-file (an attached browser is whatever the operator launched)
- behaviour is unchanged when the flags are absent; a start() failure aborts
  before serving, releases what start acquired, and user_action_required
  failures print their remedy instead of a traceback

Roadmap §12 wiring bullet checked off with the cli_test.py::TestAutoApprove
cases (happy paths, invalid flag combinations, failure paths).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* docs: document manual setup in README (#20)

Adds the missing setup path from clone to working server: install commands,
MCP-client registration, the two one-time credentials (browser session via an
operator-launched Chrome with CDP attach — the only route Google's sign-in
accepts — and the optional runtime-API OAuth consent), unattended operation via
--auto-approve with an exported session file, and maintenance commands.

Also documents the remote-Linux findings from the 2026-07-20 server run: xrdp/
software-rendered displays need Chrome launched with --disable-gpu, SSH shells
need DISPLAY/XAUTHORITY/DBUS_SESSION_BUS_ADDRESS from the active desktop
session before the keyring or a visible browser is reachable, and a crashed
desktop session can drop the keyring token.

Roadmap: §1 documentation bullet checked; stale wave-status snapshot (still
describing PR #15 as open) refreshed to the post-#19 state.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* docs: agent-facing feature guide (#23)

Agents connected to the server were observed using only a fraction of the tool
surface. Two additions fix discovery:

- docs/agent-guide.md: one-page agent guide — mental model (registered
  notebooks are persistent resources, sessions are replaceable), a goal → tool
  map of all 31 tools, standard workflows (project setup, work session, GPU
  switch with safe endpoint discovery, recovery), env vars, and pitfalls
  (close does not sync, local-path reopen resets to last-synced file, GitHub
  URLs load from the remote branch, endpoints are not mapped to notebooks,
  structured errors).
- README "For agents" section: the whole surface in ten lines, linking to the
  guide, placed before Architecture so an agent reading the README top-down
  knows every feature before any implementation detail.

All tool names, signatures, profile names, and env vars verified against
registry/tools.py, snapshots/tools.py, transfer_tools.py, runtime/tools.py,
server.py, and constants.py; plan-reviewer findings (endpoint discovery flow,
default-session semantics, local-path reopen data loss, snapshot outputs not
re-injected) are incorporated. Out-of-plan docs scope, user-requested;
recorded in the roadmap wave status.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* docs: mark agent guide merged in roadmap wave status

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(demo): satisfy E402 in drive-mount cells

PR #26 merged with `import os` placed after the drive.mount call in the two
drive-mount demo cells, which fails ruff E402 and turned CI red on main.
Move the import to the top of the cell. Roadmap: record PR #26 (merged
directly to main without a roadmap update) in the wave status.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: ruff-format drive-mount notebooks

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
henrik253 added a commit that referenced this pull request Jul 21, 2026
PR #26 merged with its pre-fix commit (the lint fix was force-pushed after
the merge), leaving main red: ruff E402 (import not at top of cell) in the
two drive-mount cells, plus ruff-format drift in the same notebooks. Moves
the imports to the top of the cell and formats the notebooks; no behavior
change.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant