meshcode 1.8.8__tar.gz → 1.8.9__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 (29) hide show
  1. {meshcode-1.8.8 → meshcode-1.8.9}/PKG-INFO +1 -1
  2. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/__init__.py +1 -1
  3. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/comms_v4.py +8 -8
  4. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/server.py +70 -5
  5. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/run_agent.py +0 -1
  6. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/setup_clients.py +22 -2
  7. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/PKG-INFO +1 -1
  8. {meshcode-1.8.8 → meshcode-1.8.9}/pyproject.toml +1 -1
  9. {meshcode-1.8.8 → meshcode-1.8.9}/README.md +0 -0
  10. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/cli.py +0 -0
  11. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/invites.py +0 -0
  12. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/launcher.py +0 -0
  13. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/launcher_install.py +0 -0
  14. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/__init__.py +0 -0
  15. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/__main__.py +0 -0
  16. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/backend.py +0 -0
  17. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/realtime.py +0 -0
  18. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/test_backend.py +0 -0
  19. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  20. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/preferences.py +0 -0
  21. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/protocol_v2.py +0 -0
  22. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/secrets.py +0 -0
  23. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/self_update.py +0 -0
  24. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/SOURCES.txt +0 -0
  25. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/dependency_links.txt +0 -0
  26. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/entry_points.txt +0 -0
  27. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/requires.txt +0 -0
  28. {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/top_level.txt +0 -0
  29. {meshcode-1.8.8 → meshcode-1.8.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 1.8.8
3
+ Version: 1.8.9
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__ = "1.8.8"
2
+ __version__ = "1.8.9"
@@ -190,7 +190,7 @@ def log_msg(text):
190
190
  try:
191
191
  with open(LOG_FILE, "a") as f:
192
192
  f.write(f"[{now()}] {text}\n")
193
- except:
193
+ except (IOError, OSError):
194
194
  pass
195
195
 
196
196
 
@@ -276,7 +276,7 @@ def can_nudge(project, name):
276
276
  last = float(nf.read_text().strip())
277
277
  if (time.time() - last) < NUDGE_COOLDOWN:
278
278
  return False
279
- except:
279
+ except (ValueError, IOError, OSError):
280
280
  pass
281
281
  return True
282
282
 
@@ -311,7 +311,7 @@ def send_notification(project, name, from_agent, pending=1):
311
311
  else:
312
312
  # Linux
313
313
  subprocess.run(['notify-send', title, body], capture_output=True, timeout=3)
314
- except:
314
+ except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError):
315
315
  pass
316
316
 
317
317
 
@@ -647,7 +647,7 @@ def nudge_agent(project, name, from_agent=""):
647
647
 
648
648
  try:
649
649
  data = json.loads(session_file.read_text())
650
- except:
650
+ except (json.JSONDecodeError, IOError, OSError):
651
651
  if _headless_spawn_allowed():
652
652
  log_msg(f"[{project}] NUDGE: {name} session file unreadable, spawning headless (opt-in)")
653
653
  ok = _spawn_headless_for_pending(project, name, from_agent)
@@ -753,7 +753,7 @@ end tell
753
753
  capture_output=True, text=True, timeout=10)
754
754
  if result.returncode == 0:
755
755
  success = True
756
- except:
756
+ except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError):
757
757
  pass
758
758
 
759
759
  if success:
@@ -786,7 +786,7 @@ def get_session_info():
786
786
  check_pid = parent
787
787
  else:
788
788
  break
789
- except:
789
+ except (subprocess.SubprocessError, OSError, ValueError):
790
790
  pass
791
791
 
792
792
  if not my_tty:
@@ -799,7 +799,7 @@ def get_session_info():
799
799
  session_tty = data.get("tty", "")
800
800
  if session_tty and (session_tty in my_tty or my_tty in session_tty):
801
801
  return data.get("project"), data.get("agent")
802
- except:
802
+ except (json.JSONDecodeError, IOError, OSError):
803
803
  continue
804
804
 
805
805
  return None, None
@@ -835,7 +835,7 @@ def register(project, name, role=""):
835
835
  check_pid = parent
836
836
  else:
837
837
  break
838
- except:
838
+ except (subprocess.SubprocessError, OSError, ValueError):
839
839
  pass
840
840
 
841
841
  # Register agent via tier-aware RPC (enforces plan limits)
@@ -221,24 +221,32 @@ if not _flip_status("idle", ""):
221
221
  # Fire-and-forget flips so tool execution is never blocked.
222
222
  # ============================================================
223
223
  import functools as _functools
224
+ import threading as _threading
225
+
226
+ _flip_lock = _threading.Lock()
224
227
 
225
228
 
226
229
  async def _async_flip_status(status: str, task: str = "") -> None:
227
230
  try:
228
- await asyncio.to_thread(_flip_status, status, task)
231
+ await asyncio.to_thread(_flip_status_locked, status, task)
229
232
  except Exception as e:
230
233
  log.debug(f"flip_status({status}) failed: {e}")
231
234
 
232
235
 
236
+ def _flip_status_locked(status: str, task: str = "") -> bool:
237
+ """Thread-safe wrapper around _flip_status to prevent concurrent RPCs."""
238
+ with _flip_lock:
239
+ return _flip_status(status, task)
240
+
241
+
233
242
  def _schedule_flip(status: str, task: str = "") -> None:
234
243
  try:
235
244
  loop = asyncio.get_running_loop()
236
245
  loop.create_task(_async_flip_status(status, task))
237
246
  except RuntimeError:
238
247
  # No running loop (sync context) — run in a throwaway thread
239
- import threading
240
- threading.Thread(
241
- target=_flip_status, args=(status, task), daemon=True
248
+ _threading.Thread(
249
+ target=_flip_status_locked, args=(status, task), daemon=True
242
250
  ).start()
243
251
 
244
252
 
@@ -434,6 +442,38 @@ LOOP-SAFETY RULES (IMPORTANT — prevent token-burning ping-pong):
434
442
  - If you find yourself sending more than ~10 messages on the same topic
435
443
  in a row, stop and reassess — you may be in a feedback loop.
436
444
 
445
+ COMMUNICATION EFFICIENCY PROTOCOL (MANDATORY):
446
+
447
+ 1. TASKS OVER MESSAGES: use meshcode_task_create/claim/complete for all
448
+ trackable work assignments, NOT messages. Messages are only for short
449
+ signals: "review needed", "blocked on X", "approved", "question: ...".
450
+ The Tasks panel in the dashboard is where the human sees progress — if
451
+ there are no tasks, the human has no visibility into what you're doing.
452
+
453
+ 2. COMPRESSED REPORTS: when reporting findings, use structured JSON in the
454
+ payload, not prose:
455
+ {{"findings": [{{"severity":"high","file":"X","issue":"Y","fix":"Z"}}],
456
+ "commits":["abc1234"], "next":"awaiting_review"}}
457
+
458
+ 3. NO PROSE PADDING: never write "As you asked, I reviewed the schema and
459
+ found that..." — go straight to the data. Max 2 sentences of context,
460
+ then structured output. Every token costs money.
461
+
462
+ 4. SIGNAL-ONLY MESSAGES: meshcode_send messages must be <100 tokens. If you
463
+ need to communicate more, create a task with the full description and
464
+ send a signal: "task created: <title>".
465
+
466
+ 5. TASK STATUS AS SOURCE OF TRUTH: always create tasks for trackable work.
467
+ Claim them when you start. Complete them when done. The human watches the
468
+ task board, not the chat. No task = invisible work = wasted effort.
469
+
470
+ 6. POLL TASKS, DON'T WAIT FOR MESSAGES: when coordinating, use
471
+ meshcode_tasks() to check task status instead of waiting for a message.
472
+ If you assigned a task, poll the board to see if it's done — don't
473
+ assume the agent will message you. Messages can be missed; task status
474
+ is persistent and reliable. Check meshcode_tasks() before each
475
+ meshcode_wait() call to avoid stale waits.
476
+
437
477
  YOUR FIRST ACTIONS WHEN THIS SESSION STARTS:
438
478
  1. Call meshcode_status() once to see who else is in the meshwork.
439
479
  2. Call meshcode_set_status(status="online", task="ready") to announce
@@ -505,12 +545,37 @@ async def _on_new_message(msg: Dict[str, Any]) -> None:
505
545
 
506
546
 
507
547
  async def _heartbeat_loop():
548
+ """Send heartbeat every 30s via HTTP. Also renews the agent lease and
549
+ logs when the WebSocket is disconnected (heartbeat still works via HTTP
550
+ so the agent stays 'online' in the DB even during WS reconnects)."""
551
+ _lease_counter = 0
508
552
  while True:
509
553
  try:
510
554
  be.sb_rpc("mc_heartbeat", {"p_project_id": _PROJECT_ID, "p_agent_name": AGENT_NAME})
511
- log.debug(f"heartbeat ok for {AGENT_NAME}")
555
+ # Log WS health for diagnostics
556
+ if _REALTIME and not _REALTIME.is_connected:
557
+ log.warning("heartbeat ok (HTTP) but WebSocket is disconnected — realtime messages may be delayed")
558
+ else:
559
+ log.debug(f"heartbeat ok for {AGENT_NAME}")
512
560
  except Exception as e:
513
561
  log.warning(f"heartbeat failed: {e}")
562
+
563
+ # Renew lease every ~2 minutes (every 4th heartbeat) to prevent
564
+ # another instance from stealing it during long sessions.
565
+ _lease_counter += 1
566
+ if _lease_counter % 4 == 0:
567
+ try:
568
+ api_key = _get_api_key()
569
+ if api_key:
570
+ be.sb_rpc("mc_acquire_agent_lease", {
571
+ "p_api_key": api_key,
572
+ "p_project_id": _PROJECT_ID,
573
+ "p_agent_name": AGENT_NAME,
574
+ "p_instance_id": _INSTANCE_ID,
575
+ })
576
+ except Exception as e:
577
+ log.warning(f"lease renewal failed: {e}")
578
+
514
579
  await asyncio.sleep(30)
515
580
 
516
581
 
@@ -148,7 +148,6 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
148
148
  cmd = [
149
149
  editor,
150
150
  "--mcp-config", str(ws / ".mcp.json"),
151
- "--strict-mcp-config",
152
151
  ]
153
152
  if mode == "bypass":
154
153
  cmd.append("--dangerously-skip-permissions")
@@ -300,10 +300,30 @@ def setup_workspace(project: str, agent: str, role: str = "",
300
300
  print(f"[meshcode] ERROR: cannot load secrets module: {e}", file=sys.stderr)
301
301
  return 2
302
302
 
303
+ if not api_key and keychain_profile == "default" and sys.stdin.isatty():
304
+ # No key in keychain — prompt inline instead of failing
305
+ print("[meshcode] No API key found. You can get one from meshcode.io/settings.", file=sys.stderr)
306
+ try:
307
+ api_key = input("[meshcode] Paste your API key (mc_...): ").strip()
308
+ except (EOFError, KeyboardInterrupt):
309
+ api_key = ""
310
+ if api_key:
311
+ # Store it so they never have to paste again
312
+ try:
313
+ import importlib as _il
314
+ _login = _il.import_module("meshcode.comms_v4").login
315
+ _login(api_key)
316
+ except Exception:
317
+ # Fallback: just store raw in keychain
318
+ try:
319
+ secrets_mod.set_api_key(api_key, profile="default")
320
+ except Exception:
321
+ pass
322
+
303
323
  if not api_key:
304
- print(f"[meshcode] ERROR: no api key found in keychain profile '{keychain_profile}'", file=sys.stderr)
324
+ print(f"[meshcode] ERROR: no api key found.", file=sys.stderr)
305
325
  if keychain_profile == "default":
306
- print("[meshcode] Run `meshcode login <api_key>` first.", file=sys.stderr)
326
+ print("[meshcode] Run `meshcode login <api_key>` or paste it when prompted.", file=sys.stderr)
307
327
  else:
308
328
  print(f"[meshcode] This profile is created by `meshcode join <token>`.", file=sys.stderr)
309
329
  return 2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 1.8.8
3
+ Version: 1.8.9
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 = "1.8.8"
7
+ version = "1.8.9"
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
File without changes
File without changes
File without changes