meshcode 2.10.29__tar.gz → 2.10.31__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.29 → meshcode-2.10.31}/PKG-INFO +1 -1
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/__init__.py +1 -1
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/server.py +89 -32
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.10.29 → meshcode-2.10.31}/pyproject.toml +1 -1
- {meshcode-2.10.29 → meshcode-2.10.31}/README.md +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/cli.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/comms_v4.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/invites.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/launcher.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/preferences.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/run_agent.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/secrets.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/self_update.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/setup_clients.py +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/setup.cfg +0 -0
- {meshcode-2.10.29 → meshcode-2.10.31}/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.31"
|
|
@@ -1217,40 +1217,53 @@ async def lifespan(_app):
|
|
|
1217
1217
|
try:
|
|
1218
1218
|
yield {"realtime": _REALTIME}
|
|
1219
1219
|
finally:
|
|
1220
|
-
|
|
1221
|
-
#
|
|
1220
|
+
# 2.10.31: minimal-risk shutdown. The previous body did network I/O,
|
|
1221
|
+
# a 5-second thread join, and an `await _REALTIME.stop()` inside the
|
|
1222
|
+
# lifespan's finally. Under Claude Code 2.1.111 ESC, that cancellation
|
|
1223
|
+
# path cascades through the awaits and unwinds the asyncio loop,
|
|
1224
|
+
# killing the subprocess. A bare FastMCP (no lifespan) in the same
|
|
1225
|
+
# bucket survives. To match that behavior without losing Realtime on
|
|
1226
|
+
# startup, we keep the startup logic and make the shutdown strictly
|
|
1227
|
+
# non-blocking, non-awaiting, non-raising. Daemon threads / realtime
|
|
1228
|
+
# socket die with the process; the lease expires on heartbeat timeout.
|
|
1222
1229
|
try:
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
"p_project_id": _PROJECT_ID,
|
|
1229
|
-
"p_agent_name": AGENT_NAME,
|
|
1230
|
-
"p_key": f"session_retro_{_dt.date.today().isoformat()}",
|
|
1231
|
-
"p_value": {
|
|
1232
|
-
"session_id": _SESSION_ID,
|
|
1233
|
-
"agent": AGENT_NAME,
|
|
1234
|
-
"shutdown_at": _dt.datetime.utcnow().isoformat(),
|
|
1235
|
-
"instance_id": _INSTANCE_ID,
|
|
1236
|
-
"auto_generated": True,
|
|
1237
|
-
},
|
|
1238
|
-
})
|
|
1230
|
+
_heartbeat_stop.set()
|
|
1231
|
+
except Exception:
|
|
1232
|
+
pass
|
|
1233
|
+
try:
|
|
1234
|
+
_record_event_bg("shutdown", {"agent": AGENT_NAME, "session_id": _SESSION_ID})
|
|
1239
1235
|
except Exception:
|
|
1240
|
-
pass
|
|
1241
|
-
|
|
1242
|
-
#
|
|
1243
|
-
|
|
1244
|
-
|
|
1236
|
+
pass
|
|
1237
|
+
# Fire-and-forget best-effort cleanup in a daemon thread so we do not
|
|
1238
|
+
# block, do not await, and any failure is confined to the thread.
|
|
1239
|
+
def _bg_cleanup():
|
|
1240
|
+
try:
|
|
1241
|
+
import datetime as _dt
|
|
1242
|
+
api_key = _get_api_key()
|
|
1243
|
+
if api_key:
|
|
1244
|
+
be.sb_rpc("mc_memory_set", {
|
|
1245
|
+
"p_api_key": api_key,
|
|
1246
|
+
"p_project_id": _PROJECT_ID,
|
|
1247
|
+
"p_agent_name": AGENT_NAME,
|
|
1248
|
+
"p_key": f"session_retro_{_dt.date.today().isoformat()}",
|
|
1249
|
+
"p_value": {
|
|
1250
|
+
"session_id": _SESSION_ID,
|
|
1251
|
+
"agent": AGENT_NAME,
|
|
1252
|
+
"shutdown_at": _dt.datetime.utcnow().isoformat(),
|
|
1253
|
+
"instance_id": _INSTANCE_ID,
|
|
1254
|
+
"auto_generated": True,
|
|
1255
|
+
},
|
|
1256
|
+
})
|
|
1257
|
+
except Exception:
|
|
1258
|
+
pass
|
|
1259
|
+
try:
|
|
1260
|
+
_release_lease()
|
|
1261
|
+
except Exception:
|
|
1262
|
+
pass
|
|
1245
1263
|
try:
|
|
1246
|
-
|
|
1247
|
-
except Exception
|
|
1248
|
-
|
|
1249
|
-
_heartbeat_stop.set()
|
|
1250
|
-
hb_thread.join(timeout=5)
|
|
1251
|
-
if hb_thread.is_alive():
|
|
1252
|
-
log.warning("heartbeat thread did not stop within 5s — lease already released")
|
|
1253
|
-
await _REALTIME.stop()
|
|
1264
|
+
_threading.Thread(target=_bg_cleanup, daemon=True, name="meshcode-shutdown-cleanup").start()
|
|
1265
|
+
except Exception:
|
|
1266
|
+
pass
|
|
1254
1267
|
|
|
1255
1268
|
|
|
1256
1269
|
# ============================================================
|
|
@@ -2852,4 +2865,48 @@ def run_server():
|
|
|
2852
2865
|
# Non-main thread or Windows doesn't expose the signal — harmless.
|
|
2853
2866
|
pass
|
|
2854
2867
|
|
|
2855
|
-
|
|
2868
|
+
# 2.10.29 field test proved: a bare FastMCP (naive.py / armored.py) loaded
|
|
2869
|
+
# via `claude mcp add` survives ESC, but meshcode does not — even after
|
|
2870
|
+
# moving out of the "Built-in" bucket to "Project". The remaining delta
|
|
2871
|
+
# between meshcode and the naked repro is our lifespan handler: it does
|
|
2872
|
+
# network I/O (release_lease, memory_set), blocking thread joins, and an
|
|
2873
|
+
# await on Realtime.stop() in its `finally`. When ESC cancels the in-flight
|
|
2874
|
+
# tool, CancelledError cascades into those shutdown steps, one of them
|
|
2875
|
+
# raises, and the event loop unwinds — process exits.
|
|
2876
|
+
#
|
|
2877
|
+
# Short of rewriting the lifespan (which we will), the pragmatic safety
|
|
2878
|
+
# net is to treat `mcp.run()` returning unexpectedly as an event loop
|
|
2879
|
+
# casualty and spin up a fresh one. FastMCP registers tools at import
|
|
2880
|
+
# time on the module-level `mcp` object, so the restart re-uses them;
|
|
2881
|
+
# only the lifespan re-runs. `SystemExit` is let through so `sys.exit`
|
|
2882
|
+
# from config-validation paths still works. stdin EOF returns from
|
|
2883
|
+
# `mcp.run()` normally, which breaks the loop.
|
|
2884
|
+
import time as _time_mod
|
|
2885
|
+
_restart_count = 0
|
|
2886
|
+
while True:
|
|
2887
|
+
try:
|
|
2888
|
+
mcp.run()
|
|
2889
|
+
except SystemExit:
|
|
2890
|
+
raise
|
|
2891
|
+
except BaseException as _e:
|
|
2892
|
+
_restart_count += 1
|
|
2893
|
+
try:
|
|
2894
|
+
sys.stderr.write(
|
|
2895
|
+
f"[meshcode-mcp] mcp.run() raised {type(_e).__name__}: {_e} "
|
|
2896
|
+
f"(restart #{_restart_count}); re-entering event loop in 0.3s\n"
|
|
2897
|
+
)
|
|
2898
|
+
sys.stderr.flush()
|
|
2899
|
+
except Exception:
|
|
2900
|
+
pass
|
|
2901
|
+
# If we're restarting more than a few times a second, something
|
|
2902
|
+
# is wrong (config error, unrecoverable import). Bail.
|
|
2903
|
+
if _restart_count > 20:
|
|
2904
|
+
try:
|
|
2905
|
+
sys.stderr.write("[meshcode-mcp] too many restarts; exiting\n")
|
|
2906
|
+
except Exception:
|
|
2907
|
+
pass
|
|
2908
|
+
break
|
|
2909
|
+
_time_mod.sleep(0.3)
|
|
2910
|
+
continue
|
|
2911
|
+
# Clean return = stdin EOF = Claude Code closed the pipe = real shutdown
|
|
2912
|
+
break
|
|
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
|