railmux 0.4.0.dev17__tar.gz → 0.4.0.dev18__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.dev17/src/railmux.egg-info → railmux-0.4.0.dev18}/PKG-INFO +1 -1
- railmux-0.4.0.dev18/src/railmux/__init__.py +1 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/cli.py +16 -2
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/tmux_server.py +50 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_cli.py +67 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_tmux_server.py +63 -0
- railmux-0.4.0.dev17/src/railmux/__init__.py +0 -1
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/LICENSE +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/MANIFEST.in +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/README.md +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/pyproject.toml +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/setup.cfg +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/__main__.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/atomic_file.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/background_index.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/codex_index.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/config.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/config_cli.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/diagnostics.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/discovery.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/display_transport.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/entrypoint.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/fast_display_client.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/fast_display_server.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/favorites.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/fuzzy.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/help_workspace.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/launcher.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/local_open.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/models.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/modes.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/pane_surface.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/path_codec.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/provider_paths.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/remote_config.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/renames.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/restart_state.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/runtime_config.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/self_update.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/session_cache.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/session_index.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/session_lease.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/settings.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ssh_args.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/system_deps.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/terminal_status.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/tmux_health.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/tool_panes.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/transcript.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ui/app.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ui/modals.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/windows_attach_relay.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/windows_bootstrap.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/windows_install_log.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/windows_msys2.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/windows_pacman.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux/windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux.egg-info/SOURCES.txt +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_agent_workspace.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_atomic_file.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_background_index.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_codex_delete.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_codex_index.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_codex_lineage.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_codex_transcript.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_config.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_config_cli.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_diagnostics.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_discovery.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_display_transport.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_fast_full_window.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_focus_highlight.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_fuzzy.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_help_agent.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_help_workspace.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_history_cleanup.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_keymap.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_launcher.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_layout_profile.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_local_clipboard.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_local_open.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_modals.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_mode_selection.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_modes.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_mouse_manager.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_new_project.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_options.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_pane_surface.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_path_codec.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_projects_pane.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_provider_paths.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_release_notes.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_release_workflow.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_remote_config.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_renames.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_restart_state.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_running_pane.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_running_sort.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_runtime_config.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_scroll_agent.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_scroll_manager.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_selection_isolation.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_self_update.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_session_cache.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_session_index.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_session_lease.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_sessions_pane.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_setting_contracts.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_settings.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_sidebar.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_soft_quit.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_ssh_args.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_ssh_compat.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_status.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_statusbar.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_system_deps.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_terminal_size.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_terminal_status.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_tmux_health.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_tmux_integration.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_tmux_status.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_tool_panes.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_transcript.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_utf8_state.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_widgets.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_windows_attach_relay.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_windows_bootstrap.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_windows_install_log.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_windows_msys2.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_windows_pacman.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_windows_wrapper_contract.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tools/release_notes.py +0 -0
- {railmux-0.4.0.dev17 → railmux-0.4.0.dev18}/tools/wheel_smoke.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0.dev18"
|
|
@@ -593,14 +593,28 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
593
593
|
if Path(sys.argv[0]).name == "__main__.py"
|
|
594
594
|
else [sys.argv[0]]
|
|
595
595
|
)
|
|
596
|
-
|
|
596
|
+
client_env = tmux_server.exec_environment()
|
|
597
597
|
dedicated_session_id = (
|
|
598
598
|
tmux_server.target_session_id(dedicated_target, "railmux")
|
|
599
599
|
if dedicated_target is not None else None
|
|
600
600
|
)
|
|
601
|
+
if dedicated_target is not None and dedicated_session_id is None:
|
|
602
|
+
from railmux.provider_paths import (
|
|
603
|
+
running_in_managed_windows_wrapper,
|
|
604
|
+
)
|
|
605
|
+
if running_in_managed_windows_wrapper():
|
|
606
|
+
dedicated_session_id = (
|
|
607
|
+
tmux_server.ensure_detached_launcher_session(
|
|
608
|
+
dedicated_target,
|
|
609
|
+
launch_prefix,
|
|
610
|
+
raw_args,
|
|
611
|
+
env=client_env,
|
|
612
|
+
)
|
|
613
|
+
)
|
|
614
|
+
cmd = tmux_server.launcher_argv(launch_prefix, raw_args)
|
|
601
615
|
return _run_tmux_client_with_watchdog(
|
|
602
616
|
cmd,
|
|
603
|
-
|
|
617
|
+
client_env,
|
|
604
618
|
expected_target=dedicated_target,
|
|
605
619
|
expected_session_id=dedicated_session_id,
|
|
606
620
|
)
|
|
@@ -645,3 +645,53 @@ def launcher_argv(
|
|
|
645
645
|
"--inside-tmux",
|
|
646
646
|
*forwarded_args,
|
|
647
647
|
)
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def ensure_detached_launcher_session(
|
|
651
|
+
target: TmuxServerTarget,
|
|
652
|
+
launch_prefix: Sequence[str],
|
|
653
|
+
forwarded_args: Sequence[str],
|
|
654
|
+
*,
|
|
655
|
+
env: Mapping[str, str] | None = None,
|
|
656
|
+
timeout: float = 5.0,
|
|
657
|
+
) -> str | None:
|
|
658
|
+
"""Create the outer UI detached on one already-proven server.
|
|
659
|
+
|
|
660
|
+
Managed Windows uses this only when a proven existing server has no outer
|
|
661
|
+
UI, commonly because Soft Quit left provider sessions alive in another
|
|
662
|
+
Terminal Services session. A new tmux client can then reject
|
|
663
|
+
``new-session -A`` before the outer session exists, so the server-origin
|
|
664
|
+
terminal bridge has no immutable session to attach.
|
|
665
|
+
|
|
666
|
+
An exact pre/post server-PID check fences the mutation. A concurrent
|
|
667
|
+
launcher may win the create race; resolving one exact resulting session is
|
|
668
|
+
success regardless of this client's ``new-session`` return code.
|
|
669
|
+
"""
|
|
670
|
+
existing = target_session_id(target, "railmux", timeout=0.5)
|
|
671
|
+
if existing is not None:
|
|
672
|
+
return existing
|
|
673
|
+
if not launch_prefix or not target_is_live(target, timeout=1.0, env=env):
|
|
674
|
+
return None
|
|
675
|
+
try:
|
|
676
|
+
subprocess.run(
|
|
677
|
+
target_argv(
|
|
678
|
+
target,
|
|
679
|
+
"new-session",
|
|
680
|
+
"-d",
|
|
681
|
+
"-s",
|
|
682
|
+
"railmux",
|
|
683
|
+
*launch_prefix,
|
|
684
|
+
"--inside-tmux",
|
|
685
|
+
*forwarded_args,
|
|
686
|
+
),
|
|
687
|
+
stdout=subprocess.DEVNULL,
|
|
688
|
+
stderr=subprocess.DEVNULL,
|
|
689
|
+
timeout=timeout,
|
|
690
|
+
check=False,
|
|
691
|
+
env=None if env is None else dict(env),
|
|
692
|
+
)
|
|
693
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
694
|
+
return None
|
|
695
|
+
if not target_is_live(target, timeout=1.0, env=env):
|
|
696
|
+
return None
|
|
697
|
+
return target_session_id(target, "railmux", timeout=1.0)
|
|
@@ -472,6 +472,73 @@ def test_prelaunch_recovery_is_scoped_to_the_dedicated_server(monkeypatch):
|
|
|
472
472
|
assert os.environ["TMUX_PANE"] == "%9"
|
|
473
473
|
|
|
474
474
|
|
|
475
|
+
def test_managed_windows_precreates_missing_outer_session_for_bridge(
|
|
476
|
+
monkeypatch,
|
|
477
|
+
):
|
|
478
|
+
target = TmuxServerTarget("/tmp/tmux-private/railmux", 789)
|
|
479
|
+
monkeypatch.setattr(
|
|
480
|
+
"railmux.cli.tmux_server.discover_target", lambda: target)
|
|
481
|
+
monkeypatch.setattr(
|
|
482
|
+
"railmux.cli.tmux_server.is_current_server", lambda _target: False)
|
|
483
|
+
monkeypatch.setattr(
|
|
484
|
+
"railmux.cli.tmux_server.target_session_id", lambda *_a, **_kw: None)
|
|
485
|
+
monkeypatch.setattr("railmux.cli.sys.argv", ["/opt/railmux/bin/railmux"])
|
|
486
|
+
monkeypatch.setattr(
|
|
487
|
+
"railmux.provider_paths.running_in_managed_windows_wrapper",
|
|
488
|
+
lambda: True,
|
|
489
|
+
)
|
|
490
|
+
prepared = MagicMock(return_value="$9")
|
|
491
|
+
monkeypatch.setattr(
|
|
492
|
+
"railmux.cli.tmux_server.ensure_detached_launcher_session", prepared)
|
|
493
|
+
run_client = MagicMock(return_value=0)
|
|
494
|
+
monkeypatch.setattr(
|
|
495
|
+
"railmux.cli._run_tmux_client_with_watchdog", run_client)
|
|
496
|
+
|
|
497
|
+
assert main(["--project", "/work"]) == 0
|
|
498
|
+
|
|
499
|
+
client_env = tmux_server.exec_environment()
|
|
500
|
+
prepared.assert_called_once_with(
|
|
501
|
+
target,
|
|
502
|
+
["/opt/railmux/bin/railmux"],
|
|
503
|
+
["--project", "/work"],
|
|
504
|
+
env=client_env,
|
|
505
|
+
)
|
|
506
|
+
assert run_client.call_args.kwargs == {
|
|
507
|
+
"expected_target": target,
|
|
508
|
+
"expected_session_id": "$9",
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def test_posix_missing_outer_session_keeps_ordinary_new_session_path(
|
|
513
|
+
monkeypatch,
|
|
514
|
+
):
|
|
515
|
+
target = TmuxServerTarget("/tmp/tmux-private/railmux", 789)
|
|
516
|
+
monkeypatch.setattr(
|
|
517
|
+
"railmux.cli.tmux_server.discover_target", lambda: target)
|
|
518
|
+
monkeypatch.setattr(
|
|
519
|
+
"railmux.cli.tmux_server.is_current_server", lambda _target: False)
|
|
520
|
+
monkeypatch.setattr(
|
|
521
|
+
"railmux.cli.tmux_server.target_session_id", lambda *_a, **_kw: None)
|
|
522
|
+
monkeypatch.setattr(
|
|
523
|
+
"railmux.provider_paths.running_in_managed_windows_wrapper",
|
|
524
|
+
lambda: False,
|
|
525
|
+
)
|
|
526
|
+
prepared = MagicMock()
|
|
527
|
+
monkeypatch.setattr(
|
|
528
|
+
"railmux.cli.tmux_server.ensure_detached_launcher_session", prepared)
|
|
529
|
+
run_client = MagicMock(return_value=0)
|
|
530
|
+
monkeypatch.setattr(
|
|
531
|
+
"railmux.cli._run_tmux_client_with_watchdog", run_client)
|
|
532
|
+
|
|
533
|
+
assert main([]) == 0
|
|
534
|
+
|
|
535
|
+
prepared.assert_not_called()
|
|
536
|
+
assert run_client.call_args.kwargs == {
|
|
537
|
+
"expected_target": target,
|
|
538
|
+
"expected_session_id": None,
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
|
|
475
542
|
def test_local_tmux_watchdog_exits_and_records_after_consecutive_failures(
|
|
476
543
|
monkeypatch, capsys,
|
|
477
544
|
):
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
import subprocess
|
|
5
5
|
from types import SimpleNamespace
|
|
6
|
+
from unittest.mock import MagicMock
|
|
6
7
|
|
|
7
8
|
import pytest
|
|
8
9
|
|
|
@@ -37,6 +38,68 @@ def test_launcher_argv_preserves_multi_argument_python_module_prefix():
|
|
|
37
38
|
]
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
def test_detached_launcher_session_is_pinned_before_and_after_create(
|
|
42
|
+
monkeypatch,
|
|
43
|
+
):
|
|
44
|
+
target = tmux_server.TmuxServerTarget("/tmp/private", 44)
|
|
45
|
+
live = []
|
|
46
|
+
session_ids = iter((None, "$7"))
|
|
47
|
+
# Another launcher can win after our initial absence check. tmux then
|
|
48
|
+
# rejects this duplicate create, while the exact resulting session is the
|
|
49
|
+
# safe shared outcome.
|
|
50
|
+
run = SimpleNamespace(returncode=1)
|
|
51
|
+
launched = []
|
|
52
|
+
|
|
53
|
+
def target_is_live(candidate, **kwargs):
|
|
54
|
+
live.append((candidate, kwargs))
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
monkeypatch.setattr(tmux_server, "target_is_live", target_is_live)
|
|
58
|
+
monkeypatch.setattr(
|
|
59
|
+
tmux_server,
|
|
60
|
+
"target_session_id",
|
|
61
|
+
lambda *_args, **_kwargs: next(session_ids),
|
|
62
|
+
)
|
|
63
|
+
monkeypatch.setattr(
|
|
64
|
+
subprocess,
|
|
65
|
+
"run",
|
|
66
|
+
lambda argv, **kwargs: launched.append((argv, kwargs)) or run,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
assert tmux_server.ensure_detached_launcher_session(
|
|
70
|
+
target,
|
|
71
|
+
["/opt/railmux/bin/python", "-m", "railmux"],
|
|
72
|
+
["--mode", "codex"],
|
|
73
|
+
env={"PATH": "/usr/bin"},
|
|
74
|
+
) == "$7"
|
|
75
|
+
|
|
76
|
+
assert len(live) == 2
|
|
77
|
+
assert launched[0][0] == [
|
|
78
|
+
"tmux", "-S", "/tmp/private",
|
|
79
|
+
"new-session", "-d", "-s", "railmux",
|
|
80
|
+
"/opt/railmux/bin/python", "-m", "railmux", "--inside-tmux",
|
|
81
|
+
"--mode", "codex",
|
|
82
|
+
]
|
|
83
|
+
assert launched[0][1]["env"] == {"PATH": "/usr/bin"}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_detached_launcher_session_refuses_changed_server_identity(
|
|
87
|
+
monkeypatch,
|
|
88
|
+
):
|
|
89
|
+
target = tmux_server.TmuxServerTarget("/tmp/private", 44)
|
|
90
|
+
session_id = MagicMock(return_value=None)
|
|
91
|
+
monkeypatch.setattr(tmux_server, "target_session_id", session_id)
|
|
92
|
+
monkeypatch.setattr(
|
|
93
|
+
tmux_server, "target_is_live", MagicMock(side_effect=(True, False)))
|
|
94
|
+
monkeypatch.setattr(
|
|
95
|
+
subprocess, "run", MagicMock(return_value=SimpleNamespace(returncode=0)))
|
|
96
|
+
|
|
97
|
+
assert tmux_server.ensure_detached_launcher_session(
|
|
98
|
+
target, ["railmux"], [],
|
|
99
|
+
) is None
|
|
100
|
+
session_id.assert_called_once_with(target, "railmux", timeout=0.5)
|
|
101
|
+
|
|
102
|
+
|
|
40
103
|
def test_current_socket_parser_allows_commas_in_the_path():
|
|
41
104
|
env = {"TMUX": "/tmp/with,comma/railmux,123,0"}
|
|
42
105
|
assert tmux_server.current_socket_path(env) == "/tmp/with,comma/railmux"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.0.dev17"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|