m5/org/openalice-pty

openalice-pty

[Atlas card](https://atlas.blal.pro/repos/openalice-pty)

Features
4
Status
·unknown
Last activity
1mo ago
Branch
mvp

Architecture rating

D1mo ago
consistencyC+

Manifest present+valid (manifest 100), AGENTS.md present, sits in openalice zone with standard docker/Cargo layout; but drift 80 with two env-drift items (RUST_LOG unused, INTERNAL_BROADCAST_SECRET undeclared) keeps it below solid.

docsC

Strong manifest (valid, summary, 3 exposes declared) plus AGENTS.md and roadmap 95, but README is a one-line Atlas-card stub and 19 missing docstrings across the source — thin in-code/architecture prose.

reliabilityD

Clean AST (code_health 100, 0 bare excepts, 0 cycles) and 100% 24h uptime, but one complexity-24 hotspot and 2 god modules; no evidence of SIGTERM graceful shutdown, PTY session reaping, WS read/write timeouts, or idempotency on the control API.

data layerD

Owns no tables, no migrations dir, no DB dep (runtime_deps_count=1) — sessions held in-memory by design. No schema/RLS/FK/GDPR signal to grade; conservative score reflects absence of any persistence layer, not a broken one.

observabilityD

/health probe live (200, matched_path=/health) is the only confirmed signal; RUST_LOG is declared-but-unused (drift), suggesting log config not wired. No metrics endpoint, tracing, or structured-log evidence.

securityD

Spawns real shells over WS — high-risk surface; only signal is INTERNAL_BROADCAST_SECRET shared-secret, and it's undeclared/undrifted (env_drift_undeclared_used). No evidence of rate-limiting, security headers, origin checks, or SSRF/injection guards on the agent control API.

testingF

No tests/ dir and empty migrations/tests presence; inspector reports zero test signal and features=52 flagged as weakest axis. No regression coverage for PTY spawn, WS streaming, or the broadcast-secret auth path.

source · auto-grader-2026-06-16

Ecosystem manifest

.atlas-deps.yml

PTY multiplexer microservice — spawns shell sessions and streams ANSI output to xterm.js over WebSocket.

Runtime deps
  • via cargo
    Shared axum plumbing — verify_internal_secret, CORS helpers, and metrics middleware.
Consumed env
  • INTERNAL_SECRET
  • PTY_PORT
  • DEFAULT_SHELL
  • PTY_RECONNECT_TTL_SECS
  • OA_PTY_SHELLS
  • RUST_LOG
Exposes
  • docker_image: openalice-pty
  • http_endpoint: http://openalice-pty:3995/v1/sessions (agent control API)
  • http_endpoint: ws://openalice-pty:3995/v1/sessions/:id/ws (PTY WebSocket stream)
Env drift · manifest ⇄ code

Atlas grepped the repo's source for env reads (rust env::var · ts process.env · py os.environ · shell $VAR) and diffed against the manifest's consumes_env. Two lists below: stale declarations and undeclared reads.

Code reads, manifest omits
  • INTERNAL_BROADCAST_SECRET
Manifest declares, code unused
  • RUST_LOG
Observed reads: 6 · declared: 6

Roadmap · what's planned next

56% avg4 goals

Code composition

tokei · loc
962loc
  • Rust89.6%
  • YAML4.2%
  • TOML3.3%
  • Dockerfile2.9%
  • Rust89.6%· 862
  • YAML4.2%· 40
  • TOML3.3%· 32
  • Dockerfile2.9%· 28
Language% codecodecommentsblanksfiles
Rust89.6%86293832
YAML4.2%401521
TOML3.3%3216131
Dockerfile2.9%2816131
Markdown0.0%090412

Telemetry ribbon · last 90 days

3 events
MAYJUNJUL2026-05-21 · commit · new commit e46fa3d → 27b88202026-05-22 · commit · new commit 27b8820 → 73b79682026-06-16 · commit · new commit 73b7968 → 9285a57NOW
commitmanifestlifecyclekind

pty/control

1 feature
  • pty.control.http_dispatcher
    src/main.rs:233
    Agent-side stdin path: POST /v1/sessions/{id}/input accepts base64-encoded bytes so the openalice-live-control dispatcher can drive a PTY session entirely over HTTP without holding a WebSocket open. Agents and human xterm.js clients share the same Session::write path — the running TUI cannot distinguish keystrokes from the two sources. `POST /v1/sessions/{id}/input` — write bytes to the PTY master. Body: `{"bytes_b64": "<base64>"}`. Decoded bytes flow into `Session::write` exactly like a WS Binary frame would — agents and humans share the same stdin path, so a TUI can't tell whether keystrokes came from xterm.js or the dispatcher. Returns 204 on success, 404 when the session is missing, 400 on malformed base64, 500 on a PTY write error. [derive(Deserialize)]

pty/session

2 features
  • pty.session.broadcast
    src/session.rs:289
    Drains the PTY master on a dedicated blocking thread and fans every byte chunk out over a tokio::sync::broadcast channel (capacity 1024). Frames with no active subscribers are silently dropped so the child process never stalls waiting on a full pipe buffer. Blocking read loop: pull bytes off the PTY master and broadcast them. Exits cleanly when the master returns EOF (child exited and its stdout closed).
  • pty.session.spawn
    src/session.rs:152
    Spawn a child process inside a real PTY pair (openpty on Unix, ConPTY on Windows via portable-pty). The blocking read loop starts immediately on a dedicated std::thread; callers get an Arc<Session> ready for WebSocket subscription without any further setup. Spawn a new PTY + child process. On success the read pump is already running on a blocking thread; subscribers attach via `stdout_tx.subscribe()`.

pty/transport

1 feature
  • pty.transport.websocket
    src/main.rs:504
    Bidirectional WebSocket handler: outbound pump broadcasts PTY stdout as Binary frames (xterm.js calls Terminal.write() directly on the bytes); inbound pump routes Binary frames to PTY stdin and Text frames to the ControlFrame dispatcher (resize / signal / ping). A 60-second reconnect TTL keeps the session alive after the client drops, enabling seamless browser refreshes without killing the running process. Drive a single client connection: subscribe to PTY stdout broadcast and forward chunks; receive client frames and dispatch them to the PTY (binary → stdin; text → control). Reconnect TTL: when the last subscriber goes away, the session is *not* killed immediately. A delayed cleanup task fires after `reconnect_ttl`; if no new subscriber attaches by then, the session is removed and the child killed.