Commit Graph
18 Commits
Author SHA1 Message Date
naps62-yoloandClaude Opus 4.7 8586f46dad feat(maestro): Dockerfile for maestro-registry (Dokploy-ready)
ci / rust (push) Successful in 1m1s
Multi-stage build (rust:1-bookworm builder → debian:bookworm-slim
runtime) for the `maestro-registry` binary. Uses BuildKit cargo cache
mounts so subsequent rebuilds are fast.

Defaults `MAESTRO_REGISTRY_BIND_ADDR=0.0.0.0:8080` so the container
is reachable from a reverse proxy (Traefik, etc.) without any extra
config. Operator supplies `MAESTRO_TOKEN` as a platform secret; with
that set the binary needs no writable volume.

`.dockerignore` keeps `target/`, `worktrees/`, `.git/`, docs, and the
WIP `web/` out of the build context.

Verified: docker build succeeds; `docker run -e MAESTRO_TOKEN=...
-p 8080:8080 maestro-registry` boots and responds 200 on the cookie
auth endpoint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:14:22 +00:00
naps62-yoloandClaude Opus 4.7 44e0a50192 feat(maestro): env-var overrides for registry token + bind_addr
Lets `maestro-registry` run in container deployments (e.g. Dokploy)
without mounting a writable config volume.

- `MAESTRO_TOKEN`: when set, bypasses `registry.toml` entirely (no
  read, no write). The container needs no persistent volume just to
  seed the shared secret — pass it as a platform secret instead.
- `MAESTRO_REGISTRY_BIND_ADDR`: overrides the file's `bind_addr`.
  Lets the container bind `0.0.0.0:$PORT` without rewriting TOML.
- New `load_config(path, env_token, env_bind_addr)` in `token.rs`
  centralises the env > file > default precedence. Takes explicit
  `Option`s so tests don't touch process-global env.
- `TokenError::InvalidBindAddr` carries the raw input + parse error
  so misconfigured deploys log a useful diagnostic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:12:12 +00:00
naps62-yoloandClaude Opus 4.7 3022ee2c62 feat(maestro): add maestro-registry binary entry point
ci / rust (push) Successful in 1m0s
Closes NAP-221. Adds the `maestro-registry` bin so the registry can be
spawned as a child process (required by the NAP-217 E2E harness) and
run as a long-lived service.

- New `crates/maestro-registry/src/main.rs` with a `#[tokio::main]`
  entry point: loads config via `ensure_token` (override path via
  `MAESTRO_REGISTRY_CONFIG`), binds the configured `bind_addr`, serves
  the production router, and shuts down cleanly on Ctrl-C. Mirrors the
  `maestrod` wiring (EnvFilter tracing, ExitCode reporting).
- `[lib]` + `[[bin]]` sections in `crates/maestro-registry/Cargo.toml`
  so the crate exposes both surfaces (same pattern as `maestro-daemon`).
- `RegistryConfig` gains a `bind_addr: SocketAddr` field with
  `serde(default)` to default to `127.0.0.1:8080` per spec §Registry.
  Existing TOML files without the field still load.
- `default_config_path` re-exported from the crate root for the bin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:28:20 +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 499cffe397 fix(maestro): use AsyncFd for FIFO PTY reader (non-blocking)
ci / rust (push) Successful in 40s
`tokio::fs::File` routes every `.read()` through `spawn_blocking`. On
an idle FIFO opened r+w, that thread blocks in `read()` forever — and
when the test runtime tries to shut down, the blocking-pool thread is
stuck on the syscall and the test binary hangs (which is exactly what
just took out CI run 11).

Switch to `std::fs::File` opened `O_RDWR | O_NONBLOCK` wrapped in
`tokio::io::unix::AsyncFd`. Reads now drive off epoll readiness with
`try_io`; no blocking-pool threads, no hung shutdown.

`Drop` still aborts the task so per-test cleanup is clean.
2026-05-24 16:47:44 +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 f64d9312ee ci(maestro): drop container, install rustup + tmux on default image
ci / rust (push) Failing after 27s
`rust:1.83-bookworm` has no Node, which breaks `actions/checkout@v4`
(it's a JS action). The default gitea runner image has Node, so go back
to that and install rustup + tmux + git + build-essential ourselves.
2026-05-24 15:37:33 +00:00
naps62-yolo 22d079145f ci(maestro): fix runner image + trigger on linear/** branches
ci / rust (push) Failing after 26s
CI has been red since NAP-211 because docker.gitea.com/runner-images:
ubuntu-latest has no `rustup`; the toolchain install step exits 127.

- Use `rust:1.83-bookworm` as the job container so rustup + cargo + a
  stable toolchain are pre-installed
- Install `tmux` + `git` so the daemon integration tests can run
- Add `rustfmt` and `clippy` components
- Also trigger on pushes to `linear/**` so yolo branches see CI before
  they're merged
2026-05-24 15:24:44 +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 c721b5ec62 feat(maestro): add registry daemon-side WS handler and state
ci / rust (push) Failing after 6s
Registry-side primitives for the daemon↔registry conversation. No
client-facing HTTP yet (that's NAP-215).

- `state::Registry`: in-memory store of connected daemons + a session
  catalog keyed by (daemon_id, session_id). Tracks pending CreateSession
  / KillSession RPCs so a single daemon connection can multiplex them.
- `handler::handle_socket`: per-connection loop that authenticates the
  Hello, applies snapshot/diff/heartbeat frames, and resolves matching
  *Result frames. Heartbeat watchdog is a `tokio::time::timeout` around
  the read side — no separate janitor task.
- `server::router_with_timeout`: axum router mounting `WS /daemon`. The
  timeout knob lets tests verify the 30s drop path in milliseconds.
- `token::ensure_token`: first-run bootstrap that generates a 256-bit
  hex token, prints it once to stderr, and persists it to
  `~/.config/maestro/registry.toml`.
- Outbound `create_session` / `kill_session` exposed as the internal
  API NAP-215's HTTP handlers will call.

Integration tests use a real axum server bound to `:0` and drive it via
`tokio-tungstenite`, covering: bad-token reject, snapshot accept, diff
apply (add/update/remove), heartbeat timeout dropping the daemon,
re-registration replacing the catalog, and a full CreateSession RPC
round-trip.

Closes NAP-213.
2026-05-24 13:42:00 +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
naps62-yoloandClaude Opus 4.7 777d246aab feat(maestro): add maestro-protocol crate with wire types
ci / rust (push) Failing after 25s
Introduces the maestro-protocol crate (NAP-211): the shared serde
types that the daemon, registry, and clients all serialize across
WebSocket boundaries. Covers:

  - Session, SessionStatus
  - CreateSessionRequest with optional WorktreeSpec
  - RpcResult<T> reply envelope
  - DaemonOutbound (Hello, snapshot, add/remove/update,
    Heartbeat, request results)
  - DaemonInbound (Ack, Error, CreateSession, KillSession)
  - ClientControl text frames (Resize, Ping, Pong, Error)

Schema mirrors the wire-protocol tables in the design spec. Unit
tests round-trip every variant and assert tag/discriminator
stability so future renames break loudly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:35:16 +00:00
naps62-yoloandClaude Opus 4.7 de8db03a18 chore(maestro): scaffold cargo workspace and CI
Adds the empty Cargo workspace (resolver = 2, members = crates/*),
a stable rust-toolchain pin with rustfmt + clippy, and a Gitea
Actions pipeline that runs cargo fmt --check, clippy -D warnings,
and cargo test on every push/PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:35:08 +00:00
naps62-yoloandClaude Opus 4.7 dc963a27f7 docs(maestro): add repo-level CLAUDE.md
Orients future sessions and Linear-driven skills: points at the spec,
the Linear project, the dependency chain (NAP-211 is the starting
point), the target workspace layout, and conventions (TDD, conventional
commits with maestro scope, real tmux+git in integration tests, no
mocking).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:05:27 +00:00
naps62-yoloandClaude Opus 4.7 810a4fbc28 docs(maestro): make network layer agnostic in spec
Removes Tailscale assumptions from the design. Operator configures bind
addresses and is responsible for reachability + perimeter (Wireguard,
private LAN, or any other mechanism); maestro does not police the network
or terminate TLS itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 11:52:17 +00:00
naps62-yoloandClaude Opus 4.7 7af764c1a3 chore(maestro): initial scaffold and brainstorm spec
Adds .claude/linear.json for Linear-driven skills (Maestro project on
team Naps62) and the brainstorm design doc covering architecture, wire
protocol, session model, auth, and prototype scope boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 11:51:03 +00:00