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.
Files changed (32) hide show
  1. {meshcode-2.10.32 → meshcode-2.10.34}/PKG-INFO +1 -1
  2. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/__init__.py +1 -1
  3. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/server.py +17 -5
  4. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/run_agent.py +90 -59
  5. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/PKG-INFO +1 -1
  6. {meshcode-2.10.32 → meshcode-2.10.34}/pyproject.toml +1 -1
  7. {meshcode-2.10.32 → meshcode-2.10.34}/README.md +0 -0
  8. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/ascii_art.py +0 -0
  9. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/cli.py +0 -0
  10. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/comms_v4.py +0 -0
  11. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/invites.py +0 -0
  12. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/launcher.py +0 -0
  13. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/launcher_install.py +0 -0
  14. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/__init__.py +0 -0
  15. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/__main__.py +0 -0
  16. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/backend.py +0 -0
  17. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/realtime.py +0 -0
  18. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/test_backend.py +0 -0
  19. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  20. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  21. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/preferences.py +0 -0
  22. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/protocol_v2.py +0 -0
  23. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/secrets.py +0 -0
  24. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/self_update.py +0 -0
  25. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode/setup_clients.py +0 -0
  26. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/SOURCES.txt +0 -0
  27. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.10.32 → meshcode-2.10.34}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.10.32 → meshcode-2.10.34}/setup.cfg +0 -0
  32. {meshcode-2.10.32 → meshcode-2.10.34}/tests/test_status_enum_coverage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.32
3
+ Version: 2.10.34
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -1,2 +1,2 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.10.32"
2
+ __version__ = "2.10.34"
@@ -1270,11 +1270,23 @@ async def lifespan(_app):
1270
1270
  # FastMCP server
1271
1271
  # ============================================================
1272
1272
 
1273
- mcp = FastMCP(
1274
- name=f"meshcode-{PROJECT_NAME}-{AGENT_NAME}",
1275
- instructions=_INSTRUCTIONS,
1276
- lifespan=lifespan,
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 None # Can't check let the RPC catch it later
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 None # No keyRPC will reject later
189
+ return "not logged inrun `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 None # Network error let the RPC catch it later
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 _try_auto_setup(agent: str, project: Optional[str] = None) -> Optional[Tuple[Path, str]]:
220
- """If agent exists on the server but has no local workspace, auto-create it.
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 (workspace_path, project_name) on success, None on failure.
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, setup_workspace
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
- import os as _os
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
- projects = data.get("projects", [])
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
- # If user specified a project, filter to that one
266
- if project:
267
- projects = [p for p in projects if p["project_name"] == project]
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 = projects[0]["project_name"]
279
- role = projects[0].get("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: Optional[str] = None, quiet: bool = False) -> Optional[Tuple[Path, str]]:
302
- """Look up the agent in the registry. Returns (workspace_path, project_name) or None.
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
- If multiple agents share the same name across projects, requires the
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
- else:
330
- # Scan all workspaces for any matching <*>-<agent>
331
- if WORKSPACES_ROOT.exists():
332
- matches = [p for p in WORKSPACES_ROOT.iterdir() if p.is_dir() and p.name.endswith(f"-{agent}")]
333
- if len(matches) == 1:
334
- return matches[0], matches[0].name.rsplit(f"-{agent}", 1)[0]
335
- if len(matches) > 1:
336
- print(f"[meshcode] ERROR: agent '{agent}' exists in multiple projects:", file=sys.stderr)
337
- for m in matches:
338
- print(f"[meshcode] {m.name}", file=sys.stderr)
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: no workspace found for agent '{agent}'", file=sys.stderr)
415
- print(f"[meshcode] Run `meshcode setup <project> {agent}` first.", file=sys.stderr)
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.32
3
+ Version: 2.10.34
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.10.32"
7
+ version = "2.10.34"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes
File without changes