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.
Files changed (32) hide show
  1. {meshcode-2.10.29 → meshcode-2.10.31}/PKG-INFO +1 -1
  2. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/__init__.py +1 -1
  3. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/server.py +89 -32
  4. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/PKG-INFO +1 -1
  5. {meshcode-2.10.29 → meshcode-2.10.31}/pyproject.toml +1 -1
  6. {meshcode-2.10.29 → meshcode-2.10.31}/README.md +0 -0
  7. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/ascii_art.py +0 -0
  8. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/cli.py +0 -0
  9. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/comms_v4.py +0 -0
  10. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/invites.py +0 -0
  11. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/launcher.py +0 -0
  12. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/launcher_install.py +0 -0
  13. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/__init__.py +0 -0
  14. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/__main__.py +0 -0
  15. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/backend.py +0 -0
  16. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/realtime.py +0 -0
  17. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/test_backend.py +0 -0
  18. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  19. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  20. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/preferences.py +0 -0
  21. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/protocol_v2.py +0 -0
  22. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/run_agent.py +0 -0
  23. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/secrets.py +0 -0
  24. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/self_update.py +0 -0
  25. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode/setup_clients.py +0 -0
  26. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/SOURCES.txt +0 -0
  27. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.10.29 → meshcode-2.10.31}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.10.29 → meshcode-2.10.31}/setup.cfg +0 -0
  32. {meshcode-2.10.29 → meshcode-2.10.31}/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.29
3
+ Version: 2.10.31
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.29"
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
- _record_event_bg("shutdown", {"agent": AGENT_NAME, "session_id": _SESSION_ID})
1221
- # Auto-save session summary to agent memory before shutdown
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
- import datetime as _dt
1224
- api_key = _get_api_key()
1225
- if api_key:
1226
- be.sb_rpc("mc_memory_set", {
1227
- "p_api_key": api_key,
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 # Never block shutdown
1241
- log.info("lifespan shutdown releasing lease + stopping heartbeat + realtime")
1242
- # Release lease FIRST before stopping heartbeat thread.
1243
- # If heartbeat join times out, the lease is already released so
1244
- # the agent won't block reconnection.
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
- _release_lease()
1247
- except Exception as _e:
1248
- log.warning(f"could not release lease: {_e}")
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
- mcp.run()
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.29
3
+ Version: 2.10.31
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.29"
7
+ version = "2.10.31"
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