Skip to content

Define Node compatibility contract and export an embeddable PTY client crate #1

Description

@schickling-assistant

Why

pty-rust proves that Rust, portable-pty, and libghostty can implement the current PTY wire protocol. It does not yet define the compatibility target for replacing the Node CLI and server, or expose the full embedded-client surface that a Rust compositor such as Fractal needs.

The target is behavioral compatibility with the Node PTY, not identical source code or an identical internal architecture. Language and terminal-engine differences are allowed. Each observable difference must be recorded as a decision.

Target

Make pty-rust:

  1. A 99.9%-compatible implementation of the current Node PTY behavior.
  2. A first-class Rust library for embedded terminal clients, similar in purpose to the Node PtyHandle.
  3. A shared conformance implementation that the Rust CLI and embedded clients use. The CLI must not keep a separate terminal model or protocol path.

Keep the current protocol as the baseline. Add protocol features only when a measured use case cannot be expressed safely by the existing frames.

Attach identity follows current Node PTY behavior. Declared PTY root plus session ID means the current session at that address. A replacement under the same ID is the target of a new attach. Generation is optional diagnostic data, not attach eligibility. Embedded clients use a local AttemptId or epoch to reject late events from an older attempt.

Current assessment

Already present:

  • Current byte-framed protocol and unknown-message preservation.
  • Local registry and socket connection.
  • Attach, detach, input, resize, exit, replay, peek, and status.
  • A libghostty terminal model.
  • Screen capture and scrollback retention.
  • Mode and query handling in the daemon.
  • A PTY test kit and CLI end-to-end tests.

Missing or not yet suitable as an embedding contract:

  • One long-lived embedded session handle with an event stream.
  • Direct, typed cell-grid reads with style and width data.
  • Wrapped-line flags for correct text selection.
  • Cursor position and visibility.
  • Active alternate-screen state.
  • Mouse tracking mode.
  • Kitty keyboard flag stack.
  • Bracketed-paste mode.
  • Scrollback length, live base position, and viewport reads by relative offset.
  • Explicit attach readiness instead of a fixed delay.
  • Reconnect and lifecycle semantics suitable for an embedded client.
  • A stable owner and thread boundary for libghostty Terminal, which is not Send.
  • Compatibility tests that run the same fixtures against Node and Rust.

Node features such as tags, remote and fabric, gc, events, and higher-level CLI commands remain separate compatibility work. They must not be hidden inside the terminal embedding API.

Proposed crate boundaries

Use the smallest split that gives the CLI and Fractal the same implementation:

  • pty-protocol: wire types and framing for the current protocol.
  • pty-client: registry and socket connection, attach lifecycle, reconnect, and typed client events.
  • pty-terminal: the single-owner libghostty terminal actor and read-only terminal snapshots.
  • pty-testkit: process-driven test helpers built on the same terminal core.
  • pty: the CLI, as a consumer of these crates.

This split is a proposal. A smaller workspace is preferred if the same ownership boundaries can remain clear.

Compatibility decisions

Add a short decision record when Rust differs from Node. Each record must state:

  • the Node behavior;
  • the Rust behavior;
  • why they differ;
  • the observable effect for clients;
  • how the behavior is tested;
  • whether migration or protocol negotiation is required.

Known first decision to document: Node converts inbound DATA payloads to UTF-8 text before node-pty.write. Rust currently writes the payload bytes directly through portable-pty. The wire frame itself is already byte-safe.

Child work

Acceptance

  • README states the compatibility and embedding direction without claiming current parity.
  • A checked compatibility matrix covers CLI, daemon, protocol, metadata, terminal state, remote transport, and embedding APIs.
  • Attach parity proves root-plus-session-ID current-session behavior, replacement under the same ID, and stale-event rejection by local attempt identity.
  • No attach implementation requires generation discovery or generation-fenced admission.
  • Node and Rust run the same attach, input, resize, replay, and exit fixtures.
  • The embedded handle exposes terminal snapshots and mode state needed by Fractal without parsing serialized ANSI output.
  • One actor owns the non-Send libghostty terminal. Consumers receive typed events and snapshots.
  • The Rust CLI uses the same client and terminal crates as embedded consumers.
  • Every intentional observable difference has a decision record and a conformance test.
  • A real byte-round-trip fixture decides whether binary input is a supported cross-implementation feature or a documented Rust-only difference.
  • No new protocol mode is added without a failing use case and a Node and Rust rollout plan.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions