feat(remote-session): Stage 2 client-attach (part 1: client protocol) [draft]#16
Draft
iret77 wants to merge 76 commits into
Draft
feat(remote-session): Stage 2 client-attach (part 1: client protocol) [draft]#16iret77 wants to merge 76 commits into
iret77 wants to merge 76 commits into
Conversation
Client side of the native session host, isolated to the remote_server crate: - ClientEvent::SessionOutput / SessionExited + push_message_to_event arms. - Client methods: open_session, attach_session (request/response) and send_session_input / send_resize_session / send_detach_session (notifications). - forward_client_event: placeholder arm for the new events (no app consumer until the app-side terminal increment; not hit at runtime yet). cargo check -p remote_server (lib + tests) green. App-side byte-flow / input / mouse wiring is the next increment (see the Stage 2 design spec).
Add RemoteServerManagerEvent::SessionOutput / SessionExited (carrying the connection session_id + host_id + daemon pty_session_id + seq/bytes or exit_code) and emit them from forward_client_event (replacing the increment-1 placeholder). Update session_id() and the three exhaustive consumers (session.rs, remote_server_controller.rs, view.rs) to handle the new variants (no-op for now). The attached-remote terminal byte-source that *subscribes* to these and feeds the ansi processor is increment 3. cargo check -p remote_server and -p warp both green.
Add `daemon_tty`, a new `TerminalManager` implementation for sessions whose PTY lives in the remote daemon and survives transport drops. It is a sibling of `remote_tty`, not a fork: both reuse the shared terminal-manager helpers (`create_terminal_model`, `init_pty_controller_model`, `wire_up_pty_controller_with_view`) and differ only in their event loop's transport. The daemon event loop is transport-agnostic (the seam for a future native UDP transport): live PTY output arrives as `RemoteServerManagerEvent::SessionOutput` pushes filtered by `pty_session_id`, and input/resize/detach are routed back through the live `RemoteServerClient` (`send_session_input` / `send_resize_session` / `send_detach_session`). Input that arrives before `OpenSession` resolves is buffered and flushed in order. Shell bootstrap is intentionally left to the daemon (server-side, once) so a later reattach stays clean. Not yet wired into `create_session` (that is increment 3b); `local_tty` (localhost + plain-vanilla SSH) remains the untouched default. Verified in isolation with `cargo check -p warp` (green).
…nce setting Add the per-host opt-in for the native persistent remote-session layer (the trigger for Option B: a resilient host opens directly as a daemon-hosted session). This increment is the data model only — additive, default off, zero behavior change; the runtime trigger (open_ssh_terminal → daemon_tty, headless connect) is the next increment. - DB: additive migration adds `ssh_servers.session_resilience TEXT NOT NULL DEFAULT 'off'`. Down uses DROP COLUMN (safe on the bundled SQLite >= 3.35; a hand-rebuilt table would be more error-prone now that the schema carries a modified auth_type CHECK and a credential_id FK). - `SessionResilience` enum (Off | PersistOnly | PersistPlusMosh) with as_db_str/parse/is_enabled, mirroring AuthType. Added to SshServerInfo, the persistence Row/NewRow + schema, and threaded through create/update/read CRUD. The read path is lenient: an unknown value degrades to Off rather than making a server unloadable. - Cloud sync: SyncServer carries the field too (serde default "off" for older payloads) so sync-down preserves a host's setting instead of resetting it. - Updated all SshServerInfo constructors; the server form preserves the stored value on edit (no UI control until Stage 4). Verified: cargo check -p persistence -p warp_ssh_manager --tests and cargo check -p warp both green. App-side test files verified via the xl test-dispatch job.
…igger (Option B) Captures the design for wiring a resilient SSH host to open directly as a daemon-hosted session: headless ControlMaster connect, daemon_tty deferring OpenSession until SessionConnected, the open_ssh_terminal branch, and the 3c-i/ii/iii increment breakdown. Records open scope decisions (v1 auth support, tab/connect ordering, ControlMaster lifecycle).
…Session until connected daemon_tty no longer assumes an already-connected transport (3a's assumption). The OpenSession request is held in `pending_open` and issued by `try_open` only once a client exists: at startup if already connected, otherwise driven by a new `RemoteServerManagerEvent::SessionConnected` arm matching this session's id. A `SessionConnectionFailed` arm drops the pending open (Stage 3 will surface an error block). This is the prerequisite for Option B's immediate-tab-then-connect flow. cargo check -p warp green.
…ns as daemon session Wire Option B end-to-end: a saved SSH host with session_resilience enabled opens directly as a daemon-hosted (native persistent) session instead of a local PTY running `ssh`. local_tty stays the untouched default for every ordinary terminal. 3c-ii — headless connect (app/src/remote_server/headless_connect.rs, unix): - alloc_daemon_session_id(): mints SessionIds in the top half of the u64 space so they can't collide with shell-bootstrap-minted ids. - is_headless_capable(): v1 = key auth only (runs under BatchMode=yes); password hosts fall back to the normal SSH path. - control_socket_path(): stable per-host local ControlPath under $HOME/.ssh. - ensure_control_master(): spawns `ssh -f -N -o ControlMaster=auto -o ControlPersist=yes -o ControlPath=… -o BatchMode=yes …`, awaits the socket. 3c-iii — trigger + tab plumbing: - DaemonSessionRequest (connection_session_id + OpenSessionParams), carried additively through NewTerminalOptions into create_session; a new daemon branch there routes to daemon_tty::create_model (all other call sites pass None). - open_ssh_terminal: try_open_daemon_ssh_terminal() takes the daemon path for resilient+headless-capable hosts — creates the daemon tab (which subscribes for SessionConnected, 3c-i) then spawn_daemon_session_connect() establishes the ControlMaster and calls RemoteServerManager::connect_session on the allocated id. connect_session emits SessionConnected → daemon_tty issues OpenSession. Compile-verified (cargo check -p warp green). Runtime behaviour (connect, stream, mouse, survive drop) needs validation against a real resilient host.
Add a Standard/Persistent toggle to the SSH server detail form so `session_resilience` is manageable in the UI (no DB editing needed). Styled like the existing auth toggle. The form loads the stored value, and Save + Connect submit the live selection, so toggling "Persistent" on a key-auth host makes "open" take the daemon-hosted session path (3c). Toggling on selects PersistOnly but preserves a higher tier (PersistPlusMosh) if already set; the UI only distinguishes off vs. on for now (B3 mosh not built). cargo check -p warp green.
The SSH/shell-enrichment feature inherited from Warp (two forks upstream) was still called "Warpify". Rename it end-to-end to "Zaplexify" while there are no users yet (so no settings migration is needed — now is the only safe time): - All word-forms: warpify/Warpify/WARPIFY and warpification/Warpified/etc. (identifiers, the terminal::warpify module → zaplexify, settings types, enum variants like WarpifiedRemote → ZaplexifiedRemote). - Persisted settings keys: warpify.ssh.* / warpify.subshells.* → zaplexify.* (no migration — first version). - User-facing strings + i18n keys (settings-warpify-* → settings-zaplexify-*; validated by the t! macro at compile time). - Bundled SSH bootstrap scripts renamed (warpify_ssh_session.sh → zaplexify_ssh_session.sh, install_tmux_and_warpify_* → *_zaplexify_*) plus their bundled_asset! references. Deliberately NOT touched (separate concerns): the inherited `WARP_*` terminal protocol/env namespace, the `~/.warp` config dir, TERM_PROGRAM="WarpTerminal", and the warp_*/zap_* crate prefixes (provenance naming scheme). cargo check -p warp green.
…RAM to Zaplex Rebrand the inherited Warp terminal-integration protocol surface (env vars, markers, consts) and terminal identity: - All 143 WARP_* names → ZAPLEX_* (e.g. WARP_SESSION_ID, WARP_HONOR_PS1, WARP_BOOTSTRAPPED, WARP_COMPLETIONS_*), consistently across Rust + the bundled shell-integration scripts (both ends of the protocol move together). - TERM_PROGRAM value WarpTerminal → ZaplexTerminal (+ its detection check). Safe to blanket because WARP_ (uppercase) and WarpTerminal do not collide with the lowercase warp_* crate names (provenance scheme, kept). cargo check -p warp green. Runtime behaviour (shell integration/blocks/completions) needs a real run.
…n protocol Runtime coverage (actually executed, not just type-checked) for the client half of the daemon-hosted session protocol, via the existing tokio::io::duplex mock harness: - open_session / attach_session request-response (correct frame fields + parsed response). - SessionOutput / SessionExited server pushes surfacing as ClientEvents. - send_session_input / send_resize_session / send_detach_session fire-and-forget frames received correctly server-side. Together with the server-side spawn_session_pty test (app local_tty/unix.rs, run on the xl dispatch), both halves of the daemon-session mechanism now have runtime coverage. `cargo test -p remote_server` → 71 passed. Also fixes a pre-existing stale assertion: oss_binary_name_matches_zap_cli expected "warp-oss" but the zap fork's binary_name() is "zap-oss" (matches the test's own name + the neighbouring ~/.zap namespace test). It was never caught because the CI gate runs cargo check, not cargo test.
Close the last headless-testable gap (Option 2): an end-to-end test of the server-side glue on a real warpui test App, no GUI and no SSH. It drives ServerModel::handle_message directly: - OpenSession spawns a real PTY + shell and replies SessionOpened. - SessionInput reaches that PTY; the background reader task streams the shell's output back as SessionOutput pushes through the model — asserted via a marker (`echo D4''EM0N` echoes verbatim but executes to `D4EM0N`, so the marker only appears in genuinely-executed output, proving the full input→PTY→output round-trip, not just terminal echo). - CloseSession reaps the shell and emits SessionExited. Builds the model via the existing struct-literal test_model() helper (so the test needs no FileModel/RepoMetadata singletons) while still getting a real ModelContext (executor + spawner) from App::test — the test App's background executor is a real 1-thread tokio runtime, so the detached PTY reader runs for real. Deadlines (async_io::Timer) guard against hangs. Unix-only. Compile-verified (cargo check -p warp --tests). Runs on the xl test-dispatch (cargo test -p warp daemon_session) — app-crate tests don't build on the dev host.
… drop)
The daemon session now outlives the client and replays missed output on
reattach — the core "survives the drop" payoff.
Server (S3a):
- handle_attach_session: replay_from(last_seq) from the session's ring, re-point
the live stream at the reconnected connection, reply SessionAttached{size,
base_seq, replay}. handle_detach_session: keep the session running (output
buffers in the ring). ListSessions stays Stage 4.
- Grace-timer guard: when the last connection drops, only arm the daemon's
shutdown grace timer if NO live sessions remain — persistent sessions keep the
daemon up until reattach (was: daemon shut down after 10 min, killing them).
Server test (S3b, runs on xl): open a session, stream output, simulate a client
drop (deregister), produce more output while detached, reconnect on a fresh
connection, AttachSession(last_seq=0) — assert the replay contains both the
pre-drop and the while-detached output, then that live output re-routes to the
re-attached connection.
Client (S3c, daemon_tty): track last_seq from SessionOutput; on
SessionReconnected, attach_session(last_seq) and feed the replay through the ANSI
processor to reconstruct the grid. (Runtime validated via the GUI/real-host E2E;
re-establishing the headless ControlMaster on an SSH drop is noted in the spec.)
Design: docs/superpowers/specs/2026-06-28-stage3-attach-replay-design.md.
cargo check -p warp --tests green.
Make the daemon's sessions listable, the foundation for the multi-session sidebar + adopt-by-id. - Session metadata: each session now carries its cwd, shell, and last-attach timestamp (open counts as the first attach; refreshed on every AttachSession). - Server handle_list_sessions: returns SessionList of SessionInfo over the registry (title derived from cwd basename else shell; alive == registry membership, since exited sessions are removed + announced via SessionExited). Wired into the dispatch (unix); non-unix rejects honestly. - Client list_sessions() request/response method. Tests: - client list_sessions_round_trip (remote_server, runs locally — green: 72 passed). - server list_sessions_reports_open_sessions (server_model_tests, xl): open two sessions in distinct temp cwds, assert both are listed with their cwds + alive, then closing one shrinks the list to the survivor. Deferred to a follow-up (noted in the spec): detached-idle GC + ring ceiling as a per-host setting (S4c), and the adopt sidebar (GUI E2E). The per-host session_resilience setting + UI already shipped in Stage 3b. Design: docs/superpowers/specs/2026-06-28-stage4-multisession-design.md. cargo check -p warp --tests green (no warnings); cargo test -p remote_server green.
Memory governor for the daemon's persistent sessions: - gc_sessions(now, max_detached_age, host_ring_cap): reaps sessions that are detached (no live attached connection) and either idle past the max age (default 24h) or, if total output-ring bytes exceed the host cap (default 256 MiB), the oldest detached ones until back under the cap. Never reaps a session with a live connection. Wall-clock is injected so it's unit-testable. - Periodic sweep (start_gc_timer, every 5 min) on the background executor, re-entering the model each tick; started from new() on unix. Test (server_model_tests, xl): open two sessions, drop the connection (both detached), then assert age-GC reaps the ancient one and keeps the recent one, and a zero host cap reaps the remaining detached session once it has ring bytes. cargo check -p warp --tests green (no warnings).
ensure_control_master no longer trusts a lingering socket file: it runs `ssh -O check` and only reuses a master that is actually alive, otherwise it removes the stale socket and spawns a fresh one. This is what lets a daemon session's transport be re-established after an SSH drop — the session kept running daemon-side, and a reconnect attempt now rebuilds a dead master instead of failing against a stale socket. Compile-verified. Full transport reconnect (manager interplay) is validated in the GUI/real-host E2E.
…nning session Back-end plumbing for the multi-session "adopt a running session" path (the sidebar's action). DaemonSessionRequest gains adopt_pty_session_id: when set, the daemon_tty event loop attaches to that existing session (replay + live via the Stage 3 reattach path) instead of opening a fresh one. A new on_transport_connected() opens-if-pending else attaches-if-adopted on connect. Workspace gets a pub `adopt_daemon_session(server, pty_session_id, ctx)` entry point that the sidebar calls (it builds the adopt-mode tab + drives the headless connect). The session listing it presents comes from RemoteServerClient::list_sessions (Stage 4 S4a). Compile-verified. The sidebar UI (rendering the list + click-to-adopt) is the GUI E2E; this is its complete back-end.
…bility Phase B3 = swap the transport beneath the session protocol from SSH ControlMaster + proxy-stdio to a native mosh-grade UDP datapath (roaming + low latency). A full implementation is a large, networking-heavy subsystem that cannot be meaningfully verified without a real lossy/roaming client-host link, so this lands design + the only safe, additive footprint: - FEATURE_UDP_TRANSPORT reserved in zaplex_remote_session::types — the negotiation name, explicitly NOT advertised by supported_features() (honest capability handshake; daemon never claims UDP until it's real). - Design doc covering the model (SSH bootstrap → AEAD key → UDP), the RemoteTransport trait seam (UdpTransport alongside SshTransport, session layer unchanged), SSP-style delta sync over the existing seq cursor, roaming, predictive echo, the capability/feature gate, and an explicit remaining-work + verification list. Design only — no UDP datapath shipped. B2 (Stages 2-4) remains the shipping transport; B3 is the deferred upside, to be built + reviewed separately once the real-host E2E has exercised B2.
…completions) A daemon session is no longer a bare VT. After spawning the PTY, the daemon writes the Zaplexify shell-integration init script as the session's first input (via the ordered writer, ahead of any user input), so the remote shell gets blocks, prompt marks, input modes, and completions — the actual premium-terminal experience. The init script self-sets TERM_PROGRAM, emits the InitShell DCS hook, and is idempotent (ZAPLEX_BOOTSTRAPPED guard), so a later re-attach won't re-run it. Shells without a known ShellType (bash/zsh/fish) fall back to a plain shell with a clear log. Required making terminal::bootstrap a pub module so the daemon path (under remote_server) can reach init_shell_script_for_shell. This closes the functional gap that previously made daemon sessions a bare remote shell. cargo check -p warp --tests green.
…nect + diagnostics Make the daemon connect robust + debuggable for the first real-host run: - spawn_daemon_session_connect now, after the ControlMaster is up, checks the remote-server binary via the RemoteTransport trait and auto-installs it if missing (install_binary) before connect_session — so a host that's never been used no longer fails silently. (Preinstall-gate/legacy fallback is skipped: a daemon session has no non-daemon fallback anyway; a genuinely unsupported host fails with a clear error.) - Diagnostic logging across the whole connect path (ControlMaster → binary check/install → connect_session → OpenSession → session opened → re-attach), so the first bring-up on a real host is traceable in the logs. cargo check -p warp --tests green (no warnings).
…it tests, runbook De-risk the first real-host bring-up with everything verifiable without a host: - daemon_session_runs_zaplexify_bootstrap (server_model_tests, xl): opens a bash session and asserts `echo TP=$TERM_PROGRAM` yields TP=ZaplexTerminal — runtime proof that the daemon actually runs the Zaplexify integration script over the spawned PTY (the previously-unverified bootstrap mechanism). Also re-exercises the existing daemon_session tests now that every session is bootstrapped (no regression). - headless_connect unit tests: is_headless_capable (key only), control_socket_path (stable + per-host), alloc_daemon_session_id (unique, top-half). - Test runbook (docs/.../daemon-session-test-runbook.md): preconditions, expected client-log sequence, failure-mode table, and what to capture — so the bring-up is efficient. Also confirmed by static trace (no code change needed): connect_session stores the client + emits SessionConnected under our allocated session_id (daemon_tty's wait resolves); resolve_server_auth maps Key + OneKey-key → AuthType::Key with key_path (daemon path triggers, password falls back); session_resilience flows DB → sidebar → open_ssh_terminal. cargo check -p warp --tests green.
…pawn The daemon-spawned PTY went through spawn_session_pty, which set TERM/TERM but never TERM_PROGRAM — unlike the local spawn (build_host_shell_command). So a daemon session had no Zaplex terminal identity: TERM_PROGRAM was empty, which the shell integration and plugins key off to recognize a Zaplex terminal. The injected bootstrap *script* supplies shell integration (it emits the InitShell DCS hook) but does NOT set TERM_PROGRAM — that belongs in the spawn env. spawn_session_pty now sets TERM_PROGRAM=ZaplexTerminal, COLORTERM=truecolor, and TERM_PROGRAM_VERSION/ZAPLEX_CLIENT_VERSION, each honoring a client override via env (mirrors build_host_shell_command). The pre-existing bootstrap test asserted only TERM_PROGRAM, which after this fix comes from the env and not the script — so it would have passed even with the script injection removed (tautological). Rewritten to pin BOTH pieces independently: the InitShell DCS hook in the output (script ran) AND TERM_PROGRAM=ZaplexTerminal (identity env). Caught by the pre-test xl run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The client-side daemon_tty EventLoop was only statically traced. These add runtime coverage of its daemon-session-specific logic (the shared ANSI parser itself is already covered by the terminal-model/ANSI tests, so we don't re-test rendering): - session_output_routes_to_terminal_and_filters_by_pty: drives the REAL path — a RemoteServerManager singleton emits SessionOutput, delivered synchronously via flush_effects to the loop's subscription. Asserts our session's output reaches the parser+model (repaint wakeup, fired after parse_bytes) and advances last_seq to seq+len (the replay cursor); a push for a foreign pty_session_id on the same connection is filtered out (no wakeup, last_seq unchanged); a contiguous follow-up chunk advances the cursor correctly. - input_before_session_open_is_buffered_then_flushed: keystrokes before OpenSession resolves are buffered in order and drained once the pty_session_id is known — nothing typed during the connect window is lost. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zap_release.yml builds all platforms (incl. ~6h Intel) and publishes a public GitHub Release — too heavy for the iterative GUI test loop. This adds a workflow_dispatch that builds ONLY the requested macOS arch via script/bundle (--selfsign, ad-hoc) and uploads the .dmg as a workflow artifact, no release. Mirrors the existing test-dispatch.yml pattern for Rust tests. dmg_tag is baked as GIT_RELEASE_TAG so the remote-server path the client probes on the target host (~/.zap/remote-server/zap-oss-<tag>) is deterministic and the daemon binary can be pre-staged there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arget The macOS DMG can't auto-install the daemon on the Linux SSH target (the release install script downloads from zerx-lab/warp releases, which has no asset for our fork's tag). So the linux remote-server binary is built here as a static-musl artifact and placed manually on the target host at ~/.zap/remote-server/zap-oss-<tag>, where check_binary finds it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Xcode 26 ships the Metal toolchain as a separately downloadable component.
Without it `xcrun metal` is missing and crates/warpui/build.rs panics
compiling the .metal shaders ('cannot execute tool metal due to missing Metal
Toolchain'). prepare_environment selected Xcode 26 but never ran
script/macos/install_build_deps (which does xcodebuild -downloadComponent
MetalToolchain), so every macOS build failed — incl. zap_release.yml. Add the
step right after setup-xcode so it targets the selected Xcode.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Use the prepared zaplex.icns directly as the macOS app icon (cargo-bundle copies a provided .icns as-is). The source zaplex-icon.png is RGB without alpha (black corners) so it isn't suitable as the icon directly; the .icns carries proper transparency + all sizes. - Disable the inherited adaptive AppIcon.icon (old zap glyph) by renaming it so script/compile_icon skips it for the oss channel — otherwise actool would override CFBundleIconFile with the old icon. (A future polished adaptive icon would need a transparent >_ glyph SVG; the provided asset is a finished icon.) - Replace the DMG installer background with the zaplex splash (700x500). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full release-lto build of the whole workspace on macos-26 (no sccache) takes ~60-120 min cold — unworkable for iterative GUI testing. Add a 'fast' input (default true) that passes --debug to script/bundle → CARGO_PROFILE=dev (unoptimized, no LTO) → minutes. The pre-staged daemon binary makes the install path irrelevant, and debug_assertions only help catch bugs during testing. Set fast=false for an optimized release-lto DMG when needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First real-host test surfaced the bug: connecting a daemon session to a host whose login profile auto-launches byobu (e.g. ~/.profile sourcing byobu-launch) made the daemon's login shell JOIN the user's existing byobu session group — cross-contaminating I/O (the injected bootstrap script leaked into the user's live session; the daemon tab mirrored it). This violates the core design (the daemon owns persistence itself; it must not compose with the user's multiplexer). Set BYOBU_DISABLE=1 in spawn_session_pty's env (byobu's documented opt-out, honored by byobu-launch). Client may override via env. Regression test: the bootstrap daemon_session test now also asserts the shell sees BYOBU_DISABLE=1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…llback - Self-review finding: the shared per-host ControlMaster was spawned with ControlPersist=yes, so the backgrounded master never exited — and since daemon sessions no longer stop it on tab close (it's shared), it leaked one ssh master process per host, surviving even app exit (-f detaches it). Switch to ControlPersist=600 so it self-retires after idle while still being reused for reconnects / new tabs in the window. The remote daemon session is independent of the master and survives regardless. - Codex review #6 (P1): the remote-server install script's tarball binary search dropped `zap-oss`, but the (deferred) release workflow still packages that name, so installs from a published OSS tarball would fail with "no binary found". Add `zap-oss` to the defensive fallback list (alongside the existing `warp-oss` legacy name), so the script accepts both the new `zaplex` and the legacy name. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-review (UI pass) of the new SSH-manager surfaces against the panel's
existing design-system patterns:
- Right-align trailing actions: the candidates header (Refresh) and candidate
rows ("+"/"Added") used a fixed-width spacer + Start alignment, so the action
floated mid-row. Switched to a left-group + SpaceBetween (the render_toolbar
pattern), pinning the action to the right edge.
- Align adopt-session rows to the tree grid: the indent was ad-hoc and placed the
session title left of its host's name; now derived from the same constants the
tree row uses, so the title lines up under the host name.
- Consistent hover feedback: added the standard fg_overlay_3 hover background to
the session rows, the candidates header toggle, and the blank/cancel buttons
(previously only candidate rows highlighted).
- Session fetch errors now render in the theme error color (like the candidates
error row) instead of muted gray; dropped the now-redundant glyph.
- Unified status-message font to ui_font_body (matching candidate messages).
- Standardized the small icon-button corner radius to 4 (was 3).
- Visual separation: small top margin before the ~/.ssh/config suggestions, and
bottom padding under the whole add block so it reads as distinct from the tree.
- server_view: onekey type pills use Wrap::row (wrap on narrow forms) like the
sibling auth/resilience/ring pill groups.
- Magic numbers -> shared constants (toolbar padding, row spacers).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The rename branch added a bottom margin the normal (hoverable) branch didn't, so a row nudged 2px when rename mode toggled. Match the normal branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…play gap, prune leaks From an independent adversarial review of the daemon-session changes (it also confirmed the round 4-6 fixes are correct): - P2: a daemon-side failure *after* the transport connected left a blank/hung tab. The OpenSession and reattach result handlers only logged. Now both write a visible notice (and OpenSession clears pending_open), mirroring the pre-connect failure path. Covers: bad cwd / unspawnable shell / fd exhaustion on open, and the session vanishing in the race between listing and adopting. - P2: replay-gap corruption after a long outage. If the daemon's OutputRing evicted bytes the client never saw, reattach applied the post-gap replay onto the stale grid (the evicted span may have held clears/cursor moves) → garbled terminal. Now, when base_seq > last_seq, reset the screen + scrollback and note the truncation before applying the replay. - P3: persistent_session_ids wasn't cleared on reconnect exhaustion (slow, bounded id leak) — now removed in that terminal branch. - P3: the SSH panel's per-host adopt-session maps (host_sessions, expanded, loading, error, row_states) weren't pruned when a node was deleted — now retained against the live node set in refresh_tree. Deferred (noted): client-side pending_input cap during very long outages; focus-existing-tab on duplicate adopt; resolve OneKey credential before offering the inline Sessions list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…urate OneKey gating The three items deferred from the self-review: - Bound client-side pending_input during long outages: consecutive resizes coalesce to the latest, and input past a 256 KiB cap drops oldest-first, so a laptop sleeping for hours can't grow the buffer without limit. Regression test added. - Duplicate adopt now focuses the existing tab instead of opening a second view onto the same daemon session (which split input/output across tabs). The workspace tracks pty_session_id -> hosting tab (pane-group id), pruning stale entries opportunistically. - The inline "Running sessions" list now resolves OneKey -> effective auth and shows a clear "needs key-based authentication" message for non-key hosts, instead of letting the headless list attempt run and surface a confusing ssh BatchMode error. (Removed the now-unused is_daemon_capable helper.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…output, OneKey listing - P2: the warp_ssh_manager test suite no longer compiled (test SshServerInfo / SyncServer literals missing ring_ceiling_mb) and its in-memory schema helper stopped before the session_resilience + ring_ceiling migrations, so repository tests would fail with "no such column". I had only run the app crate's tests and missed this. Added both migrations to setup_in_memory and the field to the four test literals; `cargo test -p warp_ssh_manager` is green again (91). - P2: fresh daemon sessions could lose initial shell/bootstrap output. The daemon auto-attaches and starts the PTY before the OpenSession response reaches the client, so SessionOutput arriving while pty_session_id was still None was dropped. Now buffer that output (bounded) and render it in order in on_session_opened. Regression test added. - P2: the inline session list / adopt used the raw saved record for OneKey hosts. It resolved the credential only to *check* key auth, then passed the unresolved server to control_socket_path/list_daemon_sessions (and adopt) — wrong username/key_path → wrong ControlMaster / auth failure. Now build a resolved server (as open_ssh_terminal does) for both listing and adopting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex review #8 (P1): I'd changed the OSS desktop entry's Exec to `zaplex`, but the deb/rpm/arch packages still name the package `zap` (PACKAGE_NAME), so they install the command as `zap` — the desktop launcher would fail. Match Exec to the actual installed command (`zap`); the runtime-identity fields (StartupWMClass, Icon, MimeType, Name) stay on the correct dev.zaplex.Zaplex / zaplex values. The full Linux package-slug rename remains deferred (macOS-only product, no Linux CI). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add a build & install preamble (aarch64 DMG via exports/, daemon binary pre-placed at ~/.zaplex/remote-server/zaplex-<tag>, tag v0.daemontest-0630). - Add test steps for the now-built adopt-sidebar (list + re-attach, focus on duplicate adopt, key-auth gating), the on-demand add-host UX, the long-drop scrollback-reset notice, and visible failure notices. - Fix the remote daemon log path (~/.zap -> ~/.zaplex). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…de + Codex) Scopes the first increment of the cockpit (the "plex"): a native, read-only data layer that discovers Claude + Codex accounts/subscriptions and aggregates per-account token usage into rolling windows with cost + heat, exposed via a CockpitModel singleton refreshed by file-watch. Grounded in the verified on-disk CLI footprint, the claudeplex reference (Claude-only; Codex is net-new), and the existing watcher/singleton patterns. UI, live session inventory, launch-on-freest, switching, multi-host, and history are scoped as later increments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y up Session persistence is a core zaplex feature (it's what makes a session survive transport drops), so: - New hosts default to PersistOnly (moved the enum #[default]); it only actually engages for headless-capable key-auth hosts, others fall back transparently. Existing saved hosts keep their stored value; the DB-load fallback and the sync legacy-payload default are pinned to Off explicitly so nothing is silently upgraded. - The persistence toggle is moved from the very bottom of the host form to directly under the auth section (prominent), instead of buried after Notes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bundled themes with background images (Phenomenon, Jellyfish, Koi, Leafy, Marble, Pink City, Snowy, Red Rock, Dark City, Solar Flare) hurt text readability and nobody uses them; the two Warp referral-reward themes are out-of-scope cruft (no referral program in zaplex). All are removed from the ThemeKind enum, the Display impl, and the built-in registry, along with their now-orphaned color helpers/constants and imports. Adds "Zaplex Dark" and makes it the default (compiled #[default]): a deep-navy solid background with the blue->purple accent from the splash screen, using the well-tuned Tokyo Night ANSI palette for readable terminal colors. The onboarding theme picker and fallbacks point at it; the vestigial inherited Adeberry A/B default-override in SettingsInitializer is removed (the compiled default now already is the desired one). Gradient (non-image) themes such as Cyber Wave, Willow Dream and Fancy Dracula are kept. Note: PhenomenonStyle in warp_core (the internal UI design-system palette used by callouts/onboarding/launch modal) is unrelated and untouched. Orphaned jpg/*_bg.jpg assets remain for now (one test reads jellyfish_bg.jpg); asset pruning is a separate cleanup. Theme-editor follow-up tracked in #18. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The integrated file manager is a pane view-mode (terminal <-> files toggle, connection-bound) rather than a fixed dual-pane MC or a sidebar tool. Specs the PaneContext model, the FsBackend abstraction (reusing the existing SFTP backend split), the cross-tab copy/move target registry with MC-speed default-target heuristic, the transfer engine, UX for beginners+pros, and P1-P3 phasing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… template) Self-contained cleanup, step 2 item 1. The toolbelt now leads with the remote-dev core (SSH hosts -> Server Files), then local workspace tools (Project, Global Search), then agent conversation history, then Skills — SSH is no longer buried mid-list. Zaplex Drive (inherited Warp Drive) is removed from the shipped toolbelt but PRESERVED as a template: the ToolPanelView::ZaplexDrive variant, all its render match-arms, and the drive/ + cloud_object/ modules stay compiled and revivable for the later claudeplex agent/sidebar work. The three unwrap_or(ZaplexDrive) active-view fallbacks now point at the always-present SshManager. Approach + remove-vs-preserve classification recorded in docs/superpowers/specs/2026-07-01-self-contained-cleanup-plan.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two host-manager UX fixes from the test feedback. A1 — Save button dirty-tracking. The host editor now snapshots every field the Save button submits (all editors + auth type, resilience, ring ceiling, group, onekey credential) into a baseline on load and after each successful save. Save is enabled only while the form differs from that baseline; it re-baselines in `reload` (which on_save already calls), so a successful save visibly disables the button again — the "it worked" feedback that was missing. Disabled state uses a muted background/text (not just a dead click) so it doesn't look clickable. The per-editor edit listener now re-renders on every edit so the button updates live. A2 — add-mode clarity. The "Add a host" block is wrapped in a card with an accent left-bar + subtle background and an accent-colored heading, so it's obvious you're in add mode; and the "no servers yet" tree empty-state is suppressed while the add block is shown (the two together read as a contradiction). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s templates)
Self-contained cleanup, step 2 items 3–4. Removes Warp/Oz from user-visible
surfaces without deleting the underlying (preserved-as-template) infrastructure.
Oz de-branding (text only; keys, Harness matching and cli_command_name
resolution untouched):
- 19 warp.ftl values neutralised ("Fix with Oz" → "Fix with AI"; "…by Oz" →
"…by the agent"; "Install Oz CLI" → "Install Zaplex CLI"; "New Oz agent
conversation" → "New agent conversation"; tip/onboarding/slash-cmd mentions;
vertical-tab kind "Oz" → "Agent"; etc.). Fluent keys are unchanged so all t!()
call sites keep resolving.
- The one release-visible hard-coded label — the invalid-settings banner's
"Fix with Oz" button (shown when BYOP AI is enabled) — is now "Fix with AI"
with the neutral Icon::AiAssistant instead of the Oz brand mark. (Its action is
the repurpose target for a later increment.)
Zaplex Drive de-listing (code preserved as template):
- Removed the top-level "Drive" menu from the menu bar and the "Toggle Drive"
item from the View menu; `make_new_drive_menu` is kept behind #[allow(dead_code)].
- Flipped the `enable_warp_drive` setting default to false — the documented master
switch that also gates the Drive keybindings (flags::ENABLE_ZAPLEX_DRIVE) and the
command-palette / command-search entries, so Drive no longer surfaces anywhere by
default while drive/ + cloud_object/ stay compiled and revivable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…f scope) The `pricing` module modelled Warp's own SaaS subscription tiers (Stripe: Business/Pro/Team/…) and pricing/addon-credits. It is out of scope for a self-contained BYOP product and is NOT the basis for the cockpit's cost tracking (which derives cost from the CLIs' own transcripts + a per-model LLM price table, not Warp SaaS plans). It was already inert: `PricingInfoModel` had zero real readers (only its singleton registration), and `StripeSubscriptionPlan` was used by a single, uncalled `TryFrom<&BillingMetadata>` impl. Removed the module, its singleton registration, the dead TryFrom impl in workspaces/workspace.rs, and three test-only registrations. The workspace's own `usage_based_pricing_*` fields (a separate, workspace-internal concept) are untouched. Builds green incl. tests. The other inert cloud stubs (request_usage/quota, drive ACL shells, shared-session transport, telemetry) are much more deeply woven (16–219 files) and are tracked for a separate, carefully-tested pass rather than removed here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…CLI) Specs how Oz's useful contextual one-shot actions are kept but routed to the user's own installed CLI agent (Claude Code/Codex/Gemini/…) instead of an in-app agent. Grounded in the real mechanism (CLIAgent + command_prefix + is_cli_agent_installed; add_tab_with_specific_agent; the agent_sdk harness command-builders). Defines an AskAgent primitive, the installed-set selection UX (0/1/many), prefill-vs-one-shot delivery, and P1-P3 phasing. Flagship P1 retargets FixSettingsWithOz (the release-visible "Fix with AI" button) off the flag-gated agent-mode and onto the user's CLI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(new crate)
New pure, headless-testable crate `zaplex_cockpit` — the read-only data foundation
for the cockpit ("plex" half of the product). Per
docs/superpowers/specs/2026-06-30-cockpit-increment1-account-usage-design.md.
- types: Provider (Claude/Codex), Account (metadata only), UsageEntry,
WindowTotals (work = in+out+cache_create+reasoning, excludes cheap cache reads),
AccountUsage, CockpitSnapshot.
- pricing: centralized per-model table (USD/1M, distinct cache rates), substring
match, reasoning bills as output, unknown models cost 0 + logged (never silently
mispriced). Seeded from current Anthropic + OpenAI list prices (flagged: refresh).
- windows: ccusage-style rolling blocks (5h / 7d) + calendar today, reset times,
heat = work/budget; all pure with explicit `now` for deterministic tests.
- claude: $HOME account discovery (~/.claude + ~/.claude-* + $CLAUDE_CONFIG_DIR,
backup dirs excluded; oauthAccount identity; plan-tier label) + defensive
transcript parsing (assistant turns, counts only).
- codex: auth.json discovery (email from the unverified id_token JWT payload,
tokens never stored) + defensive rollout parsing (per-turn last_token_usage,
ignores cumulative envelope; schema best-effort per design §10).
- build_snapshot: single I/O entry point tying discovery + usage + aggregation.
Privacy invariant enforced structurally: types carry only token counts + account
metadata, never token strings or transcript content. 17 unit + 1 integration test,
green, warning-free (cargo test -p zaplex_cockpit). No app wiring yet.
Architecture note: kept the crate warpui-free (pure/fast to test); the
CockpitModel + file-watch wiring will live in app/src/cockpit/ (deviates from the
doc's "warpui in the crate" for testability).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h + settings Wires the pure zaplex_cockpit data spine into the app (app/src/cockpit/): - CockpitModel: SingletonEntity holding the latest CockpitSnapshot, emits CockpitEvent::Updated. Refresh is driven by (a) HomeDirectoryWatcher for account add/remove and (b) a 45s reconcile tick for transcript growth + window rollover (the non-recursive home watcher can't see appends deep in projects/**/sessions/**). The blocking disk scan runs on the background executor; results apply on the model thread via the spawner round-trip — mirroring file_mcp_watcher + the daemon GC timer. - CockpitSettings (scalar): cockpit.enabled (gates refresh), cockpit.budget_5h / cockpit.budget_week overrides (0 = built-in estimate). Registered in settings init. - Registered as a singleton in lib.rs after HomeDirectoryWatcher exists. - Manifests: zaplex_cockpit added to workspace + app deps. Deviation from the design doc (for testability): the data logic stays in the warpui-free crate; only this thin model/watch layer lives in the app. Compiles clean (cargo check -p warp, 0 warnings); crate tests green. Runtime behaviour of the watcher/tick will be confirmed in the next build. No UI yet (Increment 2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ex way) Reframes the cockpit port: claudeplex wraps a cockpit around EXTERNAL infra (claude CLI + tmux fleet + SFTP commander), all of which zaplex already owns natively (pane multiplexer, persistent session daemon w/ ring ceiling, SSH manager + FM pane-mode, CLI agents, conversation history, themes, i18n, palette). So the cockpit is the missing INTELLIGENCE/ORCHESTRATION layer, not the infra — a lens over zaplex's existing panes/daemon-sessions/hosts, never a parallel world (the anti-foreign-body principle, mirroring the FM pane-mode adaptation). Includes a full parity checklist (every claudeplex feature -> its native zaplex home, nothing dropped; Codex is net-new = more than claudeplex), three native surfaces (Cockpit pane dashboard, live inventory = zaplex sessions, ambient attention indicator), native actions (account-routed launch / launch-on-freest, daemon adopt, commander), and a revised native-first increment plan C1-C5 that folds in the already-built data spine. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three glance tiers: toolbelt-icon attention badge (ambient) · sidebar toolbelt tab (quick-access while working, no main-area switch) · main-area pane (roomy overview/deep-dive). Revised C2 to build both surfaces spine-backed, sidebar first (approved). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Cockpit sidebar (left toolbelt tab, first/leading per the approved main-pane-vs-sidebar principle): a compact, glanceable account list over the zaplex_cockpit data spine, subscribing to CockpitEvent::Updated. - crate: pure `format` module (HeatLevel bands + reference palette, heat fill/pct, cost, humanized tokens, reset countdown) — 6 headless tests. Shared by sidebar + (later) pane. - app: `CockpitPanel` view — per-account compact card (label + plan badge, 5h heat bar coloured by band, today cost+tokens, 5h/wk reset countdowns) + aggregate header (accounts, total 5h/wk cost) + empty state; vertically scrollable. - Wired as `ToolPanelView::Cockpit` toolbelt tab (Icon::Grid), gated by cockpit.enabled, leading the toolbelt in compute_left_panel_views; full left_panel dispatch (button/focus/action/body), LeftPanelDisplayedTab persistence, and the single-view tooltip matches. i18n keys added. Compiles clean (cargo check -p warp, 0 warnings); crate tests green. Live-session quick-list + "needs-you" marker + quick-launch land in C3/C4; per the design its visual layout is confirmed in the next build. The roomy full pane is C2b. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"Use Oz to update this config" -> "Use AI to update this config" (the /update-tab-config footer hint), the last user-visible Oz string found in testing. Trivial literal change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…SH fallback Root cause of the "Starting…" hang (found by testing, verified not is_dev_source_build): persistence-default-ON routes every key-auth host through the daemon path, which, when the daemon binary is absent for the build's tag (fresh host, or a tag mismatch like devhost's zaplex-v0.daemontest-0630 vs expected zaplex-v0.daemontest), ground through an unbounded install-on-connect (GitHub download for a non-release tag + a 194 MB scp) that only failed after ~3 min — appearing as an eternal "Starting…" with an empty tab head. Fix (keeps persistence as the default; degrades gracefully, never hangs, never lies): - headless_connect::prepare_daemon_transport: when the binary is missing, fail FAST with the DAEMON_BINARY_MISSING sentinel instead of an install-on-connect. (Installing the daemon becomes a separate, explicit action — not a multi-minute stall per connect.) - try_open_daemon_ssh_terminal: PREFLIGHT (ControlMaster + fast binary check) OFF the main thread BEFORE creating any tab. Only on success do we open the daemon tab and connect; on ANY failure we fall back to a classic local-PTY SSH session (open_ssh_terminal force_classic=true) AND raise a prominent persistent toast warning that there are NO persistent-session features and a disconnect loses open work. No more dead "Starting…" tab. Compiles clean (cargo check -p warp, 0 warnings). Runtime behaviour to be confirmed in the next build. Deploying a matching-tag daemon binary (to exercise the *persistent* path itself) is a separate step; the fallback covers its absence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on connect Corrects my earlier over-correction (which disabled auto-install). Per the approved design: persistence must auto-install the daemon on first connect (like Warp), but without ever requiring the *remote host* to reach the internet. - install_binary: the PRIMARY path is now the client-push install (scp_install_fallback): detect the host platform, fetch the matching binary ON THE CLIENT, scp it over the existing ControlMaster. The host-side download (run_install_script(None)) is retired — remote-dev hosts are often locked-down / air-gapped. (dev-source builds still cross-compile locally.) - prepare_daemon_transport: binary missing → auto-install via that client-push path (bounded by download + scp timeouts), then connect. On genuine install failure the caller still falls back to a classic SSH session with a warning (never a silent hang). Compiles clean (0 warnings). NOTE: still needs a binary *source* to actually deliver — a published per-platform release tarball (step 2) or an embedded common-platform binary (step 3). Until then, a source-less host correctly + quickly falls back to classic SSH. Progress-bar UI during install is step 1b. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Approved 4-rung ladder: reachability probe → host-side download → client relay (embedded binary → else client download) → classic SSH + banner. Host never needs internet; any host platform; version-matched (no skew); progress on every rung; graceful classic fallback. Embeds common Linux musl binaries in the .app for the offline case. Records the plumbing per layer + the CI/packaging step + build order. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements rungs 1–3b of the approved install ladder (design doc 2026-07-02-daemon-install-ladder-design.md): - Rung 1: probe_host_internet — a fast HEAD (curl -fsI --max-time 3) over the ControlMaster so a locked-down/air-gapped host doesn't pay a full download timeout before we relay. - Rung 2: host reachable → host-side download (run_install_script, the host fetches the version-matched tarball on its own pipe — fastest). On failure → relay. - Rung 3b: client relay — client fetches the host-matching binary and scp's it over the ControlMaster (host needs no internet). (scp_install_fallback.) Rung 3a (bundled/embedded binary for the fully-offline case) + the progress-bar UI + the CI step that stages the musl binaries into the .app are the next chunks toward one testable build. Classic-SSH+warning fallback (rung 4) already in place. Compiles clean (0 warnings). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mote-server Root cause (found, not guessed): connecting any key host stalled at "Starting shell …" because the remote-server SSH enhancement (`SshRemoteServer`/`ServerFileBrowser`) is force-enabled even though zaplex has **no way to provide the host binary yet** (no published release tarballs, no embed, and the packaged DMG is not a dev cross-compile build). With the flag on but no source, every *classic/legacy* SSH session is routed through a stash-and-install (model_events.rs:92) that **withholds the interactive shell** until a doomed install finishes 404-ing through the download/scp timeouts. Both reported hosts hit this: agenthost via the classic path directly, devhost via daemon→fallback→ classic. Fix — make capability honest so nothing engages a doomed install: - `enabled_features()`: drop `SshRemoteServer` + `ServerFileBrowser` unless an install source actually exists (`is_dev_source_build`; extend with an embed check when ladder rung 3a lands). No source → classic SSH is a normal **instant plain shell** again (no stash, no install, no banner). The persistent *daemon* path does NOT gate on these flags, so resilience is unaffected. - Daemon path (`prepare_daemon_transport`): before auto-installing on first connect, a fast bounded `SshTransport::install_source_available()` (dev build, or a client-side `curl -fsIL --max-time 3` HEAD of the version-matched release asset). No source → return `DAEMON_BINARY_MISSING` in seconds → classic-SSH fallback + warning toast, instead of a multi-stage install stall. When the embed / a real release process lands, this same gate opens and becomes Warp-style auto-install-on-first-connect. Net: both hosts now connect fast — a working shell every time, with a clear "no persistent session" warning when the daemon can't be installed. No regression to the daemon path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The classic-SSH fallback warning went onto `update_toast_stack`, whose overlay is only rendered behind `FeatureFlag::AvatarInTabBar` — hard-disabled in the fork's decentralized branch (warp_features is_enabled always-false list). So the "no persistent session — a disconnect loses open work" warning never appeared in any test build. Move it to `toast_stack`, which renders unconditionally via global_toast_positioning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…with progress The full Warp-style persistent-session experience: connecting a resilient Linux host now auto-installs the zaplex remote-server on first connect — offline, with progress in the tab — and opens a persistent daemon session. Previously every connect degraded to classic SSH because no binary source existed. - Embed (install ladder rung 3a): CI builds the static musl Linux x86_64 remote-server (same GIT_RELEASE_TAG as the client → version-matched by construction), packs it in the release-asset tarball layout, and stages it into the .app (Contents/Resources/bundled/remote-server/). Runtime resolver: app/src/remote_server/embedded.rs. test-dmg.yml gains a build_remote_server job; the DMG job stages the tarball via resources/bundled/ before bundling. - setup.rs: tarball_basename() — single source of truth for the asset name, shared by download URL, embed resolver, and CI packing. - Install ladder restructured (ssh_transport.rs): detect platform once → rung 1 probe → rung 2 host download → rung 3a bundled relay → rung 3b client download relay; shared relay_tarball_install(); InstallProgress phase channel. - Progress in the daemon tab: preflight (fast, pre-tab) now classifies Ready/NeedsInstall (headless_connect::preflight_daemon_transport); on NeedsInstall the tab is created first and the install ladder streams phase lines into it (DaemonSessionRequest.install_progress_rx → daemon_tty event loop → dim-cyan notice lines). On success the session connects seamlessly; on failure the tab shows the error and a classic SSH session opens alongside with the persistent warning toast. - Source gates re-widened: an embedded tarball now counts as an install source (SshRemoteServer/ServerFileBrowser flags + daemon auto-install) — the "source-honest" gating from ccf96bc opens up exactly when a source ships. - Workspace: connect_daemon_session()/fall_back_to_classic_ssh() helpers replace the duplicated connect/fallback blocks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.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.
Draft — Stage 2, increment 1 (client protocol layer). Builds on Stage 1 (daemon session host, merged).
Enthalten
docs/superpowers/specs/2026-06-25-stage2-client-attach-design.md(Seams, Insertion-Point, Increment-Plan).remote_server-Crate):ClientEvent::SessionOutput/SessionExited+push_message_to_event-Arme.open_session,attach_session(Request/Response) undsend_session_input/send_resize_session/send_detach_session(Notifications).forward_client_event: Platzhalter-Arm für die neuen Events (noch kein App-Consumer → zur Laufzeit nicht getroffen).Verifiziert
cargo check -p remote_server(lib + tests) grün. Isoliert auf den Crate — App-Build bleibt unberührt (Gate bestätigt).Offen (nächste Increments, siehe Spec §3)
SessionOutput→ansi::Processor::parse_bytes→ Grid/Blocks); 4. Input/Resize/Maus viasend_session_input; 5. Session-Lifecycle/-Typ. Das ist die schwere app-seitige Integration — bewusst eigener Increment.Der Kern-Insertion-Point:
SessionOutput.bytes→Processor::parse_bytes(&mut terminal_model, &bytes, &mut io::sink())(identisch zum lokalen Pfad, nur ohne lokales Echo).