fabro/apps/fabro-web/app
fabro-sh-0530[bot] 79f89165f6
Wire end-to-end steering for running agents (#209)
## Summary
This makes the advertised mid-run steering path real: users can send
append or interrupt steering messages through the API, CLI, and web UI,
and the worker delivers them to live API-mode agent sessions or buffers
them for the next session. The change adds the control protocol, session
interrupt machinery, workflow hub, server route/OpenAPI/client updates,
and UI feedback needed for the whole path.

### Plan Summary
- Add `SteerKind`/`run.steer` wire protocol and `POST /runs/{id}/steer`
- Deliver steers through subprocess JSONL or the in-process
`SteeringHub`
- Support append and interrupt behavior in agent sessions, with bounded
buffering and events
- Expose steering in the CLI/web UI and surface SSE toasts

## Flow

```mermaid
flowchart TB
  UI["CLI / Web UI"] --> API["POST /runs/{id}/steer"]
  API -->|"subprocess transport"| Control["Worker control JSONL"]
  API -->|"in-process transport"| Hub["SteeringHub"]
  Control --> Hub
  Hub -->|"active API sessions"| Session["SessionControlHandle"]
  Hub -->|"no active session"| Pending["Pending buffer"]
  Pending -->|"first future API session"| Session
  Session --> Agent["Session round loop"]
  Agent --> Events["RunEvent stream"]
  Events --> UI
```

## What changed and why

- Agent sessions now expose a lightweight `SessionControlHandle`, drain
steering at the top of each round, and use a replaceable round
cancellation token for interrupts. LLM waits are cancelled promptly,
while tool execution observes cancellation cooperatively so every
committed `tool_use` still gets a matching `tool_result`.
- `SteeringHub` owns active API session registration, broadcast
delivery, pending buffering, FIFO queue caps, and steering
lifecycle/drop events. A completion coordinator closes the
final-response race without introducing a workflow dependency into the
agent crate.
- The server route replaces the 501 stub, validates run state and
best-effort CLI-only steerability, and forwards through either
subprocess control JSONL or the in-process hub. OpenAPI and generated
clients now include the request type.
- The CLI and web UI can send append or interrupt steers. Run detail and
board views open the new composer, and shared SSE subscriptions now
support per-subscriber event callbacks so invalidation and steering
toasts can coexist on one EventSource.

## Review notes

- Steering actors stay on top-level `RunEvent.actor`; event props only
carry steering kind/drop metadata.
- Buffered steers replay as append messages to the first API session
that registers after an empty-active period. Per-stage targeting remains
out of scope.
- CLI-mode agent stages are still not steerable; the server returns a
best-effort 409 when all active agent stages are CLI-mode, while the
worker hub remains the authoritative safety net.
- No persistence or schema migration is required; active and pending
steering state is in memory.
- New tests focus on protocol round-trips, hub buffering/bounds, session
steering-loop behavior, SSE fanout, and basic server rejection paths.

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 15:34:16 -04:00
..
components Wire end-to-end steering for running agents (#209) 2026-05-05 15:34:16 -04:00
data refactor(api): reuse board column contract across clients 2026-05-04 15:52:24 -04:00
hooks Wire end-to-end steering for running agents (#209) 2026-05-05 15:34:16 -04:00
layouts chore(web): namespace static images under /images and skip in HTTP logs 2026-04-30 08:57:14 -04:00
lib Wire end-to-end steering for running agents (#209) 2026-05-05 15:34:16 -04:00
routes Wire end-to-end steering for running agents (#209) 2026-05-05 15:34:16 -04:00
app.css refactor(web): drop light mode, go dark-only 2026-04-20 09:27:08 -04:00
entry.tsx refactor(web): move server state to SWR 2026-04-25 07:16:41 -04:00
install-api.test.ts feat(install): add sandbox provider step to web install wizard 2026-04-27 14:59:17 -07:00
install-api.ts feat(install): add sandbox provider step to web install wizard 2026-04-27 14:59:17 -07:00
install-app.test.tsx feat(install): add sandbox provider step to web install wizard 2026-04-27 14:59:17 -07:00
install-app.tsx chore(web): namespace static images under /images and skip in HTTP logs 2026-04-30 08:57:14 -04:00
install-config.test.ts fix(install): exclude openai-compatible from v1 setup 2026-04-19 11:43:38 -04:00
install-config.ts feat(install): redesign web wizard and scope dev token to PAT installs 2026-04-19 16:11:02 -04:00
install-flow.test.ts fix(install): exclude openai-compatible from v1 setup 2026-04-19 11:43:38 -04:00
install-flow.ts fix(install): exclude openai-compatible from v1 setup 2026-04-19 11:43:38 -04:00
install-router.test.tsx fix(install): mount wizard at root route 2026-04-24 12:59:16 -04:00
install-router.tsx fix(install): mount wizard at root route 2026-04-24 12:59:16 -04:00
mode.test.ts fix(install): harden web wizard against review findings 2026-04-19 12:43:00 -04:00
mode.ts fix(install): harden web wizard against review findings 2026-04-19 12:43:00 -04:00
root.tsx refactor(web): drop light mode, go dark-only 2026-04-20 09:27:08 -04:00
router.test.tsx refactor(setup): remove browser-based GitHub app bootstrap 2026-04-13 18:25:29 -04:00
router.tsx feat(web): add run logs view 2026-04-26 17:38:22 -04:00