3.5 KiB
Computer Use Runtime Hardening
This follow-up addresses lifecycle gaps found during review of PR #892.
Problems
clearSession()did not create a stop tombstone when no session-state record existed yet, so a first queued invocation could activate after cleanup.- Read-only host actions did not acquire a session lease and could continue
after
user_stopped. - Later lifecycle events could overwrite
blocked_urloruser_stopped. - A process-wide executor queue blocked unrelated windows, while a first per-session replacement allowed two sessions to interleave snapshot/validation/dispatch against the same window.
- The ambiguity gate included ephemeral element IDs even when a stable element identity was available.
cursor_positionformatting existed without a production backend result.- Service release reasons did not distinguish a session-only notification from
a real child-generation release, so
clearSession(A)could discard retained observations and keyboard ownership for unrelated session B.
Root Cause
The Runtime treated observation and mutation leases as the only operations that needed lifecycle fencing. Cleanup also mutated only an already-created state record, while terminal transitions shared the same unrestricted transition helper as recoverable states.
The executor queue was scoped to the caller rather than the resource being validated. Ambiguity signatures also mixed stable identity with snapshot-local IDs, and the cursor result test used only a fake backend.
The backend also inferred generation loss from the release reason. The
no-in-flight clearSession() path emits session_cleared without stopping the
child, so the reason alone cannot establish that shared executor state changed.
Fix
- Create the same-turn stop tombstone unconditionally during
clearSession(). - Require an observation lease for every host-reading or waiting action.
- Make
blocked_urlanduser_stoppedabsorb later lifecycle events. - Serialize mutations by bound PID/window, with one conservative queue for unbound mutations; unrelated bound windows may still progress independently.
- Ignore ephemeral element IDs when a stable element identity is present.
- Read
get_cursor_positionthrough the pinned cua-driver and return the resolved point without moving the pointer. - Carry
generationReleasedas an explicit service release fact. Session-only releases invalidate only their listed sessions; actual child exits sweep all retained session observations and keyboard targets.
A new turn still creates a fresh Computer Use session state, preserving the existing explicit recovery boundary.
Verification
npm --workspace @maka/runtime run typechecknpm --workspace @maka/runtime testnpm --workspace @maka/computer-use testgit diff --check