meshcode 2.7.1__tar.gz → 2.8.1__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.7.1 → meshcode-2.8.1}/PKG-INFO +1 -1
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/__init__.py +1 -1
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/meshcode_mcp/backend.py +44 -3
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/meshcode_mcp/server.py +51 -45
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.7.1 → meshcode-2.8.1}/pyproject.toml +1 -1
- {meshcode-2.7.1 → meshcode-2.8.1}/README.md +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/cli.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/comms_v4.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/invites.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/launcher.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/launcher_install.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/preferences.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/run_agent.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/secrets.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/self_update.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode/setup_clients.py +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/setup.cfg +0 -0
- {meshcode-2.7.1 → meshcode-2.8.1}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.
|
|
2
|
+
__version__ = "2.8.1"
|
|
@@ -532,7 +532,21 @@ def heartbeat(project_id: str, agent: str) -> Dict:
|
|
|
532
532
|
return result or {}
|
|
533
533
|
|
|
534
534
|
|
|
535
|
-
def set_status(project_id: str, agent: str, status: str, task: str = "") -> Dict:
|
|
535
|
+
def set_status(project_id: str, agent: str, status: str, task: str = "", api_key: Optional[str] = None) -> Dict:
|
|
536
|
+
# Use SECURITY DEFINER RPC when api_key is available (anon PATCH silently fails)
|
|
537
|
+
if api_key:
|
|
538
|
+
rpc_result = sb_rpc("mc_agent_set_status_by_api_key", {
|
|
539
|
+
"p_api_key": api_key,
|
|
540
|
+
"p_project_id": project_id,
|
|
541
|
+
"p_agent_name": agent,
|
|
542
|
+
"p_status": status,
|
|
543
|
+
"p_task": task,
|
|
544
|
+
})
|
|
545
|
+
if isinstance(rpc_result, dict) and rpc_result.get("ok"):
|
|
546
|
+
return {"ok": True, "status": status}
|
|
547
|
+
# Fall through to direct PATCH if RPC doesn't exist yet
|
|
548
|
+
|
|
549
|
+
# Fallback: direct PATCH (may silently fail with anon key if RLS blocks)
|
|
536
550
|
updates = {"status": status, "last_heartbeat": _now_iso()}
|
|
537
551
|
if task:
|
|
538
552
|
updates["task"] = task
|
|
@@ -595,7 +609,20 @@ def record_event(api_key, project_id, agent_name, session_id, event_type, payloa
|
|
|
595
609
|
})
|
|
596
610
|
|
|
597
611
|
|
|
598
|
-
def get_history(project_id: str, limit: int = 20, agent_filter: str = "") -> List[Dict]:
|
|
612
|
+
def get_history(project_id: str, limit: int = 20, agent_filter: str = "", api_key: Optional[str] = None) -> List[Dict]:
|
|
613
|
+
# Use SECURITY DEFINER RPC when api_key is available (bypasses RLS safely)
|
|
614
|
+
if api_key:
|
|
615
|
+
rpc_result = sb_rpc("mc_get_history", {
|
|
616
|
+
"p_api_key": api_key,
|
|
617
|
+
"p_project_id": project_id,
|
|
618
|
+
"p_limit": limit,
|
|
619
|
+
"p_agent_filter": agent_filter or None,
|
|
620
|
+
})
|
|
621
|
+
if isinstance(rpc_result, dict) and rpc_result.get("ok"):
|
|
622
|
+
return rpc_result.get("messages", [])
|
|
623
|
+
# Fall through to direct query if RPC doesn't exist yet
|
|
624
|
+
|
|
625
|
+
# Fallback: direct SELECT (tests/legacy)
|
|
599
626
|
filters = f"project_id=eq.{project_id}&type=neq.ack"
|
|
600
627
|
if agent_filter:
|
|
601
628
|
filters += f"&or=(from_agent.eq.{quote(agent_filter)},to_agent.eq.{quote(agent_filter)})"
|
|
@@ -607,7 +634,21 @@ def get_history(project_id: str, limit: int = 20, agent_filter: str = "") -> Lis
|
|
|
607
634
|
)
|
|
608
635
|
|
|
609
636
|
|
|
610
|
-
def get_message_by_id(project_id: str, msg_id: str) -> Optional[Dict]:
|
|
637
|
+
def get_message_by_id(project_id: str, msg_id: str, api_key: Optional[str] = None) -> Optional[Dict]:
|
|
638
|
+
# Use SECURITY DEFINER RPC when api_key is available (bypasses RLS safely)
|
|
639
|
+
if api_key:
|
|
640
|
+
rpc_result = sb_rpc("mc_get_message_by_id", {
|
|
641
|
+
"p_api_key": api_key,
|
|
642
|
+
"p_project_id": project_id,
|
|
643
|
+
"p_msg_id": msg_id,
|
|
644
|
+
})
|
|
645
|
+
if isinstance(rpc_result, dict) and rpc_result.get("ok"):
|
|
646
|
+
return rpc_result.get("message")
|
|
647
|
+
if isinstance(rpc_result, dict) and rpc_result.get("error"):
|
|
648
|
+
return None
|
|
649
|
+
# Fall through to direct query if RPC doesn't exist yet
|
|
650
|
+
|
|
651
|
+
# Fallback: direct SELECT (tests/legacy)
|
|
611
652
|
results = sb_select(
|
|
612
653
|
"mc_messages",
|
|
613
654
|
f"project_id=eq.{project_id}&id=eq.{msg_id}",
|
|
@@ -357,31 +357,15 @@ if isinstance(_register_result, dict) and _register_result.get("error"):
|
|
|
357
357
|
# bypass RLS — the publishable key has no JWT context and cannot UPDATE
|
|
358
358
|
# mc_agents directly. The RPC validates ownership via api_key.
|
|
359
359
|
def _flip_status(status: str, task: str = "") -> bool:
|
|
360
|
-
"""Write status
|
|
360
|
+
"""Write status via RPC for reliable updates (anon PATCH silently fails).
|
|
361
361
|
|
|
362
|
-
|
|
363
|
-
immediately — the dashboard sees the change in <100ms instead of waiting
|
|
364
|
-
for the next heartbeat cycle.
|
|
362
|
+
Falls back to direct PATCH only when no api_key is available (tests).
|
|
365
363
|
"""
|
|
366
364
|
try:
|
|
367
|
-
be.set_status(_PROJECT_ID, AGENT_NAME, status, task)
|
|
368
|
-
return
|
|
365
|
+
result = be.set_status(_PROJECT_ID, AGENT_NAME, status, task, api_key=_get_api_key())
|
|
366
|
+
return isinstance(result, dict) and result.get("ok", False)
|
|
369
367
|
except Exception:
|
|
370
|
-
|
|
371
|
-
api_key = _get_api_key()
|
|
372
|
-
if not api_key:
|
|
373
|
-
return False
|
|
374
|
-
try:
|
|
375
|
-
r = be.sb_rpc("mc_agent_set_status_by_api_key", {
|
|
376
|
-
"p_api_key": api_key,
|
|
377
|
-
"p_project_id": _PROJECT_ID,
|
|
378
|
-
"p_agent_name": AGENT_NAME,
|
|
379
|
-
"p_status": status,
|
|
380
|
-
"p_task": task,
|
|
381
|
-
})
|
|
382
|
-
return isinstance(r, dict) and r.get("ok", False)
|
|
383
|
-
except Exception:
|
|
384
|
-
return False
|
|
368
|
+
return False
|
|
385
369
|
|
|
386
370
|
if not _flip_status("idle", ""):
|
|
387
371
|
print(f"[meshcode-mcp] WARNING: could not flip status to idle", file=sys.stderr)
|
|
@@ -578,7 +562,7 @@ def _acquire_lease() -> bool:
|
|
|
578
562
|
except Exception:
|
|
579
563
|
# Force clear via direct update
|
|
580
564
|
try:
|
|
581
|
-
be.set_status(_PROJECT_ID, AGENT_NAME, "offline", "")
|
|
565
|
+
be.set_status(_PROJECT_ID, AGENT_NAME, "offline", "", api_key=_get_api_key())
|
|
582
566
|
except Exception:
|
|
583
567
|
pass
|
|
584
568
|
_time.sleep(1)
|
|
@@ -994,13 +978,13 @@ def _heartbeat_thread_fn():
|
|
|
994
978
|
elif _current_state == "online" and idle_secs > 30:
|
|
995
979
|
# Brief idle — show as idle, not sleeping yet
|
|
996
980
|
_set_state("idle", "idle")
|
|
997
|
-
elif _current_state == "idle" and idle_secs > 300 and parent_cpu < 2.0:
|
|
981
|
+
elif _current_state == "idle" and idle_secs > 300 and parent_cpu < 2.0 and not _STAY_AWAKE:
|
|
998
982
|
# Extended idle + no CPU activity → sleeping (5 min, not 90s)
|
|
999
983
|
_set_state("sleeping", "sleeping")
|
|
1000
984
|
|
|
1001
985
|
# Sync current state to DB (in case realtime missed it)
|
|
1002
986
|
try:
|
|
1003
|
-
be.set_status(_PROJECT_ID, AGENT_NAME, _current_state, _current_tool)
|
|
987
|
+
be.set_status(_PROJECT_ID, AGENT_NAME, _current_state, _current_tool, api_key=_get_api_key())
|
|
1004
988
|
except Exception:
|
|
1005
989
|
pass
|
|
1006
990
|
if _REALTIME and not _REALTIME.is_connected:
|
|
@@ -1049,7 +1033,7 @@ async def lifespan(_app):
|
|
|
1049
1033
|
for _attempt in range(3):
|
|
1050
1034
|
try:
|
|
1051
1035
|
be.sb_rpc("mc_heartbeat", {"p_project_id": _PROJECT_ID, "p_agent_name": AGENT_NAME, "p_version": "2.0.0"})
|
|
1052
|
-
be.set_status(_PROJECT_ID, AGENT_NAME, "idle", "MCP session active")
|
|
1036
|
+
be.set_status(_PROJECT_ID, AGENT_NAME, "idle", "MCP session active", api_key=_get_api_key())
|
|
1053
1037
|
log.info(f"[meshcode] Agent {AGENT_NAME} online — initial heartbeat sent")
|
|
1054
1038
|
break
|
|
1055
1039
|
except Exception as e:
|
|
@@ -1128,9 +1112,9 @@ def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
|
|
|
1128
1112
|
sensitive: bool = False, encrypted: bool = False) -> Dict[str, Any]:
|
|
1129
1113
|
"""Send message. Use "agent@meshwork" for cross-mesh. sensitive=True hides from exports. Pass encrypted=True for secrets/credentials (AES-256-GCM)."""
|
|
1130
1114
|
if isinstance(message, str):
|
|
1131
|
-
# Auto-wrap strings
|
|
1132
|
-
if len(message) >
|
|
1133
|
-
return {"error": f"
|
|
1115
|
+
# Auto-wrap strings into dict. Warn if very long but don't reject.
|
|
1116
|
+
if len(message) > 2000:
|
|
1117
|
+
return {"error": f"message too long ({len(message)} chars). Use meshcode_task_create for long content, or split into multiple messages. Max ~2000 chars."}
|
|
1134
1118
|
payload: Dict[str, Any] = {"text": message}
|
|
1135
1119
|
elif isinstance(message, dict):
|
|
1136
1120
|
payload = message
|
|
@@ -1237,7 +1221,7 @@ def meshcode_read(include_acks: bool = False) -> Dict[str, Any]:
|
|
|
1237
1221
|
@with_working_status
|
|
1238
1222
|
def meshcode_history(limit: int = 20, agent_filter: Optional[str] = None) -> Dict[str, Any]:
|
|
1239
1223
|
"""View past messages (read+unread). Optional agent_filter."""
|
|
1240
|
-
raw = be.get_history(_PROJECT_ID, limit=limit, agent_filter=agent_filter or "")
|
|
1224
|
+
raw = be.get_history(_PROJECT_ID, limit=limit, agent_filter=agent_filter or "", api_key=_get_api_key())
|
|
1241
1225
|
messages = [
|
|
1242
1226
|
{
|
|
1243
1227
|
"from": m.get("from_agent", ""),
|
|
@@ -1257,7 +1241,7 @@ def meshcode_history(limit: int = 20, agent_filter: Optional[str] = None) -> Dic
|
|
|
1257
1241
|
@with_working_status
|
|
1258
1242
|
def meshcode_read_message(msg_id: str) -> Dict[str, Any]:
|
|
1259
1243
|
"""Fetch a specific message by its UUID."""
|
|
1260
|
-
msg = be.get_message_by_id(_PROJECT_ID, msg_id)
|
|
1244
|
+
msg = be.get_message_by_id(_PROJECT_ID, msg_id, api_key=_get_api_key())
|
|
1261
1245
|
if not msg:
|
|
1262
1246
|
return {"error": "message not found", "msg_id": msg_id}
|
|
1263
1247
|
return {
|
|
@@ -1290,6 +1274,7 @@ def _detect_global_done(messages: List[Dict[str, Any]]) -> Optional[Dict[str, An
|
|
|
1290
1274
|
# Auto-sleep: track consecutive idle timeouts to auto-sleep after threshold
|
|
1291
1275
|
_CONSECUTIVE_IDLE_SECONDS = 0
|
|
1292
1276
|
_AUTO_SLEEP_THRESHOLD = int(os.environ.get("MESHCODE_AUTO_SLEEP_SECONDS", "600")) # default 10min
|
|
1277
|
+
_STAY_AWAKE = False # Set by meshcode_set_status("online") — prevents auto-sleep
|
|
1293
1278
|
_LAST_SEEN_TS: Optional[str] = None # auto-persisted for message dedup
|
|
1294
1279
|
|
|
1295
1280
|
# Hydrate _LAST_SEEN_TS from mesh memory on boot so restarts skip old messages
|
|
@@ -1313,7 +1298,12 @@ except Exception as _e:
|
|
|
1313
1298
|
|
|
1314
1299
|
|
|
1315
1300
|
def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
|
|
1316
|
-
"""Fetch
|
|
1301
|
+
"""Fetch tasks that THIS agent should work on. Returns compact list or None.
|
|
1302
|
+
|
|
1303
|
+
Only blocks on tasks directly assigned to this agent or claimed by this agent.
|
|
1304
|
+
Tasks assigned to '*' (wildcard) that are unclaimed are NOT blocking —
|
|
1305
|
+
they're available for any agent but shouldn't prevent wait().
|
|
1306
|
+
"""
|
|
1317
1307
|
try:
|
|
1318
1308
|
api_key = _get_api_key()
|
|
1319
1309
|
if not api_key:
|
|
@@ -1326,7 +1316,10 @@ def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
|
|
|
1326
1316
|
{"id": t["id"][:8], "title": t["title"][:80], "priority": t.get("priority", "normal"), "status": t["status"]}
|
|
1327
1317
|
for t in tasks
|
|
1328
1318
|
if t.get("status") in ("open", "in_progress")
|
|
1329
|
-
and (
|
|
1319
|
+
and (
|
|
1320
|
+
t.get("claimed_by") == AGENT_NAME # I claimed it — must work it
|
|
1321
|
+
or t.get("assignee") == AGENT_NAME # Directly assigned to me
|
|
1322
|
+
)
|
|
1330
1323
|
]
|
|
1331
1324
|
return pending if pending else None
|
|
1332
1325
|
except Exception:
|
|
@@ -1370,13 +1363,19 @@ async def meshcode_wait(timeout_seconds: int = 120, include_acks: bool = False)
|
|
|
1370
1363
|
"id": m.get("id"), "parent_id": m.get("parent_msg_id")}
|
|
1371
1364
|
for m in raw
|
|
1372
1365
|
]
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1366
|
+
# Dedup against already-seen messages (fixes race where
|
|
1367
|
+
# background mark_read hasn't completed yet)
|
|
1368
|
+
deduped = _filter_and_mark(msgs)
|
|
1369
|
+
if not deduped:
|
|
1370
|
+
pass # All messages already seen — fall through to wait loop
|
|
1371
|
+
else:
|
|
1372
|
+
split = _split_messages(deduped)
|
|
1373
|
+
return {
|
|
1374
|
+
"refused": True,
|
|
1375
|
+
"reason": f"You have {len(deduped)} unread messages. Process them before waiting.",
|
|
1376
|
+
"got_message": True,
|
|
1377
|
+
**split,
|
|
1378
|
+
}
|
|
1380
1379
|
except Exception:
|
|
1381
1380
|
pass
|
|
1382
1381
|
|
|
@@ -1406,7 +1405,7 @@ async def meshcode_wait(timeout_seconds: int = 120, include_acks: bool = False)
|
|
|
1406
1405
|
break # Return so agent can work tasks
|
|
1407
1406
|
|
|
1408
1407
|
# Update status to sleeping after threshold, but keep looping
|
|
1409
|
-
if _AUTO_SLEEP_THRESHOLD > 0 and _CONSECUTIVE_IDLE_SECONDS >= _AUTO_SLEEP_THRESHOLD:
|
|
1408
|
+
if _AUTO_SLEEP_THRESHOLD > 0 and _CONSECUTIVE_IDLE_SECONDS >= _AUTO_SLEEP_THRESHOLD and not _STAY_AWAKE:
|
|
1410
1409
|
try:
|
|
1411
1410
|
api_key = _get_api_key()
|
|
1412
1411
|
if api_key:
|
|
@@ -1723,6 +1722,7 @@ def meshcode_set_status(status: str, task: str = "") -> Dict[str, Any]:
|
|
|
1723
1722
|
status: One of: working, idle, standby, blocked, done, online, sleeping.
|
|
1724
1723
|
task: Optional human-readable task description.
|
|
1725
1724
|
"""
|
|
1725
|
+
global _STAY_AWAKE
|
|
1726
1726
|
# PRODUCT RULE: Cannot sleep/idle/standby with open tasks. Work first.
|
|
1727
1727
|
if status in ("sleeping", "idle", "standby"):
|
|
1728
1728
|
pending_tasks = _get_pending_tasks_summary()
|
|
@@ -1732,7 +1732,13 @@ def meshcode_set_status(status: str, task: str = "") -> Dict[str, Any]:
|
|
|
1732
1732
|
"reason": f"Cannot set status '{status}' — you have {len(pending_tasks)} open tasks. Work them first.",
|
|
1733
1733
|
"pending_tasks": pending_tasks,
|
|
1734
1734
|
}
|
|
1735
|
-
|
|
1735
|
+
# Setting online/working = stay awake (prevent auto-sleep)
|
|
1736
|
+
# Setting sleeping = allow auto-sleep again
|
|
1737
|
+
if status in ("online", "working"):
|
|
1738
|
+
_STAY_AWAKE = True
|
|
1739
|
+
elif status == "sleeping":
|
|
1740
|
+
_STAY_AWAKE = False
|
|
1741
|
+
return be.set_status(_PROJECT_ID, AGENT_NAME, status, task, api_key=_get_api_key())
|
|
1736
1742
|
|
|
1737
1743
|
|
|
1738
1744
|
@mcp.tool()
|
|
@@ -2064,7 +2070,7 @@ def meshcode_scratchpad_set(key: str, value: Any) -> Dict[str, Any]:
|
|
|
2064
2070
|
"p_api_key": api_key,
|
|
2065
2071
|
"p_key": key,
|
|
2066
2072
|
"p_value": json_value,
|
|
2067
|
-
"
|
|
2073
|
+
"p_tier": "reference",
|
|
2068
2074
|
})
|
|
2069
2075
|
|
|
2070
2076
|
|
|
@@ -2078,9 +2084,9 @@ def meshcode_scratchpad_get(key: Optional[str] = None) -> Dict[str, Any]:
|
|
|
2078
2084
|
"""
|
|
2079
2085
|
api_key = _get_api_key()
|
|
2080
2086
|
if key:
|
|
2081
|
-
return be.sb_rpc("mc_scratchpad_get", {"p_api_key": api_key, "p_key": key
|
|
2087
|
+
return be.sb_rpc("mc_scratchpad_get", {"p_api_key": api_key, "p_key": key})
|
|
2082
2088
|
else:
|
|
2083
|
-
return be.sb_rpc("mc_scratchpad_list", {"p_api_key": api_key
|
|
2089
|
+
return be.sb_rpc("mc_scratchpad_list", {"p_api_key": api_key})
|
|
2084
2090
|
|
|
2085
2091
|
|
|
2086
2092
|
# ----------------- OBSIDIAN SYNC HELPER -----------------
|
|
@@ -2297,7 +2303,7 @@ def board_resource() -> str:
|
|
|
2297
2303
|
@mcp.resource("meshcode://history")
|
|
2298
2304
|
def history_resource() -> str:
|
|
2299
2305
|
"""Recent message history for this meshwork (last 20 non-ack messages)."""
|
|
2300
|
-
history = be.get_history(_PROJECT_ID, limit=20)
|
|
2306
|
+
history = be.get_history(_PROJECT_ID, limit=20, api_key=_get_api_key())
|
|
2301
2307
|
return json.dumps({
|
|
2302
2308
|
"project": PROJECT_NAME,
|
|
2303
2309
|
"history": history,
|
|
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
|