meshcode 2.0.3__tar.gz → 2.0.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.0.3 → meshcode-2.0.4}/PKG-INFO +1 -1
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/__init__.py +1 -1
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/meshcode_mcp/server.py +67 -4
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.0.3 → meshcode-2.0.4}/pyproject.toml +1 -1
- {meshcode-2.0.3 → meshcode-2.0.4}/README.md +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/cli.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/comms_v4.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/invites.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/launcher.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/launcher_install.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/preferences.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/run_agent.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/secrets.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/self_update.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode/setup_clients.py +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.0.3 → meshcode-2.0.4}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.0.
|
|
2
|
+
__version__ = "2.0.4"
|
|
@@ -145,6 +145,35 @@ def _split_messages(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
|
145
145
|
from . import backend as be
|
|
146
146
|
from .realtime import RealtimeListener
|
|
147
147
|
|
|
148
|
+
# ============================================================
|
|
149
|
+
# Hot-reload: detect when backend.py changes on disk (e.g. after
|
|
150
|
+
# pip install --upgrade meshcode) and reload without restart.
|
|
151
|
+
# Only backend.py is safe to reload (stateless). server.py has
|
|
152
|
+
# module-level state that would be destroyed by reload.
|
|
153
|
+
# ============================================================
|
|
154
|
+
import importlib as _importlib
|
|
155
|
+
_be_mtime: float = 0.0
|
|
156
|
+
try:
|
|
157
|
+
_be_path = os.path.abspath(be.__file__)
|
|
158
|
+
_be_mtime = os.path.getmtime(_be_path)
|
|
159
|
+
except Exception:
|
|
160
|
+
_be_path = ""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _check_hot_reload() -> None:
|
|
164
|
+
"""If backend.py was modified since boot/last reload, reimport it."""
|
|
165
|
+
global _be_mtime, be
|
|
166
|
+
if not _be_path:
|
|
167
|
+
return
|
|
168
|
+
try:
|
|
169
|
+
current_mtime = os.path.getmtime(_be_path)
|
|
170
|
+
if current_mtime > _be_mtime:
|
|
171
|
+
_be_mtime = current_mtime
|
|
172
|
+
_importlib.reload(be)
|
|
173
|
+
log.info("[meshcode] Hot-reloaded backend.py (new version detected)")
|
|
174
|
+
except Exception as e:
|
|
175
|
+
log.debug(f"hot-reload check failed: {e}")
|
|
176
|
+
|
|
148
177
|
try:
|
|
149
178
|
from mcp.server.fastmcp import FastMCP
|
|
150
179
|
except ImportError:
|
|
@@ -317,6 +346,7 @@ def with_working_status(func):
|
|
|
317
346
|
if asyncio.iscoroutinefunction(func):
|
|
318
347
|
@_functools.wraps(func)
|
|
319
348
|
async def awrapper(*args, **kwargs):
|
|
349
|
+
_check_hot_reload()
|
|
320
350
|
if not skip:
|
|
321
351
|
_schedule_flip("working", name)
|
|
322
352
|
try:
|
|
@@ -328,6 +358,7 @@ def with_working_status(func):
|
|
|
328
358
|
else:
|
|
329
359
|
@_functools.wraps(func)
|
|
330
360
|
def swrapper(*args, **kwargs):
|
|
361
|
+
_check_hot_reload()
|
|
331
362
|
if not skip:
|
|
332
363
|
_schedule_flip("working", name)
|
|
333
364
|
try:
|
|
@@ -574,7 +605,10 @@ async def _on_new_message(msg: Dict[str, Any]) -> None:
|
|
|
574
605
|
from_agent = msg.get("from") or msg.get("from_agent") or "unknown"
|
|
575
606
|
payload = msg.get("payload") or {}
|
|
576
607
|
preview = payload.get("text", "") if isinstance(payload, dict) else str(payload)
|
|
577
|
-
|
|
608
|
+
try:
|
|
609
|
+
_try_auto_wake(from_agent, preview[:60])
|
|
610
|
+
except Exception:
|
|
611
|
+
pass # auto-wake is best-effort, never block message handling
|
|
578
612
|
|
|
579
613
|
try:
|
|
580
614
|
srv = mcp._mcp_server # FastMCP exposes the lowlevel server here
|
|
@@ -852,7 +886,20 @@ async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[
|
|
|
852
886
|
# Realtime unavailable — plain sleep fallback so we still honor timeout.
|
|
853
887
|
await asyncio.sleep(actual_timeout)
|
|
854
888
|
|
|
855
|
-
|
|
889
|
+
# On timeout, check for unclaimed tasks assigned to this agent
|
|
890
|
+
timeout_result: Dict[str, Any] = {"timed_out": True}
|
|
891
|
+
try:
|
|
892
|
+
api_key = _get_api_key()
|
|
893
|
+
open_tasks = be.task_list(api_key, _PROJECT_ID, AGENT_NAME, status_filter="open")
|
|
894
|
+
if isinstance(open_tasks, dict) and open_tasks.get("ok"):
|
|
895
|
+
my_tasks = [t for t in open_tasks.get("tasks", [])
|
|
896
|
+
if t.get("assignee") == AGENT_NAME and not t.get("claimed_by")]
|
|
897
|
+
if my_tasks:
|
|
898
|
+
timeout_result["unclaimed_tasks"] = len(my_tasks)
|
|
899
|
+
timeout_result["hint"] = f"You have {len(my_tasks)} unclaimed task(s) assigned to you. Run meshcode_tasks(status_filter='open') to see them."
|
|
900
|
+
except Exception:
|
|
901
|
+
pass
|
|
902
|
+
return timeout_result
|
|
856
903
|
|
|
857
904
|
|
|
858
905
|
@mcp.tool()
|
|
@@ -995,9 +1042,22 @@ def meshcode_task_create(title: str, description: str = "", assignee: str = "*",
|
|
|
995
1042
|
Returns: {"ok": true, "task_id": "...", "title": "..."}
|
|
996
1043
|
"""
|
|
997
1044
|
api_key = _get_api_key()
|
|
998
|
-
|
|
1045
|
+
result = be.task_create(api_key, _PROJECT_ID, AGENT_NAME, title,
|
|
999
1046
|
description=description, assignee=assignee,
|
|
1000
1047
|
priority=priority, parent_task_id=parent_task_id)
|
|
1048
|
+
# Auto-notify assignee so they wake from meshcode_wait
|
|
1049
|
+
if isinstance(result, dict) and result.get("ok") and assignee and assignee != "*" and assignee != AGENT_NAME:
|
|
1050
|
+
try:
|
|
1051
|
+
be.send_message(_PROJECT_ID, AGENT_NAME, assignee, {
|
|
1052
|
+
"type": "task_assigned",
|
|
1053
|
+
"task_id": result.get("task_id", ""),
|
|
1054
|
+
"title": title,
|
|
1055
|
+
"priority": priority,
|
|
1056
|
+
"text": f"New {priority} task assigned to you: {title}",
|
|
1057
|
+
}, msg_type="system")
|
|
1058
|
+
except Exception:
|
|
1059
|
+
pass # best-effort notification
|
|
1060
|
+
return result
|
|
1001
1061
|
|
|
1002
1062
|
|
|
1003
1063
|
@mcp.tool()
|
|
@@ -1338,7 +1398,10 @@ def meshcode_remember(key: str, value: Any) -> Dict[str, Any]:
|
|
|
1338
1398
|
})
|
|
1339
1399
|
# Best-effort sync to Obsidian vault (if configured)
|
|
1340
1400
|
if isinstance(result, dict) and result.get("ok"):
|
|
1341
|
-
|
|
1401
|
+
try:
|
|
1402
|
+
_sync_to_obsidian(key, json_value)
|
|
1403
|
+
except Exception:
|
|
1404
|
+
pass # Obsidian sync is best-effort, never block memory writes
|
|
1342
1405
|
return result
|
|
1343
1406
|
|
|
1344
1407
|
|
|
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
|