Files
aish/ffi
marfrit 10d2fc5ac1 ffi/pty: forkpty-backed spawn + session handle
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>
2026-05-10 19:03:19 +00:00
..