meshcode 2.10.35__tar.gz → 2.10.36__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.
Files changed (32) hide show
  1. {meshcode-2.10.35 → meshcode-2.10.36}/PKG-INFO +1 -1
  2. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/__init__.py +1 -1
  3. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/comms_v4.py +108 -8
  4. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/backend.py +1 -1
  5. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/server.py +47 -29
  6. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.10.35 → meshcode-2.10.36}/pyproject.toml +1 -1
  8. {meshcode-2.10.35 → meshcode-2.10.36}/README.md +0 -0
  9. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/ascii_art.py +0 -0
  10. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/cli.py +0 -0
  11. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/invites.py +0 -0
  12. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/launcher.py +0 -0
  13. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/launcher_install.py +0 -0
  14. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/__init__.py +0 -0
  15. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/__main__.py +0 -0
  16. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/realtime.py +0 -0
  17. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/test_backend.py +0 -0
  18. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  19. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  20. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/preferences.py +0 -0
  21. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/protocol_v2.py +0 -0
  22. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/run_agent.py +0 -0
  23. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/secrets.py +0 -0
  24. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/self_update.py +0 -0
  25. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/setup_clients.py +0 -0
  26. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/SOURCES.txt +0 -0
  27. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.10.35 → meshcode-2.10.36}/setup.cfg +0 -0
  32. {meshcode-2.10.35 → meshcode-2.10.36}/tests/test_status_enum_coverage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.35
3
+ Version: 2.10.36
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,2 +1,2 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.10.35"
2
+ __version__ = "2.10.36"
@@ -1730,10 +1730,21 @@ SETUP (advanced):
1730
1730
  register <proj> <name> [role] Register agent manually
1731
1731
  setup <client> <proj> <name> [role] Legacy: global MCP config
1732
1732
 
1733
+ AGENT CONTROL:
1734
+ scan Scan identicon from clipboard → run
1735
+ kill <proj> <name> Force disconnect agent
1736
+ wake <proj> <name> Send wake signal
1737
+ sleep <proj> <name> Send sleep signal
1738
+ disconnect <proj> <name> Graceful disconnect
1739
+ whoami Show logged-in identity
1740
+ profile [agent] Show/set agent profile
1741
+ connect <proj> <name> Connect existing agent
1742
+
1733
1743
  ADMIN:
1734
1744
  clear <proj> <name> Clear inbox
1735
1745
  unregister <proj> <name> Leave project
1736
1746
  prefs View/set preferences
1747
+ update Check for package updates
1737
1748
 
1738
1749
  PROFILES (multi-account):
1739
1750
  profiles List stored keychain profiles
@@ -1856,6 +1867,80 @@ Clear inbox: marks all unread messages as read for an agent.
1856
1867
  "unregister": """meshcode unregister <project> <name>
1857
1868
 
1858
1869
  Remove an agent from a meshwork (deletes the row from mc_agents).
1870
+ """,
1871
+ "setup": """meshcode setup <project> <agent> [role]
1872
+
1873
+ Create an isolated workspace at ~/meshcode/<project>-<agent>/ with
1874
+ .mcp.json configured for the agent's MCP server. Usually auto-created
1875
+ by `meshcode go`.
1876
+
1877
+ EXAMPLES:
1878
+ meshcode setup my-app backend "Backend Engineer"
1879
+ """,
1880
+ "run": """meshcode run <agent> [--project <name>] [--editor claude|cursor|code]
1881
+
1882
+ Launch an agent in your preferred editor. Detects Claude Code, Cursor,
1883
+ VS Code, Windsurf, or Codex. Use <project>/<agent> to disambiguate.
1884
+
1885
+ EXAMPLES:
1886
+ meshcode run backend
1887
+ meshcode run my-app/backend --editor cursor
1888
+ """,
1889
+ "go": """meshcode go <agent> [--project <name>]
1890
+
1891
+ Shortcut for setup + run. Creates workspace if needed, then launches.
1892
+
1893
+ EXAMPLES:
1894
+ meshcode go backend
1895
+ meshcode go my-app/frontend
1896
+ """,
1897
+ "scan": """meshcode scan
1898
+
1899
+ Read an agent identicon from clipboard (or stdin) and launch the agent.
1900
+ The identicon must contain a ⟨ project/agent ⟩ tag.
1901
+
1902
+ EXAMPLES:
1903
+ meshcode scan # reads from clipboard
1904
+ cat identicon.txt | meshcode scan
1905
+ """,
1906
+ "invite": """meshcode invite <project> <agent> [--role "..."] [--days 7]
1907
+
1908
+ Generate an invite token for a teammate to join as a specific agent.
1909
+ --days 0 = permanent (never expires).
1910
+
1911
+ EXAMPLES:
1912
+ meshcode invite my-app frontend --role "React Developer" --days 7
1913
+ """,
1914
+ "join": """meshcode join <token> [--display-name "alice"]
1915
+
1916
+ Accept an invite and create a workspace for the assigned agent.
1917
+
1918
+ EXAMPLES:
1919
+ meshcode join mc_invite_abc123 --display-name "Alice"
1920
+ """,
1921
+ "invites": """meshcode invites <project>
1922
+
1923
+ List outstanding and redeemed invites for a meshwork.
1924
+ """,
1925
+ "members": """meshcode members <project>
1926
+
1927
+ List all members (owner + invited) of a meshwork.
1928
+ """,
1929
+ "whoami": """meshcode whoami
1930
+
1931
+ Show the currently logged-in user (email, user_id, profile).
1932
+ """,
1933
+ "prefs": """meshcode prefs [key] [value]
1934
+
1935
+ View or set preferences (permission-mode, auto-update, etc.).
1936
+
1937
+ EXAMPLES:
1938
+ meshcode prefs # show all
1939
+ meshcode prefs permission-mode bypass # set
1940
+ """,
1941
+ "profiles": """meshcode profiles
1942
+
1943
+ List all stored keychain profiles (default + per-meshwork guest keys).
1859
1944
  """,
1860
1945
  }
1861
1946
 
@@ -1981,6 +2066,9 @@ if __name__ == "__main__":
1981
2066
  from_a, to_a = target.split(":", 1)
1982
2067
  else:
1983
2068
  from_a, to_a = "?", target
2069
+ if not message.strip():
2070
+ print("[meshcode] ERROR: message cannot be empty. Usage: meshcode send <project> <from>:<to> <message>")
2071
+ sys.exit(1)
1984
2072
  send_msg(proj, from_a, to_a, message, compact=compact)
1985
2073
 
1986
2074
  elif cmd == "broadcast":
@@ -1989,12 +2077,15 @@ if __name__ == "__main__":
1989
2077
  from_a = flags["from"]
1990
2078
  message = flags.get("msg", flags.get("message", " ".join(pos)))
1991
2079
  msg_type = flags.get("type", "broadcast")
1992
- broadcast(proj, from_a, message, msg_type)
1993
2080
  else:
1994
2081
  proj = pos[0] if len(pos) > 0 else "default"
1995
2082
  from_a = pos[1] if len(pos) > 1 else "?"
1996
2083
  message = " ".join(pos[2:]) if len(pos) > 2 else ""
1997
- broadcast(proj, from_a, message)
2084
+ msg_type = "broadcast"
2085
+ if not message.strip():
2086
+ print("[meshcode] ERROR: message cannot be empty. Usage: meshcode broadcast <project> <from> <message>")
2087
+ sys.exit(1)
2088
+ broadcast(proj, from_a, message, msg_type)
1998
2089
 
1999
2090
  elif cmd == "read":
2000
2091
  proj = flags.get("project", pos[0] if len(pos) > 0 else "default")
@@ -2007,8 +2098,14 @@ if __name__ == "__main__":
2007
2098
  elif cmd == "watch":
2008
2099
  proj = flags.get("project", pos[0] if len(pos) > 0 else "default")
2009
2100
  name = flags.get("name", pos[1] if len(pos) > 1 else "agent")
2010
- interval = int(flags.get("interval", pos[2] if len(pos) > 2 else "10"))
2011
- timeout = int(flags.get("timeout", pos[3] if len(pos) > 3 else "0"))
2101
+ try:
2102
+ interval = int(flags.get("interval", pos[2] if len(pos) > 2 else "10"))
2103
+ except (ValueError, TypeError):
2104
+ interval = 10
2105
+ try:
2106
+ timeout = int(flags.get("timeout", pos[3] if len(pos) > 3 else "0"))
2107
+ except (ValueError, TypeError):
2108
+ timeout = 0
2012
2109
  watch(proj, name, interval, timeout)
2013
2110
 
2014
2111
  elif cmd == "board":
@@ -2230,12 +2327,15 @@ if __name__ == "__main__":
2230
2327
  print(" meshcode scan # reads from clipboard")
2231
2328
  print(" cat identicon.txt | meshcode scan # reads from stdin")
2232
2329
  sys.exit(1)
2233
- # Look for agent or meshwork/agent
2234
- match = _re.search(r'⟨\s*(\S+)\s*⟩', art_text)
2235
- if not match:
2330
+ # Find ALL identicon tags, prefer the one with project/agent format.
2331
+ # Identicons contain two tags: ⟨ agent ⟩ and ⟨ project/agent ⟩.
2332
+ # re.search returns the first (agent-only), missing the project.
2333
+ all_tags = _re.findall(r'⟨\s*(\S+)\s*⟩', art_text)
2334
+ if not all_tags:
2236
2335
  print("[meshcode] No identicon tag found. Expected: ⟨ agent_name ⟩")
2237
2336
  sys.exit(1)
2238
- tag = match.group(1)
2337
+ # Prefer project/agent tag, fall back to agent-only
2338
+ tag = next((t for t in all_tags if "/" in t), all_tags[0])
2239
2339
  if "/" in tag:
2240
2340
  _proj, _agent = tag.split("/", 1)
2241
2341
  else:
@@ -65,7 +65,7 @@ class _CircuitBreaker:
65
65
  return self.state == self.OPEN
66
66
 
67
67
 
68
- _circuit = _CircuitBreaker(failure_threshold=5, recovery_timeout=30.0)
68
+ _circuit = _CircuitBreaker(failure_threshold=5, recovery_timeout=10.0)
69
69
 
70
70
  # Bake in production defaults — RLS-protected publishable key, safe to ship.
71
71
  _DEFAULT_SUPABASE_URL = "https://gjinagyyjttyxnaoavnz.supabase.co"
@@ -923,7 +923,9 @@ meshcode_expand_link(). No sensitive msgs cross-mesh.
923
923
 
924
924
  MEMORY: meshcode_remember(key, value) persists across sessions.
925
925
  meshcode_recall(key?) retrieves. meshcode_forget(key) deletes.
926
- Auto-remember after each task: mistakes, feedback, patterns, preferences.
926
+ Only remember reusable learnings: mistakes, feedback, patterns, preferences.
927
+ Do NOT save task summaries — tasks already persist in the task system.
928
+ Do NOT use memory for session state or ephemeral data.
927
929
  Save reusable code patterns as template_* keys for instant recall.
928
930
 
929
931
  SCRATCHPAD: meshcode_scratchpad_set/get for shared meshwork-level context
@@ -1398,6 +1400,9 @@ async def meshcode_debug_sleep(seconds: int = 30) -> Dict[str, Any]:
1398
1400
  def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
1399
1401
  sensitive: bool = False, encrypted: bool = False) -> Dict[str, Any]:
1400
1402
  """Send message. Use "agent@meshwork" for cross-mesh. sensitive=True hides from exports. Pass encrypted=True for secrets/credentials (AES-256-GCM)."""
1403
+ if not to or not to.strip():
1404
+ return {"error": "recipient 'to' cannot be empty"}
1405
+ to = to.strip()
1401
1406
  if isinstance(message, str):
1402
1407
  # Auto-wrap strings into dict. Warn if very long but don't reject.
1403
1408
  if len(message) > 2000:
@@ -1605,6 +1610,22 @@ def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
1605
1610
  or t.get("assignee") == AGENT_NAME # Directly assigned to me
1606
1611
  )
1607
1612
  ]
1613
+ # For leader agents: also include unclaimed '*' tasks as pending
1614
+ # so commanders auto-triage them instead of letting them pile up.
1615
+ _leader_haystack = ((_ROLE_DESCRIPTION or '') + ' ' + AGENT_NAME).lower()
1616
+ _LEADER_KW = ('commander', 'lead', 'orchestrat', 'coordinator', 'coordinat',
1617
+ 'coordinad', 'jefe', 'líder', 'lider', 'director', 'manager',
1618
+ 'chief', 'captain', 'boss', 'head agent')
1619
+ is_leader = any(k in _leader_haystack for k in _LEADER_KW)
1620
+ if is_leader:
1621
+ wildcard_tasks = [
1622
+ {"id": t["id"][:8], "title": t["title"][:80], "priority": t.get("priority", "normal"), "status": t["status"]}
1623
+ for t in tasks
1624
+ if t.get("status") == "open"
1625
+ and t.get("assignee") == "*"
1626
+ and not t.get("claimed_by")
1627
+ ]
1628
+ pending.extend(wildcard_tasks)
1608
1629
  return pending if pending else None
1609
1630
  except Exception:
1610
1631
  return None
@@ -1630,14 +1651,22 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
1630
1651
  global _IN_WAIT, _CONSECUTIVE_IDLE_SECONDS, _LAST_SEEN_TS
1631
1652
 
1632
1653
  # PRODUCT RULE 1: If agent has open tasks, refuse to wait. Work first.
1654
+ # Exception: commander/leader agents can wait while monitoring — they
1655
+ # delegate tasks and need to stay in the wait loop to receive reports.
1633
1656
  pending_tasks = _get_pending_tasks_summary()
1634
1657
  if pending_tasks:
1635
- return {
1636
- "refused": True,
1637
- "reason": "You have open tasks. Work them before entering wait.",
1638
- "pending_tasks": pending_tasks,
1639
- "count": len(pending_tasks),
1640
- }
1658
+ _leader_haystack = ((_ROLE_DESCRIPTION or '') + ' ' + AGENT_NAME).lower()
1659
+ _LEADER_KW = ('commander', 'lead', 'orchestrat', 'coordinator', 'coordinat',
1660
+ 'coordinad', 'jefe', 'líder', 'lider', 'director', 'manager',
1661
+ 'chief', 'captain', 'boss', 'head agent')
1662
+ _is_leader = any(k in _leader_haystack for k in _LEADER_KW)
1663
+ if not _is_leader:
1664
+ return {
1665
+ "refused": True,
1666
+ "reason": "You have open tasks. Work them before entering wait.",
1667
+ "pending_tasks": pending_tasks,
1668
+ "count": len(pending_tasks),
1669
+ }
1641
1670
 
1642
1671
  # PRODUCT RULE 2: If agent has unread messages in DB, refuse to wait.
1643
1672
  try:
@@ -2050,8 +2079,10 @@ def meshcode_init(project: str, agent: str, role: str = "") -> Dict[str, Any]:
2050
2079
  def meshcode_task_create(title: str, description: str = "", assignee: str = "*",
2051
2080
  priority: str = "normal", parent_task_id: Optional[str] = None) -> Dict[str, Any]:
2052
2081
  """Create task. assignee="*" for any, priority: low/normal/high/urgent."""
2082
+ if not title or not title.strip():
2083
+ return {"error": "title cannot be empty"}
2053
2084
  api_key = _get_api_key()
2054
- result = be.task_create(api_key, _PROJECT_ID, AGENT_NAME, title,
2085
+ result = be.task_create(api_key, _PROJECT_ID, AGENT_NAME, title.strip(),
2055
2086
  description=description, assignee=assignee,
2056
2087
  priority=priority, parent_task_id=parent_task_id)
2057
2088
  # Auto-notify assignee so they wake from meshcode_wait
@@ -2122,7 +2153,7 @@ def meshcode_task_claim(task_id: str) -> Dict[str, Any]:
2122
2153
  @mcp.tool()
2123
2154
  @with_working_status
2124
2155
  def meshcode_task_complete(task_id: str, summary: str = "", force: bool = False) -> Dict[str, Any]:
2125
- """Complete a claimed task with summary. Auto-remembers the task summary.
2156
+ """Complete a claimed task with summary.
2126
2157
 
2127
2158
  Refuses if the task has open subtasks, unless force=True is passed with a
2128
2159
  reason in `summary`. This prevents closing a parent while lanes are still
@@ -2148,25 +2179,8 @@ def meshcode_task_complete(task_id: str, summary: str = "", force: bool = False)
2148
2179
  except Exception:
2149
2180
  pass # Best-effort check; don't block on listing failure.
2150
2181
  result = be.task_complete(api_key, _PROJECT_ID, task_id, AGENT_NAME, summary=summary)
2151
- # Auto-remember task completion for future context
2152
- if isinstance(result, dict) and result.get("ok") and summary:
2153
- try:
2154
- import threading
2155
- def _auto_remember():
2156
- try:
2157
- be.sb_rpc("mc_memory_set", {
2158
- "p_api_key": api_key,
2159
- "p_agent_name": AGENT_NAME,
2160
- "p_key": f"task_{task_id[:8]}",
2161
- "p_value": {"title": result.get("title", ""), "summary": summary, "completed": True},
2162
- "p_tier": "episodic",
2163
- "p_project_name": PROJECT_NAME,
2164
- })
2165
- except Exception:
2166
- pass
2167
- threading.Thread(target=_auto_remember, daemon=True).start()
2168
- except Exception:
2169
- pass
2182
+ # Task data persists in the task system do NOT duplicate to memory.
2183
+ # Samuel: "los tasks no deben guardarse en memoria, para eso salen en tasks"
2170
2184
  return result
2171
2185
 
2172
2186
 
@@ -2268,6 +2282,7 @@ def meshcode_auto_wake() -> Dict[str, Any]:
2268
2282
  suggestions: List[Dict[str, str]] = []
2269
2283
 
2270
2284
  # 1. Check for stale agents (heartbeat >10 min, not offline/sleeping)
2285
+ agents = [] # Initialize before try so downstream checks don't NameError
2271
2286
  try:
2272
2287
  agents = be.get_board(_PROJECT_ID)
2273
2288
  import datetime as _dt
@@ -2607,12 +2622,15 @@ source: meshcode
2607
2622
  @mcp.tool()
2608
2623
  @with_working_status
2609
2624
  def meshcode_remember(key: str, value: Any) -> Dict[str, Any]:
2610
- """Store a persistent memory (survives restarts). Auto-remember after each task.
2625
+ """Store a persistent memory (survives restarts). Only for reusable learnings, NOT task data.
2611
2626
 
2612
2627
  Args:
2613
2628
  key: Short key (e.g. "team_conventions").
2614
2629
  value: Any JSON-serializable value.
2615
2630
  """
2631
+ if not key or not key.strip():
2632
+ return {"error": "key cannot be empty"}
2633
+ key = key.strip()
2616
2634
  api_key = _get_api_key()
2617
2635
  json_value = value if isinstance(value, (dict, list)) else json.dumps(value)
2618
2636
  if isinstance(json_value, str):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.35
3
+ Version: 2.10.36
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.10.35"
7
+ version = "2.10.36"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes
File without changes