railmux 0.4.0.dev31__tar.gz → 0.4.0.dev33__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.0.dev31/src/railmux.egg-info → railmux-0.4.0.dev33}/PKG-INFO +1 -1
- railmux-0.4.0.dev33/src/railmux/__init__.py +1 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/transcript.py +46 -8
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ui/app.py +96 -44
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/windows_bootstrap.py +12 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/windows_install_log.py +6 -1
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/windows_msys2.py +339 -12
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_codex_delete.py +6 -3
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_focus_highlight.py +122 -3
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_sessions_pane.py +33 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_tmux_integration.py +66 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_transcript.py +38 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_windows_bootstrap.py +30 -1
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_windows_install_log.py +12 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_windows_msys2.py +183 -0
- railmux-0.4.0.dev31/src/railmux/__init__.py +0 -1
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/LICENSE +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/MANIFEST.in +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/README.md +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/pyproject.toml +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/setup.cfg +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/__main__.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/atomic_file.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/background_index.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/cli.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/codex_index.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/config.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/config_cli.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/diagnostics.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/discovery.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/display_transport.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/entrypoint.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/fast_display_client.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/fast_display_server.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/favorites.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/fuzzy.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/help_workspace.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/launcher.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/local_open.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/models.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/modes.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/pane_surface.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/path_codec.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/provider_paths.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/remote_config.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/renames.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/restart_state.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/runtime_config.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/self_update.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/session_cache.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/session_index.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/session_lease.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/settings.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ssh_args.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/system_deps.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/terminal_status.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/tmux_health.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/tmux_server.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/tool_panes.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ui/modals.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/windows_attach_relay.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/windows_pacman.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/windows_paths.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux/windows_ui_transition.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux.egg-info/SOURCES.txt +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_agent_workspace.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_atomic_file.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_background_index.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_cli.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_codex_index.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_codex_lineage.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_codex_transcript.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_config.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_config_cli.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_diagnostics.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_discovery.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_display_transport.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_fast_full_window.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_fuzzy.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_help_agent.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_help_workspace.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_history_cleanup.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_keymap.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_launcher.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_layout_profile.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_local_clipboard.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_local_open.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_modals.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_mode_selection.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_modes.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_mouse_manager.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_new_project.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_options.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_pane_surface.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_path_codec.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_projects_pane.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_provider_paths.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_release_notes.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_release_workflow.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_remote_config.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_renames.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_restart_state.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_running_pane.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_running_sort.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_runtime_config.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_scroll_agent.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_scroll_manager.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_selection_isolation.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_self_update.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_session_cache.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_session_index.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_session_lease.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_setting_contracts.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_settings.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_sidebar.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_soft_quit.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_ssh_args.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_ssh_compat.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_status.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_statusbar.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_system_deps.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_terminal_size.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_terminal_status.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_tmux_health.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_tmux_server.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_tmux_status.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_tool_panes.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_utf8_state.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_widgets.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_windows_attach_relay.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_windows_pacman.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_windows_paths.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_windows_ui_transition.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_windows_wrapper_contract.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tools/release_notes.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tools/wheel_smoke.py +0 -0
- {railmux-0.4.0.dev31 → railmux-0.4.0.dev33}/tools/windows_archive_smoke.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0.dev33"
|
|
@@ -7,6 +7,7 @@ Usage::
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import io
|
|
10
11
|
import json
|
|
11
12
|
import os
|
|
12
13
|
import sys
|
|
@@ -41,17 +42,46 @@ _SKIP_TYPES = frozenset({
|
|
|
41
42
|
})
|
|
42
43
|
|
|
43
44
|
# Top-level record types that only ever appear in a Codex rollout JSONL. The
|
|
44
|
-
# preview
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
45
|
+
# preview reader keeps only the final 2,000 records, so for longer rollouts the
|
|
46
|
+
# leading ``session_meta`` header is dropped and the first record seen here is
|
|
47
|
+
# a ``response_item`` / ``event_msg`` / ``turn_context``. Detect Codex by the
|
|
48
|
+
# *presence* of any of these types rather than by a leading ``session_meta``
|
|
49
|
+
# (see issue #5).
|
|
49
50
|
_CODEX_TYPES = frozenset({
|
|
50
51
|
"session_meta", "response_item", "event_msg", "turn_context",
|
|
51
52
|
"compacted", "world_state",
|
|
52
53
|
})
|
|
53
54
|
_CLAUDE_TYPES = _SKIP_TYPES | frozenset({"user", "assistant"})
|
|
54
55
|
|
|
56
|
+
_TAIL_BLOCK_SIZE = 64 * 1024
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _tail_utf8_lines(path: Path, limit: int) -> io.StringIO:
|
|
60
|
+
"""Return the final *limit* UTF-8 lines without scanning from the start.
|
|
61
|
+
|
|
62
|
+
The preview caller previously launched a separate ``tail`` process before
|
|
63
|
+
the Python renderer. Reading backwards here removes that process from the
|
|
64
|
+
latency-sensitive Windows path while retaining the same record boundary.
|
|
65
|
+
One extra newline is collected before slicing so the first returned line
|
|
66
|
+
can never be a block-boundary fragment.
|
|
67
|
+
"""
|
|
68
|
+
chunks: list[bytes] = []
|
|
69
|
+
newline_count = 0
|
|
70
|
+
with path.open("rb") as stream:
|
|
71
|
+
stream.seek(0, os.SEEK_END)
|
|
72
|
+
position = stream.tell()
|
|
73
|
+
while position > 0 and newline_count <= limit:
|
|
74
|
+
size = min(_TAIL_BLOCK_SIZE, position)
|
|
75
|
+
position -= size
|
|
76
|
+
stream.seek(position)
|
|
77
|
+
chunk = stream.read(size)
|
|
78
|
+
chunks.append(chunk)
|
|
79
|
+
newline_count += chunk.count(b"\n")
|
|
80
|
+
payload = b"".join(reversed(chunks))
|
|
81
|
+
lines = payload.splitlines(keepends=True)
|
|
82
|
+
selected = b"".join(lines[-limit:])
|
|
83
|
+
return io.StringIO(selected.decode("utf-8", errors="replace"))
|
|
84
|
+
|
|
55
85
|
|
|
56
86
|
def _sanitize_text(value: object) -> str:
|
|
57
87
|
"""Return display text with terminal control sequences made inert.
|
|
@@ -382,8 +412,8 @@ def format_transcript(
|
|
|
382
412
|
|
|
383
413
|
*fmt* is an optional explicit format hint (``"codex"`` or ``"claude"``).
|
|
384
414
|
When omitted (the default), the format is auto-detected from the record
|
|
385
|
-
types actually present in the stream, so a Codex rollout whose
|
|
386
|
-
``session_meta`` header was
|
|
415
|
+
types actually present in the stream, so a bounded Codex rollout whose
|
|
416
|
+
leading ``session_meta`` header was omitted still renders as Codex.
|
|
387
417
|
|
|
388
418
|
Callers should write each chunk to stdout, e.g.::
|
|
389
419
|
|
|
@@ -670,7 +700,15 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
670
700
|
if not jsonl_path.exists():
|
|
671
701
|
print(f"File not found: {jsonl_path}", file=sys.stderr)
|
|
672
702
|
sys.exit(1)
|
|
673
|
-
|
|
703
|
+
try:
|
|
704
|
+
stream = (
|
|
705
|
+
_tail_utf8_lines(jsonl_path, preview_limit)
|
|
706
|
+
if preview_limit is not None
|
|
707
|
+
else jsonl_path
|
|
708
|
+
)
|
|
709
|
+
except OSError:
|
|
710
|
+
print(f"Could not read: {jsonl_path}", file=sys.stderr)
|
|
711
|
+
sys.exit(1)
|
|
674
712
|
|
|
675
713
|
try:
|
|
676
714
|
if preview_limit is not None:
|
|
@@ -100,6 +100,7 @@ from railmux.ui.workspace import (
|
|
|
100
100
|
COMPACT_RESIZE_OPTION,
|
|
101
101
|
DUAL_SIDEBAR_MIN_WIDTH,
|
|
102
102
|
DUAL_SIDEBAR_PERCENT,
|
|
103
|
+
DisplayTransportKind,
|
|
103
104
|
MINIMUM_AGENT_PANE_SIZE,
|
|
104
105
|
SINGLE_SIDEBAR_PERCENT,
|
|
105
106
|
SlotRestoreState,
|
|
@@ -1177,6 +1178,7 @@ class App:
|
|
|
1177
1178
|
*,
|
|
1178
1179
|
mode_key: str | None = None,
|
|
1179
1180
|
project_key: str | None = None,
|
|
1181
|
+
sync_transcript_source: bool = True,
|
|
1180
1182
|
) -> None:
|
|
1181
1183
|
"""Update one slot, painting sidebar highlights only for the Target."""
|
|
1182
1184
|
slot.active_session_id = session_id
|
|
@@ -1202,7 +1204,8 @@ class App:
|
|
|
1202
1204
|
)
|
|
1203
1205
|
elif session_id is None:
|
|
1204
1206
|
slot.project_key = None
|
|
1205
|
-
|
|
1207
|
+
if sync_transcript_source:
|
|
1208
|
+
self._sync_slot_transcript_source(slot, session_id, tmux_name)
|
|
1206
1209
|
self._paint_slot_active_target(slot, session_id, tmux_name)
|
|
1207
1210
|
|
|
1208
1211
|
def _sync_slot_transcript_source(
|
|
@@ -1296,7 +1299,8 @@ class App:
|
|
|
1296
1299
|
def _set_active_target(self, session_id: str | None,
|
|
1297
1300
|
tmux_name: str | None, *,
|
|
1298
1301
|
mode_key: str | None = None,
|
|
1299
|
-
project_key: str | None = None
|
|
1302
|
+
project_key: str | None = None,
|
|
1303
|
+
sync_transcript_source: bool = True) -> None:
|
|
1300
1304
|
"""Compatibility entry point for the currently exposed primary slot."""
|
|
1301
1305
|
self._set_slot_active_target(
|
|
1302
1306
|
self._primary_slot,
|
|
@@ -1304,6 +1308,7 @@ class App:
|
|
|
1304
1308
|
tmux_name,
|
|
1305
1309
|
mode_key=mode_key,
|
|
1306
1310
|
project_key=project_key,
|
|
1311
|
+
sync_transcript_source=sync_transcript_source,
|
|
1307
1312
|
)
|
|
1308
1313
|
|
|
1309
1314
|
def _set_active_tmux_target(
|
|
@@ -2035,21 +2040,35 @@ class App:
|
|
|
2035
2040
|
|
|
2036
2041
|
# --- history preview (display pane shows a transcript, not an agent session) ---
|
|
2037
2042
|
|
|
2043
|
+
def _paint_session_row_click_now(self, session_id: str | None) -> None:
|
|
2044
|
+
"""Acknowledge a pointer selection before synchronous tmux work."""
|
|
2045
|
+
pane = getattr(self, "_sessions_pane", None)
|
|
2046
|
+
if pane is None:
|
|
2047
|
+
return
|
|
2048
|
+
pane.set_selected_session(session_id)
|
|
2049
|
+
loop = getattr(self, "_loop", None)
|
|
2050
|
+
if loop is not None:
|
|
2051
|
+
loop.draw_screen()
|
|
2052
|
+
|
|
2038
2053
|
def _on_session_row_preview(self, session: SessionMeta) -> None:
|
|
2039
2054
|
"""Apply click semantics after rechecking whether the row is live."""
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2055
|
+
self._paint_session_row_click_now(session.session_id)
|
|
2056
|
+
try:
|
|
2057
|
+
running = self._by_session_id(session.session_id)
|
|
2058
|
+
if (running is not None
|
|
2059
|
+
and self._agent_session_alive(running.tmux_name)):
|
|
2060
|
+
self._on_running_select(
|
|
2061
|
+
RunningEntry(
|
|
2062
|
+
tmux_name=running.tmux_name,
|
|
2063
|
+
label=running.label,
|
|
2064
|
+
status=running.status,
|
|
2065
|
+
),
|
|
2066
|
+
steal_focus=False,
|
|
2067
|
+
)
|
|
2068
|
+
return
|
|
2069
|
+
self._on_session_preview(session)
|
|
2070
|
+
finally:
|
|
2071
|
+
self._paint_session_row_click_now(None)
|
|
2053
2072
|
|
|
2054
2073
|
def _on_session_preview(self, session: SessionMeta) -> None:
|
|
2055
2074
|
"""Show the provider's canonical current-branch history.
|
|
@@ -2090,10 +2109,19 @@ class App:
|
|
|
2090
2109
|
}
|
|
2091
2110
|
if slot is self._primary_slot:
|
|
2092
2111
|
self._set_active_target(
|
|
2093
|
-
session.session_id,
|
|
2112
|
+
session.session_id,
|
|
2113
|
+
None,
|
|
2114
|
+
sync_transcript_source=False,
|
|
2115
|
+
**target_kwargs,
|
|
2116
|
+
)
|
|
2094
2117
|
else:
|
|
2095
2118
|
self._set_slot_active_target(
|
|
2096
|
-
slot,
|
|
2119
|
+
slot,
|
|
2120
|
+
session.session_id,
|
|
2121
|
+
None,
|
|
2122
|
+
sync_transcript_source=False,
|
|
2123
|
+
**target_kwargs,
|
|
2124
|
+
)
|
|
2097
2125
|
if not self._show_attention_status(session.attention):
|
|
2098
2126
|
self._set_status(
|
|
2099
2127
|
f"≡ Previewing {session.display_title} (current history)")
|
|
@@ -2183,11 +2211,11 @@ class App:
|
|
|
2183
2211
|
import sys as _sys
|
|
2184
2212
|
mouse = self._less_mouse_flag
|
|
2185
2213
|
slot = slot or self._agent_workspace().target
|
|
2186
|
-
#
|
|
2187
|
-
#
|
|
2188
|
-
#
|
|
2189
|
-
# format explicitly from SessionMeta
|
|
2190
|
-
#
|
|
2214
|
+
# The renderer reads the last 2000 records itself so large sessions
|
|
2215
|
+
# appear instantly without launching a separate ``tail`` process.
|
|
2216
|
+
# This drops the leading ``session_meta`` record from long Codex
|
|
2217
|
+
# rollouts, so pass the format explicitly from SessionMeta rather than
|
|
2218
|
+
# asking the bounded input to auto-detect it (#5).
|
|
2191
2219
|
fmt = "codex" if session_type == "codex" else "claude"
|
|
2192
2220
|
path = shlex.quote(str(jsonl_path))
|
|
2193
2221
|
python = shlex.quote(_sys.executable)
|
|
@@ -2196,9 +2224,8 @@ class App:
|
|
|
2196
2224
|
# and suppress any user-configured input pre/postprocessors.
|
|
2197
2225
|
less_env = ("LESSSECURE=1 LESSHISTFILE=- "
|
|
2198
2226
|
"LESSOPEN= LESSCLOSE=")
|
|
2199
|
-
cmd = (f"
|
|
2200
|
-
f"
|
|
2201
|
-
f"--preview-limit 2000 - | "
|
|
2227
|
+
cmd = (f"{python} -m railmux.transcript --format {fmt} "
|
|
2228
|
+
f"--preview-limit 2000 {path} | "
|
|
2202
2229
|
f"{less_env} less -R +G {mouse}").rstrip()
|
|
2203
2230
|
restore = slot.restore_state
|
|
2204
2231
|
controller = getattr(self, "_railmux_pane_id", None)
|
|
@@ -2211,33 +2238,58 @@ class App:
|
|
|
2211
2238
|
f"{shlex.quote(controller)} -- "
|
|
2212
2239
|
f"{shlex.quote(restore_sequence)}"
|
|
2213
2240
|
)
|
|
2214
|
-
#
|
|
2215
|
-
#
|
|
2216
|
-
#
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2241
|
+
# A preview-to-preview switch keeps the same Railmux-owned outer pane,
|
|
2242
|
+
# whose provider and SSH transcript markers were already cleared by
|
|
2243
|
+
# the first transition. ``respawn-pane`` is itself the bounded
|
|
2244
|
+
# liveness check, so do not launch redundant tmux clients on this hot
|
|
2245
|
+
# path. Entering preview from a live agent still uses the complete
|
|
2246
|
+
# return-home identity transaction below.
|
|
2247
|
+
replacing_preview = bool(
|
|
2248
|
+
slot.in_history_mode
|
|
2249
|
+
and slot.pane_id
|
|
2250
|
+
and slot.agent_tmux_name is None
|
|
2251
|
+
and slot.swap_state is None
|
|
2252
|
+
and slot.transport_kind is DisplayTransportKind.NESTED
|
|
2253
|
+
)
|
|
2254
|
+
if replacing_preview:
|
|
2255
|
+
assert slot.pane_id is not None
|
|
2225
2256
|
if not tmux_ctl.respawn_pane(slot.pane_id, cmd):
|
|
2226
2257
|
self._set_status("failed to respawn right pane for transcript")
|
|
2227
2258
|
return False
|
|
2228
2259
|
else:
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2260
|
+
# In swap mode ``slot.pane_id`` is the real provider pane. Return
|
|
2261
|
+
# it home before the destructive ``respawn-pane -k`` below; only
|
|
2262
|
+
# the display placeholder may be replaced by the viewer.
|
|
2263
|
+
if not self._display_transport().prepare_preview(slot):
|
|
2264
|
+
self._set_status(
|
|
2265
|
+
"failed to return the agent home before transcript preview",
|
|
2266
|
+
"error",
|
|
2267
|
+
)
|
|
2232
2268
|
return False
|
|
2233
|
-
slot
|
|
2234
|
-
|
|
2269
|
+
self._sync_slot_transcript_source(slot, None, None)
|
|
2270
|
+
# prepare_preview already proved an existing pane immediately
|
|
2271
|
+
# before this mutation. A race now fails in respawn-pane rather
|
|
2272
|
+
# than paying for a second process-wide list-panes query.
|
|
2273
|
+
if slot.pane_id:
|
|
2274
|
+
if not tmux_ctl.respawn_pane(slot.pane_id, cmd):
|
|
2275
|
+
self._set_status("failed to respawn right pane for transcript")
|
|
2276
|
+
return False
|
|
2277
|
+
else:
|
|
2278
|
+
new_id = tmux_ctl.split_window_h(
|
|
2279
|
+
cmd, size_percent=70, detached=True)
|
|
2280
|
+
if not new_id:
|
|
2281
|
+
self._set_status("failed to create right pane for transcript")
|
|
2282
|
+
return False
|
|
2283
|
+
slot.pane_id = new_id
|
|
2284
|
+
self._set_railmux_focus(
|
|
2285
|
+
self._railmux_has_focus, force_border=True)
|
|
2235
2286
|
# The display pane is now showing a transcript, not an agent session.
|
|
2236
2287
|
slot.agent_tmux_name = None
|
|
2237
2288
|
slot.mode_key = self._current_mode_key()
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2289
|
+
if not replacing_preview:
|
|
2290
|
+
self._set_divider_active(
|
|
2291
|
+
not getattr(self, "_railmux_has_focus", True), force=True)
|
|
2292
|
+
self._install_tmux_bindings()
|
|
2241
2293
|
return True
|
|
2242
2294
|
|
|
2243
2295
|
def _sync_sidebar_to_agent_project(self, tmux_name: str | None) -> None:
|
|
@@ -20,6 +20,7 @@ from railmux.windows_msys2 import (
|
|
|
20
20
|
plan_managed_runtime_prune,
|
|
21
21
|
reusable_managed_base_candidate,
|
|
22
22
|
)
|
|
23
|
+
from railmux.windows_paths import managed_windows_data_root
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
_MINIMUM_WINDOWS_PYTHON = (3, 10)
|
|
@@ -49,6 +50,14 @@ def _runtime_status(
|
|
|
49
50
|
) -> int:
|
|
50
51
|
snapshot = managed_runtime_status(
|
|
51
52
|
version=__version__, environ=environ, verify=verify)
|
|
53
|
+
data_root = (
|
|
54
|
+
managed_windows_data_root(environ)
|
|
55
|
+
if runtime is None or runtime.managed
|
|
56
|
+
else None
|
|
57
|
+
)
|
|
58
|
+
if data_root is not None:
|
|
59
|
+
snapshot["data_root"] = str(data_root)
|
|
60
|
+
snapshot["log_directory"] = str(data_root / "logs")
|
|
52
61
|
if json_output:
|
|
53
62
|
json.dump(snapshot, sys.stdout, indent=2, sort_keys=True)
|
|
54
63
|
print()
|
|
@@ -75,6 +84,9 @@ def _runtime_status(
|
|
|
75
84
|
if root is not None:
|
|
76
85
|
print(f"Managed location: {root}")
|
|
77
86
|
print("Next: run 'railmux runtime install' from an interactive PowerShell.")
|
|
87
|
+
if data_root is not None:
|
|
88
|
+
print(f"Data root: {data_root}")
|
|
89
|
+
print(f"Install logs: {data_root / 'logs'}")
|
|
78
90
|
print("Provider data: shared from the Windows user profile")
|
|
79
91
|
content = snapshot.get("content_identity")
|
|
80
92
|
if isinstance(content, str):
|
|
@@ -162,7 +162,12 @@ class InstallReporter:
|
|
|
162
162
|
|
|
163
163
|
def __enter__(self) -> InstallReporter:
|
|
164
164
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
165
|
-
|
|
165
|
+
# Windows PowerShell 5 treats BOM-less UTF-8 as the active ANSI code
|
|
166
|
+
# page. The signature keeps Chinese systems from rendering the
|
|
167
|
+
# middle dot/ellipsis as mojibake while remaining ordinary UTF-8 for
|
|
168
|
+
# modern readers (use ``utf-8-sig`` when the exact first character
|
|
169
|
+
# matters).
|
|
170
|
+
self._log = self.path.open("x", encoding="utf-8-sig", newline="\n")
|
|
166
171
|
self._write_log("Railmux Windows runtime installation\n")
|
|
167
172
|
# Windows may defer the visible last-write timestamp of an open file.
|
|
168
173
|
# Never let retention mistake the active evidence log for an old one.
|