meshcode 2.10.30__tar.gz → 2.10.32__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.30 → meshcode-2.10.32}/PKG-INFO +1 -1
  2. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/__init__.py +1 -1
  3. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/meshcode_mcp/server.py +63 -31
  4. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode.egg-info/PKG-INFO +1 -1
  5. {meshcode-2.10.30 → meshcode-2.10.32}/pyproject.toml +1 -1
  6. {meshcode-2.10.30 → meshcode-2.10.32}/README.md +0 -0
  7. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/ascii_art.py +0 -0
  8. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/cli.py +0 -0
  9. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/comms_v4.py +0 -0
  10. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/invites.py +0 -0
  11. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/launcher.py +0 -0
  12. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/launcher_install.py +0 -0
  13. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/meshcode_mcp/__init__.py +0 -0
  14. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/meshcode_mcp/__main__.py +0 -0
  15. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/meshcode_mcp/backend.py +0 -0
  16. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/meshcode_mcp/realtime.py +0 -0
  17. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/meshcode_mcp/test_backend.py +0 -0
  18. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  19. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  20. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/preferences.py +0 -0
  21. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/protocol_v2.py +0 -0
  22. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/run_agent.py +0 -0
  23. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/secrets.py +0 -0
  24. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/self_update.py +0 -0
  25. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode/setup_clients.py +0 -0
  26. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode.egg-info/SOURCES.txt +0 -0
  27. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.10.30 → meshcode-2.10.32}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.10.30 → meshcode-2.10.32}/setup.cfg +0 -0
  32. {meshcode-2.10.30 → meshcode-2.10.32}/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.30
3
+ Version: 2.10.32
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.30"
2
+ __version__ = "2.10.32"
@@ -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()
1239
1231
  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.
1232
+ pass
1233
+ try:
1234
+ _record_event_bg("shutdown", {"agent": AGENT_NAME, "session_id": _SESSION_ID})
1235
+ except Exception:
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
  # ============================================================
@@ -1272,6 +1285,25 @@ except Exception:
1272
1285
 
1273
1286
  # ----------------- TOOLS -----------------
1274
1287
 
1288
+ @mcp.tool()
1289
+ async def meshcode_debug_sleep(seconds: int = 30) -> Dict[str, Any]:
1290
+ """DEBUG-ONLY: sleep N seconds via pure asyncio.sleep (no Realtime, no DB).
1291
+
1292
+ Exists to isolate whether ESC kills meshcode because of our Realtime
1293
+ listener / lifespan infrastructure, or regardless of the tool body.
1294
+ Matches armored.py's sleep_long byte-for-byte at the async layer.
1295
+ """
1296
+ import asyncio as _asyncio
1297
+ import os as _os
1298
+ try:
1299
+ await _asyncio.sleep(max(1, int(seconds)))
1300
+ return {"ok": True, "slept": int(seconds), "pid": _os.getpid()}
1301
+ except BaseException as e:
1302
+ sys.stderr.write(f"[meshcode-mcp] debug_sleep cancelled with {type(e).__name__}: {e}\n")
1303
+ sys.stderr.flush()
1304
+ raise
1305
+
1306
+
1275
1307
  @mcp.tool()
1276
1308
  @with_working_status
1277
1309
  def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.30
3
+ Version: 2.10.32
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.30"
7
+ version = "2.10.32"
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