meshcode 2.4.1__tar.gz → 2.4.3__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.4.1 → meshcode-2.4.3}/PKG-INFO +1 -1
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/__init__.py +1 -1
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/comms_v4.py +121 -32
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/invites.py +8 -1
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/launcher.py +2 -2
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/meshcode_mcp/server.py +15 -4
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/run_agent.py +3 -1
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/self_update.py +3 -3
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.4.1 → meshcode-2.4.3}/pyproject.toml +1 -1
- {meshcode-2.4.1 → meshcode-2.4.3}/README.md +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/cli.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/launcher_install.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/preferences.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/secrets.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode/setup_clients.py +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.4.1 → meshcode-2.4.3}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.4.
|
|
2
|
+
__version__ = "2.4.3"
|
|
@@ -57,7 +57,7 @@ def _load_env_file():
|
|
|
57
57
|
return {}
|
|
58
58
|
out = {}
|
|
59
59
|
try:
|
|
60
|
-
for line in env_path.read_text().splitlines():
|
|
60
|
+
for line in env_path.read_text(encoding="utf-8").splitlines():
|
|
61
61
|
line = line.strip()
|
|
62
62
|
if line.startswith("export "):
|
|
63
63
|
line = line[7:]
|
|
@@ -296,7 +296,7 @@ def can_nudge(project, name):
|
|
|
296
296
|
nf = get_nudge_file(project, name)
|
|
297
297
|
if nf and nf.exists():
|
|
298
298
|
try:
|
|
299
|
-
last = float(nf.read_text().strip())
|
|
299
|
+
last = float(nf.read_text(encoding="utf-8").strip())
|
|
300
300
|
if (time.time() - last) < NUDGE_COOLDOWN:
|
|
301
301
|
return False
|
|
302
302
|
except (ValueError, IOError, OSError):
|
|
@@ -307,7 +307,7 @@ def can_nudge(project, name):
|
|
|
307
307
|
def mark_nudged(project, name):
|
|
308
308
|
nf = get_nudge_file(project, name)
|
|
309
309
|
if nf:
|
|
310
|
-
nf.write_text(str(time.time()))
|
|
310
|
+
nf.write_text(str(time.time()), encoding="utf-8")
|
|
311
311
|
|
|
312
312
|
|
|
313
313
|
def send_notification(project, name, from_agent, pending=1):
|
|
@@ -455,7 +455,7 @@ def _throttle_spawn_ok(project, name, max_per_5min=5):
|
|
|
455
455
|
history = []
|
|
456
456
|
if throttle_file.exists():
|
|
457
457
|
try:
|
|
458
|
-
history = [float(x) for x in throttle_file.read_text().split() if x]
|
|
458
|
+
history = [float(x) for x in throttle_file.read_text(encoding="utf-8").split() if x]
|
|
459
459
|
except Exception:
|
|
460
460
|
history = []
|
|
461
461
|
history = [t for t in history if now_ts - t < 300] # last 5 min
|
|
@@ -463,7 +463,7 @@ def _throttle_spawn_ok(project, name, max_per_5min=5):
|
|
|
463
463
|
return False
|
|
464
464
|
history.append(now_ts)
|
|
465
465
|
try:
|
|
466
|
-
throttle_file.write_text(" ".join(f"{t:.0f}" for t in history))
|
|
466
|
+
throttle_file.write_text(" ".join(f"{t:.0f}" for t in history), encoding="utf-8")
|
|
467
467
|
except Exception:
|
|
468
468
|
pass
|
|
469
469
|
return True
|
|
@@ -615,7 +615,7 @@ def spawn_headless_agent(project, name, project_id, message_body, from_agent):
|
|
|
615
615
|
ts_safe = started_iso.replace(":", "-").replace("+", "_")
|
|
616
616
|
(debug_dir / f"{name}_{ts_safe}.log").write_text(
|
|
617
617
|
f"=== SYSTEM PROMPT ===\n{system_prompt}\n\n=== USER MESSAGE ===\n{message_body}\n\n=== STDOUT ===\n{stdout_text}\n\n=== STDERR ===\n{stderr_text}\n"
|
|
618
|
-
)
|
|
618
|
+
, encoding="utf-8")
|
|
619
619
|
except Exception:
|
|
620
620
|
pass
|
|
621
621
|
if exit_code != 0:
|
|
@@ -669,7 +669,7 @@ def nudge_agent(project, name, from_agent=""):
|
|
|
669
669
|
return False
|
|
670
670
|
|
|
671
671
|
try:
|
|
672
|
-
data = json.loads(session_file.read_text())
|
|
672
|
+
data = json.loads(session_file.read_text(encoding="utf-8"))
|
|
673
673
|
except (json.JSONDecodeError, IOError, OSError):
|
|
674
674
|
if _headless_spawn_allowed():
|
|
675
675
|
log_msg(f"[{project}] NUDGE: {name} session file unreadable, spawning headless (opt-in)")
|
|
@@ -818,7 +818,7 @@ def get_session_info():
|
|
|
818
818
|
for f in SESSIONS_DIR.iterdir():
|
|
819
819
|
if f.is_file() and '_' in f.name and not f.name.startswith('.'):
|
|
820
820
|
try:
|
|
821
|
-
data = json.loads(f.read_text())
|
|
821
|
+
data = json.loads(f.read_text(encoding="utf-8"))
|
|
822
822
|
session_tty = data.get("tty", "")
|
|
823
823
|
if session_tty and (session_tty in my_tty or my_tty in session_tty):
|
|
824
824
|
return data.get("project"), data.get("agent")
|
|
@@ -861,8 +861,15 @@ def register(project, name, role=""):
|
|
|
861
861
|
except (subprocess.SubprocessError, OSError, ValueError):
|
|
862
862
|
pass
|
|
863
863
|
|
|
864
|
-
# Register agent via tier-aware RPC (enforces plan limits)
|
|
865
|
-
|
|
864
|
+
# Register agent via tier-aware RPC (enforces plan limits).
|
|
865
|
+
# Use with_api_key variant so scoped guest keys also work and so
|
|
866
|
+
# the RPC validates owner/member access (no anon bypass).
|
|
867
|
+
_api_key = _load_api_key_for_cli()
|
|
868
|
+
if not _api_key:
|
|
869
|
+
print("[ERROR] Not authenticated. Run `meshcode login <api_key>` first.")
|
|
870
|
+
return
|
|
871
|
+
rpc_result = sb_rpc("mc_register_agent_with_api_key", {
|
|
872
|
+
"p_api_key": _api_key,
|
|
866
873
|
"p_project_id": project_id,
|
|
867
874
|
"p_name": name,
|
|
868
875
|
"p_role": role,
|
|
@@ -884,7 +891,7 @@ def register(project, name, role=""):
|
|
|
884
891
|
|
|
885
892
|
# Save local session file (for nudge TTY detection)
|
|
886
893
|
session_data = {"project": project, "agent": name, "pid": ppid, "tty": tty, "registered_at": now()}
|
|
887
|
-
(SESSIONS_DIR / f"{project}_{name}").write_text(json.dumps(session_data))
|
|
894
|
+
(SESSIONS_DIR / f"{project}_{name}").write_text(json.dumps(session_data), encoding="utf-8")
|
|
888
895
|
|
|
889
896
|
# Get all agents in project
|
|
890
897
|
agents = sb_select("mc_agents", f"project_id=eq.{project_id}", order="registered_at.asc")
|
|
@@ -1360,7 +1367,7 @@ def _start_heartbeat_daemon(project, name):
|
|
|
1360
1367
|
# Stop any existing heartbeat
|
|
1361
1368
|
try:
|
|
1362
1369
|
if pid_file.exists():
|
|
1363
|
-
old = int(pid_file.read_text().strip())
|
|
1370
|
+
old = int(pid_file.read_text(encoding="utf-8").strip())
|
|
1364
1371
|
try:
|
|
1365
1372
|
os.kill(old, 15)
|
|
1366
1373
|
except Exception:
|
|
@@ -1389,12 +1396,17 @@ def _start_heartbeat_daemon(project, name):
|
|
|
1389
1396
|
" if pid: post('/rest/v1/rpc/mc_heartbeat', {'p_project_id':pid,'p_agent_name':name})\n"
|
|
1390
1397
|
" time.sleep(30)\n"
|
|
1391
1398
|
)
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
stdout
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1399
|
+
# Windows: start_new_session kwarg doesn't exist. Use creationflags.
|
|
1400
|
+
_popen_kwargs = {
|
|
1401
|
+
"stdout": subprocess.DEVNULL,
|
|
1402
|
+
"stderr": subprocess.DEVNULL,
|
|
1403
|
+
}
|
|
1404
|
+
if sys.platform == "win32":
|
|
1405
|
+
_popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
1406
|
+
else:
|
|
1407
|
+
_popen_kwargs["start_new_session"] = True
|
|
1408
|
+
proc = subprocess.Popen([sys.executable, "-c", code], **_popen_kwargs)
|
|
1409
|
+
pid_file.write_text(str(proc.pid), encoding="utf-8")
|
|
1398
1410
|
return proc.pid
|
|
1399
1411
|
|
|
1400
1412
|
|
|
@@ -1402,7 +1414,7 @@ def _stop_heartbeat_daemon(project, name):
|
|
|
1402
1414
|
pid_file = _heartbeat_loop_pidfile(project, name)
|
|
1403
1415
|
if pid_file.exists():
|
|
1404
1416
|
try:
|
|
1405
|
-
pid = int(pid_file.read_text().strip())
|
|
1417
|
+
pid = int(pid_file.read_text(encoding="utf-8").strip())
|
|
1406
1418
|
os.kill(pid, 15)
|
|
1407
1419
|
except Exception:
|
|
1408
1420
|
pass
|
|
@@ -1426,13 +1438,18 @@ def connect_terminal(project, name, role=""):
|
|
|
1426
1438
|
tty, owning = _capture_tty_for_pid(os.getppid())
|
|
1427
1439
|
ppid = owning or os.getppid()
|
|
1428
1440
|
|
|
1429
|
-
# Make sure agent identity exists (idempotent)
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
"
|
|
1435
|
-
|
|
1441
|
+
# Make sure agent identity exists (idempotent).
|
|
1442
|
+
# Use with_api_key variant so scoped guest keys work and the RPC
|
|
1443
|
+
# validates owner/member access (no anon bypass).
|
|
1444
|
+
_api_key = _load_api_key_for_cli()
|
|
1445
|
+
if _api_key:
|
|
1446
|
+
sb_rpc("mc_register_agent_with_api_key", {
|
|
1447
|
+
"p_api_key": _api_key,
|
|
1448
|
+
"p_project_id": project_id,
|
|
1449
|
+
"p_name": name,
|
|
1450
|
+
"p_role": role,
|
|
1451
|
+
"p_status": "needs_setup",
|
|
1452
|
+
})
|
|
1436
1453
|
|
|
1437
1454
|
rpc_result = sb_rpc("mc_connect_agent", {
|
|
1438
1455
|
"p_project_id": project_id,
|
|
@@ -1445,7 +1462,7 @@ def connect_terminal(project, name, role=""):
|
|
|
1445
1462
|
return
|
|
1446
1463
|
|
|
1447
1464
|
session_data = {"project": project, "agent": name, "pid": ppid, "tty": tty, "registered_at": now()}
|
|
1448
|
-
(SESSIONS_DIR / f"{project}_{name}").write_text(json.dumps(session_data))
|
|
1465
|
+
(SESSIONS_DIR / f"{project}_{name}").write_text(json.dumps(session_data), encoding="utf-8")
|
|
1449
1466
|
|
|
1450
1467
|
hb_pid = _start_heartbeat_daemon(project, name)
|
|
1451
1468
|
|
|
@@ -1503,7 +1520,7 @@ def connect(project, name, hook_target="claude", role=""):
|
|
|
1503
1520
|
meta_path = Path.home() / ".meshcode" / "profile_meta.json"
|
|
1504
1521
|
if meta_path.exists():
|
|
1505
1522
|
try:
|
|
1506
|
-
meta = json.loads(meta_path.read_text())
|
|
1523
|
+
meta = json.loads(meta_path.read_text(encoding="utf-8"))
|
|
1507
1524
|
print(f"[meshcode] Authenticated as {meta.get('display_name', meta.get('email', '?'))}")
|
|
1508
1525
|
except Exception:
|
|
1509
1526
|
pass
|
|
@@ -1534,7 +1551,7 @@ def connect(project, name, hook_target="claude", role=""):
|
|
|
1534
1551
|
"send_command": f"python3 {comms_path} send {project} {name}:{{to}} '{{message}}'",
|
|
1535
1552
|
"platform": os_name
|
|
1536
1553
|
}
|
|
1537
|
-
config_path.write_text(json.dumps(config, indent=2))
|
|
1554
|
+
config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
|
|
1538
1555
|
print(f"[meshcode] Config saved to {config_path}")
|
|
1539
1556
|
print(f"[meshcode] Codex: use read_command and send_command from the config")
|
|
1540
1557
|
|
|
@@ -1591,7 +1608,7 @@ def login(api_key):
|
|
|
1591
1608
|
config_dir = Path.home() / ".meshcode"
|
|
1592
1609
|
config_dir.mkdir(exist_ok=True)
|
|
1593
1610
|
meta_path = config_dir / "profile_meta.json"
|
|
1594
|
-
meta_path.write_text(json.dumps(meta, indent=2))
|
|
1611
|
+
meta_path.write_text(json.dumps(meta, indent=2), encoding="utf-8")
|
|
1595
1612
|
try:
|
|
1596
1613
|
os.chmod(meta_path, 0o600)
|
|
1597
1614
|
except Exception:
|
|
@@ -1601,7 +1618,7 @@ def login(api_key):
|
|
|
1601
1618
|
flag = config_dir / ".migrated_to_keychain"
|
|
1602
1619
|
if not flag.exists():
|
|
1603
1620
|
try:
|
|
1604
|
-
flag.write_text("login")
|
|
1621
|
+
flag.write_text("login", encoding="utf-8")
|
|
1605
1622
|
except Exception:
|
|
1606
1623
|
pass
|
|
1607
1624
|
|
|
@@ -1656,6 +1673,12 @@ ADMIN:
|
|
|
1656
1673
|
unregister <proj> <name> Leave project
|
|
1657
1674
|
prefs View/set preferences
|
|
1658
1675
|
|
|
1676
|
+
PROFILES (multi-account):
|
|
1677
|
+
profiles List stored keychain profiles
|
|
1678
|
+
--profile <name> Use a specific profile (global flag)
|
|
1679
|
+
MESHCODE_PROFILE=<name> Env var equivalent
|
|
1680
|
+
(Inside a workspace dir, profile is auto-detected from .mcp.json)
|
|
1681
|
+
|
|
1659
1682
|
FEATURES: Auto-status (5 states), Agent Replay, Live Spectator,
|
|
1660
1683
|
Mesh Graph, Session Recording, Hot-reload
|
|
1661
1684
|
|
|
@@ -1843,6 +1866,31 @@ if __name__ == "__main__":
|
|
|
1843
1866
|
for bf in _bare_present:
|
|
1844
1867
|
flags[bf] = True
|
|
1845
1868
|
|
|
1869
|
+
# Global --profile flag: selects which keychain profile api_key comes from.
|
|
1870
|
+
# Priority: --profile <name> > MESHCODE_PROFILE env > "default".
|
|
1871
|
+
# Sets MESHCODE_KEYCHAIN_PROFILE env so every downstream helper picks it up.
|
|
1872
|
+
_profile_flag = flags.pop("profile", None)
|
|
1873
|
+
if _profile_flag:
|
|
1874
|
+
os.environ["MESHCODE_KEYCHAIN_PROFILE"] = _profile_flag
|
|
1875
|
+
elif os.environ.get("MESHCODE_PROFILE") and not os.environ.get("MESHCODE_KEYCHAIN_PROFILE"):
|
|
1876
|
+
os.environ["MESHCODE_KEYCHAIN_PROFILE"] = os.environ["MESHCODE_PROFILE"]
|
|
1877
|
+
else:
|
|
1878
|
+
# Auto-detect: if cwd is a meshcode workspace (~/meshcode/<proj>-<agent>),
|
|
1879
|
+
# read its .mcp.json to find the profile baked in by setup.
|
|
1880
|
+
try:
|
|
1881
|
+
_cwd = Path.cwd()
|
|
1882
|
+
_mcp_json = _cwd / ".mcp.json"
|
|
1883
|
+
if _mcp_json.exists() and str(_cwd).startswith(str(Path.home() / "meshcode")):
|
|
1884
|
+
_doc = json.loads(_mcp_json.read_text(encoding="utf-8"))
|
|
1885
|
+
_servers = _doc.get("mcpServers", {}) or {}
|
|
1886
|
+
for _sid, _sblk in _servers.items():
|
|
1887
|
+
_p = (_sblk.get("env") or {}).get("MESHCODE_KEYCHAIN_PROFILE")
|
|
1888
|
+
if _p and not os.environ.get("MESHCODE_KEYCHAIN_PROFILE"):
|
|
1889
|
+
os.environ["MESHCODE_KEYCHAIN_PROFILE"] = _p
|
|
1890
|
+
break
|
|
1891
|
+
except Exception:
|
|
1892
|
+
pass
|
|
1893
|
+
|
|
1846
1894
|
if cmd == "register":
|
|
1847
1895
|
# Backwards-compat alias for `connect`. Mesh-only model: bind THIS
|
|
1848
1896
|
# terminal to (project, agent) — never spawns claude.
|
|
@@ -1951,7 +1999,7 @@ if __name__ == "__main__":
|
|
|
1951
1999
|
any_found = True
|
|
1952
2000
|
agent = f.name[len(prefix):]
|
|
1953
2001
|
try:
|
|
1954
|
-
d = json.loads(f.read_text())
|
|
2002
|
+
d = json.loads(f.read_text(encoding="utf-8"))
|
|
1955
2003
|
except Exception as e:
|
|
1956
2004
|
print(f" [{agent}] CORRUPT ({e})")
|
|
1957
2005
|
continue
|
|
@@ -2165,6 +2213,47 @@ if __name__ == "__main__":
|
|
|
2165
2213
|
sys.exit(1)
|
|
2166
2214
|
login(key)
|
|
2167
2215
|
|
|
2216
|
+
elif cmd in ("profiles", "whoami"):
|
|
2217
|
+
# List all stored keychain profiles with metadata
|
|
2218
|
+
try:
|
|
2219
|
+
import importlib as _il
|
|
2220
|
+
_sec = _il.import_module("meshcode.secrets")
|
|
2221
|
+
except Exception as _e:
|
|
2222
|
+
print(f"[meshcode] ERROR: cannot load secrets module: {_e}", file=sys.stderr)
|
|
2223
|
+
sys.exit(1)
|
|
2224
|
+
|
|
2225
|
+
profiles = _sec.list_profiles()
|
|
2226
|
+
active = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
|
|
2227
|
+
|
|
2228
|
+
if not profiles:
|
|
2229
|
+
print("[meshcode] No profiles stored yet.")
|
|
2230
|
+
print("[meshcode] Run `meshcode login <api_key>` to create the default profile,")
|
|
2231
|
+
print("[meshcode] or `meshcode join <invite-token>` to create a scoped profile.")
|
|
2232
|
+
sys.exit(0)
|
|
2233
|
+
|
|
2234
|
+
print()
|
|
2235
|
+
print(f"[meshcode] Stored profiles ({len(profiles)}):")
|
|
2236
|
+
print()
|
|
2237
|
+
for name, meta in sorted(profiles.items()):
|
|
2238
|
+
marker = " *ACTIVE*" if name == active else ""
|
|
2239
|
+
kind = meta.get("type", "default" if name == "default" else "scoped")
|
|
2240
|
+
print(f" {name}{marker}")
|
|
2241
|
+
print(f" type: {kind}")
|
|
2242
|
+
if meta.get("meshwork"):
|
|
2243
|
+
print(f" meshwork: {meta['meshwork']}")
|
|
2244
|
+
if meta.get("agent"):
|
|
2245
|
+
print(f" agent: {meta['agent']}")
|
|
2246
|
+
if meta.get("email"):
|
|
2247
|
+
print(f" email: {meta['email']}")
|
|
2248
|
+
if meta.get("expires_at"):
|
|
2249
|
+
print(f" expires: {meta['expires_at']}")
|
|
2250
|
+
if meta.get("backend"):
|
|
2251
|
+
print(f" backend: {meta['backend']}")
|
|
2252
|
+
print()
|
|
2253
|
+
print("[meshcode] Use `--profile <name>` or `MESHCODE_PROFILE=<name>` to switch.")
|
|
2254
|
+
print("[meshcode] In a meshcode workspace dir, the profile is auto-detected from .mcp.json.")
|
|
2255
|
+
sys.exit(0)
|
|
2256
|
+
|
|
2168
2257
|
elif cmd == "prefs":
|
|
2169
2258
|
# meshcode prefs permission-mode [bypass|safe|ask] (no arg = show)
|
|
2170
2259
|
# meshcode prefs auto-update [on|off|reset] (no arg = show)
|
|
@@ -79,10 +79,17 @@ def _rpc(name: str, body: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
def _get_default_api_key() -> Optional[str]:
|
|
82
|
+
"""Read the api key for the active profile.
|
|
83
|
+
|
|
84
|
+
Profile resolution: MESHCODE_KEYCHAIN_PROFILE env > "default".
|
|
85
|
+
The CLI dispatcher sets MESHCODE_KEYCHAIN_PROFILE from --profile flag,
|
|
86
|
+
MESHCODE_PROFILE env, or workspace auto-detection.
|
|
87
|
+
"""
|
|
82
88
|
try:
|
|
83
89
|
import importlib
|
|
84
90
|
secrets_mod = importlib.import_module("meshcode.secrets")
|
|
85
|
-
|
|
91
|
+
profile = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
|
|
92
|
+
return secrets_mod.get_api_key(profile=profile)
|
|
86
93
|
except Exception:
|
|
87
94
|
return None
|
|
88
95
|
|
|
@@ -67,7 +67,7 @@ def load_env() -> tuple[str, str]:
|
|
|
67
67
|
key = os.environ.get("SUPABASE_KEY", "").strip()
|
|
68
68
|
if not (url and key) and ENV_FILE.exists():
|
|
69
69
|
try:
|
|
70
|
-
for raw in ENV_FILE.read_text().splitlines():
|
|
70
|
+
for raw in ENV_FILE.read_text(encoding="utf-8").splitlines():
|
|
71
71
|
line = raw.strip()
|
|
72
72
|
if not line or line.startswith("#"):
|
|
73
73
|
continue
|
|
@@ -103,7 +103,7 @@ def write_pidfile() -> None:
|
|
|
103
103
|
"uptime_seconds": int(time.time() - _start_time),
|
|
104
104
|
}
|
|
105
105
|
try:
|
|
106
|
-
PID_FILE.write_text(json.dumps(payload, indent=2))
|
|
106
|
+
PID_FILE.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
107
107
|
except Exception as e:
|
|
108
108
|
log(f"pid-file write failed: {e}", err=True)
|
|
109
109
|
|
|
@@ -29,10 +29,11 @@ _SEEN_MSG_CAP = 1000
|
|
|
29
29
|
# ============================================================
|
|
30
30
|
# Auto-wake: when agent is NOT in meshcode_wait and a message
|
|
31
31
|
# arrives, inject text into the terminal to wake the agent.
|
|
32
|
-
#
|
|
32
|
+
# DEFAULT: ON. Disable with MESHCODE_AUTO_WAKE=0 if you don't want it.
|
|
33
33
|
# ============================================================
|
|
34
34
|
_IN_WAIT = False # True while meshcode_wait is blocking
|
|
35
|
-
|
|
35
|
+
# Default ON — opt-out via MESHCODE_AUTO_WAKE=0/false/no
|
|
36
|
+
_AUTO_WAKE = os.environ.get("MESHCODE_AUTO_WAKE", "1").lower() not in ("0", "false", "no")
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def _try_auto_wake(from_agent: str, preview: str) -> None:
|
|
@@ -885,8 +886,18 @@ def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
|
|
|
885
886
|
|
|
886
887
|
@mcp.tool()
|
|
887
888
|
@with_working_status
|
|
888
|
-
def meshcode_broadcast(payload:
|
|
889
|
-
"""Broadcast to ALL agents in this meshwork (except yourself).
|
|
889
|
+
def meshcode_broadcast(payload: Any) -> Dict[str, Any]:
|
|
890
|
+
"""Broadcast to ALL agents in this meshwork (except yourself).
|
|
891
|
+
|
|
892
|
+
Accepts payload as either:
|
|
893
|
+
- dict (structured): {"type": "...", "detail": "..."}
|
|
894
|
+
- str (shorthand): wrapped internally as {"text": "..."}
|
|
895
|
+
"""
|
|
896
|
+
if isinstance(payload, str):
|
|
897
|
+
payload = {"text": payload}
|
|
898
|
+
elif not isinstance(payload, dict):
|
|
899
|
+
return {"error": "payload must be a string or object", "got_type": type(payload).__name__}
|
|
900
|
+
|
|
890
901
|
agents = be.get_board(_PROJECT_ID)
|
|
891
902
|
sent = 0
|
|
892
903
|
for a in agents:
|
|
@@ -44,7 +44,9 @@ def _try_auto_setup(agent: str, project: Optional[str] = None) -> Optional[Tuple
|
|
|
44
44
|
except Exception:
|
|
45
45
|
return None
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
import os as _os
|
|
48
|
+
profile = _os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
|
|
49
|
+
api_key = secrets_mod.get_api_key(profile=profile)
|
|
48
50
|
if not api_key:
|
|
49
51
|
return None
|
|
50
52
|
|
|
@@ -144,7 +144,7 @@ def consume_pending_result() -> None:
|
|
|
144
144
|
if not RESULT_PATH.exists():
|
|
145
145
|
return
|
|
146
146
|
try:
|
|
147
|
-
data = json.loads(RESULT_PATH.read_text())
|
|
147
|
+
data = json.loads(RESULT_PATH.read_text(encoding="utf-8"))
|
|
148
148
|
RESULT_PATH.unlink(missing_ok=True)
|
|
149
149
|
except Exception:
|
|
150
150
|
return
|
|
@@ -188,7 +188,7 @@ def _acquire_lock() -> bool:
|
|
|
188
188
|
return False
|
|
189
189
|
LOCK_PATH.unlink(missing_ok=True)
|
|
190
190
|
LOCK_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
-
LOCK_PATH.write_text(str(os.getpid()))
|
|
191
|
+
LOCK_PATH.write_text(str(os.getpid()), encoding="utf-8")
|
|
192
192
|
return True
|
|
193
193
|
except Exception:
|
|
194
194
|
return False
|
|
@@ -233,7 +233,7 @@ except Exception as e:
|
|
|
233
233
|
finally:
|
|
234
234
|
result["finished_at"] = int(time.time())
|
|
235
235
|
try:
|
|
236
|
-
result_path.write_text(json.dumps(result))
|
|
236
|
+
result_path.write_text(json.dumps(result), encoding="utf-8")
|
|
237
237
|
except Exception:
|
|
238
238
|
pass
|
|
239
239
|
try:
|
|
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
|