Agent-First Terminal v0.3.0
A program that stops reading its input could freeze every other session, a client that stops reading events could freeze the server, and a supervisor's SIGTERM was not a stop. Writes, subscriptions and shutdown are now bounded by their own owner, an SSE resume says when history is missing, and a secret window no longer leaves its screen behind.
A terminal is two programs that each assume the other keeps up. The one inside the PTY assumes someone reads its output; the one outside assumes its input gets read. afterminal sits in the middle, and this release is about what happens in the middle when either side simply stops.
A program that stops reading
A PTY has a small kernel buffer. When the program inside stops reading — a paused process, a full-screen app busy elsewhere, a shell waiting on something — a large write blocks until it does.
That write ran on an async worker, while holding the lock that coordinates every
session. So one session’s stuck input could stop screen, close and every
other call for every other session, and the UI with them.
Each chunk is now authorised under the manager lock and then written outside it, under only that session’s writer lock, on a blocking task rather than an async worker. The stock UI keeps processing events and actions for other sessions while an input write is pending. A later lease change does not cancel a chunk whose write was already authorised.
That opened one boundary that had to be closed again: a secret-input window
could start or end while a chunk was still being written, moving the line
between ordinary and private input. Starting or ending a window in that interval
now returns a retryable input_in_progress.
A client that stops reading
Event subscriptions — raw output and terminal events — had unbounded queues. A client that connected and stopped reading made the server buffer for it forever, and shutdown could wait on a producer blocked behind it.
Each subscription now holds 128 entries. When it fills, that subscriber is disconnected; the fast ones keep going. The stock UI resubscribes and publishes the full current state, so a dropped page does not stay on an older screen. On shutdown, event responses end before the HTTP server drains, which also releases a producer blocked on a client that stopped reading.
Resuming says when history is missing
The SSE stream keeps the newest 512 events. A client resuming with
Last-Event-ID used to get whatever was retained after its cursor, which is
indistinguishable from complete history when the cursor had already fallen out
of the window.
A cursor that cannot resume — invalid, older than the retained window, or ahead
of the latest sequence — now first receives a named replay_gap event with the
reason and the retained bounds, then the whole retained backlog. The notice has
no SSE id, so it does not move the client’s cursor, and ordinary events are
unchanged. A client that sees it should reread the session list and screens;
the retained events cannot reconstruct what is gone.
A stop is a stop
afterminal serve stopped on Ctrl-C and nothing else. A supervisor sending
SIGTERM, or a closed terminal sending SIGHUP, did not get a clean
shutdown. Both are handled now, and the handlers are registered before the
ready event is printed, so a supervisor that signals as soon as it sees
readiness still gets one.
Exit state became something afterminal keeps rather than reconstructs. On Windows, ConPTY can hold its output pipe open after the program exits, so an exit was only noticed at EOF; status polling now publishes it without one. An exit after a reader error is announced once, and a known exit code is never replaced by none. Waiting for that code also no longer holds the session’s child lock, which had stalled every API call on that session for up to 250 ms.
Teardown releases what it owns. A child that is being terminated is reaped even if the signal races its exit, PTY descriptors are acquired before a child exists so an exhausted descriptor table fails cleanly instead of leaving a spawned process behind, and a reader is woken on close even when another process still holds the PTY open.
A secret window leaves nothing behind
Closing a secret-input window used to make the screen visible again exactly as it was. If the program echoed what was typed, the secret was on the screen the agent was about to read.
After a secret window closes, the screen starts blank and contains only new output. Application cursor, bracketed paste and alternate-screen modes are preserved; the picture is not. Ask the program to redraw — Ctrl-L, usually — when a full screen is needed.
Smaller things
On Windows, only kill is supported, and it terminates the process started for
the session rather than a foreground process group. interrupt and terminate
return signal_not_supported, and the docs say so.
cargo install agent-first-terminal now builds the same CLI the release
packages ship: the api feature used to be release-only, and is in the default
set now. Library users that want only the core take default-features = false.
--version reports the build it came from, as every other spore’s does. The
attach example takes the session id positionally, as the CLI does, and the
skill reads quiescence from the screen snapshot instead of waiting for an
event that does not exist.
Breaking changes
- A slow event subscriber is disconnected when its 128-entry queue fills. Reconnect and reread state.
- A resume that cannot be honoured starts with
replay_gap. Handle the named event. - Opening or closing a secret window during a write returns
input_in_progress. Retry after the write completes. - The screen starts blank after a secret window closes.
- On Windows,
interruptandterminatereturnsignal_not_supported. apiis a default feature. Library users takedefault-features = false.- Agent-First UI 0.6.1 and Agent-First Data 0.35.0 are the pinned dependencies.
Getting it
$ brew install agentfirstkit/tap/afterminal
$ cargo install agent-first-terminal