meshcode 2.6.8__tar.gz → 2.6.9__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.6.8 → meshcode-2.6.9}/PKG-INFO +1 -1
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/__init__.py +1 -1
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/meshcode_mcp/realtime.py +23 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/meshcode_mcp/server.py +36 -5
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.6.8 → meshcode-2.6.9}/pyproject.toml +1 -1
- {meshcode-2.6.8 → meshcode-2.6.9}/README.md +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/cli.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/comms_v4.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/invites.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/launcher.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/launcher_install.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/preferences.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/run_agent.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/secrets.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/self_update.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode/setup_clients.py +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/setup.cfg +0 -0
- {meshcode-2.6.8 → meshcode-2.6.9}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.6.
|
|
2
|
+
__version__ = "2.6.9"
|
|
@@ -148,6 +148,25 @@ class RealtimeListener:
|
|
|
148
148
|
}
|
|
149
149
|
await ws.send(json.dumps(join_msg))
|
|
150
150
|
|
|
151
|
+
# Wait for phx_reply to confirm subscription was accepted.
|
|
152
|
+
self._subscription_ok = False
|
|
153
|
+
try:
|
|
154
|
+
reply_raw = await asyncio.wait_for(ws.recv(), timeout=10.0)
|
|
155
|
+
reply = json.loads(reply_raw)
|
|
156
|
+
reply_status = (reply.get("payload") or {}).get("status")
|
|
157
|
+
if reply_status == "ok":
|
|
158
|
+
self._subscription_ok = True
|
|
159
|
+
log.info(f"Realtime subscription OK for {self.agent_name} on {topic}")
|
|
160
|
+
else:
|
|
161
|
+
log.error(
|
|
162
|
+
f"Realtime subscription FAILED for {self.agent_name}: "
|
|
163
|
+
f"status={reply_status} payload={reply.get('payload')}"
|
|
164
|
+
)
|
|
165
|
+
except asyncio.TimeoutError:
|
|
166
|
+
log.error(f"Realtime subscription TIMEOUT — no phx_reply in 10s for {self.agent_name}")
|
|
167
|
+
except Exception as e:
|
|
168
|
+
log.error(f"Realtime subscription error reading phx_reply: {e}")
|
|
169
|
+
|
|
151
170
|
# Heartbeat task to keep the connection alive
|
|
152
171
|
heartbeat_task = asyncio.create_task(self._heartbeat(ws))
|
|
153
172
|
try:
|
|
@@ -247,3 +266,7 @@ class RealtimeListener:
|
|
|
247
266
|
@property
|
|
248
267
|
def is_connected(self) -> bool:
|
|
249
268
|
return self._connected
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def is_subscribed(self) -> bool:
|
|
272
|
+
return self._connected and getattr(self, "_subscription_ok", False)
|
|
@@ -1525,7 +1525,11 @@ async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[
|
|
|
1525
1525
|
if shaped:
|
|
1526
1526
|
return shaped
|
|
1527
1527
|
|
|
1528
|
-
|
|
1528
|
+
# Determine if realtime is actually delivering events.
|
|
1529
|
+
_rt_live = _REALTIME and _REALTIME.is_subscribed
|
|
1530
|
+
|
|
1531
|
+
if _rt_live:
|
|
1532
|
+
# 2a) Real async wait — zero CPU, zero Supabase calls.
|
|
1529
1533
|
woke = await _REALTIME.wait_for_message(timeout=float(actual_timeout))
|
|
1530
1534
|
if woke:
|
|
1531
1535
|
buffered = _REALTIME.drain()
|
|
@@ -1534,11 +1538,37 @@ async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[
|
|
|
1534
1538
|
if shaped:
|
|
1535
1539
|
return shaped
|
|
1536
1540
|
else:
|
|
1537
|
-
# Realtime
|
|
1538
|
-
|
|
1541
|
+
# 2b) Realtime NOT subscribed — aggressive DB polling every 5s
|
|
1542
|
+
# so messages arrive within seconds, not after 120s timeout.
|
|
1543
|
+
_poll_interval = 5
|
|
1544
|
+
_elapsed = 0
|
|
1545
|
+
while _elapsed < actual_timeout:
|
|
1546
|
+
await asyncio.sleep(min(_poll_interval, actual_timeout - _elapsed))
|
|
1547
|
+
_elapsed += _poll_interval
|
|
1548
|
+
try:
|
|
1549
|
+
api_key = _get_api_key()
|
|
1550
|
+
if api_key:
|
|
1551
|
+
db_pending = be.count_pending(_PROJECT_ID, AGENT_NAME, api_key=api_key)
|
|
1552
|
+
if db_pending and db_pending > 0:
|
|
1553
|
+
raw = be.read_inbox(_PROJECT_ID, AGENT_NAME, mark_read=True, api_key=api_key)
|
|
1554
|
+
if raw:
|
|
1555
|
+
msgs = [
|
|
1556
|
+
{"from": m["from_agent"], "type": m.get("type", "msg"),
|
|
1557
|
+
"ts": m.get("created_at"), "payload": m.get("payload", {}),
|
|
1558
|
+
"id": m.get("id"), "parent_id": m.get("parent_msg_id")}
|
|
1559
|
+
for m in raw
|
|
1560
|
+
]
|
|
1561
|
+
deduped = _filter_and_mark(msgs)
|
|
1562
|
+
if deduped:
|
|
1563
|
+
split = _split_messages(deduped)
|
|
1564
|
+
if not include_acks:
|
|
1565
|
+
split["acks"] = []
|
|
1566
|
+
if split["messages"] or split["done_signals"]:
|
|
1567
|
+
return {"got_message": True, "source": "db_poll_fallback", **split}
|
|
1568
|
+
except Exception:
|
|
1569
|
+
pass
|
|
1539
1570
|
|
|
1540
|
-
#
|
|
1541
|
-
# (connection drop, startup race, etc.)
|
|
1571
|
+
# Final fallback: one last DB check (covers realtime path missing msgs)
|
|
1542
1572
|
try:
|
|
1543
1573
|
api_key = _get_api_key()
|
|
1544
1574
|
if api_key:
|
|
@@ -1647,6 +1677,7 @@ def meshcode_check(include_acks: bool = False, since: Optional[str] = None) -> D
|
|
|
1647
1677
|
"agent": AGENT_NAME,
|
|
1648
1678
|
"project": PROJECT_NAME,
|
|
1649
1679
|
"realtime_connected": _REALTIME.is_connected if _REALTIME else False,
|
|
1680
|
+
"realtime_subscribed": _REALTIME.is_subscribed if _REALTIME else False,
|
|
1650
1681
|
**split,
|
|
1651
1682
|
}
|
|
1652
1683
|
# Auto-inject pending tasks
|
|
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
|