Skip to content

Phase 3: PTY support + mix e2b.terminal interactive terminal - #1

Open
col wants to merge 34 commits into
mainfrom
feat/pty
Open

Phase 3: PTY support + mix e2b.terminal interactive terminal#1
col wants to merge 34 commits into
mainfrom
feat/pty

Conversation

@col

@col col commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

Adds pseudo-terminal (PTY) support to E2bEx, reaching feature parity with the JS/Python SDKs' pty namespace. This is Phase 3 of the Commands parity roadmap (Phases 1 & 2 — streaming + background execution — already landed).

  • E2bEx.Ptycreate/3 (launches an interactive /bin/bash -i -l on a PTY of :cols×:rows), connect/4 (reconnect by pid), and by-pid send_input/5 / resize/5 / kill/4.
  • E2bEx.Pty.Handlepid/1, send_input/2, resize/2, kill/1, wait/1, disconnect/1. Distinct struct exposing only PTY-valid ops; backed by the same Commands.HandleServer.
  • Message-first model: output is the merged {ref, {:pty, bytes}} channel (not stdout/stderr), ending with a terminal {:exit, %CommandResult{}} / {:error, %Error{}}. wait/1 returns an exit-code-only %CommandResult{} (PTY output is not buffered).

Internals

  • Commands.Fold gains an emit-only pty data-event branch (decodes data.pty, emits {:pty, _}, accumulates nothing).
  • Envd.Rpc gains send_pty_input/3 (the pty input channel) and resize/3 (the Update RPC).
  • Commands.HandleServer is reused unchanged — PTY just hands it a different request map.

Notable decisions

  • No Pty.list — neither SDK has one; listing stays on Commands.
  • create/3 raises ArgumentError on missing :cols/:rows (a programmer error, like Client.new without :api_key).
  • PTY is background-only (no blocking run analog, matching the SDKs).
  • The two previously-flagged refactors (shared receive_timeout constant; collapsing streaming-request construction into Rpc) are intentionally deferred to keep this diff PTY-only.

Spec: docs/superpowers/specs/2026-06-11-e2b-pty-design.md · Plan: docs/superpowers/plans/2026-06-11-e2b-pty.md

Test Plan

  • mix test — 142 tests, 0 failures
  • mix compile --warnings-as-errors — clean
  • Wire shapes (Start pty.size, Update resize, SendInput input.pty, data.pty decode) verified against the envd proto and JS SDK
  • Coverage: Fold pty branch + invalid base64; Rpc send_pty_input/resize (+ non-2xx error); Pty.create/connect streaming; by-pid controls; Handle.wait (drain, crash) / disconnect; pre-start context error

🤖 Generated with Claude Code


Added: mix e2b.terminal (interactive terminal)

This PR now also includes a raw interactive terminal built on the PTY layer above (merged from feat/pty-terminal):

  • mix e2b.terminal SANDBOX_ID (or --template <tmpl> to create/attach/kill-on-exit) — opens a real terminal into a sandbox PTY from a normal shell: arrow keys, tab-completion, vim/htop, Ctrl-C all work.
  • E2bEx.Pty.Terminal.run/2 — the reusable session orchestrator (output loop + linked stdin reader + size-poll + crash handling), over injectable IO hooks so it's fully unit-tested (Bypass-backed, no real tty).
  • E2bEx.Pty.InputBatcher — coalesces stdin bytes (and multi-byte escape sequences) into one send_input per ~10ms window.

Ports the E2B CLI's terminal.ts, accommodating two BEAM constraints: raw mode via stty </dev/tty (no setRawMode), and resize via polling (OTP can't trap SIGWINCH). All stty/tty side effects are quarantined in the Mix task; the orchestration logic is pure and tested.

Note: the raw-terminal path itself (stty//dev/tty) is the one piece not automatically tested — it's manual-smoke-only (mix e2b.terminal --template base).

Test Plan (terminal)

  • mix test — 153 tests, 0 failures
  • mix compile --warnings-as-errors clean; mix help e2b.terminal lists the task
  • Manual: mix e2b.terminal --template base opens a working shell; window resize propagates; exit restores the local terminal

col and others added 30 commits June 11, 2026 14:40
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds Commands.start/4 which spawns a HandleServer GenServer owning the
envd Start server-stream via Req into: :self. The server folds events
through Commands.Fold and delivers {ref, _} messages to the subscriber,
blocking start/4 on :await_start until the first envd pid event arrives.
CommandHandle wraps the server pid, ref, envd pid, and context, exposing
pid/1, kill/1, send_stdin/2, close_stdin/1, and disconnect/1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds Commands.start/4 (background) returning a message-streaming
CommandHandle (wait/kill/send_stdin/close_stdin/disconnect/pid), plus
connect/4 reconnect, list/2, and by-pid control RPCs. run/4 unchanged,
refactored onto shared Commands.Fold + Envd.Rpc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a pty branch to Fold.apply_event/2 that decodes the base64 chunk
and emits {:pty, bytes} without accumulating into the CommandResult.
Widens the output/0 type to include :pty. Three new tests cover the
happy path, end-event after pty data, and invalid-base64 error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r{}}

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@col col changed the title Phase 3: PTY (pseudo-terminal) support Phase 3: PTY support + mix e2b.terminal interactive terminal Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant