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.
- {railmux-0.4.2.dev4/src/railmux.egg-info → railmux-0.4.2.dev5}/PKG-INFO +1 -1
- railmux-0.4.2.dev5/src/railmux/__init__.py +1 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/models.py +6 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/session_cache.py +54 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/session_index.py +20 -6
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/app.py +82 -2
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux.egg-info/SOURCES.txt +1 -0
- railmux-0.4.2.dev5/tests/test_claude_continuation.py +177 -0
- railmux-0.4.2.dev4/src/railmux/__init__.py +0 -1
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/LICENSE +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/MANIFEST.in +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/README.md +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/pyproject.toml +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/setup.cfg +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/__main__.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/atomic_file.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/background_index.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/cli.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/codex_index.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/config_cli.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/diagnostic_contract.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/diagnostics.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/discovery.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/display_transport.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/entrypoint.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fast_display_client.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fast_display_server.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/favorites.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/fuzzy.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/help_workspace.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/image_paste.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/launcher.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/local_open.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/modes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/pane_surface.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/path_codec.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/preview_pager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/project_favorites.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/provider_paths.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/release_version.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/remote_config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/renames.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/restart_state.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/runtime_config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/self_update.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/session_lease.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/settings.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ssh_args.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ssh_preflight.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/system_deps.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/terminal_screen.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/terminal_status.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tmux_capabilities.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tmux_health.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tmux_server.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/tool_panes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/transcript.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/modals.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_attach_relay.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_bootstrap.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_install_log.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_msys2.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_pacman.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_paths.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux/windows_ui_transition.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/app_test_harness.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_agent_workspace.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_app_codex_placeholder.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_app_quit.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_app_restart_restore.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_app_running_recovery.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_atomic_file.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_background_index.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_cli.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_code_map.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_codex_delete.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_codex_index.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_codex_lineage.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_codex_transcript.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_config_cli.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_diagnostics.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_discovery.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_display_transport.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_bootstrap.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_history.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_protocol.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_selection.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_server.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fast_display_terminal.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_focus_highlight.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_fuzzy.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_help_agent.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_help_workspace.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_history_cleanup.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_image_paste.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_keymap.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_launcher.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_layout_profile.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_local_clipboard.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_local_open.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_modals.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_mode_selection.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_modes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_mouse_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_new_project.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_options.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_pane_surface.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_path_codec.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_preview_pager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_project_favorites.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_projects_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_provider_paths.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_release_notes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_release_version.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_release_workflow.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_remote_config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_renames.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_restart_state.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_running_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_running_sort.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_runtime_config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_scroll_agent.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_scroll_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_selection_isolation.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_self_update.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_session_cache.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_session_index.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_session_lease.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_sessions_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_setting_contracts.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_settings.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_sidebar.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_ssh_args.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_ssh_compat.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_status.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_statusbar.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_system_deps.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_terminal_size.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_terminal_status.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_capabilities.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_health.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_integration.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_server.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tmux_status.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_tool_panes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_transcript.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_utf8_state.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_widgets.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_attach_relay.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_bootstrap.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_install_log.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_msys2.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_pacman.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_paths.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_synchronized_screen.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_ui_transition.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_windows_wrapper_contract.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tools/release_notes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tools/wheel_smoke.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev5}/tools/windows_archive_smoke.py +0 -0
|
@@ -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)
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|