fix(daemon): hard-exit backstop when a graceful shutdown wedges (#74)
Incident follow-up #2. When cos's `claude --resume` froze on its exit screen,
the daemon was left alive+orphaned (ppid=1) needing kill -9. Cause: cleanShutdown
awaits server.close() then process.exit() with NO overall deadline. close()'s
child-exit wait is internally bounded (~2s), but the outer promise can still hang
indefinitely — socketServer.close()'s callback never fires for a lingering/
untracked socket, or eventWriter.flush() stalls — so the daemon never reaches
process.exit() and lingers forever.
Add a hard deadline (default 5s, PTY_SHUTDOWN_DEADLINE_MS-overridable) armed by
cleanShutdown: if the graceful close() hasn't completed by the deadline, the
daemon force-exits regardless AND SIGKILLs its child (PtyServer.forceKillChild)
so a frozen child isn't left orphaned to init still alive. cleanShutdown is now
idempotent too — SIGTERM/SIGINT/onExit/spawner-watchdog can overlap; only the
first arms the deadline and drives close(), the rest get the same in-flight
promise. Same class as #69/#72 but the frozen-child / stuck-close case.
tests/shutdown-backstop.test.ts: a child that traps SIGHUP wedges the graceful
path -> backstop force-exits the daemon and reaps the child (timing-independent
proof: only the backstop's SIGKILL can kill a SIGHUP-trapping child; the graceful
path only ever sends SIGHUP). A normal session still shuts down promptly, well
under the deadline. All existing lifecycle tests (spawner-watchdog, kill-wait,
exit-event-race, exit-signal, rm-kill-ephemeral, up-down) intact.