Nesting prevention: refuse client-inside-a-client for attach,
interactive, restart-then-attach, and run -a when target is running
Request from pty-layout-claude: every pty-layout pane runs a shell
with PTY_SESSION set, so commands that silently started a nested pty
client routed detach keybindings to the outer client and left users
tangled with no clean way to exit.
New helper ensureNotNested(cmd, { force?, hint? }) in cli.ts. Called
from:
- pty attach / pty a / pty attach -r: refuse early (before
resolveRef) so even a mistyped ref yields the nesting hint instead
of a "not found" error. Hint mentions Ctrl+\\ to detach and pty-
layouts ^]n picker.
- pty restart <ref>: restart still runs (its independent of attach),
but the trailing doAttach is skipped and a one-line "(not attached:
already inside pty session X)" notice prints. -y / --yes still
skips the kill-confirm prompt; --force restores restart+attach.
- pty / pty i / pty interactive: refuse to open the TUI picker;
picker rendering inside a session with broken detach was the worst
footgun of the bunch.
- pty run -a: narrows the existing "already inside pty session,
running directly" behavior — if the target is already running,
refuse rather than silently dropping -a. Target-not-running case
keeps the run-directly behavior; -a is a no-op in that situation
anyway.
--force across all four to opt back into the old behavior (nested
clients are occasionally intentional: debugging, screen-sharing
demos).
Latent test-harness bug surfaced by the guard: Session.spawn
(@myobie/pty/testing) was leaking the harness's own PTY_SESSION into
the child env, so every interactive-TUI test in tui.test.ts tripped
the new guard. Scrub PTY_SESSION alongside the existing
PTY_SERVER_CONFIG scrubbing so Session.spawn simulates a clean user
shell regardless of what the harness is running inside.
list & gc ergonomics: vanished status, --summary/--status/age filters, gc --dry-run (closes #21)
Four changes, one PR:
1. Third session status `vanished` for the "daemon is gone, no exit
record written" case (SIGKILL / OOM / crash). Listed in its own
yellow-headered bucket with a warning icon so it doesn't blend into
cleanly-exited sessions. Reapable by `pty gc` like any other dead
session. TTL anchor falls back to createdAt so they can't accumulate
indefinitely.
2. `pty list --summary` (+ `--json --summary`) — compact counts and
oldest/newest pointers, respects all other filters.
3. `pty list --status <state>` and `--older-than/--newer-than <Ns|Nm|Nh|Nd>`
filters. Compose with `--filter-tag` and `--summary`. Grammar is
deliberately single-unit (no `1h30m`) to keep --help trivial.
4. `pty gc --dry-run` (`-n`) — preview what would be removed without
mutating anything. Covers exited AND vanished sessions AND orphan
`:l<pid>-<rand>` layout tags in one pass.
Client API:
- gc({ dryRun }), pruneOrphanLayoutTags({ dryRun })
- New `isGone(status)` helper — replaces hand-rolled
`status === "exited"` checks that should have included vanished all
along. Swept existing callers (run -a, peek fallback, pty up/down
cleanup, stats --all) to use it.
- New parseDuration/formatDuration exports for downstream tools.
Tests:
- duration.test.ts — full grammar coverage.
- list-filters.test.ts — vanished inference, --status, age filters,
--summary (text + json), filter composition.
- gc.test.ts — extended with --dry-run and vanished-session reaping.
726 → 728 local tests pass (+37 new), 0 failures.
Document the on-disk layout under docs/disk-layout.md
For non-Node consumers that want to read pty's session state directly
without paying Node CLI startup. Covers PTY_SESSION_DIR, the file
table with stability tiers (tier 1 = <name>.json, .events.jsonl;
tier 2 = .sock, .pid, .lock, theme, supervisor/), the SessionMetadata
TS shape, the events JSONL envelope and every concrete event type,
and the <target>.tmp.<pid>.<rand> atomic-write convention with an
explicit "third-party readers MUST ignore these" note.
Pre-1.0 banner: schema may change in any release; pin to a pty
version. CHANGELOG carries a "### Storage format" heading whenever
a tier-1 file shape changes.
Drift guard: tests/disk-layout-docs.test.ts asserts every
SessionMetadata field name, every concrete event-type literal, and
every file extension we write under PTY_SESSION_DIR appears in the
doc. SessionMetadata and EventBase carry "PUBLIC FORMAT" comment
markers pointing at the doc + the smoke test.
README's Events section gains a one-paragraph pointer to the doc and
mentions git-style command forwarding (pty <subcommand> ->
pty-<subcommand> on $PATH) as the recommended path for native
fast-path readers.
Add supervisor, mutable tags, peek --wait/--full, and hardening
Session supervisor watches strategy tags and restarts permanent
sessions with exponential backoff. Runs as a foreground process,
integrates with launchd via esbuild bundling. Mutable tags via
pty tag command. pty kill/down properly handle supervised sessions.
Also: peek --wait blocks until text appears, peek --full shows
scrollback, events --wait blocks for event types, interactive TUI
shows [permanent] markers with color, 10s supervisor scan interval,
supervisor state in its own subdirectory, defensive meta.args
handling, and isolated shell test sessions.