Skip to content

fix(app): derive waiting steer presentation from execution state - #53880

Merged
opencode-agent[bot] merged 4 commits into
v2from
derived-pending-steers
Oct 8, 2026
Merged

opencode-agent[bot] merged 4 commits into
v2from
derived-pending-steers

Conversation

@opencode-agent

@opencode-agent opencode-agent Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Alternative to #53758, which fixes the same gray pending flash on desktop. Credit to @usrnk1 for identifying the problem and the failure/interrupt recovery case.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

A prompt sent while a Session is idle is stored as a pending steer until the runner promotes it. Before this change, the timeline styled every pending steer as waiting: gray, with queue/delete actions. As a result, normal sends flashed gray until delivery.

#53758 fixes this with a client-side immediate record keyed by inbox ID. The client sets it at admission and has to clear it at six separate points. This PR derives the same presentation from state the client already has, so no new client state is needed.

  • When an execution starts from idle, the runner promotes every pending steer, up to the first control item. data.session.pending.status(sessionID, inboxID) reports where a pending user prompt stands:
    • starting: promoted at the next idle boundary. Either the Session is idle and the prompt is newer than the latest idle marker, or it is running and the current execution has not delivered input yet.
    • steering: the current execution already delivered user input (a delivered user message appears after the latest idle marker), so the prompt waits for the next step boundary.
    • stranded: the Session is idle and the prompt predates the latest idle marker. It outlived an execution that failed or was interrupted before delivering it.
    • queued: delivery: "queue".
  • SessionUserActions.pending now takes this status. Gray styling and the "Pending" label apply only to steering and stranded. Every undelivered steer, including a starting one, keeps Move to queue / Delete instead of Revert. stranded is kept separate so the UI can later treat it differently, for example by offering a resend.

Compared with #53758:

  • Prompts from another client while idle no longer show gray. A client-local flag cannot know about them.
  • Rapid follow-ups admitted before promotion keep normal styling, because the server delivers them in the same batch. One that arrives after the first delivery is a real steer and turns gray.
  • After a failed start, the leftover steer is gray while idle. It returns to normal once the next execution starts, since that execution will promote it.

Known limitations:

  • If the idle marker from a failed execution falls outside the loaded history page after a reload, a leftover steer shows as about to start.
  • A steer queued behind a pending move or compaction control is reported as starting while the runner handles that control first. Those steers are still delivered in the same execution: a move continues the drain at the destination without settling it. They are delayed, not stranded.

How did you verify your code works?

  • packages/client/test/solid-pending-status.test.ts covers eight cases: idle send through delivery, a mid-turn steer including queue↔steer changes, rapid follow-ups, stranded steers after failed and interrupted starts, a later execution, a remote idle prompt, and a queued prompt.
  • bun test test/solid-pending-status.test.ts test/solid-inbox-race.test.ts in packages/client: 14 tests pass.
  • e2e/regression/session-queue.spec.ts (29 tests) passes locally. The Move to queue / Delete case now covers both steering and starting steers and asserts the waiting styling for each.
  • bun typecheck passes in packages/client, packages/session-ui and packages/app. oxlint and prettier --check are clean on the changed files.
  • I recorded the real app against the e2e mock server and scripted inbox/execution events, comparing v2, feat(desktop): reserve gray message styling for pending steers #53758 and this change side by side for five scenarios: idle send, remote prompt, rapid follow-up, mid-turn steer, and failed start. In every scenario this change matched the server's promotion behavior.

Requested by: @Brendonovich (Brendan via Slack)

A prompt sent while the Session is idle is admitted as a pending steer and
was styled as waiting until the server delivered it. Derive whether a pending
steer is actually waiting from execution state instead: an idle boundary
promotes every pending steer, so a steer waits only once the current
execution has delivered input, or when it outlived an execution that ended
without delivering it.
A starting steer keeps normal styling but is still undelivered, so it keeps Move to queue and Delete instead of Revert.
@opencode-agent
opencode-agent Bot merged commit 434f7b2 into v2 Oct 8, 2026
9 checks passed
@opencode-agent
opencode-agent Bot deleted the derived-pending-steers branch October 8, 2026 07:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant