Skip to content

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

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

NianJiuZst wants to merge 5 commits 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 bsk_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.
  • Cancellation and the 60-second decision timeout keep dismissing subsequent dialogs until the original operation settles. A rejected/new request cannot replace the cleanup owner. Evaluate/navigation cleanup failures report a page-operation timeout with unknown effects, never a file-transfer error.
  • 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.5 components. The updated extension rejects daemons older than 1.5. 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.

Historical browser evidence — 2026-09-30

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

Current validation — 2026-10-20

Merged upstream main e8777ab71bb851196895515b53eea2481462dd57 into the original branch. Protocol 1.5 distinguishes native dialog control from main's protocol 1.4 incognito support; DSH uses the current bsk_* names.

  • Extension suite: 2,695 passed, 244 skipped; TypeScript, production build, Biome and Rust formatting passed.
  • Rust workspace library/binary tests: 629 passed, 1 ignored. Production dialog/handshake/queue integration tests: 21 passed. Workspace/all-target Clippy passed.
  • DSH plugin: 540 passed; production build and actual npm package/resource validation passed. Node script checks: 13 passed, including LF and CRLF skill-byte budgets (DSH entry: 4,347 / 4,432 bytes).
  • Real private daemon + installed production extension + Chrome 153: reran the confirm, prompt, empty/default input, alert, chained-dialog, beforeunload, cancellation, ownership and asynchronous-dialog scenarios above.
  • Additional out-of-tree real-browser regression: confirm → prompt → confirm cancellation settled in 125 ms, left no modal, retained the cancelled result and executed the page expression once. A real 60-second decision timeout likewise dismissed the following prompt and allowed later commands.
  • Additional out-of-tree production queue regression: five page methods (evaluate, navigate, back, forward, reload), each with acknowledged and missing cleanup. The missing-cleanup case failed before the error-contract fix and all cases pass afterward.
  • Independent P0–P2 review completed; the confirmed cleanup findings were fixed and the follow-up review passed.

New validation scripts, fixtures and logs remain outside the repository. The older screenshots above retain their historical date/head. Browser cases use predetermined decisions, not an LLM decision-quality benchmark. Hosted CI and Windows results are tracked by this PR's current-head checks.

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