Commit Graph
8 Commits
Author SHA1 Message Date
naps62-yoloandClaude Opus 4.7 c1b4fe0bb6 fix(maestro): make attach exit cleanly when session ends (NAP-226)
ci / rust (push) Successful in 2m15s
When a session terminates (the user runs `exit`, tmux is killed, or
`maestro kill` is called) the daemon's per-session broadcast channel
closes and `serve_attach` breaks out of its loop. The socket was then
dropped without a Close frame, so the client's `stream.next()` would
hang and the terminal stayed stuck in raw mode.

- daemon: send `Message::Close(None)` before returning from
  `serve_attach` whenever the session itself ended (broadcast closed,
  or session was killed between auth and subscribe).
- client: track remote-close, restore the TTY first, then print
  `[maestro] session ended` so the message lands in cooked mode.
- tests: cover the kill path and the liveness-watcher reap path on the
  daemon side.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 08:49:00 +00:00
naps62-yolo a2ffb0ad33 feat(maestro): reap dead tmux sessions via liveness watcher (NAP-227)
ci / rust (push) Successful in 1m37s
The daemon never noticed when a session's command exited and tmux tore
the session down — the in-memory `SessionManager` map plus the registry
catalog kept showing it as `Running`, and a follow-up `maestro attach`
would hang on an empty broadcast.

Adds a per-host background task that polls `tmux list-sessions` every
2s, diffs against the in-memory set, and reaps anything we still track
but tmux has lost: drops the record, deletes the sidecar + FIFO,
cleans up its worktree if requested, and emits `SessionEvent::Removed`
so the registry purges its catalog.

The task holds a `Weak<Inner>` so it exits cleanly once the last
`SessionManager` clone is dropped. Skips the tmux subprocess when the
in-memory map is empty.
2026-05-26 07:26:42 +00:00
naps62-yoloandClaude Opus 4.7 18bd385a2a feat(maestro): registry HTTP API, cookie auth, JWT issuance, events WS
ci / rust (push) Successful in 48s
Adds the client-facing surface on the registry (NAP-215). Endpoints:

- GET /api/daemons — list connected daemons (with daemon_url)
- GET /api/sessions — flat session catalog
- POST /api/sessions — forwards CreateSession to chosen daemon, 504 on
  10s daemon timeout
- DELETE /api/sessions/:id — forwards KillSession
- GET /api/sessions/:id/connect-info — returns {daemon_url, jwt} with a
  5min HS256-signed attach JWT
- WS /api/events — snapshot + delta broadcast of catalog and presence
- GET / placeholder served until NAP-218 web bundle lands

Cookie auth middleware mints a long-lived httpOnly JWT cookie on first
request and reissues silently on missing/tampered cookies. Per spec the
perimeter is the operator's network — the cookie is a second factor.

Protocol additions: daemon_url on Hello, DaemonInfo, ConnectInfo,
RegistryEvent. Daemon config + RegistryClient now carry daemon_url.

Registry state now exposes an event broadcast channel (subscribe gives
a snapshot + tokio::sync::broadcast::Receiver) and helper lookups
daemon_for_session / daemon_url.

Tests: 11 reqwest-driven HTTP integration tests cover every endpoint,
the cookie flow, 504 timeout path, worktree forwarding, and events WS
propagation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:08:41 +00:00
naps62-yolo 2b84783968 fix(maestro): drop stale wait_for_async import in attach test too
ci / rust (push) Has been cancelled
2026-05-24 16:17:28 +00:00
naps62-yolo fb2a0b1e6a fix(maestro): drop stale wait_for_async import in session_manager test
ci / rust (push) Failing after 1m14s
2026-05-24 16:10:18 +00:00
naps62-yolo 0624ad1257 style(maestro): apply cargo fmt
ci / rust (push) Failing after 1m15s
2026-05-24 15:53:35 +00:00
naps62-yolo ab9a5bf017 feat(maestro): wire daemon to registry + per-session WS stream
Completes the headless daemon stack for NAP-214.

- `config`: daemon TOML config (daemon_id, registry_url, bind_addr,
  token, state_dir, worktree_root) with sensible XDG defaults.
- `jwt`: HS256 verifier; the daemon validates per-attach JWTs locally
  with no callback to the registry. Also exposes `issue()` for tests.
- `pty::PtyBridge`: per-session FIFO + tokio broadcast pump. The FIFO is
  opened r+w so it never sees zero writers — stays alive across
  `pipe-pane` re-issues. Helper `pipe_pane_command()` builds the
  `cat >> <fifo>` shell line.
- `session::SessionManager`: authoritative live-session store. Creates
  worktrees (when requested), spawns tmux, mkfifo + pipe-pane, writes
  sidecar; tears it all down on kill. Emits diff events on a
  broadcast for the registry client. `recover()` adopts surviving
  `maestro-*` tmux sessions on startup and re-pipes them.
- `registry_client::RegistryClient`: persistent outbound WS to the
  registry with exponential backoff. Sends Hello → snapshot, drives a
  select loop that pumps inbound CreateSession/KillSession RPCs into
  the manager (replying with results), emits SessionAdded/Removed/
  Updated diffs from the manager, and heartbeats every 10s.
- `attach`: axum router mounting `WS /sessions/:id/stream?token=<jwt>`.
  Binary frames carry PTY bytes either way (subscribe to broadcast →
  send to client; receive from client → tmux send-keys -l). Text
  frames carry `ClientControl::{Resize,Ping,Pong}`.
- `bin/maestrod`: ties everything together, loads config from
  `MAESTROD_CONFIG` env or `~/.config/maestrod/config.toml`,
  recovers, spawns the registry client, binds the WS server.

Also adds `JwtClaims` to `maestro-protocol` so the daemon and the
future registry JWT issuer (NAP-215) agree on the wire shape.

Integration tests cover session manager round-trip + recovery + PTY
broadcast; attach endpoint JWT (bad/wrong-session/wrong-daemon),
PTY round-trip through real tmux, multi-attach broadcast, ping/pong;
registry client registration + snapshot + diff forwarding +
CreateSession RPC round-trip.

Closes NAP-214.
2026-05-24 15:22:36 +00:00
naps62-yolo 946aa384a0 feat(maestro): add daemon tmux/sidecar/worktree primitives
ci / rust (push) Failing after 3s
Local primitives for `maestrod`, no network code yet:

- `tmux`: subprocess wrapper around new-session, kill-session,
  list-sessions (filtered to maestro-*), send-keys -l, pipe-pane -o,
  resize-window, kill-server.
- `sidecar`: atomic JSON metadata store keyed by session UUID, with
  load_all for daemon restart recovery.
- `worktree`: `git worktree add -b` with collision-proof generated paths
  and `git worktree remove --force` for cleanup_on_kill.

Integration tests use real tmux (isolated via unique socket + temp
TMUX_TMPDIR) and real git (tmpdir repo).

Closes NAP-212.
2026-05-24 13:02:41 +00:00