overcode 0.2.4__tar.gz → 0.2.5__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.
- {overcode-0.2.4/src/overcode.egg-info → overcode-0.2.5}/PKG-INFO +1 -1
- {overcode-0.2.4 → overcode-0.2.5}/pyproject.toml +1 -1
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/agent.py +14 -1
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/monitor_daemon.py +59 -8
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/monitor_daemon_state.py +3 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/summary_columns.py +2 -2
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui.py +19 -4
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/command_bar.py +2 -2
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/daemon_status_bar.py +3 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/help_overlay.py +1 -1
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/session_summary.py +4 -9
- {overcode-0.2.4 → overcode-0.2.5/src/overcode.egg-info}/PKG-INFO +1 -1
- {overcode-0.2.4 → overcode-0.2.5}/LICENSE +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/MANIFEST.in +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/README.md +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/setup.cfg +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/__init__.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/agent_scanner.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/bundled_skills.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/claude_config.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/__init__.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/__main__.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/_shared.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/budget.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/config.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/daemon.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/hooks.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/monitoring.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/perms.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/sister.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/skills.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/config.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/daemon_claude_skill.md +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/daemon_logging.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/daemon_utils.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/data_export.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/dependency_check.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/exceptions.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/follow_mode.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/history_reader.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/hook_handler.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/hook_status_detector.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/implementations.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/interfaces.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/launcher.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/logging_config.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/mocks.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/monitor_daemon_core.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/notifier.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/pid_utils.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/presence_logger.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/protocols.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/session_manager.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/settings.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/sister_controller.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/sister_poller.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/standing_instructions.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/status_constants.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/status_detector.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/status_detector_factory.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/status_history.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/status_patterns.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/summarizer_client.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/summarizer_component.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/summary_groups.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/supervisor_daemon.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/supervisor_daemon_core.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/supervisor_layout.sh +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/testing/__init__.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/testing/renderer.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/testing/tmux_driver.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/testing/tui_eye.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/testing/tui_eye_skill.md +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/time_context.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tmux_manager.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tmux_utils.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui.tcss +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/__init__.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/daemon.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/input.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/navigation.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/session.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/view.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_helpers.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_logic.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_render.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/__init__.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/agent_select_modal.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/daemon_panel.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/fullscreen_preview.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/new_agent_defaults_modal.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/preview_pane.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/sister_selection_modal.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/status_timeline.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/summary_config_modal.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/usage_monitor.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web/__init__.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web/templates/analytics.html +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web/templates/dashboard.html +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_api.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_chartjs.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_control_api.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_server.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_server_runner.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_templates.py +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode.egg-info/SOURCES.txt +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode.egg-info/dependency_links.txt +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode.egg-info/entry_points.txt +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode.egg-info/requires.txt +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/src/overcode.egg-info/top_level.txt +0 -0
- {overcode-0.2.4 → overcode-0.2.5}/tests/test_e2e_multi_agent_jokes.py +0 -0
|
@@ -464,6 +464,11 @@ def list_agents(
|
|
|
464
464
|
if terminated_count > 0:
|
|
465
465
|
rprint(f"\n[dim]{terminated_count} terminated session(s). Run 'overcode cleanup' to remove.[/dim]")
|
|
466
466
|
|
|
467
|
+
# Hint about untracked tmux windows (#344)
|
|
468
|
+
if use_daemon and daemon_state.untracked_window_count > 0:
|
|
469
|
+
n = daemon_state.untracked_window_count
|
|
470
|
+
rprint(f"\n[dim]{n} untracked tmux window(s). Run 'overcode cleanup --untracked' to remove.[/dim]")
|
|
471
|
+
|
|
467
472
|
|
|
468
473
|
@app.command()
|
|
469
474
|
def attach(
|
|
@@ -598,6 +603,9 @@ def cleanup(
|
|
|
598
603
|
done: Annotated[
|
|
599
604
|
bool, typer.Option("--done", help="Also archive 'done' child agents (#244)")
|
|
600
605
|
] = False,
|
|
606
|
+
untracked: Annotated[
|
|
607
|
+
bool, typer.Option("--untracked", help="Kill tmux windows not tracked by any agent (#344)")
|
|
608
|
+
] = False,
|
|
601
609
|
session: SessionOption = "agents",
|
|
602
610
|
):
|
|
603
611
|
"""Remove terminated sessions from tracking.
|
|
@@ -606,6 +614,7 @@ def cleanup(
|
|
|
606
614
|
(e.g., after a machine reboot). Use 'overcode list' to see them.
|
|
607
615
|
|
|
608
616
|
Use --done to also archive done child agents (kill tmux window, move to archive).
|
|
617
|
+
Use --untracked to kill tmux windows that exist but aren't tracked by any agent.
|
|
609
618
|
"""
|
|
610
619
|
launcher = ClaudeLauncher(session)
|
|
611
620
|
count = launcher.cleanup_terminated_sessions()
|
|
@@ -619,6 +628,10 @@ def cleanup(
|
|
|
619
628
|
launcher._kill_single_session(sess)
|
|
620
629
|
done_count += 1
|
|
621
630
|
|
|
631
|
+
# Kill untracked tmux windows (#344)
|
|
632
|
+
if untracked:
|
|
633
|
+
launcher.list_sessions(kill_untracked=True)
|
|
634
|
+
|
|
622
635
|
total = count + done_count
|
|
623
636
|
if total > 0:
|
|
624
637
|
parts = []
|
|
@@ -627,7 +640,7 @@ def cleanup(
|
|
|
627
640
|
if done_count > 0:
|
|
628
641
|
parts.append(f"{done_count} done")
|
|
629
642
|
rprint(f"[green]✓ Cleaned up {' + '.join(parts)} session(s)[/green]")
|
|
630
|
-
|
|
643
|
+
elif not untracked:
|
|
631
644
|
rprint("[dim]No sessions to clean up[/dim]")
|
|
632
645
|
|
|
633
646
|
|
|
@@ -679,14 +679,37 @@ class MonitorDaemon:
|
|
|
679
679
|
|
|
680
680
|
# Archive: kill tmux window and mark terminated
|
|
681
681
|
try:
|
|
682
|
-
from .
|
|
683
|
-
tmux =
|
|
682
|
+
from .implementations import RealTmux
|
|
683
|
+
tmux = RealTmux()
|
|
684
684
|
tmux.kill_window(self.tmux_session, session.tmux_window)
|
|
685
685
|
except Exception:
|
|
686
686
|
pass # Window may already be gone
|
|
687
687
|
self.session_manager.update_session_status(session.id, "terminated")
|
|
688
688
|
self.log.info(f"Auto-archived done agent: {session.name}")
|
|
689
689
|
|
|
690
|
+
def _count_untracked_windows(self, sessions: list) -> int:
|
|
691
|
+
"""Count tmux windows not tracked by any active session (#344).
|
|
692
|
+
|
|
693
|
+
Returns count of windows that exist in tmux but aren't tracked
|
|
694
|
+
(excluding window 0 which is the default shell).
|
|
695
|
+
"""
|
|
696
|
+
try:
|
|
697
|
+
from .implementations import RealTmux
|
|
698
|
+
tmux = RealTmux()
|
|
699
|
+
if not tmux.session_exists(self.tmux_session):
|
|
700
|
+
return 0
|
|
701
|
+
tmux_windows = tmux.list_windows(self.tmux_session)
|
|
702
|
+
active_sessions = [s for s in sessions if s.status != "terminated"]
|
|
703
|
+
tracked_windows = {s.tmux_window for s in active_sessions}
|
|
704
|
+
count = 0
|
|
705
|
+
for window_info in tmux_windows:
|
|
706
|
+
window_idx = int(window_info['index'])
|
|
707
|
+
if window_idx != 0 and window_idx not in tracked_windows:
|
|
708
|
+
count += 1
|
|
709
|
+
return count
|
|
710
|
+
except Exception:
|
|
711
|
+
return 0
|
|
712
|
+
|
|
690
713
|
def _enforce_oversight_timeouts(self, sessions: list) -> None:
|
|
691
714
|
"""Enforce oversight timeouts for waiting_oversight sessions."""
|
|
692
715
|
now = datetime.now()
|
|
@@ -849,12 +872,30 @@ class MonitorDaemon:
|
|
|
849
872
|
session_states = []
|
|
850
873
|
all_waiting_user = True
|
|
851
874
|
|
|
875
|
+
# Detect window index collisions: when multiple sessions share
|
|
876
|
+
# a window index, only the most recently launched one owns it.
|
|
877
|
+
# Others are truly terminated (their window was reused).
|
|
878
|
+
from collections import Counter
|
|
879
|
+
window_counts = Counter(s.tmux_window for s in sessions if s.status != "done")
|
|
880
|
+
# For colliding windows, find the rightful owner (latest start_time)
|
|
881
|
+
window_owner = {}
|
|
882
|
+
for s in sessions:
|
|
883
|
+
if s.status == "done":
|
|
884
|
+
continue
|
|
885
|
+
w = s.tmux_window
|
|
886
|
+
if window_counts[w] > 1:
|
|
887
|
+
# Multiple sessions claim this window — latest launch wins
|
|
888
|
+
if w not in window_owner or s.start_time > window_owner[w].start_time:
|
|
889
|
+
window_owner[w] = s
|
|
890
|
+
|
|
852
891
|
for session in sessions:
|
|
853
|
-
#
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
892
|
+
# If this window is contested and we're not the owner, skip
|
|
893
|
+
pane_content = ""
|
|
894
|
+
if (window_counts.get(session.tmux_window, 0) > 1
|
|
895
|
+
and window_owner.get(session.tmux_window) is not session):
|
|
857
896
|
status, activity = STATUS_TERMINATED, "Session terminated"
|
|
897
|
+
if session.status != "terminated":
|
|
898
|
+
self.session_manager.update_session_status(session.id, "terminated")
|
|
858
899
|
elif session.status == "done":
|
|
859
900
|
status, activity = STATUS_DONE, "Completed"
|
|
860
901
|
else:
|
|
@@ -912,9 +953,17 @@ class MonitorDaemon:
|
|
|
912
953
|
else:
|
|
913
954
|
effective_status = status
|
|
914
955
|
|
|
915
|
-
# Persist terminated status
|
|
916
|
-
|
|
956
|
+
# Persist terminated status when window is truly gone.
|
|
957
|
+
# Only persist when pane_content is empty (window gone), not when
|
|
958
|
+
# a shell prompt is briefly visible (e.g. during agent revival).
|
|
959
|
+
if (effective_status == STATUS_TERMINATED
|
|
960
|
+
and session.status != "terminated"
|
|
961
|
+
and not pane_content):
|
|
917
962
|
self.session_manager.update_session_status(session.id, "terminated")
|
|
963
|
+
# Un-persist terminated if agent is found alive (revival or false positive)
|
|
964
|
+
elif (session.status == "terminated"
|
|
965
|
+
and effective_status != STATUS_TERMINATED):
|
|
966
|
+
self.session_manager.update_session_status(session.id, "running")
|
|
918
967
|
|
|
919
968
|
session_state = self.track_session_stats(session, effective_status)
|
|
920
969
|
session_state.current_activity = activity
|
|
@@ -981,8 +1030,10 @@ class MonitorDaemon:
|
|
|
981
1030
|
self._enforce_oversight_timeouts(sessions)
|
|
982
1031
|
|
|
983
1032
|
# Auto-archive "done" agents after 1 hour (#244)
|
|
1033
|
+
# Count untracked tmux windows every 2 minutes (#344)
|
|
984
1034
|
if self.state.loop_count % 60 == 0:
|
|
985
1035
|
self._auto_archive_done_agents(sessions)
|
|
1036
|
+
self.state.untracked_window_count = self._count_untracked_windows(sessions)
|
|
986
1037
|
|
|
987
1038
|
# Log summary
|
|
988
1039
|
green = sum(1 for s in session_states if s.current_status == STATUS_RUNNING)
|
|
@@ -174,6 +174,9 @@ class MonitorDaemonState:
|
|
|
174
174
|
relay_last_push: Optional[str] = None # ISO timestamp of last successful push
|
|
175
175
|
relay_last_status: str = "disabled" # "ok", "error", "disabled"
|
|
176
176
|
|
|
177
|
+
# Untracked tmux windows (#344)
|
|
178
|
+
untracked_window_count: int = 0
|
|
179
|
+
|
|
177
180
|
def to_dict(self) -> dict:
|
|
178
181
|
"""Convert to dictionary for JSON serialization."""
|
|
179
182
|
return dataclasses.asdict(self)
|
|
@@ -156,8 +156,8 @@ class ColumnContext:
|
|
|
156
156
|
local_hostname: str = ""
|
|
157
157
|
|
|
158
158
|
def mono(self, colored: str, simple: str = "bold") -> str:
|
|
159
|
-
"""Return
|
|
160
|
-
return
|
|
159
|
+
"""Return colored style (monochrome only applies to preview pane, not summaries)."""
|
|
160
|
+
return colored
|
|
161
161
|
|
|
162
162
|
def e(self, char: str) -> str:
|
|
163
163
|
"""Return ASCII fallback if emoji_free mode is active (#315)."""
|
|
@@ -27,7 +27,7 @@ from . import __version__
|
|
|
27
27
|
from .session_manager import SessionManager, Session
|
|
28
28
|
from .launcher import ClaudeLauncher
|
|
29
29
|
from .status_detector_factory import StatusDetectorDispatcher
|
|
30
|
-
from .status_constants import DEFAULT_CAPTURE_LINES, STATUS_RUNNING, STATUS_RUNNING_HEARTBEAT, STATUS_WAITING_HEARTBEAT, STATUS_WAITING_OVERSIGHT, STATUS_WAITING_USER, is_green_status
|
|
30
|
+
from .status_constants import DEFAULT_CAPTURE_LINES, STATUS_RUNNING, STATUS_RUNNING_HEARTBEAT, STATUS_TERMINATED, STATUS_WAITING_HEARTBEAT, STATUS_WAITING_OVERSIGHT, STATUS_WAITING_USER, is_green_status
|
|
31
31
|
from .history_reader import get_session_stats, ClaudeSessionStats, HistoryFile
|
|
32
32
|
from .settings import signal_activity, write_tui_heartbeat, get_event_loop_timing_path, TUIPreferences # Activity signaling to daemon
|
|
33
33
|
from .monitor_daemon_state import get_monitor_daemon_state
|
|
@@ -850,13 +850,30 @@ class SupervisorTUI(
|
|
|
850
850
|
session = fresh_sessions.get(widget.session.id, widget.session)
|
|
851
851
|
sessions_to_check.append((widget.session.id, session))
|
|
852
852
|
|
|
853
|
+
# Detect window index collisions: when multiple sessions share
|
|
854
|
+
# a window, only the most recently launched one owns it.
|
|
855
|
+
from collections import Counter
|
|
856
|
+
all_sessions = [s for _, s in sessions_to_check if s.status != "done"]
|
|
857
|
+
window_counts = Counter(s.tmux_window for s in all_sessions)
|
|
858
|
+
window_owner = {}
|
|
859
|
+
for s in all_sessions:
|
|
860
|
+
w = s.tmux_window
|
|
861
|
+
if window_counts[w] > 1:
|
|
862
|
+
if w not in window_owner or s.start_time > window_owner[w].start_time:
|
|
863
|
+
window_owner[w] = s
|
|
864
|
+
|
|
853
865
|
# Fetch only detect_status (capture_pane) in parallel — no heavy I/O
|
|
854
866
|
def fetch_status(session):
|
|
855
867
|
try:
|
|
856
868
|
if session.is_remote:
|
|
857
869
|
return (session.stats.current_state or "running", session.stats.current_task, session.pane_content or "")
|
|
870
|
+
# Window index collision — not the owner, truly terminated
|
|
871
|
+
if (window_counts.get(session.tmux_window, 0) > 1
|
|
872
|
+
and window_owner.get(session.tmux_window) is not session):
|
|
873
|
+
return (STATUS_TERMINATED, "Session terminated", "")
|
|
858
874
|
if session.status == "terminated":
|
|
859
|
-
|
|
875
|
+
# Re-check to detect revival (window is uncontested)
|
|
876
|
+
return self.detector.detect_status(session)
|
|
860
877
|
if session.status == "done":
|
|
861
878
|
# Still capture pane content so preview shows output
|
|
862
879
|
content = self.detector.get_pane_content(session.tmux_window) or ""
|
|
@@ -1389,7 +1406,6 @@ class SupervisorTUI(
|
|
|
1389
1406
|
)
|
|
1390
1407
|
widget.session = new_session
|
|
1391
1408
|
# Sync display modes
|
|
1392
|
-
widget.monochrome = self.monochrome
|
|
1393
1409
|
widget.emoji_free = self.emoji_free
|
|
1394
1410
|
# Sync pr_number from session (propagates both detection and clearing)
|
|
1395
1411
|
widget.pr_number = new_session.pr_number
|
|
@@ -1449,7 +1465,6 @@ class SupervisorTUI(
|
|
|
1449
1465
|
# Apply current summary content mode (#140)
|
|
1450
1466
|
widget.summary_content_mode = self.summary_content_mode
|
|
1451
1467
|
# Apply display modes
|
|
1452
|
-
widget.monochrome = self.monochrome
|
|
1453
1468
|
widget.emoji_free = self.emoji_free
|
|
1454
1469
|
widget.show_cost = self.show_cost
|
|
1455
1470
|
widget.any_has_budget = any_has_budget
|
|
@@ -70,7 +70,7 @@ class CommandBar(Static):
|
|
|
70
70
|
"""Inline command bar for sending instructions to agents.
|
|
71
71
|
|
|
72
72
|
Supports single-line (Input) and multi-line (TextArea) modes.
|
|
73
|
-
Toggle with Ctrl+E. Send with Enter (single) or Ctrl+Enter (multi).
|
|
73
|
+
Toggle with Ctrl+E. Send with Enter (single) or Ctrl+S / Ctrl+Enter (multi).
|
|
74
74
|
Use Ctrl+O to set as standing order instead of sending.
|
|
75
75
|
|
|
76
76
|
Modes:
|
|
@@ -242,7 +242,7 @@ class CommandBar(Static):
|
|
|
242
242
|
elif event.key == "escape":
|
|
243
243
|
self.action_clear_and_unfocus()
|
|
244
244
|
event.stop()
|
|
245
|
-
elif event.key
|
|
245
|
+
elif event.key in ("ctrl+enter", "ctrl+s") and self.expanded:
|
|
246
246
|
self.action_send_multiline()
|
|
247
247
|
event.stop()
|
|
248
248
|
|
|
@@ -155,6 +155,9 @@ class DaemonStatusBar(Static):
|
|
|
155
155
|
# Version mismatch warning
|
|
156
156
|
if state.daemon_version != DAEMON_VERSION:
|
|
157
157
|
content.append(f" ⚠v{state.daemon_version}→{DAEMON_VERSION}", style="bold yellow")
|
|
158
|
+
# Untracked tmux windows warning (#344)
|
|
159
|
+
if state.untracked_window_count > 0:
|
|
160
|
+
content.append(f" ⚠{state.untracked_window_count} untracked", style="bold yellow")
|
|
158
161
|
else:
|
|
159
162
|
content.append("○ ", style="red")
|
|
160
163
|
content.append("stopped", style="red")
|
|
@@ -81,7 +81,7 @@ class HelpOverlay(Static):
|
|
|
81
81
|
section("COMMAND BAR (i or :)")
|
|
82
82
|
row("Enter", "Send instruction", "Esc", "Clear & unfocus")
|
|
83
83
|
row("Ctrl+E", "Multi-line mode", "Ctrl+O", "Set standing order")
|
|
84
|
-
row("Ctrl+
|
|
84
|
+
row("Ctrl+S", "Send (multi-line)")
|
|
85
85
|
|
|
86
86
|
return t
|
|
87
87
|
|
|
@@ -70,7 +70,7 @@ class SessionSummary(Static, can_focus=True):
|
|
|
70
70
|
# AI-generated summaries (from daemon's SummarizerComponent)
|
|
71
71
|
self.ai_summary_short: str = "" # Short: current activity (~50 chars)
|
|
72
72
|
self.ai_summary_context: str = "" # Context: wider context (~80 chars)
|
|
73
|
-
self.monochrome: bool = False #
|
|
73
|
+
self.monochrome: bool = False # Legacy, kept for compatibility but no longer used for summaries
|
|
74
74
|
self.emoji_free: bool = False # ASCII fallbacks for emoji (#315)
|
|
75
75
|
self.show_cost: bool = False # Show $ cost instead of token counts
|
|
76
76
|
self.any_has_budget: bool = False # True if any agent has a cost budget (#173)
|
|
@@ -258,14 +258,9 @@ class SessionSummary(Static, can_focus=True):
|
|
|
258
258
|
# Status styling
|
|
259
259
|
from ..status_constants import emoji_or_ascii
|
|
260
260
|
ef = self.emoji_free
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
status_color = "bold"
|
|
265
|
-
else:
|
|
266
|
-
bg = " on #0d2137"
|
|
267
|
-
status_symbol, base_color = get_status_symbol(self.detected_status, emoji_free=ef)
|
|
268
|
-
status_color = f"bold {base_color}{bg}"
|
|
261
|
+
bg = " on #0d2137"
|
|
262
|
+
status_symbol, base_color = get_status_symbol(self.detected_status, emoji_free=ef)
|
|
263
|
+
status_color = f"bold {base_color}{bg}"
|
|
269
264
|
|
|
270
265
|
# Permissiveness mode emoji
|
|
271
266
|
if s.permissiveness_mode == "bypass":
|
|
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
|