meshcode 2.5.6__tar.gz → 2.5.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.5.6 → meshcode-2.5.8}/PKG-INFO +1 -1
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/__init__.py +1 -1
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/meshcode_mcp/backend.py +90 -8
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/meshcode_mcp/server.py +124 -8
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.5.6 → meshcode-2.5.8}/pyproject.toml +1 -1
- {meshcode-2.5.6 → meshcode-2.5.8}/README.md +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/cli.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/comms_v4.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/invites.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/launcher.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/launcher_install.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/preferences.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/run_agent.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/secrets.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/self_update.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode/setup_clients.py +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.5.6 → meshcode-2.5.8}/setup.cfg +0 -0
- {meshcode-2.5.6 → meshcode-2.5.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.5.
|
|
2
|
+
__version__ = "2.5.8"
|
|
@@ -244,8 +244,8 @@ def send_message(project_id: str, from_agent: str, to_agent: str, payload: Any,
|
|
|
244
244
|
if encrypted and api_key:
|
|
245
245
|
mesh_key = get_mesh_key(api_key, project_id)
|
|
246
246
|
if mesh_key:
|
|
247
|
-
encrypted_data = encrypt_payload(payload, mesh_key)
|
|
248
|
-
payload = {"_encrypted": encrypted_data}
|
|
247
|
+
encrypted_data = encrypt_payload(payload, mesh_key, aad=project_id)
|
|
248
|
+
payload = {"_encrypted": encrypted_data, "_aad": project_id}
|
|
249
249
|
actual_encrypted = True
|
|
250
250
|
|
|
251
251
|
# Use validated SECURITY DEFINER RPC when api_key is provided
|
|
@@ -302,6 +302,55 @@ def send_message(project_id: str, from_agent: str, to_agent: str, payload: Any,
|
|
|
302
302
|
|
|
303
303
|
|
|
304
304
|
def read_inbox(project_id: str, agent: str, mark_read: bool = True, api_key: Optional[str] = None) -> List[Dict]:
|
|
305
|
+
# Use SECURITY DEFINER RPC when api_key is available (bypasses RLS safely)
|
|
306
|
+
if api_key:
|
|
307
|
+
rpc_result = sb_rpc("mc_read_inbox", {
|
|
308
|
+
"p_api_key": api_key,
|
|
309
|
+
"p_project_id": project_id,
|
|
310
|
+
"p_agent_name": agent,
|
|
311
|
+
"p_mark_read": mark_read,
|
|
312
|
+
})
|
|
313
|
+
if isinstance(rpc_result, dict) and rpc_result.get("ok"):
|
|
314
|
+
messages = rpc_result.get("messages", [])
|
|
315
|
+
if isinstance(messages, list):
|
|
316
|
+
# Auto-decrypt and ACK (mark_read already handled by RPC)
|
|
317
|
+
mesh_key = None
|
|
318
|
+
for m in messages:
|
|
319
|
+
p = m.get("payload")
|
|
320
|
+
if isinstance(p, dict) and "_encrypted" in p:
|
|
321
|
+
if mesh_key is None:
|
|
322
|
+
mesh_key = get_mesh_key(api_key, project_id)
|
|
323
|
+
if mesh_key:
|
|
324
|
+
decrypted = decrypt_payload(p["_encrypted"], mesh_key)
|
|
325
|
+
if decrypted is not None:
|
|
326
|
+
m["payload"] = decrypted
|
|
327
|
+
m["_was_encrypted"] = True
|
|
328
|
+
if mark_read and messages:
|
|
329
|
+
import datetime as _dt
|
|
330
|
+
now = _dt.datetime.now(_dt.timezone.utc)
|
|
331
|
+
def _is_stale_rpc(m):
|
|
332
|
+
ts = m.get("created_at")
|
|
333
|
+
if not ts:
|
|
334
|
+
return True
|
|
335
|
+
try:
|
|
336
|
+
dt = _dt.datetime.fromisoformat(str(ts).replace("Z", "+00:00"))
|
|
337
|
+
return (now - dt).total_seconds() > 60
|
|
338
|
+
except Exception:
|
|
339
|
+
return True
|
|
340
|
+
ack_targets = {
|
|
341
|
+
m.get("from_agent", "")
|
|
342
|
+
for m in messages
|
|
343
|
+
if m.get("type") not in ("ack", "broadcast") and _is_stale_rpc(m)
|
|
344
|
+
}
|
|
345
|
+
for sender in ack_targets:
|
|
346
|
+
if sender:
|
|
347
|
+
send_message(project_id, agent, sender,
|
|
348
|
+
{"text": f"{agent} read your message"},
|
|
349
|
+
msg_type="ack", api_key=api_key)
|
|
350
|
+
return messages
|
|
351
|
+
# If RPC doesn't exist yet, fall through to direct query
|
|
352
|
+
|
|
353
|
+
# Fallback: direct SELECT (tests/legacy — requires anon RLS bypass)
|
|
305
354
|
messages = sb_select(
|
|
306
355
|
"mc_messages",
|
|
307
356
|
f"project_id=eq.{project_id}&to_agent=eq.{quote(agent)}&read=eq.false",
|
|
@@ -365,7 +414,19 @@ def read_inbox(project_id: str, agent: str, mark_read: bool = True, api_key: Opt
|
|
|
365
414
|
return messages
|
|
366
415
|
|
|
367
416
|
|
|
368
|
-
def count_pending(project_id: str, agent: str) -> int:
|
|
417
|
+
def count_pending(project_id: str, agent: str, api_key: Optional[str] = None) -> int:
|
|
418
|
+
# Use SECURITY DEFINER RPC when api_key is available
|
|
419
|
+
if api_key:
|
|
420
|
+
result = sb_rpc("mc_count_pending", {
|
|
421
|
+
"p_api_key": api_key,
|
|
422
|
+
"p_project_id": project_id,
|
|
423
|
+
"p_agent_name": agent,
|
|
424
|
+
})
|
|
425
|
+
if isinstance(result, dict) and result.get("ok"):
|
|
426
|
+
return result.get("count", 0)
|
|
427
|
+
# Fall through to direct query if RPC doesn't exist yet
|
|
428
|
+
|
|
429
|
+
# Fallback: direct SELECT (tests/legacy)
|
|
369
430
|
pending = sb_select(
|
|
370
431
|
"mc_messages",
|
|
371
432
|
f"project_id=eq.{project_id}&to_agent=eq.{quote(agent)}&read=eq.false&type=neq.ack",
|
|
@@ -396,8 +457,15 @@ def get_mesh_key(api_key: str, project_id: str) -> Optional[str]:
|
|
|
396
457
|
return None
|
|
397
458
|
|
|
398
459
|
|
|
399
|
-
def encrypt_payload(payload: Dict, hex_key: str) -> str:
|
|
400
|
-
"""Encrypt a JSON payload using AES-256-GCM
|
|
460
|
+
def encrypt_payload(payload: Dict, hex_key: str, aad: Optional[str] = None) -> str:
|
|
461
|
+
"""Encrypt a JSON payload using AES-256-GCM with optional AAD.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
payload: JSON-serializable dict to encrypt.
|
|
465
|
+
hex_key: Hex-encoded AES-256 key.
|
|
466
|
+
aad: Additional Authenticated Data (e.g. project_id) to bind
|
|
467
|
+
ciphertext to context and prevent replay/swap attacks.
|
|
468
|
+
"""
|
|
401
469
|
import base64
|
|
402
470
|
try:
|
|
403
471
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
@@ -406,14 +474,19 @@ def encrypt_payload(payload: Dict, hex_key: str) -> str:
|
|
|
406
474
|
key = bytes.fromhex(hex_key)
|
|
407
475
|
nonce = os.urandom(12) # 96-bit nonce for GCM
|
|
408
476
|
plaintext = json.dumps(payload).encode("utf-8")
|
|
477
|
+
aad_bytes = aad.encode("utf-8") if aad else None
|
|
409
478
|
aesgcm = AESGCM(key)
|
|
410
|
-
ciphertext = aesgcm.encrypt(nonce, plaintext,
|
|
479
|
+
ciphertext = aesgcm.encrypt(nonce, plaintext, aad_bytes)
|
|
411
480
|
# Format: base64(nonce + ciphertext)
|
|
412
481
|
return base64.b64encode(nonce + ciphertext).decode("ascii")
|
|
413
482
|
|
|
414
483
|
|
|
415
|
-
def decrypt_payload(encrypted_b64: str, hex_key: str) -> Optional[Dict]:
|
|
416
|
-
"""Decrypt an AES-256-GCM encrypted payload. Returns None on failure.
|
|
484
|
+
def decrypt_payload(encrypted_b64: str, hex_key: str, aad: Optional[str] = None) -> Optional[Dict]:
|
|
485
|
+
"""Decrypt an AES-256-GCM encrypted payload. Returns None on failure.
|
|
486
|
+
|
|
487
|
+
Tries with AAD first, falls back to no-AAD for backward compat
|
|
488
|
+
with messages encrypted before AAD was added.
|
|
489
|
+
"""
|
|
417
490
|
import base64
|
|
418
491
|
try:
|
|
419
492
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
@@ -425,6 +498,15 @@ def decrypt_payload(encrypted_b64: str, hex_key: str) -> Optional[Dict]:
|
|
|
425
498
|
nonce = raw[:12]
|
|
426
499
|
ciphertext = raw[12:]
|
|
427
500
|
aesgcm = AESGCM(key)
|
|
501
|
+
aad_bytes = aad.encode("utf-8") if aad else None
|
|
502
|
+
# Try with AAD first
|
|
503
|
+
if aad_bytes:
|
|
504
|
+
try:
|
|
505
|
+
plaintext = aesgcm.decrypt(nonce, ciphertext, aad_bytes)
|
|
506
|
+
return json.loads(plaintext.decode("utf-8"))
|
|
507
|
+
except Exception:
|
|
508
|
+
pass # Fall through to no-AAD for backward compat
|
|
509
|
+
# Try without AAD (legacy messages)
|
|
428
510
|
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
|
|
429
511
|
return json.loads(plaintext.decode("utf-8"))
|
|
430
512
|
except Exception:
|
|
@@ -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,28 @@ 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
|
+
"p_agent_name": AGENT_NAME,
|
|
1152
|
+
})
|
|
1153
|
+
if not isinstance(key_result, dict) or not key_result.get("ok"):
|
|
1154
|
+
err = key_result.get("error", "unknown") if isinstance(key_result, dict) else "RPC failed"
|
|
1155
|
+
return {"error": f"cross-mesh encryption failed: {err}"}
|
|
1156
|
+
tgt_key = key_result["key"]
|
|
1157
|
+
encrypted_data = be.encrypt_payload(payload, tgt_key)
|
|
1158
|
+
payload = {"_encrypted": encrypted_data}
|
|
1159
|
+
|
|
1160
|
+
result = be.sb_rpc("mc_send_cross_mesh", {
|
|
1139
1161
|
"p_api_key": api_key,
|
|
1140
1162
|
"p_from_project": PROJECT_NAME,
|
|
1141
1163
|
"p_from_agent": AGENT_NAME,
|
|
@@ -1144,6 +1166,9 @@ def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
|
|
|
1144
1166
|
"p_payload": payload,
|
|
1145
1167
|
"p_type": "msg",
|
|
1146
1168
|
})
|
|
1169
|
+
if isinstance(result, dict) and result.get("ok") and encrypted:
|
|
1170
|
+
result["encrypted"] = True
|
|
1171
|
+
return result
|
|
1147
1172
|
|
|
1148
1173
|
return be.send_message(_PROJECT_ID, AGENT_NAME, to, payload, msg_type="msg",
|
|
1149
1174
|
parent_msg_id=in_reply_to, sensitive=sensitive,
|
|
@@ -1402,7 +1427,7 @@ def meshcode_check(include_acks: bool = False, since: Optional[str] = None) -> D
|
|
|
1402
1427
|
since: ISO-8601 timestamp. Only return messages newer than this.
|
|
1403
1428
|
Use meshcode_remember("last_seen", ts) to persist across sessions.
|
|
1404
1429
|
"""
|
|
1405
|
-
pending = be.count_pending(_PROJECT_ID, AGENT_NAME)
|
|
1430
|
+
pending = be.count_pending(_PROJECT_ID, AGENT_NAME, api_key=_get_api_key())
|
|
1406
1431
|
# Peek at realtime buffer WITHOUT draining — check is non-destructive
|
|
1407
1432
|
realtime_buffered = _REALTIME.peek() if _REALTIME else []
|
|
1408
1433
|
# Don't mark as seen — meshcode_check is a peek, not a consume
|
|
@@ -1578,6 +1603,89 @@ def meshcode_task_reject(task_id: str, feedback: str = "") -> Dict[str, Any]:
|
|
|
1578
1603
|
})
|
|
1579
1604
|
|
|
1580
1605
|
|
|
1606
|
+
# ----------------- PROACTIVE HEALTH SCAN -----------------
|
|
1607
|
+
|
|
1608
|
+
@mcp.tool()
|
|
1609
|
+
@with_working_status
|
|
1610
|
+
def meshcode_auto_wake() -> Dict[str, Any]:
|
|
1611
|
+
"""Scan meshwork health and suggest maintenance tasks.
|
|
1612
|
+
|
|
1613
|
+
Checks: stale agents, task backlog, empty memories, unlinked meshworks.
|
|
1614
|
+
Returns a list of suggested tasks the commander can create.
|
|
1615
|
+
Call this on session start or periodically to keep the meshwork healthy.
|
|
1616
|
+
"""
|
|
1617
|
+
api_key = _get_api_key()
|
|
1618
|
+
suggestions: List[Dict[str, str]] = []
|
|
1619
|
+
|
|
1620
|
+
# 1. Check for stale agents (heartbeat >10 min, not offline/sleeping)
|
|
1621
|
+
try:
|
|
1622
|
+
agents = be.get_board(_PROJECT_ID)
|
|
1623
|
+
import datetime as _dt
|
|
1624
|
+
now = _dt.datetime.now(_dt.timezone.utc)
|
|
1625
|
+
for a in agents:
|
|
1626
|
+
if a.get("status") in ("offline", "sleeping", "done", "needs_setup"):
|
|
1627
|
+
continue
|
|
1628
|
+
hb = a.get("last_heartbeat")
|
|
1629
|
+
if hb:
|
|
1630
|
+
try:
|
|
1631
|
+
dt = _dt.datetime.fromisoformat(str(hb).replace("Z", "+00:00"))
|
|
1632
|
+
age_min = (now - dt).total_seconds() / 60
|
|
1633
|
+
if age_min > 10:
|
|
1634
|
+
suggestions.append({
|
|
1635
|
+
"title": f"Stale agent: {a['name']} ({int(age_min)}min no heartbeat)",
|
|
1636
|
+
"priority": "high",
|
|
1637
|
+
"assignee": "mesh-commander",
|
|
1638
|
+
"description": f"Agent {a['name']} shows status '{a.get('status')}' but last heartbeat was {int(age_min)} min ago. Force disconnect or investigate.",
|
|
1639
|
+
})
|
|
1640
|
+
except Exception:
|
|
1641
|
+
pass
|
|
1642
|
+
except Exception:
|
|
1643
|
+
pass
|
|
1644
|
+
|
|
1645
|
+
# 2. Check task backlog — open tasks with no assignee
|
|
1646
|
+
try:
|
|
1647
|
+
task_result = be.task_list(api_key, _PROJECT_ID, AGENT_NAME, status_filter="open")
|
|
1648
|
+
if isinstance(task_result, dict) and task_result.get("ok"):
|
|
1649
|
+
open_tasks = task_result.get("tasks", [])
|
|
1650
|
+
unassigned = [t for t in open_tasks if not t.get("assigned_to") or t.get("assigned_to") == "*"]
|
|
1651
|
+
if len(unassigned) > 3:
|
|
1652
|
+
suggestions.append({
|
|
1653
|
+
"title": f"Task backlog: {len(unassigned)} unassigned open tasks",
|
|
1654
|
+
"priority": "normal",
|
|
1655
|
+
"assignee": "mesh-commander",
|
|
1656
|
+
"description": "Review and assign or close stale tasks to keep the board clean.",
|
|
1657
|
+
})
|
|
1658
|
+
except Exception:
|
|
1659
|
+
pass
|
|
1660
|
+
|
|
1661
|
+
# 3. Check for agents with zero memories
|
|
1662
|
+
try:
|
|
1663
|
+
for a in agents:
|
|
1664
|
+
mem_result = be.sb_rpc("mc_recall_all", {
|
|
1665
|
+
"p_api_key": api_key,
|
|
1666
|
+
"p_project_id": _PROJECT_ID,
|
|
1667
|
+
"p_agent_name": a["name"],
|
|
1668
|
+
})
|
|
1669
|
+
if isinstance(mem_result, dict) and mem_result.get("ok"):
|
|
1670
|
+
memories = mem_result.get("memories", [])
|
|
1671
|
+
if len(memories) == 0 and a.get("status") not in ("needs_setup",):
|
|
1672
|
+
suggestions.append({
|
|
1673
|
+
"title": f"Agent {a['name']} has zero memories",
|
|
1674
|
+
"priority": "normal",
|
|
1675
|
+
"assignee": a["name"],
|
|
1676
|
+
"description": "Agent should save learnings, patterns, and preferences to memory for cross-session persistence.",
|
|
1677
|
+
})
|
|
1678
|
+
except Exception:
|
|
1679
|
+
pass
|
|
1680
|
+
|
|
1681
|
+
return {
|
|
1682
|
+
"ok": True,
|
|
1683
|
+
"suggestions": suggestions,
|
|
1684
|
+
"total": len(suggestions),
|
|
1685
|
+
"note": "Use meshcode_task_create to turn suggestions into assigned tasks.",
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
|
|
1581
1689
|
# ----------------- MESH LINK TOOLS -----------------
|
|
1582
1690
|
|
|
1583
1691
|
@mcp.tool()
|
|
@@ -1972,7 +2080,15 @@ def history_resource() -> str:
|
|
|
1972
2080
|
# ============================================================
|
|
1973
2081
|
|
|
1974
2082
|
def _auto_update() -> None:
|
|
1975
|
-
"""
|
|
2083
|
+
"""Upgrade meshcode from PyPI in background — opt-in via MESHCODE_AUTO_UPDATE=1.
|
|
2084
|
+
|
|
2085
|
+
Disabled by default to mitigate supply chain risk: if the PyPI account
|
|
2086
|
+
is compromised, every agent would silently install malicious code.
|
|
2087
|
+
"""
|
|
2088
|
+
if os.environ.get("MESHCODE_AUTO_UPDATE", "0") != "1":
|
|
2089
|
+
log.debug("[meshcode] Auto-update disabled (set MESHCODE_AUTO_UPDATE=1 to enable)")
|
|
2090
|
+
return
|
|
2091
|
+
|
|
1976
2092
|
import threading
|
|
1977
2093
|
|
|
1978
2094
|
def _upgrade():
|
|
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
|