railmux 0.3.3.dev4__tar.gz → 0.3.3.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.3.3.dev4/src/railmux.egg-info → railmux-0.3.3.dev5}/PKG-INFO +6 -2
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/README.md +5 -1
- railmux-0.3.3.dev5/src/railmux/__init__.py +1 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ui/app.py +275 -123
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5/src/railmux.egg-info}/PKG-INFO +6 -2
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_codex_delete.py +134 -39
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_statusbar.py +16 -0
- railmux-0.3.3.dev4/src/railmux/__init__.py +0 -1
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/LICENSE +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/MANIFEST.in +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/pyproject.toml +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/setup.cfg +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/__main__.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/atomic_file.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/background_index.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/cli.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/codex_index.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/config.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/config_cli.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/diagnostics.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/discovery.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/display_transport.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/fast_display_client.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/fast_display_server.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/favorites.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/fuzzy.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/help_workspace.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/launcher.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/local_open.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/models.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/modes.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/pane_surface.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/path_codec.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/remote_config.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/renames.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/restart_state.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/runtime_config.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/self_update.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/session_cache.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/session_index.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/settings.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ssh_args.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/system_deps.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/terminal_status.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/tmux_health.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/tmux_server.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/tool_panes.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/transcript.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ui/modals.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux.egg-info/SOURCES.txt +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_agent_workspace.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_atomic_file.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_background_index.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_cli.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_codex_index.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_codex_lineage.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_codex_transcript.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_config.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_config_cli.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_diagnostics.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_discovery.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_display_transport.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_fast_full_window.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_focus_highlight.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_fuzzy.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_help_agent.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_help_workspace.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_history_cleanup.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_keymap.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_launcher.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_layout_profile.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_local_clipboard.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_local_open.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_modals.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_mode_selection.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_modes.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_mouse_manager.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_new_project.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_options.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_pane_surface.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_path_codec.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_projects_pane.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_release_notes.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_release_workflow.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_remote_config.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_renames.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_restart_state.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_running_pane.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_running_sort.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_runtime_config.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_scroll_agent.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_scroll_manager.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_selection_isolation.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_self_update.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_session_cache.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_session_index.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_sessions_pane.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_setting_contracts.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_settings.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_sidebar.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_soft_quit.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_ssh_args.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_ssh_compat.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_status.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_system_deps.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_terminal_size.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_terminal_status.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_tmux_health.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_tmux_integration.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_tmux_server.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_tmux_status.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_tool_panes.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_transcript.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_utf8_state.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_widgets.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tools/release_notes.py +0 -0
- {railmux-0.3.3.dev4 → railmux-0.3.3.dev5}/tools/wheel_smoke.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: railmux
|
|
3
|
-
Version: 0.3.3.
|
|
3
|
+
Version: 0.3.3.dev5
|
|
4
4
|
Summary: Terminal UI to navigate, resume, and start Claude Code and Codex sessions
|
|
5
5
|
Author-email: Rightglow <zhang.taian@foxmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -204,7 +204,7 @@ see [FAQ 1](#1-how-do-i-copy-text-from-the-agent-pane).
|
|
|
204
204
|
| `r` | Rename the focused session |
|
|
205
205
|
| `s` | Toggle star — starred sessions pinned to top with ⭐ |
|
|
206
206
|
| `k` | Kill the running agent process (keeps session file) |
|
|
207
|
-
| `d` | Delete the focused session (
|
|
207
|
+
| `d` | Delete the focused session (confirms, then finishes in background) |
|
|
208
208
|
| `t` | Open or return to the managed terminal below the Target agent |
|
|
209
209
|
| `T` | Return to that agent's managed Vim viewer, when open |
|
|
210
210
|
| `m` | Switch agent mode (Claude Code ⇄ Codex) |
|
|
@@ -242,6 +242,10 @@ The rename popup starts with the current title pre-filled. Press
|
|
|
242
242
|
`Ctrl-U` to clear the entire input, `Enter` to save a non-empty title, or `Esc`
|
|
243
243
|
to cancel.
|
|
244
244
|
|
|
245
|
+
After a delete is confirmed, its popup closes immediately. The bottom-right
|
|
246
|
+
status shows **Deleting…** while Railmux stops the exact provider session and
|
|
247
|
+
removes its history in the background, then reports the final result.
|
|
248
|
+
|
|
245
249
|
Press `/` in Projects, Sessions, or Running to filter that section. Active
|
|
246
250
|
filters are marked with `[filtered]` and survive a soft restart; reopening `/`
|
|
247
251
|
shows the current value. Press `Ctrl-U` in the filter editor to clear it, then
|
|
@@ -162,7 +162,7 @@ see [FAQ 1](#1-how-do-i-copy-text-from-the-agent-pane).
|
|
|
162
162
|
| `r` | Rename the focused session |
|
|
163
163
|
| `s` | Toggle star — starred sessions pinned to top with ⭐ |
|
|
164
164
|
| `k` | Kill the running agent process (keeps session file) |
|
|
165
|
-
| `d` | Delete the focused session (
|
|
165
|
+
| `d` | Delete the focused session (confirms, then finishes in background) |
|
|
166
166
|
| `t` | Open or return to the managed terminal below the Target agent |
|
|
167
167
|
| `T` | Return to that agent's managed Vim viewer, when open |
|
|
168
168
|
| `m` | Switch agent mode (Claude Code ⇄ Codex) |
|
|
@@ -200,6 +200,10 @@ The rename popup starts with the current title pre-filled. Press
|
|
|
200
200
|
`Ctrl-U` to clear the entire input, `Enter` to save a non-empty title, or `Esc`
|
|
201
201
|
to cancel.
|
|
202
202
|
|
|
203
|
+
After a delete is confirmed, its popup closes immediately. The bottom-right
|
|
204
|
+
status shows **Deleting…** while Railmux stops the exact provider session and
|
|
205
|
+
removes its history in the background, then reports the final result.
|
|
206
|
+
|
|
203
207
|
Press `/` in Projects, Sessions, or Running to filter that section. Active
|
|
204
208
|
filters are marked with `[filtered]` and survive a soft restart; reopening `/`
|
|
205
209
|
shows the current value. Press `Ctrl-U` in the filter editor to clear it, then
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.3.dev5"
|
|
@@ -387,6 +387,35 @@ class _Running:
|
|
|
387
387
|
return None if self.is_placeholder else self.key
|
|
388
388
|
|
|
389
389
|
|
|
390
|
+
@dataclass(frozen=True)
|
|
391
|
+
class _DeleteTask:
|
|
392
|
+
"""Immutable destructive work handed from Urwid to one worker thread."""
|
|
393
|
+
|
|
394
|
+
session_id: str | None
|
|
395
|
+
jsonl_path: Path | None
|
|
396
|
+
tmux_name: str | None
|
|
397
|
+
label: str
|
|
398
|
+
session_type: str
|
|
399
|
+
pane_identity: tmux_ctl.PaneIdentity | None
|
|
400
|
+
writer_pids: tuple[int, ...]
|
|
401
|
+
lineage_ids: frozenset[str]
|
|
402
|
+
claude_home: Path
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@dataclass(frozen=True)
|
|
406
|
+
class _DeleteResult:
|
|
407
|
+
"""Worker result installed atomically by the next Urwid refresh."""
|
|
408
|
+
|
|
409
|
+
task: _DeleteTask
|
|
410
|
+
status_text: str
|
|
411
|
+
status_level: str = "info"
|
|
412
|
+
remove_running: bool = False
|
|
413
|
+
views_changed: bool = False
|
|
414
|
+
claude_path_changed: bool = False
|
|
415
|
+
invalidate_codex: bool = False
|
|
416
|
+
deleted_codex_ids: frozenset[str] = frozenset()
|
|
417
|
+
|
|
418
|
+
|
|
390
419
|
class _CloseOnClickOverlay(urwid.Overlay):
|
|
391
420
|
"""An ``urwid.Overlay`` that calls *on_click_outside* when the user
|
|
392
421
|
left-clicks anywhere outside the overlay's area.
|
|
@@ -885,6 +914,14 @@ class App:
|
|
|
885
914
|
self._mode_refresh_result: (
|
|
886
915
|
tuple[list[Project] | None, str | None] | None
|
|
887
916
|
) = None
|
|
917
|
+
# Destructive provider cleanup may include a bounded process-exit wait
|
|
918
|
+
# and several 30-second provider CLI calls. Urwid prepares display/tmux
|
|
919
|
+
# ownership on its thread, then this single worker performs only the
|
|
920
|
+
# blocking exact kill and filesystem/provider work. Results are applied
|
|
921
|
+
# by _refresh, never by the worker.
|
|
922
|
+
self._delete_lock = threading.Lock()
|
|
923
|
+
self._delete_thread: threading.Thread | None = None
|
|
924
|
+
self._delete_result: _DeleteResult | None = None
|
|
888
925
|
|
|
889
926
|
projects = list_projects(claude_home)
|
|
890
927
|
self._project_snapshot = projects
|
|
@@ -8093,6 +8130,10 @@ class App:
|
|
|
8093
8130
|
|
|
8094
8131
|
def _refresh(self) -> None:
|
|
8095
8132
|
"""Refresh every visible view against one coherent Codex generation."""
|
|
8133
|
+
# Install completed deletion tombstones before pinning the immutable
|
|
8134
|
+
# Codex generation, so the completion frame cannot briefly redraw a
|
|
8135
|
+
# provider row that the worker has already removed.
|
|
8136
|
+
self._consume_delete_result()
|
|
8096
8137
|
index = getattr(self, "_codex_index", None)
|
|
8097
8138
|
if isinstance(index, BackgroundCodexIndex):
|
|
8098
8139
|
index.begin_read()
|
|
@@ -9105,13 +9146,22 @@ class App:
|
|
|
9105
9146
|
label: str = "",
|
|
9106
9147
|
session_type: str = "claude",
|
|
9107
9148
|
identity_token: str | None = None) -> None:
|
|
9108
|
-
"""
|
|
9109
|
-
|
|
9149
|
+
"""Prepare an exact cleanup transaction, then run it off the UI thread.
|
|
9150
|
+
|
|
9151
|
+
Display ownership and immutable tmux identity are validated here on the
|
|
9152
|
+
Urwid thread. The worker receives only frozen values and never mutates
|
|
9153
|
+
widgets, the Running registry, or provider indexes directly.
|
|
9154
|
+
"""
|
|
9155
|
+
if self._delete_pending():
|
|
9156
|
+
self._set_status("A session deletion is already in progress.", "warn")
|
|
9157
|
+
return
|
|
9158
|
+
|
|
9159
|
+
# Paint the closed confirmation and durable progress message before
|
|
9160
|
+
# even the bounded tmux preparation probes run. This makes Enter feel
|
|
9161
|
+
# immediate on slow homes and provider installations.
|
|
9162
|
+
self._set_status("Deleting…", "info", force=True)
|
|
9163
|
+
self._redraw_focus_state_now()
|
|
9110
9164
|
|
|
9111
|
-
Claude sessions unlink the JSONL and clean the Claude session-env +
|
|
9112
|
-
history index. Codex sessions are deleted through ``codex delete``
|
|
9113
|
-
against the resolved CODEX_HOME and never touch any Claude path (#1)."""
|
|
9114
|
-
# 1. Kill the detached tmux session first (avoid race conditions).
|
|
9115
9165
|
if tmux_name is None and session_id is not None:
|
|
9116
9166
|
r = self._by_session_id(session_id)
|
|
9117
9167
|
tmux_name = r.tmux_name if r else None
|
|
@@ -9139,125 +9189,102 @@ class App:
|
|
|
9139
9189
|
writer_pids = tmux_ctl.session_process_ids(tmux_name)
|
|
9140
9190
|
if exact_pane is not None:
|
|
9141
9191
|
# Returning a swap-displayed pane changes its current session;
|
|
9142
|
-
# refresh and require the recorded home identity before
|
|
9192
|
+
# refresh and require the recorded home identity before the
|
|
9193
|
+
# immutable identity is handed to the worker.
|
|
9143
9194
|
exact_pane = self._exact_running_pane(owned)
|
|
9144
|
-
killed = bool(
|
|
9145
|
-
exact_pane is not None
|
|
9146
|
-
and orphan_marker.same_live_tmux(owned.orphan, exact_pane)
|
|
9147
|
-
and tmux_ctl.kill_session_identity(exact_pane)
|
|
9148
|
-
)
|
|
9149
9195
|
else:
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
if not killed and still_alive:
|
|
9160
|
-
self._set_status(
|
|
9161
|
-
f"failed to stop {tmux_name}; nothing was deleted", "error")
|
|
9162
|
-
return
|
|
9163
|
-
if writer_pids and not tmux_ctl.wait_for_processes_exit(writer_pids):
|
|
9196
|
+
topology = tmux_ctl.session_topology(tmux_name)
|
|
9197
|
+
exact_pane = (
|
|
9198
|
+
topology.single_live_pane
|
|
9199
|
+
if topology is not None else None
|
|
9200
|
+
)
|
|
9201
|
+
if (exact_pane is None
|
|
9202
|
+
or (owned is not None and owned.orphan is not None
|
|
9203
|
+
and not orphan_marker.same_live_tmux(
|
|
9204
|
+
owned.orphan, exact_pane))):
|
|
9164
9205
|
self._set_status(
|
|
9165
|
-
f"
|
|
9206
|
+
f"could not confirm the exact tmux identity for "
|
|
9207
|
+
f"{tmux_name}; nothing was deleted",
|
|
9166
9208
|
"error",
|
|
9167
9209
|
)
|
|
9168
9210
|
return
|
|
9211
|
+
is_real = bool(session_id) and not session_id.startswith("__new__-")
|
|
9212
|
+
lineage_ids = frozenset(
|
|
9213
|
+
self._codex_lineage_ids(session_id)
|
|
9214
|
+
if session_type == "codex" and is_real
|
|
9215
|
+
and session_id is not None else ()
|
|
9216
|
+
)
|
|
9217
|
+
task = _DeleteTask(
|
|
9218
|
+
session_id=session_id,
|
|
9219
|
+
jsonl_path=jsonl_path,
|
|
9220
|
+
tmux_name=tmux_name,
|
|
9221
|
+
label=label,
|
|
9222
|
+
session_type=session_type,
|
|
9223
|
+
pane_identity=exact_pane,
|
|
9224
|
+
writer_pids=writer_pids,
|
|
9225
|
+
lineage_ids=lineage_ids,
|
|
9226
|
+
claude_home=getattr(
|
|
9227
|
+
self, "_claude_home", Path.home() / ".claude"),
|
|
9228
|
+
)
|
|
9229
|
+
lock = self._delete_lock
|
|
9169
9230
|
|
|
9170
|
-
|
|
9171
|
-
self._cleanup_codex_session(session_id, tmux_name, label)
|
|
9172
|
-
return
|
|
9173
|
-
|
|
9174
|
-
# 2. Remove from our running-session registry.
|
|
9175
|
-
self._forget_running(session_id, tmux_name)
|
|
9176
|
-
|
|
9177
|
-
# 3. Delete the JSONL file (conversation history). Do not report a
|
|
9178
|
-
# successful deletion when the filesystem rejected the unlink.
|
|
9179
|
-
if jsonl_path is not None:
|
|
9180
|
-
try:
|
|
9181
|
-
jsonl_path.unlink(missing_ok=True)
|
|
9182
|
-
invalidate_session(jsonl_path)
|
|
9183
|
-
except OSError as exc:
|
|
9184
|
-
self._session_cache.invalidate()
|
|
9185
|
-
self._invalidate_project_snapshot()
|
|
9186
|
-
self._refresh()
|
|
9187
|
-
self._set_status(f"failed to delete {jsonl_path}: {exc}", "error")
|
|
9188
|
-
return
|
|
9189
|
-
|
|
9190
|
-
# 4. Remove Claude's session-env directory (session metadata).
|
|
9191
|
-
if session_id is not None and not session_id.startswith("__new__-"):
|
|
9192
|
-
claude_home = getattr(
|
|
9193
|
-
self, "_claude_home", Path.home() / ".claude")
|
|
9194
|
-
env_dir = claude_home / "session-env" / session_id
|
|
9195
|
-
if env_dir.is_dir():
|
|
9196
|
-
shutil.rmtree(env_dir, ignore_errors=True)
|
|
9197
|
-
|
|
9198
|
-
# 5. Remove from Claude's history index so it doesn't recreate a
|
|
9199
|
-
# metadata stub (Claude rebuilds missing JSONLs from this index).
|
|
9200
|
-
history_ok = True
|
|
9201
|
-
if session_id is not None and not session_id.startswith("__new__-"):
|
|
9202
|
-
claude_home = getattr(
|
|
9203
|
-
self, "_claude_home", Path.home() / ".claude")
|
|
9204
|
-
history_ok = self._remove_from_history(
|
|
9205
|
-
session_id, claude_home=claude_home) is not False
|
|
9206
|
-
|
|
9207
|
-
# A writer outside the captured tmux process tree could race the first
|
|
9208
|
-
# unlink. Verify once after history cleanup; a surviving file means the
|
|
9209
|
-
# requested delete did not complete and must not be reported as such.
|
|
9210
|
-
if jsonl_path is not None and jsonl_path.exists():
|
|
9231
|
+
def _worker() -> None:
|
|
9211
9232
|
try:
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
self._invalidate_project_snapshot()
|
|
9219
|
-
self._refresh()
|
|
9220
|
-
self._set_status(
|
|
9221
|
-
f"session was recreated and could not be deleted: {exc}",
|
|
9222
|
-
"error",
|
|
9233
|
+
result = self._execute_delete_task(task)
|
|
9234
|
+
except Exception as exc:
|
|
9235
|
+
result = _DeleteResult(
|
|
9236
|
+
task=task,
|
|
9237
|
+
status_text=f"failed to delete {label}: {exc}",
|
|
9238
|
+
status_level="error",
|
|
9223
9239
|
)
|
|
9224
|
-
|
|
9240
|
+
with lock:
|
|
9241
|
+
self._delete_result = result
|
|
9225
9242
|
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
9243
|
+
thread = threading.Thread(
|
|
9244
|
+
target=_worker,
|
|
9245
|
+
name="railmux-session-delete",
|
|
9246
|
+
daemon=True,
|
|
9247
|
+
)
|
|
9248
|
+
self._delete_thread = thread
|
|
9249
|
+
thread.start()
|
|
9231
9250
|
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9251
|
+
def _execute_delete_task(self, task: _DeleteTask) -> _DeleteResult:
|
|
9252
|
+
"""Perform blocking exact kill and provider cleanup without UI state."""
|
|
9253
|
+
if task.pane_identity is not None:
|
|
9254
|
+
killed = tmux_ctl.kill_session_identity(task.pane_identity)
|
|
9255
|
+
still_alive = tmux_ctl.exact_pane_alive(task.pane_identity)
|
|
9256
|
+
if not killed and still_alive:
|
|
9257
|
+
return _DeleteResult(
|
|
9258
|
+
task=task,
|
|
9259
|
+
status_text=(f"failed to stop {task.tmux_name}; "
|
|
9260
|
+
"nothing was deleted"),
|
|
9261
|
+
status_level="error",
|
|
9262
|
+
)
|
|
9263
|
+
if (task.writer_pids
|
|
9264
|
+
and not tmux_ctl.wait_for_processes_exit(task.writer_pids)):
|
|
9265
|
+
return _DeleteResult(
|
|
9266
|
+
task=task,
|
|
9267
|
+
status_text=(f"{task.tmux_name} is still shutting down; "
|
|
9268
|
+
"nothing was deleted"),
|
|
9269
|
+
status_level="error",
|
|
9270
|
+
views_changed=True,
|
|
9271
|
+
)
|
|
9240
9272
|
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9273
|
+
if task.session_type == "codex":
|
|
9274
|
+
return self._execute_codex_delete(task)
|
|
9275
|
+
return self._execute_claude_delete(task)
|
|
9244
9276
|
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
reports exact counts. A placeholder (no resolved UUID yet) has no
|
|
9249
|
-
rollout on disk, so killing the tmux session is the whole operation."""
|
|
9277
|
+
def _execute_codex_delete(self, task: _DeleteTask) -> _DeleteResult:
|
|
9278
|
+
"""Delete one immutable Codex rewind lineage in the worker."""
|
|
9279
|
+
session_id = task.session_id
|
|
9250
9280
|
is_real = bool(session_id) and not session_id.startswith("__new__-")
|
|
9251
|
-
lineage_ids = (
|
|
9252
|
-
self._codex_lineage_ids(session_id)
|
|
9253
|
-
if is_real and session_id is not None else set()
|
|
9254
|
-
)
|
|
9255
9281
|
deleted_ids: set[str] = set()
|
|
9256
9282
|
failed_ids: set[str] = set()
|
|
9257
|
-
if is_real:
|
|
9283
|
+
if is_real and session_id is not None:
|
|
9258
9284
|
# A collapsed row represents the complete rewind lineage. Delete
|
|
9259
9285
|
# every provider UUID so an older checkpoint cannot immediately
|
|
9260
9286
|
# reappear as a "new" session after the newest leaf is removed.
|
|
9287
|
+
lineage_ids = set(task.lineage_ids) or {session_id}
|
|
9261
9288
|
ordered = [session_id] + sorted(lineage_ids - {session_id})
|
|
9262
9289
|
for candidate in ordered:
|
|
9263
9290
|
if self._codex_delete(candidate):
|
|
@@ -9265,29 +9292,145 @@ class App:
|
|
|
9265
9292
|
else:
|
|
9266
9293
|
failed_ids.add(candidate)
|
|
9267
9294
|
if failed_ids and not deleted_ids:
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
else:
|
|
9275
|
-
self._codex_index.invalidate()
|
|
9276
|
-
self._invalidate_project_snapshot()
|
|
9277
|
-
self._refresh()
|
|
9295
|
+
return _DeleteResult(
|
|
9296
|
+
task=task,
|
|
9297
|
+
status_text=f"codex delete failed: {task.label}",
|
|
9298
|
+
status_level="error",
|
|
9299
|
+
views_changed=task.pane_identity is not None,
|
|
9300
|
+
)
|
|
9278
9301
|
if failed_ids:
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9302
|
+
return _DeleteResult(
|
|
9303
|
+
task=task,
|
|
9304
|
+
status_text=(
|
|
9305
|
+
f"Deleted {len(deleted_ids)}/{len(task.lineage_ids)} "
|
|
9306
|
+
f"rewind records for {task.label}; "
|
|
9307
|
+
f"{len(failed_ids)} failed"
|
|
9308
|
+
),
|
|
9309
|
+
status_level="error",
|
|
9310
|
+
remove_running=True,
|
|
9311
|
+
views_changed=True,
|
|
9312
|
+
invalidate_codex=True,
|
|
9313
|
+
deleted_codex_ids=frozenset(deleted_ids),
|
|
9283
9314
|
)
|
|
9284
|
-
return
|
|
9285
9315
|
suffix = (
|
|
9286
9316
|
f" ({len(deleted_ids)} rewind records)"
|
|
9287
9317
|
if len(deleted_ids) > 1 else ""
|
|
9288
9318
|
)
|
|
9289
|
-
|
|
9290
|
-
|
|
9319
|
+
return _DeleteResult(
|
|
9320
|
+
task=task,
|
|
9321
|
+
status_text=(
|
|
9322
|
+
f"{'Deleted' if is_real else 'Killed'}: {task.label}{suffix}"
|
|
9323
|
+
),
|
|
9324
|
+
remove_running=True,
|
|
9325
|
+
views_changed=True,
|
|
9326
|
+
invalidate_codex=True,
|
|
9327
|
+
deleted_codex_ids=frozenset(deleted_ids),
|
|
9328
|
+
)
|
|
9329
|
+
|
|
9330
|
+
def _execute_claude_delete(self, task: _DeleteTask) -> _DeleteResult:
|
|
9331
|
+
"""Delete Claude history and metadata in the worker."""
|
|
9332
|
+
session_id = task.session_id
|
|
9333
|
+
jsonl_path = task.jsonl_path
|
|
9334
|
+
path_changed = False
|
|
9335
|
+
if jsonl_path is not None:
|
|
9336
|
+
try:
|
|
9337
|
+
jsonl_path.unlink(missing_ok=True)
|
|
9338
|
+
path_changed = True
|
|
9339
|
+
except OSError as exc:
|
|
9340
|
+
return _DeleteResult(
|
|
9341
|
+
task=task,
|
|
9342
|
+
status_text=f"failed to delete {jsonl_path}: {exc}",
|
|
9343
|
+
status_level="error",
|
|
9344
|
+
remove_running=True,
|
|
9345
|
+
views_changed=True,
|
|
9346
|
+
)
|
|
9347
|
+
|
|
9348
|
+
is_real = bool(session_id) and not session_id.startswith("__new__-")
|
|
9349
|
+
if is_real and session_id is not None:
|
|
9350
|
+
env_dir = task.claude_home / "session-env" / session_id
|
|
9351
|
+
if env_dir.is_dir():
|
|
9352
|
+
shutil.rmtree(env_dir, ignore_errors=True)
|
|
9353
|
+
history_ok = self._remove_from_history(
|
|
9354
|
+
session_id, claude_home=task.claude_home) is not False
|
|
9355
|
+
else:
|
|
9356
|
+
history_ok = True
|
|
9357
|
+
|
|
9358
|
+
# A writer outside the captured tmux process tree could race the first
|
|
9359
|
+
# unlink. Verify once after history cleanup; a surviving file means the
|
|
9360
|
+
# requested delete did not complete and must not be reported as such.
|
|
9361
|
+
if jsonl_path is not None and jsonl_path.exists():
|
|
9362
|
+
try:
|
|
9363
|
+
jsonl_path.unlink(missing_ok=True)
|
|
9364
|
+
path_changed = True
|
|
9365
|
+
except OSError as exc:
|
|
9366
|
+
return _DeleteResult(
|
|
9367
|
+
task=task,
|
|
9368
|
+
status_text=("session was recreated and could not be "
|
|
9369
|
+
f"deleted: {exc}"),
|
|
9370
|
+
status_level="error",
|
|
9371
|
+
remove_running=True,
|
|
9372
|
+
views_changed=True,
|
|
9373
|
+
claude_path_changed=path_changed,
|
|
9374
|
+
)
|
|
9375
|
+
|
|
9376
|
+
deleted = is_real and jsonl_path is not None
|
|
9377
|
+
if not history_ok:
|
|
9378
|
+
status_text = (
|
|
9379
|
+
f"Deleted: {task.label} (history index cleanup failed)"
|
|
9380
|
+
)
|
|
9381
|
+
status_level = "warn"
|
|
9382
|
+
else:
|
|
9383
|
+
status_text = f"{'Deleted' if deleted else 'Killed'}: {task.label}"
|
|
9384
|
+
status_level = "info"
|
|
9385
|
+
return _DeleteResult(
|
|
9386
|
+
task=task,
|
|
9387
|
+
status_text=status_text,
|
|
9388
|
+
status_level=status_level,
|
|
9389
|
+
remove_running=True,
|
|
9390
|
+
views_changed=True,
|
|
9391
|
+
claude_path_changed=path_changed,
|
|
9392
|
+
)
|
|
9393
|
+
|
|
9394
|
+
def _delete_pending(self) -> bool:
|
|
9395
|
+
"""Whether a cleanup is running or waiting for UI-thread publication."""
|
|
9396
|
+
lock = getattr(self, "_delete_lock", None)
|
|
9397
|
+
if lock is None:
|
|
9398
|
+
return False
|
|
9399
|
+
with lock:
|
|
9400
|
+
thread = getattr(self, "_delete_thread", None)
|
|
9401
|
+
return bool(
|
|
9402
|
+
getattr(self, "_delete_result", None) is not None
|
|
9403
|
+
or thread is not None and thread.is_alive()
|
|
9404
|
+
)
|
|
9405
|
+
|
|
9406
|
+
def _consume_delete_result(self) -> bool:
|
|
9407
|
+
"""Publish completed destructive work on the Urwid thread."""
|
|
9408
|
+
lock = getattr(self, "_delete_lock", None)
|
|
9409
|
+
if lock is None:
|
|
9410
|
+
return False
|
|
9411
|
+
with lock:
|
|
9412
|
+
result = getattr(self, "_delete_result", None)
|
|
9413
|
+
if result is None:
|
|
9414
|
+
return False
|
|
9415
|
+
self._delete_result = None
|
|
9416
|
+
self._delete_thread = None
|
|
9417
|
+
|
|
9418
|
+
task = result.task
|
|
9419
|
+
if result.remove_running:
|
|
9420
|
+
self._forget_running(task.session_id, task.tmux_name)
|
|
9421
|
+
if result.claude_path_changed and task.jsonl_path is not None:
|
|
9422
|
+
invalidate_session(task.jsonl_path)
|
|
9423
|
+
if result.invalidate_codex:
|
|
9424
|
+
if isinstance(self._codex_index, BackgroundCodexIndex):
|
|
9425
|
+
self._codex_index.invalidate(
|
|
9426
|
+
tombstones=set(result.deleted_codex_ids))
|
|
9427
|
+
else:
|
|
9428
|
+
self._codex_index.invalidate()
|
|
9429
|
+
if result.views_changed:
|
|
9430
|
+
self._session_cache.invalidate()
|
|
9431
|
+
self._invalidate_project_snapshot()
|
|
9432
|
+
self._set_status(result.status_text, result.status_level, force=True)
|
|
9433
|
+
return True
|
|
9291
9434
|
|
|
9292
9435
|
def _codex_delete(self, uuid: str) -> bool:
|
|
9293
9436
|
"""Run ``codex delete --force <UUID>`` against the resolved CODEX_HOME.
|
|
@@ -10342,6 +10485,15 @@ class App:
|
|
|
10342
10485
|
``set_message`` that clobbered one-shot messages every poll.
|
|
10343
10486
|
"""
|
|
10344
10487
|
now = time.monotonic()
|
|
10488
|
+
if self._delete_pending():
|
|
10489
|
+
# This is operation state, not a one-shot acknowledgement. Keep it
|
|
10490
|
+
# alive past the ordinary info TTL. A newly raised warning/error
|
|
10491
|
+
# retains its short severity hold, after which progress returns.
|
|
10492
|
+
if self._status_text != "Deleting…":
|
|
10493
|
+
self._set_status("Deleting…", "info")
|
|
10494
|
+
if self._status_text == "Deleting…":
|
|
10495
|
+
self._status_since = now
|
|
10496
|
+
return
|
|
10345
10497
|
if self._status_text is not None:
|
|
10346
10498
|
ttl = self._STATUS_TTL.get(self._status_level, 6.0)
|
|
10347
10499
|
if ttl is None or now - self._status_since < ttl:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: railmux
|
|
3
|
-
Version: 0.3.3.
|
|
3
|
+
Version: 0.3.3.dev5
|
|
4
4
|
Summary: Terminal UI to navigate, resume, and start Claude Code and Codex sessions
|
|
5
5
|
Author-email: Rightglow <zhang.taian@foxmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -204,7 +204,7 @@ see [FAQ 1](#1-how-do-i-copy-text-from-the-agent-pane).
|
|
|
204
204
|
| `r` | Rename the focused session |
|
|
205
205
|
| `s` | Toggle star — starred sessions pinned to top with ⭐ |
|
|
206
206
|
| `k` | Kill the running agent process (keeps session file) |
|
|
207
|
-
| `d` | Delete the focused session (
|
|
207
|
+
| `d` | Delete the focused session (confirms, then finishes in background) |
|
|
208
208
|
| `t` | Open or return to the managed terminal below the Target agent |
|
|
209
209
|
| `T` | Return to that agent's managed Vim viewer, when open |
|
|
210
210
|
| `m` | Switch agent mode (Claude Code ⇄ Codex) |
|
|
@@ -242,6 +242,10 @@ The rename popup starts with the current title pre-filled. Press
|
|
|
242
242
|
`Ctrl-U` to clear the entire input, `Enter` to save a non-empty title, or `Esc`
|
|
243
243
|
to cancel.
|
|
244
244
|
|
|
245
|
+
After a delete is confirmed, its popup closes immediately. The bottom-right
|
|
246
|
+
status shows **Deleting…** while Railmux stops the exact provider session and
|
|
247
|
+
removes its history in the background, then reports the final result.
|
|
248
|
+
|
|
245
249
|
Press `/` in Projects, Sessions, or Running to filter that section. Active
|
|
246
250
|
filters are marked with `[filtered]` and survive a soft restart; reopening `/`
|
|
247
251
|
shows the current value. Press `Ctrl-U` in the filter editor to clear it, then
|