* fix(ssh): recover instead of wedging when the relay channel dies mid-connect
Three coupled defects made a dropped SSH relay look like a permanent bug:
1. SshRelaySession.establish()/reconnect() ran their last liveness gate before
configureRelayGraceTime(), whose mux.notify() can dispose the mux
synchronously (writer control-lane admission cap, or a throwing transport).
The session then latched _state='ready' + _onReady (status bar "connected")
while watchMuxForRelayLoss() silently no-op'd on the dead mux, so the bounded
relay backoff in ipc/ssh.ts never ran and the fs/pty/git providers stayed
registered against a dead multiplexer. Both sites now re-check
mux.isDisposed() after the notify and take the existing failure path.
2. SshChannelMultiplexer.request()/notifyWithSettlement() always reported the
permanent-shutdown string 'Multiplexer disposed' with no code, even when the
recorded dispose reason was connection_lost. The reason is now recorded and a
shared disposedError() factory serves dispose(), request(),
notifyWithSettlement(), so a transient drop reports
'SSH connection lost, reconnecting...' / CONNECTION_LOST. onDispose() on an
already-disposed mux now fires the handler synchronously with that reason
instead of returning a silent no-op (without retaining it).
3. TerminalErrorToast no longer renders a transient relay drop in the red
"please file an issue" style. The marker is matched with includes() because
the message reaches the toast IPC-wrapped.
ssh-git-response-stream-reader registers its onDispose subscriber after the
abort wiring, since an already-dead mux now fails synchronously there and the
cleanup must be able to drop the caller's abort listener.
Closes#11953
* fix(ssh): treat a mux killed during PTY reattach as relay loss
reconnect()'s post-reattach gate bare-returned when ownsAttempt() went false,
and reattachKnownPtys swallows every per-PTY error, so a control-lane failure
during a large reattach burst disposed the mux without ever reaching the catch:
providers stayed bound to the dead mux, no relay-loss watcher was installed, and
the session wedged in 'reconnecting' until restart. Take the failure path when
our own mux is the one that died so ssh.ts's bounded backoff retries.
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): recover when relay dies during setup instead of wedging
Introduce verifyRelayAttempt() to detect mux disposal at each setup phase
(consumer session, home resolution, provider registration, PTY reattach).
Routes mid-setup connection loss into relay-loss recovery instead of
hanging in reconnecting state.
* Extract SSH disposal error factory
Multiple sites were duplicating the disposal error creation logic with
specific message and code values. The renderer uses these to distinguish
temporary disconnects (show reconnection overlay) from permanent shutdown
(show error toast), so all producers must use the same factory to avoid
silent UI degradation.
---------
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>