meshcode 2.6.3__tar.gz → 2.6.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.6.3 → meshcode-2.6.5}/PKG-INFO +1 -1
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/__init__.py +1 -1
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/meshcode_mcp/server.py +91 -6
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.6.3 → meshcode-2.6.5}/pyproject.toml +1 -1
- {meshcode-2.6.3 → meshcode-2.6.5}/README.md +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/cli.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/comms_v4.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/invites.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/launcher.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/launcher_install.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/preferences.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/run_agent.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/secrets.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/self_update.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode/setup_clients.py +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/setup.cfg +0 -0
- {meshcode-2.6.3 → meshcode-2.6.5}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.6.
|
|
2
|
+
__version__ = "2.6.5"
|
|
@@ -746,7 +746,7 @@ RULES:
|
|
|
746
746
|
|
|
747
747
|
SESSION START (do these IMMEDIATELY — don't wait for user input):
|
|
748
748
|
1. meshcode_set_status(status="online", task="ready")
|
|
749
|
-
2. meshcode_check() — read
|
|
749
|
+
2. meshcode_check() — read NEW messages only (last_seen is auto-restored from mesh memory on boot, old messages are skipped automatically)
|
|
750
750
|
3. meshcode_tasks() — check for assigned/pending tasks and claim any unclaimed ones
|
|
751
751
|
4. meshcode_auto_wake() — scan meshwork health, create tasks for issues found
|
|
752
752
|
5. meshcode_status() — see who's online
|
|
@@ -1124,8 +1124,8 @@ except Exception:
|
|
|
1124
1124
|
@mcp.tool()
|
|
1125
1125
|
@with_working_status
|
|
1126
1126
|
def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
|
|
1127
|
-
sensitive: bool = False, encrypted: bool =
|
|
1128
|
-
"""Send message. Use "agent@meshwork" for cross-mesh. sensitive=True hides from exports.
|
|
1127
|
+
sensitive: bool = False, encrypted: bool = False) -> Dict[str, Any]:
|
|
1128
|
+
"""Send message. Use "agent@meshwork" for cross-mesh. sensitive=True hides from exports. Pass encrypted=True for secrets/credentials (AES-256-GCM)."""
|
|
1129
1129
|
if isinstance(message, str):
|
|
1130
1130
|
# Auto-wrap strings but warn if too long — prefer structured JSON
|
|
1131
1131
|
if len(message) > 400:
|
|
@@ -1291,6 +1291,25 @@ _CONSECUTIVE_IDLE_SECONDS = 0
|
|
|
1291
1291
|
_AUTO_SLEEP_THRESHOLD = int(os.environ.get("MESHCODE_AUTO_SLEEP_SECONDS", "600")) # default 10min
|
|
1292
1292
|
_LAST_SEEN_TS: Optional[str] = None # auto-persisted for message dedup
|
|
1293
1293
|
|
|
1294
|
+
# Hydrate _LAST_SEEN_TS from mesh memory on boot so restarts skip old messages
|
|
1295
|
+
try:
|
|
1296
|
+
_ls_result = be.sb_rpc("mc_memory_get", {
|
|
1297
|
+
"p_api_key": _get_api_key(),
|
|
1298
|
+
"p_agent_name": AGENT_NAME,
|
|
1299
|
+
"p_key": "last_seen",
|
|
1300
|
+
"p_project_name": PROJECT_NAME,
|
|
1301
|
+
})
|
|
1302
|
+
if isinstance(_ls_result, dict) and _ls_result.get("ok"):
|
|
1303
|
+
_ls_val = _ls_result.get("value")
|
|
1304
|
+
if isinstance(_ls_val, dict) and _ls_val.get("_raw"):
|
|
1305
|
+
_LAST_SEEN_TS = str(_ls_val["_raw"])
|
|
1306
|
+
elif isinstance(_ls_val, str):
|
|
1307
|
+
_LAST_SEEN_TS = _ls_val
|
|
1308
|
+
if _LAST_SEEN_TS:
|
|
1309
|
+
print(f"[meshcode] Restored last_seen={_LAST_SEEN_TS} from mesh memory.", file=sys.stderr)
|
|
1310
|
+
except Exception as _e:
|
|
1311
|
+
print(f"[meshcode] Could not restore last_seen: {_e}", file=sys.stderr)
|
|
1312
|
+
|
|
1294
1313
|
|
|
1295
1314
|
def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
|
|
1296
1315
|
"""Fetch open/in_progress tasks assigned to this agent. Returns compact list or None."""
|
|
@@ -1306,7 +1325,7 @@ def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
|
|
|
1306
1325
|
{"id": t["id"][:8], "title": t["title"][:80], "priority": t.get("priority", "normal"), "status": t["status"]}
|
|
1307
1326
|
for t in tasks
|
|
1308
1327
|
if t.get("status") in ("open", "in_progress")
|
|
1309
|
-
and (t.get("
|
|
1328
|
+
and (t.get("assignee") in (AGENT_NAME, "*", None) or t.get("claimed_by") == AGENT_NAME)
|
|
1310
1329
|
]
|
|
1311
1330
|
return pending if pending else None
|
|
1312
1331
|
except Exception:
|
|
@@ -1337,7 +1356,7 @@ async def meshcode_wait(timeout_seconds: int = 120, include_acks: bool = False)
|
|
|
1337
1356
|
# The in-memory dedupe (_SEEN_MSG_IDS) can mark messages as "seen" via
|
|
1338
1357
|
# realtime without the agent actually processing them. Always check DB.
|
|
1339
1358
|
try:
|
|
1340
|
-
db_pending = be.count_pending(_PROJECT_ID, AGENT_NAME)
|
|
1359
|
+
db_pending = be.count_pending(_PROJECT_ID, AGENT_NAME, api_key=_get_api_key())
|
|
1341
1360
|
if db_pending and db_pending > 0:
|
|
1342
1361
|
# Fetch and return the messages — mark_read=True so the next
|
|
1343
1362
|
# meshcode_wait() won't re-refuse with the same messages.
|
|
@@ -1404,11 +1423,49 @@ async def meshcode_wait(timeout_seconds: int = 120, include_acks: bool = False)
|
|
|
1404
1423
|
latest_ts = max((m.get("ts", "") for m in msgs), default="")
|
|
1405
1424
|
if latest_ts:
|
|
1406
1425
|
_LAST_SEEN_TS = latest_ts
|
|
1426
|
+
# Persist to mesh memory so next session resumes here
|
|
1427
|
+
try:
|
|
1428
|
+
be.sb_rpc("mc_memory_set", {
|
|
1429
|
+
"p_api_key": _get_api_key(),
|
|
1430
|
+
"p_agent_name": AGENT_NAME,
|
|
1431
|
+
"p_key": "last_seen",
|
|
1432
|
+
"p_value": {"_raw": latest_ts},
|
|
1433
|
+
"p_project_name": PROJECT_NAME,
|
|
1434
|
+
})
|
|
1435
|
+
except Exception:
|
|
1436
|
+
pass # best-effort, never block wait loop
|
|
1407
1437
|
return result
|
|
1408
1438
|
finally:
|
|
1409
1439
|
_IN_WAIT = False
|
|
1410
1440
|
|
|
1411
1441
|
|
|
1442
|
+
def _mark_realtime_msgs_read_in_db(messages: List[Dict[str, Any]]) -> None:
|
|
1443
|
+
"""Mark realtime-delivered messages as read in DB (background thread).
|
|
1444
|
+
|
|
1445
|
+
Without this, messages delivered via realtime stay read=false in the DB
|
|
1446
|
+
and inflate pending counts when the agent restarts.
|
|
1447
|
+
"""
|
|
1448
|
+
import threading
|
|
1449
|
+
msg_ids = [m.get("id") for m in messages if m.get("id")]
|
|
1450
|
+
if not msg_ids:
|
|
1451
|
+
return
|
|
1452
|
+
api_key = _get_api_key()
|
|
1453
|
+
if not api_key:
|
|
1454
|
+
return
|
|
1455
|
+
|
|
1456
|
+
def _do():
|
|
1457
|
+
for mid in msg_ids:
|
|
1458
|
+
try:
|
|
1459
|
+
be.sb_rpc("mc_mark_message_read", {
|
|
1460
|
+
"p_api_key": api_key,
|
|
1461
|
+
"p_project_id": _PROJECT_ID,
|
|
1462
|
+
"p_message_id": mid,
|
|
1463
|
+
})
|
|
1464
|
+
except Exception:
|
|
1465
|
+
pass
|
|
1466
|
+
threading.Thread(target=_do, daemon=True).start()
|
|
1467
|
+
|
|
1468
|
+
|
|
1412
1469
|
async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[str, Any]:
|
|
1413
1470
|
|
|
1414
1471
|
def _return_from_buffered(buffered: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
@@ -1436,6 +1493,9 @@ async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[
|
|
|
1436
1493
|
# are no real messages or done signals, treat as nothing useful.
|
|
1437
1494
|
if not split["messages"] and not split["done_signals"] and not split["acks"]:
|
|
1438
1495
|
return None
|
|
1496
|
+
# Mark realtime-delivered messages as read in DB so they don't
|
|
1497
|
+
# inflate pending counts on agent restart.
|
|
1498
|
+
_mark_realtime_msgs_read_in_db(deduped)
|
|
1439
1499
|
out: Dict[str, Any] = {
|
|
1440
1500
|
"got_message": True,
|
|
1441
1501
|
"source": "realtime",
|
|
@@ -1468,6 +1528,31 @@ async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[
|
|
|
1468
1528
|
# Realtime unavailable — plain sleep fallback so we still honor timeout.
|
|
1469
1529
|
await asyncio.sleep(actual_timeout)
|
|
1470
1530
|
|
|
1531
|
+
# Fallback: poll DB for messages that realtime may have missed
|
|
1532
|
+
# (connection drop, startup race, etc.)
|
|
1533
|
+
try:
|
|
1534
|
+
api_key = _get_api_key()
|
|
1535
|
+
if api_key:
|
|
1536
|
+
db_pending = be.count_pending(_PROJECT_ID, AGENT_NAME, api_key=api_key)
|
|
1537
|
+
if db_pending and db_pending > 0:
|
|
1538
|
+
raw = be.read_inbox(_PROJECT_ID, AGENT_NAME, mark_read=True, api_key=api_key)
|
|
1539
|
+
if raw:
|
|
1540
|
+
msgs = [
|
|
1541
|
+
{"from": m["from_agent"], "type": m.get("type", "msg"),
|
|
1542
|
+
"ts": m.get("created_at"), "payload": m.get("payload", {}),
|
|
1543
|
+
"id": m.get("id"), "parent_id": m.get("parent_msg_id")}
|
|
1544
|
+
for m in raw
|
|
1545
|
+
]
|
|
1546
|
+
deduped = _filter_and_mark(msgs)
|
|
1547
|
+
if deduped:
|
|
1548
|
+
split = _split_messages(deduped)
|
|
1549
|
+
if not include_acks:
|
|
1550
|
+
split["acks"] = []
|
|
1551
|
+
if split["messages"] or split["done_signals"]:
|
|
1552
|
+
return {"got_message": True, "source": "db_fallback", **split}
|
|
1553
|
+
except Exception:
|
|
1554
|
+
pass
|
|
1555
|
+
|
|
1471
1556
|
# Check if there's any pending work before returning timeout
|
|
1472
1557
|
pending_tasks = _get_pending_tasks_summary()
|
|
1473
1558
|
out: Dict[str, Any] = {"timed_out": True}
|
|
@@ -1750,7 +1835,7 @@ def meshcode_auto_wake() -> Dict[str, Any]:
|
|
|
1750
1835
|
task_result = be.task_list(api_key, _PROJECT_ID, AGENT_NAME, status_filter="open")
|
|
1751
1836
|
if isinstance(task_result, dict) and task_result.get("ok"):
|
|
1752
1837
|
open_tasks = task_result.get("tasks", [])
|
|
1753
|
-
unassigned = [t for t in open_tasks if not t.get("
|
|
1838
|
+
unassigned = [t for t in open_tasks if not t.get("assignee") or t.get("assignee") == "*"]
|
|
1754
1839
|
if len(unassigned) > 3:
|
|
1755
1840
|
suggestions.append({
|
|
1756
1841
|
"title": f"Task backlog: {len(unassigned)} unassigned open tasks",
|
|
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
|
|
File without changes
|