railmux 0.4.2.dev3__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.dev3/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.dev3 → railmux-0.4.2.dev5}/src/railmux/fast_display_server.py +84 -3
  4. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/models.py +6 -0
  5. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/session_cache.py +54 -0
  6. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/session_index.py +20 -6
  7. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/tmux_server.py +66 -6
  8. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ui/app.py +82 -2
  9. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5/src/railmux.egg-info}/PKG-INFO +1 -1
  10. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux.egg-info/SOURCES.txt +1 -0
  11. railmux-0.4.2.dev5/tests/test_claude_continuation.py +177 -0
  12. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_fast_display_server.py +79 -0
  13. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_tmux_server.py +60 -0
  14. railmux-0.4.2.dev3/src/railmux/__init__.py +0 -1
  15. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/LICENSE +0 -0
  16. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/MANIFEST.in +0 -0
  17. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/README.md +0 -0
  18. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/pyproject.toml +0 -0
  19. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/setup.cfg +0 -0
  20. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/__main__.py +0 -0
  21. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/atomic_file.py +0 -0
  22. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/background_index.py +0 -0
  23. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/cli.py +0 -0
  24. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/codex_index.py +0 -0
  25. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/config.py +0 -0
  26. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/config_cli.py +0 -0
  27. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/diagnostic_contract.py +0 -0
  28. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/diagnostics.py +0 -0
  29. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/discovery.py +0 -0
  30. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/display_transport.py +0 -0
  31. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/entrypoint.py +0 -0
  32. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/fast_display_client.py +0 -0
  33. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/fast_display_history.py +0 -0
  34. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/fast_display_input.py +0 -0
  35. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/fast_display_protocol.py +0 -0
  36. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/favorites.py +0 -0
  37. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/fuzzy.py +0 -0
  38. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/help_workspace.py +0 -0
  39. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/image_paste.py +0 -0
  40. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/launcher.py +0 -0
  41. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/legacy_sessions.py +0 -0
  42. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/local_clipboard.py +0 -0
  43. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/local_open.py +0 -0
  44. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/modes.py +0 -0
  45. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/mouse_manager.py +0 -0
  46. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/orphan_marker.py +0 -0
  47. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/pane_surface.py +0 -0
  48. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/path_codec.py +0 -0
  49. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/preview_pager.py +0 -0
  50. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/project_favorites.py +0 -0
  51. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/provider_paths.py +0 -0
  52. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/release_version.py +0 -0
  53. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/remote_config.py +0 -0
  54. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/renames.py +0 -0
  55. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/restart_state.py +0 -0
  56. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/runtime_config.py +0 -0
  57. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/scroll_agent.py +0 -0
  58. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/scroll_manager.py +0 -0
  59. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/selection_isolation.py +0 -0
  60. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/self_update.py +0 -0
  61. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/session_lease.py +0 -0
  62. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/setting_contracts.py +0 -0
  63. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/settings.py +0 -0
  64. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ssh_args.py +0 -0
  65. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ssh_compat.py +0 -0
  66. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ssh_display_diagnostics.py +0 -0
  67. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ssh_doctor.py +0 -0
  68. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ssh_preflight.py +0 -0
  69. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/system_deps.py +0 -0
  70. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/terminal_screen.py +0 -0
  71. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/terminal_status.py +0 -0
  72. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/tmux_binding_manager.py +0 -0
  73. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/tmux_capabilities.py +0 -0
  74. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/tmux_ctl.py +0 -0
  75. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/tmux_health.py +0 -0
  76. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/tool_panes.py +0 -0
  77. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/transcript.py +0 -0
  78. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ui/__init__.py +0 -0
  79. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ui/_widgets.py +0 -0
  80. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ui/keymap.py +0 -0
  81. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ui/modals.py +0 -0
  82. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ui/projects_pane.py +0 -0
  83. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ui/running_pane.py +0 -0
  84. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ui/sessions_pane.py +0 -0
  85. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ui/sidebar.py +0 -0
  86. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ui/statusbar.py +0 -0
  87. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/ui/workspace.py +0 -0
  88. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/windows_attach_relay.py +0 -0
  89. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/windows_bootstrap.py +0 -0
  90. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/windows_install_log.py +0 -0
  91. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/windows_msys2.py +0 -0
  92. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/windows_pacman.py +0 -0
  93. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/windows_paths.py +0 -0
  94. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/windows_tmux_lifecycle.py +0 -0
  95. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux/windows_ui_transition.py +0 -0
  96. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux.egg-info/dependency_links.txt +0 -0
  97. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux.egg-info/entry_points.txt +0 -0
  98. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux.egg-info/requires.txt +0 -0
  99. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/src/railmux.egg-info/top_level.txt +0 -0
  100. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/app_test_harness.py +0 -0
  101. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_agent_workspace.py +0 -0
  102. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_app_codex_placeholder.py +0 -0
  103. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_app_quit.py +0 -0
  104. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_app_restart_restore.py +0 -0
  105. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_app_running_recovery.py +0 -0
  106. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_atomic_file.py +0 -0
  107. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_background_index.py +0 -0
  108. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_cli.py +0 -0
  109. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_code_map.py +0 -0
  110. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_codex_delete.py +0 -0
  111. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_codex_index.py +0 -0
  112. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_codex_lineage.py +0 -0
  113. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_codex_transcript.py +0 -0
  114. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_config.py +0 -0
  115. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_config_cli.py +0 -0
  116. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_denested_transport_benchmark.py +0 -0
  117. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_diagnostics.py +0 -0
  118. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_discovery.py +0 -0
  119. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_display_transport.py +0 -0
  120. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_fast_display_bootstrap.py +0 -0
  121. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_fast_display_history.py +0 -0
  122. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_fast_display_protocol.py +0 -0
  123. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_fast_display_selection.py +0 -0
  124. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_fast_display_terminal.py +0 -0
  125. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_focus_highlight.py +0 -0
  126. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_fullscreen_binding.py +0 -0
  127. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_fuzzy.py +0 -0
  128. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_help_agent.py +0 -0
  129. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_help_workspace.py +0 -0
  130. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_history_cleanup.py +0 -0
  131. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_image_paste.py +0 -0
  132. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_keymap.py +0 -0
  133. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_launcher.py +0 -0
  134. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_layout_profile.py +0 -0
  135. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_legacy_sessions.py +0 -0
  136. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_local_clipboard.py +0 -0
  137. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_local_open.py +0 -0
  138. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_modals.py +0 -0
  139. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_mode_selection.py +0 -0
  140. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_modes.py +0 -0
  141. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_mouse_manager.py +0 -0
  142. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_new_project.py +0 -0
  143. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_options.py +0 -0
  144. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_orphan_recovery.py +0 -0
  145. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_pane_surface.py +0 -0
  146. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_path_codec.py +0 -0
  147. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_preview_pager.py +0 -0
  148. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_project_favorites.py +0 -0
  149. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_projects_pane.py +0 -0
  150. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_provider_paths.py +0 -0
  151. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_release_notes.py +0 -0
  152. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_release_version.py +0 -0
  153. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_release_workflow.py +0 -0
  154. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_remote_config.py +0 -0
  155. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_renames.py +0 -0
  156. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_restart_state.py +0 -0
  157. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_running_pane.py +0 -0
  158. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_running_sort.py +0 -0
  159. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_runtime_config.py +0 -0
  160. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_scroll_agent.py +0 -0
  161. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_scroll_manager.py +0 -0
  162. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_selection_isolation.py +0 -0
  163. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_self_update.py +0 -0
  164. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_session_cache.py +0 -0
  165. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_session_index.py +0 -0
  166. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_session_lease.py +0 -0
  167. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_sessions_pane.py +0 -0
  168. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_setting_contracts.py +0 -0
  169. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_settings.py +0 -0
  170. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_sidebar.py +0 -0
  171. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_ssh_args.py +0 -0
  172. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_ssh_compat.py +0 -0
  173. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_ssh_display_benchmark.py +0 -0
  174. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_ssh_display_diagnostics.py +0 -0
  175. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_ssh_doctor.py +0 -0
  176. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_status.py +0 -0
  177. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_statusbar.py +0 -0
  178. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_system_deps.py +0 -0
  179. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_terminal_size.py +0 -0
  180. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_terminal_status.py +0 -0
  181. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_termux_local_touch.py +0 -0
  182. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_tmux_binding_manager.py +0 -0
  183. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_tmux_capabilities.py +0 -0
  184. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_tmux_ctl.py +0 -0
  185. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_tmux_health.py +0 -0
  186. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_tmux_integration.py +0 -0
  187. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_tmux_status.py +0 -0
  188. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_tool_panes.py +0 -0
  189. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_transcript.py +0 -0
  190. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_utf8_state.py +0 -0
  191. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_web_demo_recorder.py +0 -0
  192. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_wheel_smoke.py +0 -0
  193. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_widgets.py +0 -0
  194. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_windows_attach_relay.py +0 -0
  195. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_windows_bootstrap.py +0 -0
  196. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_windows_install_log.py +0 -0
  197. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_windows_msys2.py +0 -0
  198. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_windows_pacman.py +0 -0
  199. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_windows_paths.py +0 -0
  200. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_windows_synchronized_screen.py +0 -0
  201. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_windows_tmux_lifecycle.py +0 -0
  202. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_windows_ui_transition.py +0 -0
  203. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_windows_wrapper_contract.py +0 -0
  204. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tests/test_yolo_prompt.py +0 -0
  205. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tools/denested_transport_benchmark.py +0 -0
  206. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tools/release_notes.py +0 -0
  207. {railmux-0.4.2.dev3 → railmux-0.4.2.dev5}/tools/wheel_smoke.py +0 -0
  208. {railmux-0.4.2.dev3 → 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.dev3
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"
@@ -1747,6 +1747,83 @@ def _transcript_rows(
1747
1747
  os.close(fd)
1748
1748
 
1749
1749
 
1750
+ _OVERLAP_STYLE_RE = re.compile(rb"\x1b\[[0-9;]*m")
1751
+ _OVERLAP_SPACE_RE = re.compile(r"\s+")
1752
+ _OVERLAP_ANCHOR_CHARS = 12
1753
+ _OVERLAP_SINGLE_ANCHOR_CHARS = 24
1754
+ _OVERLAP_SEARCH_SCREENS = 4
1755
+ _OVERLAP_MIN_SEARCH_ROWS = 200
1756
+
1757
+
1758
+ def _overlap_text(row: bytes) -> str:
1759
+ """Return a row's visible text without styling or any whitespace.
1760
+
1761
+ The transcript and the provider wrap and indent the same message
1762
+ differently, so only the whitespace-free character stream is comparable.
1763
+ """
1764
+ text = _OVERLAP_STYLE_RE.sub(b"", row).decode("utf-8", errors="replace")
1765
+ return _OVERLAP_SPACE_RE.sub("", text)
1766
+
1767
+
1768
+ def _transcript_rows_before_screen(
1769
+ transcript_rows: Sequence[bytes],
1770
+ screen_rows: Sequence[bytes],
1771
+ ) -> int:
1772
+ """Return how many transcript rows precede the live screen's content.
1773
+
1774
+ Transcript-backed history appends the live viewport after the transcript,
1775
+ whose tail usually holds the same newest message. Locate the screen's
1776
+ text in the transcript tail through distinctive anchor rows and cut the
1777
+ transcript where the screen begins. Two anchors must agree on that start
1778
+ unless one long anchor is the only candidate; without a credible match
1779
+ every transcript row is kept.
1780
+ """
1781
+ total = len(transcript_rows)
1782
+ window = max(
1783
+ _OVERLAP_MIN_SEARCH_ROWS, _OVERLAP_SEARCH_SCREENS * len(screen_rows))
1784
+ first = max(0, total - window)
1785
+ row_starts: list[int] = []
1786
+ parts: list[str] = []
1787
+ offset = 0
1788
+ for row in transcript_rows[first:]:
1789
+ row_starts.append(offset)
1790
+ text = _overlap_text(row)
1791
+ parts.append(text)
1792
+ offset += len(text)
1793
+ haystack = "".join(parts)
1794
+ screen_offset = 0
1795
+ starts: list[tuple[int, int]] = []
1796
+ for row in screen_rows:
1797
+ text = _overlap_text(row)
1798
+ if len(text) >= _OVERLAP_ANCHOR_CHARS and haystack.count(text) == 1:
1799
+ starts.append((haystack.index(text) - screen_offset, len(text)))
1800
+ if len(starts) == 2:
1801
+ break
1802
+ screen_offset += len(text)
1803
+ if not starts:
1804
+ return total
1805
+ if len(starts) == 1:
1806
+ if starts[0][1] < _OVERLAP_SINGLE_ANCHOR_CHARS:
1807
+ return total
1808
+ else:
1809
+ # Allow small rendering differences (list numbering, bullets).
1810
+ tolerance = 16 + abs(starts[0][0]) // 20
1811
+ if abs(starts[0][0] - starts[1][0]) > tolerance:
1812
+ return total
1813
+ start = max(0, starts[0][0])
1814
+ # Keep a row that begins before the screen: it may carry text the screen
1815
+ # no longer shows, and a partial duplicate is better than a loss.
1816
+ keep = len(row_starts)
1817
+ for index, row_start in enumerate(row_starts):
1818
+ if row_start >= start:
1819
+ keep = index
1820
+ break
1821
+ # Drop trailing blank rows so the screen is not preceded by a gap.
1822
+ while keep and not parts[keep - 1]:
1823
+ keep -= 1
1824
+ return first + keep
1825
+
1826
+
1750
1827
  def _capture_pane_history(
1751
1828
  pyte: object,
1752
1829
  pane: _PaneGeometry,
@@ -1852,14 +1929,18 @@ def _capture_pane_history(
1852
1929
  transcript_used = transcript_entry is not None and bool(transcript_entry.rows)
1853
1930
  if transcript_used:
1854
1931
  assert transcript_entry is not None
1855
- transcript_lines = transcript_entry.rows
1932
+ kept = _transcript_rows_before_screen(transcript_entry.rows, current_raw)
1933
+ trimmed = len(transcript_entry.rows) - kept
1934
+ transcript_lines = transcript_entry.rows[:kept]
1856
1935
  history_count = max(0, max_lines - pane.height)
1857
1936
  source_lines = (
1858
1937
  transcript_lines[-history_count:] if history_count else ()
1859
1938
  ) + current_raw
1860
1939
  timeline_end = max(
1861
1940
  pane.height,
1862
- (transcript_entry.total_rows or len(transcript_entry.rows)) + pane.height,
1941
+ (transcript_entry.total_rows or len(transcript_entry.rows))
1942
+ - trimmed
1943
+ + pane.height,
1863
1944
  )
1864
1945
  else:
1865
1946
  source_lines = rendered_raw
@@ -1910,7 +1991,7 @@ def _capture_pane_history(
1910
1991
  budget_truncated
1911
1992
  or (
1912
1993
  transcript_entry.more_available
1913
- or len(transcript_entry.rows) + pane.height > len(lines)
1994
+ or len(transcript_lines) + pane.height > len(lines)
1914
1995
  if transcript_used and transcript_entry is not None
1915
1996
  else pane.history_size + pane.height > len(lines)
1916
1997
  )
@@ -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:
@@ -22,6 +22,8 @@ _SESSION_ID_RE = re.compile(r"[A-Za-z0-9-]{1,256}\Z")
22
22
  _HISTORY_SOURCE_SCHEMA = 1
23
23
  _TRANSCRIPT_SOURCE_SCHEMA = 1
24
24
  _MAX_TRANSCRIPT_SOURCE_BYTES = 8192
25
+ _CONTINUATION_TAIL_BYTES = 4096
26
+ _MAX_CONTINUATION_HOPS = 8
25
27
  _DEFAULT_DISCOVERY_TIMEOUT = 2.0
26
28
  _WINDOWS_DISCOVERY_TIMEOUT = 5.0
27
29
 
@@ -415,11 +417,8 @@ def decode_transcript_source(raw: str) -> TranscriptSource | None:
415
417
  return TranscriptSource(provider, session_id, path)
416
418
 
417
419
 
418
- def open_transcript_source(raw: str) -> tuple[TranscriptSource, int] | None:
419
- """Open an exact same-user transcript without following the final symlink."""
420
- source = decode_transcript_source(raw)
421
- if source is None:
422
- return None
420
+ def _open_owned_regular_file(path: Path) -> int | None:
421
+ """Open a same-user regular file without following the final symlink."""
423
422
  flags = os.O_RDONLY
424
423
  if hasattr(os, "O_CLOEXEC"):
425
424
  flags |= os.O_CLOEXEC
@@ -427,7 +426,7 @@ def open_transcript_source(raw: str) -> tuple[TranscriptSource, int] | None:
427
426
  flags |= os.O_NOFOLLOW
428
427
  fd = -1
429
428
  try:
430
- fd = os.open(source.path, flags)
429
+ fd = os.open(path, flags)
431
430
  info = os.fstat(fd)
432
431
  except OSError:
433
432
  if fd >= 0:
@@ -443,6 +442,67 @@ def open_transcript_source(raw: str) -> tuple[TranscriptSource, int] | None:
443
442
  ):
444
443
  os.close(fd)
445
444
  return None
445
+ return fd
446
+
447
+
448
+ def _claude_continuation_id(fd: int) -> str | None:
449
+ """Return the successor session named by a final ``continued-in`` record.
450
+
451
+ Claude Code can move a live conversation to a new session file, copying
452
+ the full history and ending the old file with one ``continued-in``
453
+ record. Only that exact final record is trusted.
454
+ """
455
+ try:
456
+ size = os.fstat(fd).st_size
457
+ tail = os.pread(
458
+ fd,
459
+ min(size, _CONTINUATION_TAIL_BYTES),
460
+ max(0, size - _CONTINUATION_TAIL_BYTES),
461
+ )
462
+ except OSError:
463
+ return None
464
+ lines = tail.rstrip(b"\n").rsplit(b"\n", 1)
465
+ if b"continued-in" not in lines[-1]:
466
+ return None
467
+ try:
468
+ record = json.loads(lines[-1])
469
+ except (UnicodeDecodeError, ValueError):
470
+ return None
471
+ if not isinstance(record, dict) or record.get("type") != "continued-in":
472
+ return None
473
+ successor = record.get("continuedInSessionId")
474
+ if not isinstance(successor, str) or not _SESSION_ID_RE.fullmatch(successor):
475
+ return None
476
+ return successor
477
+
478
+
479
+ def open_transcript_source(raw: str) -> tuple[TranscriptSource, int] | None:
480
+ """Open an exact same-user transcript without following the final symlink.
481
+
482
+ A Claude transcript that was continued in another session resolves to the
483
+ newest same-directory successor, so history keeps following the live pane.
484
+ """
485
+ source = decode_transcript_source(raw)
486
+ if source is None:
487
+ return None
488
+ fd = _open_owned_regular_file(source.path)
489
+ if fd is None:
490
+ return None
491
+ if source.provider != "claude":
492
+ return source, fd
493
+ seen = {source.session_id}
494
+ for _ in range(_MAX_CONTINUATION_HOPS):
495
+ successor = _claude_continuation_id(fd)
496
+ if successor is None or successor in seen:
497
+ break
498
+ path = source.path.with_name(f"{successor}.jsonl")
499
+ next_fd = _open_owned_regular_file(path)
500
+ if next_fd is None:
501
+ break
502
+ os.close(fd)
503
+ fd = next_fd
504
+ source = TranscriptSource(source.provider, successor, path)
505
+ seen.add(successor)
446
506
  return source, fd
447
507
 
448
508
 
@@ -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.dev3
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