overcode 0.3.1__tar.gz → 0.3.3__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 (119) hide show
  1. {overcode-0.3.1/src/overcode.egg-info → overcode-0.3.3}/PKG-INFO +1 -1
  2. {overcode-0.3.1 → overcode-0.3.3}/pyproject.toml +1 -1
  3. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/claude_config.py +17 -0
  4. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/agent.py +2 -1
  5. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/split.py +47 -28
  6. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/history_reader.py +31 -1
  7. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/hook_handler.py +3 -0
  8. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/hook_status_detector.py +104 -91
  9. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/job_launcher.py +18 -3
  10. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/monitor_daemon.py +46 -0
  11. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/settings.py +33 -1
  12. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/status_detector.py +31 -4
  13. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/status_detector_factory.py +26 -16
  14. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/summary_columns.py +13 -4
  15. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/supervisor_layout.sh +10 -5
  16. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tmux_manager.py +1 -0
  17. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tmux_utils.py +22 -0
  18. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui.py +50 -18
  19. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui.tcss +1 -1
  20. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_actions/session.py +26 -39
  21. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_actions/view.py +1 -1
  22. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/daemon_panel.py +41 -32
  23. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/help_overlay.py +2 -2
  24. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/session_summary.py +1 -1
  25. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/usage_monitor.py +2 -2
  26. {overcode-0.3.1 → overcode-0.3.3/src/overcode.egg-info}/PKG-INFO +1 -1
  27. {overcode-0.3.1 → overcode-0.3.3}/LICENSE +0 -0
  28. {overcode-0.3.1 → overcode-0.3.3}/MANIFEST.in +0 -0
  29. {overcode-0.3.1 → overcode-0.3.3}/README.md +0 -0
  30. {overcode-0.3.1 → overcode-0.3.3}/setup.cfg +0 -0
  31. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/__init__.py +0 -0
  32. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/agent_scanner.py +0 -0
  33. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/bundled_skills.py +0 -0
  34. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/claude_pid.py +0 -0
  35. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/__init__.py +0 -0
  36. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/__main__.py +0 -0
  37. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/_shared.py +0 -0
  38. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/budget.py +0 -0
  39. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/config.py +0 -0
  40. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/daemon.py +0 -0
  41. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/hooks.py +0 -0
  42. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/jobs.py +0 -0
  43. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/monitoring.py +0 -0
  44. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/perms.py +0 -0
  45. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/sister.py +0 -0
  46. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/cli/skills.py +0 -0
  47. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/config.py +0 -0
  48. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/daemon_claude_skill.md +0 -0
  49. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/daemon_logging.py +0 -0
  50. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/daemon_utils.py +0 -0
  51. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/data_export.py +0 -0
  52. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/dependency_check.py +0 -0
  53. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/duration.py +0 -0
  54. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/exceptions.py +0 -0
  55. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/follow_mode.py +0 -0
  56. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/implementations.py +0 -0
  57. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/interfaces.py +0 -0
  58. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/job_manager.py +0 -0
  59. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/launcher.py +0 -0
  60. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/logging_config.py +0 -0
  61. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/mocks.py +0 -0
  62. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/monitor_daemon_core.py +0 -0
  63. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/monitor_daemon_state.py +0 -0
  64. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/notifier.py +0 -0
  65. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/pid_utils.py +0 -0
  66. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/presence_logger.py +0 -0
  67. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/protocols.py +0 -0
  68. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/session_manager.py +0 -0
  69. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/sister_controller.py +0 -0
  70. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/sister_poller.py +0 -0
  71. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/standing_instructions.py +0 -0
  72. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/status_constants.py +0 -0
  73. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/status_history.py +0 -0
  74. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/status_patterns.py +0 -0
  75. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/summarizer_client.py +0 -0
  76. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/summarizer_component.py +0 -0
  77. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/summary_groups.py +0 -0
  78. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/supervisor_daemon.py +0 -0
  79. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/supervisor_daemon_core.py +0 -0
  80. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/testing/__init__.py +0 -0
  81. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/testing/renderer.py +0 -0
  82. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/testing/tmux_driver.py +0 -0
  83. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/testing/tui_eye.py +0 -0
  84. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/testing/tui_eye_skill.md +0 -0
  85. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/time_context.py +0 -0
  86. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_actions/__init__.py +0 -0
  87. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_actions/daemon.py +0 -0
  88. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_actions/input.py +0 -0
  89. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_actions/navigation.py +0 -0
  90. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_helpers.py +0 -0
  91. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_logic.py +0 -0
  92. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_render.py +0 -0
  93. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/__init__.py +0 -0
  94. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/agent_select_modal.py +0 -0
  95. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/command_bar.py +0 -0
  96. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/daemon_status_bar.py +0 -0
  97. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/fullscreen_preview.py +0 -0
  98. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/instruction_history_modal.py +0 -0
  99. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/job_summary.py +0 -0
  100. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/new_agent_defaults_modal.py +0 -0
  101. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/preview_pane.py +0 -0
  102. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/sister_selection_modal.py +0 -0
  103. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/status_timeline.py +0 -0
  104. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/tui_widgets/summary_config_modal.py +0 -0
  105. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/web/__init__.py +0 -0
  106. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/web/templates/analytics.html +0 -0
  107. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/web/templates/dashboard.html +0 -0
  108. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/web_api.py +0 -0
  109. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/web_chartjs.py +0 -0
  110. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/web_control_api.py +0 -0
  111. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/web_server.py +0 -0
  112. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/web_server_runner.py +0 -0
  113. {overcode-0.3.1 → overcode-0.3.3}/src/overcode/web_templates.py +0 -0
  114. {overcode-0.3.1 → overcode-0.3.3}/src/overcode.egg-info/SOURCES.txt +0 -0
  115. {overcode-0.3.1 → overcode-0.3.3}/src/overcode.egg-info/dependency_links.txt +0 -0
  116. {overcode-0.3.1 → overcode-0.3.3}/src/overcode.egg-info/entry_points.txt +0 -0
  117. {overcode-0.3.1 → overcode-0.3.3}/src/overcode.egg-info/requires.txt +0 -0
  118. {overcode-0.3.1 → overcode-0.3.3}/src/overcode.egg-info/top_level.txt +0 -0
  119. {overcode-0.3.1 → overcode-0.3.3}/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.3.1
3
+ Version: 0.3.3
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.3.1"
7
+ version = "0.3.3"
8
8
  description = "A supervisor for managing multiple Claude Code instances in tmux"
9
9
  authors = [
10
10
  {name = "Mike Bond"}
@@ -137,6 +137,23 @@ class ClaudeConfigEditor:
137
137
  results.append((event, cmd))
138
138
  return results
139
139
 
140
+ # ----- Hook detection -----
141
+
142
+ @staticmethod
143
+ def are_overcode_hooks_installed() -> bool:
144
+ """Check if core overcode hooks are installed at user level.
145
+
146
+ Requires at least UserPromptSubmit, Stop, and PostToolUse hooks
147
+ to consider hooks mode viable.
148
+ """
149
+ editor = ClaudeConfigEditor.user_level()
150
+ try:
151
+ editor.load()
152
+ except (ValueError, FileNotFoundError):
153
+ return False
154
+ core_events = ("UserPromptSubmit", "Stop", "PostToolUse")
155
+ return all(editor.has_hook(event, "overcode hook-handler") for event in core_events)
156
+
140
157
  # ----- Permission management -----
141
158
 
142
159
  def add_permission(self, tool_pattern: str) -> bool:
@@ -970,9 +970,10 @@ def show(
970
970
  else:
971
971
  # Daemon not running — fall back to direct detection
972
972
  from ..status_detector_factory import create_status_detector
973
+ from ..settings import resolve_detection_mode
973
974
  detector = create_status_detector(
974
975
  session,
975
- strategy="hooks" if sess.hook_status_detection else "polling",
976
+ strategy=resolve_detection_mode(session),
976
977
  )
977
978
  status, activity, pane_content_raw = detector.detect_status(sess)
978
979
 
@@ -34,6 +34,7 @@ from typing import Annotated
34
34
  import typer
35
35
 
36
36
  from ._shared import app, SessionOption
37
+ from ..tmux_utils import get_pane_base_index
37
38
 
38
39
 
39
40
  def _acquire_setup_lock() -> bool:
@@ -179,11 +180,13 @@ def _setup_linked_session(agents_session: str) -> str:
179
180
 
180
181
  def _are_keybindings_installed() -> bool:
181
182
  """Check if overcode keybindings are already installed."""
182
- # Check for our Tab binding as a sentinel
183
+ # Check for our M-j binding as a sentinel — look for the exact
184
+ # send-keys target rather than a substring match on the window name,
185
+ # to avoid false positives from unrelated bindings (#384).
183
186
  result = _tmux("list-keys", "-T", "root")
184
187
  if result.returncode != 0:
185
188
  return False
186
- return SPLIT_WINDOW_NAME in result.stdout
189
+ return f"send-keys -t overcode:{SPLIT_WINDOW_NAME}." in result.stdout
187
190
 
188
191
 
189
192
  def _remove_keybindings() -> None:
@@ -232,27 +235,28 @@ def _setup_keybindings(linked_session: str = "", toggle_key: str = "") -> None:
232
235
  # --- Agent navigation from the bottom (terminal) pane ---
233
236
  # Option+J / Option+K (Meta+j/k) cycle agents by sending j/k
234
237
  # to the monitor pane, which navigates and syncs the terminal.
238
+ _base = get_pane_base_index()
235
239
  _in_bottom = (
236
240
  f"#{{&&:#{{==:#{{window_name}},{SPLIT_WINDOW_NAME}}},"
237
- f"#{{!=:#{{pane_index}},0}}}}"
241
+ f"#{{!=:#{{pane_index}},{_base}}}}}"
238
242
  )
239
243
  _tmux(
240
244
  "bind-key", "-n", "M-j",
241
245
  "if-shell", "-F", _in_bottom,
242
- f"send-keys -t overcode:{SPLIT_WINDOW_NAME}.0 j",
246
+ f"send-keys -t overcode:{SPLIT_WINDOW_NAME}.{_base} j",
243
247
  "send-keys M-j",
244
248
  )
245
249
  _tmux(
246
250
  "bind-key", "-n", "M-k",
247
251
  "if-shell", "-F", _in_bottom,
248
- f"send-keys -t overcode:{SPLIT_WINDOW_NAME}.0 k",
252
+ f"send-keys -t overcode:{SPLIT_WINDOW_NAME}.{_base} k",
249
253
  "send-keys M-k",
250
254
  )
251
255
  # Option+B: navigate to bell (next agent needing attention)
252
256
  _tmux(
253
257
  "bind-key", "-n", "M-b",
254
258
  "if-shell", "-F", _in_bottom,
255
- f"send-keys -t overcode:{SPLIT_WINDOW_NAME}.0 b",
259
+ f"send-keys -t overcode:{SPLIT_WINDOW_NAME}.{_base} b",
256
260
  "send-keys M-b",
257
261
  )
258
262
 
@@ -263,12 +267,12 @@ def _setup_keybindings(linked_session: str = "", toggle_key: str = "") -> None:
263
267
  # copy mode in the inner session via the tmux API.
264
268
  if linked_session:
265
269
  # PageUp: enter copy mode + scroll up, but only when in the
266
- # bottom pane (pane_index != 0) of the split window.
270
+ # bottom pane (pane_index != base) of the split window.
267
271
  # Top pane (Textual TUI) and other windows get normal PageUp.
268
272
  _tmux(
269
273
  "bind-key", "-n", "PPage",
270
274
  "if-shell", "-F",
271
- f"#{{&&:#{{==:#{{window_name}},{SPLIT_WINDOW_NAME}}},#{{!=:#{{pane_index}},0}}}}",
275
+ f"#{{&&:#{{==:#{{window_name}},{SPLIT_WINDOW_NAME}}},#{{!=:#{{pane_index}},{_base}}}}}",
272
276
  f"copy-mode -t {linked_session} -u",
273
277
  "send-keys PPage",
274
278
  )
@@ -276,7 +280,7 @@ def _setup_keybindings(linked_session: str = "", toggle_key: str = "") -> None:
276
280
  _tmux(
277
281
  "bind-key", "-n", "NPage",
278
282
  "if-shell", "-F",
279
- f"#{{&&:#{{==:#{{window_name}},{SPLIT_WINDOW_NAME}}},#{{!=:#{{pane_index}},0}}}}",
283
+ f"#{{&&:#{{==:#{{window_name}},{SPLIT_WINDOW_NAME}}},#{{!=:#{{pane_index}},{_base}}}}}",
280
284
  f"send-keys -t {linked_session} NPage",
281
285
  "send-keys NPage",
282
286
  )
@@ -288,7 +292,7 @@ def _setup_keybindings(linked_session: str = "", toggle_key: str = "") -> None:
288
292
  # already active), then send scroll-up/down commands to it.
289
293
  _in_bottom = (
290
294
  f"#{{&&:#{{==:#{{window_name}},{SPLIT_WINDOW_NAME}}},"
291
- f"#{{!=:#{{pane_index}},0}}}}"
295
+ f"#{{!=:#{{pane_index}},{_base}}}}}"
292
296
  )
293
297
  _tmux(
294
298
  "bind-key", "-n", "WheelUpPane",
@@ -410,7 +414,7 @@ def tmux_resize():
410
414
 
411
415
  # Apply the new ratio
412
416
  new_height = max(int(int(info.split(":")[1]) * next_ratio / 100), 5)
413
- _tmux("resize-pane", "-t", f"{target}.0", "-y", str(new_height))
417
+ _tmux("resize-pane", "-t", f"{target}.{get_pane_base_index()}", "-y", str(new_height))
414
418
 
415
419
 
416
420
  @app.command("tmux")
@@ -487,11 +491,10 @@ def tmux_layout(
487
491
  rprint(" tmux set -g terminal-features ''")
488
492
  return
489
493
 
490
- # Verify agents session exists
494
+ # Create agents session if it doesn't exist (#397)
491
495
  if not _tmux_check("has-session", "-t", session):
492
- rprint(f"[red]No tmux session '{session}' found.[/red]")
493
- rprint(f"[dim]Launch some agents first, or create it: tmux new-session -d -s {session}[/dim]")
494
- raise typer.Exit(1)
496
+ rprint(f"[dim]Creating tmux session '{session}'...[/dim]")
497
+ _tmux("new-session", "-d", "-s", session)
495
498
 
496
499
  # --- Toggle key selection (runs if not yet configured) ---
497
500
  from ..config import get_tmux_toggle_key, set_tmux_toggle_key
@@ -601,20 +604,30 @@ def _tmux_layout_locked(session: str, ratio: int, rprint) -> None:
601
604
  if existing:
602
605
  if _is_split_window_healthy(existing):
603
606
  if in_tmux:
604
- # Check if a real (non-tiny) client is already on overcode.
607
+ # Check if a real (non-nested) client is already on overcode.
605
608
  # If so, no switch needed — the user is already there or
606
609
  # another terminal has it open. Switching blindly can
607
610
  # accidentally move the bottom pane's nested client from
608
611
  # oc-view-agents to overcode, creating a recursive display
609
612
  # that collapses the window.
613
+ #
614
+ # Detect nested clients by comparing client TTYs against
615
+ # pane TTYs in the split window (#387). A nested tmux
616
+ # client's TTY matches one of the pane TTYs.
617
+ pane_ttys = set(
618
+ _tmux_output(
619
+ "list-panes", "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}",
620
+ "-F", "#{pane_tty}",
621
+ ).splitlines()
622
+ )
610
623
  oc_clients = _tmux_output(
611
624
  "list-clients", "-t", oc_session,
612
- "-F", "#{client_height}",
625
+ "-F", "#{client_tty}",
613
626
  )
614
627
  has_real_client = any(
615
- int(h) > 10
616
- for h in oc_clients.splitlines()
617
- if h.strip().isdigit()
628
+ tty.strip() not in pane_ttys
629
+ for tty in oc_clients.splitlines()
630
+ if tty.strip()
618
631
  )
619
632
  if not has_real_client:
620
633
  # No real client on overcode yet — switch the caller's
@@ -632,14 +645,14 @@ def _tmux_layout_locked(session: str, ratio: int, rprint) -> None:
632
645
  else:
633
646
  # Respawn before attach — execlp replaces this process
634
647
  _tmux("respawn-pane", "-k",
635
- "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}.0",
648
+ "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}.{get_pane_base_index()}",
636
649
  monitor_cmd)
637
650
  rprint(f"[green]Attaching to existing {SPLIT_WINDOW_NAME} window (monitor restarted)...[/green]")
638
651
  time.sleep(0.2)
639
652
  os.execlp("tmux", "tmux", "attach-session", "-t", oc_session)
640
653
  # Always restart the monitor so code changes take effect
641
654
  _tmux("respawn-pane", "-k",
642
- "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}.0",
655
+ "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}.{get_pane_base_index()}",
643
656
  monitor_cmd)
644
657
  rprint(f"[green]Switched to existing {SPLIT_WINDOW_NAME} window (monitor restarted)[/green]")
645
658
  return
@@ -668,7 +681,7 @@ def _tmux_layout_locked(session: str, ratio: int, rprint) -> None:
668
681
 
669
682
  # The bottom pane runs a nested tmux attach. We must unset $TMUX
670
683
  # because tmux refuses to attach from inside an existing session.
671
- attach_cmd = f"unset TMUX; tmux attach-session -t {linked}"
684
+ attach_cmd = f"sh -c 'unset TMUX; exec tmux attach-session -t {linked}'"
672
685
 
673
686
  # If an "overcode" session exists but has no split window, it might be:
674
687
  # (a) a stale session from a previous overcode run, or
@@ -749,7 +762,7 @@ def _tmux_layout_locked(session: str, ratio: int, rprint) -> None:
749
762
  result = _tmux(
750
763
  "split-window", "-v",
751
764
  "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}",
752
- "-p", str(100 - ratio),
765
+ "-l", f"{100 - ratio}%",
753
766
  attach_cmd,
754
767
  )
755
768
  if result.returncode != 0:
@@ -757,17 +770,23 @@ def _tmux_layout_locked(session: str, ratio: int, rprint) -> None:
757
770
  raise typer.Exit(1)
758
771
 
759
772
  # Focus top pane
760
- _tmux("select-pane", "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}.0")
773
+ _tmux("select-pane", "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}.{get_pane_base_index()}")
761
774
 
762
775
  rprint(f"[green]Split layout ready.[/green] Tab toggles panes.")
763
776
 
764
777
  else:
765
778
  # --- Outside tmux: create detached, split, then attach ---
766
779
  if need_new_session:
780
+ # Use actual terminal size (fall back to 200x50 if unavailable)
781
+ try:
782
+ term_size = os.get_terminal_size()
783
+ term_x, term_y = str(term_size.columns), str(term_size.lines)
784
+ except OSError:
785
+ term_x, term_y = "200", "50"
767
786
  result = _tmux(
768
787
  "new-session", "-d", "-s", oc_session,
769
788
  "-n", SPLIT_WINDOW_NAME,
770
- "-x", "200", "-y", "50",
789
+ "-x", term_x, "-y", term_y,
771
790
  monitor_cmd,
772
791
  )
773
792
  if result.returncode != 0:
@@ -792,12 +811,12 @@ def _tmux_layout_locked(session: str, ratio: int, rprint) -> None:
792
811
  _tmux(
793
812
  "split-window", "-v",
794
813
  "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}",
795
- "-p", str(100 - ratio),
814
+ "-l", f"{100 - ratio}%",
796
815
  attach_cmd,
797
816
  )
798
817
 
799
818
  # Focus top pane
800
- _tmux("select-pane", "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}.0")
819
+ _tmux("select-pane", "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}.{get_pane_base_index()}")
801
820
 
802
821
  # Attach to the session (replaces this process)
803
822
  rprint(f"[green]Attaching to split layout...[/green]")
@@ -33,8 +33,12 @@ CLAUDE_PROJECTS_PATH = Path.home() / ".claude" / "projects"
33
33
 
34
34
  # Model name → context window size in tokens.
35
35
  # Default 200K for unknown models. Update as new models ship.
36
+ # Claude Code with 1M context reports the same model ID — we detect
37
+ # the actual context size from token counts at runtime and update here
38
+ # for the models known to support extended context.
36
39
  MODEL_CONTEXT_WINDOWS: Dict[str, int] = {
37
- "claude-opus-4-6": 200_000,
40
+ "claude-opus-4-6": 1_000_000,
41
+ "claude-sonnet-4-6": 1_000_000,
38
42
  "claude-sonnet-4-5-20250929": 200_000,
39
43
  "claude-haiku-4-5-20251001": 200_000,
40
44
  "claude-3-5-sonnet-20241022": 200_000,
@@ -45,6 +49,32 @@ MODEL_CONTEXT_WINDOWS: Dict[str, int] = {
45
49
  }
46
50
  DEFAULT_CONTEXT_WINDOW = 200_000
47
51
 
52
+ # Model ID → human-readable short name for display
53
+ MODEL_SHORT_NAMES: Dict[str, str] = {
54
+ "claude-opus-4-6": "Op4.6",
55
+ "claude-sonnet-4-6": "Sn4.6",
56
+ "claude-sonnet-4-5-20250929": "Sn4.5",
57
+ "claude-haiku-4-5-20251001": "Hk4.5",
58
+ "claude-3-5-sonnet-20241022": "Sn3.5",
59
+ "claude-3-5-haiku-20241022": "Hk3.5",
60
+ "claude-3-opus-20240229": "Op3",
61
+ "claude-3-sonnet-20240229": "Sn3",
62
+ "claude-3-haiku-20240307": "Hk3",
63
+ }
64
+
65
+
66
+ def model_short_name(model: Optional[str]) -> str:
67
+ """Return a short display name for a model ID.
68
+
69
+ Examples:
70
+ "claude-opus-4-6" → "Op4.6"
71
+ "claude-haiku-4-5-20251001" → "Hk4.5"
72
+ "unknown-model" → "unknown-model"
73
+ """
74
+ if not model:
75
+ return ""
76
+ return MODEL_SHORT_NAMES.get(model, model)
77
+
48
78
 
49
79
  def model_context_window(model: Optional[str]) -> int:
50
80
  """Return the context window size for a given model name.
@@ -25,8 +25,11 @@ logger = logging.getLogger(__name__)
25
25
  # All hooks that overcode installs
26
26
  OVERCODE_HOOKS: list[tuple[str, str]] = [
27
27
  ("UserPromptSubmit", "overcode hook-handler"),
28
+ ("PreToolUse", "overcode hook-handler"),
28
29
  ("PostToolUse", "overcode hook-handler"),
30
+ ("PostToolUseFailure", "overcode hook-handler"),
29
31
  ("Stop", "overcode hook-handler"),
32
+ ("StopFailure", "overcode hook-handler"),
30
33
  ("PermissionRequest", "overcode hook-handler"),
31
34
  ("SessionEnd", "overcode hook-handler"),
32
35
  ]