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.
Files changed (31) hide show
  1. {meshcode-2.5.5 → meshcode-2.5.7}/PKG-INFO +1 -1
  2. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/__init__.py +1 -1
  3. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/backend.py +13 -5
  4. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/server.py +126 -9
  5. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/PKG-INFO +1 -1
  6. {meshcode-2.5.5 → meshcode-2.5.7}/pyproject.toml +1 -1
  7. {meshcode-2.5.5 → meshcode-2.5.7}/README.md +0 -0
  8. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/cli.py +0 -0
  9. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/comms_v4.py +0 -0
  10. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/invites.py +0 -0
  11. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/launcher.py +0 -0
  12. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/launcher_install.py +0 -0
  13. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/__init__.py +0 -0
  14. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/__main__.py +0 -0
  15. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/realtime.py +0 -0
  16. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/test_backend.py +0 -0
  17. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  18. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  19. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/preferences.py +0 -0
  20. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/protocol_v2.py +0 -0
  21. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/run_agent.py +0 -0
  22. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/secrets.py +0 -0
  23. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/self_update.py +0 -0
  24. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode/setup_clients.py +0 -0
  25. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/SOURCES.txt +0 -0
  26. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/dependency_links.txt +0 -0
  27. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/entry_points.txt +0 -0
  28. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/requires.txt +0 -0
  29. {meshcode-2.5.5 → meshcode-2.5.7}/meshcode.egg-info/top_level.txt +0 -0
  30. {meshcode-2.5.5 → meshcode-2.5.7}/setup.cfg +0 -0
  31. {meshcode-2.5.5 → meshcode-2.5.7}/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.5.5
3
+ Version: 2.5.7
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.5.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 auth/validation error, propagate it
273
- if isinstance(result, dict) and result.get("error") and result.get("error_code"):
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
- # If RPC doesn't exist yet (migration not applied), fall through to direct insert
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 (tests / legacy)
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. meshcode_status() — see who's online
752
- 5. If other agents are online → meshcode_send them a greeting
753
- 6. meshcode_wait() enter the loop
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 or encrypted:
1136
- return {"error": "sensitive/encrypted messages cannot be sent cross-mesh"}
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
- return be.sb_rpc("mc_send_cross_mesh", {
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.5.5
3
+ Version: 2.5.7
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.5.5"
7
+ version = "2.5.7"
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
File without changes
File without changes
File without changes
File without changes
File without changes