Repository navigation
Conversation
## Summary - Add `zedra-host::tmux` with the owned `zedra-pi-<hex>` session-name codec: reversible, collision-free, safe as a tmux target; empty IDs refused, every foreign or malformed name decodes to untracked. - Add pure parsers for the proven tmux 3.3a two-call `list-panes` `|`-formats (exact 4-field process records; positional metadata records with pipe-tolerant titles), explicit dead/exit-status representation, and `tmux -V` version parsing with a 3.3a minimum. - Unit tests cover UUID/whitespace/quote/metacharacter/Unicode IDs, malformed hex, foreign names, version boundaries, malformed records, dead panes, round-trip stability, and classification precedence. ## Notes - tmux 3.3a sanitizes all format output, so the plan's non-printing field separator is replaced by the proven two-call `|`-format recorded in the todo deltaqdev#2 proof findings. - Pure codec and parsers only; subprocess operations, live capability checks, and attach/kill flows follow in the next commit.
## Summary - Concrete TmuxClient: PATH discovery with version gating (>= 3.3a), private-socket argv builder, and a 5s subprocess deadline on every call. - prepare_session runs new-session -d -A headless with the workdir and the authoritative resume command, tolerates the proven already-prepared race response plus one fresh-socket connect retry, then sets session-scoped mouse on and window-size largest. - list_sessions returns only Zedra-owned sessions with their first pane's process + metadata via the proven two-call list-panes formats; foreign and malformed names are omitted; both proven no-server stderr variants map to an empty list instead of an error. - terminate_session rebuilds the target from (slug, session_id), refuses foreign slugs, empty IDs, and non-owned targets, and treats an already-vanished session as terminated. - attach_command returns "exec <tmux> attach-session -t <name> || exit $?" with shell quoting so a failed attach cannot fall back to a login shell. ## Notes - Unit tests for the operations land in the following test(host) commit to keep this commit within the 400-line boundary; lifecycle tests with a real private-socket tmux are todo deltaqdev#9. - Subprocess helper reuses agent::utils::command_output_with_timeout.
## Summary - Exact argv assertions for list/prepare/set-option/kill paths, including private-socket injection, on top of the TmuxClient from the previous commit. - Shell-quoting coverage for attach_command: spaced binary paths, quoted socket names, and quote-bearing session names. - Proven no-server stderr variants classify as available capability; a missing binary surfaces as an error instead of an empty list. - Owned-only listing filter, terminate validation for foreign slugs and empty IDs, and empty-ID rejection before any subprocess spawn. ## Notes - All tests are pure and never touch a real tmux server; the ignored private-socket lifecycle suite is todo deltaqdev#9.
## Summary Append `AgentShareList` and `AgentShareTerminate` request variants at the `ZedraProto` tail with all-new wire types (`AgentShareListReq`, `AgentShareSession`, `AgentShareListResult`, `AgentShareTerminateReq`, `AgentShareTerminateResult`). No ALPN bump, no change to existing wire types or `proto_v3.rs`, following the `HostDirList`/`HostWorkspaceOpen` append-only rule. Add the `AgentActor::supports_shared_sessions()` capability (false default, true override only in `PiActor`) and gate new `rpc_daemon.rs` dispatch through it — no Pi-specific match arms. List resolves tmux availability through `TmuxClient`; missing/too-old tmux returns `available: false` with the exact actionable reason while persisted history is unaffected. Terminate validates slug + session id through the actor registry and tmux ownership codec before killing the tmux session; `terminal_ids` stays empty until terminals carry shared spawn metadata. Add per-feature `AtomicBool` downgrade state to `SessionHandle` (`shared_agent_sessions_supported`) with `agent_share_list` / `agent_share_terminate`; an incompatible old host disables only these new calls for the connection. Document the request/result contract in `docs/PROTOCOL_SPECS.md` (§5.8 + changelog). ## Notes Protocol round-trip tests and downgrade tests follow in the next commit to keep each change within the 400-line budget. Resume routing, `TermSession` spawn metadata, and app UI are later todos.
## Summary Protocol round-trip tests for the new `AgentShare*` wire types and enum variants (including the unavailable-tmux and error payload shapes), and `SessionHandle` downgrade tests: an incompatible old host disables only the shared-session calls, unrelated capabilities stay enabled for the same connection, transport errors do not downgrade, and disabled calls fail fast without touching the wire. ## Notes Plan-sanctioned test split from `feat(rpc): expose shared agent sessions` to stay within the 400-line per-commit budget; both commits build and pass their narrow checks independently.
## Summary
- Add host-internal spawn metadata: `SpawnOptions.identity_launch_cmd` plus
`shared: Option<SharedSpawnIdentity>` carried onto `TermSession`;
`initial_host_meta` reports the identity command (the original Pi resume
command) so terminal cards never see the outer tmux attach wrapper.
- Route both resume entry points (`ZedraMessage::AgentResume` and REST
`POST /v1/agents/{slug}/sessions/{id}/resume`) through one helper,
`agent::resume_terminal_launch`: actors without the shared-session
capability keep the exact direct launch; Pi gets
`new-session -d -A` prepared before any terminal is created and returns
the attach command plus the original Pi command as identity metadata.
- Prepare-before-insert: missing/old/hung tmux, invalid ids, timeouts, and
option failures surface as an actionable resume error with no
`TermSession` inserted and never a direct-Pi fallback; a PTY allocation
failure after atomic creation leaves the shared session discoverable.
- Explicit share termination kills the validated tmux session first and
only then removes every matching `TermSession` from the current
`ServerSession`, reaping those client children through the normal close
path and returning their terminal ids; a failed kill keeps host state
intact. Normal close still terminates only the selected tmux client.
- One-active-client architecture (output slots, dispatch gating, token
ownership, generation guards) is untouched; fresh Pi launches and
non-Pi agents behave exactly as before.
## Notes
- tmux discovery is an injected closure (`resume_terminal_launch_with`) so
tests can point at missing, too-old, or hung tmux binaries.
- `remove_shared_terminals` returns terminals in `terminal_order`, keeping
the terminate result deterministic despite the HashMap storage.
- `TermSession::terminate` no longer blocks on a stubborn child:
portable-pty's `kill` is SIGHUP-only and a blocking wait wedged on
macOS, so close now polls `try_wait` with SIGKILL retries under a 5s
deadline.
## Summary - Add `shared_resume_tests` covering the focused contract: two tmux-client `TermSession`s get distinct `OutputSenderSlot`s; normal close removes only the selected client; explicit shared termination removes every matching terminal id in terminal order while leaving other shared and plain terminals untouched. - Resume-path tests inject tmux discovery: a missing binary, a too-old stub (`tmux 3.2`), and a hung stub (5s probe timeout) each fail the resume before any `TermSession` is inserted and without a direct-Pi fallback; non-shared actors keep the exact direct launch; blank ids fail before anything spawns. ## Notes - Plan-sanctioned split from `feat(host): resume Pi sessions through tmux` to respect the 400-line per-commit budget; both commits build independently. - Test terminals spawn a self-exiting PTY child so reaping is instant and hermetic; real-tmux lifecycle coverage stays in the ignored private-socket suite (todo deltaqdev#9).
## Summary
- Add runtime-only shared-session state to WorkspaceState (serde-skipped,
per-slug snapshot with replace/remove/clear that notify and clear on
disconnect) fed by AgentShareList polls.
- Poll AgentShareList("pi") from a cancellable view-owned task every two
seconds while the global sessions view or the Pi detail view is open;
requests never overlap; unavailable tmux logs one deduped warning and
leaves persisted history fully usable.
- Merge persisted rows with the live tmux snapshot in a pure helper by
(slug, session_id): history keeps its order and timestamps, shares
without a persisted row are never fabricated, dead panes stay attached.
## Notes
- Badge rendering and tap behavior land in the next commit; terminate in
the one after.
- AgentSessions now receives the WorkspaceState entity like AgentDetail.
## Summary - Merge tests: live/past attachment through day grouping (a live share never reorders persisted history), dead panes stay attached with exit codes, shares without a persisted row are never fabricated, duplicate snapshot entries collapse to the first, foreign slugs never pick up Pi shares. - WorkspaceState tests: poll snapshots replace with change reporting, identical snapshots are no-ops, unavailability warns once per stretch and resets on recovery, share removal matches exactly one session id, and disconnect clearing drops the whole snapshot. ## Notes - Plan-sanctioned test split from the previous commit to stay within the 400-line budget.
## Summary - Extend SessionCardProps with an optional shared snapshot and render a small semantic status badge on session cards in both the global sessions view and the Pi detail view: green Live for a running pane, muted Ended (or Ended (code N) when the host reports an exit code) for a dead pane. - Add a pure SharedSessionStatus mapping (live/dead/exit code to label and semantic color selection) used by the badge renderer. - Surface the host's actionable resume failure reason (missing or old tmux, prepare failure) in the existing resume failure alert instead of a generic message. ## Notes - Tap behavior is unchanged: cards still dispatch ResumeAgentSession; the host resume helper decides tmux attach versus direct launch, so an older host keeps its prior direct-resume behavior. - No raw tmux names, targets, commands, or socket paths are rendered.
## Summary
- Add workspace_action::TerminateSharedAgentSession { slug, session_id }
and offer it from session cards via long-press only when the row
carries a shared tmux snapshot, so plain past rows and foreign tmux
sessions never offer termination.
- Trigger the standard medium haptic and a native destructive
confirmation (Terminate shared session? / This stops Pi and
disconnects every attached terminal. / Terminate / Cancel) and route
the confirmed action through Workspace's pending-native-action slot.
- Handle the action by calling AgentShareTerminate through the session
handle: a downgraded old host fails fast with the standard
unavailable error instead of hanging. On success, remove the returned
terminal IDs through the same local terminal-card cleanup path used
by normal close and drop the shared snapshot so the row immediately
falls back to persisted history. On failure, keep every card and
state entry and show the host's exact error, mirroring the resume
alert pattern.
- Test the terminate-offer predicate (shared snapshot present vs
absent) as a pure helper next to the card renderer.
## Notes
- Closing a terminal card remains client-only: explicit termination is
the only path that stops the shared Pi process and disconnects every
attached terminal.
## Summary - Add the ignored `tmux_shared_session_lifecycle` integration test: real tmux on a per-run private `-L` socket with drop-guard `kill-server` cleanup. It races eight concurrent `new-session -d -A` prepares (exactly one inner process), attaches two pty clients through the production attach command, proves simultaneous input from both, title/cwd metadata tracking, detach without killing Pi, reattach, and explicit termination that ends Pi and every attached client. Skips cleanly without tmux or below 3.3a via `supported_version`. - Fix `prepare_session` retrying the already-prepared answer (rc 1 + "open terminal failed: not a terminal"), which failed every resume of a live shared session; the retry stays reserved for fresh-socket connect races. Stub-binary regression test added. ## Notes - Client names differ across tmux releases (tty path on 3.7, client-<pid> on 3.3a), so the test selects clients by `client_pid` and detaches by that record's own name. - Focused host coverage for distinct output slots, selective close/terminate, and missing/old/hung tmux already exists in `rpc_daemon::shared_resume_tests`; sequential reconnect after a first client disconnect is already proven by `test_second_client_auth_succeeds_after_first_client_close`, so no new test was added for it.
## Summary - MANAGED_AGENTS.md: describe Pi shared sessions — the `zedra-pi-<hex>` ownership codec, actor command authority, tmux 3.3a capability and actionable errors with no direct-resume fallback, close-versus-terminate semantics, the fresh-launch limitation, and the lossy-metadata/scrollback limitations. - ARCHITECTURE.md: document the shared-session process topology — tmux owns the Pi PTY, macOS SSH and each Zedra terminal attach as independent tmux clients, `zedra-host` restart preserves tmux while container recreation does not, and one-active-Zedra-client is intentionally preserved. - MANUAL_TEST.md: add section 29 release gate — simultaneous macOS SSH plus one Zedra client with two cards, independent maximize resize rule (120x40 + 38x100 -> 120x99), copy-mode scrollback, close/detach versus terminate, tmux-server-loss fallback to past-only state, host restart, `Host occupied` for a simultaneous second Zedra device, sequential handoff, and behavior without tmux. ## Notes - Zedra local scrollback starts at attachment time; older history lives in tmux copy mode and is not replayed into Alacritty. - The proven resize rule maximizes width and height independently across interactive clients under `window-size largest`; the docs avoid overclaiming a single "largest client" dimension.
## Summary - With `exit-empty on` (tmux default), killing the last owned session exits the server; a subsequent terminate on the same or another row then fails with `no server running on ...` even though nothing is left to terminate. - Extend the terminate success condition with the existing `is_no_server` check, matching `list_session_names`'s mapping of both proven no-server stderr variants, so `terminate_session` accepts `can't find session:` OR a dead server as already terminated. ## Notes - Regression test `terminate_session_accepts_no_server_as_terminated` mirrors `prepare_session_accepts_already_prepared_session` via a stub `tmux` binary that fails `kill-session` with `no server running on /tmp/tmux-0/stub`.
Generalize shared sessions across registered actors and add the detected non-owned tmux lifecycle.
bu5hm4nn
force-pushed
the
feat/tmux-agent-sessions
branch
from
September 10, 2026 16:30
980d870 to
369e367
Compare
The branch added ios_cancel_keypad_composer and removed zedra_ios_extended_keypad in the device header, Rust bridge, and NativeBridge.swift, but the simulator slice of the xcframework header was left stale. Align it so both slices declare the same FFI surface.
Co-authored-by: Codex <codex@openai.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Notes