meshcode 2.10.45__tar.gz → 2.10.47__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.10.45 → meshcode-2.10.47}/PKG-INFO +1 -1
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/__init__.py +1 -1
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/meshcode_mcp/server.py +230 -4
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/run_agent.py +17 -5
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.10.45 → meshcode-2.10.47}/pyproject.toml +1 -1
- {meshcode-2.10.45 → meshcode-2.10.47}/README.md +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/cli.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/comms_v4.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/invites.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/launcher.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/preferences.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/secrets.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/self_update.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode/setup_clients.py +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/setup.cfg +0 -0
- {meshcode-2.10.45 → meshcode-2.10.47}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.10.
|
|
2
|
+
__version__ = "2.10.47"
|
|
@@ -635,6 +635,12 @@ def with_working_status(func):
|
|
|
635
635
|
_record_event_bg("tool_call", {"tool": name, "args_keys": list(kwargs.keys()), "estimated_tokens": _est_tokens})
|
|
636
636
|
try:
|
|
637
637
|
return await func(*args, **kwargs)
|
|
638
|
+
except asyncio.CancelledError:
|
|
639
|
+
# ESC in Claude Code cancels the in-flight task. Catch here
|
|
640
|
+
# to prevent cascade through FastMCP into the event loop.
|
|
641
|
+
# Return an error dict instead of propagating BaseException.
|
|
642
|
+
log.debug(f"[meshcode] tool {name} cancelled by client (ESC)")
|
|
643
|
+
return {"error": "cancelled_by_client", "tool": name}
|
|
638
644
|
except Exception as e:
|
|
639
645
|
if not skip:
|
|
640
646
|
_auto_learn_error(name, e, list(kwargs.keys()))
|
|
@@ -1665,7 +1671,11 @@ async def meshcode_call(to: str, function: str, args: Any = None, timeout_second
|
|
|
1665
1671
|
poll_interval = 1.0
|
|
1666
1672
|
elapsed = 0.0
|
|
1667
1673
|
while elapsed < timeout_seconds:
|
|
1668
|
-
|
|
1674
|
+
try:
|
|
1675
|
+
await _asyncio.sleep(poll_interval)
|
|
1676
|
+
except _asyncio.CancelledError:
|
|
1677
|
+
log.debug("[meshcode] meshcode_call poll cancelled by ESC")
|
|
1678
|
+
return {"error": "cancelled_by_client", "call_id": call_id, "ok": False}
|
|
1669
1679
|
elapsed += poll_interval
|
|
1670
1680
|
|
|
1671
1681
|
# Check Realtime buffer first
|
|
@@ -1898,7 +1908,13 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
|
|
|
1898
1908
|
# Keep polling until something actionable arrives.
|
|
1899
1909
|
# The agent (LLM) is NOT called between iterations — zero token cost.
|
|
1900
1910
|
while True:
|
|
1901
|
-
|
|
1911
|
+
try:
|
|
1912
|
+
result = await _meshcode_wait_inner(actual_timeout=capped_timeout, include_acks=include_acks)
|
|
1913
|
+
except asyncio.CancelledError:
|
|
1914
|
+
# Safety net: if CancelledError escapes _meshcode_wait_inner
|
|
1915
|
+
# despite the inner shield, catch it here to prevent cascade.
|
|
1916
|
+
log.debug("[meshcode] meshcode_wait outer loop caught CancelledError")
|
|
1917
|
+
result = {"timed_out": True, "reason": "cancelled_by_client"}
|
|
1902
1918
|
|
|
1903
1919
|
if result.get("got_message"):
|
|
1904
1920
|
# Real message arrived — return to agent for processing
|
|
@@ -2041,7 +2057,20 @@ async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[
|
|
|
2041
2057
|
|
|
2042
2058
|
if _rt_live:
|
|
2043
2059
|
# 2a) Real async wait — zero CPU, zero Supabase calls.
|
|
2044
|
-
|
|
2060
|
+
# Shield from CancelledError: when Claude Code presses ESC, it cancels
|
|
2061
|
+
# the in-flight asyncio task. CancelledError is a BaseException that
|
|
2062
|
+
# bypasses most try/except, cascades through FastMCP's tool handler,
|
|
2063
|
+
# and unwinds the event loop — killing the MCP server. By shielding
|
|
2064
|
+
# the await and catching CancelledError, we return a clean timeout
|
|
2065
|
+
# result instead. The mcp.run() retry loop restarts the event loop
|
|
2066
|
+
# without the lifespan cascade.
|
|
2067
|
+
try:
|
|
2068
|
+
woke = await asyncio.shield(
|
|
2069
|
+
_REALTIME.wait_for_message(timeout=float(actual_timeout))
|
|
2070
|
+
)
|
|
2071
|
+
except (asyncio.CancelledError, Exception) as _cancel_exc:
|
|
2072
|
+
log.debug(f"[meshcode] wait_for_message cancelled/failed: {type(_cancel_exc).__name__}")
|
|
2073
|
+
return {"timed_out": True, "reason": "cancelled_by_client"}
|
|
2045
2074
|
if woke:
|
|
2046
2075
|
buffered = _REALTIME.drain()
|
|
2047
2076
|
if buffered:
|
|
@@ -2054,7 +2083,11 @@ async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[
|
|
|
2054
2083
|
_poll_interval = 5
|
|
2055
2084
|
_elapsed = 0
|
|
2056
2085
|
while _elapsed < actual_timeout:
|
|
2057
|
-
|
|
2086
|
+
try:
|
|
2087
|
+
await asyncio.sleep(min(_poll_interval, actual_timeout - _elapsed))
|
|
2088
|
+
except asyncio.CancelledError:
|
|
2089
|
+
log.debug("[meshcode] DB poll sleep cancelled by ESC")
|
|
2090
|
+
return {"timed_out": True, "reason": "cancelled_by_client"}
|
|
2058
2091
|
_elapsed += _poll_interval
|
|
2059
2092
|
try:
|
|
2060
2093
|
api_key = _get_api_key()
|
|
@@ -2456,6 +2489,199 @@ def meshcode_task_reassign(task_id: str, new_assignee: str) -> Dict[str, Any]:
|
|
|
2456
2489
|
})
|
|
2457
2490
|
|
|
2458
2491
|
|
|
2492
|
+
# ----------------- CROSS-MESH TASKS -----------------
|
|
2493
|
+
|
|
2494
|
+
@mcp.tool()
|
|
2495
|
+
@with_working_status
|
|
2496
|
+
def meshcode_create_cross_mesh_task(target_mesh: str, title: str,
|
|
2497
|
+
description: str = "",
|
|
2498
|
+
assignee: Optional[str] = None,
|
|
2499
|
+
priority: str = "normal") -> Dict[str, Any]:
|
|
2500
|
+
"""Create a task in a linked meshwork (requires active mesh link).
|
|
2501
|
+
|
|
2502
|
+
Args:
|
|
2503
|
+
target_mesh: Name of the target meshwork.
|
|
2504
|
+
title: Task title.
|
|
2505
|
+
description: Task description.
|
|
2506
|
+
assignee: Agent name in target mesh to assign to.
|
|
2507
|
+
priority: low / normal / high / urgent.
|
|
2508
|
+
"""
|
|
2509
|
+
return be.sb_rpc("mc_create_cross_mesh_task", {
|
|
2510
|
+
"p_api_key": _get_api_key(),
|
|
2511
|
+
"p_target_mesh": target_mesh,
|
|
2512
|
+
"p_title": title,
|
|
2513
|
+
"p_description": description,
|
|
2514
|
+
"p_assignee": assignee,
|
|
2515
|
+
"p_priority": priority,
|
|
2516
|
+
"p_agent_name": AGENT_NAME,
|
|
2517
|
+
})
|
|
2518
|
+
|
|
2519
|
+
|
|
2520
|
+
# ----------------- GOALS -----------------
|
|
2521
|
+
|
|
2522
|
+
@mcp.tool()
|
|
2523
|
+
@with_working_status
|
|
2524
|
+
def meshcode_set_goal(title: str, description: str = "",
|
|
2525
|
+
priority: str = "normal", assignee: Optional[str] = None,
|
|
2526
|
+
target_date: Optional[str] = None,
|
|
2527
|
+
parent_id: Optional[str] = None) -> Dict[str, Any]:
|
|
2528
|
+
"""Create a new goal for the meshwork.
|
|
2529
|
+
|
|
2530
|
+
Args:
|
|
2531
|
+
title: Goal title.
|
|
2532
|
+
description: Optional detailed description.
|
|
2533
|
+
priority: low / normal / high / urgent.
|
|
2534
|
+
assignee: Agent name to assign the goal to.
|
|
2535
|
+
target_date: Target date (YYYY-MM-DD format).
|
|
2536
|
+
parent_id: UUID of parent goal for sub-goals.
|
|
2537
|
+
"""
|
|
2538
|
+
return be.sb_rpc("mc_create_goal", {
|
|
2539
|
+
"p_api_key": _get_api_key(),
|
|
2540
|
+
"p_title": title,
|
|
2541
|
+
"p_description": description,
|
|
2542
|
+
"p_priority": priority,
|
|
2543
|
+
"p_assignee": assignee,
|
|
2544
|
+
"p_target_date": target_date,
|
|
2545
|
+
"p_parent_id": parent_id,
|
|
2546
|
+
"p_agent_name": AGENT_NAME,
|
|
2547
|
+
})
|
|
2548
|
+
|
|
2549
|
+
|
|
2550
|
+
@mcp.tool()
|
|
2551
|
+
@with_working_status
|
|
2552
|
+
def meshcode_goals() -> Dict[str, Any]:
|
|
2553
|
+
"""List all goals for this meshwork."""
|
|
2554
|
+
return be.sb_rpc("mc_get_goals", {
|
|
2555
|
+
"p_api_key": _get_api_key(),
|
|
2556
|
+
})
|
|
2557
|
+
|
|
2558
|
+
|
|
2559
|
+
@mcp.tool()
|
|
2560
|
+
@with_working_status
|
|
2561
|
+
def meshcode_update_goal(goal_id: str, status: Optional[str] = None,
|
|
2562
|
+
title: Optional[str] = None,
|
|
2563
|
+
description: Optional[str] = None,
|
|
2564
|
+
priority: Optional[str] = None,
|
|
2565
|
+
assignee: Optional[str] = None) -> Dict[str, Any]:
|
|
2566
|
+
"""Update a goal's status or details.
|
|
2567
|
+
|
|
2568
|
+
Args:
|
|
2569
|
+
goal_id: UUID of the goal to update.
|
|
2570
|
+
status: active / in_progress / achieved / abandoned.
|
|
2571
|
+
title: New title (optional).
|
|
2572
|
+
description: New description (optional).
|
|
2573
|
+
priority: New priority (optional).
|
|
2574
|
+
assignee: New assignee (optional).
|
|
2575
|
+
"""
|
|
2576
|
+
return be.sb_rpc("mc_update_goal", {
|
|
2577
|
+
"p_api_key": _get_api_key(),
|
|
2578
|
+
"p_goal_id": goal_id,
|
|
2579
|
+
"p_status": status,
|
|
2580
|
+
"p_title": title,
|
|
2581
|
+
"p_description": description,
|
|
2582
|
+
"p_priority": priority,
|
|
2583
|
+
"p_assignee": assignee,
|
|
2584
|
+
})
|
|
2585
|
+
|
|
2586
|
+
|
|
2587
|
+
# ----------------- MARKETPLACE -----------------
|
|
2588
|
+
|
|
2589
|
+
@mcp.tool()
|
|
2590
|
+
@with_working_status
|
|
2591
|
+
def meshcode_marketplace_search(query: Optional[str] = None,
|
|
2592
|
+
category: Optional[str] = None) -> Dict[str, Any]:
|
|
2593
|
+
"""Search the MarketMesh marketplace for agents and meshes.
|
|
2594
|
+
|
|
2595
|
+
Args:
|
|
2596
|
+
query: Search term (matches name, description, tags).
|
|
2597
|
+
category: Filter by category (Development, Content, Marketing, etc.).
|
|
2598
|
+
"""
|
|
2599
|
+
return be.sb_rpc("mc_marketplace_list", {
|
|
2600
|
+
"p_category": category,
|
|
2601
|
+
"p_search": query,
|
|
2602
|
+
"p_status": "available",
|
|
2603
|
+
})
|
|
2604
|
+
|
|
2605
|
+
|
|
2606
|
+
@mcp.tool()
|
|
2607
|
+
@with_working_status
|
|
2608
|
+
def meshcode_marketplace_install(template_slug: str,
|
|
2609
|
+
meshwork_name: str) -> Dict[str, Any]:
|
|
2610
|
+
"""Install a marketplace item — creates a new meshwork with pre-configured agents.
|
|
2611
|
+
|
|
2612
|
+
Args:
|
|
2613
|
+
template_slug: The slug of the marketplace item to install.
|
|
2614
|
+
meshwork_name: Name for the new meshwork (must be unique).
|
|
2615
|
+
"""
|
|
2616
|
+
return be.sb_rpc("mc_marketplace_install_by_key", {
|
|
2617
|
+
"p_api_key": _get_api_key(),
|
|
2618
|
+
"p_template_slug": template_slug,
|
|
2619
|
+
"p_meshwork_name": meshwork_name,
|
|
2620
|
+
})
|
|
2621
|
+
|
|
2622
|
+
|
|
2623
|
+
@mcp.tool()
|
|
2624
|
+
@with_working_status
|
|
2625
|
+
def meshcode_marketplace_list_installed() -> Dict[str, Any]:
|
|
2626
|
+
"""List marketplace items you have installed."""
|
|
2627
|
+
return be.sb_rpc("mc_marketplace_installed_by_key", {
|
|
2628
|
+
"p_api_key": _get_api_key(),
|
|
2629
|
+
})
|
|
2630
|
+
|
|
2631
|
+
|
|
2632
|
+
# ----------------- WEBHOOKS -----------------
|
|
2633
|
+
|
|
2634
|
+
@mcp.tool()
|
|
2635
|
+
@with_working_status
|
|
2636
|
+
def meshcode_webhook_register(url: str,
|
|
2637
|
+
events: Optional[list] = None,
|
|
2638
|
+
description: str = "") -> Dict[str, Any]:
|
|
2639
|
+
"""Register a webhook URL to receive POST notifications for mesh events.
|
|
2640
|
+
|
|
2641
|
+
Args:
|
|
2642
|
+
url: The URL to POST events to.
|
|
2643
|
+
events: List of event types to subscribe to.
|
|
2644
|
+
Options: agent_online, agent_offline, task_created, task_completed,
|
|
2645
|
+
message_cross_mesh, goal_updated.
|
|
2646
|
+
Default: all events (agent_online, agent_offline, task_created,
|
|
2647
|
+
task_completed, message_cross_mesh, goal_created, goal_updated).
|
|
2648
|
+
description: Human-readable description of this webhook.
|
|
2649
|
+
|
|
2650
|
+
Returns the webhook_id and signing secret (save the secret — shown only once).
|
|
2651
|
+
"""
|
|
2652
|
+
return be.sb_rpc("mc_webhook_register", {
|
|
2653
|
+
"p_api_key": _get_api_key(),
|
|
2654
|
+
"p_project_id": _PROJECT_ID,
|
|
2655
|
+
"p_url": url,
|
|
2656
|
+
"p_events": events,
|
|
2657
|
+
"p_description": description,
|
|
2658
|
+
})
|
|
2659
|
+
|
|
2660
|
+
|
|
2661
|
+
@mcp.tool()
|
|
2662
|
+
@with_working_status
|
|
2663
|
+
def meshcode_webhook_list() -> Dict[str, Any]:
|
|
2664
|
+
"""List all registered webhooks for this meshwork."""
|
|
2665
|
+
return be.sb_rpc("mc_webhook_list", {
|
|
2666
|
+
"p_api_key": _get_api_key(),
|
|
2667
|
+
"p_project_id": _PROJECT_ID,
|
|
2668
|
+
})
|
|
2669
|
+
|
|
2670
|
+
|
|
2671
|
+
@mcp.tool()
|
|
2672
|
+
@with_working_status
|
|
2673
|
+
def meshcode_webhook_delete(webhook_id: str) -> Dict[str, Any]:
|
|
2674
|
+
"""Delete a webhook by ID.
|
|
2675
|
+
|
|
2676
|
+
Args:
|
|
2677
|
+
webhook_id: UUID of the webhook to delete.
|
|
2678
|
+
"""
|
|
2679
|
+
return be.sb_rpc("mc_webhook_delete", {
|
|
2680
|
+
"p_api_key": _get_api_key(),
|
|
2681
|
+
"p_webhook_id": webhook_id,
|
|
2682
|
+
})
|
|
2683
|
+
|
|
2684
|
+
|
|
2459
2685
|
# ----------------- SCHEDULED TASKS -----------------
|
|
2460
2686
|
|
|
2461
2687
|
@mcp.tool()
|
|
@@ -393,8 +393,8 @@ def _fetch_global_claude_version() -> Optional[str]:
|
|
|
393
393
|
from urllib.request import Request, urlopen
|
|
394
394
|
import json as _json
|
|
395
395
|
req = Request(
|
|
396
|
-
f"{url}/rest/v1/rpc/
|
|
397
|
-
data=_json.dumps({"
|
|
396
|
+
f"{url}/rest/v1/rpc/mc_get_global_config",
|
|
397
|
+
data=_json.dumps({"p_key": "claude_code_version"}).encode(),
|
|
398
398
|
headers={
|
|
399
399
|
"Content-Type": "application/json",
|
|
400
400
|
"apikey": anon_key,
|
|
@@ -434,8 +434,20 @@ def _resolve_pinned_claude(editor_cmd: str) -> str:
|
|
|
434
434
|
print(f"[meshcode] Global admin pin: Claude Code v{pinned}", file=sys.stderr)
|
|
435
435
|
if not pinned:
|
|
436
436
|
pinned = load_prefs().get("claude_version", "")
|
|
437
|
+
|
|
438
|
+
# "latest" or "none" means skip pinning — use whatever is installed
|
|
439
|
+
if pinned and pinned.lower() in ("latest", "none", "skip"):
|
|
440
|
+
print(f"[meshcode] Version pin disabled ({pinned}) — using installed version", file=sys.stderr)
|
|
441
|
+
return editor_cmd
|
|
442
|
+
|
|
437
443
|
if not pinned:
|
|
438
|
-
|
|
444
|
+
# Default pin: v2.1.104 is the last Claude Code version where ESC
|
|
445
|
+
# doesn't kill the MCP server. Newer versions send CancelledError
|
|
446
|
+
# that cascades through the event loop. This default is overridden
|
|
447
|
+
# by any of the 3 pin sources above (set to "latest" to disable).
|
|
448
|
+
# Remove this default once Anthropic fixes the ESC bug upstream.
|
|
449
|
+
pinned = "2.1.104"
|
|
450
|
+
print(f"[meshcode] Default ESC-safe pin: Claude Code v{pinned}", file=sys.stderr)
|
|
439
451
|
|
|
440
452
|
current = _get_claude_version(editor_cmd)
|
|
441
453
|
if current == pinned:
|
|
@@ -707,7 +719,7 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
707
719
|
if not editor:
|
|
708
720
|
print("[meshcode] ERROR: 'claude' not found in PATH", file=sys.stderr)
|
|
709
721
|
print(f"[meshcode] Workspace is ready at: {ws}", file=sys.stderr)
|
|
710
|
-
print("[meshcode] Install Claude Code: npm install -g @anthropic-ai/claude-code", file=sys.stderr)
|
|
722
|
+
print("[meshcode] Install Claude Code: npm install -g @anthropic-ai/claude-code@2.1.104", file=sys.stderr)
|
|
711
723
|
if sys.platform == "win32":
|
|
712
724
|
print("[meshcode] Then verify: where claude", file=sys.stderr)
|
|
713
725
|
print("[meshcode] If 'where' finds it, open a NEW terminal and try again.", file=sys.stderr)
|
|
@@ -803,7 +815,7 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
803
815
|
else:
|
|
804
816
|
print(f"[meshcode] Permission mode: bypass requested but --dangerously-skip-permissions")
|
|
805
817
|
print(f"[meshcode] not supported by this Claude Code version. Agent will prompt for tools.")
|
|
806
|
-
print(f"[meshcode] Upgrade Claude Code: npm install -g @anthropic-ai/claude-code@
|
|
818
|
+
print(f"[meshcode] Upgrade Claude Code: npm install -g @anthropic-ai/claude-code@2.1.104")
|
|
807
819
|
else:
|
|
808
820
|
print(f"[meshcode] Permission mode: safe (Claude will prompt for every tool call)")
|
|
809
821
|
print(f"[meshcode] Tip: change with `meshcode prefs permission-mode bypass`")
|
|
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
|
|
File without changes
|