Skip to content

fix(dialog): let agents decide native JavaScript dialogs - #379

Open
NianJiuZst wants to merge 1 commit into
Tencent:mainfrom
NianJiuZst:codex/js-dialog-agent-control
Open

NianJiuZst wants to merge 1 commit into
Tencent:mainfrom
NianJiuZst:codex/js-dialog-agent-control

Conversation

@NianJiuZst

@NianJiuZst NianJiuZst commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

A native confirm("Delete all 200 test rows?") currently accepts automatically, so the page proceeds without an agent decision. Prompts and beforeunload dialogs have the same forced-accept behavior. This change lets the agent inspect the pending dialog, accept or dismiss it, supply prompt input, and obtain the original action's result without executing the action again.

Fixes #359.

How it works

  • Hold all four native dialog types (alert, confirm, prompt, beforeunload) with a dialog ID, type, message, bounded default preview, attachment identity, and decision deadline. Record Chrome's actual closing event rather than assuming the requested decision succeeded.
  • Add dialog status/accept/dismiss on a same-session control path that bypasses the busy action queue and renderer-dependent preparation/debug screenshots. Changing an auto-accept default alone would leave a sequential agent unable to answer the modal while its original call blocks.
  • Return dialog_pending (CLI exit 6) with an original operation_id. Keep the complete dispatched operation, busy/inflight ownership, audit, and transfer postprocessing alive. operation await retrieves its original success or failure; chained dialogs retain that operation ID. It never repeats the click/evaluation/navigation.
  • Add five actions to the existing DSH browser_assist tool and preserve pending IDs in model-facing errors. CLI and DSH skills tell the agent to decide from the requested workflow, including choosing input/confirmation, without turning each native dialog into human request-help.
  • Enforce session ownership and reject stale or concurrent decisions. Invalidate pending identities/results across stop, detach, window close, disconnect, and reconnect. An asynchronous dialog can be discovered with status; a new ordinary action refused before dispatch explicitly reports dispatched: false.
  • Preserve prompt defaults in full when input is omitted; CDP otherwise substitutes an empty string. Explicit --text "" remains empty input. Handle beforeunload dismissal as a cancelled navigation that keeps the current page, including Chrome's ERR_ABORTED event ordering.

Requires matching protocol 1.4 components. The updated extension rejects daemons older than 1.4. Unanswered dialogs reject after 60 seconds and fail the original operation. Receipts are bounded to 64 slots, 4 MiB per result, and up to five minutes from completion; completed receipts can be evicted at capacity. Cancellation cannot undo page effects already dispatched.

Real browser verification

Passed on macOS ARM64 / Chrome for Testing 153.0.8010.12, at 2026-09-30 07:26:39 UTC. Tested implementation: d928da30e9e00e8ef613adaf9fc4f5e6b83b6265.

The runner launches a visible browser with a fresh profile, production MV3 extension, and a private daemon. Every dialog answer uses the actual CLI → daemon IPC → extension WebSocket → chrome.debugger path. Remote CDP is only used for browser setup/metadata; it never answers a dialog. Debug capture remains enabled while the native modal is pending.

Actual example Observed result
Dismiss confirm opened by evaluate Original false; retrieving twice leaves the invocation counter at one
Accept confirm opened by a real button click The separate click changes the page state and increments the counter once
Accept prompt with Agent-selected name Original result is exactly that string
Accept empty input / dismiss prompt Exact "" / null
Accept prompt with omitted input Preserves anonymous, and all 10,000 characters of a long default
Explicitly acknowledge alert Original action completes only after the decision
Confirm followed by prompt Same original operation, distinct dialog IDs, one invocation; stale first ID refused
Dismiss / accept beforeunload Original navigation cancelled and URL kept / original navigation completes at destination
Cancel original operation Modal cleared; original cancelled failure remains retrievable
Another session Cannot inspect, answer, or retrieve the owner's dialog
Asynchronous prompt followed by an ordinary action Status discovers it; new action is refused before dispatch with no side effects
No answer for the real 60-second deadline Confirm rejected; original fails; deletion remains false with one invocation

Reproduction instructions.

For example, these commands in the recorded run supplied custom prompt input and retrieved the original value:

bsk dialog accept ad99a8cd-bfe1-4e59-a33d-53db512ef285 --session mwxo --text 'Agent-selected name' --json
bsk operation await op-6cdb1049-a28d-4353-9a0b-1c8b7b057720 --session mwxo --json
# state: completed; result.value: "Agent-selected name"

Screenshots from that run

Unedited OS captures of the isolated test window. A modal can defer repainting the counters behind it; state assertions run after closure.

Pending confirm before a decision:

Native confirm pending

Pending prompt before supplying input:

Native prompt pending

Page results after dismissal, a separately accepted click, and custom input:

Results after explicit decisions

Local checks

  • cargo test --workspace --locked: 837 passed, 0 failed, 1 ignored across 46 targets, including a real daemon IPC/WS regression with a controlled extension peer.
  • cargo fmt --all -- --check and cargo clippy --workspace --all-targets --locked -- -D warnings: passed. A test-only owned-vector iteration cleanup in the updater avoids the current Rust redundant-clone lint; updater runtime behavior is unchanged.
  • pnpm --filter @browser-skill/extension test: 2,440 passed, 122 skipped; compile and production build passed.
  • DSH plugin: 448 passed; typecheck, build, and npm skill package validation passed.
  • Biome, Stylelint, Node script tests, CLI/DSH skill budgets including CRLF, Cargo skill package validation, and browser-eval validation passed.
  • The opt-in real Chrome runner passed with BSK_DIALOG_TEST_TIMEOUT=1, including the actual 60-second fallback.

The real-browser cases use predetermined decisions to verify available controls and continuation; they are not an LLM decision-quality benchmark. DSH model-facing routing has separate plugin tests. Hosted CI and Windows behavior are reported by the PR checks separately.

@NianJiuZst
NianJiuZst force-pushed the codex/js-dialog-agent-control branch from f488ab1 to d928da3 Compare September 30, 2026 07:56

This branch has not been deployed

No deployments
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.

[Feature] Give agents control over JS dialogs (confirm/prompt/beforeunload): dismiss/text/status instead of forced auto-accept

1 participant