Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9d0b436
feat(browser): default Chrome profile mode args instead of failing va…
Aug 14, 2026
c1037e5
fix(browser): keep Chrome profile mode on a Chrome-brand binary
Aug 14, 2026
12634ee
fix(browser): require a non-default Chrome data root
Aug 14, 2026
2fda799
fix(browser): cover Chrome channels and Linux Edge
Aug 14, 2026
4bcbb65
fix(browser): reject Linux environment default roots
Aug 14, 2026
b306bfc
fix(browser): reject sandboxed Chrome defaults
Aug 14, 2026
51d5cef
docs(browser): use a non-default profile root
Aug 14, 2026
52a6017
fix(browser): cover Linux Canary profile roots
Aug 14, 2026
0025194
fix(browser): trust profile discovery environment
Aug 14, 2026
f531efd
fix(browser): require Chrome-branded profile binaries
Aug 14, 2026
ad5d5fd
docs(browser): document Chrome-only profile mode
Aug 14, 2026
51338bc
fix(browser): preserve Snap Chromium launcher
Aug 14, 2026
c6e22c0
fix(browser): bind Snap launcher target
Aug 14, 2026
ee4c5c5
fix(browser): reuse wrapper-launched Chrome profiles
Aug 14, 2026
bc1b750
fix(browser): verify wrapper process images
Aug 14, 2026
9d14b18
fix(browser): abort wrapper process scans
Aug 14, 2026
1a7690e
fix(browser): canonicalize profile paths asynchronously
Aug 14, 2026
a10f5e3
fix(browser): propagate CDP probe cancellation
Aug 14, 2026
abd5b9f
fix(browser): recheck abort after CDP response
Aug 14, 2026
9aaab75
fix(browser): cancel stalled path resolution
Aug 14, 2026
3230e54
test(browser): prove default-root refusal on every platform
Aug 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions docs/tools/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
| `viewport` | `{ width: number; height: number; scale?: number }` | No | Requested viewport. For headless launch this becomes the initial viewport; for a page it is applied with `page.setViewport()`. `scale` maps to Puppeteer `deviceScaleFactor`. |
| `wait_until` | `"load" \| "domcontentloaded" \| "networkidle0" \| "networkidle2"` | No | Navigation wait condition. Defaults to `"networkidle2"` where omitted. |
| `dialogs` | `"accept" \| "dismiss"` | No | Installs a page `dialog` handler that auto-accepts or auto-dismisses dialogs. Omitted means no handler. |
| `app` | `{ path?: string; cdp_url?: string; browser?: "chrome"; user_data_dir?: string; profile_directory?: string; background?: boolean; no_focus?: boolean; cdp_port?: number; args?: string[]; target?: string }` | No | Selects browser kind. No `app` uses the session `browser.headless` setting. `app.path` alone is resolved against the session cwd and used as the executable path for spawn/attach reuse. `app.cdp_url` connects to an existing CDP endpoint. `app.browser: "chrome"` selects guarded saved-profile mode and requires `path`, `user_data_dir`, and `profile_directory`. `args` are appended only when spawning `app.path` or a Chrome profile. `target` is used for attached/spawned/profile page selection. |
| `app` | `{ path?: string; cdp_url?: string; browser?: "chrome"; user_data_dir?: string; profile_directory?: string; background?: boolean; no_focus?: boolean; cdp_port?: number; args?: string[]; target?: string }` | No | Selects browser kind. No `app` uses the session `browser.headless` setting. `app.path` alone is resolved against the session cwd and used as the executable path for spawn/attach reuse. `app.cdp_url` connects to an existing CDP endpoint. `app.browser: "chrome"` selects guarded saved-profile mode: `path` defaults to installed Chrome/Chromium and `profile_directory` defaults to `"Default"`, while `user_data_dir` is required and must be a non-default Chrome data directory because Chrome 136+ disables remote debugging for its default data directory. Non-Chrome executables and default Chrome data roots are rejected. `args` are appended only when spawning `app.path` or a Chrome profile. `target` is used for attached/spawned/profile page selection. |

### `action: "close"`

Expand Down Expand Up @@ -92,7 +92,7 @@ The tool returns one result per call; no streaming partial output is emitted fro
1. `BrowserTool.execute()` (`packages/coding-agent/src/tools/browser.ts`) abort-checks, clamps `timeout` via `clampTimeout("browser", ...)`, defaults `name` to `"main"`, and dispatches `open`, `close`, `act`, or `run`.
2. `open` resolves browser kind with `resolveBrowserKind()`:
- `app.cdp_url` → `{ kind: "connected" }` after trimming trailing slashes.
- `app.browser: "chrome"` → `{ kind: "chrome-profile" }` after resolving `path` and `user_data_dir` against session cwd and copying `profile_directory`, `background`, `no_focus`, and optional `cdp_port`.
- `app.browser: "chrome"` → `{ kind: "chrome-profile" }`. `path` defaults to installed Chrome/Chromium (`resolveSystemChromeForProfile()`, admitting only Chrome/Chromium brands) and `profile_directory` defaults to `"Default"`. `user_data_dir` is required, resolved against the session cwd, and rejected when it resolves (including through a symlink) to a platform default Stable/Beta/Dev/Canary/Chromium root; trusted Linux environment overrides plus Flatpak and Snap defaults are included. Chrome 136+ does not honor remote-debugging switches for default Chrome data directories. Explicit Edge, Brave, Vivaldi, Opera, and unknown browser executables are rejected before profile fields are resolved. `background`, `no_focus`, and optional `cdp_port` are copied through.
- `app.path` → `{ kind: "spawned" }` after resolving against session cwd.
- otherwise → `{ kind: "headless", headless: session.settings.get("browser.headless") }`.
3. `open` rejects reusing the same tab name across different browser kinds (`sameBrowserKind()`); callers must close first.
Expand All @@ -114,19 +114,18 @@ The tool returns one result per call; no streaming partial output is emitted fro
8. `WorkerCore.#init()` (`packages/coding-agent/src/tools/browser/tab-worker.ts`) connects back to the browser websocket endpoint. Headless mode opens a new page, applies stealth patches, applies viewport, installs dialog handling if requested, and optionally navigates. Attach mode resolves the requested target page and optionally installs dialog handling.
9. On success the worker sends `ready` with `{ url, title, viewport, targetId }`; the supervisor stores a `TabSession`, increments browser-handle refcount with `holdBrowser()`, and keeps the tab in a process-global `Map<string, TabSession>`.

### Existing Chrome profile mode
### Existing non-default Chrome profile mode

Use this mode when automation needs cookies and login state from a saved Chrome profile without risking the daily Chrome process:
Use this mode for a dedicated, persistent Chrome data root that already contains the automation profile and login state. Chrome 136+ rejects remote debugging against the browser's default data root, so do not point this mode at the daily Chrome root. Create and sign in to a separate root first, close that Chrome instance, then let GJC reopen it with the guarded CDP lifecycle:

```json
{
"action": "open",
"name": "work-browser",
"app": {
"browser": "chrome",
"path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"user_data_dir": "~/Library/Application Support/Google/Chrome",
"profile_directory": "Profile 10",
"user_data_dir": "~/Library/Application Support/GJC/Chrome Automation",
"profile_directory": "Default",
"background": true,
"no_focus": true,
"target": "example.com"
Expand All @@ -137,6 +136,7 @@ Use this mode when automation needs cookies and login state from a saved Chrome
Security and lifecycle rules:

- CDP is bound to `127.0.0.1`; do not expose logged-in profile CDP ports on a public interface. A CDP client has full browser-account access.
- `user_data_dir` must be a separate non-default data root. Platform Stable/Beta/Dev/Canary/Chromium defaults, Linux environment/Flatpak/Snap defaults, and aliases to them are rejected before launch. Use `app.cdp_url` only for an already-authorized endpoint that you intentionally started and control.
- Saved-profile and attached-CDP automation can read and act with that profile's cookies and authenticated accounts. Use it only when that credentialed access is intentional.
- Never use generic `app.path` spawning for a daily Chrome profile: it may kill stale same-path processes. Use explicit `app.browser: "chrome"` profile mode, which applies the ownership guards below.
- A matching already-running profile is reused only when its localhost CDP endpoint responds. A matching profile running normally without CDP is refused with remediation text; GJC does not kill or relaunch it.
Expand Down
1 change: 1 addition & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

### Changed
- Updated every bundled GLM model profile (`glm-eco`, `glm-medium`, and `glm-pro`) from ZAI GLM-5.2 to GLM-5.3.
- `browser` Chrome profile mode (`app.browser: "chrome"`) now defaults an omitted `path` to installed Chrome/Chromium and an omitted `profile_directory` to `"Default"`. `user_data_dir` remains explicit and must be non-default because Chrome 136+ disables remote debugging for default Chrome data directories; Stable/Beta/Dev/Canary/Chromium roots (including trusted Linux environment, Flatpak, and Snap defaults), symlink aliases to them, and non-Chrome browser executables are rejected with remediation instead of timing out or risking cross-brand profile access. Profile and Windows executable discovery use trusted environment sources that ignore repository `.env`, platform path semantics are preserved, executable/profile-root canonicalization races stalled filesystem work against cancellation, the canonical Snap Chromium launcher is admitted only when it resolves to `/usr/bin/snap`, and wrapper-launched Linux Chrome processes are reused through an abort-aware asynchronous `/proc` scan only after kernel executable identity plus exact profile/loopback-CDP guards pass. Cancellation propagates before, during, and after CDP probing, and omitted profile names render as `Default` in the TUI.
- Defense-in-depth: when an Anthropic-origin assistant transcript message carrying directly adjacent `thinking`/`redacted_thinking` blocks is persisted, a single bounded warn is emitted per session manager instance — but only in development/test builds, never in production. The diagnostic names only the envelope shape (block count, adjacency presence, provider), never raw thinking text, signatures, redacted payloads, or transcript-path metadata. Storage is never mutated — the send-boundary collapse remains the wire source of truth; this is a read-only observation that helps surface upstream producers of the rejected shape (#4443).
### Fixed
- Post-merge repair for #4542: `CHAT_DAEMON_GENERATIONS.discord` 64→65 and `.slack` 67→68 so the `SessionRouter` initial attachment replay change is generation-fenced for already-running Discord and Slack daemons. The semantic guard manifest is regenerated with the corrected generation and declaration digests.
Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/src/prompts/tools/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Drives a real Chromium tab with full puppeteer access via JS execution.
- `act` — run a list of structured `actions` against an existing tab without writing JS (preferred for routine navigation/interaction). Each step is `{ verb, … }`; verbs: `navigate {url, wait_until?}`, `click {id|selector}`, `type {id|selector, text}`, `fill {selector, value}`, `select {selector, values}`, `press {key, selector?}`, `scroll {dx?, dy?}`, `back`, `wait {selector?|ms?}`, `observe {viewport_only?, include_all?}`, `extract {format?}`, `screenshot`. Address elements by the numeric `id` from a prior `observe` (preferred) or a selector. Steps run in order; the tool returns per-step results.
- `run` — execute JS against an existing tab. `code` is the body of an async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. The return value is JSON-stringified into the tool result; `display(value)` calls accumulate text/images. Use `run` only when an `act` verb does not cover what you need.
- Tabs survive across `run` calls and across in-process subagents. Open once, reuse many times.
- Browser kinds: no `app` launches headless Chromium; `app.path` reuses CDP or kills stale same-path processes before spawning — NEVER use it for a daily Chrome profile; use explicit `app.browser: "chrome"` profile mode instead. Saved-profile/CDP automation has access to that profile's cookies and authenticated accounts. Profile mode refuses a matching non-CDP Chrome instead of killing/relaunching it, and `kill: true` can terminate only a Chrome process GJC launched; `app.cdp_url` is externally owned and disconnect-only. CDP must stay on `127.0.0.1`: it grants full browser-account access.
- Browser kinds: no `app` launches headless Chromium; `app.path` reuses CDP or kills stale same-path processes before spawning — NEVER use it for a daily Chrome profile; use explicit `app.browser: "chrome"` profile mode instead. In profile mode, `path` defaults to installed Chrome/Chromium and `profile_directory` defaults to `"Default"`, but `user_data_dir` must name a separate non-default Chrome data directory: Chrome 136+ disables remote debugging for its default data directory. Only Chrome/Chromium executables are admitted; Edge, Brave, Vivaldi, Opera, unknown browser brands, and default Chrome data roots are rejected. Use `app.cdp_url` to attach to an already-authorized browser. Saved-profile/CDP automation has access to that profile's cookies and authenticated accounts. Profile mode refuses a matching non-CDP Chrome instead of killing/relaunching it, and `kill: true` can terminate only a Chrome process GJC launched; `app.cdp_url` is externally owned and disconnect-only. CDP must stay on `127.0.0.1`: it grants full browser-account access.
- Inside `run`, `tab` exposes high-level helpers (`goto`, `observe`, `id`, `click`, `type`, `fill`, `press`, `waitFor`, `screenshot`, `extract`, …); reach for `page` (raw puppeteer Page) when they don't cover it.
- Selectors accept CSS as well as puppeteer query handlers: `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`.
- Runtime diagnostics are opt-in: pass `diagnostics: true` to `open` to subscribe the tab to page `Runtime.exceptionThrown` and `console.error` events. The next successful `act`/`run` response then includes at most 20 `runtimeDiagnostics` entries plus `runtimeDiagnosticsDropped`, then drains them. Entries contain only kind, time, origin-only URL, line/column, and a built-in error class from a fixed allowlist — never path segments, query strings, messages, console arguments, values, or stacks. Output is byte-bounded and marks truncation explicitly.
Expand Down
Loading
Loading