meshcode 2.5.5__tar.gz → 2.5.7__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.5.5 → meshcode-2.5.7}/PKG-INFO +1 -1
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/__init__.py +1 -1
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/backend.py +13 -5
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/server.py +126 -9
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.5.5 → meshcode-2.5.7}/pyproject.toml +1 -1
- {meshcode-2.5.5 → meshcode-2.5.7}/README.md +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/cli.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/comms_v4.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/invites.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/launcher.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/launcher_install.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/preferences.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/run_agent.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/secrets.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/self_update.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/setup_clients.py +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/setup.cfg +0 -0
- {meshcode-2.5.5 → meshcode-2.5.7}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.5.
|
|
2
|
+
__version__ = "2.5.7"
|
|
@@ -269,12 +269,18 @@ def send_message(project_id: str, from_agent: str, to_agent: str, payload: Any,
|
|
|
269
269
|
if actual_encrypted:
|
|
270
270
|
out["encrypted"] = True
|
|
271
271
|
return out
|
|
272
|
-
# If RPC returned an
|
|
273
|
-
|
|
272
|
+
# If RPC returned an error, propagate it — do NOT fall through to
|
|
273
|
+
# direct insert (RLS blocks anon INSERT since migration 089).
|
|
274
|
+
if isinstance(result, dict) and result.get("error"):
|
|
274
275
|
return {"error": result["error"]}
|
|
275
|
-
#
|
|
276
|
+
# RPC returned unexpected shape but didn't error — treat as success
|
|
277
|
+
if result is not None:
|
|
278
|
+
return {"sent": True}
|
|
276
279
|
|
|
277
|
-
# Fallback: direct insert
|
|
280
|
+
# Fallback: direct insert — ONLY for tests or legacy (no api_key)
|
|
281
|
+
if api_key:
|
|
282
|
+
# api_key was provided but RPC path didn't return — should not happen
|
|
283
|
+
return {"error": "mc_send_message RPC unavailable and direct insert blocked by RLS"}
|
|
278
284
|
msg = {
|
|
279
285
|
"project_id": project_id,
|
|
280
286
|
"from_agent": from_agent,
|
|
@@ -326,7 +332,9 @@ def read_inbox(project_id: str, agent: str, mark_read: bool = True, api_key: Opt
|
|
|
326
332
|
# If RPC succeeded, skip direct update
|
|
327
333
|
if isinstance(result, dict) and result.get("ok"):
|
|
328
334
|
marked = True
|
|
329
|
-
if not marked:
|
|
335
|
+
if not marked and not api_key:
|
|
336
|
+
# Only attempt direct update when no api_key (tests/legacy).
|
|
337
|
+
# With api_key, RLS blocks anon UPDATE since migration 089.
|
|
330
338
|
sb_update("mc_messages", f"id=eq.{m['id']}", {"read": True})
|
|
331
339
|
|
|
332
340
|
# Auto-ACK senders — but ONLY for messages older than 60s. If the
|
|
@@ -748,9 +748,10 @@ SESSION START (do these IMMEDIATELY — don't wait for user input):
|
|
|
748
748
|
1. meshcode_set_status(status="online", task="ready")
|
|
749
749
|
2. meshcode_check() — read pending messages
|
|
750
750
|
3. meshcode_tasks() — check for assigned/pending tasks and claim any unclaimed ones
|
|
751
|
-
4.
|
|
752
|
-
5.
|
|
753
|
-
6.
|
|
751
|
+
4. meshcode_auto_wake() — scan meshwork health, create tasks for issues found
|
|
752
|
+
5. meshcode_status() — see who's online
|
|
753
|
+
6. If other agents are online → meshcode_send them a greeting
|
|
754
|
+
7. meshcode_wait() — enter the loop
|
|
754
755
|
|
|
755
756
|
CRITICAL: You communicate by CALLING TOOLS, not by thinking or writing text.
|
|
756
757
|
To talk to another agent → call meshcode_send(to="agent", message="...")
|
|
@@ -792,6 +793,9 @@ COMMANDER PROTOCOL (you are the team lead):
|
|
|
792
793
|
- Keep the human informed with brief status updates at milestones.
|
|
793
794
|
- You are autonomous: fix small issues yourself, delegate big ones.
|
|
794
795
|
- After each sprint: consolidate learnings, update scratchpad, save to memory.
|
|
796
|
+
- PROACTIVE MAINTENANCE: call meshcode_auto_wake() on session start and after
|
|
797
|
+
completing sprints. It scans meshwork health (stale agents, task backlog,
|
|
798
|
+
empty memories). Turn suggestions into real tasks — quality over quantity.
|
|
795
799
|
"""
|
|
796
800
|
return base
|
|
797
801
|
|
|
@@ -1132,10 +1136,27 @@ def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
|
|
|
1132
1136
|
# Cross-mesh routing: if 'to' contains '@', parse as agent@meshwork
|
|
1133
1137
|
if "@" in to:
|
|
1134
1138
|
target_agent, target_meshwork = to.split("@", 1)
|
|
1135
|
-
if sensitive
|
|
1136
|
-
return {"error": "sensitive
|
|
1139
|
+
if sensitive and not encrypted:
|
|
1140
|
+
return {"error": "sensitive messages must use encrypted=True for cross-mesh"}
|
|
1137
1141
|
api_key = _get_api_key()
|
|
1138
|
-
|
|
1142
|
+
|
|
1143
|
+
# Bridge re-encryption: encrypt payload with the TARGET mesh key so
|
|
1144
|
+
# the receiving agent decrypts normally. Uses mc_get_cross_mesh_key
|
|
1145
|
+
# RPC which validates the link exists before returning the key.
|
|
1146
|
+
if encrypted:
|
|
1147
|
+
key_result = be.sb_rpc("mc_get_cross_mesh_key", {
|
|
1148
|
+
"p_api_key": api_key,
|
|
1149
|
+
"p_source_project": PROJECT_NAME,
|
|
1150
|
+
"p_target_project": target_meshwork,
|
|
1151
|
+
})
|
|
1152
|
+
if not isinstance(key_result, dict) or not key_result.get("ok"):
|
|
1153
|
+
err = key_result.get("error", "unknown") if isinstance(key_result, dict) else "RPC failed"
|
|
1154
|
+
return {"error": f"cross-mesh encryption failed: {err}"}
|
|
1155
|
+
tgt_key = key_result["key"]
|
|
1156
|
+
encrypted_data = be.encrypt_payload(payload, tgt_key)
|
|
1157
|
+
payload = {"_encrypted": encrypted_data}
|
|
1158
|
+
|
|
1159
|
+
result = be.sb_rpc("mc_send_cross_mesh", {
|
|
1139
1160
|
"p_api_key": api_key,
|
|
1140
1161
|
"p_from_project": PROJECT_NAME,
|
|
1141
1162
|
"p_from_agent": AGENT_NAME,
|
|
@@ -1144,6 +1165,9 @@ def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
|
|
|
1144
1165
|
"p_payload": payload,
|
|
1145
1166
|
"p_type": "msg",
|
|
1146
1167
|
})
|
|
1168
|
+
if isinstance(result, dict) and result.get("ok") and encrypted:
|
|
1169
|
+
result["encrypted"] = True
|
|
1170
|
+
return result
|
|
1147
1171
|
|
|
1148
1172
|
return be.send_message(_PROJECT_ID, AGENT_NAME, to, payload, msg_type="msg",
|
|
1149
1173
|
parent_msg_id=in_reply_to, sensitive=sensitive,
|
|
@@ -1394,8 +1418,14 @@ def meshcode_done(reason: str) -> Dict[str, Any]:
|
|
|
1394
1418
|
|
|
1395
1419
|
@mcp.tool()
|
|
1396
1420
|
@with_working_status
|
|
1397
|
-
def meshcode_check(include_acks: bool = False) -> Dict[str, Any]:
|
|
1398
|
-
"""Peek at inbox (non-destructive). Returns pending count + new messages.
|
|
1421
|
+
def meshcode_check(include_acks: bool = False, since: Optional[str] = None) -> Dict[str, Any]:
|
|
1422
|
+
"""Peek at inbox (non-destructive). Returns pending count + new messages.
|
|
1423
|
+
|
|
1424
|
+
Args:
|
|
1425
|
+
include_acks: Include ack messages in response.
|
|
1426
|
+
since: ISO-8601 timestamp. Only return messages newer than this.
|
|
1427
|
+
Use meshcode_remember("last_seen", ts) to persist across sessions.
|
|
1428
|
+
"""
|
|
1399
1429
|
pending = be.count_pending(_PROJECT_ID, AGENT_NAME)
|
|
1400
1430
|
# Peek at realtime buffer WITHOUT draining — check is non-destructive
|
|
1401
1431
|
realtime_buffered = _REALTIME.peek() if _REALTIME else []
|
|
@@ -1421,11 +1451,15 @@ def meshcode_check(include_acks: bool = False) -> Dict[str, Any]:
|
|
|
1421
1451
|
if _seen_key({"id": m.get("id"), "from": m.get("from_agent"), "payload": m.get("payload", {}), "ts": m.get("created_at")}) not in _SEEN_MSG_IDS
|
|
1422
1452
|
]
|
|
1423
1453
|
|
|
1454
|
+
# Filter by `since` timestamp if provided
|
|
1455
|
+
if since:
|
|
1456
|
+
deduped = [m for m in deduped if m.get("ts") and str(m["ts"]) > since]
|
|
1457
|
+
|
|
1424
1458
|
split = _split_messages(deduped)
|
|
1425
1459
|
if not include_acks:
|
|
1426
1460
|
split["acks"] = []
|
|
1427
1461
|
return {
|
|
1428
|
-
"pending": pending,
|
|
1462
|
+
"pending": pending if not since else len(split.get("messages", [])),
|
|
1429
1463
|
"agent": AGENT_NAME,
|
|
1430
1464
|
"project": PROJECT_NAME,
|
|
1431
1465
|
"realtime_connected": _REALTIME.is_connected if _REALTIME else False,
|
|
@@ -1568,6 +1602,89 @@ def meshcode_task_reject(task_id: str, feedback: str = "") -> Dict[str, Any]:
|
|
|
1568
1602
|
})
|
|
1569
1603
|
|
|
1570
1604
|
|
|
1605
|
+
# ----------------- PROACTIVE HEALTH SCAN -----------------
|
|
1606
|
+
|
|
1607
|
+
@mcp.tool()
|
|
1608
|
+
@with_working_status
|
|
1609
|
+
def meshcode_auto_wake() -> Dict[str, Any]:
|
|
1610
|
+
"""Scan meshwork health and suggest maintenance tasks.
|
|
1611
|
+
|
|
1612
|
+
Checks: stale agents, task backlog, empty memories, unlinked meshworks.
|
|
1613
|
+
Returns a list of suggested tasks the commander can create.
|
|
1614
|
+
Call this on session start or periodically to keep the meshwork healthy.
|
|
1615
|
+
"""
|
|
1616
|
+
api_key = _get_api_key()
|
|
1617
|
+
suggestions: List[Dict[str, str]] = []
|
|
1618
|
+
|
|
1619
|
+
# 1. Check for stale agents (heartbeat >10 min, not offline/sleeping)
|
|
1620
|
+
try:
|
|
1621
|
+
agents = be.get_board(_PROJECT_ID)
|
|
1622
|
+
import datetime as _dt
|
|
1623
|
+
now = _dt.datetime.now(_dt.timezone.utc)
|
|
1624
|
+
for a in agents:
|
|
1625
|
+
if a.get("status") in ("offline", "sleeping", "done", "needs_setup"):
|
|
1626
|
+
continue
|
|
1627
|
+
hb = a.get("last_heartbeat")
|
|
1628
|
+
if hb:
|
|
1629
|
+
try:
|
|
1630
|
+
dt = _dt.datetime.fromisoformat(str(hb).replace("Z", "+00:00"))
|
|
1631
|
+
age_min = (now - dt).total_seconds() / 60
|
|
1632
|
+
if age_min > 10:
|
|
1633
|
+
suggestions.append({
|
|
1634
|
+
"title": f"Stale agent: {a['name']} ({int(age_min)}min no heartbeat)",
|
|
1635
|
+
"priority": "high",
|
|
1636
|
+
"assignee": "mesh-commander",
|
|
1637
|
+
"description": f"Agent {a['name']} shows status '{a.get('status')}' but last heartbeat was {int(age_min)} min ago. Force disconnect or investigate.",
|
|
1638
|
+
})
|
|
1639
|
+
except Exception:
|
|
1640
|
+
pass
|
|
1641
|
+
except Exception:
|
|
1642
|
+
pass
|
|
1643
|
+
|
|
1644
|
+
# 2. Check task backlog — open tasks with no assignee
|
|
1645
|
+
try:
|
|
1646
|
+
task_result = be.task_list(api_key, _PROJECT_ID, AGENT_NAME, status_filter="open")
|
|
1647
|
+
if isinstance(task_result, dict) and task_result.get("ok"):
|
|
1648
|
+
open_tasks = task_result.get("tasks", [])
|
|
1649
|
+
unassigned = [t for t in open_tasks if not t.get("assigned_to") or t.get("assigned_to") == "*"]
|
|
1650
|
+
if len(unassigned) > 3:
|
|
1651
|
+
suggestions.append({
|
|
1652
|
+
"title": f"Task backlog: {len(unassigned)} unassigned open tasks",
|
|
1653
|
+
"priority": "normal",
|
|
1654
|
+
"assignee": "mesh-commander",
|
|
1655
|
+
"description": "Review and assign or close stale tasks to keep the board clean.",
|
|
1656
|
+
})
|
|
1657
|
+
except Exception:
|
|
1658
|
+
pass
|
|
1659
|
+
|
|
1660
|
+
# 3. Check for agents with zero memories
|
|
1661
|
+
try:
|
|
1662
|
+
for a in agents:
|
|
1663
|
+
mem_result = be.sb_rpc("mc_recall_all", {
|
|
1664
|
+
"p_api_key": api_key,
|
|
1665
|
+
"p_project_id": _PROJECT_ID,
|
|
1666
|
+
"p_agent_name": a["name"],
|
|
1667
|
+
})
|
|
1668
|
+
if isinstance(mem_result, dict) and mem_result.get("ok"):
|
|
1669
|
+
memories = mem_result.get("memories", [])
|
|
1670
|
+
if len(memories) == 0 and a.get("status") not in ("needs_setup",):
|
|
1671
|
+
suggestions.append({
|
|
1672
|
+
"title": f"Agent {a['name']} has zero memories",
|
|
1673
|
+
"priority": "normal",
|
|
1674
|
+
"assignee": a["name"],
|
|
1675
|
+
"description": "Agent should save learnings, patterns, and preferences to memory for cross-session persistence.",
|
|
1676
|
+
})
|
|
1677
|
+
except Exception:
|
|
1678
|
+
pass
|
|
1679
|
+
|
|
1680
|
+
return {
|
|
1681
|
+
"ok": True,
|
|
1682
|
+
"suggestions": suggestions,
|
|
1683
|
+
"total": len(suggestions),
|
|
1684
|
+
"note": "Use meshcode_task_create to turn suggestions into assigned tasks.",
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
|
|
1571
1688
|
# ----------------- MESH LINK TOOLS -----------------
|
|
1572
1689
|
|
|
1573
1690
|
@mcp.tool()
|
|
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
|