railmux 0.4.2.dev4__tar.gz → 0.4.2.dev6__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.dev6}/PKG-INFO +1 -1
- railmux-0.4.2.dev6/src/railmux/__init__.py +1 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/fast_display_server.py +42 -21
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/models.py +6 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/session_cache.py +54 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/session_index.py +20 -6
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/transcript.py +272 -43
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ui/app.py +82 -2
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux.egg-info/SOURCES.txt +1 -0
- railmux-0.4.2.dev6/tests/test_claude_continuation.py +177 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_fast_display_server.py +32 -4
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_transcript.py +76 -0
- railmux-0.4.2.dev4/src/railmux/__init__.py +0 -1
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/LICENSE +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/MANIFEST.in +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/README.md +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/pyproject.toml +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/setup.cfg +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/__main__.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/atomic_file.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/background_index.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/cli.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/codex_index.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/config_cli.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/diagnostic_contract.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/diagnostics.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/discovery.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/display_transport.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/entrypoint.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/fast_display_client.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/favorites.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/fuzzy.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/help_workspace.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/image_paste.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/launcher.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/local_open.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/modes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/pane_surface.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/path_codec.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/preview_pager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/project_favorites.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/provider_paths.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/release_version.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/remote_config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/renames.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/restart_state.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/runtime_config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/self_update.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/session_lease.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/settings.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ssh_args.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ssh_preflight.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/system_deps.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/terminal_screen.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/terminal_status.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/tmux_capabilities.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/tmux_health.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/tmux_server.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/tool_panes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ui/modals.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/windows_attach_relay.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/windows_bootstrap.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/windows_install_log.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/windows_msys2.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/windows_pacman.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/windows_paths.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux/windows_ui_transition.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/app_test_harness.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_agent_workspace.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_app_codex_placeholder.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_app_quit.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_app_restart_restore.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_app_running_recovery.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_atomic_file.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_background_index.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_cli.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_code_map.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_codex_delete.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_codex_index.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_codex_lineage.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_codex_transcript.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_config_cli.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_diagnostics.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_discovery.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_display_transport.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_fast_display_bootstrap.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_fast_display_history.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_fast_display_protocol.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_fast_display_selection.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_fast_display_terminal.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_focus_highlight.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_fuzzy.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_help_agent.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_help_workspace.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_history_cleanup.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_image_paste.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_keymap.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_launcher.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_layout_profile.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_local_clipboard.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_local_open.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_modals.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_mode_selection.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_modes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_mouse_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_new_project.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_options.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_pane_surface.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_path_codec.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_preview_pager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_project_favorites.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_projects_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_provider_paths.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_release_notes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_release_version.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_release_workflow.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_remote_config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_renames.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_restart_state.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_running_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_running_sort.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_runtime_config.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_scroll_agent.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_scroll_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_selection_isolation.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_self_update.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_session_cache.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_session_index.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_session_lease.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_sessions_pane.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_setting_contracts.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_settings.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_sidebar.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_ssh_args.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_ssh_compat.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_status.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_statusbar.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_system_deps.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_terminal_size.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_terminal_status.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_tmux_capabilities.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_tmux_health.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_tmux_integration.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_tmux_server.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_tmux_status.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_tool_panes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_utf8_state.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_widgets.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_windows_attach_relay.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_windows_bootstrap.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_windows_install_log.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_windows_msys2.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_windows_pacman.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_windows_paths.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_windows_synchronized_screen.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_windows_ui_transition.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_windows_wrapper_contract.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tools/release_notes.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tools/wheel_smoke.py +0 -0
- {railmux-0.4.2.dev4 → railmux-0.4.2.dev6}/tools/windows_archive_smoke.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.2.dev6"
|
|
@@ -374,7 +374,7 @@ class _TranscriptFormatCacheEntry:
|
|
|
374
374
|
_TRANSCRIPT_CACHE: OrderedDict[tuple[str, int], _TranscriptCacheEntry] = OrderedDict()
|
|
375
375
|
_TRANSCRIPT_CACHE_LIMIT = 4
|
|
376
376
|
_TRANSCRIPT_FORMAT_CACHE: OrderedDict[
|
|
377
|
-
tuple[str, str], _TranscriptFormatCacheEntry
|
|
377
|
+
tuple[str, str, int | None], _TranscriptFormatCacheEntry
|
|
378
378
|
] = OrderedDict()
|
|
379
379
|
_TRANSCRIPT_FORMAT_CACHE_LIMIT = 2
|
|
380
380
|
_TRANSCRIPT_FORMAT_CACHE_MAX_CHARS = 4 * 1024 * 1024
|
|
@@ -1699,7 +1699,9 @@ def _transcript_rows(
|
|
|
1699
1699
|
):
|
|
1700
1700
|
_TRANSCRIPT_CACHE.move_to_end(key)
|
|
1701
1701
|
return cached
|
|
1702
|
-
|
|
1702
|
+
# Claude history is pre-wrapped to the pane; Codex text is not.
|
|
1703
|
+
format_width = width if source.provider == "claude" else None
|
|
1704
|
+
format_key = (str(source.path), source.provider, format_width)
|
|
1703
1705
|
formatted_entry = _TRANSCRIPT_FORMAT_CACHE.get(format_key)
|
|
1704
1706
|
if formatted_entry is not None and formatted_entry.identity == identity:
|
|
1705
1707
|
_TRANSCRIPT_FORMAT_CACHE.move_to_end(format_key)
|
|
@@ -1715,6 +1717,7 @@ def _transcript_rows(
|
|
|
1715
1717
|
io.StringIO(raw),
|
|
1716
1718
|
source.provider,
|
|
1717
1719
|
claude_native=source.provider == "claude",
|
|
1720
|
+
width=format_width,
|
|
1718
1721
|
)
|
|
1719
1722
|
)
|
|
1720
1723
|
if len(formatted) <= _TRANSCRIPT_FORMAT_CACHE_MAX_CHARS:
|
|
@@ -1773,10 +1776,12 @@ def _transcript_rows_before_screen(
|
|
|
1773
1776
|
|
|
1774
1777
|
Transcript-backed history appends the live viewport after the transcript,
|
|
1775
1778
|
whose tail usually holds the same newest message. Locate the screen's
|
|
1776
|
-
text in the transcript tail through distinctive anchor rows
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1779
|
+
text in the transcript tail through distinctive anchor rows: the largest
|
|
1780
|
+
group of anchors that agree on where the screen starts wins, so screen
|
|
1781
|
+
regions the transcript renders differently (such as a diff) cannot veto
|
|
1782
|
+
the match. A lone anchor must be long and unopposed. From the earliest agreeing anchor
|
|
1783
|
+
the cut walks back over screen rows that also precede it in the
|
|
1784
|
+
transcript; without a credible match every transcript row is kept.
|
|
1780
1785
|
"""
|
|
1781
1786
|
total = len(transcript_rows)
|
|
1782
1787
|
window = max(
|
|
@@ -1791,26 +1796,42 @@ def _transcript_rows_before_screen(
|
|
|
1791
1796
|
parts.append(text)
|
|
1792
1797
|
offset += len(text)
|
|
1793
1798
|
haystack = "".join(parts)
|
|
1799
|
+
screen_texts = [_overlap_text(row) for row in screen_rows]
|
|
1794
1800
|
screen_offset = 0
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1801
|
+
# (screen row, transcript position, anchor length, implied screen start)
|
|
1802
|
+
anchors: list[tuple[int, int, int, int]] = []
|
|
1803
|
+
for index, text in enumerate(screen_texts):
|
|
1798
1804
|
if len(text) >= _OVERLAP_ANCHOR_CHARS and haystack.count(text) == 1:
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1805
|
+
position = haystack.index(text)
|
|
1806
|
+
anchors.append(
|
|
1807
|
+
(index, position, len(text), position - screen_offset))
|
|
1802
1808
|
screen_offset += len(text)
|
|
1803
|
-
if not
|
|
1809
|
+
if not anchors:
|
|
1804
1810
|
return total
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
return total
|
|
1808
|
-
else:
|
|
1811
|
+
|
|
1812
|
+
def agreeing(anchor: tuple[int, int, int, int]) -> list:
|
|
1809
1813
|
# Allow small rendering differences (list numbering, bullets).
|
|
1810
|
-
tolerance = 16 + abs(
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
+
tolerance = 16 + abs(anchor[3]) // 20
|
|
1815
|
+
return [
|
|
1816
|
+
other for other in anchors
|
|
1817
|
+
if abs(other[3] - anchor[3]) <= tolerance
|
|
1818
|
+
]
|
|
1819
|
+
|
|
1820
|
+
group = max(
|
|
1821
|
+
(agreeing(anchor) for anchor in anchors),
|
|
1822
|
+
key=lambda members: (len(members), max(m[3] for m in members)),
|
|
1823
|
+
)
|
|
1824
|
+
if len(group) == 1 and (
|
|
1825
|
+
len(anchors) > 1 or group[0][2] < _OVERLAP_SINGLE_ANCHOR_CHARS
|
|
1826
|
+
):
|
|
1827
|
+
return total
|
|
1828
|
+
index, start, _length, _implied = min(group)
|
|
1829
|
+
for text in reversed(screen_texts[:index]):
|
|
1830
|
+
if not text:
|
|
1831
|
+
continue
|
|
1832
|
+
if start < len(text) or haystack[start - len(text):start] != text:
|
|
1833
|
+
break
|
|
1834
|
+
start -= len(text)
|
|
1814
1835
|
# Keep a row that begins before the screen: it may carry text the screen
|
|
1815
1836
|
# no longer shows, and a partial duplicate is better than a loss.
|
|
1816
1837
|
keep = len(row_starts)
|
|
@@ -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:
|