meshcode 2.4.3__tar.gz → 2.4.5__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.4.3 → meshcode-2.4.5}/PKG-INFO +1 -1
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/__init__.py +1 -1
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/meshcode_mcp/server.py +89 -11
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.4.3 → meshcode-2.4.5}/pyproject.toml +1 -1
- {meshcode-2.4.3 → meshcode-2.4.5}/README.md +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/cli.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/comms_v4.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/invites.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/launcher.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/launcher_install.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/preferences.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/run_agent.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/secrets.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/self_update.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode/setup_clients.py +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.4.3 → meshcode-2.4.5}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.4.
|
|
2
|
+
__version__ = "2.4.5"
|
|
@@ -37,14 +37,32 @@ _AUTO_WAKE = os.environ.get("MESHCODE_AUTO_WAKE", "1").lower() not in ("0", "fal
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def _try_auto_wake(from_agent: str, preview: str) -> None:
|
|
40
|
-
"""Inject a nudge into the terminal
|
|
40
|
+
"""Inject a nudge into the terminal ONLY when agent is truly sleeping.
|
|
41
41
|
|
|
42
42
|
macOS: AppleScript → keystroke into Terminal/iTerm2
|
|
43
43
|
Windows: PowerShell SendKeys to the console window
|
|
44
44
|
Linux: xdotool (best-effort)
|
|
45
|
+
|
|
46
|
+
Conditions to fire (ALL must be true):
|
|
47
|
+
- _AUTO_WAKE is enabled
|
|
48
|
+
- NOT in meshcode_wait (wait loop already handles delivery)
|
|
49
|
+
- _current_state is 'sleeping' (truly idle, not working, not just online)
|
|
50
|
+
- LLM CPU is low (not actively generating)
|
|
51
|
+
Otherwise the keystroke would interrupt the user's typing or
|
|
52
|
+
the LLM mid-thought.
|
|
45
53
|
"""
|
|
46
|
-
if
|
|
54
|
+
if not _AUTO_WAKE or _IN_WAIT:
|
|
55
|
+
return
|
|
56
|
+
# Only fire when agent is actually sleeping — never when working or online
|
|
57
|
+
# (working = LLM generating; online = brief post-tool window; both would be interrupted)
|
|
58
|
+
if _current_state != 'sleeping':
|
|
47
59
|
return
|
|
60
|
+
# Belt-and-suspenders: also check parent CPU is low (LLM not generating right now)
|
|
61
|
+
try:
|
|
62
|
+
if _get_parent_cpu() > 5.0:
|
|
63
|
+
return # LLM is actively generating, don't interrupt
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
48
66
|
import subprocess, platform, re
|
|
49
67
|
# Sanitize inputs: strip everything except alphanumeric, spaces, basic punctuation
|
|
50
68
|
safe_agent = re.sub(r'[^a-zA-Z0-9_\- ]', '', from_agent)[:50]
|
|
@@ -183,6 +201,21 @@ except Exception:
|
|
|
183
201
|
_be_path = ""
|
|
184
202
|
|
|
185
203
|
|
|
204
|
+
def _capture_session() -> None:
|
|
205
|
+
"""Stash the MCP session ref for silent auto-wake when agent is idle."""
|
|
206
|
+
global _STASHED_SESSION
|
|
207
|
+
if _STASHED_SESSION is not None:
|
|
208
|
+
return
|
|
209
|
+
try:
|
|
210
|
+
srv = mcp._mcp_server
|
|
211
|
+
ctx = getattr(srv, "request_context", None)
|
|
212
|
+
if ctx and getattr(ctx, "session", None):
|
|
213
|
+
_STASHED_SESSION = ctx.session
|
|
214
|
+
log.info("[meshcode] MCP session stashed for silent auto-wake")
|
|
215
|
+
except Exception:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
|
|
186
219
|
def _check_hot_reload() -> None:
|
|
187
220
|
"""If backend.py was modified since boot/last reload, reimport it."""
|
|
188
221
|
global _be_mtime, be
|
|
@@ -410,6 +443,7 @@ def with_working_status(func):
|
|
|
410
443
|
@_functools.wraps(func)
|
|
411
444
|
async def awrapper(*args, **kwargs):
|
|
412
445
|
_check_hot_reload()
|
|
446
|
+
_capture_session() # stash session on first tool call for silent auto-wake
|
|
413
447
|
if not skip:
|
|
414
448
|
_set_state("working", name)
|
|
415
449
|
_record_event_bg("tool_call", {"tool": name, "args_keys": list(kwargs.keys())})
|
|
@@ -426,6 +460,7 @@ def with_working_status(func):
|
|
|
426
460
|
@_functools.wraps(func)
|
|
427
461
|
def swrapper(*args, **kwargs):
|
|
428
462
|
_check_hot_reload()
|
|
463
|
+
_capture_session() # stash session on first tool call for silent auto-wake
|
|
429
464
|
if not skip:
|
|
430
465
|
_set_state("working", name)
|
|
431
466
|
_record_event_bg("tool_call", {"tool": name, "args_keys": list(kwargs.keys())})
|
|
@@ -680,22 +715,65 @@ async def _on_new_message(msg: Dict[str, Any]) -> None:
|
|
|
680
715
|
# Auto-wake: if agent is idle (not in wait loop), nudge the terminal
|
|
681
716
|
from_agent = msg.get("from") or msg.get("from_agent") or "unknown"
|
|
682
717
|
payload = msg.get("payload") or {}
|
|
683
|
-
|
|
718
|
+
if isinstance(payload, dict):
|
|
719
|
+
# Try common content fields in order of usefulness
|
|
720
|
+
preview = (
|
|
721
|
+
payload.get("text")
|
|
722
|
+
or payload.get("message")
|
|
723
|
+
or payload.get("action")
|
|
724
|
+
or payload.get("context")
|
|
725
|
+
or payload.get("type")
|
|
726
|
+
or (str(next(iter(payload.values()))) if payload else "")
|
|
727
|
+
)
|
|
728
|
+
preview = str(preview) if preview else "(see inbox)"
|
|
729
|
+
else:
|
|
730
|
+
preview = str(payload)
|
|
684
731
|
try:
|
|
685
732
|
_try_auto_wake(from_agent, preview[:60])
|
|
686
733
|
except Exception:
|
|
687
734
|
pass # auto-wake is best-effort, never block message handling
|
|
688
735
|
|
|
736
|
+
# SILENT WAKE: 3-tier strategy, all without OS keystrokes
|
|
737
|
+
# 1. send_resource_updated (lightweight cache invalidation)
|
|
738
|
+
# 2. create_message (sampling) — server PUSH that wakes the LLM (if client supports)
|
|
739
|
+
# 3. AppleScript (handled above) — fallback for clients that don't support either
|
|
689
740
|
try:
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
741
|
+
from pydantic import AnyUrl
|
|
742
|
+
url = AnyUrl("meshcode://inbox")
|
|
743
|
+
session = _STASHED_SESSION
|
|
744
|
+
if session is None:
|
|
745
|
+
srv = mcp._mcp_server
|
|
746
|
+
ctx = getattr(srv, "request_context", None)
|
|
747
|
+
if ctx and getattr(ctx, "session", None):
|
|
748
|
+
session = ctx.session
|
|
749
|
+
if session is not None:
|
|
750
|
+
# 1. resource_updated (always cheap, may not wake)
|
|
751
|
+
try:
|
|
752
|
+
await session.send_resource_updated(url)
|
|
753
|
+
log.info(f"silent wake A: resource_updated sent for msg from {msg.get('from')}")
|
|
754
|
+
except Exception as _re:
|
|
755
|
+
log.debug(f"resource_updated failed: {_re}")
|
|
756
|
+
# 2. sampling/create_message — server PUSH that asks the LLM to think
|
|
757
|
+
# Only attempt if client advertised sampling capability
|
|
758
|
+
try:
|
|
759
|
+
client_caps = getattr(session, "client_capabilities", None)
|
|
760
|
+
if client_caps and getattr(client_caps, "sampling", None):
|
|
761
|
+
from mcp.types import SamplingMessage, TextContent
|
|
762
|
+
nudge_text = f"You have a new mesh message from {from_agent}: {preview[:80]}. Call meshcode_check() to read your inbox, then process it. Stay in meshcode_wait() afterward."
|
|
763
|
+
await session.create_message(
|
|
764
|
+
messages=[SamplingMessage(role="user", content=TextContent(type="text", text=nudge_text))],
|
|
765
|
+
max_tokens=4096,
|
|
766
|
+
system_prompt="A new mesh message arrived while you were idle. Process it now.",
|
|
767
|
+
)
|
|
768
|
+
log.info(f"silent wake B: sampling/create_message PUSHED for msg from {msg.get('from')}")
|
|
769
|
+
except Exception as _ce:
|
|
770
|
+
log.debug(f"sampling/create_message failed: {_ce}")
|
|
696
771
|
except Exception as e:
|
|
697
|
-
log.debug(f"
|
|
772
|
+
log.debug(f"silent wake unavailable: {e}")
|
|
773
|
+
|
|
698
774
|
|
|
775
|
+
# Stashed MCP session for silent auto-wake (works when agent is idle, no active tool call)
|
|
776
|
+
_STASHED_SESSION = None
|
|
699
777
|
|
|
700
778
|
_heartbeat_stop = _threading.Event()
|
|
701
779
|
|
|
@@ -986,7 +1064,7 @@ def _detect_global_done(messages: List[Dict[str, Any]]) -> Optional[Dict[str, An
|
|
|
986
1064
|
|
|
987
1065
|
@mcp.tool()
|
|
988
1066
|
@with_working_status
|
|
989
|
-
async def meshcode_wait(timeout_seconds: int =
|
|
1067
|
+
async def meshcode_wait(timeout_seconds: int = 60, include_acks: bool = False) -> Dict[str, Any]:
|
|
990
1068
|
"""Block until a mesh message arrives or timeout. Your idle state.
|
|
991
1069
|
|
|
992
1070
|
Args:
|
|
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
|