meshcode 2.10.36__tar.gz → 2.10.37__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.36 → meshcode-2.10.37}/PKG-INFO +1 -1
  2. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/__init__.py +1 -1
  3. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/meshcode_mcp/backend.py +41 -29
  4. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/meshcode_mcp/realtime.py +17 -5
  5. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/meshcode_mcp/server.py +37 -21
  6. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.10.36 → meshcode-2.10.37}/pyproject.toml +1 -1
  8. {meshcode-2.10.36 → meshcode-2.10.37}/README.md +0 -0
  9. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/ascii_art.py +0 -0
  10. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/cli.py +0 -0
  11. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/comms_v4.py +0 -0
  12. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/invites.py +0 -0
  13. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/launcher.py +0 -0
  14. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/launcher_install.py +0 -0
  15. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/meshcode_mcp/__init__.py +0 -0
  16. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/meshcode_mcp/__main__.py +0 -0
  17. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/meshcode_mcp/test_backend.py +0 -0
  18. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  19. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  20. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/preferences.py +0 -0
  21. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/protocol_v2.py +0 -0
  22. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/run_agent.py +0 -0
  23. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/secrets.py +0 -0
  24. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/self_update.py +0 -0
  25. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode/setup_clients.py +0 -0
  26. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode.egg-info/SOURCES.txt +0 -0
  27. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.10.36 → meshcode-2.10.37}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.10.36 → meshcode-2.10.37}/setup.cfg +0 -0
  32. {meshcode-2.10.36 → meshcode-2.10.37}/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.36
3
+ Version: 2.10.37
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.36"
2
+ __version__ = "2.10.37"
@@ -105,69 +105,81 @@ SCHEMA = "meshcode"
105
105
  # ── Persistent HTTPS Connection Pool ──────────────────────────────
106
106
  # Each urlopen() opens a new TCP+TLS connection (~1-2s overhead).
107
107
  # This pool reuses connections, cutting typical latency from ~3s to ~100ms.
108
+ # Pool of N connections (default 3) to avoid lock contention between the
109
+ # heartbeat thread, tool call handlers, and background recording thread.
108
110
  class _ConnectionPool:
109
- """Thread-safe persistent HTTPS connection to Supabase."""
111
+ """Thread-safe HTTPS connection pool to Supabase (N connections, round-robin)."""
110
112
 
111
- def __init__(self, url: str, max_idle: float = 30.0):
113
+ def __init__(self, url: str, pool_size: int = 3, max_idle: float = 30.0):
112
114
  parsed = urlparse(url)
113
115
  self._host = parsed.hostname
114
116
  self._port = parsed.port or 443
115
- self._conn: Optional[http.client.HTTPSConnection] = None
116
- self._lock = _threading.Lock()
117
+ self._pool_size = pool_size
118
+ self._conns: List[Optional[http.client.HTTPSConnection]] = [None] * pool_size
119
+ self._locks = [_threading.Lock() for _ in range(pool_size)]
120
+ self._last_used = [0.0] * pool_size
117
121
  self._max_idle = max_idle
118
- self._last_used = 0.0
119
122
  self._ctx = ssl.create_default_context()
123
+ self._counter = 0
124
+ self._counter_lock = _threading.Lock()
125
+
126
+ def _pick_slot(self) -> int:
127
+ """Round-robin slot selection."""
128
+ with self._counter_lock:
129
+ slot = self._counter % self._pool_size
130
+ self._counter += 1
131
+ return slot
120
132
 
121
- def _get_conn(self) -> http.client.HTTPSConnection:
133
+ def _get_conn(self, slot: int) -> http.client.HTTPSConnection:
122
134
  now = _time.monotonic()
123
- if self._conn is not None:
124
- # Close stale connections
125
- if now - self._last_used > self._max_idle:
135
+ if self._conns[slot] is not None:
136
+ if now - self._last_used[slot] > self._max_idle:
126
137
  try:
127
- self._conn.close()
138
+ self._conns[slot].close()
128
139
  except Exception:
129
140
  pass
130
- self._conn = None
131
- if self._conn is None:
132
- self._conn = http.client.HTTPSConnection(
141
+ self._conns[slot] = None
142
+ if self._conns[slot] is None:
143
+ self._conns[slot] = http.client.HTTPSConnection(
133
144
  self._host, self._port, timeout=10, context=self._ctx
134
145
  )
135
- return self._conn
146
+ return self._conns[slot]
136
147
 
137
148
  def request(self, method: str, path: str, body: Optional[bytes], headers: Dict[str, str]) -> tuple:
138
- """Returns (status, response_body_str). Thread-safe with retry on broken pipe."""
139
- with self._lock:
149
+ """Returns (status, response_body_str). Thread-safe with per-slot locking."""
150
+ slot = self._pick_slot()
151
+ with self._locks[slot]:
140
152
  for attempt in range(2):
141
- conn = self._get_conn()
153
+ conn = self._get_conn(slot)
142
154
  try:
143
155
  conn.request(method, path, body=body, headers=headers)
144
156
  resp = conn.getresponse()
145
157
  data = resp.read().decode("utf-8")
146
- self._last_used = _time.monotonic()
158
+ self._last_used[slot] = _time.monotonic()
147
159
  return resp.status, data
148
160
  except (http.client.RemoteDisconnected, BrokenPipeError,
149
161
  ConnectionResetError, OSError) as e:
150
- # Connection went stale — close and retry once
151
162
  try:
152
- self._conn.close()
163
+ self._conns[slot].close()
153
164
  except Exception:
154
165
  pass
155
- self._conn = None
166
+ self._conns[slot] = None
156
167
  if attempt == 0:
157
168
  continue
158
169
  raise
159
170
 
160
171
  def close(self):
161
- with self._lock:
162
- if self._conn:
163
- try:
164
- self._conn.close()
165
- except Exception:
166
- pass
167
- self._conn = None
172
+ for i in range(self._pool_size):
173
+ with self._locks[i]:
174
+ if self._conns[i]:
175
+ try:
176
+ self._conns[i].close()
177
+ except Exception:
178
+ pass
179
+ self._conns[i] = None
168
180
 
169
181
 
170
- _pool = _ConnectionPool(SUPABASE_URL)
182
+ _pool = _ConnectionPool(SUPABASE_URL, pool_size=3)
171
183
 
172
184
 
173
185
  def _now_iso() -> str:
@@ -53,9 +53,12 @@ class RealtimeListener:
53
53
  self.notify_callback = notify_callback
54
54
  self.service_role_key = service_role_key
55
55
 
56
- # Last 500 unread messages — drained by meshcode_check tool
57
- self.queue: Deque[Dict] = deque(maxlen=500)
56
+ # Message queue — drained by meshcode_check/meshcode_wait.
57
+ # Cap at 2000 (was 500). When full, oldest messages are dropped
58
+ # and _dropped_count is incremented so the agent knows.
59
+ self.queue: Deque[Dict] = deque(maxlen=2000)
58
60
  self._overflow_warned = False
61
+ self._dropped_count = 0
59
62
  self._task: Optional[asyncio.Task] = None
60
63
  # asyncio.Event() in Py3.10+ no longer requires a running loop, but
61
64
  # on older Python or certain Windows event-loop policies it can
@@ -272,9 +275,14 @@ class RealtimeListener:
272
275
  "id": record.get("id"),
273
276
  "parent_id": record.get("parent_msg_id"),
274
277
  }
275
- if len(self.queue) >= 400 and not self._overflow_warned:
276
- log.warning(f"Message queue at {len(self.queue)}/500risk of dropping messages")
277
- self._overflow_warned = True
278
+ if len(self.queue) >= self.queue.maxlen:
279
+ # Queue is fullthis append will drop the oldest message
280
+ self._dropped_count += 1
281
+ if not self._overflow_warned:
282
+ log.warning(f"Message queue FULL ({self.queue.maxlen}) — dropping oldest messages ({self._dropped_count} dropped so far)")
283
+ self._overflow_warned = True
284
+ elif len(self.queue) >= self.queue.maxlen * 0.8 and not self._overflow_warned:
285
+ log.warning(f"Message queue at {len(self.queue)}/{self.queue.maxlen} — approaching limit")
278
286
  self.queue.append(enriched)
279
287
  # Wake any meshcode_wait blocked on this event.
280
288
  try:
@@ -327,6 +335,10 @@ class RealtimeListener:
327
335
  await self.stop()
328
336
  await self.start()
329
337
 
338
+ @property
339
+ def dropped_count(self) -> int:
340
+ return self._dropped_count
341
+
330
342
  @property
331
343
  def is_connected(self) -> bool:
332
344
  return self._connected
@@ -864,6 +864,19 @@ except Exception as _e:
864
864
  log.warning(f"could not fetch agent profile: {_e}")
865
865
 
866
866
 
867
+ # ── Leader/commander detection (single source of truth) ───────
868
+ _LEADER_KEYWORDS = (
869
+ 'commander', 'lead', 'orchestrat', 'coordinator', 'coordinat',
870
+ 'coordinad', 'jefe', 'líder', 'lider', 'director', 'manager',
871
+ 'chief', 'captain', 'boss', 'head agent',
872
+ )
873
+
874
+ def _is_leader_agent() -> bool:
875
+ """Check if this agent is a leader/commander based on name + role keywords."""
876
+ haystack = ((_ROLE_DESCRIPTION or '') + ' ' + AGENT_NAME).lower()
877
+ return any(k in haystack for k in _LEADER_KEYWORDS)
878
+
879
+
867
880
  def _build_instructions() -> str:
868
881
  """Build the system-instructions block injected via the MCP InitializeResult.
869
882
 
@@ -939,15 +952,7 @@ what CLI command to run next (e.g. "meshcode run backend in a new terminal").
939
952
  Setup help → README.md or https://meshcode.io/docs
940
953
  """
941
954
  # Inject commander protocol if this agent is a leader
942
- # Match leader-like agent names and roles across languages.
943
- # Substring match: "orchestrator" hits on "orchestrat", "coordinador" hits on "coordinat".
944
- _leader_haystack = ((_ROLE_DESCRIPTION or '') + ' ' + AGENT_NAME).lower()
945
- _LEADER_KEYWORDS = (
946
- 'commander', 'lead', 'orchestrat', 'coordinator', 'coordinat',
947
- 'coordinad', 'jefe', 'líder', 'lider', 'director', 'manager',
948
- 'chief', 'captain', 'boss', 'head agent',
949
- )
950
- is_leader = any(k in _leader_haystack for k in _LEADER_KEYWORDS)
955
+ is_leader = _is_leader_agent()
951
956
  if is_leader:
952
957
  base += """
953
958
  COMMANDER PROTOCOL (you are the team lead):
@@ -1612,11 +1617,7 @@ def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
1612
1617
  ]
1613
1618
  # For leader agents: also include unclaimed '*' tasks as pending
1614
1619
  # so commanders auto-triage them instead of letting them pile up.
1615
- _leader_haystack = ((_ROLE_DESCRIPTION or '') + ' ' + AGENT_NAME).lower()
1616
- _LEADER_KW = ('commander', 'lead', 'orchestrat', 'coordinator', 'coordinat',
1617
- 'coordinad', 'jefe', 'líder', 'lider', 'director', 'manager',
1618
- 'chief', 'captain', 'boss', 'head agent')
1619
- is_leader = any(k in _leader_haystack for k in _LEADER_KW)
1620
+ is_leader = _is_leader_agent()
1620
1621
  if is_leader:
1621
1622
  wildcard_tasks = [
1622
1623
  {"id": t["id"][:8], "title": t["title"][:80], "priority": t.get("priority", "normal"), "status": t["status"]}
@@ -1626,6 +1627,9 @@ def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
1626
1627
  and not t.get("claimed_by")
1627
1628
  ]
1628
1629
  pending.extend(wildcard_tasks)
1630
+ # Sort by priority so urgent tasks surface first
1631
+ _PRIORITY_ORDER = {"urgent": 0, "high": 1, "normal": 2, "low": 3}
1632
+ pending.sort(key=lambda t: _PRIORITY_ORDER.get(t.get("priority", "normal"), 2))
1629
1633
  return pending if pending else None
1630
1634
  except Exception:
1631
1635
  return None
@@ -1655,12 +1659,7 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
1655
1659
  # delegate tasks and need to stay in the wait loop to receive reports.
1656
1660
  pending_tasks = _get_pending_tasks_summary()
1657
1661
  if pending_tasks:
1658
- _leader_haystack = ((_ROLE_DESCRIPTION or '') + ' ' + AGENT_NAME).lower()
1659
- _LEADER_KW = ('commander', 'lead', 'orchestrat', 'coordinator', 'coordinat',
1660
- 'coordinad', 'jefe', 'líder', 'lider', 'director', 'manager',
1661
- 'chief', 'captain', 'boss', 'head agent')
1662
- _is_leader = any(k in _leader_haystack for k in _LEADER_KW)
1663
- if not _is_leader:
1662
+ if not _is_leader_agent():
1664
1663
  return {
1665
1664
  "refused": True,
1666
1665
  "reason": "You have open tasks. Work them before entering wait.",
@@ -2671,12 +2670,29 @@ def meshcode_recall(key: Optional[str] = None) -> Dict[str, Any]:
2671
2670
  "p_key": key,
2672
2671
  })
2673
2672
  else:
2674
- return be.sb_rpc("mc_memory_list", {
2673
+ # Return critical tier (full content) + counts of other tiers.
2674
+ # Episodic memories are large and should be searched on demand
2675
+ # via meshcode_recall_search(), not dumped into context.
2676
+ critical = be.sb_rpc("mc_memory_list", {
2675
2677
  "p_api_key": api_key,
2676
2678
  "p_agent_name": AGENT_NAME,
2677
2679
  "p_tier": "critical",
2678
2680
  "p_project_name": PROJECT_NAME,
2679
2681
  })
2682
+ # Also fetch reference tier keys (lightweight, useful context)
2683
+ reference = be.sb_rpc("mc_memory_list", {
2684
+ "p_api_key": api_key,
2685
+ "p_agent_name": AGENT_NAME,
2686
+ "p_tier": "reference",
2687
+ "p_project_name": PROJECT_NAME,
2688
+ })
2689
+ if isinstance(critical, dict) and critical.get("ok"):
2690
+ ref_keys = []
2691
+ if isinstance(reference, dict) and reference.get("ok"):
2692
+ ref_keys = [m.get("key") for m in reference.get("memories", [])]
2693
+ critical["reference_keys"] = ref_keys
2694
+ critical["tip"] = "Use meshcode_recall_search(query) to search episodic memories."
2695
+ return critical
2680
2696
 
2681
2697
 
2682
2698
  @mcp.tool()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.36
3
+ Version: 2.10.37
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.36"
7
+ version = "2.10.37"
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