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.
Files changed (111) hide show
  1. {overcode-0.2.4/src/overcode.egg-info → overcode-0.2.5}/PKG-INFO +1 -1
  2. {overcode-0.2.4 → overcode-0.2.5}/pyproject.toml +1 -1
  3. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/agent.py +14 -1
  4. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/monitor_daemon.py +59 -8
  5. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/monitor_daemon_state.py +3 -0
  6. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/summary_columns.py +2 -2
  7. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui.py +19 -4
  8. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/command_bar.py +2 -2
  9. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/daemon_status_bar.py +3 -0
  10. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/help_overlay.py +1 -1
  11. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/session_summary.py +4 -9
  12. {overcode-0.2.4 → overcode-0.2.5/src/overcode.egg-info}/PKG-INFO +1 -1
  13. {overcode-0.2.4 → overcode-0.2.5}/LICENSE +0 -0
  14. {overcode-0.2.4 → overcode-0.2.5}/MANIFEST.in +0 -0
  15. {overcode-0.2.4 → overcode-0.2.5}/README.md +0 -0
  16. {overcode-0.2.4 → overcode-0.2.5}/setup.cfg +0 -0
  17. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/__init__.py +0 -0
  18. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/agent_scanner.py +0 -0
  19. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/bundled_skills.py +0 -0
  20. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/claude_config.py +0 -0
  21. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/__init__.py +0 -0
  22. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/__main__.py +0 -0
  23. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/_shared.py +0 -0
  24. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/budget.py +0 -0
  25. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/config.py +0 -0
  26. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/daemon.py +0 -0
  27. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/hooks.py +0 -0
  28. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/monitoring.py +0 -0
  29. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/perms.py +0 -0
  30. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/sister.py +0 -0
  31. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/cli/skills.py +0 -0
  32. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/config.py +0 -0
  33. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/daemon_claude_skill.md +0 -0
  34. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/daemon_logging.py +0 -0
  35. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/daemon_utils.py +0 -0
  36. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/data_export.py +0 -0
  37. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/dependency_check.py +0 -0
  38. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/exceptions.py +0 -0
  39. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/follow_mode.py +0 -0
  40. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/history_reader.py +0 -0
  41. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/hook_handler.py +0 -0
  42. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/hook_status_detector.py +0 -0
  43. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/implementations.py +0 -0
  44. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/interfaces.py +0 -0
  45. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/launcher.py +0 -0
  46. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/logging_config.py +0 -0
  47. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/mocks.py +0 -0
  48. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/monitor_daemon_core.py +0 -0
  49. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/notifier.py +0 -0
  50. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/pid_utils.py +0 -0
  51. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/presence_logger.py +0 -0
  52. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/protocols.py +0 -0
  53. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/session_manager.py +0 -0
  54. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/settings.py +0 -0
  55. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/sister_controller.py +0 -0
  56. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/sister_poller.py +0 -0
  57. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/standing_instructions.py +0 -0
  58. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/status_constants.py +0 -0
  59. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/status_detector.py +0 -0
  60. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/status_detector_factory.py +0 -0
  61. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/status_history.py +0 -0
  62. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/status_patterns.py +0 -0
  63. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/summarizer_client.py +0 -0
  64. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/summarizer_component.py +0 -0
  65. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/summary_groups.py +0 -0
  66. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/supervisor_daemon.py +0 -0
  67. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/supervisor_daemon_core.py +0 -0
  68. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/supervisor_layout.sh +0 -0
  69. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/testing/__init__.py +0 -0
  70. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/testing/renderer.py +0 -0
  71. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/testing/tmux_driver.py +0 -0
  72. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/testing/tui_eye.py +0 -0
  73. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/testing/tui_eye_skill.md +0 -0
  74. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/time_context.py +0 -0
  75. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tmux_manager.py +0 -0
  76. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tmux_utils.py +0 -0
  77. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui.tcss +0 -0
  78. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/__init__.py +0 -0
  79. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/daemon.py +0 -0
  80. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/input.py +0 -0
  81. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/navigation.py +0 -0
  82. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/session.py +0 -0
  83. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_actions/view.py +0 -0
  84. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_helpers.py +0 -0
  85. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_logic.py +0 -0
  86. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_render.py +0 -0
  87. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/__init__.py +0 -0
  88. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/agent_select_modal.py +0 -0
  89. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/daemon_panel.py +0 -0
  90. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/fullscreen_preview.py +0 -0
  91. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/new_agent_defaults_modal.py +0 -0
  92. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/preview_pane.py +0 -0
  93. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/sister_selection_modal.py +0 -0
  94. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/status_timeline.py +0 -0
  95. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/tui_widgets/summary_config_modal.py +0 -0
  96. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/usage_monitor.py +0 -0
  97. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web/__init__.py +0 -0
  98. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web/templates/analytics.html +0 -0
  99. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web/templates/dashboard.html +0 -0
  100. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_api.py +0 -0
  101. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_chartjs.py +0 -0
  102. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_control_api.py +0 -0
  103. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_server.py +0 -0
  104. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_server_runner.py +0 -0
  105. {overcode-0.2.4 → overcode-0.2.5}/src/overcode/web_templates.py +0 -0
  106. {overcode-0.2.4 → overcode-0.2.5}/src/overcode.egg-info/SOURCES.txt +0 -0
  107. {overcode-0.2.4 → overcode-0.2.5}/src/overcode.egg-info/dependency_links.txt +0 -0
  108. {overcode-0.2.4 → overcode-0.2.5}/src/overcode.egg-info/entry_points.txt +0 -0
  109. {overcode-0.2.4 → overcode-0.2.5}/src/overcode.egg-info/requires.txt +0 -0
  110. {overcode-0.2.4 → overcode-0.2.5}/src/overcode.egg-info/top_level.txt +0 -0
  111. {overcode-0.2.4 → overcode-0.2.5}/tests/test_e2e_multi_agent_jokes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: overcode
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: A supervisor for managing multiple Claude Code instances in tmux
5
5
  Author: Mike Bond
6
6
  Project-URL: Homepage, https://github.com/mkb23/overcode
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "overcode"
7
- version = "0.2.4"
7
+ version = "0.2.5"
8
8
  description = "A supervisor for managing multiple Claude Code instances in tmux"
9
9
  authors = [
10
10
  {name = "Mike Bond"}
@@ -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
- else:
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 .tmux_utils import TmuxHelper
683
- tmux = TmuxHelper()
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
- # Skip sessions already known to be terminated/done
854
- # avoids a wasted tmux call and prevents desync where
855
- # detect_status returns waiting_user for a gone window.
856
- if session.status == "terminated":
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 so future loops skip detect_status
916
- if effective_status == STATUS_TERMINATED and session.status != "terminated":
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 simplified style when monochrome is enabled."""
160
- return simple if self.monochrome else colored
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
- return ("terminated", "(tmux window no longer exists)", "")
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 == "ctrl+enter" and self.expanded:
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+Enter", "Send (multi-line)")
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 # B&W mode for terminals with ANSI issues (#138)
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
- if self.monochrome:
262
- bg = ""
263
- status_symbol, _ = get_status_symbol(self.detected_status, emoji_free=ef)
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":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: overcode
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: A supervisor for managing multiple Claude Code instances in tmux
5
5
  Author: Mike Bond
6
6
  Project-URL: Homepage, https://github.com/mkb23/overcode
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes