mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
`pane_is_free` read the pane's local process tree and folded two very different answers into one `false`. On a pane that is only the near end of a connection that tree describes the tunnel: a pane routed to a remote daemon has no local pty at all, so `DaemonPane::procs` returned `Default::default()` and the tree came back empty; a pane whose shell is running `ssh` has a tree whose depth-1 process is the `ssh` itself, busy for exactly as long as you are logged in. Either way `free` was unreachable structurally, `seen_busy` was set on every poll — which suppressed the one hint that would have pointed at the gap — and the wait rode the whole `--timeout` before recommending `--until free`, the flag that had just failed. Freeness is now three-valued: free, busy, or "nothing here can answer", and the last one carries its reason. On a remote pane freeness is the far shell's own OSC 133 prompt marks. That is sound because the near shell cannot be at a prompt while the connection owns its pty, so a prompt mark on such a pane can only have come from the far side. Two things had to change for the daemon to be able to say it. The reader suppresses relayed prompt marks so a foreground program cannot engage the local line editor — right for the editor, and precisely wrong here — so `ShellState` now keeps the mark's own unsuppressed reading beside the editor's. And "not at a prompt" on a remote pane means nothing until the far shell has proved it reports at all, since the newest mark is otherwise the near shell's own "I started `ssh`", which nothing will ever supersede; a latch records the first prompt mark that arrives while the pane is remote, and is cleared on every hop. `PaneProcs` carries all of this to the CLI in a new optional `context` — remote target, whether this machine holds the pty, the mark's reading, the latch — so the answer still costs one request per poll and an older server, which omits the field, keeps today's tree-only behaviour. When the far host has no shell integration the honest answer is that this machine cannot tell an idle remote prompt from a running remote command. `wait` says so — exit 1, `status: unknown`, a `free_unknown` string naming the host — after one poll of grace for a handshake still in flight, and only when `free` was the only state that could still answer, so `--until done,free` keeps waiting on `done`. Without that it would hang forever on a wait with no `--timeout`. The `no-agent` timeout hint now fires only for a caller who did not already pass `--until free`, and the reason freeness never resolved is printed and put in the JSON in its place. Deliberately left alone: on a local pane the process tree still holds the verdict. A prompt mark can only turn a busy tree into free — which is what fixes a plain `ssh` pane on Windows, where the daemon has no way to name the pane as remote — never the other way round, so no pane that reads busy today can start reading free because an integration went quiet. `free` therefore now means "will take input" rather than strictly "back to the bare shell": a pane sitting at a nested shell's prompt is free, and the reference says so. The handoff record is unchanged, so a pane mid-`ssh` that survives an exec comes back reporting "cannot determine" until the far side's next prompt rather than guessing. Claude-Session: https://claude.ai/code/session_01UUyWQXzcBAoBzaSX8pc7nU