claude-team-mcp 0.9.1__py3-none-any.whl → 0.9.2__py3-none-any.whl

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.
@@ -139,6 +139,7 @@ class RecoveredSession:
139
139
  coordinator_annotation: Optional[str] = None
140
140
  worktree_path: Optional[str] = None
141
141
  main_repo_path: Optional[str] = None
142
+ codex_jsonl_path: Optional[str] = None
142
143
 
143
144
  @staticmethod
144
145
  def map_event_state_to_status(event_state: EventState) -> SessionStatus:
@@ -178,6 +179,7 @@ class RecoveredSession:
178
179
  "worktree_path": self.worktree_path,
179
180
  "main_repo_path": self.main_repo_path,
180
181
  "agent_type": self.agent_type,
182
+ "codex_jsonl_path": self.codex_jsonl_path,
181
183
  # Recovery-specific fields
182
184
  "source": "event_log",
183
185
  "event_state": self.event_state,
@@ -227,6 +229,9 @@ class ManagedSession:
227
229
  # Agent type: "claude" (default) or "codex"
228
230
  agent_type: AgentType = "claude"
229
231
 
232
+ # Cached Codex JSONL path (discovered at spawn time via marker polling)
233
+ codex_jsonl_path: Optional[Path] = None
234
+
230
235
  def __post_init__(self):
231
236
  """Auto-populate terminal_id from terminal_session if not set."""
232
237
  if self.terminal_id is None:
@@ -255,6 +260,7 @@ class ManagedSession:
255
260
  "worktree_path": str(self.worktree_path) if self.worktree_path else None,
256
261
  "main_repo_path": str(self.main_repo_path) if self.main_repo_path else None,
257
262
  "agent_type": self.agent_type,
263
+ "codex_jsonl_path": str(self.codex_jsonl_path) if self.codex_jsonl_path else None,
258
264
  # Source field for distinguishing live vs recovered sessions
259
265
  "source": "registry",
260
266
  }
@@ -292,22 +298,29 @@ class ManagedSession:
292
298
  Get the path to this session's JSONL file.
293
299
 
294
300
  For Claude workers: uses marker-based discovery in ~/.claude/projects/.
295
- For Codex workers: uses marker-based discovery in ~/.codex/sessions/.
301
+ For Codex workers: uses cached path or marker-based discovery in
302
+ ~/.codex/sessions/. Returns None (not a wrong file) when discovery fails.
296
303
 
297
304
  Returns:
298
305
  Path object, or None if session cannot be discovered
299
306
  """
300
307
  if self.agent_type == "codex":
301
- from .idle_detection import find_codex_session_file
308
+ # Use cached path if available (set at spawn time)
309
+ if self.codex_jsonl_path and self.codex_jsonl_path.exists():
310
+ return self.codex_jsonl_path
302
311
 
303
- # Prefer marker-based match, fall back to most recent for legacy sessions.
312
+ # Try marker-based discovery with generous timeout (workers can run for hours)
304
313
  match = find_codex_session_by_internal_id(
305
314
  self.session_id,
306
- max_age_seconds=600,
315
+ max_age_seconds=86400,
307
316
  )
308
317
  if match:
318
+ # Cache for future calls
319
+ self.codex_jsonl_path = match.jsonl_path
309
320
  return match.jsonl_path
310
- return find_codex_session_file(max_age_seconds=600)
321
+
322
+ # No blind fallback - returning None is better than returning wrong data
323
+ return None
311
324
  else:
312
325
  # For Claude, use marker-based discovery
313
326
  # Auto-discover if not already known
@@ -353,16 +366,10 @@ class ManagedSession:
353
366
  True if idle, False if working or session file not available
354
367
  """
355
368
  if self.agent_type == "codex":
356
- from .idle_detection import find_codex_session_file, is_codex_idle
369
+ from .idle_detection import is_codex_idle
357
370
 
358
- # Prefer marker-based match, fall back to most recent for legacy sessions.
359
- match = find_codex_session_by_internal_id(
360
- self.session_id,
361
- max_age_seconds=600,
362
- )
363
- session_file = match.jsonl_path if match else None
364
- if not session_file:
365
- session_file = find_codex_session_file(max_age_seconds=600)
371
+ # Use the same path resolution as get_jsonl_path() (cached or marker-based)
372
+ session_file = self.get_jsonl_path()
366
373
  if not session_file:
367
374
  return False
368
375
  return is_codex_idle(session_file)
@@ -795,6 +802,7 @@ class SessionRegistry:
795
802
  coordinator_annotation=data.get("coordinator_annotation"),
796
803
  worktree_path=data.get("worktree_path"),
797
804
  main_repo_path=data.get("main_repo_path"),
805
+ codex_jsonl_path=data.get("codex_jsonl_path"),
798
806
  )
799
807
 
800
808
  def count(self) -> int:
@@ -838,6 +838,42 @@ async def await_marker_in_jsonl(
838
838
  return None
839
839
 
840
840
 
841
+ async def await_codex_marker_in_jsonl(
842
+ session_id: str,
843
+ timeout: float = 30.0,
844
+ poll_interval: float = 0.5,
845
+ ) -> Optional[CodexSessionMatch]:
846
+ """
847
+ Poll for a Codex session marker to appear in the JSONL.
848
+
849
+ Codex workers write markers into ~/.codex/sessions/ files. This function
850
+ polls until the marker for the given session_id is found.
851
+
852
+ Args:
853
+ session_id: The internal session ID to search for in markers
854
+ timeout: Maximum seconds to wait (default 30)
855
+ poll_interval: Seconds between polls (default 0.5, slower than Claude
856
+ because Codex takes longer to start)
857
+
858
+ Returns:
859
+ CodexSessionMatch if found, None on timeout
860
+ """
861
+ import asyncio
862
+
863
+ start = time.time()
864
+
865
+ while time.time() - start < timeout:
866
+ match = find_codex_session_by_internal_id(
867
+ session_id,
868
+ max_age_seconds=300,
869
+ )
870
+ if match:
871
+ return match
872
+ await asyncio.sleep(poll_interval)
873
+
874
+ return None
875
+
876
+
841
877
  # =============================================================================
842
878
  # Session Discovery
843
879
  # =============================================================================
@@ -78,7 +78,8 @@ def register_tools(mcp: FastMCP, ensure_connection) -> None:
78
78
  # Check if already managed
79
79
  for managed in registry.list_all():
80
80
  if (
81
- managed.terminal_session.backend_id == backend_id
81
+ hasattr(managed, 'terminal_session')
82
+ and managed.terminal_session.backend_id == backend_id
82
83
  and managed.terminal_session.native_id == target_id
83
84
  ):
84
85
  return error_response(
@@ -90,7 +90,7 @@ def register_tools(mcp: FastMCP, ensure_connection) -> None:
90
90
  managed_ids = {
91
91
  s.terminal_session.native_id
92
92
  for s in registry.list_all()
93
- if s.terminal_session.backend_id == backend_id
93
+ if hasattr(s, 'terminal_session') and s.terminal_session.backend_id == backend_id
94
94
  }
95
95
 
96
96
  try:
@@ -187,7 +187,11 @@ def register_tools(mcp: FastMCP, ensure_connection) -> None:
187
187
  # Then immediately:
188
188
  message_workers(session_ids=["Groucho"], message="Your task is...")
189
189
  """
190
- from ..session_state import await_marker_in_jsonl, generate_marker_message
190
+ from ..session_state import (
191
+ await_codex_marker_in_jsonl,
192
+ await_marker_in_jsonl,
193
+ generate_marker_message,
194
+ )
191
195
 
192
196
  app_ctx = ctx.request_context.lifespan_context
193
197
  registry = app_ctx.registry
@@ -452,7 +456,7 @@ def register_tools(mcp: FastMCP, ensure_connection) -> None:
452
456
  managed_session_ids = {
453
457
  s.terminal_session.native_id
454
458
  for s in registry.list_all()
455
- if s.terminal_session.backend_id == backend.backend_id
459
+ if hasattr(s, 'terminal_session') and s.terminal_session.backend_id == backend.backend_id
456
460
  }
457
461
 
458
462
  # Find a window with enough space for ALL workers
@@ -693,7 +697,7 @@ def register_tools(mcp: FastMCP, ensure_connection) -> None:
693
697
  submit=True,
694
698
  )
695
699
 
696
- # Wait for markers to appear in JSONL (Claude only)
700
+ # Wait for markers to appear in JSONL (Claude and Codex)
697
701
  for i, managed in enumerate(managed_sessions):
698
702
  if managed.agent_type == "claude":
699
703
  claude_session_id = await await_marker_in_jsonl(
@@ -709,6 +713,24 @@ def register_tools(mcp: FastMCP, ensure_connection) -> None:
709
713
  f"Marker polling timed out for {managed.session_id}, "
710
714
  "JSONL correlation unavailable"
711
715
  )
716
+ elif managed.agent_type == "codex":
717
+ # Poll for Codex marker and cache the JSONL path
718
+ codex_match = await await_codex_marker_in_jsonl(
719
+ managed.session_id,
720
+ timeout=30.0,
721
+ poll_interval=0.5,
722
+ )
723
+ if codex_match:
724
+ managed.codex_jsonl_path = codex_match.jsonl_path
725
+ logger.info(
726
+ f"Codex JSONL path cached for {managed.session_id}: "
727
+ f"{codex_match.jsonl_path}"
728
+ )
729
+ else:
730
+ logger.warning(
731
+ f"Codex marker polling timed out for {managed.session_id}, "
732
+ "JSONL correlation unavailable"
733
+ )
712
734
 
713
735
  # Send worker prompts - always use generate_worker_prompt with bead/custom_prompt
714
736
  workers_awaiting_task: list[str] = [] # Workers with no bead and no prompt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-team-mcp
3
- Version: 0.9.1
3
+ Version: 0.9.2
4
4
  Summary: MCP server for managing multiple Claude Code sessions via iTerm2
5
5
  Project-URL: Homepage, https://github.com/Martian-Engineering/claude-team
6
6
  Project-URL: Repository, https://github.com/Martian-Engineering/claude-team
@@ -12,9 +12,9 @@ claude_team_mcp/idle_detection.py,sha256=KRnhObbVM--S8HPowWq7UkjXxq5AMpGdVHY5UUv
12
12
  claude_team_mcp/iterm_utils.py,sha256=Imp4yUwq06avwdEw3bOSaO168DpCUveazc71Q8AZOFw,37621
13
13
  claude_team_mcp/names.py,sha256=qUJlO2WlFjxB4Q1vLLTcEjTvKA63fJ3i2QBz80ABgnQ,15978
14
14
  claude_team_mcp/profile.py,sha256=yqytAE0dIe6NQ6FodldSepO-ZIeR7-C0UfK3FsO7Pn4,12326
15
- claude_team_mcp/registry.py,sha256=VGEG1eWaHW4RdBtpK6F8nc9tfm363Fl48nSu-jpHRDE,29554
15
+ claude_team_mcp/registry.py,sha256=iJ1UApZ3zWzHWvMayyaNLoXBRE2mO9hbuD0KQ2XfmpQ,29985
16
16
  claude_team_mcp/server.py,sha256=HmpGfckw6rgXPNcJCJzLbiqsFM9yoDr05MgwonUuQNU,18969
17
- claude_team_mcp/session_state.py,sha256=DFNPWog7yyc5yF1_lMhDMcBMpCRGi22YRI6IdoIEf0o,46814
17
+ claude_team_mcp/session_state.py,sha256=juDC1xpBlQpLCvZgOUTjmAb-d-RCSHRaTyn-NS-b4Hs,47819
18
18
  claude_team_mcp/subprocess_cache.py,sha256=6Q1NIMn2X5_75S4s_a9iPBka-UCz6RW0EMxPSPEaLxo,3612
19
19
  claude_team_mcp/worker_prompt.py,sha256=NvzUrR6Zqjp8HuL-xOeAncdvACHbyN7O0nISWyySHq4,15771
20
20
  claude_team_mcp/worktree.py,sha256=q05yhIqlpsG9CW-QDRq5SyqC7T3iYYxjLDkUiGX2M-g,19102
@@ -30,11 +30,11 @@ claude_team_mcp/terminal_backends/base.py,sha256=9GrFDm-Jah8y6DALQrq8dei9UgWeZkW
30
30
  claude_team_mcp/terminal_backends/iterm.py,sha256=A5klhOpzDrZ-636aN1Hlnnfxk4OA-Rz_OuaQi1ABz0Y,9317
31
31
  claude_team_mcp/terminal_backends/tmux.py,sha256=blMb669GlGjxedOLLu35NmuZpsN74Z4bq1EmMGqO7G0,24512
32
32
  claude_team_mcp/tools/__init__.py,sha256=NrtUzCC5Df-0GDqdQKlFqD4yv69pf5qbKIsypRxuxRg,1659
33
- claude_team_mcp/tools/adopt_worker.py,sha256=LuuRv9S6_ecN0vYA2ADANvuKMi64QQAMA6vRiwXaGqc,7053
33
+ claude_team_mcp/tools/adopt_worker.py,sha256=UFgg9P_wytO98lYQVfGgPRCqaYaJJGUdt5-rYVXnwlk,7110
34
34
  claude_team_mcp/tools/annotate_worker.py,sha256=mBo4YMaaaNQxQBduYK1DPA-Ioift-ZQ9tYlBM6S15t8,1639
35
35
  claude_team_mcp/tools/check_idle_workers.py,sha256=IjDNDeal9M7lTONWjKZrt8bXu7O277ossHz_2oyLRcY,3374
36
36
  claude_team_mcp/tools/close_workers.py,sha256=A--QqoL5KBUFbsNNe4-SYxsKOn1plY5_IfV6wHT8gvE,8029
37
- claude_team_mcp/tools/discover_workers.py,sha256=3hjSBx-cuK8YXDAUQY4cRaS4VvZr61wYwpR18nIz_Uo,11986
37
+ claude_team_mcp/tools/discover_workers.py,sha256=_QVZRGfVP0k1-ZUNix1UlsfHrdVBuyM2ohtw_h1zIv0,12021
38
38
  claude_team_mcp/tools/examine_worker.py,sha256=7Nd3EOdsGVDjMJ8ov9NhmcrjN8K9IE4i_noXHOP1uI8,1620
39
39
  claude_team_mcp/tools/issue_tracker_help.py,sha256=KxgjFhXp3NCUDjqTl3UnIiFV-Y5DM80C1EoZXfA82QM,1668
40
40
  claude_team_mcp/tools/list_workers.py,sha256=bD-i1kI_qCgFF1TJc_U5TpEc_Gk6baqkFzAInKLVJgs,4906
@@ -42,14 +42,14 @@ claude_team_mcp/tools/list_worktrees.py,sha256=EXPxzvHRX7a1c9CedBjZVn-lckiljdTH7
42
42
  claude_team_mcp/tools/message_workers.py,sha256=xa-y88gb2ugi36eRNE0ChAAZ1tJ62HuXwSlGhp7qDlE,13464
43
43
  claude_team_mcp/tools/poll_worker_changes.py,sha256=uv2-jhhJCZ4sTkuKqSBLK894GHof3Ue9roplgPwaTu0,8329
44
44
  claude_team_mcp/tools/read_worker_logs.py,sha256=Xe1Ch1ZJShI11et_beOwFL9q0WM5yMorhgDpstOd4Dw,6120
45
- claude_team_mcp/tools/spawn_workers.py,sha256=Md5EsEq5rhXx1WweabX8lPN-em2_s3h1C1xmYezrqS8,37723
45
+ claude_team_mcp/tools/spawn_workers.py,sha256=PGY4-LzRnJfnWVQ4kyhNNie1HNPhbioe6dXiRDohEoM,38730
46
46
  claude_team_mcp/tools/wait_idle_workers.py,sha256=VvHeIK2BM1q6SoY0DODCeKAEkAj3-MsB6g87scu2cyM,5555
47
47
  claude_team_mcp/tools/worker_events.py,sha256=s1_pFMFrH-Kppni6RT9yzQbsJ_ChhmF8ujM2lkdEl1s,9448
48
48
  claude_team_mcp/utils/__init__.py,sha256=Jyc-N1RXZnMdXLjSgRBB2ih04v-h0woUzZcVYYfc7wQ,647
49
49
  claude_team_mcp/utils/constants.py,sha256=FGDHeo0reZ89365fuXJGIl2Y5MFQAoKfWtmYr7UQzhQ,5815
50
50
  claude_team_mcp/utils/errors.py,sha256=kP0MPjLIpEOZkbGAzDxonMFERbsjwfID5VVY5qAR2II,2949
51
51
  claude_team_mcp/utils/worktree_detection.py,sha256=oMGcb7p1jvr7qWs06sxUMTAV8jRialcVqziCTCdW7XU,3251
52
- claude_team_mcp-0.9.1.dist-info/METADATA,sha256=omNaScxSlSY6-VENPCe3u-gxvqcMM5dmF0Vf5MtA6Ug,21211
53
- claude_team_mcp-0.9.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
54
- claude_team_mcp-0.9.1.dist-info/entry_points.txt,sha256=cmk5dmK50RVquExT-k_J72akl3qKIerPpVqfit7kQtQ,53
55
- claude_team_mcp-0.9.1.dist-info/RECORD,,
52
+ claude_team_mcp-0.9.2.dist-info/METADATA,sha256=7NIO_cETJlIIft77OreT48npeIFtGXnV9NXQ0o31l5U,21211
53
+ claude_team_mcp-0.9.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
54
+ claude_team_mcp-0.9.2.dist-info/entry_points.txt,sha256=cmk5dmK50RVquExT-k_J72akl3qKIerPpVqfit7kQtQ,53
55
+ claude_team_mcp-0.9.2.dist-info/RECORD,,