Skip to content

fix(control-hub): make browser.wait actually wait - #2212

Merged
bobleer merged 1 commit into
mainfrom
bob/bitfun-agent-wait-issue-1c9f2f
Aug 11, 2026
Merged

fix(control-hub): make browser.wait actually wait#2212
bobleer merged 1 commit into
mainfrom
bob/bitfun-agent-wait-issue-1c9f2f

Conversation

@bobleer

@bobleer bobleer commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Problem

Asked to pause 30 minutes, the agent called ControlHub { domain: "browser", action: "wait", params: { ms: 1800000 } } and got back { ok: true, data: { action: "wait", success: true }, summary: "Wait completed" } — in milliseconds. It then carried straight on to the next step.

This was not a flake. Three things stacked up:

  1. The handler read only params.duration_ms, so ms was dropped on the floor.
  2. With neither a duration nor a condition, BrowserActions::wait fell through to Ok({ success: true, action: "wait" }) and returned immediately. That payload — no ms field, success: true — is exactly what the failing call produced.
  3. Even a correctly-keyed call was capped at ms.min(30_000), silently. A 30-minute wait was not expressible.

Underneath all three: the ControlHub description documented no wait parameters whatsoever, so the model had to guess the key and got no feedback when it guessed wrong.

Changes

  • Accept the spellings models actually emitduration_ms / ms / wait_ms / sleep_ms, plus seconds / secs / duration_s variants (converted), numeric strings and floats included. Millisecond keys are read before second keys so a call carrying both cannot be off by 1000×.
  • Never silently no-op — a wait with neither duration nor condition returns INVALID_PARAMS with the correct usage, instead of reporting a success that did not happen.
  • Cap raised to 60 minutes, in step with AgentWaitTool::MAX_TIMEOUT_MS. The result reports ms / requested_ms / clamped, and the summary reads Waited 30m00s — or says "clamped" outright when it was shortened.
  • Interruptible — the sleep races the turn's cancellation token. call_impl also stops folding BitFunError::Cancelled into an ok: false envelope, which would both hide a user's stop from the pipeline and invite the model to "recover" from a turn already being torn down.
  • No browser session required for a pure pause — it touches no page, and agents pace themselves long before they open a browser. Condition waits still resolve a session as before.
  • { condition, timeout_ms } disambiguated — a condition always wins, and any duration passed alongside becomes its timeout rather than a sleep that never looks at the page. Condition waits keep the previous 15s default, now configurable; the selector poll became deadline-based instead of a fixed 30 iterations.
  • Documented in the tool description, including a pointer to the Cron tool for anything recurring — wait holds the turn open for its full duration, so it suits a one-off pause, not a schedule.

Testing

cargo test -p bitfun-core --lib — 1946 passed, 0 failed.

Seven new tests cover the parameter aliases, that the wait genuinely elapses, clamp reporting, the missing-parameter rejection, cancellation mid-wait, {condition, timeout_ms} not degrading into a sleep, and the description carrying both the parameter docs and the Cron pointer.

cargo fmt --check and cargo clippy are unchanged from the base — 7 pre-existing fmt diffs and 11 pre-existing MutexGuard held across await findings, all in files this PR does not touch.

`browser.wait` read only `duration_ms`, so the very plausible
`{ "ms": 1800000 }` was dropped and the call fell through to a branch that
returned `{ success: true }` instantly. An agent asked to pause 30 minutes
got "Wait completed" back in milliseconds and moved straight on. The
action documented no parameters at all, so the model had to guess the key,
and even a correct guess was silently capped at 30 seconds.

- Accept the spellings models emit: `duration_ms` / `ms` / `wait_ms` /
  `sleep_ms`, plus `seconds` / `secs` variants, numeric strings included.
- Reject a `wait` carrying neither duration nor condition with
  INVALID_PARAMS instead of reporting a success that never waited.
- Raise the cap to 60 minutes, in step with AgentWait's MAX_TIMEOUT_MS, and
  report `ms` / `requested_ms` / `clamped` so a shortened wait says so.
- Race the sleep against the turn's cancellation token, and stop
  `call_impl` from folding Cancelled into an `ok: false` envelope — a stop
  during a long pause must not wait out the pause, nor look like a tool
  error the model tries to recover from.
- Serve duration waits before session resolution: a pure pause touches no
  page, and agents pace themselves long before they open a browser.
- Resolve `{ condition, timeout_ms }`: the condition always wins and any
  duration bounds it, rather than sleeping and never looking at the page.
  Condition waits keep their previous 15s default, now configurable.
- Document all of it in the tool description, and point repeating schedules
  at the Cron tool, which ends the turn instead of pinning it open.
@bobleer
bobleer merged commit 9f8b560 into main Aug 11, 2026
7 checks passed
@bobleer
bobleer deleted the bob/bitfun-agent-wait-issue-1c9f2f branch August 11, 2026 06:06
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