railmux 0.3.3.dev1__tar.gz → 0.3.3.dev2__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.dev1/src/railmux.egg-info → railmux-0.3.3.dev2}/PKG-INFO +1 -1
- railmux-0.3.3.dev2/src/railmux/__init__.py +1 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/display_transport.py +18 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ui/app.py +30 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_display_transport.py +21 -1
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_soft_quit.py +39 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_tmux_integration.py +9 -2
- railmux-0.3.3.dev1/src/railmux/__init__.py +0 -1
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/LICENSE +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/MANIFEST.in +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/README.md +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/pyproject.toml +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/setup.cfg +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/__main__.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/atomic_file.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/background_index.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/cli.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/codex_index.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/config.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/config_cli.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/diagnostics.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/discovery.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/fast_display_client.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/fast_display_server.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/favorites.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/fuzzy.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/help_workspace.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/launcher.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/local_open.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/models.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/modes.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/pane_surface.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/path_codec.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/remote_config.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/renames.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/restart_state.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/runtime_config.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/self_update.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/session_cache.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/session_index.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/settings.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ssh_args.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/system_deps.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/terminal_status.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/tmux_health.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/tmux_server.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/tool_panes.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/transcript.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ui/modals.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux.egg-info/SOURCES.txt +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_agent_workspace.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_atomic_file.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_background_index.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_cli.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_codex_delete.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_codex_index.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_codex_lineage.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_codex_transcript.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_config.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_config_cli.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_diagnostics.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_discovery.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_fast_full_window.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_focus_highlight.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_fuzzy.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_help_agent.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_help_workspace.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_history_cleanup.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_keymap.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_launcher.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_layout_profile.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_local_clipboard.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_local_open.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_modals.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_mode_selection.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_modes.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_mouse_manager.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_new_project.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_options.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_pane_surface.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_path_codec.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_projects_pane.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_release_notes.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_release_workflow.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_remote_config.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_renames.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_restart_state.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_running_pane.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_running_sort.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_runtime_config.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_scroll_agent.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_scroll_manager.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_selection_isolation.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_self_update.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_session_cache.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_session_index.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_sessions_pane.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_setting_contracts.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_settings.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_sidebar.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_ssh_args.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_ssh_compat.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_status.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_statusbar.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_system_deps.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_terminal_size.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_terminal_status.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_tmux_health.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_tmux_server.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_tmux_status.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_tool_panes.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_transcript.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_utf8_state.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_widgets.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tools/release_notes.py +0 -0
- {railmux-0.3.3.dev1 → railmux-0.3.3.dev2}/tools/wheel_smoke.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.3.dev2"
|
|
@@ -738,6 +738,22 @@ class AgentDisplayTransport:
|
|
|
738
738
|
def _with_phase(state: SwapState, phase: str) -> SwapState:
|
|
739
739
|
return SwapState(**{**asdict(state), "phase": phase})
|
|
740
740
|
|
|
741
|
+
@staticmethod
|
|
742
|
+
def _fit_home_to_display(state: SwapState) -> None:
|
|
743
|
+
"""Best-effort preserve the provider's visible geometry at home.
|
|
744
|
+
|
|
745
|
+
While a real pane is displayed, its named session contains only the
|
|
746
|
+
inert placeholder. Resize that detached home window to the real
|
|
747
|
+
pane's current size before swapping back. Otherwise background output
|
|
748
|
+
is formatted at the placeholder's older (often much narrower) launch
|
|
749
|
+
geometry and later appears as a narrow discontinuity in scrollback.
|
|
750
|
+
|
|
751
|
+
Geometry is presentation-only: failure must never weaken or abort the
|
|
752
|
+
identity-pinned return transaction.
|
|
753
|
+
"""
|
|
754
|
+
tmux_ctl.fit_session_to_pane(
|
|
755
|
+
state.agent_tmux_name, state.agent_pane_id)
|
|
756
|
+
|
|
741
757
|
def park(self, slot: AgentSlot) -> bool:
|
|
742
758
|
"""Move one displayed swap agent home without forgetting its slot.
|
|
743
759
|
|
|
@@ -764,6 +780,7 @@ class AgentDisplayTransport:
|
|
|
764
780
|
parking = self._with_phase(state, "parking")
|
|
765
781
|
if not _write_marker_pair(parking, slot.key):
|
|
766
782
|
return False
|
|
783
|
+
self._fit_home_to_display(state)
|
|
767
784
|
if not tmux_ctl.swap_panes(
|
|
768
785
|
state.agent_pane_id, state.placeholder_pane_id):
|
|
769
786
|
_write_marker_pair(state, slot.key)
|
|
@@ -857,6 +874,7 @@ class AgentDisplayTransport:
|
|
|
857
874
|
return False
|
|
858
875
|
returning = SwapState(**{**asdict(state), "phase": "returning"})
|
|
859
876
|
_write_marker_pair(returning, slot.key)
|
|
877
|
+
self._fit_home_to_display(state)
|
|
860
878
|
if not tmux_ctl.swap_panes(
|
|
861
879
|
state.agent_pane_id, state.placeholder_pane_id):
|
|
862
880
|
return False
|
|
@@ -8520,6 +8520,7 @@ class App:
|
|
|
8520
8520
|
# Codex New Project can use its in-memory synthetic project earlier.
|
|
8521
8521
|
by_path = {p.real_path: p for p in projects}
|
|
8522
8522
|
claimed = self._running_session_ids() | set(self._running)
|
|
8523
|
+
resolved_any = False
|
|
8523
8524
|
for r in placeholders:
|
|
8524
8525
|
# Codex New Project owns an in-memory synthetic project before its
|
|
8525
8526
|
# first rollout makes that cwd visible in the Codex index. Claude
|
|
@@ -8605,15 +8606,30 @@ class App:
|
|
|
8605
8606
|
r.project = candidate.project
|
|
8606
8607
|
r.placeholder_path = None
|
|
8607
8608
|
r.created_at = 0.0
|
|
8609
|
+
# Publish the same provider generation atomically to Running. The
|
|
8610
|
+
# normal refinement pass below may add process-derived status, but
|
|
8611
|
+
# the promoted row must never retain its stale ``(new)`` label,
|
|
8612
|
+
# zero mtime, or placeholder activity for one extra refresh.
|
|
8613
|
+
r.status = candidate.status
|
|
8614
|
+
r.last_mtime = candidate.last_mtime
|
|
8615
|
+
r.attention = candidate.attention
|
|
8608
8616
|
self._running[candidate.session_id] = r
|
|
8609
8617
|
self._stamp_running(r)
|
|
8610
8618
|
claimed.add(candidate.session_id)
|
|
8619
|
+
resolved_any = True
|
|
8611
8620
|
displayed_slot = self._agent_workspace().slot_for_agent(r.tmux_name)
|
|
8612
8621
|
if displayed_slot is not None:
|
|
8613
8622
|
self._set_slot_active_target(
|
|
8614
8623
|
displayed_slot, candidate.session_id, r.tmux_name)
|
|
8615
8624
|
if displayed_slot is self._agent_workspace().target:
|
|
8616
8625
|
self._set_current_project(candidate.project)
|
|
8626
|
+
if resolved_any:
|
|
8627
|
+
# Placeholder launch time may already have consumed the ordinary
|
|
8628
|
+
# one-minute sort budget. Its first authoritative provider
|
|
8629
|
+
# generation is a one-time identity transition, so allow the
|
|
8630
|
+
# immediately following Running refresh to place it by real
|
|
8631
|
+
# activity without enabling per-poll row movement.
|
|
8632
|
+
self._running_sort_ts = 0.0
|
|
8617
8633
|
|
|
8618
8634
|
def _correlate_codex_rollout(self, r: "_Running") -> set[str] | None:
|
|
8619
8635
|
"""Exact child→rollout correlation for a Codex placeholder (#12).
|
|
@@ -8628,6 +8644,20 @@ class App:
|
|
|
8628
8644
|
(heuristic) rather than raising into the UI."""
|
|
8629
8645
|
try:
|
|
8630
8646
|
sessions_dir = self._codex_home_path() / "sessions"
|
|
8647
|
+
# Swap display moves the real provider pane out of its named home
|
|
8648
|
+
# session, leaving an inert placeholder there. Follow the exact
|
|
8649
|
+
# displayed pane identity just as status aggregation does;
|
|
8650
|
+
# querying the home session in this state can only inspect the
|
|
8651
|
+
# placeholder and leaves every new session unresolved until the
|
|
8652
|
+
# first preview happens to return it home.
|
|
8653
|
+
real_pane = self._display_transport().displayed_real_pane(
|
|
8654
|
+
r.tmux_name)
|
|
8655
|
+
if real_pane is not None:
|
|
8656
|
+
identity = tmux_ctl.pane_identity(real_pane)
|
|
8657
|
+
if identity is None:
|
|
8658
|
+
return set() if tmux_ctl.proc_fs_available() else None
|
|
8659
|
+
return tmux_ctl.process_tree_rollout_ids(
|
|
8660
|
+
identity.pane_pid, sessions_dir)
|
|
8631
8661
|
return tmux_ctl.session_rollout_ids(r.tmux_name, sessions_dir)
|
|
8632
8662
|
except Exception:
|
|
8633
8663
|
# On a procfs platform an inspection failure is ambiguity, not
|
|
@@ -41,6 +41,7 @@ class FakeTmux:
|
|
|
41
41
|
self.next_pane = 10
|
|
42
42
|
self.next_session = 10
|
|
43
43
|
self.swap_calls: list[tuple[str, str]] = []
|
|
44
|
+
self.fit_calls: list[tuple[str, str]] = []
|
|
44
45
|
self.killed_sessions: list[str] = []
|
|
45
46
|
self.fail_marker_window: str | None = None
|
|
46
47
|
self.fail_swap_at: int | None = None
|
|
@@ -69,7 +70,7 @@ class FakeTmux:
|
|
|
69
70
|
"split_window_v": self.split_window_h,
|
|
70
71
|
"create_dual_pane_layout": self.create_dual_pane_layout,
|
|
71
72
|
"respawn_pane": self.respawn_pane,
|
|
72
|
-
"fit_session_to_pane":
|
|
73
|
+
"fit_session_to_pane": self.fit_session_to_pane,
|
|
73
74
|
"wait_session_detached": lambda name, timeout=1.0: (
|
|
74
75
|
self.sessions[name]["attached"] == 0),
|
|
75
76
|
"create_grouped_session": self.create_grouped_session,
|
|
@@ -218,6 +219,10 @@ class FakeTmux:
|
|
|
218
219
|
self._relocate(target, source_window)
|
|
219
220
|
return True
|
|
220
221
|
|
|
222
|
+
def fit_session_to_pane(self, session, pane):
|
|
223
|
+
self.fit_calls.append((session, pane))
|
|
224
|
+
return True
|
|
225
|
+
|
|
221
226
|
def kill_session(self, name):
|
|
222
227
|
session = self.sessions.pop(name, None)
|
|
223
228
|
if session is None:
|
|
@@ -310,6 +315,7 @@ def test_successful_swap_out_and_home(rig):
|
|
|
310
315
|
assert fake.window_options
|
|
311
316
|
|
|
312
317
|
assert manager.return_home(workspace.primary)
|
|
318
|
+
assert fake.fit_calls == [("agent-a", "%2")]
|
|
313
319
|
assert fake.panes["%2"].window_id == "@2"
|
|
314
320
|
assert workspace.primary.pane_id == placeholder
|
|
315
321
|
assert workspace.primary.swap_state is None
|
|
@@ -324,6 +330,7 @@ def test_compact_parking_keeps_agent_home_and_slot_placeholder_stable(rig):
|
|
|
324
330
|
assert state is not None
|
|
325
331
|
|
|
326
332
|
assert manager.park(workspace.primary)
|
|
333
|
+
assert fake.fit_calls == [("agent-a", "%2")]
|
|
327
334
|
assert workspace.primary.display_parked is True
|
|
328
335
|
assert workspace.primary.agent_tmux_name == "agent-a"
|
|
329
336
|
assert workspace.primary.pane_id == state.placeholder_pane_id
|
|
@@ -356,6 +363,19 @@ def test_compact_park_swap_failure_leaves_agent_displayed(rig):
|
|
|
356
363
|
assert fake.panes["%2"].window_id == "@1"
|
|
357
364
|
|
|
358
365
|
|
|
366
|
+
def test_return_geometry_failure_does_not_weaken_identity_transaction(
|
|
367
|
+
rig, monkeypatch,
|
|
368
|
+
):
|
|
369
|
+
fake, workspace, manager = rig
|
|
370
|
+
assert manager.attach(workspace.primary, "agent-a").ok
|
|
371
|
+
monkeypatch.setattr(
|
|
372
|
+
transport_mod.tmux_ctl, "fit_session_to_pane", lambda *_args: False)
|
|
373
|
+
|
|
374
|
+
assert manager.return_home(workspace.primary)
|
|
375
|
+
assert fake.panes["%2"].window_id == "@2"
|
|
376
|
+
assert workspace.primary.swap_state is None
|
|
377
|
+
|
|
378
|
+
|
|
359
379
|
def test_compact_park_marker_commit_failure_rolls_back_display(rig, monkeypatch):
|
|
360
380
|
fake, workspace, manager = rig
|
|
361
381
|
assert manager.attach(workspace.primary, "agent-a").ok
|
|
@@ -2817,6 +2817,7 @@ def test_resolve_placeholders_codex_rekeys_to_real_uuid():
|
|
|
2817
2817
|
app = App.__new__(App)
|
|
2818
2818
|
app._codex_mode = True
|
|
2819
2819
|
app._right_pane_claude = None
|
|
2820
|
+
app._running_sort_ts = 1234.0
|
|
2820
2821
|
app._running = {
|
|
2821
2822
|
"__new__-1": _Running(
|
|
2822
2823
|
key="__new__-1", tmux_name="cx-new----1",
|
|
@@ -2840,6 +2841,10 @@ def test_resolve_placeholders_codex_rekeys_to_real_uuid():
|
|
|
2840
2841
|
entry = app._running[real_id]
|
|
2841
2842
|
assert entry.tmux_name == "cx-new----1" # same tmux session, re-keyed
|
|
2842
2843
|
assert not entry.is_placeholder
|
|
2844
|
+
assert entry.label == "test-proj/Real session"
|
|
2845
|
+
assert entry.status == "idle"
|
|
2846
|
+
assert entry.last_mtime == 1000.0
|
|
2847
|
+
assert app._running_sort_ts == 0.0
|
|
2843
2848
|
app._codex_index.sessions_for_cwd.assert_called_once_with(
|
|
2844
2849
|
proj.real_path, refresh=False)
|
|
2845
2850
|
|
|
@@ -3180,6 +3185,40 @@ def test_correlate_codex_rollout_fails_closed_without_config(monkeypatch):
|
|
|
3180
3185
|
assert app._correlate_codex_rollout(r) == set()
|
|
3181
3186
|
|
|
3182
3187
|
|
|
3188
|
+
def test_correlate_codex_rollout_follows_swap_displayed_real_pane(monkeypatch):
|
|
3189
|
+
"""A swap leaves only an inert placeholder in the named home session."""
|
|
3190
|
+
session_id = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
|
|
3191
|
+
sessions_dir = Path("/codex/sessions")
|
|
3192
|
+
app = App.__new__(App)
|
|
3193
|
+
app._codex_home_path = lambda: sessions_dir.parent
|
|
3194
|
+
manager = MagicMock()
|
|
3195
|
+
manager.displayed_real_pane.return_value = "%42"
|
|
3196
|
+
app._display_transport_manager = manager
|
|
3197
|
+
running = _Running(
|
|
3198
|
+
key="__new__-tok-1",
|
|
3199
|
+
tmux_name="cx-new----tok-1",
|
|
3200
|
+
label="test-proj/(new)",
|
|
3201
|
+
session_type="codex",
|
|
3202
|
+
)
|
|
3203
|
+
monkeypatch.setattr(
|
|
3204
|
+
tmux_ctl,
|
|
3205
|
+
"pane_identity",
|
|
3206
|
+
lambda pane: tmux_ctl.PaneIdentity(
|
|
3207
|
+
pane, 4242, "railmux", "$1", "@1", False, 100, 30
|
|
3208
|
+
),
|
|
3209
|
+
)
|
|
3210
|
+
process_probe = MagicMock(return_value={session_id})
|
|
3211
|
+
monkeypatch.setattr(tmux_ctl, "process_tree_rollout_ids", process_probe)
|
|
3212
|
+
home_probe = MagicMock(side_effect=AssertionError(
|
|
3213
|
+
"the home session contains only the swap placeholder"
|
|
3214
|
+
))
|
|
3215
|
+
monkeypatch.setattr(tmux_ctl, "session_rollout_ids", home_probe)
|
|
3216
|
+
|
|
3217
|
+
assert app._correlate_codex_rollout(running) == {session_id}
|
|
3218
|
+
process_probe.assert_called_once_with(4242, sessions_dir)
|
|
3219
|
+
home_probe.assert_not_called()
|
|
3220
|
+
|
|
3221
|
+
|
|
3183
3222
|
def test_launch_snapshots_pre_existing_ids(monkeypatch):
|
|
3184
3223
|
"""_launch captures the cwd's existing session ids into the placeholder's
|
|
3185
3224
|
pre_launch_ids before starting the child (#12)."""
|
|
@@ -2308,7 +2308,7 @@ def test_real_compact_parking_keeps_hidden_provider_geometry_stable(
|
|
|
2308
2308
|
check=True,
|
|
2309
2309
|
)
|
|
2310
2310
|
subprocess.run(
|
|
2311
|
-
["tmux", "new-session", "-d", "-x", "
|
|
2311
|
+
["tmux", "new-session", "-d", "-x", "46", "-y", "25",
|
|
2312
2312
|
"-s", "compact-park-agent", "sleep 60"],
|
|
2313
2313
|
check=True,
|
|
2314
2314
|
)
|
|
@@ -2327,10 +2327,17 @@ def test_real_compact_parking_keeps_hidden_provider_geometry_stable(
|
|
|
2327
2327
|
state = workspace.primary.swap_state
|
|
2328
2328
|
assert state is not None
|
|
2329
2329
|
provider_pid = tmux_ctl.pane_identity(state.agent_pane_id).pane_pid
|
|
2330
|
+
visible_size = tmux_ctl.pane_size(state.agent_pane_id)
|
|
2331
|
+
assert visible_size is not None
|
|
2332
|
+
assert visible_size[0] == 82
|
|
2333
|
+
assert visible_size != (46, 25)
|
|
2330
2334
|
|
|
2331
2335
|
assert manager.park(workspace.primary)
|
|
2332
2336
|
parked_size = tmux_ctl.pane_size(state.agent_pane_id)
|
|
2333
|
-
|
|
2337
|
+
# The detached home began at 46x25. Parking must carry over the real
|
|
2338
|
+
# pane's visible geometry instead of narrowing background output back to
|
|
2339
|
+
# that stale launch size.
|
|
2340
|
+
assert parked_size == visible_size
|
|
2334
2341
|
subprocess.run(
|
|
2335
2342
|
["tmux", "resize-window", "-t", display_session, "-x", "105", "-y", "20"],
|
|
2336
2343
|
check=True,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.3.dev1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|