railmux 0.4.2.dev4__tar.gz → 0.4.2.dev5__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 (208) hide show
  1. {railmux-0.4.2.dev4/src/railmux.egg-info → railmux-0.4.2.dev5}/PKG-INFO +1 -1
  2. railmux-0.4.2.dev5/src/railmux/__init__.py +1 -0
  3. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/models.py +6 -0
  4. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/session_cache.py +54 -0
  5. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/session_index.py +20 -6
  6. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/app.py +82 -2
  7. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5/src/railmux.egg-info}/PKG-INFO +1 -1
  8. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux.egg-info/SOURCES.txt +1 -0
  9. railmux-0.4.2.dev5/tests/test_claude_continuation.py +177 -0
  10. railmux-0.4.2.dev4/src/railmux/__init__.py +0 -1
  11. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/LICENSE +0 -0
  12. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/MANIFEST.in +0 -0
  13. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/README.md +0 -0
  14. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/pyproject.toml +0 -0
  15. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/setup.cfg +0 -0
  16. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/__main__.py +0 -0
  17. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/atomic_file.py +0 -0
  18. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/background_index.py +0 -0
  19. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/cli.py +0 -0
  20. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/codex_index.py +0 -0
  21. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/config.py +0 -0
  22. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/config_cli.py +0 -0
  23. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/diagnostic_contract.py +0 -0
  24. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/diagnostics.py +0 -0
  25. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/discovery.py +0 -0
  26. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/display_transport.py +0 -0
  27. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/entrypoint.py +0 -0
  28. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fast_display_client.py +0 -0
  29. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fast_display_history.py +0 -0
  30. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fast_display_input.py +0 -0
  31. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fast_display_protocol.py +0 -0
  32. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fast_display_server.py +0 -0
  33. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/favorites.py +0 -0
  34. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fuzzy.py +0 -0
  35. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/help_workspace.py +0 -0
  36. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/image_paste.py +0 -0
  37. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/launcher.py +0 -0
  38. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/legacy_sessions.py +0 -0
  39. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/local_clipboard.py +0 -0
  40. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/local_open.py +0 -0
  41. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/modes.py +0 -0
  42. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/mouse_manager.py +0 -0
  43. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/orphan_marker.py +0 -0
  44. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/pane_surface.py +0 -0
  45. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/path_codec.py +0 -0
  46. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/preview_pager.py +0 -0
  47. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/project_favorites.py +0 -0
  48. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/provider_paths.py +0 -0
  49. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/release_version.py +0 -0
  50. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/remote_config.py +0 -0
  51. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/renames.py +0 -0
  52. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/restart_state.py +0 -0
  53. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/runtime_config.py +0 -0
  54. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/scroll_agent.py +0 -0
  55. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/scroll_manager.py +0 -0
  56. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/selection_isolation.py +0 -0
  57. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/self_update.py +0 -0
  58. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/session_lease.py +0 -0
  59. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/setting_contracts.py +0 -0
  60. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/settings.py +0 -0
  61. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ssh_args.py +0 -0
  62. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ssh_compat.py +0 -0
  63. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ssh_display_diagnostics.py +0 -0
  64. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ssh_doctor.py +0 -0
  65. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ssh_preflight.py +0 -0
  66. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/system_deps.py +0 -0
  67. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/terminal_screen.py +0 -0
  68. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/terminal_status.py +0 -0
  69. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tmux_binding_manager.py +0 -0
  70. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tmux_capabilities.py +0 -0
  71. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tmux_ctl.py +0 -0
  72. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tmux_health.py +0 -0
  73. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tmux_server.py +0 -0
  74. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tool_panes.py +0 -0
  75. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/transcript.py +0 -0
  76. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/__init__.py +0 -0
  77. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/_widgets.py +0 -0
  78. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/keymap.py +0 -0
  79. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/modals.py +0 -0
  80. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/projects_pane.py +0 -0
  81. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/running_pane.py +0 -0
  82. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/sessions_pane.py +0 -0
  83. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/sidebar.py +0 -0
  84. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/statusbar.py +0 -0
  85. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/workspace.py +0 -0
  86. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_attach_relay.py +0 -0
  87. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_bootstrap.py +0 -0
  88. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_install_log.py +0 -0
  89. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_msys2.py +0 -0
  90. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_pacman.py +0 -0
  91. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_paths.py +0 -0
  92. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_tmux_lifecycle.py +0 -0
  93. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_ui_transition.py +0 -0
  94. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux.egg-info/dependency_links.txt +0 -0
  95. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux.egg-info/entry_points.txt +0 -0
  96. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux.egg-info/requires.txt +0 -0
  97. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux.egg-info/top_level.txt +0 -0
  98. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/app_test_harness.py +0 -0
  99. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_agent_workspace.py +0 -0
  100. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_app_codex_placeholder.py +0 -0
  101. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_app_quit.py +0 -0
  102. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_app_restart_restore.py +0 -0
  103. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_app_running_recovery.py +0 -0
  104. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_atomic_file.py +0 -0
  105. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_background_index.py +0 -0
  106. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_cli.py +0 -0
  107. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_code_map.py +0 -0
  108. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_codex_delete.py +0 -0
  109. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_codex_index.py +0 -0
  110. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_codex_lineage.py +0 -0
  111. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_codex_transcript.py +0 -0
  112. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_config.py +0 -0
  113. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_config_cli.py +0 -0
  114. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_denested_transport_benchmark.py +0 -0
  115. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_diagnostics.py +0 -0
  116. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_discovery.py +0 -0
  117. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_display_transport.py +0 -0
  118. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_bootstrap.py +0 -0
  119. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_history.py +0 -0
  120. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_protocol.py +0 -0
  121. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_selection.py +0 -0
  122. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_server.py +0 -0
  123. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_terminal.py +0 -0
  124. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_focus_highlight.py +0 -0
  125. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fullscreen_binding.py +0 -0
  126. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fuzzy.py +0 -0
  127. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_help_agent.py +0 -0
  128. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_help_workspace.py +0 -0
  129. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_history_cleanup.py +0 -0
  130. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_image_paste.py +0 -0
  131. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_keymap.py +0 -0
  132. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_launcher.py +0 -0
  133. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_layout_profile.py +0 -0
  134. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_legacy_sessions.py +0 -0
  135. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_local_clipboard.py +0 -0
  136. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_local_open.py +0 -0
  137. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_modals.py +0 -0
  138. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_mode_selection.py +0 -0
  139. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_modes.py +0 -0
  140. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_mouse_manager.py +0 -0
  141. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_new_project.py +0 -0
  142. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_options.py +0 -0
  143. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_orphan_recovery.py +0 -0
  144. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_pane_surface.py +0 -0
  145. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_path_codec.py +0 -0
  146. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_preview_pager.py +0 -0
  147. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_project_favorites.py +0 -0
  148. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_projects_pane.py +0 -0
  149. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_provider_paths.py +0 -0
  150. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_release_notes.py +0 -0
  151. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_release_version.py +0 -0
  152. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_release_workflow.py +0 -0
  153. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_remote_config.py +0 -0
  154. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_renames.py +0 -0
  155. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_restart_state.py +0 -0
  156. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_running_pane.py +0 -0
  157. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_running_sort.py +0 -0
  158. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_runtime_config.py +0 -0
  159. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_scroll_agent.py +0 -0
  160. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_scroll_manager.py +0 -0
  161. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_selection_isolation.py +0 -0
  162. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_self_update.py +0 -0
  163. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_session_cache.py +0 -0
  164. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_session_index.py +0 -0
  165. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_session_lease.py +0 -0
  166. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_sessions_pane.py +0 -0
  167. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_setting_contracts.py +0 -0
  168. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_settings.py +0 -0
  169. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_sidebar.py +0 -0
  170. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_ssh_args.py +0 -0
  171. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_ssh_compat.py +0 -0
  172. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_ssh_display_benchmark.py +0 -0
  173. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_ssh_display_diagnostics.py +0 -0
  174. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_ssh_doctor.py +0 -0
  175. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_status.py +0 -0
  176. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_statusbar.py +0 -0
  177. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_system_deps.py +0 -0
  178. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_terminal_size.py +0 -0
  179. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_terminal_status.py +0 -0
  180. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_termux_local_touch.py +0 -0
  181. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_binding_manager.py +0 -0
  182. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_capabilities.py +0 -0
  183. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_ctl.py +0 -0
  184. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_health.py +0 -0
  185. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_integration.py +0 -0
  186. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_server.py +0 -0
  187. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_status.py +0 -0
  188. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tool_panes.py +0 -0
  189. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_transcript.py +0 -0
  190. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_utf8_state.py +0 -0
  191. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_web_demo_recorder.py +0 -0
  192. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_wheel_smoke.py +0 -0
  193. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_widgets.py +0 -0
  194. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_attach_relay.py +0 -0
  195. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_bootstrap.py +0 -0
  196. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_install_log.py +0 -0
  197. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_msys2.py +0 -0
  198. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_pacman.py +0 -0
  199. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_paths.py +0 -0
  200. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_synchronized_screen.py +0 -0
  201. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_tmux_lifecycle.py +0 -0
  202. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_ui_transition.py +0 -0
  203. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_wrapper_contract.py +0 -0
  204. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_yolo_prompt.py +0 -0
  205. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tools/denested_transport_benchmark.py +0 -0
  206. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tools/release_notes.py +0 -0
  207. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tools/wheel_smoke.py +0 -0
  208. {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/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.2.dev4
3
+ Version: 0.4.2.dev5
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.2.dev5"
@@ -84,6 +84,12 @@ class SessionMeta:
84
84
  # A Railmux lease observed in the same shared provider history root. This
85
85
  # is a read-time UI overlay, never provider transcript metadata.
86
86
  remote_owner: str | None = None
87
+ # Successor named by a final Claude ``continued-in`` record: the live
88
+ # conversation moved to that session file. Always None for Codex.
89
+ continued_in_id: str | None = None
90
+ # Claude Code background-job (``sessionKind: bg``) session. Listings hide
91
+ # these unless one is the ``continued-in`` successor of a visible session.
92
+ background: bool = False
87
93
 
88
94
  @property
89
95
  def display_title(self) -> str:
@@ -33,6 +33,9 @@ class SessionCache:
33
33
  # User-assigned titles, overlaid at read time so they survive Claude
34
34
  # Code rewriting its own ai-title record every turn.
35
35
  self._renames = renames
36
+ # Claude ``continued-in`` links seen by listings: predecessor id ->
37
+ # successor id. The App uses them to move live bindings and favorites.
38
+ self.continuations: dict[str, str] = {}
36
39
 
37
40
  def list_sessions(
38
41
  self, project: Project, top_n: int = _DEFAULT_TOP_N
@@ -86,9 +89,60 @@ class SessionCache:
86
89
  if stale.parent == project.claude_dir and stale not in current_paths:
87
90
  del self._entries[stale]
88
91
 
92
+ results = self._hide_continued(project, results)
89
93
  results.sort(key=lambda s: s.last_mtime, reverse=True)
90
94
  return results
91
95
 
96
+ def _hide_continued(
97
+ self, project: Project, results: list[SessionMeta]
98
+ ) -> list[SessionMeta]:
99
+ """Drop background sessions and sessions continued in a successor.
100
+
101
+ Claude Code copies the whole conversation into the successor, so the
102
+ predecessor is a stale duplicate. A user rename moves forward with it.
103
+ A background successor of a visible session is the same interactive
104
+ conversation now hosted by Claude's daemon, so it stays visible.
105
+ """
106
+ by_id = {meta.session_id: meta for meta in results}
107
+ visible = {meta.session_id for meta in results if not meta.background}
108
+ pending = list(visible)
109
+ while pending:
110
+ successor = by_id[pending.pop()].continued_in_id
111
+ if successor in by_id and successor not in visible:
112
+ visible.add(successor)
113
+ pending.append(successor)
114
+ results = [meta for meta in results if meta.session_id in visible]
115
+ hidden: dict[str, str] = {}
116
+ for meta in results:
117
+ successor = meta.continued_in_id
118
+ if (
119
+ successor is not None
120
+ and successor != meta.session_id
121
+ and (project.claude_dir / f"{successor}.jsonl").is_file()
122
+ ):
123
+ hidden[meta.session_id] = successor
124
+ if not hidden:
125
+ return results
126
+ self.continuations.update(hidden)
127
+ carried: set[str] = set()
128
+ if self._renames is not None:
129
+ # Walk each chain forward so A -> B -> C carries A's rename to C.
130
+ for _ in range(len(hidden)):
131
+ changed = False
132
+ for predecessor, successor in hidden.items():
133
+ title = self._renames.get(predecessor)
134
+ if title and not self._renames.get(successor):
135
+ self._renames.set(successor, title)
136
+ carried.add(successor)
137
+ changed = True
138
+ if not changed:
139
+ break
140
+ return [
141
+ self._with_override(meta) if meta.session_id in carried else meta
142
+ for meta in results
143
+ if meta.session_id not in hidden
144
+ ]
145
+
92
146
  def get(self, project: Project, session_id: str) -> SessionMeta | None:
93
147
  """Cache-backed lookup of a single session's metadata by id.
94
148
 
@@ -43,6 +43,7 @@ class _SessionScanState:
43
43
  last_rtype: str = ""
44
44
  last_stop_reason: str = ""
45
45
  background: bool = False
46
+ continued_in: str | None = None
46
47
 
47
48
  def clone(self) -> _SessionScanState:
48
49
  return _SessionScanState(
@@ -62,6 +63,7 @@ class _SessionScanState:
62
63
  last_rtype=self.last_rtype,
63
64
  last_stop_reason=self.last_stop_reason,
64
65
  background=self.background,
66
+ continued_in=self.continued_in,
65
67
  )
66
68
 
67
69
 
@@ -169,14 +171,25 @@ def _can_resume(fd: int, st: os.stat_result, state: _SessionScanState) -> bool:
169
171
 
170
172
 
171
173
  def _consume_record(state: _SessionScanState, rec: object) -> None:
172
- if not isinstance(rec, dict) or state.background:
174
+ if not isinstance(rec, dict):
173
175
  return
174
176
  # Background-job sessions are not interactive — they can't be resumed in
175
- # a terminal and shouldn't appear in the sidebar.
177
+ # a terminal and shouldn't appear in the sidebar. Their metadata is still
178
+ # collected because Claude Code also hosts a live conversation that was
179
+ # ``continued-in`` a daemon session this way; listings decide visibility.
176
180
  if rec.get("sessionKind") == "bg":
177
181
  state.background = True
178
- return
179
182
  rtype = rec.get("type")
183
+ # Claude Code ends a session file with ``continued-in`` when the live
184
+ # conversation moves to a new session. Only a final record counts.
185
+ successor = rec.get("continuedInSessionId")
186
+ state.continued_in = (
187
+ successor
188
+ if rtype == "continued-in"
189
+ and isinstance(successor, str)
190
+ and _looks_like_uuid(successor)
191
+ else None
192
+ )
180
193
  if rtype == "ai-title":
181
194
  state.title = rec.get("aiTitle") or state.title
182
195
  # NOTE: "last-prompt" is deliberately NOT treated as a turn. Claude Code
@@ -235,8 +248,6 @@ def _meta_from_state(
235
248
  state: _SessionScanState,
236
249
  st: os.stat_result,
237
250
  ) -> SessionMeta | None:
238
- if state.background:
239
- return None
240
251
  assistant_count = len(state.assistant_token_totals)
241
252
  message_count = state.user_count + assistant_count
242
253
  if message_count == 0 or (state.user_count > 0 and assistant_count == 0):
@@ -277,6 +288,8 @@ def _meta_from_state(
277
288
  last_user_message=preview,
278
289
  status=status,
279
290
  pending_tool=pending_tool,
291
+ continued_in_id=state.continued_in,
292
+ background=state.background,
280
293
  )
281
294
 
282
295
 
@@ -352,7 +365,8 @@ def _scan_session_incremental(
352
365
 
353
366
 
354
367
  def _scan_session(project: Project, jsonl_path: Path) -> SessionMeta | None:
355
- return _scan_session_incremental(project, jsonl_path).meta
368
+ meta = _scan_session_incremental(project, jsonl_path).meta
369
+ return None if meta is not None and meta.background else meta
356
370
 
357
371
 
358
372
  def _looks_like_uuid(s: str) -> bool:
@@ -426,6 +426,7 @@ def _session_lease_failure_description(category: str | None) -> str:
426
426
  # Unlike the short-lived runtime state file, a session option lives exactly as
427
427
  # long as the tmux session and survives Railmux restarts without renaming it.
428
428
  _SESSION_BINDING_OPTION = "@railmux_binding_v1"
429
+ _CLAUDE_CONTINUATION_MAX_HOPS = 8
429
430
  _HELP_SESSION_OPTION = "@railmux_help_v1"
430
431
  _HELP_POLICY_VERSION = "read-only-auto-v2"
431
432
 
@@ -9194,6 +9195,7 @@ class App:
9194
9195
  # stays stuck True, defeating the 3s project-scan cache. Codex
9195
9196
  # resolution must run too or neither ever clears.
9196
9197
  self._resolve_placeholders(projects, server)
9198
+ self._follow_claude_continuations()
9197
9199
  running_ids = self._running_session_ids()
9198
9200
  if self._selected_project is not None:
9199
9201
  matched = next((p for p in projects if p.encoded_name == self._selected_project.encoded_name), None)
@@ -10228,6 +10230,83 @@ class App:
10228
10230
  # activity without enabling per-poll row movement.
10229
10231
  self._running_sort_ts = 0.0
10230
10232
 
10233
+ def _follow_claude_continuations(self) -> None:
10234
+ """Move live Claude bindings and favorites to ``continued-in`` successors.
10235
+
10236
+ Claude Code can continue a live conversation in a new session file and
10237
+ end the old file with one ``continued-in`` record. The pane is the
10238
+ same, so re-key its running entry to the newest successor: the binding,
10239
+ Preview marker, lease and Sessions row then follow the live writer.
10240
+ """
10241
+ cache = getattr(self, "_session_cache", None)
10242
+ if cache is None:
10243
+ return
10244
+ continuations = getattr(cache, "continuations", {})
10245
+ if continuations and getattr(self, "_favorites", None) is not None:
10246
+ favorites = self._favorites.get_ids()
10247
+ carried: set[str] = set()
10248
+ for predecessor in favorites & continuations.keys():
10249
+ successor = continuations[predecessor]
10250
+ for _ in range(_CLAUDE_CONTINUATION_MAX_HOPS):
10251
+ if successor not in continuations:
10252
+ break
10253
+ successor = continuations[successor]
10254
+ if successor not in favorites:
10255
+ carried.add(successor)
10256
+ if carried:
10257
+ self._favorites.set_many(carried, True)
10258
+ claimed = self._running_session_ids() | set(self._running)
10259
+ for r in list(self._running.values()):
10260
+ registered_mode = self._modes().for_tmux_name(r.tmux_name)
10261
+ session_type = (
10262
+ registered_mode.session_type if registered_mode else r.session_type)
10263
+ if (
10264
+ session_type != "claude"
10265
+ or r.is_placeholder
10266
+ or r.is_legacy
10267
+ or r.project is None
10268
+ ):
10269
+ continue
10270
+ meta = cache.get(r.project, r.key)
10271
+ successor_meta = None
10272
+ seen = {r.key}
10273
+ while (
10274
+ meta is not None
10275
+ and meta.continued_in_id is not None
10276
+ and meta.continued_in_id not in seen
10277
+ and len(seen) <= _CLAUDE_CONTINUATION_MAX_HOPS
10278
+ ):
10279
+ seen.add(meta.continued_in_id)
10280
+ meta = cache.get(r.project, meta.continued_in_id)
10281
+ if meta is not None:
10282
+ successor_meta = meta
10283
+ if successor_meta is None:
10284
+ continue
10285
+ successor = successor_meta.session_id
10286
+ if successor in claimed:
10287
+ continue
10288
+ if r.orphan is not None:
10289
+ resolved_marker = r.orphan.resolved(successor)
10290
+ if not self._write_orphan_marker(resolved_marker):
10291
+ continue
10292
+ r.orphan = resolved_marker
10293
+ del self._running[r.key]
10294
+ r.key = successor
10295
+ r.provider_session_id = None
10296
+ r.label = f"{r.project.display_name}/{successor_meta.display_title}"
10297
+ r.status = successor_meta.status
10298
+ r.last_mtime = successor_meta.last_mtime
10299
+ r.attention = successor_meta.attention
10300
+ self._running[successor] = r
10301
+ self._stamp_running(r)
10302
+ self._record_session_lease_result(
10303
+ r, self._ensure_running_session_lease(r, successor_meta))
10304
+ claimed.add(successor)
10305
+ displayed_slot = self._agent_workspace().slot_for_agent(r.tmux_name)
10306
+ if displayed_slot is not None:
10307
+ self._set_slot_active_target(
10308
+ displayed_slot, successor, r.tmux_name)
10309
+
10231
10310
  def _correlate_codex_rollout(self, r: "_Running") -> set[str] | None:
10232
10311
  """Exact child→rollout correlation for a Codex placeholder (#12).
10233
10312
 
@@ -10284,11 +10363,12 @@ class App:
10284
10363
  return self._codex_representative(session_id)
10285
10364
  if project is None:
10286
10365
  return None
10287
- from railmux.session_index import _scan_session
10366
+ from railmux.session_index import _scan_session_incremental
10288
10367
  jsonl_path = project.claude_dir / f"{session_id}.jsonl"
10289
10368
  if not jsonl_path.exists():
10290
10369
  return None
10291
- return _scan_session(project, jsonl_path)
10370
+ # An explicit id may name a background ``continued-in`` successor.
10371
+ return _scan_session_incremental(project, jsonl_path).meta
10292
10372
 
10293
10373
  # --- kill / delete session ---
10294
10374
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.2.dev4
3
+ Version: 0.4.2.dev5
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
@@ -99,6 +99,7 @@ tests/test_app_restart_restore.py
99
99
  tests/test_app_running_recovery.py
100
100
  tests/test_atomic_file.py
101
101
  tests/test_background_index.py
102
+ tests/test_claude_continuation.py
102
103
  tests/test_cli.py
103
104
  tests/test_code_map.py
104
105
  tests/test_codex_delete.py
@@ -0,0 +1,177 @@
1
+ """Claude ``continued-in`` session files: listing, renames and live re-keying."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from unittest.mock import MagicMock
8
+
9
+ import pytest
10
+
11
+ from railmux.session_cache import SessionCache
12
+ from railmux.session_index import _scan_session
13
+ from railmux.ui.app import App, _Running
14
+
15
+ from tests.app_test_harness import (
16
+ _project,
17
+ isolate_tmux_identity_stamps as isolate_tmux_identity_stamps,
18
+ )
19
+
20
+
21
+ pytestmark = pytest.mark.usefixtures("isolate_tmux_identity_stamps")
22
+
23
+ OLD = "02b59000-6bb4-4ec5-bdca-24f7a96b0467"
24
+ MID = "47fca075-9cb8-44fb-a314-d57ef2256ad9"
25
+ NEW = "96fd3642-6c17-4c9b-8129-08d3d9db3e8a"
26
+
27
+
28
+ def _turn() -> list[dict]:
29
+ return [
30
+ {"type": "user", "message": {"role": "user", "content": "hello"}},
31
+ {"type": "assistant", "message": {
32
+ "role": "assistant", "id": "m1", "stop_reason": "end_turn",
33
+ "content": [{"type": "text", "text": "hi"}],
34
+ }},
35
+ ]
36
+
37
+
38
+ def _continued(successor: str) -> dict:
39
+ return {
40
+ "type": "continued-in",
41
+ "sessionId": "previous",
42
+ "continuedInSessionId": successor,
43
+ }
44
+
45
+
46
+ def _write(directory: Path, session_id: str, records: list[dict]) -> Path:
47
+ directory.mkdir(parents=True, exist_ok=True)
48
+ path = directory / f"{session_id}.jsonl"
49
+ path.write_text("".join(json.dumps(record) + "\n" for record in records))
50
+ return path
51
+
52
+
53
+ class _MemoryRenames:
54
+ def __init__(self, titles: dict[str, str] | None = None) -> None:
55
+ self.titles = dict(titles or {})
56
+
57
+ def get(self, session_id: str) -> str | None:
58
+ return self.titles.get(session_id)
59
+
60
+ def set(self, session_id: str, title: str) -> None:
61
+ self.titles[session_id] = title
62
+
63
+
64
+ def test_scan_records_only_a_final_continued_in(tmp_path):
65
+ project = _project(claude_dir=tmp_path)
66
+ final = _write(tmp_path, OLD, _turn() + [_continued(NEW)])
67
+ resumed = _write(tmp_path, MID, _turn() + [_continued(NEW)] + _turn())
68
+ invalid = _write(tmp_path, NEW, _turn() + [_continued("../escape")])
69
+
70
+ assert _scan_session(project, final).continued_in_id == NEW
71
+ assert _scan_session(project, resumed).continued_in_id is None
72
+ assert _scan_session(project, invalid).continued_in_id is None
73
+
74
+
75
+ def test_listing_hides_continued_sessions_and_carries_rename(tmp_path):
76
+ project = _project(claude_dir=tmp_path)
77
+ _write(tmp_path, OLD, _turn() + [_continued(MID)])
78
+ _write(tmp_path, MID, _turn() + [_continued(NEW)])
79
+ _write(tmp_path, NEW, _turn())
80
+ renames = _MemoryRenames({OLD: "Rubin bringup"})
81
+ cache = SessionCache(renames)
82
+
83
+ sessions = cache.list_sessions(project)
84
+
85
+ assert [session.session_id for session in sessions] == [NEW]
86
+ assert sessions[0].title == "Rubin bringup"
87
+ assert renames.titles[NEW] == "Rubin bringup"
88
+ assert cache.continuations == {OLD: MID, MID: NEW}
89
+
90
+
91
+ def test_listing_keeps_session_whose_successor_is_missing(tmp_path):
92
+ project = _project(claude_dir=tmp_path)
93
+ _write(tmp_path, OLD, _turn() + [_continued(NEW)])
94
+
95
+ sessions = SessionCache().list_sessions(project)
96
+
97
+ assert [session.session_id for session in sessions] == [OLD]
98
+
99
+
100
+ def _app(project, running: dict[str, _Running]) -> App:
101
+ app = App.__new__(App)
102
+ app._running = running
103
+ app._session_cache = SessionCache()
104
+ app._session_cache.list_sessions(project)
105
+ app._favorites = MagicMock()
106
+ app._favorites.get_ids.return_value = {OLD}
107
+ app._stamp_running = MagicMock(return_value=True)
108
+ app._ensure_running_session_lease = MagicMock(return_value=True)
109
+ app._record_session_lease_result = MagicMock()
110
+ app._set_slot_active_target = MagicMock()
111
+ return app
112
+
113
+
114
+ def test_running_claude_entry_follows_continuation_chain(tmp_path):
115
+ project = _project(claude_dir=tmp_path)
116
+ _write(tmp_path, OLD, _turn() + [_continued(MID)])
117
+ _write(tmp_path, MID, _turn() + [_continued(NEW)])
118
+ _write(tmp_path, NEW, _turn())
119
+ running = _Running(
120
+ key=OLD, tmux_name="cc-new---b14c77-2", label="x", project=project)
121
+ app = _app(project, {OLD: running})
122
+ app._agent_workspace().target.agent_tmux_name = running.tmux_name
123
+
124
+ app._follow_claude_continuations()
125
+
126
+ assert app._running == {NEW: running}
127
+ assert running.key == NEW and running.tmux_name == "cc-new---b14c77-2"
128
+ app._stamp_running.assert_called_once_with(running)
129
+ lease_session = app._ensure_running_session_lease.call_args.args[1]
130
+ assert lease_session.session_id == NEW
131
+ app._set_slot_active_target.assert_called_once()
132
+ assert app._set_slot_active_target.call_args.args[1:] == (
133
+ NEW, running.tmux_name)
134
+ app._favorites.set_many.assert_called_once_with({NEW}, True)
135
+
136
+
137
+ def test_running_entry_stays_when_successor_is_already_running(tmp_path):
138
+ project = _project(claude_dir=tmp_path)
139
+ _write(tmp_path, OLD, _turn() + [_continued(NEW)])
140
+ _write(tmp_path, NEW, _turn())
141
+ old = _Running(key=OLD, tmux_name="cc-a", label="a", project=project)
142
+ new = _Running(key=NEW, tmux_name="cc-b", label="b", project=project)
143
+ app = _app(project, {OLD: old, NEW: new})
144
+
145
+ app._follow_claude_continuations()
146
+
147
+ assert app._running == {OLD: old, NEW: new}
148
+ app._stamp_running.assert_not_called()
149
+
150
+
151
+ def _background(records: list[dict]) -> list[dict]:
152
+ return [{**record, "sessionKind": "bg"} for record in records]
153
+
154
+
155
+ def test_background_successor_of_visible_session_stays_listed(tmp_path):
156
+ project = _project(claude_dir=tmp_path)
157
+ _write(tmp_path, OLD, _turn() + [_continued(NEW)])
158
+ _write(tmp_path, NEW, _background(_turn()))
159
+ _write(tmp_path, MID, _background(_turn()))
160
+
161
+ sessions = SessionCache().list_sessions(project)
162
+
163
+ assert [session.session_id for session in sessions] == [NEW]
164
+ assert sessions[0].background
165
+ assert _scan_session(project, tmp_path / f"{NEW}.jsonl") is None
166
+
167
+
168
+ def test_running_entry_follows_background_successor(tmp_path):
169
+ project = _project(claude_dir=tmp_path)
170
+ _write(tmp_path, OLD, _turn() + [_continued(NEW)])
171
+ _write(tmp_path, NEW, _background(_turn()))
172
+ running = _Running(key=OLD, tmux_name="cc-a", label="a", project=project)
173
+ app = _app(project, {OLD: running})
174
+
175
+ app._follow_claude_continuations()
176
+
177
+ assert app._running == {NEW: running}
@@ -1 +0,0 @@
1
- __version__ = "0.4.2.dev4"
File without changes
File without changes
File without changes
File without changes