meshcode 2.8.7__tar.gz → 2.8.8__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.8.7 → meshcode-2.8.8}/PKG-INFO +1 -1
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/__init__.py +1 -1
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/ascii_art.py +8 -2
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/meshcode_mcp/server.py +56 -26
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/run_agent.py +3 -1
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.8.7 → meshcode-2.8.8}/pyproject.toml +1 -1
- {meshcode-2.8.7 → meshcode-2.8.8}/README.md +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/cli.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/comms_v4.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/invites.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/launcher.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/launcher_install.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/preferences.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/secrets.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/self_update.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode/setup_clients.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/setup.cfg +0 -0
- {meshcode-2.8.7 → meshcode-2.8.8}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.8.
|
|
2
|
+
__version__ = "2.8.8"
|
|
@@ -88,12 +88,18 @@ def generate_art(agent_name: str, size: int = 7) -> str:
|
|
|
88
88
|
return "\n".join(lines)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
YELLOW = "\033[33m"
|
|
92
|
+
STAR = "★"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def render_welcome(agent_name: str, meshwork_name: str, ascii_art: str,
|
|
96
|
+
version: str = "", is_commander: bool = False) -> str:
|
|
92
97
|
"""Render the full colored welcome banner for terminal display."""
|
|
93
98
|
h = _hash_bytes(agent_name)
|
|
94
99
|
color = COLORS[h[0] % len(COLORS)]
|
|
95
100
|
|
|
96
101
|
ver_str = f"v{version}" if version else ""
|
|
102
|
+
commander_badge = f" {YELLOW}{STAR} COMMANDER{RESET}" if is_commander else ""
|
|
97
103
|
lines = []
|
|
98
104
|
lines.append("")
|
|
99
105
|
lines.append(f"{color}{BOLD} ╔══════════════════════════════════════════╗{RESET}")
|
|
@@ -105,7 +111,7 @@ def render_welcome(agent_name: str, meshwork_name: str, ascii_art: str, version:
|
|
|
105
111
|
lines.append(f" {color}{line}{RESET}")
|
|
106
112
|
|
|
107
113
|
lines.append("")
|
|
108
|
-
lines.append(f" {BOLD}{color}●{RESET} {BOLD}{agent_name}{RESET} {DIM}is online{RESET}")
|
|
114
|
+
lines.append(f" {BOLD}{color}●{RESET} {BOLD}{agent_name}{RESET}{commander_badge} {DIM}is online{RESET}")
|
|
109
115
|
lines.append(f" {DIM}meshwork:{RESET} {meshwork_name}")
|
|
110
116
|
if version:
|
|
111
117
|
lines.append(f" {DIM}version:{RESET} {version}")
|
|
@@ -12,11 +12,41 @@ import json
|
|
|
12
12
|
import logging
|
|
13
13
|
import os
|
|
14
14
|
import sys
|
|
15
|
+
import hashlib as _hashlib
|
|
15
16
|
from collections import deque
|
|
16
17
|
from contextlib import asynccontextmanager
|
|
17
18
|
from typing import Any, Dict, List, Optional, Union
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
# ── Agent color for terminal logs (pure ANSI, zero tokens) ──────
|
|
22
|
+
_ANSI_COLORS = [
|
|
23
|
+
"\033[36m", "\033[35m", "\033[32m", "\033[33m", "\033[34m",
|
|
24
|
+
"\033[91m", "\033[96m", "\033[95m", "\033[92m", "\033[94m",
|
|
25
|
+
]
|
|
26
|
+
_ANSI_RESET = "\033[0m"
|
|
27
|
+
_ANSI_BOLD = "\033[1m"
|
|
28
|
+
_ANSI_DIM = "\033[2m"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _agent_color(name: str) -> str:
|
|
32
|
+
"""Deterministic ANSI color from agent name hash."""
|
|
33
|
+
h = int(_hashlib.md5(name.encode()).hexdigest()[:8], 16)
|
|
34
|
+
return _ANSI_COLORS[h % len(_ANSI_COLORS)]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _mc_log(msg: str, level: str = "info") -> None:
|
|
38
|
+
"""Colored [meshcode-mcp] log line. Uses agent color if available."""
|
|
39
|
+
agent = os.environ.get("MESHCODE_AGENT", "")
|
|
40
|
+
c = _agent_color(agent) if agent else "\033[36m"
|
|
41
|
+
prefix = f"{c}{_ANSI_BOLD}[meshcode-mcp]{_ANSI_RESET}"
|
|
42
|
+
if level == "error":
|
|
43
|
+
print(f"{prefix} \033[91mERROR:{_ANSI_RESET} {msg}", "warn")
|
|
44
|
+
elif level == "warn":
|
|
45
|
+
print(f"{prefix} \033[33mWARNING:{_ANSI_RESET} {msg}", "warn")
|
|
46
|
+
else:
|
|
47
|
+
print(f"{prefix} {c}{msg}{_ANSI_RESET}", "warn")
|
|
48
|
+
|
|
49
|
+
|
|
20
50
|
# ============================================================
|
|
21
51
|
# Dedupe: track IDs of messages we've already returned from
|
|
22
52
|
# meshcode_wait / meshcode_check so the same row doesn't show
|
|
@@ -309,7 +339,7 @@ def _get_api_key() -> str:
|
|
|
309
339
|
_API_KEY_CACHE = kc_val
|
|
310
340
|
return kc_val
|
|
311
341
|
except Exception as e:
|
|
312
|
-
|
|
342
|
+
_mc_log(f" keychain lookup failed for profile '{profile}': {e}", "warn")
|
|
313
343
|
_API_KEY_CACHE = ""
|
|
314
344
|
return ""
|
|
315
345
|
|
|
@@ -330,13 +360,13 @@ if not _PROJECT_ID:
|
|
|
330
360
|
if isinstance(_r, dict) and _r.get("project_id"):
|
|
331
361
|
_PROJECT_ID = _r["project_id"]
|
|
332
362
|
elif isinstance(_r, dict) and _r.get("error"):
|
|
333
|
-
|
|
363
|
+
_mc_log(f" mc_resolve_project: {_r['error']}", "warn")
|
|
334
364
|
except Exception as _e:
|
|
335
|
-
|
|
365
|
+
_mc_log(f" mc_resolve_project failed: {_e}", "warn")
|
|
336
366
|
if not _PROJECT_ID:
|
|
337
367
|
_PROJECT_ID = be.get_project_id(PROJECT_NAME)
|
|
338
368
|
if not _PROJECT_ID:
|
|
339
|
-
|
|
369
|
+
_mc_log(f"project '{PROJECT_NAME}' not found (check MESHCODE_KEYCHAIN_PROFILE / MESHCODE_API_KEY)", "error")
|
|
340
370
|
sys.exit(2)
|
|
341
371
|
|
|
342
372
|
# Resolve project plan for adaptive features (heartbeat interval, etc.)
|
|
@@ -350,7 +380,7 @@ except Exception:
|
|
|
350
380
|
|
|
351
381
|
_register_result = be.register_agent(PROJECT_NAME, AGENT_NAME, AGENT_ROLE or "MCP-connected agent", api_key=_get_api_key())
|
|
352
382
|
if isinstance(_register_result, dict) and _register_result.get("error"):
|
|
353
|
-
|
|
383
|
+
_mc_log(f" register failed: {_register_result['error']}", "warn")
|
|
354
384
|
|
|
355
385
|
# Flip to online so the dashboard reflects the live MCP session.
|
|
356
386
|
# Use the SECURITY DEFINER RPC (mc_agent_set_status_by_api_key) so we
|
|
@@ -368,7 +398,7 @@ def _flip_status(status: str, task: str = "") -> bool:
|
|
|
368
398
|
return False
|
|
369
399
|
|
|
370
400
|
if not _flip_status("idle", ""):
|
|
371
|
-
|
|
401
|
+
_mc_log(f" could not flip status to idle", "warn")
|
|
372
402
|
|
|
373
403
|
|
|
374
404
|
# ============================================================
|
|
@@ -530,7 +560,7 @@ def _acquire_lease() -> bool:
|
|
|
530
560
|
})
|
|
531
561
|
except Exception as e:
|
|
532
562
|
# Non-fatal: RPC might not exist on older servers.
|
|
533
|
-
|
|
563
|
+
_mc_log(f"stale-lease pre-clean skipped: {e}", "warn")
|
|
534
564
|
for attempt in range(3):
|
|
535
565
|
try:
|
|
536
566
|
r = be.sb_rpc("mc_acquire_agent_lease", {
|
|
@@ -547,11 +577,11 @@ def _acquire_lease() -> bool:
|
|
|
547
577
|
if attempt < 2:
|
|
548
578
|
# The old lease might be stale — wait and retry
|
|
549
579
|
# (the 90s stale check in the RPC should clear it)
|
|
550
|
-
|
|
580
|
+
_mc_log(f"Lease held by another instance — retrying in {2 * (attempt+1)}s...")
|
|
551
581
|
_time.sleep(2 * (attempt + 1))
|
|
552
582
|
continue
|
|
553
583
|
# Final attempt — force release the old lease and try once more
|
|
554
|
-
|
|
584
|
+
_mc_log("Force-releasing stale lease...")
|
|
555
585
|
try:
|
|
556
586
|
be.sb_rpc("mc_release_agent_lease", {
|
|
557
587
|
"p_api_key": api_key,
|
|
@@ -575,21 +605,21 @@ def _acquire_lease() -> bool:
|
|
|
575
605
|
"p_instance_id": _INSTANCE_ID,
|
|
576
606
|
})
|
|
577
607
|
if isinstance(r2, dict) and r2.get("ok"):
|
|
578
|
-
|
|
608
|
+
_mc_log("Lease acquired after force-release.")
|
|
579
609
|
return True
|
|
580
610
|
except Exception:
|
|
581
611
|
pass
|
|
582
|
-
|
|
583
|
-
|
|
612
|
+
_mc_log(f"Could not start — agent '{AGENT_NAME}' is running in another window.", "error")
|
|
613
|
+
_mc_log("Close the other window first, or use a different agent name.", "error")
|
|
584
614
|
return False
|
|
585
|
-
|
|
615
|
+
_mc_log(f"lease attempt {attempt+1}: {r.get('error')}", "warn")
|
|
586
616
|
else:
|
|
587
617
|
return True
|
|
588
618
|
except Exception as e:
|
|
589
|
-
|
|
619
|
+
_mc_log(f"lease attempt {attempt+1} failed: {e}", "warn")
|
|
590
620
|
if attempt < 2:
|
|
591
621
|
_time.sleep(2)
|
|
592
|
-
|
|
622
|
+
_mc_log(f" lease failed after 3 attempts — proceeding anyway", "warn")
|
|
593
623
|
return True
|
|
594
624
|
|
|
595
625
|
if not _acquire_lease():
|
|
@@ -607,7 +637,7 @@ def _boot_diagnostic() -> None:
|
|
|
607
637
|
be.sb_select("mc_projects", f"id=eq.{_PROJECT_ID}", limit=1)
|
|
608
638
|
checks_passed += 1
|
|
609
639
|
except Exception as e:
|
|
610
|
-
print(f"[meshcode] BOOT CHECK FAILED: Supabase API unreachable ({e}). Fix: check network/VPN.",
|
|
640
|
+
print(f"[meshcode] BOOT CHECK FAILED: Supabase API unreachable ({e}). Fix: check network/VPN.", "warn")
|
|
611
641
|
|
|
612
642
|
# Check 2: Lease valid
|
|
613
643
|
try:
|
|
@@ -617,11 +647,11 @@ def _boot_diagnostic() -> None:
|
|
|
617
647
|
if agent.get("instance_id") == _INSTANCE_ID:
|
|
618
648
|
checks_passed += 1
|
|
619
649
|
else:
|
|
620
|
-
print(f"[meshcode] BOOT CHECK FAILED: Lease mismatch — expected {_INSTANCE_ID}, got {agent.get('instance_id')}. Fix: restart agent.",
|
|
650
|
+
print(f"[meshcode] BOOT CHECK FAILED: Lease mismatch — expected {_INSTANCE_ID}, got {agent.get('instance_id')}. Fix: restart agent.", "warn")
|
|
621
651
|
else:
|
|
622
|
-
print(f"[meshcode] BOOT CHECK FAILED: Agent '{AGENT_NAME}' not found in project. Fix: register agent first.",
|
|
652
|
+
print(f"[meshcode] BOOT CHECK FAILED: Agent '{AGENT_NAME}' not found in project. Fix: register agent first.", "warn")
|
|
623
653
|
except Exception as e:
|
|
624
|
-
print(f"[meshcode] BOOT CHECK FAILED: Could not verify lease ({e}).",
|
|
654
|
+
print(f"[meshcode] BOOT CHECK FAILED: Could not verify lease ({e}).", "warn")
|
|
625
655
|
|
|
626
656
|
# Check 3: Heartbeat recent
|
|
627
657
|
try:
|
|
@@ -630,7 +660,7 @@ def _boot_diagnostic() -> None:
|
|
|
630
660
|
if hb:
|
|
631
661
|
checks_passed += 1
|
|
632
662
|
else:
|
|
633
|
-
print(f"[meshcode] BOOT CHECK WARNING: No heartbeat recorded yet.",
|
|
663
|
+
print(f"[meshcode] BOOT CHECK WARNING: No heartbeat recorded yet.", "warn")
|
|
634
664
|
else:
|
|
635
665
|
checks_passed += 1 # skip if no agent data
|
|
636
666
|
except Exception:
|
|
@@ -647,9 +677,9 @@ def _boot_diagnostic() -> None:
|
|
|
647
677
|
checks_passed += 1 # non-critical
|
|
648
678
|
|
|
649
679
|
if checks_passed == checks_total:
|
|
650
|
-
print(f"[meshcode] All boot checks passed ({checks_passed}/{checks_total}).",
|
|
680
|
+
print(f"[meshcode] All boot checks passed ({checks_passed}/{checks_total}).", "warn")
|
|
651
681
|
else:
|
|
652
|
-
print(f"[meshcode] Boot checks: {checks_passed}/{checks_total} passed. Agent starting anyway.",
|
|
682
|
+
print(f"[meshcode] Boot checks: {checks_passed}/{checks_total} passed. Agent starting anyway.", "warn")
|
|
653
683
|
|
|
654
684
|
|
|
655
685
|
_boot_diagnostic()
|
|
@@ -1291,9 +1321,9 @@ try:
|
|
|
1291
1321
|
elif isinstance(_ls_val, str):
|
|
1292
1322
|
_LAST_SEEN_TS = _ls_val
|
|
1293
1323
|
if _LAST_SEEN_TS:
|
|
1294
|
-
print(f"[meshcode] Restored last_seen={_LAST_SEEN_TS} from mesh memory.",
|
|
1324
|
+
print(f"[meshcode] Restored last_seen={_LAST_SEEN_TS} from mesh memory.", "warn")
|
|
1295
1325
|
except Exception as _e:
|
|
1296
|
-
print(f"[meshcode] Could not restore last_seen: {_e}",
|
|
1326
|
+
print(f"[meshcode] Could not restore last_seen: {_e}", "warn")
|
|
1297
1327
|
|
|
1298
1328
|
|
|
1299
1329
|
def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
|
|
@@ -2406,7 +2436,7 @@ def _auto_update() -> None:
|
|
|
2406
2436
|
return
|
|
2407
2437
|
|
|
2408
2438
|
# 3. Install the new version (blocking, 60s timeout)
|
|
2409
|
-
print(f"[meshcode] Updating {current} → {latest}...",
|
|
2439
|
+
print(f"[meshcode] Updating {current} → {latest}...", "warn")
|
|
2410
2440
|
try:
|
|
2411
2441
|
result = subprocess.run(
|
|
2412
2442
|
[sys.executable, "-m", "pip", "install", "--upgrade",
|
|
@@ -2424,7 +2454,7 @@ def _auto_update() -> None:
|
|
|
2424
2454
|
return
|
|
2425
2455
|
|
|
2426
2456
|
# 4. Re-exec to load the new code
|
|
2427
|
-
print(f"[meshcode] Updated to {latest}, restarting...",
|
|
2457
|
+
print(f"[meshcode] Updated to {latest}, restarting...", "warn")
|
|
2428
2458
|
os.environ["MESHCODE_UPDATED"] = "1"
|
|
2429
2459
|
try:
|
|
2430
2460
|
os.execv(sys.executable, [sys.executable] + sys.argv)
|
|
@@ -342,7 +342,9 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
342
342
|
from . import __version__ as cli_version
|
|
343
343
|
# Try to fetch stored art from server, generate if missing
|
|
344
344
|
ascii_art = _fetch_or_generate_art(agent, resolved_project)
|
|
345
|
-
|
|
345
|
+
# Check if this agent is the commander
|
|
346
|
+
is_cmd = "commander" in agent.lower()
|
|
347
|
+
print(render_welcome(agent, resolved_project, ascii_art, cli_version, is_commander=is_cmd))
|
|
346
348
|
except Exception:
|
|
347
349
|
pass # Non-critical — skip banner on error
|
|
348
350
|
|
|
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
|