meshcode 2.10.19__tar.gz → 2.10.21__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 (32) hide show
  1. {meshcode-2.10.19 → meshcode-2.10.21}/PKG-INFO +1 -1
  2. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/__init__.py +1 -1
  3. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/server.py +31 -82
  4. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/PKG-INFO +1 -1
  5. {meshcode-2.10.19 → meshcode-2.10.21}/pyproject.toml +1 -1
  6. {meshcode-2.10.19 → meshcode-2.10.21}/README.md +0 -0
  7. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/ascii_art.py +0 -0
  8. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/cli.py +0 -0
  9. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/comms_v4.py +0 -0
  10. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/invites.py +0 -0
  11. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/launcher.py +0 -0
  12. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/launcher_install.py +0 -0
  13. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/__init__.py +0 -0
  14. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/__main__.py +0 -0
  15. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/backend.py +0 -0
  16. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/realtime.py +0 -0
  17. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/test_backend.py +0 -0
  18. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  19. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  20. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/preferences.py +0 -0
  21. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/protocol_v2.py +0 -0
  22. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/run_agent.py +0 -0
  23. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/secrets.py +0 -0
  24. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/self_update.py +0 -0
  25. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/setup_clients.py +0 -0
  26. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/SOURCES.txt +0 -0
  27. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.10.19 → meshcode-2.10.21}/setup.cfg +0 -0
  32. {meshcode-2.10.19 → meshcode-2.10.21}/tests/test_status_enum_coverage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.19
3
+ Version: 2.10.21
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -1,2 +1,2 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.10.19"
2
+ __version__ = "2.10.21"
@@ -8,11 +8,9 @@ Run with:
8
8
  MESHCODE_PROJECT=my-app MESHCODE_AGENT=backend python -m meshcode_mcp serve
9
9
  """
10
10
  import asyncio
11
- import atexit
12
11
  import json
13
12
  import logging
14
13
  import os
15
- import signal
16
14
  import sys
17
15
  import hashlib as _hashlib
18
16
  import traceback as _traceback
@@ -518,17 +516,16 @@ def _schedule_flip(status: str, task: str = "") -> None:
518
516
 
519
517
 
520
518
  def _set_state(state: str, tool: str = "") -> None:
521
- """Update the state machine and broadcast to dashboard. Thread-safe."""
519
+ """Update the state machine and broadcast to dashboard."""
522
520
  global _current_state, _current_tool, _last_tool_at, _working_timer
523
- with _flip_lock:
524
- # Cancel any pending working→online timer
525
- if _working_timer is not None:
526
- _working_timer.cancel()
527
- _working_timer = None
528
- _current_state = state
529
- _current_tool = tool
530
- if state == "working":
531
- _last_tool_at = _time.time()
521
+ # Cancel any pending working→online timer
522
+ if _working_timer is not None:
523
+ _working_timer.cancel()
524
+ _working_timer = None
525
+ _current_state = state
526
+ _current_tool = tool
527
+ if state == "working":
528
+ _last_tool_at = _time.time()
532
529
  _schedule_flip(state, tool)
533
530
 
534
531
 
@@ -576,18 +573,10 @@ def with_working_status(func):
576
573
  _record_event_bg("tool_call", {"tool": name, "args_keys": list(kwargs.keys())})
577
574
  try:
578
575
  return await func(*args, **kwargs)
579
- except (asyncio.CancelledError, KeyboardInterrupt):
580
- # User pressed ESC or MCP client cancelled the request.
581
- # Return clean dict — never propagate BaseException to FastMCP.
582
- _mc_log(f"tool {name} cancelled by client", "warn")
583
- return {"cancelled": True, "tool": name}
584
576
  except Exception as e:
585
577
  if not skip:
586
578
  _auto_learn_error(name, e, list(kwargs.keys()))
587
- # NEVER re-raise — return structured error instead of crashing
588
- import traceback as _tb
589
- _log_crash_to_db("tool_exception", f"{name}: {type(e).__name__}: {e}\n{_tb.format_exc()[-500:]}")
590
- return {"error": f"tool {name} failed: {type(e).__name__}: {e}", "_recovered": True}
579
+ raise
591
580
  finally:
592
581
  if not skip:
593
582
  global _last_tool_at
@@ -608,10 +597,7 @@ def with_working_status(func):
608
597
  except Exception as e:
609
598
  if not skip:
610
599
  _auto_learn_error(name, e, list(kwargs.keys()))
611
- # NEVER re-raise — return structured error instead of crashing
612
- import traceback as _tb
613
- _log_crash_to_db("tool_exception", f"{name}: {type(e).__name__}: {e}\n{_tb.format_exc()[-500:]}")
614
- return {"error": f"tool {name} failed: {type(e).__name__}: {e}", "_recovered": True}
600
+ raise
615
601
  finally:
616
602
  if not skip:
617
603
  global _last_tool_at
@@ -812,31 +798,13 @@ def _log_crash_to_db(reason: str = "unknown", error_detail: str = "") -> None:
812
798
  _mc_log(f" crash logged: {reason}", "warn")
813
799
 
814
800
 
815
- def _on_exit() -> None:
816
- """atexit handler — release lease and log shutdown."""
817
- _log_crash_to_db("process_exit", "atexit handler fired")
818
- _release_lease()
819
-
820
-
821
- def _on_signal(signum, frame) -> None:
822
- """Signal handler for SIGTERM/SIGINT — graceful shutdown.
823
-
824
- CRITICAL: Do NOT call sys.exit() here. sys.exit() inside a signal handler
825
- can corrupt the asyncio event loop (raises SystemExit mid-await), which
826
- crashes FastMCP's stdio transport. Instead, just log + release lease and
827
- let the normal shutdown path handle process exit.
828
- """
829
- sig_name = signal.Signals(signum).name if hasattr(signal, 'Signals') else str(signum)
830
- _mc_log(f"Received {sig_name} — releasing lease", "warn")
831
- _log_crash_to_db("signal", f"Received {sig_name}")
832
- _release_lease()
833
- # Do NOT sys.exit() — let FastMCP's event loop shut down cleanly.
834
- # The process will exit naturally when the event loop finishes.
835
-
836
801
 
837
- atexit.register(_on_exit)
838
- signal.signal(signal.SIGTERM, _on_signal)
839
- signal.signal(signal.SIGINT, _on_signal)
802
+ # NOTE: Do NOT install signal handlers (SIGTERM, SIGINT) or atexit hooks here.
803
+ # FastMCP/anyio manages its own event loop and signal handling. Custom signal
804
+ # handlers override Python's default KeyboardInterrupt, preventing anyio from
805
+ # cancelling tasks cleanly. Network calls inside signal/atexit handlers can
806
+ # deadlock or corrupt the event loop. The lease will be released by the
807
+ # lifespan shutdown handler instead.
840
808
 
841
809
 
842
810
  # ============================================================
@@ -1129,12 +1097,11 @@ def _heartbeat_thread_fn():
1129
1097
  try:
1130
1098
  be.sb_rpc("mc_heartbeat", {"p_project_id": _PROJECT_ID, "p_agent_name": AGENT_NAME, "p_version": _SDK_VERSION})
1131
1099
 
1132
- # CPU-based status detection — read shared state under lock
1100
+ # CPU-based status detection
1133
1101
  parent_cpu = _get_parent_cpu()
1134
- with _flip_lock:
1135
- cur_state = _current_state
1136
- in_wait = _IN_WAIT
1137
- idle_secs = _time.time() - _last_tool_at
1102
+ cur_state = _current_state
1103
+ in_wait = _IN_WAIT
1104
+ idle_secs = _time.time() - _last_tool_at
1138
1105
 
1139
1106
  if in_wait:
1140
1107
  # Actually in meshcode_wait right now — listening for messages
@@ -1631,13 +1598,6 @@ async def meshcode_wait(timeout_seconds: int = 120, include_acks: bool = False)
1631
1598
  except Exception:
1632
1599
  pass
1633
1600
  return result
1634
- except (asyncio.CancelledError, KeyboardInterrupt, SystemExit):
1635
- # User pressed ESC or Claude Code cancelled the tool call.
1636
- # Return a clean dict instead of propagating — this prevents
1637
- # FastMCP/anyio event loop corruption and keeps the MCP server alive.
1638
- _mc_log("meshcode_wait cancelled by client (ESC) — returning cleanly", "warn")
1639
- _set_state("online", "interrupted")
1640
- return {"cancelled": True, "reason": "Tool call cancelled by user"}
1641
1601
  finally:
1642
1602
  _IN_WAIT = False
1643
1603
 
@@ -2682,30 +2642,19 @@ def _auto_update() -> None:
2682
2642
  def run_server():
2683
2643
  """Start the MCP server on stdio (default for Claude Code).
2684
2644
 
2685
- Wraps mcp.run() with crash recovery: if the event loop dies for any
2686
- reason, log the crash, release the lease, and exit cleanly instead of
2687
- leaving the agent in a zombie state.
2645
+ IMPORTANT: Do NOT wrap mcp.run() with try/except, signal handlers, or
2646
+ atexit hooks. FastMCP/anyio manages its own event loop lifecycle and
2647
+ signal handling. Any interference (custom SIGINT handler, atexit network
2648
+ calls, catching KeyboardInterrupt) corrupts the event loop and causes
2649
+ the MCP server to crash when Claude Code cancels a tool call (ESC).
2688
2650
  """
2689
2651
  _auto_update()
2690
2652
  print(
2691
2653
  f"[meshcode-mcp] Starting server for {AGENT_NAME}@{PROJECT_NAME}",
2692
2654
  file=sys.stderr,
2693
2655
  )
2694
- try:
2695
- # Restore real stdout for FastMCP's JSON-RPC transport.
2696
- # sys.stdout was redirected to stderr at module load to prevent
2697
- # accidental stdout writes from corrupting the MCP protocol.
2698
- sys.stdout = _REAL_STDOUT
2699
- mcp.run()
2700
- except KeyboardInterrupt:
2701
- _log_crash_to_db("keyboard_interrupt", "User stopped the agent")
2702
- except SystemExit as e:
2703
- _log_crash_to_db("system_exit", f"exit code: {e.code}")
2704
- raise # re-raise so the process exits with the correct code
2705
- except Exception as e:
2706
- import traceback as _tb
2707
- tb_str = _tb.format_exc()
2708
- _log_crash_to_db("unhandled_exception", f"{type(e).__name__}: {e}\n{tb_str}")
2709
- print(f"[meshcode-mcp] FATAL: {e}", file=sys.stderr)
2710
- print(f"[meshcode-mcp] Stack trace logged to mc_agent_crash_logs", file=sys.stderr)
2711
- sys.exit(1)
2656
+ # Restore real stdout for FastMCP's JSON-RPC transport.
2657
+ # sys.stdout was redirected to stderr at module load to prevent
2658
+ # accidental stdout writes from corrupting the MCP protocol.
2659
+ sys.stdout = _REAL_STDOUT
2660
+ mcp.run()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.19
3
+ Version: 2.10.21
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.10.19"
7
+ version = "2.10.21"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes
File without changes