SPA attach had no reconnect at all. Any dropped frame closed the socket, `onClosed` fired, and the pane was destroyed permanently — the TUI reconnects forever on the same endpoint. Session death also reached the UI on two uncoordinated channels: the row from the events socket, the pane from `ws.onclose`, with nothing reconciling them, so either could happen without the other. The catalog is now the authority. `shouldRetryClose` decides from session status: Running means the link dropped, so retry with 500ms-to-10s backoff; Dead means the PTY stream was sealed and nothing more is coming; absent means it is gone. Application closes (4000+, a relayed daemon rejection) and clean closes are never retried. The shell also closes any pane whose session has left the catalog. The policy lives in reconnect.ts so it is tested without a DOM. kill_session checked `has()` before `kill()`, which only opened a window for the liveness watcher to reap in between — turning a 404 into a 502. `kill` reports the miss itself. with_spawn_history logged and carried on when `Arc::get_mut` failed, producing a daemon whose recent-dirs picker is permanently empty with nothing to show for it. It panics now: that is a construction-order mistake, at startup, in code that runs once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
33 lines
1.2 KiB
TypeScript
33 lines
1.2 KiB
TypeScript
// Attach-socket reconnect policy. Split out of attach.ts so it can be tested
|
|
// without a DOM or an xterm instance.
|
|
|
|
import type { Session, Uuid } from "./types.js";
|
|
|
|
/** First retry delay after an unexpected close, doubling up to the cap. */
|
|
export const INITIAL_RECONNECT_MS = 500;
|
|
export const MAX_RECONNECT_MS = 10_000;
|
|
|
|
/** Close codes at or above this are application-defined; `maestro-web` uses
|
|
* them to relay a daemon rejection, which retrying would only loop on. */
|
|
const APPLICATION_CLOSE = 4000;
|
|
|
|
/** Normal Closure — the peer ended the stream deliberately. */
|
|
const CLEAN_CLOSE = 1000;
|
|
|
|
/**
|
|
* Should a closed attach socket be retried?
|
|
*
|
|
* The catalog is the authority: a session still `Running` lost its link, so
|
|
* retry; one that is `Dead` has had its PTY stream sealed and will never send
|
|
* again, and one absent from the catalog is gone. Before this existed, any
|
|
* dropped frame destroyed the pane permanently.
|
|
*/
|
|
export function shouldRetryClose(
|
|
sessionId: Uuid,
|
|
code: number,
|
|
sessions: ReadonlyMap<Uuid, Session>,
|
|
): boolean {
|
|
if (code >= APPLICATION_CLOSE || code === CLEAN_CLOSE) return false;
|
|
return sessions.get(sessionId)?.status === "Running";
|
|
}
|