meshcode 2.10.32__tar.gz → 2.10.34__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.32 → meshcode-2.10.34}/PKG-INFO +1 -1
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/__init__.py +1 -1
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/server.py +17 -5
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/run_agent.py +90 -59
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.10.32 → meshcode-2.10.34}/pyproject.toml +1 -1
- {meshcode-2.10.32 → meshcode-2.10.34}/README.md +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/cli.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/comms_v4.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/invites.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/launcher.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/preferences.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/secrets.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/self_update.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/setup_clients.py +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/setup.cfg +0 -0
- {meshcode-2.10.32 → meshcode-2.10.34}/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.34"
|
|
@@ -1270,11 +1270,23 @@ async def lifespan(_app):
|
|
|
1270
1270
|
# FastMCP server
|
|
1271
1271
|
# ============================================================
|
|
1272
1272
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1273
|
+
_DISABLE_LIFESPAN = os.environ.get("MESHCODE_DISABLE_LIFESPAN", "").lower() in ("1", "true", "yes")
|
|
1274
|
+
if _DISABLE_LIFESPAN:
|
|
1275
|
+
# Diagnostic mode (2.10.33): bypass lifespan entirely to isolate whether
|
|
1276
|
+
# the Realtime + heartbeat + boot-event machinery is what dies under ESC.
|
|
1277
|
+
# Realtime won't subscribe; meshcode_wait will degrade to DB polling.
|
|
1278
|
+
# meshcode_debug_sleep still works and is our smoke test.
|
|
1279
|
+
mcp = FastMCP(
|
|
1280
|
+
name=f"meshcode-{PROJECT_NAME}-{AGENT_NAME}",
|
|
1281
|
+
instructions=_INSTRUCTIONS,
|
|
1282
|
+
)
|
|
1283
|
+
print("[meshcode-mcp] LIFESPAN DISABLED (MESHCODE_DISABLE_LIFESPAN=1) — diagnostic mode", file=sys.stderr, flush=True)
|
|
1284
|
+
else:
|
|
1285
|
+
mcp = FastMCP(
|
|
1286
|
+
name=f"meshcode-{PROJECT_NAME}-{AGENT_NAME}",
|
|
1287
|
+
instructions=_INSTRUCTIONS,
|
|
1288
|
+
lifespan=lifespan,
|
|
1289
|
+
)
|
|
1278
1290
|
# Belt-and-suspenders: some FastMCP versions don't forward `instructions=` to
|
|
1279
1291
|
# the underlying lowlevel server's InitializeResult. Set it directly too.
|
|
1280
1292
|
try:
|
|
@@ -177,7 +177,7 @@ def _check_agent_ownership(agent: str, project: str) -> Optional[str]:
|
|
|
177
177
|
import importlib
|
|
178
178
|
secrets_mod = importlib.import_module("meshcode.secrets")
|
|
179
179
|
except Exception:
|
|
180
|
-
return
|
|
180
|
+
return "cannot load auth modules; re-install meshcode"
|
|
181
181
|
|
|
182
182
|
# Try scoped profile first (from invite join), then default
|
|
183
183
|
scoped_profile = f"mesh:{project}:{agent}"
|
|
@@ -186,7 +186,7 @@ def _check_agent_ownership(agent: str, project: str) -> Optional[str]:
|
|
|
186
186
|
profile = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
|
|
187
187
|
api_key = secrets_mod.get_api_key(profile=profile)
|
|
188
188
|
if not api_key:
|
|
189
|
-
return
|
|
189
|
+
return "not logged in — run `meshcode login <api_key>` first"
|
|
190
190
|
|
|
191
191
|
sb = _load_supabase_env()
|
|
192
192
|
try:
|
|
@@ -208,35 +208,35 @@ def _check_agent_ownership(agent: str, project: str) -> Optional[str]:
|
|
|
208
208
|
)
|
|
209
209
|
with urlopen(req, timeout=10) as resp:
|
|
210
210
|
data = json.loads(resp.read().decode())
|
|
211
|
-
except Exception:
|
|
212
|
-
return
|
|
211
|
+
except Exception as e:
|
|
212
|
+
return f"could not verify meshwork access ({e}); refusing to launch"
|
|
213
213
|
|
|
214
214
|
if isinstance(data, dict) and data.get("error"):
|
|
215
215
|
return data["error"]
|
|
216
|
+
if not isinstance(data, dict) or not data.get("ok"):
|
|
217
|
+
return "ownership check returned unexpected response; refusing to launch"
|
|
216
218
|
return None
|
|
217
219
|
|
|
218
220
|
|
|
219
|
-
def
|
|
220
|
-
"""
|
|
221
|
+
def _resolve_user_projects_for_agent(agent: str) -> Optional[list]:
|
|
222
|
+
"""Ask the server which meshworks owned by the current user have this agent.
|
|
221
223
|
|
|
222
|
-
Returns
|
|
224
|
+
Returns list of dicts [{project_name, project_id, role}], or None on auth/network error.
|
|
225
|
+
Always scoped to the authenticated user (RPC enforces owner_id = caller user).
|
|
223
226
|
"""
|
|
224
227
|
try:
|
|
225
|
-
from .setup_clients import _load_supabase_env
|
|
228
|
+
from .setup_clients import _load_supabase_env
|
|
226
229
|
import importlib
|
|
227
230
|
secrets_mod = importlib.import_module("meshcode.secrets")
|
|
228
231
|
except Exception:
|
|
229
232
|
return None
|
|
230
233
|
|
|
231
|
-
|
|
232
|
-
profile = _os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
|
|
234
|
+
profile = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
|
|
233
235
|
api_key = secrets_mod.get_api_key(profile=profile)
|
|
234
236
|
if not api_key:
|
|
235
237
|
return None
|
|
236
238
|
|
|
237
239
|
sb = _load_supabase_env()
|
|
238
|
-
|
|
239
|
-
# Ask the server which project(s) this agent belongs to
|
|
240
240
|
try:
|
|
241
241
|
from urllib.request import Request, urlopen
|
|
242
242
|
body = json.dumps({"p_api_key": api_key, "p_agent_name": agent}).encode()
|
|
@@ -256,27 +256,36 @@ def _try_auto_setup(agent: str, project: Optional[str] = None) -> Optional[Tuple
|
|
|
256
256
|
return None
|
|
257
257
|
|
|
258
258
|
if not isinstance(data, dict) or data.get("error"):
|
|
259
|
+
return []
|
|
260
|
+
projects = data.get("projects") or []
|
|
261
|
+
return projects if isinstance(projects, list) else []
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _try_auto_setup(agent: str, project: str) -> Optional[Tuple[Path, str]]:
|
|
265
|
+
"""If agent exists on the server but has no local workspace, auto-create it.
|
|
266
|
+
|
|
267
|
+
Caller MUST pass an explicit project — this function never auto-picks one.
|
|
268
|
+
Returns (workspace_path, project_name) on success, None on failure.
|
|
269
|
+
"""
|
|
270
|
+
if not project:
|
|
259
271
|
return None
|
|
260
272
|
|
|
261
|
-
|
|
273
|
+
try:
|
|
274
|
+
from .setup_clients import setup_workspace
|
|
275
|
+
except Exception:
|
|
276
|
+
return None
|
|
277
|
+
|
|
278
|
+
projects = _resolve_user_projects_for_agent(agent)
|
|
262
279
|
if not projects:
|
|
263
280
|
return None
|
|
264
281
|
|
|
265
|
-
#
|
|
266
|
-
if project
|
|
267
|
-
|
|
268
|
-
if not projects:
|
|
269
|
-
return None
|
|
270
|
-
|
|
271
|
-
if len(projects) > 1:
|
|
272
|
-
print(f"[meshcode] Agent '{agent}' exists in multiple projects:", file=sys.stderr)
|
|
273
|
-
for p in projects:
|
|
274
|
-
print(f"[meshcode] meshcode run {agent} --project {p['project_name']}", file=sys.stderr)
|
|
275
|
-
print(f"[meshcode] Specify which one with --project.", file=sys.stderr)
|
|
282
|
+
# Filter to the explicitly requested project. Never silently auto-pick.
|
|
283
|
+
match = [p for p in projects if p.get("project_name") == project]
|
|
284
|
+
if not match:
|
|
276
285
|
return None
|
|
277
286
|
|
|
278
|
-
resolved_project =
|
|
279
|
-
role =
|
|
287
|
+
resolved_project = match[0]["project_name"]
|
|
288
|
+
role = match[0].get("role", "")
|
|
280
289
|
|
|
281
290
|
print(f"[meshcode] Workspace recreated automatically for agent '{agent}' (project: {resolved_project})")
|
|
282
291
|
rc = setup_workspace(resolved_project, agent, role)
|
|
@@ -298,50 +307,50 @@ def _load_registry() -> dict:
|
|
|
298
307
|
return {}
|
|
299
308
|
|
|
300
309
|
|
|
301
|
-
def _find_agent_workspace(agent: str, project:
|
|
302
|
-
"""Look up the agent
|
|
310
|
+
def _find_agent_workspace(agent: str, project: str, quiet: bool = False) -> Optional[Tuple[Path, str]]:
|
|
311
|
+
"""Look up the agent's workspace for an EXPLICIT project. Returns (path, project) or None.
|
|
303
312
|
|
|
304
|
-
|
|
305
|
-
user to disambiguate by passing project explicitly.
|
|
313
|
+
Caller MUST pass project — this function never auto-picks across meshworks.
|
|
306
314
|
When quiet=True, suppresses error messages (used before auto-setup fallback).
|
|
307
315
|
"""
|
|
316
|
+
if not project:
|
|
317
|
+
return None
|
|
318
|
+
|
|
308
319
|
reg = _load_registry()
|
|
309
320
|
agents = reg.get("agents", {})
|
|
310
321
|
|
|
311
|
-
# Direct hit by agent name (current model: agent names unique within registry)
|
|
312
322
|
info = agents.get(agent)
|
|
313
|
-
if info:
|
|
323
|
+
if info and info.get("project") == project:
|
|
314
324
|
ws = Path(info["workspace"])
|
|
315
|
-
if not ws.exists():
|
|
316
|
-
# Don't print error in quiet mode — caller will try auto-setup
|
|
317
|
-
return None
|
|
318
|
-
if project and info.get("project") != project:
|
|
319
|
-
if not quiet:
|
|
320
|
-
print(f"[meshcode] ERROR: agent '{agent}' belongs to project '{info.get('project')}', not '{project}'", file=sys.stderr)
|
|
321
|
-
return None
|
|
322
|
-
return ws, info.get("project", "")
|
|
323
|
-
|
|
324
|
-
# Fallback: scan ~/meshcode for any dir matching <project>-<agent>
|
|
325
|
-
if project:
|
|
326
|
-
ws = WORKSPACES_ROOT / f"{project}-{agent}"
|
|
327
325
|
if ws.exists():
|
|
328
326
|
return ws, project
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
print(f"[meshcode] Disambiguate: meshcode run {agent} --project <name>", file=sys.stderr)
|
|
340
|
-
return None
|
|
327
|
+
# Registry points at a missing dir — caller will try auto-setup
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
if info and info.get("project") != project and not quiet:
|
|
331
|
+
print(f"[meshcode] NOTE: registry has agent '{agent}' under project '{info.get('project')}', not '{project}'", file=sys.stderr)
|
|
332
|
+
|
|
333
|
+
# Filesystem fallback: ~/meshcode/<project>-<agent>
|
|
334
|
+
ws = WORKSPACES_ROOT / f"{project}-{agent}"
|
|
335
|
+
if ws.exists():
|
|
336
|
+
return ws, project
|
|
341
337
|
|
|
342
338
|
return None
|
|
343
339
|
|
|
344
340
|
|
|
341
|
+
def _list_local_projects_for_agent(agent: str) -> list:
|
|
342
|
+
"""Return project names whose local workspace dir matches <project>-<agent>."""
|
|
343
|
+
if not WORKSPACES_ROOT.exists():
|
|
344
|
+
return []
|
|
345
|
+
out = []
|
|
346
|
+
for p in WORKSPACES_ROOT.iterdir():
|
|
347
|
+
if p.is_dir() and p.name.endswith(f"-{agent}"):
|
|
348
|
+
proj = p.name.rsplit(f"-{agent}", 1)[0]
|
|
349
|
+
if proj:
|
|
350
|
+
out.append(proj)
|
|
351
|
+
return out
|
|
352
|
+
|
|
353
|
+
|
|
345
354
|
def _detect_editor() -> Optional[str]:
|
|
346
355
|
"""Pick the user's preferred MCP-aware editor."""
|
|
347
356
|
override = os.environ.get("MESHCODE_EDITOR", "").strip().lower()
|
|
@@ -406,13 +415,35 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
406
415
|
print(f"[meshcode] (or copy this exact line into a new terminal: meshcode run {agent})", file=sys.stderr)
|
|
407
416
|
return 2
|
|
408
417
|
|
|
418
|
+
# ── Require explicit meshwork ────────────────────────────────────
|
|
419
|
+
# Agent names are not globally unique (e.g. "commander" exists in many
|
|
420
|
+
# meshworks). Without an explicit meshwork, we cannot safely pick one
|
|
421
|
+
# — even if only one is visible locally, another could exist on the
|
|
422
|
+
# server. Force the user to disambiguate and authenticate against a
|
|
423
|
+
# specific meshwork they own.
|
|
424
|
+
if not project:
|
|
425
|
+
print(f"[meshcode] ERROR: specify which meshwork '{agent}' belongs to.", file=sys.stderr)
|
|
426
|
+
local_projects = _list_local_projects_for_agent(agent)
|
|
427
|
+
server_projects = _resolve_user_projects_for_agent(agent) or []
|
|
428
|
+
candidates = set(local_projects) | {p.get("project_name") for p in server_projects if p.get("project_name")}
|
|
429
|
+
if candidates:
|
|
430
|
+
print(f"[meshcode] '{agent}' is available in these meshworks of yours:", file=sys.stderr)
|
|
431
|
+
for name in sorted(candidates):
|
|
432
|
+
print(f"[meshcode] meshcode run {name}/{agent}", file=sys.stderr)
|
|
433
|
+
else:
|
|
434
|
+
print(f"[meshcode] No meshworks of yours contain agent '{agent}'.", file=sys.stderr)
|
|
435
|
+
print(f"[meshcode] Create one first: meshcode setup <meshwork> {agent}", file=sys.stderr)
|
|
436
|
+
print(f"[meshcode] Usage: meshcode run <meshwork>/{agent} (or --project <meshwork>)", file=sys.stderr)
|
|
437
|
+
return 2
|
|
438
|
+
|
|
409
439
|
found = _find_agent_workspace(agent, project, quiet=True)
|
|
410
440
|
if not found:
|
|
411
|
-
# Auto-setup: if agent exists on the server, recreate workspace
|
|
441
|
+
# Auto-setup: if agent exists on the server under THIS meshwork, recreate workspace
|
|
412
442
|
found = _try_auto_setup(agent, project)
|
|
413
443
|
if not found:
|
|
414
|
-
print(f"[meshcode] ERROR:
|
|
415
|
-
print(f"[meshcode] Run `meshcode setup
|
|
444
|
+
print(f"[meshcode] ERROR: agent '{agent}' not found in meshwork '{project}' for your account.", file=sys.stderr)
|
|
445
|
+
print(f"[meshcode] Run `meshcode setup {project} {agent}` first,", file=sys.stderr)
|
|
446
|
+
print(f"[meshcode] or check the meshwork name at https://meshcode.io", file=sys.stderr)
|
|
416
447
|
return 2
|
|
417
448
|
ws, resolved_project = found
|
|
418
449
|
|
|
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
|