Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Changelog

## Unreleased
- Changed `browser_handoff` into an asynchronous start operation. It now returns `completed` immediately after the durable handoff and global browser blackout are active, allowing the initiating agent to continue independent non-browser work while the user completes the step; browser observation and mutation remain blocked until the handoff clears.
- Added a disabled-by-default popup setting named YOLO mode. When enabled, recognizable consequential controls execute in the original `browser_act` call instead of entering Commit review; enabling it discards pending staged actions while task ownership, origin policy, revision checks, handoff blackout, credential isolation, and all other Standard boundaries remain enforced.
- Replaced the Chrome Bridge v1 runtime with the AgentTab 2.0 release candidate: a Rust production host over OS-native local IPC, nine task-scoped Standard methods, explicit resumable capabilities, a developer-only tenth method, TypeScript and Python SDKs, MCP and OMP adapters, a transactional installer, and a minimal extension. Consequential controls now use a two-party Commit flow: `browser_act` stages an exact effect, the popup approves the durable review record without executing it, and the requesting task must consume its private one-use token through `browser_commit`.
- Added explicit `browser_finish` lifecycle finalization across Core RPC, the extension, TypeScript and Python SDKs, CLI, MCP, OMP, and Pi. Automatic cleanup tracks tab provenance, closes task-created tabs, retains adopted tabs, ungroups retained tabs, and releases ownership; popup policy can require confirmation or retain all tabs, while active handoff, Commit review, and in-flight work defer cleanup without destroying resumability.
- Fixed OMP adapter compatibility with providers that reject top-level union tool schemas. `browser_open` and `browser_snapshot` now expose provider-compatible object schemas while retaining strict runtime validation for their mode-specific parameters.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The command has no path, token, or shell-specific argument and is suitable for P
1. An agent calls `browser_open` with `mode: "create"`. AgentTab creates a background tab for that task and returns its task, tab, window, page-revision, and automation-route identifiers. `placement: "new_window"` may create the task's first tab in a separate unfocused normal window.
2. On a normal web origin, the agent calls `browser_snapshot`, works from revisioned accessibility references, then calls `browser_act` with the expected page revision. It cannot act on unrelated tabs.
3. If managed policy enables 1Password and an ordinary sign-in page has at most three origin-matching Login items, the agent can request a short-lived opaque token and ask the host to fill named field refs. Credential values travel only from `op` to the host and extension, never through Core RPC or the adapter. Every other human-only input uses `browser_handoff`, which focuses that tab, pauses automation, and blocks browser observation until the declared completion condition or **I'm done**.
4. If AgentTab recognizes a send, publish, purchase, delete, upload, authorization, or permission-grant control, `browser_act` can return `commit_required`. The extension shows the staged effect in its popup. A human must approve it there before the agent can call `browser_commit` with the one-use staged token.
4. If AgentTab recognizes a send, publish, purchase, delete, upload, authorization, or permission-grant control, `browser_act` can return `commit_required`. The extension shows the staged effect in its popup. A human must approve it there before the agent can call `browser_commit` with the one-use staged token. The disabled-by-default YOLO mode setting skips this Commit review and executes recognized consequential controls in the original `browser_act` call.
5. The task can list only its own tabs with `browser_tabs`. A separate client gets a separate task unless it proves its durable resume capability.
6. When browser work is complete, the agent calls `browser_finish`. Automatic cleanup closes tabs created by the task, preserves tabs adopted from the user's existing browser state, ungroups retained tabs, and releases task ownership. The popup setting can instead require confirmation or retain every tab.

Expand All @@ -37,7 +37,7 @@ Commit is a two-party, best-effort semantic barrier, not proof that a page has n

- **Task ownership is an execution and coordination boundary, not profile isolation.** AgentTab can use the signed-in session in the browser profile, but Standard mode does not expose raw cookies, storage, passwords, arbitrary JavaScript, raw CDP, coordinate actions, network interception, or a generic browser-global mutation API. Its one window-level operation creates an unfocused normal window for the first tab of an otherwise empty task.
- **Your Turn is the only routine focus transition.** Routine task work stays in task-owned tabs. During handoff, all agent observation and capture are denied so human credentials are not captured.
- **Commit requires human approval and agent intent.** A staged action is bound to its task, tab, page revision, element fingerprint, effect, and short expiry. Popup approval records consent but does not execute it. The agent must then call `browser_commit`; a changed page, expired stage, used token, or unapproved stage cannot execute.
- **Commit requires human approval and agent intent unless YOLO mode is enabled.** By default, a staged action is bound to its task, tab, page revision, element fingerprint, effect, and short expiry. Popup approval records consent but does not execute it. The agent must then call `browser_commit`; a changed page, expired stage, used token, or unapproved stage cannot execute. YOLO mode is an explicit local opt-out from Commit review, not from ownership, origin policy, revisions, handoff blackout, or credential boundaries.
- **Local by default.** Policy, task state, audit records, and IPC stay on the machine. AgentTab has no telemetry. See [Telemetry](docs/telemetry.md) and [Security](docs/security.md).

## Tool surface
Expand Down
10 changes: 6 additions & 4 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,17 @@ The Core response has `protocol: "agenttab.rpc"`, `version: 1`, matching `reques

Mutation methods carry a UUIDv7 idempotency key in Core RPC. MCP, OMP, and Pi bind one key to each harness invocation ID so a retry of that invocation retains its reconciliation identity. Reusing a completed key for identical work returns the durable response; reusing it with different input is a conflict. A mutation found only as started after recovery returns `unknown` and is not replayed.

The stdio MCP reader dispatches requests concurrently, while its writer serializes complete JSON-RPC lines. A long `browser_wait` or `browser_handoff` therefore does not block `ping`, discovery, or an independent tool call at the adapter layer.
`browser_handoff` returns as soon as the durable handoff and global browser blackout are active. The stdio MCP reader also dispatches requests concurrently while its writer serializes complete JSON-RPC lines, so handoff completion never holds the initiating agent call or blocks `ping`, discovery, or independent non-browser work.

Raw TypeScript and Python SDK clients raise `AgentTabTransportError` for an ambiguous timeout, connection close, or transport failure. The error carries the method and, for mutations, the exact generated or caller-supplied idempotency key. A caller may reconnect and explicitly retry the same method and parameters with that key; the SDK never replays the request automatically. MCP and OMP adapters likewise return the failed invocation, discard a cached client only when its transport is closed, and reconnect on the next invocation.

### Credentials and Your Turn handoff

When managed policy enables 1Password, call `browser_credentials` on an ordinary sign-in page before requesting manual password entry. `prepare` derives the current origin from host-owned tab state. `fill` accepts only accessibility refs and returns filled-field booleans; credential material never crosses Core RPC. Submit separately through `browser_act`, inspect the result, and use `next` only after the site rejects the current candidate.

Call `browser_handoff` when credential preparation returns `needs_user`, the bounded candidates fail, or the site requires a passkey, security key, CAPTCHA, payment secret, account recovery, or unsupported verification. AgentTab activates a global blackout, focuses the declared tab, opens its user-facing handoff state, and denies browser observation and capture for every task while the handoff is active. Automation resumes only after the declared navigation, URL, selector, or manual completion condition is satisfied and the handoff is cleared.
Call `browser_handoff` when credential preparation returns `needs_user`, the bounded candidates fail, or the site requires a passkey, security key, CAPTCHA, payment secret, account recovery, or unsupported verification. AgentTab activates a global browser blackout, focuses the declared tab, opens its user-facing handoff state, and returns a completed start result immediately. Browser automation resumes only after the declared navigation, URL, selector, or manual completion condition is satisfied and the handoff is cleared.

The agent must not attempt snapshots, page reads, or mutations during a handoff. It should report the handoff prompt to the user and wait for the terminal tool result or an explicit user completion.
The agent must not attempt snapshots, page reads, or browser mutations during a handoff. It should report the prompt to the user, continue independent non-browser work, and resume browser work after explicit user completion or a later status check confirms the handoff cleared.

### Staged Commit

Expand All @@ -128,7 +128,9 @@ The agent must not attempt snapshots, page reads, or mutations during a handoff.

The token is bound to the task, tab, effect, page revision, and element fingerprint. It expires after at most five minutes and is one-use. The extension popup must first record a human approval for that exact stage. Approval does not execute the action and does not expose the native token. The agent must then call `browser_commit`, which takes only the staged token, revalidates the target, and executes only an approved stage. A changed page, ownership change, expiry, unapproved stage, or repeated token makes the commit fail.

A `browser_act` batch is sequential and non-atomic. The extension stops before the first recognizable staged action and does not execute later actions implicitly. The current host response preserves the staged token and binding metadata, but does not publicly return the extension's completed-prefix list or staged index. Clients must not infer how many preceding actions ran from a `commit_required` response; inspect the page before deciding the next action. This is a source limitation, not a guarantee of an atomic batch.
YOLO mode is a disabled-by-default local popup setting. When enabled, recognized consequential controls execute inside the original `browser_act` call and return its ordinary completed result instead of `commit_required`. Enabling the setting discards pending staged actions. It bypasses only Commit review: task ownership, origin policy, expected page revisions, restricted-origin routing, handoff blackout, credential isolation, and action validation still apply.

With YOLO mode off, a `browser_act` batch is sequential and non-atomic. The extension stops before the first recognizable staged action and does not execute later actions implicitly. The current host response preserves the staged token and binding metadata, but does not publicly return the extension's completed-prefix list or staged index. Clients must not infer how many preceding actions ran from a `commit_required` response; inspect the page before deciding the next action. This is a source limitation, not a guarantee of an atomic batch. With YOLO mode on, the recognized action executes inline and the batch continues until completion or the next failure.

Commit reduces recognizable risk only. It requires both the popup's human approval and the agent's later `browser_commit`, but it cannot prove that a page's labels, event handlers, or side effects are benign.

Expand Down
2 changes: 2 additions & 0 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Before a Standard mutation, the extension prepares the target, classifies recogn

Commit is a best-effort semantic barrier, not proof that an action is harmless. It requires two distinct events, human approval in the popup and the agent's later Commit request. A page can hide an external effect behind an innocent label, alter meaning through script, or use an effect AgentTab does not recognize. Harmless-looking controls may execute without review. Batches are sequential and non-atomic: work stops before a staged action and never runs later actions implicitly.

YOLO mode is an explicit, disabled-by-default opt-out from Commit review. When enabled, recognized consequential controls execute during the original `browser_act` call, and pending staged actions are discarded when the setting changes on. The mode does not bypass task ownership, origin policy, expected revisions, restricted-origin routing, handoff blackout, credential isolation, or action validation.

## Your Turn blackout

During a `browser_handoff`, AgentTab pauses browser work and applies a global blackout across tasks. Page observations and captures are denied while the human enters information. The extension persists the active handoff before focusing the tab; the host restores the blackout from durable state after restart. Completion requires the declared condition or explicit completion, capture scrubbing, and host acknowledgement before automation resumes.
Expand Down
12 changes: 10 additions & 2 deletions packages/extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
commitRequired,
completed,
failed,
needsUser,
type NativeDispatchCommand,
type NativeOriginPolicy,
type NativeResponse,
Expand Down Expand Up @@ -409,7 +408,7 @@ async function dispatch(command: NativeDispatchCommand): Promise<NativeResponse>
if (!scheduler.isAccepting() || (await readState()).paused) {
throw scheduler.notStarted("AgentTab is paused");
}
return needsUser(
return completed(
command.request_id,
await handoff.begin(
command.task_id,
Expand Down Expand Up @@ -697,6 +696,7 @@ async function handlePopupMessage(message: Record<string, unknown>): Promise<Rec
automation_enabled: await automationEnabled(),
paused: state.paused,
developer_mode: state.developerMode,
skip_commit_review: state.skipCommitReview,
handoff: state.handoff.active ? { prompt: state.handoff.prompt } : null,
show_agent_pointer: state.showAgentPointer,
cleanup_policy: state.cleanupPolicy,
Expand Down Expand Up @@ -725,6 +725,14 @@ async function handlePopupMessage(message: Record<string, unknown>): Promise<Rec
});
return { enabled };
}
if (message.kind === "set_skip_commit_review" && typeof message.enabled === "boolean") {
const enabled = message.enabled;
await mutateState((state) => {
state.skipCommitReview = enabled;
if (enabled) state.stagedCommits = {};
});
return { enabled };
}
if (
message.kind === "set_cleanup_policy" &&
(message.policy === "automatic" || message.policy === "ask" || message.policy === "keep")
Expand Down
2 changes: 1 addition & 1 deletion packages/extension/src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ export class StandardBrowserRuntime {
}
await this.revisions.assertExpected(tabId, pageRevision);
const stagedConsequence = await this.consequence(tabId, pageRevision, action);
if (stagedConsequence) {
if (stagedConsequence && !(await readState()).skipCommitReview) {
const staged: StagedCommit = {
native_token: randomToken(),
task_id: taskId,
Expand Down
1 change: 1 addition & 0 deletions packages/extension/src/handoff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export class HandoffController {
started_at_ms: startedAt,
});
return {
handoff_started: true,
task_id: taskId,
tab_id: numericTabId,
prompt: params.prompt,
Expand Down
7 changes: 7 additions & 0 deletions packages/extension/src/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ <h2 id="tasks-title" class="label">Task groups</h2>
<option value="keep">Keep task tabs</option>
</select>
</label>
<label class="toggle-row" for="yolo">
<span class="row-copy">
<strong>YOLO mode</strong>
<small>Execute recognized consequential actions without Commit review. Ownership, origin, handoff, and credential boundaries stay enforced.</small>
</span>
<input id="yolo" type="checkbox">
</label>
<label class="toggle-row" for="pointer">
<span class="row-copy">
<strong>Agent pointer</strong>
Expand Down
13 changes: 13 additions & 0 deletions packages/extension/src/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface UiState {
automationEnabled: boolean;
paused: boolean;
developerMode: boolean;
skipCommitReview: boolean;
pointer: boolean | null;
cleanupPolicy: "automatic" | "ask" | "keep";
handoffPrompt: string | null;
Expand Down Expand Up @@ -89,6 +90,7 @@ const taskCount = element("task-count", HTMLSpanElement);
const taskList = element("tasks", HTMLUListElement);
const taskError = element("task-error", HTMLParagraphElement);
const pointerToggle = element("pointer", HTMLInputElement);
const yoloToggle = element("yolo", HTMLInputElement);
const cleanupPolicy = element("cleanup-policy", HTMLSelectElement);
const pointerDetail = element("pointer-detail", HTMLElement);
const settingsError = element("settings-error", HTMLParagraphElement);
Expand Down Expand Up @@ -180,6 +182,7 @@ async function load(): Promise<UiState> {
automationEnabled: response.automation_enabled === true,
paused: response.paused === true,
developerMode: response.developer_mode === true,
skipCommitReview: response.skip_commit_review === true,
pointer: typeof response.show_agent_pointer === "boolean" ? response.show_agent_pointer : null,
cleanupPolicy: response.cleanup_policy === "ask" || response.cleanup_policy === "keep"
? response.cleanup_policy
Expand Down Expand Up @@ -354,6 +357,7 @@ function render(state: UiState): void {

developerChip.hidden = !state.developerMode;
developerPanel.hidden = !state.developerMode;
yoloToggle.checked = state.skipCommitReview;

handoffPanel.hidden = state.handoffPrompt === null;
if (state.handoffPrompt !== null) {
Expand Down Expand Up @@ -488,6 +492,15 @@ pointerToggle.addEventListener("change", () => {
});
});

yoloToggle.addEventListener("change", () => {
const enabled = yoloToggle.checked;
void guard(settingsError, async () => {
await send({ kind: "set_skip_commit_review", enabled });
}).then((ran) => {
if (!ran) yoloToggle.checked = !enabled;
});
});

handoffCancel.addEventListener("click", () => {
void guard(handoffError, async () => {
await send({ kind: "handoff_finish", completed: false });
Expand Down
3 changes: 3 additions & 0 deletions packages/extension/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface ExtensionState {
schemaVersion: typeof SCHEMA_VERSION;
paused: boolean;
developerMode: boolean;
skipCommitReview: boolean;
showAgentPointer: boolean;
cleanupPolicy: CleanupPolicy;
tasks: Record<string, TaskRecord>;
Expand All @@ -74,6 +75,7 @@ function defaultState(): ExtensionState {
schemaVersion: SCHEMA_VERSION,
paused: false,
developerMode: false,
skipCommitReview: false,
showAgentPointer: true,
cleanupPolicy: "automatic",
tasks: {},
Expand Down Expand Up @@ -270,6 +272,7 @@ function parseState(value: unknown): ExtensionState | null {
schemaVersion: SCHEMA_VERSION,
paused: raw.paused,
developerMode: raw.developerMode,
skipCommitReview: raw.skipCommitReview === true,
showAgentPointer: raw.showAgentPointer,
cleanupPolicy: cleanupPolicy as CleanupPolicy,
tasks,
Expand Down
Loading
Loading