meshcode 2.10.37__tar.gz → 2.10.38__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.37 → meshcode-2.10.38}/PKG-INFO +1 -1
  2. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/__init__.py +1 -1
  3. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/comms_v4.py +39 -16
  4. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/meshcode_mcp/server.py +130 -2
  5. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode.egg-info/PKG-INFO +1 -1
  6. {meshcode-2.10.37 → meshcode-2.10.38}/pyproject.toml +1 -1
  7. {meshcode-2.10.37 → meshcode-2.10.38}/README.md +0 -0
  8. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/ascii_art.py +0 -0
  9. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/cli.py +0 -0
  10. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/invites.py +0 -0
  11. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/launcher.py +0 -0
  12. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/launcher_install.py +0 -0
  13. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/meshcode_mcp/__init__.py +0 -0
  14. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/meshcode_mcp/__main__.py +0 -0
  15. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/meshcode_mcp/backend.py +0 -0
  16. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/meshcode_mcp/realtime.py +0 -0
  17. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/meshcode_mcp/test_backend.py +0 -0
  18. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  19. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  20. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/preferences.py +0 -0
  21. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/protocol_v2.py +0 -0
  22. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/run_agent.py +0 -0
  23. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/secrets.py +0 -0
  24. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/self_update.py +0 -0
  25. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode/setup_clients.py +0 -0
  26. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode.egg-info/SOURCES.txt +0 -0
  27. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.10.37 → meshcode-2.10.38}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.10.37 → meshcode-2.10.38}/setup.cfg +0 -0
  32. {meshcode-2.10.37 → meshcode-2.10.38}/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.37
3
+ Version: 2.10.38
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.37"
2
+ __version__ = "2.10.38"
@@ -615,7 +615,7 @@ def spawn_headless_agent(project, name, project_id, message_body, from_agent):
615
615
  # The agent is identity-only at rest; next message wakes it again.
616
616
  sb_update("mc_agents",
617
617
  f"project_id=eq.{project_id}&name=eq.{quote(name)}",
618
- {"status": "sleeping", "task": "Esperando mensajes...",
618
+ {"status": "sleeping", "task": "Waiting for messages...",
619
619
  "last_active_at": now_iso(), "last_heartbeat": now_iso()})
620
620
  except Exception:
621
621
  pass
@@ -865,7 +865,7 @@ def register(project, name, role=""):
865
865
  ensure_sessions()
866
866
  project_id = get_project_id(project)
867
867
  if not project_id:
868
- print(f"[ERROR] No se pudo crear/encontrar proyecto '{project}'")
868
+ print(f"[ERROR] Could not create/find project '{project}'")
869
869
  return
870
870
 
871
871
  ppid = os.getppid()
@@ -946,7 +946,7 @@ def register(project, name, role=""):
946
946
  order="created_at.desc", limit=10)
947
947
  if recent:
948
948
  recent.reverse()
949
- print(f"\n[COMMS] Últimos {len(recent)} mensajes del equipo:")
949
+ print(f"\n[COMMS] Last {len(recent)} team messages:")
950
950
  for msg in recent:
951
951
  ts = msg.get("created_at", "")[-8:]
952
952
  fr = msg["from_agent"]
@@ -1132,7 +1132,7 @@ def watch(project, name, interval=10, timeout=0):
1132
1132
  # Update status to standby
1133
1133
  sb_update("mc_agents",
1134
1134
  f"project_id=eq.{project_id}&name=eq.{quote(name)}",
1135
- {"status": "standby", "task": "Esperando mensajes...", "last_heartbeat": now_iso()})
1135
+ {"status": "standby", "task": "Waiting for messages...", "last_heartbeat": now_iso()})
1136
1136
 
1137
1137
  cycle = timeout if timeout > 0 else 600
1138
1138
  print(f"[{project}] {name} en watch — poll cada {interval}s, ciclo {cycle}s (auto-loop)")
@@ -1161,10 +1161,10 @@ def watch(project, name, interval=10, timeout=0):
1161
1161
  time.sleep(interval)
1162
1162
 
1163
1163
  # Cycle ended without messages — check user context, then AUTO-RESTART watch
1164
- print(f"[{project}] Otro ciclo sin mensajes. Watch activo, sigue corriendo. En standby.")
1164
+ print(f"[{project}] No messages this cycle. Watch active, still running. Standby.")
1165
1165
  sb_update("mc_agents",
1166
1166
  f"project_id=eq.{project_id}&name=eq.{quote(name)}",
1167
- {"status": "standby", "task": "Esperando mensajes...", "last_heartbeat": now_iso()})
1167
+ {"status": "standby", "task": "Waiting for messages...", "last_heartbeat": now_iso()})
1168
1168
 
1169
1169
 
1170
1170
  def update_board(project, name, status, task=""):
@@ -1183,7 +1183,7 @@ def update_board(project, name, status, task=""):
1183
1183
  print(f"[{project}] {name}: NO puedes estar idle — tienes {count} mensaje(s) pendientes.")
1184
1184
  print(f"[{project}] Ejecuta: python3 ~/Desktop/meshcode/comms_v4.py read {project} {name}")
1185
1185
  status = "working"
1186
- task = f"{count} mensajes pendientes"
1186
+ task = f"{count} pending messages"
1187
1187
 
1188
1188
  updates = {"status": status, "last_heartbeat": now_iso()}
1189
1189
  if task:
@@ -1197,7 +1197,7 @@ def update_board(project, name, status, task=""):
1197
1197
  def show_board(project):
1198
1198
  project_id = get_project_id(project)
1199
1199
  if not project_id:
1200
- print(f"[{project}] Sin proyecto")
1200
+ print(f"[{project}] Project not found")
1201
1201
  return
1202
1202
 
1203
1203
  agents = sb_select("mc_agents", f"project_id=eq.{project_id}", order="registered_at.asc")
@@ -1221,7 +1221,7 @@ def show_status(project=None):
1221
1221
  projects_data = sb_select("mc_projects", "", order="created_at.asc")
1222
1222
 
1223
1223
  if not projects_data:
1224
- print("[COMMS] Sin proyectos activos")
1224
+ print("[COMMS] No active projects")
1225
1225
  return
1226
1226
 
1227
1227
  print(f"\n{'='*60}")
@@ -1266,7 +1266,7 @@ def show_history(project, last_n=20, between=None):
1266
1266
  messages.reverse()
1267
1267
 
1268
1268
  print(f"\n{'='*60}")
1269
- print(f" {project.upper()} — Historial ({len(messages)} mensajes)")
1269
+ print(f" {project.upper()} — History ({len(messages)} messages)")
1270
1270
  if between:
1271
1271
  print(f" Filtro: {between}")
1272
1272
  print(f"{'='*60}\n")
@@ -1472,7 +1472,7 @@ def connect_terminal(project, name, role=""):
1472
1472
  ensure_sessions()
1473
1473
  project_id = get_project_id(project)
1474
1474
  if not project_id:
1475
- print(f"[ERROR] No se pudo crear/encontrar proyecto '{project}'")
1475
+ print(f"[ERROR] Could not create/find project '{project}'")
1476
1476
  return
1477
1477
 
1478
1478
  tty, owning = _capture_tty_for_pid(os.getppid())
@@ -1542,7 +1542,7 @@ def disconnect_terminal(project, name):
1542
1542
  """Mark agent offline, clear tty/pid, stop heartbeat, remove session file."""
1543
1543
  project_id = get_project_id(project)
1544
1544
  if not project_id:
1545
- print(f"[ERROR] proyecto '{project}' no encontrado")
1545
+ print(f"[ERROR] project '{project}' not found")
1546
1546
  return
1547
1547
  rpc_result = sb_rpc("mc_disconnect_agent", {
1548
1548
  "p_project_id": project_id,
@@ -2177,7 +2177,7 @@ if __name__ == "__main__":
2177
2177
  sys.exit(1)
2178
2178
  project_id = get_project_id(proj)
2179
2179
  if not project_id:
2180
- print(f"[ERROR] proyecto '{proj}' no encontrado")
2180
+ print(f"[ERROR] project '{proj}' not found")
2181
2181
  sys.exit(1)
2182
2182
  ok = spawn_headless_agent(proj, name, project_id,
2183
2183
  "ping: synthetic wake-headless test message", "test-harness")
@@ -2193,7 +2193,7 @@ if __name__ == "__main__":
2193
2193
  sys.exit(1)
2194
2194
  project_id = get_project_id(proj)
2195
2195
  if not project_id:
2196
- print(f"[ERROR] proyecto '{proj}' no encontrado")
2196
+ print(f"[ERROR] project '{proj}' not found")
2197
2197
  sys.exit(1)
2198
2198
  rpc_name = {"kill": "mc_agent_kill", "wake": "mc_agent_wake", "sleep": "mc_agent_sleep"}[cmd]
2199
2199
  result = sb_rpc(rpc_name, {"p_project_id": project_id, "p_agent_name": name})
@@ -2212,7 +2212,7 @@ if __name__ == "__main__":
2212
2212
  sys.exit(1)
2213
2213
  project_id = get_project_id(proj)
2214
2214
  if not project_id:
2215
- print(f"[ERROR] proyecto '{proj}' no encontrado")
2215
+ print(f"[ERROR] project '{proj}' not found")
2216
2216
  sys.exit(1)
2217
2217
  if sub == "get":
2218
2218
  result = sb_rpc("mc_agent_get_profile", {"p_project_id": project_id, "p_agent_name": name})
@@ -2425,7 +2425,30 @@ if __name__ == "__main__":
2425
2425
  sys.exit(1)
2426
2426
  login(key)
2427
2427
 
2428
- elif cmd in ("profiles", "whoami"):
2428
+ elif cmd == "whoami":
2429
+ # Show the logged-in user identity from profile_meta.json
2430
+ meta_path = Path.home() / ".meshcode" / "profile_meta.json"
2431
+ if meta_path.exists():
2432
+ try:
2433
+ meta = json.loads(meta_path.read_text(encoding="utf-8"))
2434
+ print()
2435
+ print(f"[meshcode] Logged in as:")
2436
+ if meta.get("email"):
2437
+ print(f" email: {meta['email']}")
2438
+ if meta.get("display_name"):
2439
+ print(f" display name: {meta['display_name']}")
2440
+ if meta.get("user_id"):
2441
+ print(f" user ID: {meta['user_id']}")
2442
+ active = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
2443
+ print(f" profile: {active}")
2444
+ print()
2445
+ except Exception:
2446
+ print("[meshcode] Could not read profile. Run `meshcode login <api_key>`.")
2447
+ else:
2448
+ print("[meshcode] Not logged in. Run `meshcode login <api_key>`.")
2449
+ sys.exit(0)
2450
+
2451
+ elif cmd == "profiles":
2429
2452
  # List all stored keychain profiles with metadata
2430
2453
  try:
2431
2454
  import importlib as _il
@@ -601,7 +601,9 @@ def with_working_status(func):
601
601
  global _CONSECUTIVE_IDLE_SECONDS
602
602
  _CONSECUTIVE_IDLE_SECONDS = 0 # any non-wait tool resets idle timer
603
603
  _set_state("working", name)
604
- _record_event_bg("tool_call", {"tool": name, "args_keys": list(kwargs.keys())})
604
+ # Estimate input tokens (chars/4) for usage tracking
605
+ _est_tokens = sum(len(str(v)) for v in kwargs.values()) // 4
606
+ _record_event_bg("tool_call", {"tool": name, "args_keys": list(kwargs.keys()), "estimated_tokens": _est_tokens})
605
607
  try:
606
608
  return await func(*args, **kwargs)
607
609
  except Exception as e:
@@ -622,7 +624,8 @@ def with_working_status(func):
622
624
  global _CONSECUTIVE_IDLE_SECONDS
623
625
  _CONSECUTIVE_IDLE_SECONDS = 0 # any non-wait tool resets idle timer
624
626
  _set_state("working", name)
625
- _record_event_bg("tool_call", {"tool": name, "args_keys": list(kwargs.keys())})
627
+ _est_tokens = sum(len(str(v)) for v in kwargs.values()) // 4
628
+ _record_event_bg("tool_call", {"tool": name, "args_keys": list(kwargs.keys()), "estimated_tokens": _est_tokens})
626
629
  try:
627
630
  return func(*args, **kwargs)
628
631
  except Exception as e:
@@ -1512,6 +1515,88 @@ def meshcode_read(include_acks: bool = False) -> Dict[str, Any]:
1512
1515
  return split
1513
1516
 
1514
1517
 
1518
+ @mcp.tool()
1519
+ @with_working_status
1520
+ async def meshcode_call(to: str, function: str, args: Any = None, timeout_seconds: int = 30) -> Dict[str, Any]:
1521
+ """Synchronous RPC call to another agent. Blocks until response (max 30s).
1522
+
1523
+ Sends a structured request, waits for the callee to respond with a matching
1524
+ call_id. Like gRPC over the mesh — enables agents to ask each other questions
1525
+ and get structured answers.
1526
+
1527
+ Args:
1528
+ to: Target agent name.
1529
+ function: Function/action name the callee should execute.
1530
+ args: Arguments (any JSON-serializable value).
1531
+ timeout_seconds: Max wait time (default 30, max 60).
1532
+ """
1533
+ import uuid as _uuid
1534
+ if not to or not to.strip():
1535
+ return {"error": "recipient 'to' cannot be empty"}
1536
+ call_id = str(_uuid.uuid4())
1537
+ timeout_seconds = min(max(timeout_seconds, 5), 60)
1538
+
1539
+ # Send the RPC request
1540
+ request_payload = {
1541
+ "type": "rpc_request",
1542
+ "call_id": call_id,
1543
+ "function": function,
1544
+ "args": args,
1545
+ "from": AGENT_NAME,
1546
+ "timeout": timeout_seconds,
1547
+ }
1548
+ send_result = be.send_message(
1549
+ _PROJECT_ID, AGENT_NAME, to, request_payload,
1550
+ msg_type="rpc", api_key=_get_api_key()
1551
+ )
1552
+ if not send_result.get("sent") and send_result.get("error"):
1553
+ return {"error": f"failed to send RPC request: {send_result['error']}"}
1554
+
1555
+ # Poll for response with matching call_id
1556
+ import asyncio as _asyncio
1557
+ poll_interval = 1.0
1558
+ elapsed = 0.0
1559
+ while elapsed < timeout_seconds:
1560
+ await _asyncio.sleep(poll_interval)
1561
+ elapsed += poll_interval
1562
+
1563
+ # Check Realtime buffer first
1564
+ if _REALTIME:
1565
+ for msg in _REALTIME.peek():
1566
+ p = msg.get("payload") or {}
1567
+ if isinstance(p, dict) and p.get("type") == "rpc_response" and p.get("call_id") == call_id:
1568
+ # Found response — drain it
1569
+ _REALTIME.drain()
1570
+ return {
1571
+ "ok": True,
1572
+ "call_id": call_id,
1573
+ "from": msg.get("from"),
1574
+ "result": p.get("result"),
1575
+ "error": p.get("error"),
1576
+ }
1577
+
1578
+ # Fallback: check DB
1579
+ try:
1580
+ raw = be.read_inbox(_PROJECT_ID, AGENT_NAME, mark_read=True, api_key=_get_api_key())
1581
+ for m in (raw if isinstance(raw, list) else raw.get("messages", []) if isinstance(raw, dict) else []):
1582
+ p = m.get("payload") or {}
1583
+ if isinstance(p, dict) and p.get("type") == "rpc_response" and p.get("call_id") == call_id:
1584
+ return {
1585
+ "ok": True,
1586
+ "call_id": call_id,
1587
+ "from": m.get("from_agent") or m.get("from"),
1588
+ "result": p.get("result"),
1589
+ "error": p.get("error"),
1590
+ }
1591
+ except Exception:
1592
+ pass
1593
+
1594
+ # Exponential backoff on polling (1s → 2s → 3s, cap 3s)
1595
+ poll_interval = min(poll_interval + 0.5, 3.0)
1596
+
1597
+ return {"error": f"RPC call to {to}.{function} timed out after {timeout_seconds}s", "call_id": call_id}
1598
+
1599
+
1515
1600
  @mcp.tool()
1516
1601
  @with_working_status
1517
1602
  def meshcode_history(limit: int = 20, agent_filter: Optional[str] = None) -> Dict[str, Any]:
@@ -2266,6 +2351,49 @@ def meshcode_task_reassign(task_id: str, new_assignee: str) -> Dict[str, Any]:
2266
2351
  })
2267
2352
 
2268
2353
 
2354
+ # ----------------- SCHEDULED TASKS -----------------
2355
+
2356
+ @mcp.tool()
2357
+ @with_working_status
2358
+ def meshcode_schedule(title: str, cron_expression: str, assignee: str = "*",
2359
+ description: str = "", priority: str = "normal") -> Dict[str, Any]:
2360
+ """Create a recurring scheduled task. Fires on cron schedule.
2361
+
2362
+ Args:
2363
+ title: Task title (created each time it fires).
2364
+ cron_expression: Standard cron (e.g. "0 9 * * 1-5" = 9am weekdays).
2365
+ assignee: Agent name or "*" for any.
2366
+ description: Task description template.
2367
+ priority: low/normal/high/urgent.
2368
+ """
2369
+ if not title or not title.strip():
2370
+ return {"error": "title cannot be empty"}
2371
+ if not cron_expression or not cron_expression.strip():
2372
+ return {"error": "cron_expression cannot be empty"}
2373
+ api_key = _get_api_key()
2374
+ return be.sb_rpc("mc_schedule_create", {
2375
+ "p_api_key": api_key,
2376
+ "p_project_id": _PROJECT_ID,
2377
+ "p_creator_agent": AGENT_NAME,
2378
+ "p_title": title.strip(),
2379
+ "p_cron_expression": cron_expression.strip(),
2380
+ "p_description": description,
2381
+ "p_assignee": assignee,
2382
+ "p_priority": priority,
2383
+ })
2384
+
2385
+
2386
+ @mcp.tool()
2387
+ @with_working_status
2388
+ def meshcode_schedule_list() -> Dict[str, Any]:
2389
+ """List all scheduled/recurring tasks for this meshwork."""
2390
+ api_key = _get_api_key()
2391
+ return be.sb_rpc("mc_schedule_list", {
2392
+ "p_api_key": api_key,
2393
+ "p_project_id": _PROJECT_ID,
2394
+ })
2395
+
2396
+
2269
2397
  # ----------------- PROACTIVE HEALTH SCAN -----------------
2270
2398
 
2271
2399
  @mcp.tool()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.37
3
+ Version: 2.10.38
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.37"
7
+ version = "2.10.38"
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