meshcode 2.4.2__tar.gz → 2.4.4__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.2 → meshcode-2.4.4}/PKG-INFO +1 -1
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/__init__.py +1 -1
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/meshcode_mcp/server.py +72 -11
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.4.2 → meshcode-2.4.4}/pyproject.toml +1 -1
- {meshcode-2.4.2 → meshcode-2.4.4}/README.md +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/cli.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/comms_v4.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/invites.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/launcher.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/launcher_install.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/preferences.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/run_agent.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/secrets.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/self_update.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode/setup_clients.py +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.4.2 → meshcode-2.4.4}/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.4"
|
|
@@ -29,10 +29,11 @@ _SEEN_MSG_CAP = 1000
|
|
|
29
29
|
# ============================================================
|
|
30
30
|
# Auto-wake: when agent is NOT in meshcode_wait and a message
|
|
31
31
|
# arrives, inject text into the terminal to wake the agent.
|
|
32
|
-
#
|
|
32
|
+
# DEFAULT: ON. Disable with MESHCODE_AUTO_WAKE=0 if you don't want it.
|
|
33
33
|
# ============================================================
|
|
34
34
|
_IN_WAIT = False # True while meshcode_wait is blocking
|
|
35
|
-
|
|
35
|
+
# Default ON — opt-out via MESHCODE_AUTO_WAKE=0/false/no
|
|
36
|
+
_AUTO_WAKE = os.environ.get("MESHCODE_AUTO_WAKE", "1").lower() not in ("0", "false", "no")
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def _try_auto_wake(from_agent: str, preview: str) -> None:
|
|
@@ -182,6 +183,21 @@ except Exception:
|
|
|
182
183
|
_be_path = ""
|
|
183
184
|
|
|
184
185
|
|
|
186
|
+
def _capture_session() -> None:
|
|
187
|
+
"""Stash the MCP session ref for silent auto-wake when agent is idle."""
|
|
188
|
+
global _STASHED_SESSION
|
|
189
|
+
if _STASHED_SESSION is not None:
|
|
190
|
+
return
|
|
191
|
+
try:
|
|
192
|
+
srv = mcp._mcp_server
|
|
193
|
+
ctx = getattr(srv, "request_context", None)
|
|
194
|
+
if ctx and getattr(ctx, "session", None):
|
|
195
|
+
_STASHED_SESSION = ctx.session
|
|
196
|
+
log.info("[meshcode] MCP session stashed for silent auto-wake")
|
|
197
|
+
except Exception:
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
|
|
185
201
|
def _check_hot_reload() -> None:
|
|
186
202
|
"""If backend.py was modified since boot/last reload, reimport it."""
|
|
187
203
|
global _be_mtime, be
|
|
@@ -409,6 +425,7 @@ def with_working_status(func):
|
|
|
409
425
|
@_functools.wraps(func)
|
|
410
426
|
async def awrapper(*args, **kwargs):
|
|
411
427
|
_check_hot_reload()
|
|
428
|
+
_capture_session() # stash session on first tool call for silent auto-wake
|
|
412
429
|
if not skip:
|
|
413
430
|
_set_state("working", name)
|
|
414
431
|
_record_event_bg("tool_call", {"tool": name, "args_keys": list(kwargs.keys())})
|
|
@@ -425,6 +442,7 @@ def with_working_status(func):
|
|
|
425
442
|
@_functools.wraps(func)
|
|
426
443
|
def swrapper(*args, **kwargs):
|
|
427
444
|
_check_hot_reload()
|
|
445
|
+
_capture_session() # stash session on first tool call for silent auto-wake
|
|
428
446
|
if not skip:
|
|
429
447
|
_set_state("working", name)
|
|
430
448
|
_record_event_bg("tool_call", {"tool": name, "args_keys": list(kwargs.keys())})
|
|
@@ -679,22 +697,65 @@ async def _on_new_message(msg: Dict[str, Any]) -> None:
|
|
|
679
697
|
# Auto-wake: if agent is idle (not in wait loop), nudge the terminal
|
|
680
698
|
from_agent = msg.get("from") or msg.get("from_agent") or "unknown"
|
|
681
699
|
payload = msg.get("payload") or {}
|
|
682
|
-
|
|
700
|
+
if isinstance(payload, dict):
|
|
701
|
+
# Try common content fields in order of usefulness
|
|
702
|
+
preview = (
|
|
703
|
+
payload.get("text")
|
|
704
|
+
or payload.get("message")
|
|
705
|
+
or payload.get("action")
|
|
706
|
+
or payload.get("context")
|
|
707
|
+
or payload.get("type")
|
|
708
|
+
or (str(next(iter(payload.values()))) if payload else "")
|
|
709
|
+
)
|
|
710
|
+
preview = str(preview) if preview else "(see inbox)"
|
|
711
|
+
else:
|
|
712
|
+
preview = str(payload)
|
|
683
713
|
try:
|
|
684
714
|
_try_auto_wake(from_agent, preview[:60])
|
|
685
715
|
except Exception:
|
|
686
716
|
pass # auto-wake is best-effort, never block message handling
|
|
687
717
|
|
|
718
|
+
# SILENT WAKE: 3-tier strategy, all without OS keystrokes
|
|
719
|
+
# 1. send_resource_updated (lightweight cache invalidation)
|
|
720
|
+
# 2. create_message (sampling) — server PUSH that wakes the LLM (if client supports)
|
|
721
|
+
# 3. AppleScript (handled above) — fallback for clients that don't support either
|
|
688
722
|
try:
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
723
|
+
from pydantic import AnyUrl
|
|
724
|
+
url = AnyUrl("meshcode://inbox")
|
|
725
|
+
session = _STASHED_SESSION
|
|
726
|
+
if session is None:
|
|
727
|
+
srv = mcp._mcp_server
|
|
728
|
+
ctx = getattr(srv, "request_context", None)
|
|
729
|
+
if ctx and getattr(ctx, "session", None):
|
|
730
|
+
session = ctx.session
|
|
731
|
+
if session is not None:
|
|
732
|
+
# 1. resource_updated (always cheap, may not wake)
|
|
733
|
+
try:
|
|
734
|
+
await session.send_resource_updated(url)
|
|
735
|
+
log.info(f"silent wake A: resource_updated sent for msg from {msg.get('from')}")
|
|
736
|
+
except Exception as _re:
|
|
737
|
+
log.debug(f"resource_updated failed: {_re}")
|
|
738
|
+
# 2. sampling/create_message — server PUSH that asks the LLM to think
|
|
739
|
+
# Only attempt if client advertised sampling capability
|
|
740
|
+
try:
|
|
741
|
+
client_caps = getattr(session, "client_capabilities", None)
|
|
742
|
+
if client_caps and getattr(client_caps, "sampling", None):
|
|
743
|
+
from mcp.types import SamplingMessage, TextContent
|
|
744
|
+
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."
|
|
745
|
+
await session.create_message(
|
|
746
|
+
messages=[SamplingMessage(role="user", content=TextContent(type="text", text=nudge_text))],
|
|
747
|
+
max_tokens=4096,
|
|
748
|
+
system_prompt="A new mesh message arrived while you were idle. Process it now.",
|
|
749
|
+
)
|
|
750
|
+
log.info(f"silent wake B: sampling/create_message PUSHED for msg from {msg.get('from')}")
|
|
751
|
+
except Exception as _ce:
|
|
752
|
+
log.debug(f"sampling/create_message failed: {_ce}")
|
|
695
753
|
except Exception as e:
|
|
696
|
-
log.debug(f"
|
|
754
|
+
log.debug(f"silent wake unavailable: {e}")
|
|
755
|
+
|
|
697
756
|
|
|
757
|
+
# Stashed MCP session for silent auto-wake (works when agent is idle, no active tool call)
|
|
758
|
+
_STASHED_SESSION = None
|
|
698
759
|
|
|
699
760
|
_heartbeat_stop = _threading.Event()
|
|
700
761
|
|
|
@@ -985,7 +1046,7 @@ def _detect_global_done(messages: List[Dict[str, Any]]) -> Optional[Dict[str, An
|
|
|
985
1046
|
|
|
986
1047
|
@mcp.tool()
|
|
987
1048
|
@with_working_status
|
|
988
|
-
async def meshcode_wait(timeout_seconds: int =
|
|
1049
|
+
async def meshcode_wait(timeout_seconds: int = 60, include_acks: bool = False) -> Dict[str, Any]:
|
|
989
1050
|
"""Block until a mesh message arrives or timeout. Your idle state.
|
|
990
1051
|
|
|
991
1052
|
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
|