Commit Graph
213 Commits
Author SHA1 Message Date
naps62-yolo 5af5ceb9a8 feat(maestro): trigger haiku naming for ACP sessions (#206)
ci / web (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / flake (push) Has been cancelled
2026-09-08 15:35:27 +01:00
Miguel PalhasandClaude Opus 4.6 6af4cda650 fix(maestro): gitroot tests tolerate /tmp inside a repo
The sandbox has /tmp/.git, so the ancestor walk finds a git root
above the tempdir. Weakened the assertion: the result must not point
inside the .git-free tempdir, but may point above it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-08 14:50:50 +01:00
Miguel PalhasandClaude Opus 4.6 3c10a3a08a fix(maestro): skip Starting sessions in liveness watcher
ci / web (push) Failing after 21s
ci / flake (push) Successful in 3m30s
ci / rust (push) Successful in 6m8s
A structured session still booting its adapter has no ACP handle and
no tmux pane, so the liveness pass misclassified it as a vanished
terminal session and reaped it. Claude Code adapters take long enough
to boot that the 2s liveness tick fires before the handshake finishes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-08 07:25:48 +01:00
Miguel PalhasandClaude Opus 4.6 cb8b93f178 test(maestro): update tests for async structured spawn
Tests now assert `Starting` after create and use `wait_for_ready`
before checking post-handshake state. Unsupported command test
expects `BadRequest` instead of a persisted Dead session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-07 21:28:22 +01:00
Miguel PalhasandClaude Opus 4.6 8d35f7f8c8 feat(maestro): async structured session spawn
Register a structured session immediately with `activity: Starting`
and run the ACP handshake in a background task. The HTTP caller gets
an instant response; the user can begin typing a prompt while the
adapter boots.

- Add `ActivityState::Starting` to the protocol
- Open the event log eagerly so the structured WS can connect during
  boot (empty transcript + idle state)
- `queue_acp_prompt` stashes a prompt while `Starting`; the background
  task drains it on successful handshake
- `apply_structured_activity` skips overwriting `Starting`
- `subscribe_acp_projection` allows connecting during `Starting`
- TUI/CLI map `Starting` to idle colour / "starting" label

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-07 21:08:08 +01:00
Miguel PalhasandClaude Opus 4.6 88afd0df3f feat(maestro): auto-approve ACP permissions via preset config
ci / web (push) Failing after 6s
ci / rust (push) Failing after 51s
ci / flake (push) Successful in 2m41s
Structured sessions ignored the --dangerously-skip-permissions flags in
preset commands because the daemon discards CLI flags when spawning the
ACP adapter binary. This adds an `auto_approve` bool to SpawnPreset and
CreateSessionRequest so the operator can declare it in config.toml:

  { id = "claude", command = "claude", auto_approve = true }

When set, the daemon's permission pump immediately responds AllowAlways
to every ACP permission request without surfacing it to clients.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-07 11:19:24 +01:00
Miguel Palhas 4df8fdb7dc feat(maestro): attach files in structured views 2026-09-07 10:20:08 +01:00
Miguel Palhas 8c151a5fb2 Merge remote-tracking branch 'origin/main' into blitz/codex-structured-sessions
ci / web (push) Failing after 8s
ci / flake (push) Failing after 2m18s
ci / rust (push) Successful in 8m53s
# Conflicts:
#	crates/maestro-daemon/tests/worktree.rs
#	crates/maestro-e2e/tests/e2e.rs
#	web/src/attach.ts
#	web/src/connections.test.ts
#	web/src/connections.ts
2026-09-06 23:16:27 +01:00
Miguel Palhas 002f1b466b test(maestro): cover Codex ACP lifecycle 2026-09-06 18:32:19 +01:00
Miguel Palhas bbf622ed9b feat(maestro): select ACP adapter by command 2026-09-06 18:11:04 +01:00
Miguel Palhas a1f3690aaf feat: show structured turn activity 2026-09-06 16:50:46 +01:00
Miguel Palhas ba7fd68004 feat: add structured session picker 2026-09-06 16:18:33 +01:00
Miguel Palhas ccdfec8257 fix(daemon): persist structured user prompts 2026-09-06 14:19:45 +01:00
Miguel Palhas 30f91176ae fix(daemon): coalesce anonymous ACP text chunks 2026-09-06 14:10:11 +01:00
Miguel Palhas 57efb615f6 fix(maestro): key activity watermark by stream 2026-09-05 12:19:16 +01:00
Miguel Palhas fe26dab6a8 fix(maestro): remove structured lock inversion 2026-09-05 11:55:25 +01:00
Miguel Palhas ac2eb01bda fix(maestro): allow concurrent ACP handshakes 2026-09-05 11:29:44 +01:00
Miguel Palhas f765d415d6 fix(maestro): reject structured PTY access 2026-09-05 10:20:41 +01:00
Miguel Palhas b225fbdd69 docs(maestro): narrow the Notice forward-compat claim in STRUCTURED-VIEW.md
Notice only covers SessionUpdate variants the agent-client-protocol
dependency already decodes but the projection doesn't special-case.
SessionUpdate is an internally-tagged enum with no unknown-variant
fallback, so a genuinely new wire variant fails deserialization before
the projection ever sees it, rather than becoming a Notice. A new
variant needs the dependency upgraded first.

Adds a projection test asserting that an unrecognised sessionUpdate
tag is a deserialization error.

Refs #186
2026-09-05 05:28:11 +01:00
Miguel Palhas 4a1a10c49a fix(maestro): avoid nested session locks 2026-09-05 05:15:58 +01:00
Miguel PalhasandClaude Sonnet 5 0b34d1608f fix(maestro): make FaultyLogSink honor the Write contract on partial writes
write() reported Ok(partial) and Err from the same call, which Write
forbids. Defer the error to the following call so write_all observes
a genuine short write, then a failure — matching what a real fd does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 05:02:01 +01:00
Miguel Palhas 075dea509a fix(maestro): make EventLog::append transactional under partial write/flush failure
A write_all that errors mid-line, or a flush that errors after a full
write, could leave last_seq and the log's on-disk line count out of
sync: a well-formed but uncounted line, or a partial one, would corrupt
every later cursor once the next append landed beside it. append now
captures the pre-append length and truncates back to it on either
failure, so the same EventLog can be appended to again immediately. If
the rollback itself can't restore that length, append returns a Fatal
variant so callers kill the session instead of promising consistency
they can no longer keep — record_permission_request and its pump now
route this through the session-death path used elsewhere.

Adds deterministic fault injection (a seekable/truncatable LogSink
double) to prove the same EventLog recovers with valid JSONL and
live/replay agreement, rather than swapping in a healthy handle. Fixes
the row-index rollback test to actually shift a later row, and
tightens the persistence-failure wording in STRUCTURED-VIEW.md.
2026-09-05 04:55:26 +01:00
Miguel Palhas c067f48a24 fix(maestro): roll back phantom approval row on log append failure
request_approval() appended the Approval row to the live projection
before the daemon tried to persist it. On append failure the pending
nonce was cancelled but the row stayed in the in-memory transcript, so
a WebSocket snapshot taken afterward showed a row durable replay could
never reproduce.

Add Projection::rollback_row to undo the append and call it alongside
the existing cancel_pending_approvals on failure. Extract the pump's
per-request handling into record_permission_request so the failure
path is directly testable without a live ACP connection.

Fault-injection regression tests use a read-only file handle
(EventLog::for_test, cfg(test)) to force append() to fail
deterministically, then assert the live snapshot and durable replay
agree (both empty) and a subsequent request still persists and
replays correctly.

Fixes #184.
2026-09-05 04:34:47 +01:00
Miguel Palhas 8f5d96a95b feat(maestro): derive structured activity 2026-09-05 04:08:43 +01:00
Miguel Palhas 6fa7dc63e4 fix: serialize structured prompt dispatch 2026-09-05 03:11:16 +01:00
Miguel Palhas 5c0a4c4640 fix: bind approvals to prompt turns 2026-09-05 02:57:29 +01:00
Miguel Palhas 8773671afd fix(daemon): unify approval replay log 2026-09-05 02:44:59 +01:00
Miguel Palhas d9815d86c6 feat(daemon): resolve ACP approvals 2026-09-05 02:32:01 +01:00
Miguel Palhas bbae09bc48 fix(daemon): reject dead structured sessions 2026-09-05 02:13:55 +01:00
Miguel Palhas c7c294996e fix(daemon): remove unused turn token 2026-09-05 02:07:07 +01:00
Miguel Palhas 3c638b08de fix: clarify structured prompt failures 2026-09-05 02:05:06 +01:00
Miguel Palhas be23a2b539 fix: harden structured replay 2026-09-05 01:56:07 +01:00
Miguel Palhas b98f5cb3e5 feat(maestro): serve structured session API 2026-09-05 01:46:21 +01:00
Miguel Palhas 0292f373f7 test(daemon): cover projection lifecycle 2026-09-05 01:31:20 +01:00
Miguel Palhas eea337ce36 fix(daemon): scope active tools to turn 2026-09-05 01:30:16 +01:00
Miguel Palhas fd3ea64949 fix(daemon): reconcile transcript projections 2026-09-05 01:26:12 +01:00
Miguel Palhas 046c110460 feat(maestro): project ACP transcript state 2026-09-05 01:13:39 +01:00
Miguel Palhas 3a68c1dd2f fix(maestro): preserve dismissed recovery cleanup 2026-09-05 00:57:36 +01:00
Miguel Palhas 25435c34b2 fix(maestro): classify exited ACP children 2026-09-05 00:47:51 +01:00
Miguel Palhas 2a49814572 fix(maestro): mark graceful ACP recovery 2026-09-05 00:35:50 +01:00
Miguel Palhas cf322746a7 fix(maestro): serialize structured shutdown 2026-09-05 00:22:59 +01:00
Miguel Palhas bfe4965081 fix(maestro): stop ACP before WS drain 2026-09-05 00:07:19 +01:00
Miguel Palhas 9d7ed8c071 fix(maestro): serialize shutdown finalization 2026-09-05 00:07:19 +01:00
Miguel Palhas c277ad630c fix(maestro): retain worktree on ACP restart 2026-09-04 23:53:20 +01:00
Miguel Palhas fc9c62d46b test(maestro): cover structured restart worktree 2026-09-04 23:53:20 +01:00
Miguel Palhas e9142bb027 fix(maestro): close ACP replay seams
Serialize replay reads with event appends, acquire the live receiver under that lock, and drain the event pump during shutdown. Recovery now removes its FIFO when opening the log fails.
2026-09-04 23:25:46 +01:00
Miguel Palhas d11098a478 feat(maestro): persist live ACP events
Own the ACP update receiver inside SessionManager, append each notification before broadcasting it, and recover structured sidecars by loading their stored ACP session id without duplicating replayed history.
2026-09-04 23:20:48 +01:00
Miguel PalhasandClaude Fable 5.1 fd54d4f833 feat(maestro): add the per-session event log to SidecarStore (#135)
<sidecar_dir>/<id>.events.jsonl holds one session/update notification
per line; seq is the line's 1-based position. open_events returns an
append handle held for the session's lifetime, and terminates a
partial last line first so a hard kill mid-write never glues the next
event onto the fragment. read_events takes a Since (forward, exclusive)
or Before (backward, exclusive, None for the tail) cursor with a limit;
unparseable lines keep their seq and are skipped. delete removes the
log with the sidecar; load_all ignores .jsonl.

Each append is a single write_all to the kernel, no fsync: a killed
daemon loses nothing already written, and an fsync per streamed chunk
is the wrong trade for a log that is not meant to survive power loss.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 22:14:33 +01:00
Miguel Palhas 6c20a99d58 feat(maestro): end adapter children on daemon shutdown (#134)
SIGTERM now counts as a shutdown alongside ctrl-c: systemd stops the
unit with SIGTERM, and with KillMode=process nothing else would reach
the claude-code-acp children. After the listener drains the manager
terminates every adapter before the process exits.
2026-09-04 22:03:23 +01:00
Miguel Palhas b1718559df feat(maestro): spawn a claude-code-acp child per structured session (#134)
A Structured spawn gets no tmux pane: the daemon starts the adapter in
the session cwd with the session env (including MAESTRO_SESSION_ID and
MAESTRO_URL), runs the handshake, and records the ACP session id on the
sidecar. An adapter that cannot come up still yields a session, Dead
with activity Error and the reason as activity_detail, so a missing
adapter reads as the install command in the sidebar rather than as a
session that never appeared.

The liveness pass checks a structured session's child instead of the
tmux list. kill sends session/cancel, waits, then SIGTERMs through the
ACP handle. SessionManager::shutdown ends every adapter concurrently
and marks those sessions Dead on disk. take_acp_updates hands #135 the
raw session/update stream once, with earlier notifications still queued.
2026-09-04 22:02:39 +01:00