railmux 0.4.0.dev22__tar.gz → 0.4.0.dev23__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {railmux-0.4.0.dev22/src/railmux.egg-info → railmux-0.4.0.dev23}/PKG-INFO +1 -1
- railmux-0.4.0.dev23/src/railmux/__init__.py +1 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fast_display_client.py +142 -2
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fast_display_server.py +5 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_fast_full_window.py +187 -1
- railmux-0.4.0.dev22/src/railmux/__init__.py +0 -1
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/LICENSE +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/MANIFEST.in +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/README.md +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/pyproject.toml +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/setup.cfg +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/__main__.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/atomic_file.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/background_index.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/cli.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/codex_index.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/config.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/config_cli.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/diagnostics.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/discovery.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/display_transport.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/entrypoint.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/favorites.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fuzzy.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/help_workspace.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/launcher.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/local_open.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/models.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/modes.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/pane_surface.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/path_codec.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/provider_paths.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/remote_config.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/renames.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/restart_state.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/runtime_config.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/self_update.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/session_cache.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/session_index.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/session_lease.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/settings.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ssh_args.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/system_deps.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/terminal_status.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/tmux_health.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/tmux_server.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/tool_panes.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/transcript.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/app.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/modals.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_attach_relay.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_bootstrap.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_install_log.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_msys2.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_pacman.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux.egg-info/SOURCES.txt +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_agent_workspace.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_atomic_file.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_background_index.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_cli.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_codex_delete.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_codex_index.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_codex_lineage.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_codex_transcript.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_config.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_config_cli.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_diagnostics.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_discovery.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_display_transport.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_focus_highlight.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_fuzzy.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_help_agent.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_help_workspace.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_history_cleanup.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_keymap.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_launcher.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_layout_profile.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_local_clipboard.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_local_open.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_modals.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_mode_selection.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_modes.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_mouse_manager.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_new_project.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_options.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_pane_surface.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_path_codec.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_projects_pane.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_provider_paths.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_release_notes.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_release_workflow.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_remote_config.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_renames.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_restart_state.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_running_pane.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_running_sort.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_runtime_config.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_scroll_agent.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_scroll_manager.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_selection_isolation.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_self_update.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_session_cache.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_session_index.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_session_lease.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_sessions_pane.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_setting_contracts.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_settings.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_sidebar.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_soft_quit.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_ssh_args.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_ssh_compat.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_status.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_statusbar.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_system_deps.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_terminal_size.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_terminal_status.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_health.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_integration.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_server.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_status.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tool_panes.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_transcript.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_utf8_state.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_widgets.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_attach_relay.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_bootstrap.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_install_log.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_msys2.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_pacman.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_wrapper_contract.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tools/release_notes.py +0 -0
- {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tools/wheel_smoke.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0.dev23"
|
|
@@ -281,6 +281,7 @@ class RemoteHello:
|
|
|
281
281
|
config_status: str = "valid"
|
|
282
282
|
tmux_configured: bool = False
|
|
283
283
|
config_protocol: int = 0
|
|
284
|
+
platform: str = "posix"
|
|
284
285
|
|
|
285
286
|
|
|
286
287
|
@dataclass(frozen=True)
|
|
@@ -296,6 +297,11 @@ class RemoteStartKind(Enum):
|
|
|
296
297
|
TIMEOUT = "timeout"
|
|
297
298
|
|
|
298
299
|
|
|
300
|
+
class RemoteLaunchMode(Enum):
|
|
301
|
+
POSIX = "posix"
|
|
302
|
+
DIRECT = "direct"
|
|
303
|
+
|
|
304
|
+
|
|
299
305
|
class RemoteAttachKind(Enum):
|
|
300
306
|
ACCEPTED = "accepted"
|
|
301
307
|
BUSY = "busy"
|
|
@@ -369,6 +375,7 @@ def parse_remote_hello(line: bytes) -> RemoteHello:
|
|
|
369
375
|
config_status = value.get("config_status", "valid")
|
|
370
376
|
tmux_configured = value.get("tmux_configured", False)
|
|
371
377
|
config_protocol = value.get("config_protocol", 0)
|
|
378
|
+
platform = value.get("platform", "posix")
|
|
372
379
|
if (
|
|
373
380
|
not isinstance(version, str)
|
|
374
381
|
or not version
|
|
@@ -383,6 +390,7 @@ def parse_remote_hello(line: bytes) -> RemoteHello:
|
|
|
383
390
|
or not isinstance(config_protocol, int)
|
|
384
391
|
or isinstance(config_protocol, bool)
|
|
385
392
|
or not 0 <= config_protocol <= 65535
|
|
393
|
+
or platform not in {"posix", "windows-msys2"}
|
|
386
394
|
):
|
|
387
395
|
raise ValueError("invalid Railmux remote hello")
|
|
388
396
|
try:
|
|
@@ -397,6 +405,7 @@ def parse_remote_hello(line: bytes) -> RemoteHello:
|
|
|
397
405
|
config_status,
|
|
398
406
|
tmux_configured,
|
|
399
407
|
config_protocol,
|
|
408
|
+
platform,
|
|
400
409
|
)
|
|
401
410
|
|
|
402
411
|
|
|
@@ -1540,12 +1549,18 @@ def _remote_launch_command(server_args: Sequence[str]) -> str:
|
|
|
1540
1549
|
return "; ".join(branches)
|
|
1541
1550
|
|
|
1542
1551
|
|
|
1552
|
+
def _remote_direct_launch_command(server_args: Sequence[str]) -> str:
|
|
1553
|
+
"""Build a shell-neutral command accepted by Windows OpenSSH PowerShell."""
|
|
1554
|
+
return shlex.join(["railmux", *server_args])
|
|
1555
|
+
|
|
1556
|
+
|
|
1543
1557
|
def build_remote_command_argv(
|
|
1544
1558
|
destination: str,
|
|
1545
1559
|
*,
|
|
1546
1560
|
remote_args: Sequence[str],
|
|
1547
1561
|
ssh_args: Sequence[str],
|
|
1548
1562
|
force_tty: bool = False,
|
|
1563
|
+
launch_mode: RemoteLaunchMode = RemoteLaunchMode.POSIX,
|
|
1549
1564
|
) -> list[str]:
|
|
1550
1565
|
"""Build SSH argv through the shared remote Railmux discovery ladder."""
|
|
1551
1566
|
return [
|
|
@@ -1556,7 +1571,11 @@ def build_remote_command_argv(
|
|
|
1556
1571
|
# user arguments so a copied -t/-T flag cannot invert the transport.
|
|
1557
1572
|
"-tt" if force_tty else "-T",
|
|
1558
1573
|
destination,
|
|
1559
|
-
|
|
1574
|
+
(
|
|
1575
|
+
_remote_direct_launch_command(remote_args)
|
|
1576
|
+
if launch_mode is RemoteLaunchMode.DIRECT
|
|
1577
|
+
else _remote_launch_command(remote_args)
|
|
1578
|
+
),
|
|
1560
1579
|
]
|
|
1561
1580
|
|
|
1562
1581
|
|
|
@@ -1570,6 +1589,7 @@ def build_ssh_argv(
|
|
|
1570
1589
|
ssh_args: Sequence[str],
|
|
1571
1590
|
replace_existing_client: bool = False,
|
|
1572
1591
|
existing_session_only: bool = False,
|
|
1592
|
+
launch_mode: RemoteLaunchMode = RemoteLaunchMode.POSIX,
|
|
1573
1593
|
) -> list[str]:
|
|
1574
1594
|
server_args = _remote_server_args(
|
|
1575
1595
|
session=session,
|
|
@@ -1592,6 +1612,7 @@ def build_ssh_argv(
|
|
|
1592
1612
|
"-o",
|
|
1593
1613
|
"ServerAliveCountMax=3",
|
|
1594
1614
|
),
|
|
1615
|
+
launch_mode=launch_mode,
|
|
1595
1616
|
)
|
|
1596
1617
|
|
|
1597
1618
|
|
|
@@ -1757,6 +1778,43 @@ def remote_install_help(destination: str, version: str) -> str:
|
|
|
1757
1778
|
)
|
|
1758
1779
|
|
|
1759
1780
|
|
|
1781
|
+
def remote_windows_install_help(destination: str, version: str) -> str:
|
|
1782
|
+
return (
|
|
1783
|
+
f"Update the user-level Windows preview on {destination} from "
|
|
1784
|
+
"PowerShell, then retry:\n"
|
|
1785
|
+
f" py -m pip install --upgrade railmux=={version}\n"
|
|
1786
|
+
" railmux runtime install --yes\n"
|
|
1787
|
+
"The runtime command installs only the matching Railmux app layer and "
|
|
1788
|
+
"reuses a verified managed MSYS2 base when its pinned version is "
|
|
1789
|
+
"unchanged. It does not modify Codex or Claude session files. If 'py' "
|
|
1790
|
+
"is unavailable, use the same Windows Python executable that installed "
|
|
1791
|
+
"the Railmux command."
|
|
1792
|
+
)
|
|
1793
|
+
|
|
1794
|
+
|
|
1795
|
+
def _remote_launch_mode(args: argparse.Namespace) -> RemoteLaunchMode:
|
|
1796
|
+
selected = getattr(args, "_selected_remote_launch_mode", None)
|
|
1797
|
+
if isinstance(selected, RemoteLaunchMode):
|
|
1798
|
+
return selected
|
|
1799
|
+
if getattr(args, "remote_platform", "auto") == "windows":
|
|
1800
|
+
return RemoteLaunchMode.DIRECT
|
|
1801
|
+
return RemoteLaunchMode.POSIX
|
|
1802
|
+
|
|
1803
|
+
|
|
1804
|
+
def _remember_remote_launch_mode(
|
|
1805
|
+
args: argparse.Namespace, mode: RemoteLaunchMode
|
|
1806
|
+
) -> None:
|
|
1807
|
+
args._selected_remote_launch_mode = mode
|
|
1808
|
+
|
|
1809
|
+
|
|
1810
|
+
def _remote_uses_windows_runtime(
|
|
1811
|
+
args: argparse.Namespace, hello: RemoteHello | None = None
|
|
1812
|
+
) -> bool:
|
|
1813
|
+
return _remote_launch_mode(args) is RemoteLaunchMode.DIRECT or (
|
|
1814
|
+
hello is not None and hello.platform == "windows-msys2"
|
|
1815
|
+
)
|
|
1816
|
+
|
|
1817
|
+
|
|
1760
1818
|
def remote_tmux_help(destination: str) -> str:
|
|
1761
1819
|
return (
|
|
1762
1820
|
f"tmux is not installed or not on PATH on {destination}. Install it "
|
|
@@ -1989,6 +2047,7 @@ def _reconnect_remote_attach(
|
|
|
1989
2047
|
ssh_args=ssh_args,
|
|
1990
2048
|
replace_existing_client=replace_existing_client,
|
|
1991
2049
|
existing_session_only=existing_session_only,
|
|
2050
|
+
launch_mode=_remote_launch_mode(args),
|
|
1992
2051
|
)
|
|
1993
2052
|
process = (
|
|
1994
2053
|
_spawn_remote(argv, suppress_stderr=True)
|
|
@@ -2215,6 +2274,7 @@ def prepare_remote_process(
|
|
|
2215
2274
|
if before_interaction is not None:
|
|
2216
2275
|
before_interaction()
|
|
2217
2276
|
|
|
2277
|
+
launch_mode = _remote_launch_mode(args)
|
|
2218
2278
|
argv = build_ssh_argv(
|
|
2219
2279
|
args.destination,
|
|
2220
2280
|
session=args.session,
|
|
@@ -2222,11 +2282,62 @@ def prepare_remote_process(
|
|
|
2222
2282
|
height=current_size.lines,
|
|
2223
2283
|
fps=args.fps,
|
|
2224
2284
|
ssh_args=args.ssh_arg,
|
|
2285
|
+
launch_mode=launch_mode,
|
|
2225
2286
|
)
|
|
2226
2287
|
if on_stage is not None:
|
|
2227
2288
|
on_stage("Connecting to remote host…")
|
|
2228
2289
|
process = _spawn_remote(argv)
|
|
2229
2290
|
startup = await_remote_startup(process)
|
|
2291
|
+
if (
|
|
2292
|
+
getattr(args, "remote_platform", "auto") == "auto"
|
|
2293
|
+
and launch_mode is RemoteLaunchMode.POSIX
|
|
2294
|
+
and startup.kind is RemoteStartKind.FAILED
|
|
2295
|
+
and startup.returncode != 255
|
|
2296
|
+
):
|
|
2297
|
+
_stop_unstarted_remote(process)
|
|
2298
|
+
direct_argv = build_ssh_argv(
|
|
2299
|
+
args.destination,
|
|
2300
|
+
session=args.session,
|
|
2301
|
+
width=current_size.columns,
|
|
2302
|
+
height=current_size.lines,
|
|
2303
|
+
fps=args.fps,
|
|
2304
|
+
ssh_args=args.ssh_arg,
|
|
2305
|
+
launch_mode=RemoteLaunchMode.DIRECT,
|
|
2306
|
+
)
|
|
2307
|
+
process = _spawn_remote(direct_argv)
|
|
2308
|
+
startup = await_remote_startup(process)
|
|
2309
|
+
if startup.kind is RemoteStartKind.HELLO:
|
|
2310
|
+
launch_mode = RemoteLaunchMode.DIRECT
|
|
2311
|
+
_remember_remote_launch_mode(args, launch_mode)
|
|
2312
|
+
else:
|
|
2313
|
+
_stop_unstarted_remote(process)
|
|
2314
|
+
if startup.returncode == 255:
|
|
2315
|
+
raise ProbeError("ssh could not connect to the remote host")
|
|
2316
|
+
if startup.kind is RemoteStartKind.TIMEOUT:
|
|
2317
|
+
raise ProbeError(
|
|
2318
|
+
"timed out while checking the remote host's direct Railmux launcher"
|
|
2319
|
+
)
|
|
2320
|
+
raise ProbeError(
|
|
2321
|
+
"the remote shell did not accept the POSIX discovery command, "
|
|
2322
|
+
"and a direct Railmux launch did not complete. If this is a "
|
|
2323
|
+
"Windows preview host:\n"
|
|
2324
|
+
f"{remote_windows_install_help(args.destination, __version__)}"
|
|
2325
|
+
)
|
|
2326
|
+
elif startup.kind is RemoteStartKind.HELLO:
|
|
2327
|
+
_remember_remote_launch_mode(args, launch_mode)
|
|
2328
|
+
|
|
2329
|
+
if (
|
|
2330
|
+
getattr(args, "remote_platform", "auto") == "windows"
|
|
2331
|
+
and startup.kind is not RemoteStartKind.HELLO
|
|
2332
|
+
):
|
|
2333
|
+
_stop_unstarted_remote(process)
|
|
2334
|
+
if startup.returncode == 255:
|
|
2335
|
+
raise ProbeError("ssh could not connect to the remote host")
|
|
2336
|
+
if startup.kind is RemoteStartKind.TIMEOUT:
|
|
2337
|
+
raise ProbeError(
|
|
2338
|
+
"timed out waiting for the Windows Railmux compatibility handshake"
|
|
2339
|
+
)
|
|
2340
|
+
raise ProbeError(remote_windows_install_help(args.destination, __version__))
|
|
2230
2341
|
install_version = __version__
|
|
2231
2342
|
install_reason: str | None = None
|
|
2232
2343
|
if startup.kind is RemoteStartKind.MISSING:
|
|
@@ -2281,6 +2392,12 @@ def prepare_remote_process(
|
|
|
2281
2392
|
if decision.action == "prompt" and decision.prompt == "remote_install":
|
|
2282
2393
|
assert decision.reason is not None
|
|
2283
2394
|
assert decision.install_version is not None
|
|
2395
|
+
if _remote_uses_windows_runtime(args, hello):
|
|
2396
|
+
_stop_unstarted_remote(process)
|
|
2397
|
+
raise ProbeError(
|
|
2398
|
+
f"{decision.reason}\n"
|
|
2399
|
+
f"{remote_windows_install_help(args.destination, decision.install_version)}"
|
|
2400
|
+
)
|
|
2284
2401
|
consents["remote_install"] = _confirm_remote_install(
|
|
2285
2402
|
args,
|
|
2286
2403
|
decision.reason,
|
|
@@ -2295,9 +2412,14 @@ def prepare_remote_process(
|
|
|
2295
2412
|
_stop_unstarted_remote(process)
|
|
2296
2413
|
message = decision.reason or "incompatible remote Railmux"
|
|
2297
2414
|
if decision.install_version is not None:
|
|
2415
|
+
install_help = (
|
|
2416
|
+
remote_windows_install_help
|
|
2417
|
+
if _remote_uses_windows_runtime(args, hello)
|
|
2418
|
+
else remote_install_help
|
|
2419
|
+
)
|
|
2298
2420
|
message = (
|
|
2299
2421
|
f"{message}\n"
|
|
2300
|
-
f"{
|
|
2422
|
+
f"{install_help(args.destination, decision.install_version)}"
|
|
2301
2423
|
)
|
|
2302
2424
|
raise ProbeError(message)
|
|
2303
2425
|
if decision.warning is not None:
|
|
@@ -2320,6 +2442,14 @@ def prepare_remote_process(
|
|
|
2320
2442
|
raise AssertionError(f"unknown compatibility action: {decision.action}")
|
|
2321
2443
|
|
|
2322
2444
|
assert install_reason is not None
|
|
2445
|
+
if _remote_uses_windows_runtime(
|
|
2446
|
+
args, startup.hello if startup.kind is RemoteStartKind.HELLO else None
|
|
2447
|
+
):
|
|
2448
|
+
_stop_unstarted_remote(process)
|
|
2449
|
+
raise ProbeError(
|
|
2450
|
+
f"{install_reason}\n"
|
|
2451
|
+
f"{remote_windows_install_help(args.destination, install_version)}"
|
|
2452
|
+
)
|
|
2323
2453
|
if startup.kind is not RemoteStartKind.HELLO and not _confirm_remote_install(
|
|
2324
2454
|
args,
|
|
2325
2455
|
install_reason,
|
|
@@ -2430,6 +2560,16 @@ def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
|
|
|
2430
2560
|
default=20.0,
|
|
2431
2561
|
help="maximum coalesced display update rate, 1-60 (default: 20)",
|
|
2432
2562
|
)
|
|
2563
|
+
parser.add_argument(
|
|
2564
|
+
"--remote-platform",
|
|
2565
|
+
choices=("auto", "posix", "windows"),
|
|
2566
|
+
default="auto",
|
|
2567
|
+
help=(
|
|
2568
|
+
"remote shell family: auto tries POSIX discovery then direct "
|
|
2569
|
+
"Windows launch; windows avoids the failed POSIX probe and an "
|
|
2570
|
+
"extra password prompt (default: auto)"
|
|
2571
|
+
),
|
|
2572
|
+
)
|
|
2433
2573
|
parser.add_argument(
|
|
2434
2574
|
"--no-mouse",
|
|
2435
2575
|
action="store_true",
|
|
@@ -248,6 +248,11 @@ def _emit_remote_hello(
|
|
|
248
248
|
"config_status": config_status,
|
|
249
249
|
"tmux_configured": tmux_configured,
|
|
250
250
|
"config_protocol": REMOTE_CONFIG_PROTOCOL,
|
|
251
|
+
"platform": (
|
|
252
|
+
"windows-msys2"
|
|
253
|
+
if os.environ.get("RAILMUX_WINDOWS_RUNTIME") == "msys2"
|
|
254
|
+
else "posix"
|
|
255
|
+
),
|
|
251
256
|
"version": __version__,
|
|
252
257
|
},
|
|
253
258
|
separators=(",", ":"),
|
|
@@ -68,6 +68,7 @@ from railmux.fast_display_client import (
|
|
|
68
68
|
LOCAL_ESCAPE,
|
|
69
69
|
RemoteHello,
|
|
70
70
|
RemoteAttachKind,
|
|
71
|
+
RemoteLaunchMode,
|
|
71
72
|
RemoteStartKind,
|
|
72
73
|
RemoteStartup,
|
|
73
74
|
ScreenModel,
|
|
@@ -4392,6 +4393,22 @@ def test_full_window_ssh_command_uses_railmux_remote_subcommand_and_protocol():
|
|
|
4392
4393
|
assert "--width 120 --height 40 --fps 20.0" in argv[-1]
|
|
4393
4394
|
|
|
4394
4395
|
|
|
4396
|
+
def test_windows_remote_command_uses_shell_neutral_direct_launch():
|
|
4397
|
+
argv = build_ssh_argv(
|
|
4398
|
+
"windows-server",
|
|
4399
|
+
session="rail mux",
|
|
4400
|
+
width=120,
|
|
4401
|
+
height=40,
|
|
4402
|
+
fps=20.0,
|
|
4403
|
+
ssh_args=(),
|
|
4404
|
+
launch_mode=RemoteLaunchMode.DIRECT,
|
|
4405
|
+
)
|
|
4406
|
+
|
|
4407
|
+
assert argv[-1].startswith("railmux remote-server ")
|
|
4408
|
+
assert "if [" not in argv[-1]
|
|
4409
|
+
assert "--session 'rail mux'" in argv[-1]
|
|
4410
|
+
|
|
4411
|
+
|
|
4395
4412
|
def test_full_window_ssh_keepalive_defaults_follow_user_overrides():
|
|
4396
4413
|
argv = build_ssh_argv(
|
|
4397
4414
|
"server",
|
|
@@ -4538,6 +4555,12 @@ def test_remote_hello_is_strictly_bounded_and_typed():
|
|
|
4538
4555
|
)
|
|
4539
4556
|
assert configured.config_status == "invalid"
|
|
4540
4557
|
assert configured.tmux_configured is True
|
|
4558
|
+
windows = parse_remote_hello(
|
|
4559
|
+
REMOTE_HELLO_PREFIX
|
|
4560
|
+
+ b'{"platform":"windows-msys2","protocol":6,"ready":true,'
|
|
4561
|
+
b'"tmux":true,"version":"1.2.3"}\n'
|
|
4562
|
+
)
|
|
4563
|
+
assert windows.platform == "windows-msys2"
|
|
4541
4564
|
with pytest.raises(ValueError):
|
|
4542
4565
|
parse_remote_hello(
|
|
4543
4566
|
REMOTE_HELLO_PREFIX + b'{"protocol":true,"ready":true,"tmux":true,'
|
|
@@ -4545,6 +4568,12 @@ def test_remote_hello_is_strictly_bounded_and_typed():
|
|
|
4545
4568
|
)
|
|
4546
4569
|
with pytest.raises(ValueError):
|
|
4547
4570
|
parse_remote_hello(REMOTE_HELLO_PREFIX + b"not-json\n")
|
|
4571
|
+
with pytest.raises(ValueError):
|
|
4572
|
+
parse_remote_hello(
|
|
4573
|
+
REMOTE_HELLO_PREFIX
|
|
4574
|
+
+ b'{"platform":"windows","protocol":6,"ready":true,'
|
|
4575
|
+
b'"tmux":true,"version":"1.2.3"}\n'
|
|
4576
|
+
)
|
|
4548
4577
|
|
|
4549
4578
|
|
|
4550
4579
|
def test_remote_startup_wait_reads_hello_before_raw_mode():
|
|
@@ -4853,6 +4882,14 @@ def test_ssh_parser_accepts_ordered_exact_and_grouped_arguments():
|
|
|
4853
4882
|
]
|
|
4854
4883
|
|
|
4855
4884
|
|
|
4885
|
+
def test_ssh_parser_selects_remote_platform_without_changing_default():
|
|
4886
|
+
assert parse_client_args(["server"]).remote_platform == "auto"
|
|
4887
|
+
assert (
|
|
4888
|
+
parse_client_args(["server", "--remote-platform", "windows"]).remote_platform
|
|
4889
|
+
== "windows"
|
|
4890
|
+
)
|
|
4891
|
+
|
|
4892
|
+
|
|
4856
4893
|
def test_reconnect_attach_forces_noninteractive_bounded_ssh(monkeypatch):
|
|
4857
4894
|
process = _PreflightProcess()
|
|
4858
4895
|
built = MagicMock(return_value=["ssh", "remote"])
|
|
@@ -4901,6 +4938,7 @@ def test_reconnect_attach_forces_noninteractive_bounded_ssh(monkeypatch):
|
|
|
4901
4938
|
"ConnectTimeout=5",
|
|
4902
4939
|
]
|
|
4903
4940
|
assert built.call_args.kwargs["replace_existing_client"] is False
|
|
4941
|
+
assert built.call_args.kwargs["launch_mode"] is RemoteLaunchMode.POSIX
|
|
4904
4942
|
spawn.assert_called_once_with(
|
|
4905
4943
|
["ssh", "remote"],
|
|
4906
4944
|
suppress_stderr=True,
|
|
@@ -4948,6 +4986,44 @@ def test_reconnect_connect_timeout_keeps_user_first_value(monkeypatch):
|
|
|
4948
4986
|
)
|
|
4949
4987
|
|
|
4950
4988
|
|
|
4989
|
+
def test_reconnect_reuses_selected_windows_launch_mode(monkeypatch):
|
|
4990
|
+
process = _PreflightProcess()
|
|
4991
|
+
built = MagicMock(return_value=["ssh", "remote"])
|
|
4992
|
+
monkeypatch.setattr(fast_display_client, "build_ssh_argv", built)
|
|
4993
|
+
monkeypatch.setattr(
|
|
4994
|
+
fast_display_client,
|
|
4995
|
+
"_spawn_remote",
|
|
4996
|
+
MagicMock(return_value=process),
|
|
4997
|
+
)
|
|
4998
|
+
monkeypatch.setattr(
|
|
4999
|
+
fast_display_client,
|
|
5000
|
+
"await_remote_startup",
|
|
5001
|
+
lambda *_args, **_kwargs: RemoteStartup(
|
|
5002
|
+
RemoteStartKind.HELLO,
|
|
5003
|
+
RemoteHello(
|
|
5004
|
+
fast_display_client.__version__,
|
|
5005
|
+
PROTOCOL_VERSION,
|
|
5006
|
+
True,
|
|
5007
|
+
platform="windows-msys2",
|
|
5008
|
+
),
|
|
5009
|
+
),
|
|
5010
|
+
)
|
|
5011
|
+
monkeypatch.setattr(
|
|
5012
|
+
fast_display_client,
|
|
5013
|
+
"await_remote_attach_status",
|
|
5014
|
+
lambda *_args, **_kwargs: RemoteAttachKind.ACCEPTED,
|
|
5015
|
+
)
|
|
5016
|
+
args = parse_client_args(["server", "--remote-platform", "windows"])
|
|
5017
|
+
|
|
5018
|
+
fast_display_client._reconnect_remote_attach(
|
|
5019
|
+
args,
|
|
5020
|
+
os.terminal_size((120, 40)),
|
|
5021
|
+
replace_existing_client=False,
|
|
5022
|
+
)
|
|
5023
|
+
|
|
5024
|
+
assert built.call_args.kwargs["launch_mode"] is RemoteLaunchMode.DIRECT
|
|
5025
|
+
|
|
5026
|
+
|
|
4951
5027
|
def test_remote_command_keeps_railmux_tty_mode_after_user_flags():
|
|
4952
5028
|
binary = build_remote_command_argv(
|
|
4953
5029
|
"server",
|
|
@@ -5348,6 +5424,71 @@ def test_compatible_remote_is_confirmed_before_attach(monkeypatch):
|
|
|
5348
5424
|
]
|
|
5349
5425
|
|
|
5350
5426
|
|
|
5427
|
+
def test_auto_remote_launch_falls_back_to_windows_and_pins_reconnect_mode(
|
|
5428
|
+
monkeypatch,
|
|
5429
|
+
):
|
|
5430
|
+
_accept_attach(monkeypatch)
|
|
5431
|
+
posix = _PreflightProcess(1)
|
|
5432
|
+
windows = _PreflightProcess()
|
|
5433
|
+
processes = iter((posix, windows))
|
|
5434
|
+
commands = []
|
|
5435
|
+
|
|
5436
|
+
def spawn(argv):
|
|
5437
|
+
commands.append(argv)
|
|
5438
|
+
return next(processes)
|
|
5439
|
+
|
|
5440
|
+
startups = iter(
|
|
5441
|
+
(
|
|
5442
|
+
RemoteStartup(RemoteStartKind.FAILED, returncode=1),
|
|
5443
|
+
RemoteStartup(
|
|
5444
|
+
RemoteStartKind.HELLO,
|
|
5445
|
+
RemoteHello(
|
|
5446
|
+
fast_display_client.__version__,
|
|
5447
|
+
PROTOCOL_VERSION,
|
|
5448
|
+
True,
|
|
5449
|
+
platform="windows-msys2",
|
|
5450
|
+
),
|
|
5451
|
+
),
|
|
5452
|
+
)
|
|
5453
|
+
)
|
|
5454
|
+
monkeypatch.setattr(fast_display_client, "_spawn_remote", spawn)
|
|
5455
|
+
monkeypatch.setattr(
|
|
5456
|
+
fast_display_client,
|
|
5457
|
+
"await_remote_startup",
|
|
5458
|
+
lambda _process: next(startups),
|
|
5459
|
+
)
|
|
5460
|
+
args = parse_client_args(["server"])
|
|
5461
|
+
|
|
5462
|
+
selected = prepare_remote_process(args, os.terminal_size((120, 40)))
|
|
5463
|
+
|
|
5464
|
+
assert selected is windows
|
|
5465
|
+
assert posix.terminated is False
|
|
5466
|
+
assert "if [" in commands[0][-1]
|
|
5467
|
+
assert commands[1][-1].startswith("railmux remote-server ")
|
|
5468
|
+
assert args._selected_remote_launch_mode is RemoteLaunchMode.DIRECT
|
|
5469
|
+
assert windows.stdin.getvalue() == REMOTE_START
|
|
5470
|
+
|
|
5471
|
+
|
|
5472
|
+
def test_explicit_windows_remote_failure_never_runs_posix_installer(monkeypatch):
|
|
5473
|
+
process = _PreflightProcess(1)
|
|
5474
|
+
args = parse_client_args(["server", "--remote-platform", "windows"])
|
|
5475
|
+
monkeypatch.setattr(fast_display_client, "_spawn_remote", lambda _argv: process)
|
|
5476
|
+
monkeypatch.setattr(
|
|
5477
|
+
fast_display_client,
|
|
5478
|
+
"await_remote_startup",
|
|
5479
|
+
lambda _process: RemoteStartup(RemoteStartKind.FAILED, returncode=1),
|
|
5480
|
+
)
|
|
5481
|
+
install = MagicMock()
|
|
5482
|
+
monkeypatch.setattr(fast_display_client, "_install_remote_and_start", install)
|
|
5483
|
+
|
|
5484
|
+
with pytest.raises(fast_display_client.ProbeError) as exc:
|
|
5485
|
+
prepare_remote_process(args, os.terminal_size((120, 40)))
|
|
5486
|
+
|
|
5487
|
+
assert "py -m pip install --upgrade" in str(exc.value)
|
|
5488
|
+
assert "railmux runtime install --yes" in str(exc.value)
|
|
5489
|
+
install.assert_not_called()
|
|
5490
|
+
|
|
5491
|
+
|
|
5351
5492
|
def test_reconnect_flag_does_not_change_initial_ssh_command():
|
|
5352
5493
|
plain = parse_client_args(["server"])
|
|
5353
5494
|
reconnecting = parse_client_args(["server", "--reconnect"])
|
|
@@ -5492,7 +5633,8 @@ def test_missing_remote_prompts_then_installs_and_starts(monkeypatch):
|
|
|
5492
5633
|
missing = _PreflightProcess(127)
|
|
5493
5634
|
installed = _PreflightProcess()
|
|
5494
5635
|
args = parse_client_args(["server"])
|
|
5495
|
-
|
|
5636
|
+
spawn = MagicMock(return_value=missing)
|
|
5637
|
+
monkeypatch.setattr(fast_display_client, "_spawn_remote", spawn)
|
|
5496
5638
|
monkeypatch.setattr(
|
|
5497
5639
|
fast_display_client,
|
|
5498
5640
|
"await_remote_startup",
|
|
@@ -5515,6 +5657,7 @@ def test_missing_remote_prompts_then_installs_and_starts(monkeypatch):
|
|
|
5515
5657
|
|
|
5516
5658
|
assert selected is installed
|
|
5517
5659
|
assert installed.stdin.getvalue() == REMOTE_START
|
|
5660
|
+
spawn.assert_called_once()
|
|
5518
5661
|
|
|
5519
5662
|
|
|
5520
5663
|
def test_missing_remote_decline_returns_copyable_install_help(
|
|
@@ -5813,6 +5956,37 @@ def test_older_remote_protocol_prompts_for_matching_remote_upgrade(monkeypatch):
|
|
|
5813
5956
|
assert upgraded.stdin.getvalue() == REMOTE_START
|
|
5814
5957
|
|
|
5815
5958
|
|
|
5959
|
+
def test_windows_remote_upgrade_fails_closed_with_windows_guidance(monkeypatch):
|
|
5960
|
+
process = _PreflightProcess()
|
|
5961
|
+
args = parse_client_args(["server", "--remote-platform", "windows"])
|
|
5962
|
+
monkeypatch.setattr(fast_display_client, "_spawn_remote", lambda _argv: process)
|
|
5963
|
+
monkeypatch.setattr(
|
|
5964
|
+
fast_display_client,
|
|
5965
|
+
"await_remote_startup",
|
|
5966
|
+
lambda _process: RemoteStartup(
|
|
5967
|
+
RemoteStartKind.HELLO,
|
|
5968
|
+
RemoteHello(
|
|
5969
|
+
"0.1.0",
|
|
5970
|
+
PROTOCOL_VERSION - 1,
|
|
5971
|
+
True,
|
|
5972
|
+
platform="windows-msys2",
|
|
5973
|
+
),
|
|
5974
|
+
),
|
|
5975
|
+
)
|
|
5976
|
+
confirm = MagicMock()
|
|
5977
|
+
install = MagicMock()
|
|
5978
|
+
monkeypatch.setattr(fast_display_client, "_confirm", confirm)
|
|
5979
|
+
monkeypatch.setattr(fast_display_client, "_install_remote_and_start", install)
|
|
5980
|
+
|
|
5981
|
+
with pytest.raises(fast_display_client.ProbeError) as exc:
|
|
5982
|
+
prepare_remote_process(args, os.terminal_size((120, 40)))
|
|
5983
|
+
|
|
5984
|
+
assert "py -m pip install --upgrade" in str(exc.value)
|
|
5985
|
+
assert "railmux runtime install --yes" in str(exc.value)
|
|
5986
|
+
confirm.assert_not_called()
|
|
5987
|
+
install.assert_not_called()
|
|
5988
|
+
|
|
5989
|
+
|
|
5816
5990
|
def test_older_compatible_remote_can_be_upgraded_to_local_version(monkeypatch):
|
|
5817
5991
|
_accept_attach(monkeypatch)
|
|
5818
5992
|
monkeypatch.setattr(fast_display_client, "__version__", "0.2.5")
|
|
@@ -6066,6 +6240,18 @@ def test_remote_server_hello_reports_version_protocol_and_dependency(monkeypatch
|
|
|
6066
6240
|
)
|
|
6067
6241
|
|
|
6068
6242
|
|
|
6243
|
+
def test_remote_server_hello_identifies_managed_windows_runtime(monkeypatch):
|
|
6244
|
+
output = io.BytesIO()
|
|
6245
|
+
monkeypatch.setenv("RAILMUX_WINDOWS_RUNTIME", "msys2")
|
|
6246
|
+
monkeypatch.setattr(fast_display_server.shutil, "which", lambda _name: "/tmux")
|
|
6247
|
+
monkeypatch.setattr(fast_display_server.sys, "stdout", MagicMock(buffer=output))
|
|
6248
|
+
|
|
6249
|
+
fast_display_server._emit_remote_hello(True)
|
|
6250
|
+
|
|
6251
|
+
hello = parse_remote_hello(output.getvalue())
|
|
6252
|
+
assert hello.platform == "windows-msys2"
|
|
6253
|
+
|
|
6254
|
+
|
|
6069
6255
|
def test_remote_server_waits_for_exact_start_confirmation(monkeypatch):
|
|
6070
6256
|
remote_input = MagicMock(buffer=io.BytesIO(REMOTE_START))
|
|
6071
6257
|
monkeypatch.setattr(fast_display_server.sys, "stdin", remote_input)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.0.dev22"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|