meshcode 2.0.7__tar.gz → 2.0.9__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.7 → meshcode-2.0.9}/PKG-INFO +1 -1
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/__init__.py +1 -1
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/meshcode_mcp/backend.py +67 -3
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/meshcode_mcp/server.py +24 -20
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.0.7 → meshcode-2.0.9}/pyproject.toml +1 -1
- {meshcode-2.0.7 → meshcode-2.0.9}/README.md +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/cli.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/comms_v4.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/invites.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/launcher.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/launcher_install.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/preferences.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/run_agent.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/secrets.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/self_update.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode/setup_clients.py +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.0.7 → meshcode-2.0.9}/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.9"
|
|
@@ -105,6 +105,29 @@ def sb_update(table: str, filters: str, updates: Dict) -> Any:
|
|
|
105
105
|
return _request("PATCH", f"{table}?{filters}", data=updates, prefer="return=representation")
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
# Session recording config — set by MCP server at boot
|
|
109
|
+
_recording_enabled = False
|
|
110
|
+
_recording_api_key = ""
|
|
111
|
+
_recording_project_id = ""
|
|
112
|
+
_recording_agent_name = ""
|
|
113
|
+
_recording_session_id = ""
|
|
114
|
+
|
|
115
|
+
# RPCs that should NOT be recorded (internal/heartbeat/recording itself)
|
|
116
|
+
_SKIP_RECORDING = {"mc_heartbeat", "mc_record_event", "mc_agent_set_status_by_api_key",
|
|
117
|
+
"mc_acquire_agent_lease", "mc_release_agent_lease"}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def enable_recording(api_key: str, project_id: str, agent_name: str, session_id: str):
|
|
121
|
+
"""Called by MCP server at boot to enable session recording."""
|
|
122
|
+
global _recording_enabled, _recording_api_key, _recording_project_id
|
|
123
|
+
global _recording_agent_name, _recording_session_id
|
|
124
|
+
_recording_enabled = True
|
|
125
|
+
_recording_api_key = api_key
|
|
126
|
+
_recording_project_id = project_id
|
|
127
|
+
_recording_agent_name = agent_name
|
|
128
|
+
_recording_session_id = session_id
|
|
129
|
+
|
|
130
|
+
|
|
108
131
|
def sb_rpc(fn_name: str, params: Dict) -> Any:
|
|
109
132
|
url = f"{SUPABASE_URL}/rest/v1/rpc/{fn_name}"
|
|
110
133
|
body = json.dumps(params).encode("utf-8")
|
|
@@ -112,16 +135,57 @@ def sb_rpc(fn_name: str, params: Dict) -> Any:
|
|
|
112
135
|
try:
|
|
113
136
|
with urlopen(req, timeout=10) as resp:
|
|
114
137
|
raw = resp.read().decode("utf-8")
|
|
115
|
-
|
|
138
|
+
result = json.loads(raw) if raw.strip() else None
|
|
116
139
|
except HTTPError as e:
|
|
117
140
|
err = e.read().decode("utf-8", errors="replace")
|
|
118
141
|
try:
|
|
119
|
-
|
|
142
|
+
result = {"_error": json.loads(err).get("message", err[:200])}
|
|
120
143
|
except Exception:
|
|
121
|
-
|
|
144
|
+
result = {"_error": err[:200]}
|
|
145
|
+
# Record errors too
|
|
146
|
+
if _recording_enabled and fn_name not in _SKIP_RECORDING:
|
|
147
|
+
_bg_record("error", {"rpc": fn_name, "error": str(err)[:200]})
|
|
148
|
+
return result
|
|
122
149
|
except URLError as e:
|
|
123
150
|
return {"_error": str(e.reason)}
|
|
124
151
|
|
|
152
|
+
# Auto-record tool calls to session events (hot-reloadable)
|
|
153
|
+
if _recording_enabled and fn_name not in _SKIP_RECORDING:
|
|
154
|
+
_bg_record("tool_call", {"rpc": fn_name})
|
|
155
|
+
|
|
156
|
+
return result
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _bg_record(event_type: str, payload: dict):
|
|
160
|
+
"""Background-thread recording — never blocks the caller."""
|
|
161
|
+
import threading
|
|
162
|
+
def _do():
|
|
163
|
+
try:
|
|
164
|
+
sb_rpc_raw("mc_record_event", {
|
|
165
|
+
"p_api_key": _recording_api_key,
|
|
166
|
+
"p_project_id": _recording_project_id,
|
|
167
|
+
"p_agent_name": _recording_agent_name,
|
|
168
|
+
"p_session_id": _recording_session_id,
|
|
169
|
+
"p_event_type": event_type,
|
|
170
|
+
"p_payload": payload,
|
|
171
|
+
})
|
|
172
|
+
except Exception:
|
|
173
|
+
pass
|
|
174
|
+
threading.Thread(target=_do, daemon=True).start()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def sb_rpc_raw(fn_name: str, params: Dict) -> Any:
|
|
178
|
+
"""Raw RPC call without recording (to avoid infinite recursion)."""
|
|
179
|
+
url = f"{SUPABASE_URL}/rest/v1/rpc/{fn_name}"
|
|
180
|
+
body = json.dumps(params).encode("utf-8")
|
|
181
|
+
req = Request(url, data=body, method="POST", headers=_headers(content_profile=False))
|
|
182
|
+
try:
|
|
183
|
+
with urlopen(req, timeout=10) as resp:
|
|
184
|
+
raw = resp.read().decode("utf-8")
|
|
185
|
+
return json.loads(raw) if raw.strip() else None
|
|
186
|
+
except Exception:
|
|
187
|
+
return None
|
|
188
|
+
|
|
125
189
|
|
|
126
190
|
# ============================================================
|
|
127
191
|
# Project + agent helpers
|
|
@@ -342,8 +342,8 @@ _current_state = "online"
|
|
|
342
342
|
_last_tool_at = _time.time()
|
|
343
343
|
_current_tool = ""
|
|
344
344
|
_IDLE_THRESHOLD_S = 120 # seconds without tool call → IDLE
|
|
345
|
-
|
|
346
|
-
|
|
345
|
+
_WORKING_COOLDOWN_S = 60 # seconds after last tool returns before flipping to ONLINE
|
|
346
|
+
_working_timer: Optional[_threading.Timer] = None
|
|
347
347
|
|
|
348
348
|
|
|
349
349
|
async def _async_flip_status(status: str, task: str = "") -> None:
|
|
@@ -372,11 +372,11 @@ def _schedule_flip(status: str, task: str = "") -> None:
|
|
|
372
372
|
|
|
373
373
|
def _set_state(state: str, tool: str = "") -> None:
|
|
374
374
|
"""Update the state machine and broadcast to dashboard."""
|
|
375
|
-
global _current_state, _current_tool, _last_tool_at,
|
|
376
|
-
# Cancel any pending
|
|
377
|
-
if
|
|
378
|
-
|
|
379
|
-
|
|
375
|
+
global _current_state, _current_tool, _last_tool_at, _working_timer
|
|
376
|
+
# Cancel any pending working→online timer
|
|
377
|
+
if _working_timer is not None:
|
|
378
|
+
_working_timer.cancel()
|
|
379
|
+
_working_timer = None
|
|
380
380
|
_current_state = state
|
|
381
381
|
_current_tool = tool
|
|
382
382
|
if state == "working":
|
|
@@ -384,11 +384,11 @@ def _set_state(state: str, tool: str = "") -> None:
|
|
|
384
384
|
_schedule_flip(state, tool)
|
|
385
385
|
|
|
386
386
|
|
|
387
|
-
def
|
|
388
|
-
"""Called after
|
|
389
|
-
global
|
|
390
|
-
|
|
391
|
-
if _current_state == "
|
|
387
|
+
def _working_to_online() -> None:
|
|
388
|
+
"""Called after WORKING cooldown expires — flip to ONLINE."""
|
|
389
|
+
global _working_timer
|
|
390
|
+
_working_timer = None
|
|
391
|
+
if _current_state == "working":
|
|
392
392
|
_set_state("online", "")
|
|
393
393
|
|
|
394
394
|
|
|
@@ -406,13 +406,12 @@ def with_working_status(func):
|
|
|
406
406
|
return await func(*args, **kwargs)
|
|
407
407
|
finally:
|
|
408
408
|
if not skip:
|
|
409
|
-
global _last_tool_at,
|
|
409
|
+
global _last_tool_at, _working_timer
|
|
410
410
|
_last_tool_at = _time.time()
|
|
411
|
-
#
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
_processing_timer.start()
|
|
411
|
+
# Stay WORKING with 60s cooldown — covers LLM thinking between tools
|
|
412
|
+
_working_timer = _threading.Timer(_WORKING_COOLDOWN_S, _working_to_online)
|
|
413
|
+
_working_timer.daemon = True
|
|
414
|
+
_working_timer.start()
|
|
416
415
|
return awrapper
|
|
417
416
|
else:
|
|
418
417
|
@_functools.wraps(func)
|
|
@@ -700,9 +699,9 @@ def _heartbeat_thread_fn():
|
|
|
700
699
|
while not _heartbeat_stop.is_set():
|
|
701
700
|
try:
|
|
702
701
|
be.sb_rpc("mc_heartbeat", {"p_project_id": _PROJECT_ID, "p_agent_name": AGENT_NAME, "p_version": "2.0.0"})
|
|
703
|
-
# Idle detection: only from online
|
|
702
|
+
# Idle detection: only from online state (NOT waiting or working)
|
|
704
703
|
# Agents in meshcode_wait should stay WAITING, not flip to IDLE
|
|
705
|
-
if _current_state
|
|
704
|
+
if _current_state == "online" and (_time.time() - _last_tool_at) > _IDLE_THRESHOLD_S:
|
|
706
705
|
_set_state("idle", f"idle ({int((_time.time() - _last_tool_at) / 60)}m)")
|
|
707
706
|
# Sync current state to DB (in case realtime missed it)
|
|
708
707
|
try:
|
|
@@ -763,6 +762,11 @@ async def lifespan(_app):
|
|
|
763
762
|
hb_thread = _threading.Thread(target=_heartbeat_thread_fn, daemon=True, name="meshcode-heartbeat")
|
|
764
763
|
hb_thread.start()
|
|
765
764
|
log.info(f"lifespan started — Realtime + heartbeat thread active for {AGENT_NAME}")
|
|
765
|
+
# Enable session recording in backend.py (hot-reloadable)
|
|
766
|
+
try:
|
|
767
|
+
be.enable_recording(_get_api_key(), _PROJECT_ID, AGENT_NAME, _SESSION_ID)
|
|
768
|
+
except Exception:
|
|
769
|
+
pass
|
|
766
770
|
_record_event_bg("boot", {"agent": AGENT_NAME, "project": PROJECT_NAME, "session_id": _SESSION_ID})
|
|
767
771
|
try:
|
|
768
772
|
yield {"realtime": _REALTIME}
|
|
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
|