feat(desktop): keep the machine awake for background chats and yield the page to the user
Summary
Phase 2c of the desktop background executor. #8668 and #8670 are on staging.
Prevent sleep while a chat is running
- While any chat has desktop work claimed on this machine, the app holds a
prevent-app-suspensionpower blocker. It releases the blocker as soon as the last result is delivered. It never holds one while idle. - The behaviour is on by default, with a switch in Settings → Desktop. A one-line note says a closed lid still puts the computer to sleep, because the switch can't prevent that. The switch shows only when Sim runs chats on this device in the background.
- A result that keeps failing to reach Sim holds the machine awake for at most 10 minutes. Delivery keeps retrying after that.
- The switch is a new optional bridge method,
settings.setPreventSleepWhileRunning, so older desktop builds don't show it.
The agent yields the page to the user (takeover)
- Only calls from the background executor yield. A call from the chat view the user is watching behaves exactly as on staging, because there the user steers the agent directly.
- A background call that only reads the page (snapshot, read text, list tabs) never waits, since it cannot collide with the user.
- When the user clicks, scrolls or types in the page a background chat's agent is driving, that chat's next acting browser action waits. While it waits, the tab is marked as needing attention.
- The action resumes in the same page once the user has left it alone for 4 seconds.
- The wait is bounded at 30 seconds, or half the action's watchdog if that is shorter. A user who keeps working past that gets a clear "not run" rather than a timeout.
- A user who starts working mid-action (between a batch's steps, or during a click's target probes) stops it before its next input. The result says earlier steps may have taken effect.
- The activity counts only on the tab the user acted in.
- A Stop still cancels an action while it waits.
browser_request_takeoverkeeps its own explicit hand-back flow.- Other chats' browsers are unaffected.
Not in this PR: a background chat's page dialog can still take focus
A page in a hidden chat that calls confirm() or alert() still takes focus from the user's window. Electron shows its native message box before the driver's CDP handler answers the dialog. This happens whether or not the executor runs the chat, and it overlaps with #8432's dialog work. Suppressing native dialogs (disableDialogs) would also break the agent's dialog: {accept: true} answers. I reproduced it in a packaged-app E2E and left it out of this PR so it can be decided with #8432.
Test plan
-
Unit tests (
sleep-blocker.test.ts):- one blocker is held only while work runs;
- the switch is followed live;
- an idle machine never starts one.
-
Packaged-app E2E (
background-executor.spec.ts):- I: synthetic user input in the agent's tab holds a background
browser_clickuntil 4 s of quiet. The click then runs exactly once. - J: the blocker is taken when a background command starts and released once its result is delivered.
Both fail with the change reverted (mutation-checked). The full suite, A to J, passes in 1.1 min.
- I: synthetic user input in the agent's tab holds a background
-
type-check,check:audits,check:desktop-ipc,check:desktop-bridge, and the desktop Vitest suite (931 tests).