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.
- {meshcode-2.10.19 → meshcode-2.10.21}/PKG-INFO +1 -1
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/__init__.py +1 -1
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/server.py +31 -82
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.10.19 → meshcode-2.10.21}/pyproject.toml +1 -1
- {meshcode-2.10.19 → meshcode-2.10.21}/README.md +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/cli.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/comms_v4.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/invites.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/launcher.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/preferences.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/run_agent.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/secrets.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/self_update.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode/setup_clients.py +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/setup.cfg +0 -0
- {meshcode-2.10.19 → meshcode-2.10.21}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.10.
|
|
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.
|
|
519
|
+
"""Update the state machine and broadcast to dashboard."""
|
|
522
520
|
global _current_state, _current_tool, _last_tool_at, _working_timer
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
838
|
-
signal.signal
|
|
839
|
-
|
|
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
|
|
1100
|
+
# CPU-based status detection
|
|
1133
1101
|
parent_cpu = _get_parent_cpu()
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
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
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
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()
|
|
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
|