feat: steer-only agent messages and a polished message box

Merged
DA
9d ago
+738-1182

Dean chose steer only: a message to a coding agent always steers the turn under way or opens one, and nothing is queued anywhere. This replaces O1's eve-side queueing (#1900) and reshapes O2's message box (#1907) into one consistent card. Base is coding-agents.

Server

  • POST /api/agents/{id}/messages takes { text, clientKey }. delivery is gone; the coder channel's turnPolicy is steer, and the eve client no longer names a per-message policy (both of Luke's channels steer).
  • Idempotent sends. The line's row stands under sentLineId(clientKey) in messages.client_id. The route reads it before GitHub is asked (sentLineStands) and the writer reads it again under the conversation lock, so a send repeated after a lost answer answers the agent as it stands and sends nothing; two sends racing under one key send once. This closes the gap O2 reported (a lost answer plus Retry repeating an instruction).
  • takeAwaitingLine matches the oldest turnless user row by its words. That is safe with steer only: lines are received in the order sent, and two identical awaiting lines are interchangeable (same words, no turn, no turnId on the wire), so whichever row a receipt takes, the end state is the same. The old delivery-id match existed only for a steer overtaking a queued message, which can no longer happen. tests/coder-host.test.ts covers two identical messages.
  • Goldens re-recorded under LUKE_UPDATE_FIXTURES=1: coding-agent-wire-codingAgentMessageRequestSchema and coding-agent-view-codingAgentMessageParamsSchema lose delivery and gain clientKey.

Desktop

  • The box is AI Elements' PromptInput in AgentTab's composer slot: one 12px-radius card on the raised surface behind a hairline that brightens on focus-within, the textarea in the body font with no inner border, auto-growing from one to about eight lines, and a toolbar row inside the card's bottom edge.
  • Root cause of the monospace well: base.css resets button but not textarea, and Tailwind's preflight is deliberately left out, so the field was the browser's default (monospace, hard border). base.css now resets textarea to font: inherit, border: 0, and the composer test holds that as a stylesheet contract.
  • Nothing changes with the agent's state: same placeholder ("Message the agent…"), same size, no hint line, no queue rows, no split menu, no ⌥Enter. The one exception is the round 28px button at the right: Send ↑ with text (disabled when empty and idle), Stop ■ when empty and a turn runs, both with tooltips.
  • Enter sends, Shift+Enter is a new line. Every send mints a clientKey; Retry reuses it. Errors are an inline line under the card in the host's words with Retry, the text staying in the box; a retired agent keeps the box, disabled, with the reason and no Retry.
  • O2's foundations stay: the optimistic send with reconcile, the (agent, turnId) ledger with observeMessaged, and Stop owned by the composer.

Removed

  • packages/hosted/src/coding-agent-wire.ts: CODING_AGENT_DELIVERY / CodingAgentDelivery, the request's delivery field, awaitedDeliverySchema / readAwaitedDelivery / awaitedDeliveryOf, and the queueing docs.
  • packages/wire: MESSAGE_DELIVERY / MessageDelivery (nothing imports them any more) and the delivery field of the stored typed-ask metadata.
  • apps/web/server/coding-agents-app.ts: the delivery parameter and its comments. writeAwaitingLine stays, since it is the write every steer takes; it now takes the key and checks it under the lock.
  • apps/web/server/hosted/brain-host/eve-sessions.ts: the per-message turnPolicy (TURN_POLICY_OF_DELIVERY, EveMessage.delivery).
  • apps/web/server/hosted/coder-host/channel.ts: TURN_POLICY QUEUE → STEER, comment rewritten; apps/web/coder/channels/eve.ts: the "queue policy" wording.
  • apps/desktop/src/renderer/planning/agent-composer.tsx: the Queue rows, the delivery split menu, ⌥Enter, the hint line, the state-dependent placeholder.
  • apps/desktop/src/renderer/ai-elements/queue.tsx and its CSS (.agent-composer-queue*, -hint, -chevron, -menu, -closed).
  • Desktop IPC and model (coding-agent-acts, testing/acts.ts, coding-agent-model.ts, host-operator.ts): the delivery argument, agentLines's queued split.
  • apps/web/server/README.md: the queue and metadata.delivery prose. docs/PLANNING.md had none.
  • Their tests, deleted because the feature is removed; the unrelated "queued" words (turn lifecycle, planning questions, the brain-host channel's own policy, preview deployment) are untouched.
  • O2 added no shortcut, settings, or keycap entry for queueing, so there was nothing to remove there.

Screenshots

Rendered in headless Chrome over the real AgentTabView and the built renderer stylesheet (Linux fonts, so the face is DejaVu rather than SF Pro); committed on pr-assets.

Idle, emptyRunning, with text
idle, emptyrunning, text
Running, empty (Stop)Error with Retry
running, emptyerror, retry

Verification

./scripts/check.sh exits 0 locally (lint, knip, discarded-effect, typecheck, every test file, the harness tests). CI could not verify the Mac: no macOS job runs on a pull request, and Dean waived ./scripts/verify.sh for this work, so the Mac rendering is unverified beyond the headless captures above.

🤖 Generated with Claude Code


Open workspace in Conductor


Devin Review