meshcode 2.0.0__tar.gz → 2.0.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.0.0 → meshcode-2.0.2}/PKG-INFO +24 -1
- {meshcode-2.0.0 → meshcode-2.0.2}/README.md +23 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/__init__.py +1 -1
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/comms_v4.py +59 -14
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/meshcode_mcp/server.py +171 -67
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/run_agent.py +91 -10
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/setup_clients.py +48 -3
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode.egg-info/PKG-INFO +24 -1
- {meshcode-2.0.0 → meshcode-2.0.2}/pyproject.toml +1 -1
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/cli.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/invites.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/launcher.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/launcher_install.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/preferences.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/secrets.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode/self_update.py +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.0.0 → meshcode-2.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcode
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.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
|
|
@@ -158,6 +158,29 @@ meshcode revoke-member my-project <user> # kick a member instantly
|
|
|
158
158
|
|
|
159
159
|
---
|
|
160
160
|
|
|
161
|
+
## Agent account management
|
|
162
|
+
|
|
163
|
+
Your AI agents can manage your MeshCode account from inside the mesh. Just tell your agent what you need:
|
|
164
|
+
|
|
165
|
+
- **"Create a backend agent"** → agent calls `meshcode_create_meshwork` + `meshcode_add_agent`
|
|
166
|
+
- **"Change the frontend role to UI designer"** → agent calls `meshcode_edit_agent`
|
|
167
|
+
- **"Give the backend agent a note about our conventions"** → agent calls `meshcode_edit_memory`
|
|
168
|
+
|
|
169
|
+
Available MCP tools for agents:
|
|
170
|
+
|
|
171
|
+
| Tool | What it does |
|
|
172
|
+
|------|-------------|
|
|
173
|
+
| `meshcode_create_meshwork(name)` | Create a new meshwork |
|
|
174
|
+
| `meshcode_add_agent(name, role)` | Add an agent to the current meshwork |
|
|
175
|
+
| `meshcode_edit_agent(name, role?, launch_prompt?)` | Update agent role or system prompt |
|
|
176
|
+
| `meshcode_edit_memory(agent_name, key, value)` | Edit another agent's persistent memory |
|
|
177
|
+
| `meshcode_scratchpad_set(key, value)` | Write to shared meshwork memory |
|
|
178
|
+
| `meshcode_link(target_meshwork)` | Link two meshworks for cross-mesh communication |
|
|
179
|
+
|
|
180
|
+
The agent will always tell you what CLI command to run next (e.g., "Open a new terminal and run `meshcode run backend`").
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
161
184
|
## Editor support
|
|
162
185
|
|
|
163
186
|
| Editor | Auto-detected? | Config file written |
|
|
@@ -133,6 +133,29 @@ meshcode revoke-member my-project <user> # kick a member instantly
|
|
|
133
133
|
|
|
134
134
|
---
|
|
135
135
|
|
|
136
|
+
## Agent account management
|
|
137
|
+
|
|
138
|
+
Your AI agents can manage your MeshCode account from inside the mesh. Just tell your agent what you need:
|
|
139
|
+
|
|
140
|
+
- **"Create a backend agent"** → agent calls `meshcode_create_meshwork` + `meshcode_add_agent`
|
|
141
|
+
- **"Change the frontend role to UI designer"** → agent calls `meshcode_edit_agent`
|
|
142
|
+
- **"Give the backend agent a note about our conventions"** → agent calls `meshcode_edit_memory`
|
|
143
|
+
|
|
144
|
+
Available MCP tools for agents:
|
|
145
|
+
|
|
146
|
+
| Tool | What it does |
|
|
147
|
+
|------|-------------|
|
|
148
|
+
| `meshcode_create_meshwork(name)` | Create a new meshwork |
|
|
149
|
+
| `meshcode_add_agent(name, role)` | Add an agent to the current meshwork |
|
|
150
|
+
| `meshcode_edit_agent(name, role?, launch_prompt?)` | Update agent role or system prompt |
|
|
151
|
+
| `meshcode_edit_memory(agent_name, key, value)` | Edit another agent's persistent memory |
|
|
152
|
+
| `meshcode_scratchpad_set(key, value)` | Write to shared meshwork memory |
|
|
153
|
+
| `meshcode_link(target_meshwork)` | Link two meshworks for cross-mesh communication |
|
|
154
|
+
|
|
155
|
+
The agent will always tell you what CLI command to run next (e.g., "Open a new terminal and run `meshcode run backend`").
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
136
159
|
## Editor support
|
|
137
160
|
|
|
138
161
|
| Editor | Auto-detected? | Config file written |
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.0.
|
|
2
|
+
__version__ = "2.0.2"
|
|
@@ -1248,19 +1248,32 @@ def show_history(project, last_n=20, between=None):
|
|
|
1248
1248
|
|
|
1249
1249
|
|
|
1250
1250
|
def list_projects():
|
|
1251
|
-
|
|
1252
|
-
if not
|
|
1253
|
-
print("[
|
|
1251
|
+
api_key = _load_api_key_for_cli()
|
|
1252
|
+
if not api_key:
|
|
1253
|
+
print("[meshcode] Not authenticated. Run `meshcode login <api_key>` first.")
|
|
1254
|
+
return
|
|
1255
|
+
|
|
1256
|
+
data = sb_rpc("mc_list_user_projects", {"p_api_key": api_key})
|
|
1257
|
+
if isinstance(data, dict) and data.get("error"):
|
|
1258
|
+
print(f"[meshcode] ERROR: {data['error']}")
|
|
1259
|
+
return
|
|
1260
|
+
|
|
1261
|
+
projects = []
|
|
1262
|
+
if isinstance(data, dict):
|
|
1263
|
+
projects = data.get("projects", [])
|
|
1264
|
+
|
|
1265
|
+
if not projects:
|
|
1266
|
+
print("[meshcode] No projects found. Create one at meshcode.io or with `meshcode_create_meshwork`.")
|
|
1254
1267
|
return
|
|
1255
1268
|
|
|
1256
1269
|
print(f"\n{'='*60}")
|
|
1257
|
-
print(f"
|
|
1270
|
+
print(f" YOUR MESHWORKS")
|
|
1258
1271
|
print(f"{'='*60}")
|
|
1259
1272
|
|
|
1260
|
-
for proj in
|
|
1261
|
-
agents =
|
|
1273
|
+
for proj in projects:
|
|
1274
|
+
agents = proj.get("agents", [])
|
|
1262
1275
|
statuses = [f"{a['name']}({a.get('status','?')})" for a in agents]
|
|
1263
|
-
print(f" [{proj['name']}] {', '.join(statuses) if statuses else '
|
|
1276
|
+
print(f" [{proj['name']}] {', '.join(statuses) if statuses else 'no agents'}")
|
|
1264
1277
|
print()
|
|
1265
1278
|
|
|
1266
1279
|
|
|
@@ -1503,11 +1516,12 @@ def connect(project, name, hook_target="claude", role=""):
|
|
|
1503
1516
|
comms_path = str(Path(__file__).resolve())
|
|
1504
1517
|
|
|
1505
1518
|
if hook_target == "claude":
|
|
1506
|
-
#
|
|
1507
|
-
#
|
|
1519
|
+
# Use workspace flow (NOT global) to avoid polluting ~/.claude.json.
|
|
1520
|
+
# Global configs cause all Claude Code windows to load all agents,
|
|
1521
|
+
# triggering lease conflicts and "x failed" errors.
|
|
1508
1522
|
import importlib
|
|
1509
|
-
|
|
1510
|
-
|
|
1523
|
+
_setup_ws = importlib.import_module("meshcode.setup_clients").setup_workspace
|
|
1524
|
+
_setup_ws(project, name, actual_role)
|
|
1511
1525
|
|
|
1512
1526
|
elif hook_target == "codex":
|
|
1513
1527
|
config_path = Path.cwd() / ".meshcode.json"
|
|
@@ -1893,7 +1907,7 @@ if __name__ == "__main__":
|
|
|
1893
1907
|
proj = sys.argv[2] if len(sys.argv) > 2 else None
|
|
1894
1908
|
show_status(proj)
|
|
1895
1909
|
|
|
1896
|
-
elif cmd
|
|
1910
|
+
elif cmd in ("projects", "list", "ls"):
|
|
1897
1911
|
list_projects()
|
|
1898
1912
|
|
|
1899
1913
|
elif cmd == "history":
|
|
@@ -2180,6 +2194,37 @@ if __name__ == "__main__":
|
|
|
2180
2194
|
show_help()
|
|
2181
2195
|
|
|
2182
2196
|
else:
|
|
2183
|
-
|
|
2184
|
-
|
|
2197
|
+
known_cmds = [
|
|
2198
|
+
"register", "send", "broadcast", "read", "check", "watch",
|
|
2199
|
+
"board", "update", "status", "projects", "list", "ls",
|
|
2200
|
+
"history", "clear", "unregister", "connect", "disconnect",
|
|
2201
|
+
"setup", "run", "invite", "join", "invites", "members",
|
|
2202
|
+
"revoke-invite", "revoke-member", "login", "prefs", "launcher",
|
|
2203
|
+
"help", "profile", "validate-sessions", "wake-headless",
|
|
2204
|
+
]
|
|
2205
|
+
# Simple fuzzy: prefix match + Levenshtein-like best match
|
|
2206
|
+
suggestions = [c for c in known_cmds if c.startswith(cmd)]
|
|
2207
|
+
if not suggestions:
|
|
2208
|
+
# Try substring match
|
|
2209
|
+
suggestions = [c for c in known_cmds if cmd in c]
|
|
2210
|
+
if not suggestions:
|
|
2211
|
+
# Levenshtein distance 2 or less
|
|
2212
|
+
def _dist(a, b):
|
|
2213
|
+
if len(a) > len(b): a, b = b, a
|
|
2214
|
+
dists = list(range(len(a) + 1))
|
|
2215
|
+
for j, cb in enumerate(b):
|
|
2216
|
+
new = [j + 1]
|
|
2217
|
+
for i, ca in enumerate(a):
|
|
2218
|
+
cost = 0 if ca == cb else 1
|
|
2219
|
+
new.append(min(new[-1] + 1, dists[i + 1] + 1, dists[i] + cost))
|
|
2220
|
+
dists = new
|
|
2221
|
+
return dists[-1]
|
|
2222
|
+
scored = [(c, _dist(cmd, c)) for c in known_cmds]
|
|
2223
|
+
suggestions = [c for c, d in scored if d <= 2]
|
|
2224
|
+
|
|
2225
|
+
if suggestions:
|
|
2226
|
+
print(f"[meshcode] Unknown command: '{cmd}'. Did you mean: {', '.join(suggestions[:3])}?")
|
|
2227
|
+
else:
|
|
2228
|
+
print(f"[meshcode] Unknown command: '{cmd}'.")
|
|
2229
|
+
print(f"[meshcode] Run `meshcode help` for all commands.")
|
|
2185
2230
|
sys.exit(1)
|
|
@@ -240,30 +240,31 @@ if isinstance(_register_result, dict) and _register_result.get("error"):
|
|
|
240
240
|
# bypass RLS — the publishable key has no JWT context and cannot UPDATE
|
|
241
241
|
# mc_agents directly. The RPC validates ownership via api_key.
|
|
242
242
|
def _flip_status(status: str, task: str = "") -> bool:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
"""Write status directly to mc_agents table for instant Realtime propagation.
|
|
244
|
+
|
|
245
|
+
Uses direct PATCH (not RPC) so Supabase Realtime fires an UPDATE event
|
|
246
|
+
immediately — the dashboard sees the change in <100ms instead of waiting
|
|
247
|
+
for the next heartbeat cycle.
|
|
248
|
+
"""
|
|
249
|
+
try:
|
|
250
|
+
be.set_status(_PROJECT_ID, AGENT_NAME, status, task)
|
|
251
|
+
return True
|
|
252
|
+
except Exception:
|
|
253
|
+
# Fallback to RPC if direct PATCH fails (RLS issue)
|
|
254
|
+
api_key = _get_api_key()
|
|
255
|
+
if not api_key:
|
|
256
|
+
return False
|
|
246
257
|
try:
|
|
247
|
-
be.
|
|
248
|
-
|
|
258
|
+
r = be.sb_rpc("mc_agent_set_status_by_api_key", {
|
|
259
|
+
"p_api_key": api_key,
|
|
260
|
+
"p_project_id": _PROJECT_ID,
|
|
261
|
+
"p_agent_name": AGENT_NAME,
|
|
262
|
+
"p_status": status,
|
|
263
|
+
"p_task": task,
|
|
264
|
+
})
|
|
265
|
+
return isinstance(r, dict) and r.get("ok", False)
|
|
249
266
|
except Exception:
|
|
250
267
|
return False
|
|
251
|
-
try:
|
|
252
|
-
r = be.sb_rpc("mc_agent_set_status_by_api_key", {
|
|
253
|
-
"p_api_key": api_key,
|
|
254
|
-
"p_project_id": _PROJECT_ID,
|
|
255
|
-
"p_agent_name": AGENT_NAME,
|
|
256
|
-
"p_status": status,
|
|
257
|
-
"p_task": task,
|
|
258
|
-
})
|
|
259
|
-
if isinstance(r, dict) and r.get("ok"):
|
|
260
|
-
return True
|
|
261
|
-
if isinstance(r, dict) and r.get("error"):
|
|
262
|
-
log.warning(f"set_status RPC: {r['error']}")
|
|
263
|
-
return False
|
|
264
|
-
except Exception as e:
|
|
265
|
-
log.warning(f"set_status RPC threw: {e}")
|
|
266
|
-
return False
|
|
267
268
|
|
|
268
269
|
if not _flip_status("idle", ""):
|
|
269
270
|
print(f"[meshcode-mcp] WARNING: could not flip status to idle", file=sys.stderr)
|
|
@@ -341,23 +342,67 @@ def _acquire_lease() -> bool:
|
|
|
341
342
|
api_key = _get_api_key()
|
|
342
343
|
if not api_key:
|
|
343
344
|
return True # legacy clients without api_key skip lease check
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
"
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
345
|
+
import time as _time
|
|
346
|
+
for attempt in range(3):
|
|
347
|
+
try:
|
|
348
|
+
r = be.sb_rpc("mc_acquire_agent_lease", {
|
|
349
|
+
"p_api_key": api_key,
|
|
350
|
+
"p_project_id": _PROJECT_ID,
|
|
351
|
+
"p_agent_name": AGENT_NAME,
|
|
352
|
+
"p_instance_id": _INSTANCE_ID,
|
|
353
|
+
})
|
|
354
|
+
if isinstance(r, dict) and r.get("ok"):
|
|
355
|
+
return True
|
|
356
|
+
if isinstance(r, dict) and r.get("error"):
|
|
357
|
+
err = str(r.get("error", ""))
|
|
358
|
+
if "already running" in err:
|
|
359
|
+
if attempt < 2:
|
|
360
|
+
# The old lease might be stale — wait and retry
|
|
361
|
+
# (the 90s stale check in the RPC should clear it)
|
|
362
|
+
print(f"[meshcode-mcp] Lease held by another instance — retrying in {2 * (attempt+1)}s...", file=sys.stderr)
|
|
363
|
+
_time.sleep(2 * (attempt + 1))
|
|
364
|
+
continue
|
|
365
|
+
# Final attempt — force release the old lease and try once more
|
|
366
|
+
print(f"[meshcode-mcp] Force-releasing stale lease...", file=sys.stderr)
|
|
367
|
+
try:
|
|
368
|
+
be.sb_rpc("mc_release_agent_lease", {
|
|
369
|
+
"p_api_key": api_key,
|
|
370
|
+
"p_project_id": _PROJECT_ID,
|
|
371
|
+
"p_agent_name": AGENT_NAME,
|
|
372
|
+
"p_instance_id": r.get("held_by", "unknown"),
|
|
373
|
+
})
|
|
374
|
+
except Exception:
|
|
375
|
+
# Force clear via direct update
|
|
376
|
+
try:
|
|
377
|
+
be.set_status(_PROJECT_ID, AGENT_NAME, "offline", "")
|
|
378
|
+
except Exception:
|
|
379
|
+
pass
|
|
380
|
+
_time.sleep(1)
|
|
381
|
+
# One more try after force-release
|
|
382
|
+
try:
|
|
383
|
+
r2 = be.sb_rpc("mc_acquire_agent_lease", {
|
|
384
|
+
"p_api_key": api_key,
|
|
385
|
+
"p_project_id": _PROJECT_ID,
|
|
386
|
+
"p_agent_name": AGENT_NAME,
|
|
387
|
+
"p_instance_id": _INSTANCE_ID,
|
|
388
|
+
})
|
|
389
|
+
if isinstance(r2, dict) and r2.get("ok"):
|
|
390
|
+
print(f"[meshcode-mcp] Lease acquired after force-release.", file=sys.stderr)
|
|
391
|
+
return True
|
|
392
|
+
except Exception:
|
|
393
|
+
pass
|
|
394
|
+
print(f"[meshcode-mcp] ERROR: Could not start — agent '{AGENT_NAME}' is running in another window.", file=sys.stderr)
|
|
395
|
+
print(f"[meshcode-mcp] Close the other window first, or use a different agent name.", file=sys.stderr)
|
|
396
|
+
return False
|
|
397
|
+
print(f"[meshcode-mcp] lease attempt {attempt+1}: {r.get('error')}", file=sys.stderr)
|
|
398
|
+
else:
|
|
399
|
+
return True
|
|
400
|
+
except Exception as e:
|
|
401
|
+
print(f"[meshcode-mcp] lease attempt {attempt+1} failed: {e}", file=sys.stderr)
|
|
402
|
+
if attempt < 2:
|
|
403
|
+
_time.sleep(2)
|
|
404
|
+
print(f"[meshcode-mcp] WARNING: lease failed after 3 attempts — proceeding anyway", file=sys.stderr)
|
|
405
|
+
return True
|
|
361
406
|
|
|
362
407
|
if not _acquire_lease():
|
|
363
408
|
sys.exit(2)
|
|
@@ -418,7 +463,7 @@ def _build_instructions() -> str:
|
|
|
418
463
|
f"\nUSER-PROVIDED ROLE PROMPT (from the dashboard):\n---\n{_LAUNCH_PROMPT}\n---\n"
|
|
419
464
|
if _LAUNCH_PROMPT else ""
|
|
420
465
|
)
|
|
421
|
-
|
|
466
|
+
base = f"""You are agent "{AGENT_NAME}" in meshwork "{PROJECT_NAME}".{role_block}{launch_block}
|
|
422
467
|
|
|
423
468
|
BEHAVIOR LOOP (your default state — never exit unless told):
|
|
424
469
|
1. Act on task/message → 2. meshcode_send if needed → 3. meshcode_wait()
|
|
@@ -427,6 +472,9 @@ BEHAVIOR LOOP (your default state — never exit unless told):
|
|
|
427
472
|
5. Only break loop if: user says stop, fatal error, or "tell the human X".
|
|
428
473
|
|
|
429
474
|
RULES:
|
|
475
|
+
- NEVER use CLI commands (meshcode watch, meshcode read, meshcode send) in bash.
|
|
476
|
+
Use ONLY the MCP tools: meshcode_check, meshcode_read, meshcode_send, meshcode_wait.
|
|
477
|
+
CLI commands are for humans in terminal. You have MCP tools — use them.
|
|
430
478
|
- Tasks > messages. Use meshcode_tasks/claim/complete for trackable work.
|
|
431
479
|
- Messages <100 tokens, signal-only. Long content → create task instead.
|
|
432
480
|
- No empty acks ("OK"/"Got it"). No prose padding. JSON reports only.
|
|
@@ -436,8 +484,10 @@ RULES:
|
|
|
436
484
|
- No feedback loops: stop if >10 messages on same topic.
|
|
437
485
|
|
|
438
486
|
SESSION START:
|
|
439
|
-
1.
|
|
440
|
-
2.
|
|
487
|
+
1. meshcode_set_status(status="online", task="ready") — announce you're online
|
|
488
|
+
2. meshcode_check() — read any messages waiting in your inbox
|
|
489
|
+
3. meshcode_status() — see who's online
|
|
490
|
+
4. Act on user task or meshcode_wait()
|
|
441
491
|
(Memories are pre-loaded below — no need to call meshcode_recall on boot.)
|
|
442
492
|
|
|
443
493
|
CROSS-MESH: meshcode_send(to="agent@meshwork") routes via active link.
|
|
@@ -459,6 +509,23 @@ what CLI command to run next (e.g. "meshcode run backend in a new terminal").
|
|
|
459
509
|
|
|
460
510
|
Setup help → README.md or https://meshcode.io/docs
|
|
461
511
|
"""
|
|
512
|
+
# Inject commander protocol if this agent is a leader
|
|
513
|
+
is_leader = any(k in (_ROLE_DESCRIPTION or '').lower() + AGENT_NAME.lower() for k in ('commander', 'lead', 'orchestrat'))
|
|
514
|
+
if is_leader:
|
|
515
|
+
base += """
|
|
516
|
+
COMMANDER PROTOCOL (you are the team lead):
|
|
517
|
+
- ALWAYS delegate via meshcode_task_create, NEVER via long messages.
|
|
518
|
+
- Tasks are the source of truth. Messages are signals only (<100 tokens).
|
|
519
|
+
- Workflow: identify work → create task → signal assignee → poll progress → verify → next.
|
|
520
|
+
- Poll meshcode_tasks() to track completion. Don't wait for messages.
|
|
521
|
+
- Verify builds/quality before approving. Don't approve blindly.
|
|
522
|
+
- Communicate changes to affected agents IMMEDIATELY after you make them.
|
|
523
|
+
- Organize: break big requests into multiple tasks, assign to the right agent.
|
|
524
|
+
- Keep the human informed with brief status updates at milestones.
|
|
525
|
+
- You are autonomous: fix small issues yourself, delegate big ones.
|
|
526
|
+
- After each sprint: consolidate learnings, update scratchpad, save to memory.
|
|
527
|
+
"""
|
|
528
|
+
return base
|
|
462
529
|
|
|
463
530
|
|
|
464
531
|
_INSTRUCTIONS = _build_instructions()
|
|
@@ -513,26 +580,34 @@ async def _on_new_message(msg: Dict[str, Any]) -> None:
|
|
|
513
580
|
log.debug(f"send_resource_updated unavailable: {e}")
|
|
514
581
|
|
|
515
582
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
583
|
+
_heartbeat_stop = _threading.Event()
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def _heartbeat_thread_fn():
|
|
587
|
+
"""Heartbeat in a DAEMON THREAD — independent of asyncio event loop.
|
|
588
|
+
|
|
589
|
+
This ensures heartbeats continue even when tool calls are cancelled,
|
|
590
|
+
meshcode_wait is rejected, or the asyncio loop is busy. The agent
|
|
591
|
+
stays 'online' in the dashboard as long as the MCP process is alive.
|
|
592
|
+
"""
|
|
593
|
+
lease_counter = 0
|
|
594
|
+
while not _heartbeat_stop.is_set():
|
|
522
595
|
try:
|
|
523
|
-
be.sb_rpc("mc_heartbeat", {"p_project_id": _PROJECT_ID, "p_agent_name": AGENT_NAME})
|
|
524
|
-
#
|
|
596
|
+
be.sb_rpc("mc_heartbeat", {"p_project_id": _PROJECT_ID, "p_agent_name": AGENT_NAME, "p_version": "2.0.0"})
|
|
597
|
+
# Also ensure status is at least "idle" (not "offline") between tool calls
|
|
598
|
+
try:
|
|
599
|
+
be.set_status(_PROJECT_ID, AGENT_NAME, "idle", "")
|
|
600
|
+
except Exception:
|
|
601
|
+
pass
|
|
525
602
|
if _REALTIME and not _REALTIME.is_connected:
|
|
526
|
-
log.warning("heartbeat ok (HTTP) but WebSocket
|
|
603
|
+
log.warning("heartbeat ok (HTTP) but WebSocket disconnected")
|
|
527
604
|
else:
|
|
528
605
|
log.debug(f"heartbeat ok for {AGENT_NAME}")
|
|
529
606
|
except Exception as e:
|
|
530
607
|
log.warning(f"heartbeat failed: {e}")
|
|
531
608
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
_lease_counter += 1
|
|
535
|
-
if _lease_counter % 4 == 0:
|
|
609
|
+
lease_counter += 1
|
|
610
|
+
if lease_counter % 4 == 0:
|
|
536
611
|
try:
|
|
537
612
|
api_key = _get_api_key()
|
|
538
613
|
if api_key:
|
|
@@ -545,7 +620,7 @@ async def _heartbeat_loop():
|
|
|
545
620
|
except Exception as e:
|
|
546
621
|
log.warning(f"lease renewal failed: {e}")
|
|
547
622
|
|
|
548
|
-
|
|
623
|
+
_heartbeat_stop.wait(30) # sleep but interruptible on shutdown
|
|
549
624
|
|
|
550
625
|
|
|
551
626
|
@asynccontextmanager
|
|
@@ -560,17 +635,30 @@ async def lifespan(_app):
|
|
|
560
635
|
notify_callback=_on_new_message,
|
|
561
636
|
)
|
|
562
637
|
await _REALTIME.start()
|
|
563
|
-
|
|
564
|
-
|
|
638
|
+
|
|
639
|
+
# IMMEDIATE: send first heartbeat + set online status BEFORE any tool calls.
|
|
640
|
+
# Without this, the agent appears offline for up to 30s after boot.
|
|
641
|
+
for _attempt in range(3):
|
|
642
|
+
try:
|
|
643
|
+
be.sb_rpc("mc_heartbeat", {"p_project_id": _PROJECT_ID, "p_agent_name": AGENT_NAME, "p_version": "2.0.0"})
|
|
644
|
+
be.set_status(_PROJECT_ID, AGENT_NAME, "idle", "MCP session active")
|
|
645
|
+
log.info(f"[meshcode] Agent {AGENT_NAME} online — initial heartbeat sent")
|
|
646
|
+
break
|
|
647
|
+
except Exception as e:
|
|
648
|
+
log.warning(f"initial heartbeat attempt {_attempt+1} failed: {e}")
|
|
649
|
+
import time; time.sleep(2)
|
|
650
|
+
|
|
651
|
+
# Heartbeat in daemon thread — independent of asyncio event loop.
|
|
652
|
+
_heartbeat_stop.clear()
|
|
653
|
+
hb_thread = _threading.Thread(target=_heartbeat_thread_fn, daemon=True, name="meshcode-heartbeat")
|
|
654
|
+
hb_thread.start()
|
|
655
|
+
log.info(f"lifespan started — Realtime + heartbeat thread active for {AGENT_NAME}")
|
|
565
656
|
try:
|
|
566
657
|
yield {"realtime": _REALTIME}
|
|
567
658
|
finally:
|
|
568
659
|
log.info("lifespan shutdown — stopping heartbeat + realtime + releasing lease")
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
await hb_task
|
|
572
|
-
except asyncio.CancelledError:
|
|
573
|
-
pass
|
|
660
|
+
_heartbeat_stop.set()
|
|
661
|
+
hb_thread.join(timeout=5)
|
|
574
662
|
await _REALTIME.stop()
|
|
575
663
|
# Flip to offline + release lease so the dashboard reflects reality
|
|
576
664
|
# within seconds (not waiting for the 30s cron to notice).
|
|
@@ -786,15 +874,32 @@ def meshcode_done(reason: str) -> Dict[str, Any]:
|
|
|
786
874
|
@mcp.tool()
|
|
787
875
|
@with_working_status
|
|
788
876
|
def meshcode_check(include_acks: bool = False) -> Dict[str, Any]:
|
|
789
|
-
"""Non-blocking: returns pending message count +
|
|
790
|
-
Realtime listener since the last check.
|
|
877
|
+
"""Non-blocking: returns pending message count + any new messages.
|
|
791
878
|
|
|
792
|
-
|
|
793
|
-
|
|
879
|
+
Checks realtime buffer first, then falls back to DB if buffer is empty
|
|
880
|
+
but there are pending messages (handles messages that arrived before
|
|
881
|
+
the realtime listener connected).
|
|
794
882
|
"""
|
|
795
883
|
pending = be.count_pending(_PROJECT_ID, AGENT_NAME)
|
|
796
884
|
realtime_buffered = _REALTIME.drain() if _REALTIME else []
|
|
797
885
|
deduped = _filter_and_mark(realtime_buffered)
|
|
886
|
+
|
|
887
|
+
# Fallback: if realtime buffer is empty but DB has pending messages,
|
|
888
|
+
# fetch them from the DB so they're not invisible to the agent.
|
|
889
|
+
if not deduped and pending > 0:
|
|
890
|
+
raw = be.read_inbox(_PROJECT_ID, AGENT_NAME)
|
|
891
|
+
deduped = _filter_and_mark([
|
|
892
|
+
{
|
|
893
|
+
"from": m["from_agent"],
|
|
894
|
+
"type": m.get("type", "msg"),
|
|
895
|
+
"ts": m.get("created_at"),
|
|
896
|
+
"payload": m.get("payload", {}),
|
|
897
|
+
"id": m.get("id"),
|
|
898
|
+
"parent_id": m.get("parent_msg_id"),
|
|
899
|
+
}
|
|
900
|
+
for m in raw
|
|
901
|
+
])
|
|
902
|
+
|
|
798
903
|
split = _split_messages(deduped)
|
|
799
904
|
if not include_acks:
|
|
800
905
|
split["acks"] = []
|
|
@@ -1271,7 +1376,6 @@ def meshcode_forget(key: str) -> Dict[str, Any]:
|
|
|
1271
1376
|
|
|
1272
1377
|
# ----------------- RESOURCES -----------------
|
|
1273
1378
|
|
|
1274
|
-
@mcp.resource("meshcode://inbox")
|
|
1275
1379
|
@mcp.tool()
|
|
1276
1380
|
def meshcode_auto_wake(enabled: bool) -> Dict[str, Any]:
|
|
1277
1381
|
"""Toggle auto-wake: when enabled, if this agent receives a mesh message
|
|
@@ -32,6 +32,77 @@ WORKSPACES_ROOT = Path.home() / "meshcode"
|
|
|
32
32
|
REGISTRY_PATH = WORKSPACES_ROOT / ".registry.json"
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def _try_auto_setup(agent: str, project: Optional[str] = None) -> Optional[Tuple[Path, str]]:
|
|
36
|
+
"""If agent exists on the server but has no local workspace, auto-create it.
|
|
37
|
+
|
|
38
|
+
Returns (workspace_path, project_name) on success, None on failure.
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
from .setup_clients import _load_supabase_env, setup_workspace
|
|
42
|
+
import importlib
|
|
43
|
+
secrets_mod = importlib.import_module("meshcode.secrets")
|
|
44
|
+
except Exception:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
api_key = secrets_mod.get_api_key(profile="default")
|
|
48
|
+
if not api_key:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
sb = _load_supabase_env()
|
|
52
|
+
|
|
53
|
+
# Ask the server which project(s) this agent belongs to
|
|
54
|
+
try:
|
|
55
|
+
from urllib.request import Request, urlopen
|
|
56
|
+
body = json.dumps({"p_api_key": api_key, "p_agent_name": agent}).encode()
|
|
57
|
+
req = Request(
|
|
58
|
+
f"{sb['SUPABASE_URL']}/rest/v1/rpc/mc_resolve_agent_projects",
|
|
59
|
+
data=body,
|
|
60
|
+
method="POST",
|
|
61
|
+
headers={
|
|
62
|
+
"apikey": sb["SUPABASE_KEY"],
|
|
63
|
+
"Authorization": f"Bearer {sb['SUPABASE_KEY']}",
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
with urlopen(req, timeout=10) as resp:
|
|
68
|
+
data = json.loads(resp.read().decode())
|
|
69
|
+
except Exception:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
if not isinstance(data, dict) or data.get("error"):
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
projects = data.get("projects", [])
|
|
76
|
+
if not projects:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# If user specified a project, filter to that one
|
|
80
|
+
if project:
|
|
81
|
+
projects = [p for p in projects if p["project_name"] == project]
|
|
82
|
+
if not projects:
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
if len(projects) > 1:
|
|
86
|
+
print(f"[meshcode] Agent '{agent}' exists in multiple projects:", file=sys.stderr)
|
|
87
|
+
for p in projects:
|
|
88
|
+
print(f"[meshcode] meshcode run {agent} --project {p['project_name']}", file=sys.stderr)
|
|
89
|
+
print(f"[meshcode] Specify which one with --project.", file=sys.stderr)
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
resolved_project = projects[0]["project_name"]
|
|
93
|
+
role = projects[0].get("role", "")
|
|
94
|
+
|
|
95
|
+
print(f"[meshcode] Workspace recreated automatically for agent '{agent}' (project: {resolved_project})")
|
|
96
|
+
rc = setup_workspace(resolved_project, agent, role)
|
|
97
|
+
if rc != 0:
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
ws = WORKSPACES_ROOT / f"{resolved_project}-{agent}"
|
|
101
|
+
if ws.exists():
|
|
102
|
+
return ws, resolved_project
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
35
106
|
def _load_registry() -> dict:
|
|
36
107
|
if not REGISTRY_PATH.exists():
|
|
37
108
|
return {}
|
|
@@ -41,11 +112,12 @@ def _load_registry() -> dict:
|
|
|
41
112
|
return {}
|
|
42
113
|
|
|
43
114
|
|
|
44
|
-
def _find_agent_workspace(agent: str, project: Optional[str] = None) -> Optional[Tuple[Path, str]]:
|
|
115
|
+
def _find_agent_workspace(agent: str, project: Optional[str] = None, quiet: bool = False) -> Optional[Tuple[Path, str]]:
|
|
45
116
|
"""Look up the agent in the registry. Returns (workspace_path, project_name) or None.
|
|
46
117
|
|
|
47
118
|
If multiple agents share the same name across projects, requires the
|
|
48
119
|
user to disambiguate by passing project explicitly.
|
|
120
|
+
When quiet=True, suppresses error messages (used before auto-setup fallback).
|
|
49
121
|
"""
|
|
50
122
|
reg = _load_registry()
|
|
51
123
|
agents = reg.get("agents", {})
|
|
@@ -55,11 +127,11 @@ def _find_agent_workspace(agent: str, project: Optional[str] = None) -> Optional
|
|
|
55
127
|
if info:
|
|
56
128
|
ws = Path(info["workspace"])
|
|
57
129
|
if not ws.exists():
|
|
58
|
-
print
|
|
59
|
-
print(f"[meshcode] Re-run: meshcode setup {info.get('project','<project>')} {agent}", file=sys.stderr)
|
|
130
|
+
# Don't print error in quiet mode — caller will try auto-setup
|
|
60
131
|
return None
|
|
61
132
|
if project and info.get("project") != project:
|
|
62
|
-
|
|
133
|
+
if not quiet:
|
|
134
|
+
print(f"[meshcode] ERROR: agent '{agent}' belongs to project '{info.get('project')}', not '{project}'", file=sys.stderr)
|
|
63
135
|
return None
|
|
64
136
|
return ws, info.get("project", "")
|
|
65
137
|
|
|
@@ -81,8 +153,6 @@ def _find_agent_workspace(agent: str, project: Optional[str] = None) -> Optional
|
|
|
81
153
|
print(f"[meshcode] Disambiguate: meshcode run {agent} --project <name>", file=sys.stderr)
|
|
82
154
|
return None
|
|
83
155
|
|
|
84
|
-
print(f"[meshcode] ERROR: no workspace found for agent '{agent}'", file=sys.stderr)
|
|
85
|
-
print(f"[meshcode] Run `meshcode setup <project> {agent}` first.", file=sys.stderr)
|
|
86
156
|
return None
|
|
87
157
|
|
|
88
158
|
|
|
@@ -122,9 +192,14 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
122
192
|
print(f"[meshcode] (or copy this exact line into a new terminal: meshcode run {agent})", file=sys.stderr)
|
|
123
193
|
return 2
|
|
124
194
|
|
|
125
|
-
found = _find_agent_workspace(agent, project)
|
|
195
|
+
found = _find_agent_workspace(agent, project, quiet=True)
|
|
126
196
|
if not found:
|
|
127
|
-
|
|
197
|
+
# Auto-setup: if agent exists on the server, recreate workspace
|
|
198
|
+
found = _try_auto_setup(agent, project)
|
|
199
|
+
if not found:
|
|
200
|
+
print(f"[meshcode] ERROR: no workspace found for agent '{agent}'", file=sys.stderr)
|
|
201
|
+
print(f"[meshcode] Run `meshcode setup <project> {agent}` first.", file=sys.stderr)
|
|
202
|
+
return 2
|
|
128
203
|
ws, resolved_project = found
|
|
129
204
|
server_id = f"meshcode-{resolved_project}-{agent}"
|
|
130
205
|
|
|
@@ -183,8 +258,14 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
183
258
|
cmd = [editor, str(ws)]
|
|
184
259
|
|
|
185
260
|
try:
|
|
186
|
-
|
|
187
|
-
|
|
261
|
+
if sys.platform == "win32":
|
|
262
|
+
# Windows: no execvp, use subprocess and wait
|
|
263
|
+
import subprocess as _sp
|
|
264
|
+
result = _sp.run(cmd)
|
|
265
|
+
sys.exit(result.returncode)
|
|
266
|
+
else:
|
|
267
|
+
# Unix: replace this process with the editor
|
|
268
|
+
os.execvp(cmd[0], cmd)
|
|
188
269
|
except FileNotFoundError:
|
|
189
270
|
print(f"[meshcode] ERROR: '{editor}' not found in PATH", file=sys.stderr)
|
|
190
271
|
return 127
|
|
@@ -120,6 +120,8 @@ def _resolve_project_id(api_key: str, project: str, sb: Dict[str, str]) -> str:
|
|
|
120
120
|
return data["project_id"]
|
|
121
121
|
if isinstance(data, dict) and data.get("error"):
|
|
122
122
|
print(f"[meshcode] ERROR: could not resolve project '{project}': {data['error']}", file=sys.stderr)
|
|
123
|
+
# Try to suggest the user's actual projects
|
|
124
|
+
_suggest_projects(api_key, sb)
|
|
123
125
|
sys.exit(2)
|
|
124
126
|
except Exception as e:
|
|
125
127
|
print(f"[meshcode] ERROR: could not resolve project '{project}': {e}", file=sys.stderr)
|
|
@@ -128,6 +130,32 @@ def _resolve_project_id(api_key: str, project: str, sb: Dict[str, str]) -> str:
|
|
|
128
130
|
return ""
|
|
129
131
|
|
|
130
132
|
|
|
133
|
+
def _suggest_projects(api_key: str, sb: dict):
|
|
134
|
+
"""Try to list the user's projects to help with typos."""
|
|
135
|
+
try:
|
|
136
|
+
from urllib.request import Request as _Req, urlopen as _urlopen
|
|
137
|
+
body = json.dumps({"p_api_key": api_key}).encode()
|
|
138
|
+
req = _Req(
|
|
139
|
+
f"{sb['SUPABASE_URL']}/rest/v1/rpc/mc_list_user_projects",
|
|
140
|
+
data=body,
|
|
141
|
+
method="POST",
|
|
142
|
+
headers={
|
|
143
|
+
"apikey": sb["SUPABASE_KEY"],
|
|
144
|
+
"Authorization": f"Bearer {sb['SUPABASE_KEY']}",
|
|
145
|
+
"Content-Type": "application/json",
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
with _urlopen(req, timeout=10) as resp:
|
|
149
|
+
data = json.loads(resp.read().decode())
|
|
150
|
+
projects = data.get("projects", []) if isinstance(data, dict) else []
|
|
151
|
+
if projects:
|
|
152
|
+
print(f"[meshcode] Your projects:", file=sys.stderr)
|
|
153
|
+
for p in projects:
|
|
154
|
+
print(f"[meshcode] - {p['name']}", file=sys.stderr)
|
|
155
|
+
except Exception:
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
|
|
131
159
|
def _build_server_block(project: str, project_id: str, agent: str, role: str,
|
|
132
160
|
api_key: str, sb: Dict[str, str],
|
|
133
161
|
keychain_profile: str = "default") -> Dict[str, Any]:
|
|
@@ -588,12 +616,29 @@ def setup(*args) -> int:
|
|
|
588
616
|
return 1
|
|
589
617
|
|
|
590
618
|
if args[0] in CLIENT_CONFIG_PATHS:
|
|
619
|
+
if args[0] == "claude-desktop":
|
|
620
|
+
# Claude Desktop needs global config — only exception
|
|
621
|
+
if len(args) < 3:
|
|
622
|
+
print(f"[meshcode] Missing agent name.", file=sys.stderr)
|
|
623
|
+
if len(args) >= 2:
|
|
624
|
+
print(f"[meshcode] Usage: meshcode setup claude-desktop {args[1]} <agent-name>", file=sys.stderr)
|
|
625
|
+
else:
|
|
626
|
+
print(f"[meshcode] Usage: meshcode setup claude-desktop <project> <agent>", file=sys.stderr)
|
|
627
|
+
return 1
|
|
628
|
+
return setup_global(args[0], args[1], args[2], args[3] if len(args) > 3 else "")
|
|
629
|
+
# All other clients: redirect to workspace flow (no global pollution)
|
|
630
|
+
print(f"[meshcode] NOTE: 'meshcode setup {args[0]}' is deprecated. Using workspace flow.", file=sys.stderr)
|
|
591
631
|
if len(args) < 3:
|
|
592
|
-
print("
|
|
632
|
+
print(f"[meshcode] Missing agent name.", file=sys.stderr)
|
|
633
|
+
if len(args) >= 2:
|
|
634
|
+
print(f"[meshcode] Usage: meshcode setup {args[1]} <agent-name>", file=sys.stderr)
|
|
635
|
+
else:
|
|
636
|
+
print(f"[meshcode] Usage: meshcode setup <project> <agent>", file=sys.stderr)
|
|
593
637
|
return 1
|
|
594
|
-
return
|
|
638
|
+
return setup_workspace(args[1], args[2], args[3] if len(args) > 3 else "")
|
|
595
639
|
|
|
596
640
|
if len(args) < 2:
|
|
597
|
-
print("
|
|
641
|
+
print(f"[meshcode] Missing agent name.", file=sys.stderr)
|
|
642
|
+
print(f"[meshcode] Usage: meshcode setup {args[0]} <agent-name>", file=sys.stderr)
|
|
598
643
|
return 1
|
|
599
644
|
return setup_workspace(args[0], args[1], args[2] if len(args) > 2 else "")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcode
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.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
|
|
@@ -158,6 +158,29 @@ meshcode revoke-member my-project <user> # kick a member instantly
|
|
|
158
158
|
|
|
159
159
|
---
|
|
160
160
|
|
|
161
|
+
## Agent account management
|
|
162
|
+
|
|
163
|
+
Your AI agents can manage your MeshCode account from inside the mesh. Just tell your agent what you need:
|
|
164
|
+
|
|
165
|
+
- **"Create a backend agent"** → agent calls `meshcode_create_meshwork` + `meshcode_add_agent`
|
|
166
|
+
- **"Change the frontend role to UI designer"** → agent calls `meshcode_edit_agent`
|
|
167
|
+
- **"Give the backend agent a note about our conventions"** → agent calls `meshcode_edit_memory`
|
|
168
|
+
|
|
169
|
+
Available MCP tools for agents:
|
|
170
|
+
|
|
171
|
+
| Tool | What it does |
|
|
172
|
+
|------|-------------|
|
|
173
|
+
| `meshcode_create_meshwork(name)` | Create a new meshwork |
|
|
174
|
+
| `meshcode_add_agent(name, role)` | Add an agent to the current meshwork |
|
|
175
|
+
| `meshcode_edit_agent(name, role?, launch_prompt?)` | Update agent role or system prompt |
|
|
176
|
+
| `meshcode_edit_memory(agent_name, key, value)` | Edit another agent's persistent memory |
|
|
177
|
+
| `meshcode_scratchpad_set(key, value)` | Write to shared meshwork memory |
|
|
178
|
+
| `meshcode_link(target_meshwork)` | Link two meshworks for cross-mesh communication |
|
|
179
|
+
|
|
180
|
+
The agent will always tell you what CLI command to run next (e.g., "Open a new terminal and run `meshcode run backend`").
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
161
184
|
## Editor support
|
|
162
185
|
|
|
163
186
|
| Editor | Auto-detected? | Config file written |
|
|
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
|