10d2fc5ac1
Phase 1 PTY substrate per PHASE1.md §5. Replaces Phase 0's io.popen sentinel-echo path with a real PTY so interactive cmds (vim, less, htop) work and exit-status comes from waitpid instead of parsing a sentinel out of stdout. API: pty.spawn(cmd) -> session | (nil, err) session:read(count) -> (data, n) ; n == 0 means EOF session:write(data) -> bytes session:close() ; closes master_fd; child gets SIGHUP session:wait(options) -> (kind, val) ; "exit"/"signal"/"other"/nil session:signal(sig) -> ok ; kill(pid, sig) Child branch execs `/bin/sh -c cmd`, preserving Phase 0's shell- interpretation semantics (quoting, redirection, pipes still work). The PTY makes vim/less/htop functional because the child gets a real tty for line discipline instead of a pipe. Loader uses the versioned-soname fallback idiom (util / util.so.1 / util.so.0) so a runtime-only host without libutil-dev works. Smoke covers: echo hello (exit 0), false (1), exit 7, bogus binary (sh's 127), multi-line printf, cat bidirectional (write ping -> read echo+cat output -> close master -> child exits via SIGHUP). Next: executor.lua swap from popen+sentinel to pty.spawn. That commit also retires the §7 amendment paragraph (no longer needed once popen is gone). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>