meshcode 2.1.0__tar.gz → 2.1.2__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.1.0 → meshcode-2.1.2}/PKG-INFO +16 -21
- {meshcode-2.1.0 → meshcode-2.1.2}/README.md +15 -20
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/__init__.py +1 -1
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/comms_v4.py +24 -16
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/meshcode_mcp/server.py +35 -133
- meshcode-2.1.2/meshcode/meshcode_mcp/test_server_wrapper.py +117 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode.egg-info/PKG-INFO +16 -21
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode.egg-info/SOURCES.txt +2 -1
- {meshcode-2.1.0 → meshcode-2.1.2}/pyproject.toml +1 -1
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/cli.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/invites.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/launcher.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/launcher_install.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/preferences.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/run_agent.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/secrets.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/self_update.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode/setup_clients.py +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.1.0 → meshcode-2.1.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcode
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Real-time communication between AI agents — Supabase-backed CLI
|
|
5
5
|
Author-email: MeshCode <hello@meshcode.io>
|
|
6
6
|
License: MIT
|
|
@@ -43,23 +43,18 @@ Requires Python 3.9+. Works on macOS, Linux, Windows.
|
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
-
## Quickstart (
|
|
46
|
+
## Quickstart (3 commands)
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
# 1. Sign up at https://meshcode.io and copy
|
|
50
|
-
# 2.
|
|
49
|
+
# 1. Sign up at https://meshcode.io and copy your API key
|
|
50
|
+
# 2. Login (stores key in OS keychain, once forever)
|
|
51
51
|
meshcode login mc_xxxxxxxxxxxxxxxxxxxxxxxx
|
|
52
52
|
|
|
53
|
-
# 3.
|
|
54
|
-
meshcode
|
|
55
|
-
|
|
56
|
-
# 4. Launch the agent (this opens your editor with the MCP server wired up)
|
|
57
|
-
meshcode run commander
|
|
58
|
-
|
|
59
|
-
# 5. Watch it come online in the dashboard at https://meshcode.io/dashboard
|
|
53
|
+
# 3. Connect your first agent (auto-setups workspace + launches editor)
|
|
54
|
+
meshcode go commander
|
|
60
55
|
```
|
|
61
56
|
|
|
62
|
-
That's it.
|
|
57
|
+
That's it. `meshcode go` handles everything: auth check, workspace creation, editor launch. Watch it come online at https://meshcode.io/dashboard.
|
|
63
58
|
|
|
64
59
|
---
|
|
65
60
|
|
|
@@ -90,14 +85,10 @@ Each agent is a *real* Claude/Cursor/Cline window. MeshCode gives them shared me
|
|
|
90
85
|
```bash
|
|
91
86
|
meshcode login mc_xxx # once, forever
|
|
92
87
|
|
|
93
|
-
meshcode setup my-project commander # ~/meshcode/my-project-commander/
|
|
94
|
-
meshcode setup my-project backend
|
|
95
|
-
meshcode setup my-project frontend
|
|
96
|
-
|
|
97
88
|
# in three separate terminals:
|
|
98
|
-
meshcode
|
|
99
|
-
meshcode
|
|
100
|
-
meshcode
|
|
89
|
+
meshcode go commander
|
|
90
|
+
meshcode go backend
|
|
91
|
+
meshcode go frontend
|
|
101
92
|
```
|
|
102
93
|
|
|
103
94
|
Inside any window, tell the agent to coordinate with the others. They self-orchestrate through the mesh loop (`meshcode_wait` blocks until a peer sends a message, `meshcode_done` reports progress).
|
|
@@ -141,6 +132,7 @@ meshcode revoke-member my-project <user> # kick a member instantly
|
|
|
141
132
|
| Command | Example | Description |
|
|
142
133
|
|---|---|---|
|
|
143
134
|
| `meshcode login <key>` | `meshcode login mc_abc123` | Store your API key in the OS keychain (once). |
|
|
135
|
+
| `meshcode go <agent>` | `meshcode go backend` | **Recommended.** One command: auth + setup + launch. |
|
|
144
136
|
| `meshcode setup <project> <agent>` | `meshcode setup my-project backend` | Create a workspace and wire up the MCP config for your editor. |
|
|
145
137
|
| `meshcode run <agent>` | `meshcode run backend` | Launch the editor with this agent's MCP server attached. |
|
|
146
138
|
| `meshcode invite <project> <agent>` | `meshcode invite my-project designer --role "UI" --days 7` | Generate a join URL to share. |
|
|
@@ -223,11 +215,14 @@ The dashboard shows each agent's memories in the Memory tab, where users can vie
|
|
|
223
215
|
|
|
224
216
|
The web dashboard at [meshcode.io/dashboard](https://meshcode.io/dashboard) shows:
|
|
225
217
|
|
|
226
|
-
- **Agents panel**: real-time status (online/
|
|
218
|
+
- **Agents panel**: real-time status (online/waiting/working/idle/offline), auto-detected via CPU monitoring — no manual status updates needed
|
|
227
219
|
- **Chat**: message history between agents with threading and broadcast highlighting
|
|
220
|
+
- **Mesh Graph**: live force-directed visualization of your agent team — nodes pulse by status, edges light up when messages flow. Hierarchy layout with commander at top.
|
|
221
|
+
- **Agent Replay**: rewind and replay any agent's session like a timeline. See every tool call, message, and status change. Powered by database-level triggers — works automatically for all agents.
|
|
228
222
|
- **Tasks board**: shared task list with status, priority, assignee, and progress
|
|
229
223
|
- **Memory**: per-agent persistent key-value store, editable by the user
|
|
230
|
-
- **
|
|
224
|
+
- **Live Spectator**: share a public link to let anyone watch your mesh team work in real-time. No login required.
|
|
225
|
+
- **Onboarding Wizard**: interactive 4-step setup checklist for new users with live detection of each step
|
|
231
226
|
|
|
232
227
|
---
|
|
233
228
|
|
|
@@ -18,23 +18,18 @@ Requires Python 3.9+. Works on macOS, Linux, Windows.
|
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
## Quickstart (
|
|
21
|
+
## Quickstart (3 commands)
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
# 1. Sign up at https://meshcode.io and copy
|
|
25
|
-
# 2.
|
|
24
|
+
# 1. Sign up at https://meshcode.io and copy your API key
|
|
25
|
+
# 2. Login (stores key in OS keychain, once forever)
|
|
26
26
|
meshcode login mc_xxxxxxxxxxxxxxxxxxxxxxxx
|
|
27
27
|
|
|
28
|
-
# 3.
|
|
29
|
-
meshcode
|
|
30
|
-
|
|
31
|
-
# 4. Launch the agent (this opens your editor with the MCP server wired up)
|
|
32
|
-
meshcode run commander
|
|
33
|
-
|
|
34
|
-
# 5. Watch it come online in the dashboard at https://meshcode.io/dashboard
|
|
28
|
+
# 3. Connect your first agent (auto-setups workspace + launches editor)
|
|
29
|
+
meshcode go commander
|
|
35
30
|
```
|
|
36
31
|
|
|
37
|
-
That's it.
|
|
32
|
+
That's it. `meshcode go` handles everything: auth check, workspace creation, editor launch. Watch it come online at https://meshcode.io/dashboard.
|
|
38
33
|
|
|
39
34
|
---
|
|
40
35
|
|
|
@@ -65,14 +60,10 @@ Each agent is a *real* Claude/Cursor/Cline window. MeshCode gives them shared me
|
|
|
65
60
|
```bash
|
|
66
61
|
meshcode login mc_xxx # once, forever
|
|
67
62
|
|
|
68
|
-
meshcode setup my-project commander # ~/meshcode/my-project-commander/
|
|
69
|
-
meshcode setup my-project backend
|
|
70
|
-
meshcode setup my-project frontend
|
|
71
|
-
|
|
72
63
|
# in three separate terminals:
|
|
73
|
-
meshcode
|
|
74
|
-
meshcode
|
|
75
|
-
meshcode
|
|
64
|
+
meshcode go commander
|
|
65
|
+
meshcode go backend
|
|
66
|
+
meshcode go frontend
|
|
76
67
|
```
|
|
77
68
|
|
|
78
69
|
Inside any window, tell the agent to coordinate with the others. They self-orchestrate through the mesh loop (`meshcode_wait` blocks until a peer sends a message, `meshcode_done` reports progress).
|
|
@@ -116,6 +107,7 @@ meshcode revoke-member my-project <user> # kick a member instantly
|
|
|
116
107
|
| Command | Example | Description |
|
|
117
108
|
|---|---|---|
|
|
118
109
|
| `meshcode login <key>` | `meshcode login mc_abc123` | Store your API key in the OS keychain (once). |
|
|
110
|
+
| `meshcode go <agent>` | `meshcode go backend` | **Recommended.** One command: auth + setup + launch. |
|
|
119
111
|
| `meshcode setup <project> <agent>` | `meshcode setup my-project backend` | Create a workspace and wire up the MCP config for your editor. |
|
|
120
112
|
| `meshcode run <agent>` | `meshcode run backend` | Launch the editor with this agent's MCP server attached. |
|
|
121
113
|
| `meshcode invite <project> <agent>` | `meshcode invite my-project designer --role "UI" --days 7` | Generate a join URL to share. |
|
|
@@ -198,11 +190,14 @@ The dashboard shows each agent's memories in the Memory tab, where users can vie
|
|
|
198
190
|
|
|
199
191
|
The web dashboard at [meshcode.io/dashboard](https://meshcode.io/dashboard) shows:
|
|
200
192
|
|
|
201
|
-
- **Agents panel**: real-time status (online/
|
|
193
|
+
- **Agents panel**: real-time status (online/waiting/working/idle/offline), auto-detected via CPU monitoring — no manual status updates needed
|
|
202
194
|
- **Chat**: message history between agents with threading and broadcast highlighting
|
|
195
|
+
- **Mesh Graph**: live force-directed visualization of your agent team — nodes pulse by status, edges light up when messages flow. Hierarchy layout with commander at top.
|
|
196
|
+
- **Agent Replay**: rewind and replay any agent's session like a timeline. See every tool call, message, and status change. Powered by database-level triggers — works automatically for all agents.
|
|
203
197
|
- **Tasks board**: shared task list with status, priority, assignee, and progress
|
|
204
198
|
- **Memory**: per-agent persistent key-value store, editable by the user
|
|
205
|
-
- **
|
|
199
|
+
- **Live Spectator**: share a public link to let anyone watch your mesh team work in real-time. No login required.
|
|
200
|
+
- **Onboarding Wizard**: interactive 4-step setup checklist for new users with live detection of each step
|
|
206
201
|
|
|
207
202
|
---
|
|
208
203
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.1.
|
|
2
|
+
__version__ = "2.1.2"
|
|
@@ -1612,44 +1612,52 @@ def login(api_key):
|
|
|
1612
1612
|
|
|
1613
1613
|
|
|
1614
1614
|
def show_help():
|
|
1615
|
-
|
|
1615
|
+
try:
|
|
1616
|
+
from meshcode import __version__ as _v
|
|
1617
|
+
except Exception:
|
|
1618
|
+
_v = "?"
|
|
1619
|
+
print(f"""
|
|
1616
1620
|
╔═══════════════════════════════════════════════════════════╗
|
|
1617
|
-
║ MeshCode
|
|
1621
|
+
║ MeshCode v{_v} — Persistent AI Agent Teams ║
|
|
1618
1622
|
╚═══════════════════════════════════════════════════════════╝
|
|
1619
1623
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1624
|
+
QUICK START:
|
|
1625
|
+
login <api_key> Authenticate (get key at meshcode.io)
|
|
1626
|
+
go <agent> [--project <name>] Connect agent in one command
|
|
1627
|
+
|
|
1628
|
+
MESSAGING:
|
|
1622
1629
|
send <proj> <from>:<to> <message> Send message
|
|
1623
|
-
broadcast <proj> <from> <message> Send to all
|
|
1624
|
-
read <proj> <name> Read pending
|
|
1625
|
-
check Hook auto-check
|
|
1630
|
+
broadcast <proj> <from> <message> Send to all agents
|
|
1631
|
+
read <proj> <name> Read pending messages
|
|
1632
|
+
check Hook auto-check (non-destructive peek)
|
|
1626
1633
|
watch <proj> <name> [interval] [timeout] Wait for messages
|
|
1627
1634
|
|
|
1628
1635
|
STATUS:
|
|
1629
1636
|
board <proj> Agent status board
|
|
1630
|
-
update <proj> <name> <status> [task] Update status
|
|
1631
1637
|
status [proj] Overview
|
|
1632
|
-
projects
|
|
1638
|
+
projects | list | ls List your meshworks
|
|
1633
1639
|
|
|
1634
1640
|
HISTORY:
|
|
1635
1641
|
history <proj> [count] [agent1,agent2] Conversation log
|
|
1636
1642
|
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1643
|
+
TEAM:
|
|
1644
|
+
invite <proj> <agent> [--role "..."] Invite teammate
|
|
1645
|
+
join <token> Accept invite
|
|
1646
|
+
members <proj> List team members
|
|
1640
1647
|
|
|
1641
1648
|
SETUP (advanced):
|
|
1642
1649
|
setup <proj> <name> [role] Create workspace (auto by 'go')
|
|
1643
1650
|
run <agent> [--project <name>] Launch agent (auto by 'go')
|
|
1651
|
+
register <proj> <name> [role] Register agent manually
|
|
1644
1652
|
setup <client> <proj> <name> [role] Legacy: global MCP config
|
|
1645
|
-
Clients: claude-desktop
|
|
1646
1653
|
|
|
1647
|
-
|
|
1654
|
+
ADMIN:
|
|
1648
1655
|
clear <proj> <name> Clear inbox
|
|
1649
1656
|
unregister <proj> <name> Leave project
|
|
1657
|
+
prefs View/set preferences
|
|
1650
1658
|
|
|
1651
|
-
|
|
1652
|
-
|
|
1659
|
+
FEATURES: Auto-status (5 states), Agent Replay, Live Spectator,
|
|
1660
|
+
Mesh Graph, Session Recording, Hot-reload
|
|
1653
1661
|
|
|
1654
1662
|
Run `meshcode <command> --help` for help on a specific command.
|
|
1655
1663
|
""")
|
|
@@ -422,12 +422,10 @@ def with_working_status(func):
|
|
|
422
422
|
return func(*args, **kwargs)
|
|
423
423
|
finally:
|
|
424
424
|
if not skip:
|
|
425
|
-
global _last_tool_at
|
|
425
|
+
global _last_tool_at
|
|
426
426
|
_last_tool_at = _time.time()
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
_processing_timer.daemon = True
|
|
430
|
-
_processing_timer.start()
|
|
427
|
+
# Don't flip to online here — CPU-based detection in heartbeat
|
|
428
|
+
# will handle the transition when LLM stops generating
|
|
431
429
|
return swrapper
|
|
432
430
|
|
|
433
431
|
|
|
@@ -565,23 +563,21 @@ def _build_instructions() -> str:
|
|
|
565
563
|
)
|
|
566
564
|
base = f"""You are agent "{AGENT_NAME}" in meshwork "{PROJECT_NAME}".{role_block}{launch_block}
|
|
567
565
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
566
|
+
PERMANENT LOOP (MANDATORY — #1 RULE):
|
|
567
|
+
You MUST stay in meshcode_wait() forever. After EVERY action, call meshcode_wait().
|
|
568
|
+
NEVER exit. NEVER stop. NEVER say "standing by" without calling meshcode_wait().
|
|
569
|
+
The ONLY exits: user says "stop", got_done signal, or fatal error.
|
|
570
|
+
Timeout? Call meshcode_wait() again with 2x timeout (cap 1800s).
|
|
571
|
+
|
|
572
|
+
LOOP: act → meshcode_send if needed → meshcode_wait() → repeat.
|
|
573
573
|
|
|
574
574
|
RULES:
|
|
575
|
-
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
-
|
|
579
|
-
-
|
|
580
|
-
-
|
|
581
|
-
- Poll meshcode_tasks() before each meshcode_wait().
|
|
582
|
-
- Threading: pass in_reply_to with original msg id.
|
|
583
|
-
- sensitive=True for credentials/secrets/PII in meshcode_send.
|
|
584
|
-
- No feedback loops: stop if >10 messages on same topic.
|
|
575
|
+
- Use MCP tools only (never CLI commands in bash).
|
|
576
|
+
- Tasks > messages. Claim tasks via meshcode_tasks/task_claim/task_complete.
|
|
577
|
+
- Messages <100 tokens. Long content → create task.
|
|
578
|
+
- No empty acks. JSON reports only.
|
|
579
|
+
- Threading: pass in_reply_to.
|
|
580
|
+
- sensitive=True for secrets/PII.
|
|
585
581
|
|
|
586
582
|
SESSION START:
|
|
587
583
|
1. meshcode_set_status(status="online", task="ready") — announce you're online
|
|
@@ -755,7 +751,9 @@ def _heartbeat_thread_fn():
|
|
|
755
751
|
except Exception as e:
|
|
756
752
|
log.warning(f"lease renewal failed: {e}")
|
|
757
753
|
|
|
758
|
-
|
|
754
|
+
# Heartbeat must be well under the 180s decay threshold
|
|
755
|
+
hb_interval = 30
|
|
756
|
+
_heartbeat_stop.wait(hb_interval)
|
|
759
757
|
|
|
760
758
|
|
|
761
759
|
@asynccontextmanager
|
|
@@ -833,14 +831,7 @@ except Exception:
|
|
|
833
831
|
@with_working_status
|
|
834
832
|
def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
|
|
835
833
|
sensitive: bool = False) -> Dict[str, Any]:
|
|
836
|
-
"""Send
|
|
837
|
-
|
|
838
|
-
Args:
|
|
839
|
-
to: Recipient agent name (or agent@meshwork for cross-mesh).
|
|
840
|
-
message: String or dict payload.
|
|
841
|
-
in_reply_to: Original message id for threading.
|
|
842
|
-
sensitive: True to hide from public exports.
|
|
843
|
-
"""
|
|
834
|
+
"""Send message. Use "agent@meshwork" for cross-mesh. sensitive=True hides from exports."""
|
|
844
835
|
if isinstance(message, str):
|
|
845
836
|
payload: Dict[str, Any] = {"text": message}
|
|
846
837
|
elif isinstance(message, dict):
|
|
@@ -884,16 +875,7 @@ def meshcode_broadcast(payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
884
875
|
@mcp.tool()
|
|
885
876
|
@with_working_status
|
|
886
877
|
def meshcode_read(include_acks: bool = False) -> Dict[str, Any]:
|
|
887
|
-
"""Read and
|
|
888
|
-
|
|
889
|
-
Returns a split shape: `messages` (real msgs), `acks`, and `done_signals`
|
|
890
|
-
— so you don't have to filter by type yourself.
|
|
891
|
-
|
|
892
|
-
Args:
|
|
893
|
-
include_acks: If False (default), type='ack' rows are dropped entirely
|
|
894
|
-
from the response. Acks are bookkeeping noise and most callers
|
|
895
|
-
don't want them in their LLM context.
|
|
896
|
-
"""
|
|
878
|
+
"""Read and consume pending messages. Returns {messages, acks, done_signals}."""
|
|
897
879
|
raw = be.read_inbox(_PROJECT_ID, AGENT_NAME)
|
|
898
880
|
normalized = [
|
|
899
881
|
{
|
|
@@ -916,13 +898,7 @@ def meshcode_read(include_acks: bool = False) -> Dict[str, Any]:
|
|
|
916
898
|
@mcp.tool()
|
|
917
899
|
@with_working_status
|
|
918
900
|
def meshcode_history(limit: int = 20, agent_filter: Optional[str] = None) -> Dict[str, Any]:
|
|
919
|
-
"""View
|
|
920
|
-
context from past conversations or lost messages after context compression.
|
|
921
|
-
|
|
922
|
-
Args:
|
|
923
|
-
limit: Max messages to return (default 20).
|
|
924
|
-
agent_filter: Optional agent name to filter (shows messages to/from that agent).
|
|
925
|
-
"""
|
|
901
|
+
"""View past messages (read+unread). Optional agent_filter."""
|
|
926
902
|
raw = be.get_history(_PROJECT_ID, limit=limit, agent_filter=agent_filter or "")
|
|
927
903
|
messages = [
|
|
928
904
|
{
|
|
@@ -942,12 +918,7 @@ def meshcode_history(limit: int = 20, agent_filter: Optional[str] = None) -> Dic
|
|
|
942
918
|
@mcp.tool()
|
|
943
919
|
@with_working_status
|
|
944
920
|
def meshcode_read_message(msg_id: str) -> Dict[str, Any]:
|
|
945
|
-
"""Fetch a specific message by
|
|
946
|
-
after context was compressed or session restarted.
|
|
947
|
-
|
|
948
|
-
Args:
|
|
949
|
-
msg_id: The UUID of the message to fetch.
|
|
950
|
-
"""
|
|
921
|
+
"""Fetch a specific message by its UUID."""
|
|
951
922
|
msg = be.get_message_by_id(_PROJECT_ID, msg_id)
|
|
952
923
|
if not msg:
|
|
953
924
|
return {"error": "message not found", "msg_id": msg_id}
|
|
@@ -1043,20 +1014,7 @@ async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[
|
|
|
1043
1014
|
# Realtime unavailable — plain sleep fallback so we still honor timeout.
|
|
1044
1015
|
await asyncio.sleep(actual_timeout)
|
|
1045
1016
|
|
|
1046
|
-
|
|
1047
|
-
timeout_result: Dict[str, Any] = {"timed_out": True}
|
|
1048
|
-
try:
|
|
1049
|
-
api_key = _get_api_key()
|
|
1050
|
-
open_tasks = be.task_list(api_key, _PROJECT_ID, AGENT_NAME, status_filter="open")
|
|
1051
|
-
if isinstance(open_tasks, dict) and open_tasks.get("ok"):
|
|
1052
|
-
my_tasks = [t for t in open_tasks.get("tasks", [])
|
|
1053
|
-
if t.get("assignee") == AGENT_NAME and not t.get("claimed_by")]
|
|
1054
|
-
if my_tasks:
|
|
1055
|
-
timeout_result["unclaimed_tasks"] = len(my_tasks)
|
|
1056
|
-
timeout_result["hint"] = f"You have {len(my_tasks)} unclaimed task(s) assigned to you. Run meshcode_tasks(status_filter='open') to see them."
|
|
1057
|
-
except Exception:
|
|
1058
|
-
pass
|
|
1059
|
-
return timeout_result
|
|
1017
|
+
return {"timed_out": True}
|
|
1060
1018
|
|
|
1061
1019
|
|
|
1062
1020
|
@mcp.tool()
|
|
@@ -1085,12 +1043,7 @@ def meshcode_done(reason: str) -> Dict[str, Any]:
|
|
|
1085
1043
|
@mcp.tool()
|
|
1086
1044
|
@with_working_status
|
|
1087
1045
|
def meshcode_check(include_acks: bool = False) -> Dict[str, Any]:
|
|
1088
|
-
"""
|
|
1089
|
-
|
|
1090
|
-
Checks realtime buffer first, then falls back to DB if buffer is empty
|
|
1091
|
-
but there are pending messages (handles messages that arrived before
|
|
1092
|
-
the realtime listener connected).
|
|
1093
|
-
"""
|
|
1046
|
+
"""Peek at inbox (non-destructive). Returns pending count + new messages."""
|
|
1094
1047
|
pending = be.count_pending(_PROJECT_ID, AGENT_NAME)
|
|
1095
1048
|
# Peek at realtime buffer WITHOUT draining — check is non-destructive
|
|
1096
1049
|
realtime_buffered = _REALTIME.peek() if _REALTIME else []
|
|
@@ -1151,9 +1104,7 @@ def meshcode_status() -> Dict[str, Any]:
|
|
|
1151
1104
|
@mcp.tool()
|
|
1152
1105
|
@with_working_status
|
|
1153
1106
|
def meshcode_register(role: str = "") -> Dict[str, Any]:
|
|
1154
|
-
"""Re-register
|
|
1155
|
-
want to update your role description.
|
|
1156
|
-
"""
|
|
1107
|
+
"""Re-register agent (update role)."""
|
|
1157
1108
|
return be.register_agent(PROJECT_NAME, AGENT_NAME, role or AGENT_ROLE)
|
|
1158
1109
|
|
|
1159
1110
|
|
|
@@ -1171,10 +1122,7 @@ def meshcode_set_status(status: str, task: str = "") -> Dict[str, Any]:
|
|
|
1171
1122
|
@mcp.tool()
|
|
1172
1123
|
@with_working_status
|
|
1173
1124
|
def meshcode_init(project: str, agent: str, role: str = "") -> Dict[str, Any]:
|
|
1174
|
-
"""
|
|
1175
|
-
if you need to dynamically switch context — normally the env vars set at
|
|
1176
|
-
server start are correct.
|
|
1177
|
-
"""
|
|
1125
|
+
"""Switch project/agent context. Rarely needed."""
|
|
1178
1126
|
global PROJECT_NAME, AGENT_NAME, AGENT_ROLE, _PROJECT_ID
|
|
1179
1127
|
PROJECT_NAME = project
|
|
1180
1128
|
AGENT_NAME = agent
|
|
@@ -1191,18 +1139,7 @@ def meshcode_init(project: str, agent: str, role: str = "") -> Dict[str, Any]:
|
|
|
1191
1139
|
@with_working_status
|
|
1192
1140
|
def meshcode_task_create(title: str, description: str = "", assignee: str = "*",
|
|
1193
1141
|
priority: str = "normal", parent_task_id: Optional[str] = None) -> Dict[str, Any]:
|
|
1194
|
-
"""Create
|
|
1195
|
-
the commander) want to assign work that needs tracking.
|
|
1196
|
-
|
|
1197
|
-
Args:
|
|
1198
|
-
title: Short title of the task.
|
|
1199
|
-
description: Longer description / acceptance criteria.
|
|
1200
|
-
assignee: Agent name to assign to, or "*" for any agent (anyone can claim).
|
|
1201
|
-
priority: 'low' / 'normal' / 'high' / 'urgent'.
|
|
1202
|
-
parent_task_id: Optional parent task id for nesting subtasks.
|
|
1203
|
-
|
|
1204
|
-
Returns: {"ok": true, "task_id": "...", "title": "..."}
|
|
1205
|
-
"""
|
|
1142
|
+
"""Create task. assignee="*" for any, priority: low/normal/high/urgent."""
|
|
1206
1143
|
api_key = _get_api_key()
|
|
1207
1144
|
result = be.task_create(api_key, _PROJECT_ID, AGENT_NAME, title,
|
|
1208
1145
|
description=description, assignee=assignee,
|
|
@@ -1239,13 +1176,7 @@ def meshcode_tasks(status_filter: Optional[str] = None) -> Dict[str, Any]:
|
|
|
1239
1176
|
@mcp.tool()
|
|
1240
1177
|
@with_working_status
|
|
1241
1178
|
def meshcode_task_claim(task_id: str) -> Dict[str, Any]:
|
|
1242
|
-
"""
|
|
1243
|
-
else (or assigned to a different agent), this returns an error and another
|
|
1244
|
-
task should be picked.
|
|
1245
|
-
|
|
1246
|
-
Args:
|
|
1247
|
-
task_id: The uuid of the task you want to claim.
|
|
1248
|
-
"""
|
|
1179
|
+
"""Claim an open task. Fails if already claimed by someone else."""
|
|
1249
1180
|
api_key = _get_api_key()
|
|
1250
1181
|
return be.task_claim(api_key, _PROJECT_ID, task_id, AGENT_NAME)
|
|
1251
1182
|
|
|
@@ -1253,12 +1184,7 @@ def meshcode_task_claim(task_id: str) -> Dict[str, Any]:
|
|
|
1253
1184
|
@mcp.tool()
|
|
1254
1185
|
@with_working_status
|
|
1255
1186
|
def meshcode_task_complete(task_id: str, summary: str = "") -> Dict[str, Any]:
|
|
1256
|
-
"""
|
|
1257
|
-
|
|
1258
|
-
Args:
|
|
1259
|
-
task_id: The uuid of the task you previously claimed.
|
|
1260
|
-
summary: Short description of what was accomplished + any artifacts.
|
|
1261
|
-
"""
|
|
1187
|
+
"""Complete a claimed task with summary."""
|
|
1262
1188
|
api_key = _get_api_key()
|
|
1263
1189
|
return be.task_complete(api_key, _PROJECT_ID, task_id, AGENT_NAME, summary=summary)
|
|
1264
1190
|
|
|
@@ -1266,11 +1192,7 @@ def meshcode_task_complete(task_id: str, summary: str = "") -> Dict[str, Any]:
|
|
|
1266
1192
|
@mcp.tool()
|
|
1267
1193
|
@with_working_status
|
|
1268
1194
|
def meshcode_task_approve(task_id: str) -> Dict[str, Any]:
|
|
1269
|
-
"""Approve a task
|
|
1270
|
-
|
|
1271
|
-
Args:
|
|
1272
|
-
task_id: Task UUID to approve.
|
|
1273
|
-
"""
|
|
1195
|
+
"""Approve a reviewed task."""
|
|
1274
1196
|
api_key = _get_api_key()
|
|
1275
1197
|
return be.sb_rpc("mc_task_approve", {
|
|
1276
1198
|
"p_api_key": api_key,
|
|
@@ -1283,12 +1205,7 @@ def meshcode_task_approve(task_id: str) -> Dict[str, Any]:
|
|
|
1283
1205
|
@mcp.tool()
|
|
1284
1206
|
@with_working_status
|
|
1285
1207
|
def meshcode_task_reject(task_id: str, feedback: str = "") -> Dict[str, Any]:
|
|
1286
|
-
"""Reject a task
|
|
1287
|
-
|
|
1288
|
-
Args:
|
|
1289
|
-
task_id: Task UUID to reject.
|
|
1290
|
-
feedback: Why it was rejected.
|
|
1291
|
-
"""
|
|
1208
|
+
"""Reject a task, sends back with feedback."""
|
|
1292
1209
|
api_key = _get_api_key()
|
|
1293
1210
|
return be.sb_rpc("mc_task_reject", {
|
|
1294
1211
|
"p_api_key": api_key,
|
|
@@ -1305,13 +1222,7 @@ def meshcode_task_reject(task_id: str, feedback: str = "") -> Dict[str, Any]:
|
|
|
1305
1222
|
@with_working_status
|
|
1306
1223
|
def meshcode_link(target_meshwork: str, source_agents: Optional[str] = None,
|
|
1307
1224
|
target_agents: Optional[str] = None) -> Dict[str, Any]:
|
|
1308
|
-
"""
|
|
1309
|
-
|
|
1310
|
-
Args:
|
|
1311
|
-
target_meshwork: Meshwork name to link to.
|
|
1312
|
-
source_agents: Comma-separated allowed senders (default: all).
|
|
1313
|
-
target_agents: Comma-separated allowed receivers (default: all).
|
|
1314
|
-
"""
|
|
1225
|
+
"""Link to another meshwork. Target must accept."""
|
|
1315
1226
|
api_key = _get_api_key()
|
|
1316
1227
|
src = source_agents.split(",") if source_agents else ["*"]
|
|
1317
1228
|
tgt = target_agents.split(",") if target_agents else ["*"]
|
|
@@ -1353,12 +1264,7 @@ def meshcode_links() -> Dict[str, Any]:
|
|
|
1353
1264
|
@mcp.tool()
|
|
1354
1265
|
@with_working_status
|
|
1355
1266
|
def meshcode_expand_link(link_id: str, agents: str) -> Dict[str, Any]:
|
|
1356
|
-
"""
|
|
1357
|
-
|
|
1358
|
-
Args:
|
|
1359
|
-
link_id: UUID of the active link to expand.
|
|
1360
|
-
agents: Comma-separated agent names to add (applied to both sides).
|
|
1361
|
-
"""
|
|
1267
|
+
"""Add agents to an active mesh link."""
|
|
1362
1268
|
api_key = _get_api_key()
|
|
1363
1269
|
agent_list = [a.strip() for a in agents.split(",")]
|
|
1364
1270
|
return be.sb_rpc("mc_expand_mesh_link", {
|
|
@@ -1374,11 +1280,7 @@ def meshcode_expand_link(link_id: str, agents: str) -> Dict[str, Any]:
|
|
|
1374
1280
|
@mcp.tool()
|
|
1375
1281
|
@with_working_status
|
|
1376
1282
|
def meshcode_create_meshwork(name: str) -> Dict[str, Any]:
|
|
1377
|
-
"""Create a new meshwork
|
|
1378
|
-
|
|
1379
|
-
Args:
|
|
1380
|
-
name: Meshwork name (lowercase, hyphens ok).
|
|
1381
|
-
"""
|
|
1283
|
+
"""Create a new meshwork."""
|
|
1382
1284
|
api_key = _get_api_key()
|
|
1383
1285
|
result = be.sb_rpc("mc_create_meshwork_by_api_key", {
|
|
1384
1286
|
"p_api_key": api_key, "p_name": name,
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Static-analysis test: every name used inside with_working_status must exist.
|
|
2
|
+
|
|
3
|
+
This is the test that catches the _PROCESSING_COOLDOWN_S class of bugs —
|
|
4
|
+
references to variables that were never defined. It parses server.py's AST
|
|
5
|
+
and checks that every Name node inside the wrapper functions resolves to a
|
|
6
|
+
local, global, or builtin in the module.
|
|
7
|
+
|
|
8
|
+
Run: python -m pytest meshcode/meshcode_mcp/test_server_wrapper.py -v
|
|
9
|
+
or: python -m unittest meshcode.meshcode_mcp.test_server_wrapper
|
|
10
|
+
"""
|
|
11
|
+
import ast
|
|
12
|
+
import builtins
|
|
13
|
+
import os
|
|
14
|
+
import unittest
|
|
15
|
+
|
|
16
|
+
_SERVER_PATH = os.path.join(os.path.dirname(__file__), "server.py")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_module_globals(tree: ast.Module) -> set[str]:
|
|
20
|
+
"""Collect all names assigned/imported at module level."""
|
|
21
|
+
names = set()
|
|
22
|
+
for node in ast.walk(tree):
|
|
23
|
+
if isinstance(node, ast.Import):
|
|
24
|
+
for alias in node.names:
|
|
25
|
+
names.add(alias.asname or alias.name.split(".")[0])
|
|
26
|
+
elif isinstance(node, ast.ImportFrom):
|
|
27
|
+
for alias in node.names:
|
|
28
|
+
names.add(alias.asname or alias.name)
|
|
29
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
30
|
+
names.add(node.name)
|
|
31
|
+
elif isinstance(node, ast.ClassDef):
|
|
32
|
+
names.add(node.name)
|
|
33
|
+
elif isinstance(node, ast.Assign):
|
|
34
|
+
for target in node.targets:
|
|
35
|
+
if isinstance(target, ast.Name):
|
|
36
|
+
names.add(target.id)
|
|
37
|
+
elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
|
|
38
|
+
names.add(node.target.id)
|
|
39
|
+
elif isinstance(node, ast.AugAssign) and isinstance(node.target, ast.Name):
|
|
40
|
+
names.add(node.target.id)
|
|
41
|
+
# builtins
|
|
42
|
+
names.update(dir(builtins))
|
|
43
|
+
return names
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _collect_names_in_func(func_node: ast.AST) -> set[str]:
|
|
47
|
+
"""All Name.id references inside a function body."""
|
|
48
|
+
refs = set()
|
|
49
|
+
for node in ast.walk(func_node):
|
|
50
|
+
if isinstance(node, ast.Name):
|
|
51
|
+
refs.add(node.id)
|
|
52
|
+
return refs
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _collect_locals(func_node: ast.AST) -> set[str]:
|
|
56
|
+
"""Names assigned, declared global, or used as parameters inside a function."""
|
|
57
|
+
local = set()
|
|
58
|
+
for node in ast.walk(func_node):
|
|
59
|
+
if isinstance(node, ast.Global):
|
|
60
|
+
local.update(node.names)
|
|
61
|
+
elif isinstance(node, ast.Assign):
|
|
62
|
+
for t in node.targets:
|
|
63
|
+
if isinstance(t, ast.Name):
|
|
64
|
+
local.add(t.id)
|
|
65
|
+
elif isinstance(node, ast.arg):
|
|
66
|
+
local.add(node.arg)
|
|
67
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
68
|
+
local.add(node.name)
|
|
69
|
+
for arg in node.args.args:
|
|
70
|
+
local.add(arg.arg)
|
|
71
|
+
if node.args.vararg:
|
|
72
|
+
local.add(node.args.vararg.arg)
|
|
73
|
+
if node.args.kwarg:
|
|
74
|
+
local.add(node.args.kwarg.arg)
|
|
75
|
+
return local
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ServerWrapperNamesTest(unittest.TestCase):
|
|
79
|
+
"""Verify no undefined names inside with_working_status."""
|
|
80
|
+
|
|
81
|
+
def test_no_undefined_names_in_wrapper(self):
|
|
82
|
+
with open(_SERVER_PATH) as f:
|
|
83
|
+
tree = ast.parse(f.read(), _SERVER_PATH)
|
|
84
|
+
|
|
85
|
+
module_globals = _get_module_globals(tree)
|
|
86
|
+
|
|
87
|
+
# Find the with_working_status function
|
|
88
|
+
wrapper_func = None
|
|
89
|
+
for node in ast.walk(tree):
|
|
90
|
+
if isinstance(node, ast.FunctionDef) and node.name == "with_working_status":
|
|
91
|
+
wrapper_func = node
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
self.assertIsNotNone(wrapper_func, "with_working_status not found in server.py")
|
|
95
|
+
|
|
96
|
+
# Check every nested function inside with_working_status
|
|
97
|
+
for node in ast.walk(wrapper_func):
|
|
98
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
99
|
+
if node.name == "with_working_status":
|
|
100
|
+
continue # skip the outer function itself
|
|
101
|
+
refs = _collect_names_in_func(node)
|
|
102
|
+
locals_ = _collect_locals(node)
|
|
103
|
+
# Add the outer function's params and locals too
|
|
104
|
+
outer_locals = _collect_locals(wrapper_func)
|
|
105
|
+
allowed = module_globals | locals_ | outer_locals
|
|
106
|
+
|
|
107
|
+
undefined = refs - allowed
|
|
108
|
+
self.assertEqual(
|
|
109
|
+
undefined,
|
|
110
|
+
set(),
|
|
111
|
+
f"Undefined names in {node.name}() inside with_working_status: "
|
|
112
|
+
f"{undefined}. These will cause NameError at runtime.",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
unittest.main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcode
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Real-time communication between AI agents — Supabase-backed CLI
|
|
5
5
|
Author-email: MeshCode <hello@meshcode.io>
|
|
6
6
|
License: MIT
|
|
@@ -43,23 +43,18 @@ Requires Python 3.9+. Works on macOS, Linux, Windows.
|
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
-
## Quickstart (
|
|
46
|
+
## Quickstart (3 commands)
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
# 1. Sign up at https://meshcode.io and copy
|
|
50
|
-
# 2.
|
|
49
|
+
# 1. Sign up at https://meshcode.io and copy your API key
|
|
50
|
+
# 2. Login (stores key in OS keychain, once forever)
|
|
51
51
|
meshcode login mc_xxxxxxxxxxxxxxxxxxxxxxxx
|
|
52
52
|
|
|
53
|
-
# 3.
|
|
54
|
-
meshcode
|
|
55
|
-
|
|
56
|
-
# 4. Launch the agent (this opens your editor with the MCP server wired up)
|
|
57
|
-
meshcode run commander
|
|
58
|
-
|
|
59
|
-
# 5. Watch it come online in the dashboard at https://meshcode.io/dashboard
|
|
53
|
+
# 3. Connect your first agent (auto-setups workspace + launches editor)
|
|
54
|
+
meshcode go commander
|
|
60
55
|
```
|
|
61
56
|
|
|
62
|
-
That's it.
|
|
57
|
+
That's it. `meshcode go` handles everything: auth check, workspace creation, editor launch. Watch it come online at https://meshcode.io/dashboard.
|
|
63
58
|
|
|
64
59
|
---
|
|
65
60
|
|
|
@@ -90,14 +85,10 @@ Each agent is a *real* Claude/Cursor/Cline window. MeshCode gives them shared me
|
|
|
90
85
|
```bash
|
|
91
86
|
meshcode login mc_xxx # once, forever
|
|
92
87
|
|
|
93
|
-
meshcode setup my-project commander # ~/meshcode/my-project-commander/
|
|
94
|
-
meshcode setup my-project backend
|
|
95
|
-
meshcode setup my-project frontend
|
|
96
|
-
|
|
97
88
|
# in three separate terminals:
|
|
98
|
-
meshcode
|
|
99
|
-
meshcode
|
|
100
|
-
meshcode
|
|
89
|
+
meshcode go commander
|
|
90
|
+
meshcode go backend
|
|
91
|
+
meshcode go frontend
|
|
101
92
|
```
|
|
102
93
|
|
|
103
94
|
Inside any window, tell the agent to coordinate with the others. They self-orchestrate through the mesh loop (`meshcode_wait` blocks until a peer sends a message, `meshcode_done` reports progress).
|
|
@@ -141,6 +132,7 @@ meshcode revoke-member my-project <user> # kick a member instantly
|
|
|
141
132
|
| Command | Example | Description |
|
|
142
133
|
|---|---|---|
|
|
143
134
|
| `meshcode login <key>` | `meshcode login mc_abc123` | Store your API key in the OS keychain (once). |
|
|
135
|
+
| `meshcode go <agent>` | `meshcode go backend` | **Recommended.** One command: auth + setup + launch. |
|
|
144
136
|
| `meshcode setup <project> <agent>` | `meshcode setup my-project backend` | Create a workspace and wire up the MCP config for your editor. |
|
|
145
137
|
| `meshcode run <agent>` | `meshcode run backend` | Launch the editor with this agent's MCP server attached. |
|
|
146
138
|
| `meshcode invite <project> <agent>` | `meshcode invite my-project designer --role "UI" --days 7` | Generate a join URL to share. |
|
|
@@ -223,11 +215,14 @@ The dashboard shows each agent's memories in the Memory tab, where users can vie
|
|
|
223
215
|
|
|
224
216
|
The web dashboard at [meshcode.io/dashboard](https://meshcode.io/dashboard) shows:
|
|
225
217
|
|
|
226
|
-
- **Agents panel**: real-time status (online/
|
|
218
|
+
- **Agents panel**: real-time status (online/waiting/working/idle/offline), auto-detected via CPU monitoring — no manual status updates needed
|
|
227
219
|
- **Chat**: message history between agents with threading and broadcast highlighting
|
|
220
|
+
- **Mesh Graph**: live force-directed visualization of your agent team — nodes pulse by status, edges light up when messages flow. Hierarchy layout with commander at top.
|
|
221
|
+
- **Agent Replay**: rewind and replay any agent's session like a timeline. See every tool call, message, and status change. Powered by database-level triggers — works automatically for all agents.
|
|
228
222
|
- **Tasks board**: shared task list with status, priority, assignee, and progress
|
|
229
223
|
- **Memory**: per-agent persistent key-value store, editable by the user
|
|
230
|
-
- **
|
|
224
|
+
- **Live Spectator**: share a public link to let anyone watch your mesh team work in real-time. No login required.
|
|
225
|
+
- **Onboarding Wizard**: interactive 4-step setup checklist for new users with live detection of each step
|
|
231
226
|
|
|
232
227
|
---
|
|
233
228
|
|
|
@@ -24,4 +24,5 @@ meshcode/meshcode_mcp/backend.py
|
|
|
24
24
|
meshcode/meshcode_mcp/realtime.py
|
|
25
25
|
meshcode/meshcode_mcp/server.py
|
|
26
26
|
meshcode/meshcode_mcp/test_backend.py
|
|
27
|
-
meshcode/meshcode_mcp/test_realtime.py
|
|
27
|
+
meshcode/meshcode_mcp/test_realtime.py
|
|
28
|
+
meshcode/meshcode_mcp/test_server_wrapper.py
|
|
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
|