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.
- {meshcode-1.8.8 → meshcode-1.8.9}/PKG-INFO +1 -1
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/__init__.py +1 -1
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/comms_v4.py +8 -8
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/server.py +70 -5
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/run_agent.py +0 -1
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/setup_clients.py +22 -2
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-1.8.8 → meshcode-1.8.9}/pyproject.toml +1 -1
- {meshcode-1.8.8 → meshcode-1.8.9}/README.md +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/cli.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/invites.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/launcher.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/launcher_install.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/preferences.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/protocol_v2.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/secrets.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode/self_update.py +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-1.8.8 → meshcode-1.8.9}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "1.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(
|
|
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
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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
|
|
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>`
|
|
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
|
|
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
|