Folds the alknet-tty-local crate into alktty as a feature-gated local
submodule (the single-crate + local-feature design from ADR-054; the
cyclic-dep workaround the mono-repo needed doesn't apply to one crate).
src/local/mod.rs:
- Module root, re-exports LocalTtyBackend, declares backend/pipe/pty
- Doc-commented as feature-gated + non-wasm by design (portable-pty +
tokio::process need a real OS; enabling local on a wasm target is a
build error by design)
src/local/backend.rs:
- LocalTtyBackend: implements crate::backend::TtyBackend
- allocate() dispatches on params.terminal: Some -> pty::allocate_pty,
None -> pipe::allocate_pipe (ADR-054)
- 7 tests: PTY/pipe dispatch, empty-cmd rejection (both modes),
resource_id returns None, new() constructs
src/local/pty.rs:
- portable_pty + 3 std threads (reader/writer/waiter) feeding tokio
mpsc/oneshot (REQ-TTY-01 blocking->async bridge)
- PtyControl: resize + REQ-TTY-02 process-group signal forwarding
(libc::kill(-pgid, sig) + kill(pid, sig) fallback + ChildKiller::kill
for unknown names)
- LocalExitFuture: ADR-056 kill-on-Drop guard (ChildKiller on cancel,
Option::take disarms on resolve)
- StdinSink: AsyncWrite over mpc::Sender<StdinCmd> with in-flight
reserve+send parking for full-channel backpressure
- 7 tests incl. process-group reach (bash -c sleep), cancel-cleanup,
unknown-signal fallback
src/local/pipe.rs:
- tokio::process::Command + tokio_util::io::ReaderStream
- PipeControl: no-op resize, libc::kill(pid, sig) with SIGKILL
fallback for unknown names (pid-only, no process group - documented
limitation of the runner case)
- PipeExitFuture: in-place Child::wait() poll (avoids self-referential
borrow) + ADR-056 kill-on-Drop guard (start_kill on cancel)
- BytesStream: wraps ReaderStream, strips io::Error to EOF
- 9 tests incl. separate stderr, SIGTERM=-15, SIGKILL=-9 fallback,
cancel-cleanup pid probe (kill(pid,0) returns ESRCH)
Import migration: alknet_tty::backend::{...} -> crate::backend::{...},
alknet_tty::control::signal_from_name -> crate::control::signal_from_name.
Cargo.toml: no changes needed (portable-pty + tokio-util optional, libc
under cfg(unix), and the local feature wiring tokio/process +
tokio/rt-multi-thread were already in the scaffold per Phase 0).
Also fixes pre-existing rustfmt drift in adapter.rs/channels.rs/
session.rs/lib.rs left by the phase 2 commit (cargo fmt --check without
--features local reported 28 diffs; cargo fmt does not accept
--features, so the earlier 'clean' check was a false negative — the
check errored on the unknown flag and grepped an empty stdout). Lesson:
run cargo fmt --check with no feature flags; cargo fmt doesn't gate on
features.
Verification:
- cargo test --features local -> 103/103 pass (was 80 at phase 2 end;
+23 new tests across the 3 local modules: 7 backend, 7 pty, 9 pipe)
- cargo test (no features) -> 80/80 pass (local module not compiled)
- cargo clippy --features local --all-targets -> clean
- cargo clippy --all-targets (no features) -> clean
- cargo check --target wasm32-unknown-unknown -> clean (default crate
stays wasm-clean; local is feature-gated and non-wasm by design)
- cargo fmt --check -> clean
59 lines
2.2 KiB
Rust
59 lines
2.2 KiB
Rust
//! alktty: Terminal session protocol for the `alk/tty` ALPN.
|
|
//!
|
|
//! Producer/consumer protocol crate on top of alkcall channels. Two
|
|
//! halves (per alkcall's protocol-crate pattern):
|
|
//!
|
|
//! - **Producer half** — [`adapter::TtyAdapter`] (direct `alk/tty`
|
|
//! ALPN via `ProtocolHandler`) + [`channels`] (registers the
|
|
//! `channels/tty/sub` open op via `ChannelCore::register_openable`
|
|
//! for the `alk/channels` multiplexed path).
|
|
//! - **Consumer half** — [`session::TtySession`] (typed client wrapper
|
|
//! around the wire protocol, with `connect_direct` and
|
|
//! `open_via_channels` constructors).
|
|
//!
|
|
//! Two-carriage wire format (ADR-052): a JSON negotiation frame, then
|
|
//! raw chunks (`[stream_type: u8][length: u32 be][payload]`).
|
|
//! Backend-agnostic via the [`backend::TtyBackend`] trait (ADR-053).
|
|
//! Depends on alkcall (ADR-057 — the negotiation framing is
|
|
//! self-contained; alkcall's `EventEnvelope` framing is not reused).
|
|
//!
|
|
//! # WASM target
|
|
//!
|
|
//! The default crate (no features) compiles to
|
|
//! `wasm32-unknown-unknown`. The [`local`] module is feature-gated
|
|
//! and non-wasm by design (`portable-pty` + `tokio::process` need a
|
|
//! real OS). Downstream TS/Python adapters compile the protocol
|
|
//! layer in a sandbox; the local-process backend runs on a real OS.
|
|
//!
|
|
//! # Local backend
|
|
//!
|
|
//! The local backend ([`local::LocalTtyBackend`]) is gated behind the
|
|
//! `local` feature. It implements [`backend::TtyBackend`] via
|
|
//! `portable_pty` (PTY mode, terminal semantics) and
|
|
//! `tokio::process::Command` (pipe mode, the runner case). The
|
|
//! blocking→async bridge for PTY mode uses three dedicated std
|
|
//! threads feeding tokio mpsc/oneshot channels (REQ-TTY-01).
|
|
//!
|
|
//! # Assembly pattern
|
|
//!
|
|
//! ```ignore
|
|
//! let mut backends = std::collections::HashMap::new();
|
|
//! backends.insert(
|
|
//! "local".into(),
|
|
//! std::sync::Arc::new(alktty::local::LocalTtyBackend::new())
|
|
//! as std::sync::Arc<dyn alktty::backend::TtyBackend>,
|
|
//! );
|
|
//! let tty_adapter = alktty::adapter::TtyAdapter::new(backends);
|
|
//! ```
|
|
|
|
pub mod adapter;
|
|
pub mod backend;
|
|
pub mod channels;
|
|
pub mod control;
|
|
pub mod negotiation;
|
|
pub mod session;
|
|
pub mod wire;
|
|
|
|
#[cfg(feature = "local")]
|
|
pub mod local;
|