glm-5.3-flash 01e94914c1 chore: raise rust-version floor to 1.88
The 1.85 claim is now false at the dependency level: the lockfile pins
alkcall 0.7.x, and alkcall 0.7.1 raised its MSRV floor to 1.88, so no
1.85 toolchain can resolve this dependency graph regardless of what
Cargo.toml declares. Raising the floor keeps the claim honest and
aligns with the ecosystem floor (noq's 1.88, matching the QUIC path;
iroh sits above at 1.91). alktty's own code is 1.85-clean (verified:
cargo check under 1.85 passes with the pre-bump lockfile) — the raise
is dependency-driven only, so no clippy fixes are needed.

- Cargo.toml: rust-version 1.85 -> 1.88 (version-line bump deferred to
  release time)

Verification:
- 1.88: cargo test --locked (113 default / 156 all-features pass),
  clippy --all-targets -D warnings
- stable 1.94: test --locked, clippy --all-targets -D warnings,
  fmt, doc --no-deps, wasm32-unknown-unknown check + clippy clean
2026-09-10 02:53:33 +00:00
2026-08-14 12:41:38 +00:00
2026-08-14 12:41:38 +00:00

alktty

Terminal session protocol for the alk/tty ALPN: a JSON negotiation frame followed by raw chunks ([stream_type: u8][length: u32 be][payload]), a backend-agnostic TtyBackend trait, a backend-agnostic producer adapter, and a typed consumer client.

A producer/consumer protocol crate on top of alkcall channels — no networking, no transport dependencies. Downstream crates (a alknet-docker or alknet-ssh backend, a sandboxed TS/Python adapter) compose on top of it: implement TtyBackend for your execution environment, and the adapter handles the wire.

Quick start

Producer — register backends and run the adapter

use std::collections::HashMap;
use std::sync::Arc;

// `LocalTtyBackend` needs the `local` feature; swap in any `TtyBackend`
// implementation (docker, ssh, ...) compiled for your target.
let mut backends: HashMap<String, Arc<dyn alktty::TtyBackend>> = HashMap::new();
backends.insert("local".into(), Arc::new(alktty::LocalTtyBackend::new()));

let tty_adapter = alktty::TtyAdapter::new(backends);
// tty_adapter implements alkcall::core::ProtocolHandler on the `alk/tty`
// ALPN — hand it to your transport accept loop:
//   tty_adapter.handle(connection, &auth).await

Producer — via alk/channels instead of a dedicated ALPN

use std::collections::HashMap;
use std::sync::Arc;

let backends: HashMap<String, Arc<dyn alktty::TtyBackend>> =
    std::iter::once(("local".to_string(), Arc::new(alktty::LocalTtyBackend::new()) as _))
        .collect();

// In your channels acceptor's `install_channel_zero` hook (per-connection):
//   alktty::register_openable(&channel_core, Arc::new(backends), None, &mut registry, auth)
// Consumers open `channels/tty/sub`; the open op's params ARE the
// negotiation (ADR-009) — no second frame on the channel stream.

Consumer — open a session over the direct ALPN

use alkcall::core::Connection;
use alktty::{NegotiateRequest, TtySession};

let connection: Connection = /* dial the transport, negotiate `alk/tty` */;
let session = TtySession::connect_direct(
    connection,
    NegotiateRequest {
        carriage: "raw".into(),
        backend: "local".into(),
        tty: None,
        cmd: vec!["bash".into()],
        cwd: None,
        env: Default::default(),
        backend_params: Default::default(),
    },
).await?;

session.resize(120, 40, 0, 0).await?;
session.send_stdin("ls -l\n".into()).await?;
session.close_stdin().await?;

let mut stdout = session.recv_stdout().await;
while let Some(bytes) = futures::StreamExt::next(&mut stdout).await {
    // The stream ends on the zero-length stdout sentinel ("drained") —
    // it is never yielded as an item.
    // ... render bytes ...
}
let code = session.wait().await?;

Consumer — open a session via alk/channels

use alkcall::channels::client::ChannelClient;
use alktty::TtySession;

let client: ChannelClient = /* from_connection on an `alk/channels` connection */;
let session = TtySession::open_via_channels(
    &client,
    serde_json::json!({
        "carriage": "raw",
        "backend": "local",
        "cmd": ["bash"],
        "tty": {"term": "xterm-256color", "cols": 120, "rows": 40,
                "pixel_width": 0, "pixel_height": 0}
    }),
).await?;

Architecture

Two roles, symmetric per the alk convention (avoid "server"/"client": both sides of a connection can be either):

Role Direct alk/tty Via alk/channels
Producer (runs backends, emits terminal bytes) TtyAdapter as a ProtocolHandler — negotiation frame → access control → backend allocation → three-pump session driver register_openable — the registry's ACL gates the open op; the handler spawns the same session driver pre-negotiated
Consumer (types input, renders output) TtySession::connect_direct — writes the negotiation frame, exposes typed stdin/stdout/stderr/control/exit methods TtySession::open_via_channels — the open op's params are the negotiation

One connection hosts many sessions (one per bidi stream on the direct path, one per channel on the channels path); sessions are independent.

The wire format (ADR-001) is two carriages: a 4-byte length-prefixed JSON negotiation frame, then raw chunks — [stream_type: u8][length: u32 be][payload] with five stream types (STREAM_STDIN=0 … STREAM_CTRL_OUT=4; see alktty::wire). Zero-length data chunks are sentinels (stdin EOF from the client, stdout drained from the server); the exit code rides as the final ctrl_out control chunk (ADR-005). The codec is alktty::wire's ChunkReader/ChunkWriter; the machine-readable contract is the BAST document at docs/architecture/tty-bast.md.

WASM target

The default crate (no features) compiles to wasm32-unknown-unknown — this is what makes the downstream TS/Python adapter story work: a wasm-compiled alktty is the protocol layer for a sandboxed adapter. The local feature (the LocalTtyBackend reference implementation over portable_pty + tokio::process) needs a real OS and is non-wasm by design.

Documentation

  • Architecture docs — the authoritative spec: ADRs (001–009), the wire format, the backend trait contract, and the local backend's cancel-cleanup semantics.
  • API docs — full crate documentation on docs.rs.

License

MIT OR Apache-2.0

S
Description
No description provided
Readme
2.3 MiB
0 Stars 6 Watchers 0 Forks
Languages
Rust 96.3%
Python 3%
Shell 0.7%