Merge remote-tracking branch 'origin/main' into animato
ci / web (push) Successful in 22s
ci / flake (push) Successful in 4m47s
ci / rust (push) Successful in 5m59s

# Conflicts:
#	crates/maestro-daemon/Cargo.toml
#	crates/maestro-daemon/src/local_api.rs
#	crates/maestro/Cargo.toml
This commit is contained in:
Miguel Palhas
2026-09-02 18:37:53 +01:00
43 changed files with 1918 additions and 360 deletions
+45
View File
@@ -20,6 +20,25 @@ A card is always two lines: **line 1** and **line 2**.
Line 1 carries no badges. The `⚠ blocked work` badge (#83) and the `exited`
badge move off this row into line 2.
### Where a name comes from (#131)
`metadata.name_source` says whether the name is settled or provisional:
| Source | Means | Rendered |
|---|---|---|
| `User` | Typed in the spawn form, or set by a rename. | Normal |
| `Generated` | The daemon's own handle, assigned because the spawn carried no name. | Muted |
| `Haiku` | Derived from the session's first prompt. | Normal |
A missing or unrecognised source reads as `User`. That way a session named
before the field existed is never dimmed, and never autonamed over.
Only `Generated` renders muted, and it is the only source the daemon will
replace: the first prompt an agent reports to `POST /api/sessions/:id/prompt`
settles the session at `Haiku`. A session that never reports a prompt — a
shell, or an agent with no hook installed — keeps its muted handle for as long
as it lives. That is accurate, not a missing state: nothing has named it yet.
The name is editable in place (#125). In the web client, right-clicking a card
opens a context menu whose **rename** entry swaps the name for a text field —
Enter commits, Escape cancels. The TUI opens the same edit as a prompt, bound
@@ -211,6 +230,32 @@ arriving at `POST /api/sessions/:id/activity` is parsed strictly and an unknown
state is a 4xx: an agent posting a state the daemon does not know is a bug in
the agent, and answering 204 would hide it.
## Focus clears `AwaitingInput`
A session's pane — the pane a client's own attach view is showing, never a
sidebar highlight — clears its `AwaitingInput` glow once it has been
**continuously focused for 5 seconds**: the operator has looked, so the rail
stops asking for attention and the card reads as `Idle` instead. This is a
real state change, not a client-side paint-over — the daemon computes it, so
every client watching the session (web, TUI, a second pane on the same
session) sees the same colour.
The acknowledgement is sticky: it survives losing focus, and holds until the
session is genuinely `Active` again — new work, not merely a refocus. Only
after that fresh `Active` cycle does the *next* `AwaitingInput` glow again.
Unfocusing and refocusing alone never re-arms it.
This applies uniformly to `AwaitingInput` from either source — the daemon's
own inference or an agent's report (#105) — since the rail draws them
identically. It does not touch `Error`; that state's own TTL (below) is
unrelated and unaffected by focus.
Each client reports its own pane's focus with `POST /api/sessions/:id/focus`
(`{"focused": bool}`) whenever the pane it is showing changes — on attach, on
switching which session a pane shows, and on leaving the pane. A client
sends `false` for the session it just left before `true` for the session it
lands on, so a click that swaps panes never briefly reports both focused.
## Keyboard walk
`j`/`k` (and the arrow keys) move a highlight through the sidebar. Both clients