Keep session manager open on launch failure

This commit is contained in:
2026-09-12 18:25:39 +00:00
parent e5c5c3015f
commit d6697d46f8
5 changed files with 217 additions and 42 deletions
+95 -29
View File
@@ -17,6 +17,7 @@ import subprocess
import sys
import tempfile
import threading
import time
from typing import Callable, Iterable, Mapping, Sequence
from urllib.parse import quote
@@ -986,21 +987,72 @@ class TmuxHub:
return self.window_for_key(endpoint.key) or replaced
@serialized_hub_mutation
def activate_endpoint(self, endpoint: Endpoint, herdr_bin: str) -> ManagedWindow:
def activate_endpoint(
self,
endpoint: Endpoint,
herdr_bin: str,
*,
verify_startup: bool = False,
) -> ManagedWindow:
window = self.window_for_key(endpoint.key)
started = False
if window is None:
window = self.create_window(endpoint, herdr_bin)
self.remove_bootstrap()
started = True
elif window.endpoint is not None and (
window.endpoint.remote_transport != endpoint.remote_transport
or window.endpoint.remote_keybindings != endpoint.remote_keybindings
):
window = self.replace_endpoint(window, endpoint, herdr_bin)
started = True
elif window.dead:
self.reconnect(window.window_id)
started = True
if verify_startup and started:
window = self.wait_for_window_startup(window.window_id)
self.select_window(window.window_id)
return window
def wait_for_window_startup(
self,
window_id: str,
*,
timeout: float = 0.4,
) -> ManagedWindow:
"""Catch commands that exit immediately before presenting their UI."""
deadline = time.monotonic() + timeout
while True:
window = next(
(
candidate
for candidate in self.managed_windows()
if candidate.window_id == window_id
),
None,
)
if window is None:
raise HubError("Session Tab 在启动过程中消失")
if window.dead:
capture = self.run(
["capture-pane", "-p", "-t", window_id, "-S", "-20"],
check=False,
)
lines = [
line.strip()
for line in (capture.stdout or "").splitlines()
if line.strip()
]
detail = " · ".join(lines[-3:])
message = "Session 进程启动后立即退出"
if detail:
message += f":{detail}"
raise HubError(message)
remaining = deadline - time.monotonic()
if remaining <= 0:
return window
time.sleep(min(0.05, remaining))
def attach(self) -> None:
if inside_any_tmux():
if inside_socket(self.socket_name):
@@ -1292,6 +1344,46 @@ def command_choose(arguments: argparse.Namespace) -> int:
]
return StopSessionOutcome(tuple(updated), tab_closed, warning)
def activate_selection(result: SelectorResult) -> None:
if result.kind == "window":
assert result.window_id is not None
if result.dead:
hub.reconnect(result.window_id)
hub.wait_for_window_startup(result.window_id)
hub.select_window(result.window_id)
return
if result.kind == "local":
assert result.session is not None
hub.activate_endpoint(
Endpoint.local(result.session),
resolve_herdr(),
verify_startup=True,
)
return
assert result.target is not None and result.session is not None
endpoint = Endpoint.remote(
result.target,
result.session,
result.remote_keybindings,
result.remote_transport,
)
try:
remote_registry.upsert(
RemoteEntry(
endpoint.target,
endpoint.remote_keybindings,
endpoint.remote_transport,
)
)
except RegistryError as error:
hub.display_message(f"herdr-hub: remote registry: {error}")
hub.activate_endpoint(
endpoint,
resolve_herdr(),
verify_startup=True,
)
try:
selector = SessionSelector(
catalog_windows(windows),
@@ -1299,6 +1391,7 @@ def command_choose(arguments: argparse.Namespace) -> int:
ssh_bin,
local_sessions=(endpoint.session for endpoint in local_report.endpoints),
aliases=hub.aliases,
activate_handler=activate_selection,
rename_handler=rename_session,
close_window_handler=close_window,
delete_session_handler=delete_session,
@@ -1326,34 +1419,7 @@ def command_choose(arguments: argparse.Namespace) -> int:
assert result.window_id is not None
hub.dismiss_window(result.window_id)
return 0
if result.kind == "window":
assert result.window_id is not None
if result.dead:
hub.reconnect(result.window_id)
hub.select_window(result.window_id)
return 0
if result.kind == "local":
assert result.session is not None
hub.activate_endpoint(Endpoint.local(result.session), resolve_herdr())
return 0
assert result.target is not None and result.session is not None
endpoint = Endpoint.remote(
result.target,
result.session,
result.remote_keybindings,
result.remote_transport,
)
try:
remote_registry.upsert(
RemoteEntry(
endpoint.target,
endpoint.remote_keybindings,
endpoint.remote_transport,
)
)
except RegistryError as error:
hub.display_message(f"herdr-hub: remote registry: {error}")
hub.activate_endpoint(endpoint, resolve_herdr())
activate_selection(result)
return 0