meshcode 2.10.17__tar.gz → 2.10.19__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.17 → meshcode-2.10.19}/PKG-INFO +1 -1
  2. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/__init__.py +1 -1
  3. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/meshcode_mcp/backend.py +110 -40
  4. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/meshcode_mcp/server.py +45 -22
  5. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode.egg-info/PKG-INFO +1 -1
  6. {meshcode-2.10.17 → meshcode-2.10.19}/pyproject.toml +1 -1
  7. {meshcode-2.10.17 → meshcode-2.10.19}/README.md +0 -0
  8. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/ascii_art.py +0 -0
  9. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/cli.py +0 -0
  10. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/comms_v4.py +0 -0
  11. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/invites.py +0 -0
  12. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/launcher.py +0 -0
  13. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/launcher_install.py +0 -0
  14. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/meshcode_mcp/__init__.py +0 -0
  15. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/meshcode_mcp/__main__.py +0 -0
  16. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/meshcode_mcp/realtime.py +0 -0
  17. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/meshcode_mcp/test_backend.py +0 -0
  18. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  19. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  20. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/preferences.py +0 -0
  21. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/protocol_v2.py +0 -0
  22. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/run_agent.py +0 -0
  23. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/secrets.py +0 -0
  24. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/self_update.py +0 -0
  25. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode/setup_clients.py +0 -0
  26. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode.egg-info/SOURCES.txt +0 -0
  27. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.10.17 → meshcode-2.10.19}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.10.17 → meshcode-2.10.19}/setup.cfg +0 -0
  32. {meshcode-2.10.17 → meshcode-2.10.19}/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.17
3
+ Version: 2.10.19
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.17"
2
+ __version__ = "2.10.19"
@@ -1,17 +1,19 @@
1
1
  """Thin Supabase REST backend used by both the MCP server and tests.
2
2
 
3
3
  Reuses the helpers from comms_v4.py without going through subprocess.
4
- Zero deps beyond stdlib (urllib).
4
+ Zero deps beyond stdlib (urllib + http.client for connection pooling).
5
5
  """
6
+ import http.client
6
7
  import json
7
8
  import os
9
+ import ssl
8
10
  import time as _time
9
11
  import threading as _threading
10
12
  from datetime import datetime
11
13
  from pathlib import Path
12
14
  from typing import Any, Dict, List, Optional
13
15
  from urllib.error import HTTPError, URLError
14
- from urllib.parse import quote
16
+ from urllib.parse import quote, urlparse
15
17
  from urllib.request import Request, urlopen
16
18
 
17
19
 
@@ -97,6 +99,74 @@ SUPABASE_SERVICE_ROLE_KEY = (
97
99
  SCHEMA = "meshcode"
98
100
 
99
101
 
102
+ # ── Persistent HTTPS Connection Pool ──────────────────────────────
103
+ # Each urlopen() opens a new TCP+TLS connection (~1-2s overhead).
104
+ # This pool reuses connections, cutting typical latency from ~3s to ~100ms.
105
+ class _ConnectionPool:
106
+ """Thread-safe persistent HTTPS connection to Supabase."""
107
+
108
+ def __init__(self, url: str, max_idle: float = 30.0):
109
+ parsed = urlparse(url)
110
+ self._host = parsed.hostname
111
+ self._port = parsed.port or 443
112
+ self._conn: Optional[http.client.HTTPSConnection] = None
113
+ self._lock = _threading.Lock()
114
+ self._max_idle = max_idle
115
+ self._last_used = 0.0
116
+ self._ctx = ssl.create_default_context()
117
+
118
+ def _get_conn(self) -> http.client.HTTPSConnection:
119
+ now = _time.monotonic()
120
+ if self._conn is not None:
121
+ # Close stale connections
122
+ if now - self._last_used > self._max_idle:
123
+ try:
124
+ self._conn.close()
125
+ except Exception:
126
+ pass
127
+ self._conn = None
128
+ if self._conn is None:
129
+ self._conn = http.client.HTTPSConnection(
130
+ self._host, self._port, timeout=10, context=self._ctx
131
+ )
132
+ return self._conn
133
+
134
+ def request(self, method: str, path: str, body: Optional[bytes], headers: Dict[str, str]) -> tuple:
135
+ """Returns (status, response_body_str). Thread-safe with retry on broken pipe."""
136
+ with self._lock:
137
+ for attempt in range(2):
138
+ conn = self._get_conn()
139
+ try:
140
+ conn.request(method, path, body=body, headers=headers)
141
+ resp = conn.getresponse()
142
+ data = resp.read().decode("utf-8")
143
+ self._last_used = _time.monotonic()
144
+ return resp.status, data
145
+ except (http.client.RemoteDisconnected, BrokenPipeError,
146
+ ConnectionResetError, OSError) as e:
147
+ # Connection went stale — close and retry once
148
+ try:
149
+ self._conn.close()
150
+ except Exception:
151
+ pass
152
+ self._conn = None
153
+ if attempt == 0:
154
+ continue
155
+ raise
156
+
157
+ def close(self):
158
+ with self._lock:
159
+ if self._conn:
160
+ try:
161
+ self._conn.close()
162
+ except Exception:
163
+ pass
164
+ self._conn = None
165
+
166
+
167
+ _pool = _ConnectionPool(SUPABASE_URL)
168
+
169
+
100
170
  def _now_iso() -> str:
101
171
  return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S+00:00")
102
172
 
@@ -118,27 +188,29 @@ def _headers(*, prefer: Optional[str] = None, content_profile: bool = True) -> D
118
188
  def _request(method: str, path: str, *, data: Any = None, prefer: Optional[str] = None) -> Any:
119
189
  if not _circuit.can_execute():
120
190
  return {"_error": "circuit breaker open — Supabase temporarily unavailable", "_code": 503}
121
- url = f"{SUPABASE_URL}/rest/v1/{path}"
191
+ rest_path = f"/rest/v1/{path}"
122
192
  body = json.dumps(data).encode("utf-8") if data else None
123
- req = Request(url, data=body, method=method, headers=_headers(prefer=prefer))
193
+ hdrs = _headers(prefer=prefer)
124
194
  try:
125
- with urlopen(req, timeout=10) as resp:
126
- raw = resp.read().decode("utf-8")
195
+ status, raw = _pool.request(method, rest_path, body, hdrs)
196
+ if 200 <= status < 300:
127
197
  _circuit.record_success()
128
198
  return json.loads(raw) if raw.strip() else None
129
- except HTTPError as e:
130
- err = e.read().decode("utf-8", errors="replace")
131
- # 4xx = client error (not a backend failure), don't trip breaker
132
- if 400 <= e.code < 500:
199
+ elif 400 <= status < 500:
133
200
  _circuit.record_success()
201
+ try:
202
+ err_obj = json.loads(raw)
203
+ return {"_error": err_obj.get("message", raw[:200]), "_code": status}
204
+ except Exception:
205
+ return {"_error": raw[:200], "_code": status}
134
206
  else:
135
207
  _circuit.record_failure()
136
- try:
137
- err_obj = json.loads(err)
138
- return {"_error": err_obj.get("message", err[:200]), "_code": e.code}
139
- except Exception:
140
- return {"_error": err[:200], "_code": e.code}
141
- except (URLError, OSError, TimeoutError) as e:
208
+ try:
209
+ err_obj = json.loads(raw)
210
+ return {"_error": err_obj.get("message", raw[:200]), "_code": status}
211
+ except Exception:
212
+ return {"_error": raw[:200], "_code": status}
213
+ except (URLError, OSError, TimeoutError, http.client.HTTPException) as e:
142
214
  _circuit.record_failure()
143
215
  return {"_error": str(getattr(e, 'reason', e)), "_code": 0}
144
216
 
@@ -200,34 +272,31 @@ def sb_rpc(fn_name: str, params: Dict, *, _max_retries: int = 3) -> Any:
200
272
  if not _circuit.can_execute():
201
273
  return {"_error": "circuit breaker open — Supabase temporarily unavailable", "_circuit": "open"}
202
274
  last_err = None
275
+ rpc_path = f"/rest/v1/rpc/{fn_name}"
276
+ body = json.dumps(params).encode("utf-8")
277
+ hdrs = _headers(content_profile=False)
203
278
  for attempt in range(_max_retries):
204
- url = f"{SUPABASE_URL}/rest/v1/rpc/{fn_name}"
205
- body = json.dumps(params).encode("utf-8")
206
- req = Request(url, data=body, method="POST", headers=_headers(content_profile=False))
207
279
  try:
208
- with urlopen(req, timeout=10) as resp:
209
- raw = resp.read().decode("utf-8")
280
+ status, raw = _pool.request("POST", rpc_path, body, hdrs)
281
+ if 200 <= status < 300:
210
282
  result = json.loads(raw) if raw.strip() else None
211
- _circuit.record_success()
212
- # Auto-record tool calls to session events (hot-reloadable)
213
- if _recording_enabled and fn_name not in _SKIP_RECORDING:
214
- _bg_record("tool_call", {"rpc": fn_name})
215
- return result
216
- except HTTPError as e:
217
- err = e.read().decode("utf-8", errors="replace")
218
- # 4xx errors are not transient — don't retry, don't trip breaker
219
- if 400 <= e.code < 500:
283
+ _circuit.record_success()
284
+ if _recording_enabled and fn_name not in _SKIP_RECORDING:
285
+ _bg_record("tool_call", {"rpc": fn_name})
286
+ return result
287
+ elif 400 <= status < 500:
220
288
  _circuit.record_success()
221
289
  try:
222
- result = {"_error": json.loads(err).get("message", err[:200])}
290
+ result = {"_error": json.loads(raw).get("message", raw[:200])}
223
291
  except Exception:
224
- result = {"_error": err[:200]}
292
+ result = {"_error": raw[:200]}
225
293
  if _recording_enabled and fn_name not in _SKIP_RECORDING:
226
- _bg_record("error", {"rpc": fn_name, "error": str(err)[:200]})
294
+ _bg_record("error", {"rpc": fn_name, "error": raw[:200]})
227
295
  return result
228
- _circuit.record_failure()
229
- last_err = err
230
- except (URLError, OSError, TimeoutError) as e:
296
+ else:
297
+ _circuit.record_failure()
298
+ last_err = raw[:200]
299
+ except (URLError, OSError, TimeoutError, http.client.HTTPException) as e:
231
300
  _circuit.record_failure()
232
301
  last_err = str(getattr(e, 'reason', e))
233
302
  # Retry with jitter for transient errors (5xx, network)
@@ -260,13 +329,14 @@ def _bg_record(event_type: str, payload: dict):
260
329
 
261
330
  def sb_rpc_raw(fn_name: str, params: Dict) -> Any:
262
331
  """Raw RPC call without recording (to avoid infinite recursion)."""
263
- url = f"{SUPABASE_URL}/rest/v1/rpc/{fn_name}"
332
+ rpc_path = f"/rest/v1/rpc/{fn_name}"
264
333
  body = json.dumps(params).encode("utf-8")
265
- req = Request(url, data=body, method="POST", headers=_headers(content_profile=False))
334
+ hdrs = _headers(content_profile=False)
266
335
  try:
267
- with urlopen(req, timeout=10) as resp:
268
- raw = resp.read().decode("utf-8")
336
+ status, raw = _pool.request("POST", rpc_path, body, hdrs)
337
+ if 200 <= status < 300:
269
338
  return json.loads(raw) if raw.strip() else None
339
+ return None
270
340
  except Exception:
271
341
  return None
272
342
 
@@ -85,11 +85,11 @@ def _mc_log(msg: str, level: str = "info") -> None:
85
85
  c = _agent_color(agent) if agent else "\033[36m"
86
86
  prefix = f"{c}{_ANSI_BOLD}[meshcode-mcp]{_ANSI_RESET}"
87
87
  if level == "error":
88
- print(f"{prefix} \033[91mERROR:{_ANSI_RESET} {msg}", "warn")
88
+ print(f"{prefix} \033[91mERROR:{_ANSI_RESET} {msg}", file=sys.stderr)
89
89
  elif level == "warn":
90
- print(f"{prefix} \033[33mWARNING:{_ANSI_RESET} {msg}", "warn")
90
+ print(f"{prefix} \033[33mWARNING:{_ANSI_RESET} {msg}", file=sys.stderr)
91
91
  else:
92
- print(f"{prefix} {c}{msg}{_ANSI_RESET}", "warn")
92
+ print(f"{prefix} {c}{msg}{_ANSI_RESET}", file=sys.stderr)
93
93
 
94
94
 
95
95
  # ============================================================
@@ -576,6 +576,11 @@ def with_working_status(func):
576
576
  _record_event_bg("tool_call", {"tool": name, "args_keys": list(kwargs.keys())})
577
577
  try:
578
578
  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}
579
584
  except Exception as e:
580
585
  if not skip:
581
586
  _auto_learn_error(name, e, list(kwargs.keys()))
@@ -713,7 +718,7 @@ def _boot_diagnostic() -> None:
713
718
  be.sb_select("mc_projects", f"id=eq.{_PROJECT_ID}", limit=1)
714
719
  checks_passed += 1
715
720
  except Exception as e:
716
- print(f"[meshcode] BOOT CHECK FAILED: Supabase API unreachable ({e}). Fix: check network/VPN.", "warn")
721
+ print(f"[meshcode] BOOT CHECK FAILED: Supabase API unreachable ({e}). Fix: check network/VPN.", file=sys.stderr)
717
722
 
718
723
  # Check 2: Lease valid
719
724
  try:
@@ -723,11 +728,11 @@ def _boot_diagnostic() -> None:
723
728
  if agent.get("instance_id") == _INSTANCE_ID:
724
729
  checks_passed += 1
725
730
  else:
726
- print(f"[meshcode] BOOT CHECK FAILED: Lease mismatch — expected {_INSTANCE_ID}, got {agent.get('instance_id')}. Fix: restart agent.", "warn")
731
+ print(f"[meshcode] BOOT CHECK FAILED: Lease mismatch — expected {_INSTANCE_ID}, got {agent.get('instance_id')}. Fix: restart agent.", file=sys.stderr)
727
732
  else:
728
- print(f"[meshcode] BOOT CHECK FAILED: Agent '{AGENT_NAME}' not found in project. Fix: register agent first.", "warn")
733
+ print(f"[meshcode] BOOT CHECK FAILED: Agent '{AGENT_NAME}' not found in project. Fix: register agent first.", file=sys.stderr)
729
734
  except Exception as e:
730
- print(f"[meshcode] BOOT CHECK FAILED: Could not verify lease ({e}).", "warn")
735
+ print(f"[meshcode] BOOT CHECK FAILED: Could not verify lease ({e}).", file=sys.stderr)
731
736
 
732
737
  # Check 3: Heartbeat recent
733
738
  try:
@@ -736,7 +741,7 @@ def _boot_diagnostic() -> None:
736
741
  if hb:
737
742
  checks_passed += 1
738
743
  else:
739
- print(f"[meshcode] BOOT CHECK WARNING: No heartbeat recorded yet.", "warn")
744
+ print(f"[meshcode] BOOT CHECK WARNING: No heartbeat recorded yet.", file=sys.stderr)
740
745
  else:
741
746
  checks_passed += 1 # skip if no agent data
742
747
  except Exception:
@@ -753,9 +758,9 @@ def _boot_diagnostic() -> None:
753
758
  checks_passed += 1 # non-critical
754
759
 
755
760
  if checks_passed == checks_total:
756
- print(f"[meshcode] All boot checks passed ({checks_passed}/{checks_total}).", "warn")
761
+ print(f"[meshcode] All boot checks passed ({checks_passed}/{checks_total}).", file=sys.stderr)
757
762
  else:
758
- print(f"[meshcode] Boot checks: {checks_passed}/{checks_total} passed. Agent starting anyway.", "warn")
763
+ print(f"[meshcode] Boot checks: {checks_passed}/{checks_total} passed. Agent starting anyway.", file=sys.stderr)
759
764
 
760
765
 
761
766
  _boot_diagnostic()
@@ -783,9 +788,11 @@ _SHUTDOWN_LOGGED = False
783
788
  def _log_crash_to_db(reason: str = "unknown", error_detail: str = "") -> None:
784
789
  """Best-effort crash log to mc_agent_crash_logs table. Non-fatal if table doesn't exist."""
785
790
  global _SHUTDOWN_LOGGED
786
- if _SHUTDOWN_LOGGED:
787
- return
788
- _SHUTDOWN_LOGGED = True
791
+ # Only suppress duplicates for process-level shutdown events, not tool exceptions
792
+ if reason in ("process_exit", "signal", "keyboard_interrupt", "system_exit", "unhandled_exception"):
793
+ if _SHUTDOWN_LOGGED:
794
+ return
795
+ _SHUTDOWN_LOGGED = True
789
796
  try:
790
797
  be.sb_rpc("mc_log_error", {
791
798
  "p_api_key": _get_api_key(),
@@ -812,15 +819,24 @@ def _on_exit() -> None:
812
819
 
813
820
 
814
821
  def _on_signal(signum, frame) -> None:
815
- """Signal handler for SIGTERM/SIGINT — clean shutdown."""
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
+ """
816
829
  sig_name = signal.Signals(signum).name if hasattr(signal, 'Signals') else str(signum)
830
+ _mc_log(f"Received {sig_name} — releasing lease", "warn")
817
831
  _log_crash_to_db("signal", f"Received {sig_name}")
818
832
  _release_lease()
819
- sys.exit(128 + signum)
833
+ # Do NOT sys.exit() let FastMCP's event loop shut down cleanly.
834
+ # The process will exit naturally when the event loop finishes.
820
835
 
821
836
 
822
837
  atexit.register(_on_exit)
823
838
  signal.signal(signal.SIGTERM, _on_signal)
839
+ signal.signal(signal.SIGINT, _on_signal)
824
840
 
825
841
 
826
842
  # ============================================================
@@ -1462,9 +1478,9 @@ try:
1462
1478
  elif isinstance(_ls_val, str):
1463
1479
  _LAST_SEEN_TS = _ls_val
1464
1480
  if _LAST_SEEN_TS:
1465
- print(f"[meshcode] Restored last_seen={_LAST_SEEN_TS} from mesh memory.", "warn")
1481
+ print(f"[meshcode] Restored last_seen={_LAST_SEEN_TS} from mesh memory.", file=sys.stderr)
1466
1482
  except Exception as _e:
1467
- print(f"[meshcode] Could not restore last_seen: {_e}", "warn")
1483
+ print(f"[meshcode] Could not restore last_seen: {_e}", file=sys.stderr)
1468
1484
 
1469
1485
 
1470
1486
  def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
@@ -1615,6 +1631,13 @@ async def meshcode_wait(timeout_seconds: int = 120, include_acks: bool = False)
1615
1631
  except Exception:
1616
1632
  pass
1617
1633
  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"}
1618
1641
  finally:
1619
1642
  _IN_WAIT = False
1620
1643
 
@@ -2624,7 +2647,7 @@ def _auto_update() -> None:
2624
2647
  return
2625
2648
 
2626
2649
  # 3. Install the new version (blocking, 60s timeout)
2627
- print(f"[meshcode] Updating {current} → {latest}...", "warn")
2650
+ print(f"[meshcode] Updating {current} → {latest}...", file=sys.stderr)
2628
2651
  try:
2629
2652
  result = subprocess.run(
2630
2653
  [sys.executable, "-m", "pip", "install", "--upgrade",
@@ -2644,11 +2667,11 @@ def _auto_update() -> None:
2644
2667
  # 4. In MCP mode, NEVER re-exec — it kills the stdio pipe to Claude Code.
2645
2668
  # The new version will load on the next clean boot.
2646
2669
  if os.environ.get("MESHCODE_MCP_SERVE") == "1":
2647
- print(f"[meshcode] Updated {current} → {latest}. Will load on next boot (MCP mode — cannot restart).", "warn")
2670
+ print(f"[meshcode] Updated {current} → {latest}. Will load on next boot (MCP mode — cannot restart).", file=sys.stderr)
2648
2671
  return
2649
2672
 
2650
2673
  # CLI mode: safe to re-exec
2651
- print(f"[meshcode] Updated to {latest}, restarting...", "warn")
2674
+ print(f"[meshcode] Updated to {latest}, restarting...", file=sys.stderr)
2652
2675
  os.environ["MESHCODE_UPDATED"] = "1"
2653
2676
  try:
2654
2677
  os.execv(sys.executable, [sys.executable] + sys.argv)
@@ -2683,6 +2706,6 @@ def run_server():
2683
2706
  import traceback as _tb
2684
2707
  tb_str = _tb.format_exc()
2685
2708
  _log_crash_to_db("unhandled_exception", f"{type(e).__name__}: {e}\n{tb_str}")
2686
- print(f"[meshcode-mcp] FATAL: {e}", "warn")
2687
- print(f"[meshcode-mcp] Stack trace logged to mc_agent_crash_logs", "warn")
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)
2688
2711
  sys.exit(1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.17
3
+ Version: 2.10.19
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.17"
7
+ version = "2.10.19"
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