meshcode 2.4.2__py3-none-any.whl → 2.4.4__py3-none-any.whl

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/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.4.2"
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
- # Toggle via MESHCODE_AUTO_WAKE=1 env var or meshcode_auto_wake tool.
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
- _AUTO_WAKE = os.environ.get("MESHCODE_AUTO_WAKE", "").lower() in ("1", "true", "yes")
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
- preview = payload.get("text", "") if isinstance(payload, dict) else str(payload)
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
- srv = mcp._mcp_server # FastMCP exposes the lowlevel server here
690
- ctx = getattr(srv, "request_context", None)
691
- if ctx and getattr(ctx, "session", None):
692
- from pydantic import AnyUrl
693
- await ctx.session.send_resource_updated(AnyUrl("meshcode://inbox"))
694
- log.info(f"sent MCP resource_updated notification for msg from {msg.get('from')}")
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"send_resource_updated unavailable: {e}")
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 = 120, include_acks: bool = False) -> Dict[str, Any]:
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.4.2
3
+ Version: 2.4.4
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,4 +1,4 @@
1
- meshcode/__init__.py,sha256=qB8hIuwmMwgqGtGO8uzgyFNbZAvLm4XVrhaj4QjLR1Y,84
1
+ meshcode/__init__.py,sha256=EKhxAjtj724I75uAYgYuKJlJ0zRf3lIUUnqZFQyQRnc,84
2
2
  meshcode/cli.py,sha256=zHVYBoj7EIx6J_rxgsALT9bqr1YAIPU9yUVdCRwpP0g,1477
3
3
  meshcode/comms_v4.py,sha256=yhsNF8aVFjBI0w_mO4jOyz3W60HoxzcP7eQyd2Kkvos,93426
4
4
  meshcode/invites.py,sha256=TWN5dAPrKBhBKzWQ_e4nzwW5QmiBd7mxd1RwjLWr7go,14690
@@ -14,12 +14,12 @@ meshcode/meshcode_mcp/__init__.py,sha256=OdzyVP8YHG4BaSuUvVAOy7FgGp6og2vOVbyV_Iu
14
14
  meshcode/meshcode_mcp/__main__.py,sha256=rrnTlA7ciA_C6b5QlbKlQli6O4vPEJRksCoeLIj8k1Q,1533
15
15
  meshcode/meshcode_mcp/backend.py,sha256=gglWwJ47wt66LlyFitlmc2Bc6Xc-NYr814g7FBof0nE,14414
16
16
  meshcode/meshcode_mcp/realtime.py,sha256=nglDB813psFAhEfUxp6vESH-CUD_T1mQzA9qCCRTPKU,9525
17
- meshcode/meshcode_mcp/server.py,sha256=wIdaLFPN_bZ01ojet44ye7JtEb4xSO6xSkYGv_ZEYPo,61428
17
+ meshcode/meshcode_mcp/server.py,sha256=8gSVx2DXk2_71NMImne97VN-qXDM7e5sYNEuigv184A,64387
18
18
  meshcode/meshcode_mcp/test_backend.py,sha256=B-J__pStnN4vVBzYN4yz2CKRylwDz09ZJbj4RVUtpr0,3101
19
19
  meshcode/meshcode_mcp/test_realtime.py,sha256=g2V3QzumdZgus4-b2Q-3bPFwX5Ib7BuzYah_vUrOAOU,3254
20
20
  meshcode/meshcode_mcp/test_server_wrapper.py,sha256=ohbKkfN_k-ApElnnOxSx4_y5lWwUBM3urfm8JndOloM,4464
21
- meshcode-2.4.2.dist-info/METADATA,sha256=jvhPUKsADXoYVcAmxFKxLsquGwylehA3kmZXNGWL7b8,15353
22
- meshcode-2.4.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
23
- meshcode-2.4.2.dist-info/entry_points.txt,sha256=-sX1FVOjMA-96Rt4GDCFCyFRGjAuYJSgXkBpWnDfShA,98
24
- meshcode-2.4.2.dist-info/top_level.txt,sha256=M_cv_gd_8ojgYoHk9qOCaDmmX47ypozEOYKtQvm03H8,9
25
- meshcode-2.4.2.dist-info/RECORD,,
21
+ meshcode-2.4.4.dist-info/METADATA,sha256=bZOe_ajHJ8boB4t2ikjYjVBw4eadS3yXHneEms9Rz7E,15353
22
+ meshcode-2.4.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
23
+ meshcode-2.4.4.dist-info/entry_points.txt,sha256=-sX1FVOjMA-96Rt4GDCFCyFRGjAuYJSgXkBpWnDfShA,98
24
+ meshcode-2.4.4.dist-info/top_level.txt,sha256=M_cv_gd_8ojgYoHk9qOCaDmmX47ypozEOYKtQvm03H8,9
25
+ meshcode-2.4.4.dist-info/RECORD,,