railmux 0.4.0.dev30__tar.gz → 0.4.0.dev31__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.
Files changed (183) hide show
  1. {railmux-0.4.0.dev30/src/railmux.egg-info → railmux-0.4.0.dev31}/PKG-INFO +1 -1
  2. railmux-0.4.0.dev31/src/railmux/__init__.py +1 -0
  3. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/cli.py +27 -1
  4. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/windows_install_log.py +7 -1
  5. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/windows_pacman.py +24 -3
  6. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31/src/railmux.egg-info}/PKG-INFO +1 -1
  7. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_cli.py +75 -0
  8. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_windows_install_log.py +1 -0
  9. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_windows_pacman.py +35 -0
  10. railmux-0.4.0.dev30/src/railmux/__init__.py +0 -1
  11. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/LICENSE +0 -0
  12. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/MANIFEST.in +0 -0
  13. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/README.md +0 -0
  14. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/pyproject.toml +0 -0
  15. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/setup.cfg +0 -0
  16. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/__main__.py +0 -0
  17. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/atomic_file.py +0 -0
  18. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/background_index.py +0 -0
  19. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/codex_index.py +0 -0
  20. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/config.py +0 -0
  21. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/config_cli.py +0 -0
  22. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/diagnostics.py +0 -0
  23. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/discovery.py +0 -0
  24. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/display_transport.py +0 -0
  25. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/entrypoint.py +0 -0
  26. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/fast_display_client.py +0 -0
  27. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/fast_display_history.py +0 -0
  28. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/fast_display_input.py +0 -0
  29. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/fast_display_protocol.py +0 -0
  30. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/fast_display_server.py +0 -0
  31. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/favorites.py +0 -0
  32. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/fuzzy.py +0 -0
  33. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/help_workspace.py +0 -0
  34. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/launcher.py +0 -0
  35. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/legacy_sessions.py +0 -0
  36. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/local_clipboard.py +0 -0
  37. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/local_open.py +0 -0
  38. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/models.py +0 -0
  39. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/modes.py +0 -0
  40. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/mouse_manager.py +0 -0
  41. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/orphan_marker.py +0 -0
  42. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/pane_surface.py +0 -0
  43. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/path_codec.py +0 -0
  44. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/provider_paths.py +0 -0
  45. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/remote_config.py +0 -0
  46. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/renames.py +0 -0
  47. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/restart_state.py +0 -0
  48. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/runtime_config.py +0 -0
  49. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/scroll_agent.py +0 -0
  50. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/scroll_manager.py +0 -0
  51. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/selection_isolation.py +0 -0
  52. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/self_update.py +0 -0
  53. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/session_cache.py +0 -0
  54. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/session_index.py +0 -0
  55. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/session_lease.py +0 -0
  56. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/setting_contracts.py +0 -0
  57. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/settings.py +0 -0
  58. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ssh_args.py +0 -0
  59. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ssh_compat.py +0 -0
  60. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ssh_display_diagnostics.py +0 -0
  61. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ssh_doctor.py +0 -0
  62. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/system_deps.py +0 -0
  63. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/terminal_status.py +0 -0
  64. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/tmux_binding_manager.py +0 -0
  65. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/tmux_ctl.py +0 -0
  66. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/tmux_health.py +0 -0
  67. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/tmux_server.py +0 -0
  68. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/tool_panes.py +0 -0
  69. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/transcript.py +0 -0
  70. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ui/__init__.py +0 -0
  71. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ui/_widgets.py +0 -0
  72. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ui/app.py +0 -0
  73. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ui/keymap.py +0 -0
  74. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ui/modals.py +0 -0
  75. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ui/projects_pane.py +0 -0
  76. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ui/running_pane.py +0 -0
  77. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ui/sessions_pane.py +0 -0
  78. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ui/sidebar.py +0 -0
  79. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ui/statusbar.py +0 -0
  80. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/ui/workspace.py +0 -0
  81. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/windows_attach_relay.py +0 -0
  82. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/windows_bootstrap.py +0 -0
  83. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/windows_msys2.py +0 -0
  84. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/windows_paths.py +0 -0
  85. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/windows_tmux_lifecycle.py +0 -0
  86. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux/windows_ui_transition.py +0 -0
  87. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux.egg-info/SOURCES.txt +0 -0
  88. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux.egg-info/dependency_links.txt +0 -0
  89. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux.egg-info/entry_points.txt +0 -0
  90. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux.egg-info/requires.txt +0 -0
  91. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/src/railmux.egg-info/top_level.txt +0 -0
  92. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_agent_workspace.py +0 -0
  93. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_atomic_file.py +0 -0
  94. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_background_index.py +0 -0
  95. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_codex_delete.py +0 -0
  96. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_codex_index.py +0 -0
  97. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_codex_lineage.py +0 -0
  98. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_codex_transcript.py +0 -0
  99. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_config.py +0 -0
  100. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_config_cli.py +0 -0
  101. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_denested_transport_benchmark.py +0 -0
  102. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_diagnostics.py +0 -0
  103. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_discovery.py +0 -0
  104. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_display_transport.py +0 -0
  105. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_fast_full_window.py +0 -0
  106. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_focus_highlight.py +0 -0
  107. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_fullscreen_binding.py +0 -0
  108. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_fuzzy.py +0 -0
  109. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_help_agent.py +0 -0
  110. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_help_workspace.py +0 -0
  111. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_history_cleanup.py +0 -0
  112. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_keymap.py +0 -0
  113. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_launcher.py +0 -0
  114. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_layout_profile.py +0 -0
  115. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_legacy_sessions.py +0 -0
  116. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_local_clipboard.py +0 -0
  117. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_local_open.py +0 -0
  118. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_modals.py +0 -0
  119. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_mode_selection.py +0 -0
  120. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_modes.py +0 -0
  121. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_mouse_manager.py +0 -0
  122. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_new_project.py +0 -0
  123. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_options.py +0 -0
  124. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_orphan_recovery.py +0 -0
  125. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_pane_surface.py +0 -0
  126. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_path_codec.py +0 -0
  127. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_projects_pane.py +0 -0
  128. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_provider_paths.py +0 -0
  129. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_release_notes.py +0 -0
  130. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_release_workflow.py +0 -0
  131. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_remote_config.py +0 -0
  132. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_renames.py +0 -0
  133. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_restart_state.py +0 -0
  134. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_running_pane.py +0 -0
  135. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_running_sort.py +0 -0
  136. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_runtime_config.py +0 -0
  137. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_scroll_agent.py +0 -0
  138. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_scroll_manager.py +0 -0
  139. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_selection_isolation.py +0 -0
  140. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_self_update.py +0 -0
  141. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_session_cache.py +0 -0
  142. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_session_index.py +0 -0
  143. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_session_lease.py +0 -0
  144. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_sessions_pane.py +0 -0
  145. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_setting_contracts.py +0 -0
  146. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_settings.py +0 -0
  147. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_sidebar.py +0 -0
  148. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_soft_quit.py +0 -0
  149. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_ssh_args.py +0 -0
  150. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_ssh_compat.py +0 -0
  151. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_ssh_display_benchmark.py +0 -0
  152. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_ssh_display_diagnostics.py +0 -0
  153. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_ssh_doctor.py +0 -0
  154. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_status.py +0 -0
  155. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_statusbar.py +0 -0
  156. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_system_deps.py +0 -0
  157. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_terminal_size.py +0 -0
  158. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_terminal_status.py +0 -0
  159. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_termux_local_touch.py +0 -0
  160. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_tmux_binding_manager.py +0 -0
  161. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_tmux_ctl.py +0 -0
  162. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_tmux_health.py +0 -0
  163. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_tmux_integration.py +0 -0
  164. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_tmux_server.py +0 -0
  165. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_tmux_status.py +0 -0
  166. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_tool_panes.py +0 -0
  167. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_transcript.py +0 -0
  168. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_utf8_state.py +0 -0
  169. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_web_demo_recorder.py +0 -0
  170. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_wheel_smoke.py +0 -0
  171. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_widgets.py +0 -0
  172. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_windows_attach_relay.py +0 -0
  173. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_windows_bootstrap.py +0 -0
  174. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_windows_msys2.py +0 -0
  175. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_windows_paths.py +0 -0
  176. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_windows_tmux_lifecycle.py +0 -0
  177. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_windows_ui_transition.py +0 -0
  178. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_windows_wrapper_contract.py +0 -0
  179. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tests/test_yolo_prompt.py +0 -0
  180. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tools/denested_transport_benchmark.py +0 -0
  181. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tools/release_notes.py +0 -0
  182. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tools/wheel_smoke.py +0 -0
  183. {railmux-0.4.0.dev30 → railmux-0.4.0.dev31}/tools/windows_archive_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.0.dev30
3
+ Version: 0.4.0.dev31
4
4
  Summary: Terminal UI to navigate, resume, and start Claude Code and Codex sessions
5
5
  Author-email: Rightglow <zhang.taian@foxmail.com>
6
6
  License-Expression: MIT
@@ -0,0 +1 @@
1
+ __version__ = "0.4.0.dev31"
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
  import os
5
+ import signal
5
6
  import shutil
6
7
  import subprocess
7
8
  import sys
@@ -109,6 +110,9 @@ def _run_tmux_client_with_watchdog(
109
110
  attributes = termios.tcgetattr(sys.stdin.fileno())
110
111
  except (OSError, termios.error):
111
112
  pass
113
+ direct_terminal_size = (
114
+ _interactive_terminal_size() if managed_windows else None
115
+ )
112
116
  started_at = time.monotonic()
113
117
  try:
114
118
  popen_kwargs: dict[str, object] = {"env": env}
@@ -127,7 +131,7 @@ def _run_tmux_client_with_watchdog(
127
131
  *,
128
132
  asynchronous_probe: bool = False,
129
133
  ) -> tuple[int, bool]:
130
- nonlocal expected_target, expected_session_id
134
+ nonlocal direct_terminal_size, expected_target, expected_session_id
131
135
  watchdog = tmux_health.FailureWatchdog.starting(
132
136
  (
133
137
  time.monotonic()
@@ -151,6 +155,28 @@ def _run_tmux_client_with_watchdog(
151
155
  pump = getattr(process, "pump", None)
152
156
  if pump is None:
153
157
  time.sleep(0.25)
158
+ if managed_windows:
159
+ current_size = _interactive_terminal_size()
160
+ if (
161
+ current_size is not None
162
+ and current_size != direct_terminal_size
163
+ ):
164
+ direct_terminal_size = current_size
165
+ pid = getattr(process, "pid", None)
166
+ if (
167
+ isinstance(pid, int)
168
+ and not isinstance(pid, bool)
169
+ and pid > 0
170
+ ):
171
+ try:
172
+ # MSYS2 occasionally misses the native
173
+ # console's resize notification. Signal only
174
+ # this launcher's direct tmux client so it
175
+ # re-reads both dimensions; tmux remains the
176
+ # authority for shared-window sizing.
177
+ os.kill(pid, signal.SIGWINCH)
178
+ except (OSError, ValueError):
179
+ pass
154
180
  else:
155
181
  pump(0.25)
156
182
  now = time.monotonic()
@@ -324,7 +324,13 @@ class InstallReporter:
324
324
  if package_count is not None:
325
325
  self._package_total = int(package_count.group(1))
326
326
  self._package_step = max(1, self._package_total // 10)
327
- self._progress_detail = f"preparing {self._package_total} packages"
327
+ self._progress_detail = (
328
+ f"waiting for the first transfer among "
329
+ f"{self._package_total} packages"
330
+ )
331
+ self.note(
332
+ f"Package transaction resolved: {self._package_total} packages."
333
+ )
328
334
  return
329
335
  total_download = _TOTAL_DOWNLOAD_RE.match(line)
330
336
  if total_download is not None:
@@ -30,7 +30,9 @@ _MIRRORLIST_LIMIT = 256 * 1024
30
30
  _PACMAN_CONF_LIMIT = 256 * 1024
31
31
  _PROBE_BYTES = 256 * 1024
32
32
  _PROBE_TIMEOUT = 8.0
33
+ _PACKAGE_PROBE_BYTES = 256 * 1024
33
34
  _PACKAGE_PROBE_TIMEOUT = 5.0
35
+ _PACKAGE_PROBE_MIN_RATE = 128 * 1024
34
36
  _SWITCH_RATIO = 1.25
35
37
  _FRESHNESS_WINDOW_SECONDS = 6 * 60 * 60
36
38
  _ZSTD_MAGIC = b"\x28\xb5\x2f\xfd"
@@ -91,18 +93,21 @@ def _probe_package_url(
91
93
  url: str,
92
94
  *,
93
95
  opener: Callable[..., object] = urllib.request.urlopen,
96
+ clock: Callable[[], float] = time.monotonic,
94
97
  ) -> None:
95
- """Require one real package prefix instead of trusting database access."""
98
+ """Require a responsive real package path, not only database access."""
96
99
  if urllib.parse.urlsplit(url).scheme.lower() != "https":
97
100
  raise PacmanMirrorError("package URL is not HTTPS")
98
101
  request = urllib.request.Request(
99
102
  url,
100
103
  headers={
101
104
  "Accept-Encoding": "identity",
102
- "Range": "bytes=0-1023",
105
+ "Range": f"bytes=0-{_PACKAGE_PROBE_BYTES - 1}",
103
106
  "User-Agent": "Railmux-pacman-package-probe/1",
104
107
  },
105
108
  )
109
+ started = clock()
110
+ data = bytearray()
106
111
  try:
107
112
  with opener(request, timeout=_PACKAGE_PROBE_TIMEOUT) as response:
108
113
  final_url = getattr(response, "geturl", lambda: request.full_url)()
@@ -111,15 +116,31 @@ def _probe_package_url(
111
116
  status = getattr(response, "status", 200)
112
117
  if status not in (200, 206):
113
118
  raise PacmanMirrorError(f"package returned HTTP {status}")
114
- data = response.read(1024)
119
+ while len(data) < _PACKAGE_PROBE_BYTES:
120
+ chunk = response.read(min(
121
+ 64 * 1024, _PACKAGE_PROBE_BYTES - len(data)
122
+ ))
123
+ if not chunk:
124
+ break
125
+ data.extend(chunk)
126
+ if clock() - started >= _PACKAGE_PROBE_TIMEOUT:
127
+ break
115
128
  except PacmanMirrorError:
116
129
  raise
117
130
  except urllib.error.HTTPError as exc:
118
131
  raise PacmanMirrorError(f"package returned HTTP {exc.code}") from exc
119
132
  except (OSError, urllib.error.URLError, http.client.HTTPException) as exc:
120
133
  raise PacmanMirrorError("package probe failed") from exc
134
+ elapsed = max(clock() - started, 0.001)
135
+ if len(data) < 1024:
136
+ raise PacmanMirrorError("mirror returned too little package data")
121
137
  if not data.startswith(_ZSTD_MAGIC):
122
138
  raise PacmanMirrorError("mirror did not return an MSYS package")
139
+ if (
140
+ elapsed >= _PACKAGE_PROBE_TIMEOUT
141
+ or len(data) / elapsed < _PACKAGE_PROBE_MIN_RATE
142
+ ):
143
+ raise PacmanMirrorError("package transfer was too slow")
123
144
 
124
145
 
125
146
  def probe_pacman_mirror(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.0.dev30
3
+ Version: 0.4.0.dev31
4
4
  Summary: Terminal UI to navigate, resume, and start Claude Code and Codex sessions
5
5
  Author-email: Rightglow <zhang.taian@foxmail.com>
6
6
  License-Expression: MIT
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import signal
2
3
  import subprocess
3
4
  import sys
4
5
  from unittest.mock import MagicMock, patch
@@ -632,6 +633,80 @@ def test_local_tmux_watchdog_exits_and_records_after_consecutive_failures(
632
633
  assert "stopped responding" in capsys.readouterr().err
633
634
 
634
635
 
636
+ def test_managed_windows_direct_attach_notifies_height_only_resize(monkeypatch):
637
+ target = TmuxServerTarget("/tmp/railmux", 77)
638
+
639
+ class DirectClient:
640
+ pid = 321
641
+ returncode = None
642
+
643
+ def poll(self):
644
+ return self.returncode
645
+
646
+ client = DirectClient()
647
+ monkeypatch.setattr(
648
+ "railmux.cli.subprocess.Popen", lambda *_args, **_kwargs: client)
649
+ monkeypatch.setattr("railmux.cli.sys.stdin.isatty", lambda: True)
650
+ monkeypatch.setattr("railmux.cli.sys.stdout.isatty", lambda: True)
651
+ monkeypatch.setattr("railmux.cli.termios.tcgetattr", lambda _fd: ["saved"])
652
+ sizes = [os.terminal_size((120, 40)), os.terminal_size((120, 55))]
653
+ monkeypatch.setattr(
654
+ "railmux.cli.os.get_terminal_size",
655
+ lambda _fd: sizes.pop(0) if len(sizes) > 1 else sizes[0],
656
+ )
657
+
658
+ def finish_after_poll(_seconds):
659
+ client.returncode = 0
660
+
661
+ monkeypatch.setattr("railmux.cli.time.sleep", finish_after_poll)
662
+ monkeypatch.setattr(
663
+ "railmux.provider_paths.running_in_managed_windows_wrapper",
664
+ lambda: True,
665
+ )
666
+ notify = MagicMock()
667
+ monkeypatch.setattr("railmux.cli.os.kill", notify)
668
+
669
+ assert _run_tmux_client_with_watchdog(
670
+ ["tmux", "-L", "railmux"], {}, expected_target=target,
671
+ expected_session_id="$7",
672
+ ) == 0
673
+ notify.assert_called_once_with(321, signal.SIGWINCH)
674
+
675
+
676
+ def test_posix_direct_attach_does_not_poll_or_signal_resize(monkeypatch):
677
+ class DirectClient:
678
+ pid = 321
679
+ returncode = None
680
+
681
+ def poll(self):
682
+ return self.returncode
683
+
684
+ client = DirectClient()
685
+ monkeypatch.setattr(
686
+ "railmux.cli.subprocess.Popen", lambda *_args, **_kwargs: client)
687
+ monkeypatch.setattr("railmux.cli.sys.stdin.isatty", lambda: True)
688
+ monkeypatch.setattr("railmux.cli.termios.tcgetattr", lambda _fd: ["saved"])
689
+
690
+ def finish_after_poll(_seconds):
691
+ client.returncode = 0
692
+
693
+ monkeypatch.setattr("railmux.cli.time.sleep", finish_after_poll)
694
+ monkeypatch.setattr(
695
+ "railmux.provider_paths.running_in_managed_windows_wrapper",
696
+ lambda: False,
697
+ )
698
+ get_size = MagicMock()
699
+ notify = MagicMock()
700
+ monkeypatch.setattr("railmux.cli.os.get_terminal_size", get_size)
701
+ monkeypatch.setattr("railmux.cli.os.kill", notify)
702
+
703
+ assert _run_tmux_client_with_watchdog(
704
+ ["tmux", "-L", "railmux"], {},
705
+ ) == 0
706
+ get_size.assert_not_called()
707
+ notify.assert_not_called()
708
+
709
+
635
710
  def test_abrupt_tmux_client_exit_records_disappeared_server(monkeypatch):
636
711
  target = TmuxServerTarget("/tmp/railmux", 77)
637
712
 
@@ -310,6 +310,7 @@ def test_compact_reporter_surfaces_extraction_and_package_progress(tmp_path):
310
310
  assert "Extracting private runtime: 42%" in rendered
311
311
  assert "Extracting private runtime: 100%" in rendered
312
312
  assert "Package transaction: 48 packages, 57.40 MiB download." in rendered
313
+ assert "Package transaction resolved: 48 packages." in rendered
313
314
  assert "Downloading packages: 1/48" in rendered
314
315
  assert "Verifying downloaded package signatures" in rendered
315
316
  assert "Installing packages: 1/48" in rendered
@@ -10,6 +10,7 @@ from railmux.windows_pacman import (
10
10
  PACMAN_MIRROR_SOURCES,
11
11
  PacmanMirrorError,
12
12
  PacmanMirrorProbe,
13
+ _probe_package_url,
13
14
  deactivate_pacman_hosts,
14
15
  optimize_pacman_mirror,
15
16
  probe_pacman_mirror,
@@ -231,6 +232,40 @@ def test_network_probe_is_bounded_and_rejects_an_https_downgrade():
231
232
  assert requests[0][1] == 8.0
232
233
 
233
234
 
235
+ def test_package_probe_measures_a_real_bounded_transfer():
236
+ requests = []
237
+
238
+ def opener(request, *, timeout):
239
+ requests.append((request, timeout))
240
+ return FakeResponse(
241
+ b"\x28\xb5\x2f\xfd" + b"x" * (256 * 1024),
242
+ final_url="https://mirror.invalid/python.pkg.tar.zst",
243
+ )
244
+
245
+ _probe_package_url(
246
+ "https://mirror.invalid/python.pkg.tar.zst",
247
+ opener=opener,
248
+ clock=lambda: 1.0,
249
+ )
250
+
251
+ assert requests[0][0].get_header("Range") == "bytes=0-262143"
252
+ assert requests[0][1] == 5.0
253
+
254
+
255
+ def test_package_probe_rejects_a_working_but_slow_source():
256
+ times = [0.0, 3.0]
257
+
258
+ with pytest.raises(PacmanMirrorError, match="too slow"):
259
+ _probe_package_url(
260
+ "https://mirror.invalid/python.pkg.tar.zst",
261
+ opener=lambda _request, **_kwargs: FakeResponse(
262
+ b"\x28\xb5\x2f\xfd" + b"x" * (256 * 1024),
263
+ final_url="https://mirror.invalid/python.pkg.tar.zst",
264
+ ),
265
+ clock=lambda: times.pop(0) if len(times) > 1 else times[0],
266
+ )
267
+
268
+
234
269
  def test_optimizer_limits_active_pool_but_reprobes_inactive_candidates(tmp_path):
235
270
  mirrorlist = write_mirrorlist(tmp_path)
236
271
  nju = PACMAN_MIRROR_SOURCES[-1][1]
@@ -1 +0,0 @@
1
- __version__ = "0.4.0.dev30"
File without changes
File without changes
File without changes
File without changes