meshcode 2.11.93__tar.gz → 2.11.96__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.11.93 → meshcode-2.11.96}/PKG-INFO +1 -1
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/__init__.py +1 -1
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/hostd.py +35 -3
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/server.py +81 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.93 → meshcode-2.11.96}/pyproject.toml +1 -1
- {meshcode-2.11.93 → meshcode-2.11.96}/README.md +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/__main__.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/cli.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/compat.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/daemon.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/doctor.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/invites.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/launcher.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/preferences.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/secrets.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/self_update.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/up.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/upload.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/setup.cfg +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_core.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_doctor.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -446,9 +446,15 @@ def _do_respawns(api_key: str, host_id: str) -> int:
|
|
|
446
446
|
# PROMPTLY (the RPC returned it at a 15s stale gate, not STALE_SECONDS) and recorded as a
|
|
447
447
|
# RECYCLE (mc_record_recycle), NEVER against the crash respawn cap.
|
|
448
448
|
_is_recycle = bool(c.get("recycle_fast"))
|
|
449
|
+
# 798dd1bd: on-demand recycle with recycle_visible -> reopen a FRESH VISIBLE window
|
|
450
|
+
# (Samuel's "terminals reopen fresh"); else PRESERVE the agent's headless state
|
|
451
|
+
# (don't silently un-headless a headless fleet — backend2 A2). One-shot: mc_record_recycle
|
|
452
|
+
# clears recycle_visible so subsequent respawns use the normal headless flag.
|
|
453
|
+
_visible = _is_recycle and bool(c.get("recycle_visible"))
|
|
454
|
+
_hl = False if _visible else bool(c.get("headless"))
|
|
449
455
|
_log(f"{'RECYCLE-RESPAWN' if _is_recycle else 'RESPAWN'} {proj}/{agent} "
|
|
450
|
-
f"(stale {c.get('heartbeat_age_s')}s, count={c.get('respawn_count')})")
|
|
451
|
-
if _spawn_agent(proj, agent, headless=
|
|
456
|
+
f"({'VISIBLE ' if _visible else ''}stale {c.get('heartbeat_age_s')}s, count={c.get('respawn_count')})")
|
|
457
|
+
if _spawn_agent(proj, agent, headless=_hl):
|
|
452
458
|
if _is_recycle:
|
|
453
459
|
_rpc("mc_record_recycle",
|
|
454
460
|
{"p_api_key": api_key, "p_project_id": c.get("project_id"), "p_agent_name": agent})
|
|
@@ -515,13 +521,38 @@ def _record_headless_pid(target: str, pid: int) -> None:
|
|
|
515
521
|
try:
|
|
516
522
|
st = _load_state()
|
|
517
523
|
pids = st.get("headless_pids") or {}
|
|
518
|
-
pids[target] = pid
|
|
524
|
+
pids[target] = pid # overwrite any stale entry for this target (refresh on respawn)
|
|
519
525
|
st["headless_pids"] = pids
|
|
520
526
|
_save_state(st)
|
|
521
527
|
except Exception:
|
|
522
528
|
pass
|
|
523
529
|
|
|
524
530
|
|
|
531
|
+
def _gc_headless_pids() -> None:
|
|
532
|
+
"""GC dead PIDs from headless_pids (cb90b058) so a stale entry can't mask a live agent +
|
|
533
|
+
the recorded PID stays accurate for enforce. Cheap; runs once per sweep. Best-effort."""
|
|
534
|
+
try:
|
|
535
|
+
st = _load_state()
|
|
536
|
+
pids = st.get("headless_pids") or {}
|
|
537
|
+
if not pids:
|
|
538
|
+
return
|
|
539
|
+
live = {}
|
|
540
|
+
for target, pid in pids.items():
|
|
541
|
+
try:
|
|
542
|
+
os.kill(int(pid), 0) # raises ProcessLookupError if the PID is gone
|
|
543
|
+
live[target] = pid
|
|
544
|
+
except (ProcessLookupError, ValueError):
|
|
545
|
+
pass # dead / bad -> drop
|
|
546
|
+
except Exception:
|
|
547
|
+
live[target] = pid # alive (PermissionError) or unknown -> keep (reuse-guard covers kill-time)
|
|
548
|
+
if len(live) != len(pids):
|
|
549
|
+
st["headless_pids"] = live
|
|
550
|
+
_save_state(st)
|
|
551
|
+
_log(f"GC headless_pids: dropped {len(pids)-len(live)} dead PID(s)")
|
|
552
|
+
except Exception:
|
|
553
|
+
pass
|
|
554
|
+
|
|
555
|
+
|
|
525
556
|
def _pid_cmdline(pid: int) -> str:
|
|
526
557
|
"""Best-effort command line for a pid (to avoid killing a reused PID). '' on failure."""
|
|
527
558
|
try:
|
|
@@ -1206,6 +1237,7 @@ def cmd_hostd(args: list) -> int:
|
|
|
1206
1237
|
recycled = _do_recycles(api_key, host_id)
|
|
1207
1238
|
ver_recycled = _do_version_recycles(api_key, host_id)
|
|
1208
1239
|
stopped = _do_stops(api_key, host_id)
|
|
1240
|
+
_gc_headless_pids() # cb90b058: drop dead PIDs (stale entry can't mask a live agent)
|
|
1209
1241
|
_up = int(time.monotonic() - _spawn_mono)
|
|
1210
1242
|
if relaunched or recycled or ver_recycled or stopped or enforced:
|
|
1211
1243
|
_log(f"sweep done (uptime={_up}s) — {relaunched} respawned, {recycled} recycled, {ver_recycled} version-recycled, {stopped} stopped, {enforced} recycle-enforced")
|
|
@@ -621,6 +621,37 @@ def _record_event_bg(event_type: str, payload: dict = None) -> None:
|
|
|
621
621
|
pass
|
|
622
622
|
|
|
623
623
|
|
|
624
|
+
# FLEET-RECYCLE P0 (task 798dd1bd): stamp mc_agents.last_active_at on FOREGROUND
|
|
625
|
+
# model activity (every tool call, incl meshcode_wait) so the recycle enforce
|
|
626
|
+
# sweep (mc_recycle_enforce_candidates, mig419) can distinguish a genuinely-HUNG
|
|
627
|
+
# agent — model stopped calling tools -> last_active_at goes STALE -> enforce —
|
|
628
|
+
# from an attended/busy-looping one — still cycling -> FRESH -> defer. CRUX
|
|
629
|
+
# (commander hard requirement): this MUST be triggered by foreground tool calls,
|
|
630
|
+
# NEVER the bg heartbeat thread (which beats even when the model is hung; that
|
|
631
|
+
# would keep last_active_at fresh forever and defeat the whole defer). Client-
|
|
632
|
+
# throttled to <=1/30s to avoid write amplification on rapid tool bursts; fired
|
|
633
|
+
# in a daemon thread so it adds zero latency to the tool call.
|
|
634
|
+
_last_active_stamp_mono: float = 0.0
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _touch_active_bg() -> None:
|
|
638
|
+
"""Fire-and-forget foreground last_active_at stamp, throttled to <=1 per 30s."""
|
|
639
|
+
global _last_active_stamp_mono
|
|
640
|
+
try:
|
|
641
|
+
now = _time.monotonic()
|
|
642
|
+
if now - _last_active_stamp_mono < 30:
|
|
643
|
+
return
|
|
644
|
+
_last_active_stamp_mono = now
|
|
645
|
+
import threading
|
|
646
|
+
threading.Thread(
|
|
647
|
+
target=be.sb_rpc,
|
|
648
|
+
args=("mc_touch_active", {"p_project_id": _PROJECT_ID, "p_agent_name": AGENT_NAME}),
|
|
649
|
+
daemon=True,
|
|
650
|
+
).start()
|
|
651
|
+
except Exception:
|
|
652
|
+
pass
|
|
653
|
+
|
|
654
|
+
|
|
624
655
|
# ============================================================
|
|
625
656
|
# Hot-reload: detect when backend.py changes on disk (e.g. after
|
|
626
657
|
# pip install --upgrade meshcode) and reload without restart.
|
|
@@ -1005,6 +1036,7 @@ def with_working_status(func):
|
|
|
1005
1036
|
async def awrapper(*args, **kwargs):
|
|
1006
1037
|
_check_hot_reload()
|
|
1007
1038
|
_capture_session() # stash session on first tool call for silent auto-wake
|
|
1039
|
+
_touch_active_bg() # FLEET-RECYCLE P0: foreground last_active_at stamp (covers wait too, before skip)
|
|
1008
1040
|
if not skip:
|
|
1009
1041
|
global _CONSECUTIVE_IDLE_SECONDS
|
|
1010
1042
|
_CONSECUTIVE_IDLE_SECONDS = 0 # any non-wait tool resets idle timer
|
|
@@ -1052,6 +1084,7 @@ def with_working_status(func):
|
|
|
1052
1084
|
def swrapper(*args, **kwargs):
|
|
1053
1085
|
_check_hot_reload()
|
|
1054
1086
|
_capture_session() # stash session on first tool call for silent auto-wake
|
|
1087
|
+
_touch_active_bg() # FLEET-RECYCLE P0: foreground last_active_at stamp (covers wait too, before skip)
|
|
1055
1088
|
if not skip:
|
|
1056
1089
|
global _CONSECUTIVE_IDLE_SECONDS
|
|
1057
1090
|
_CONSECUTIVE_IDLE_SECONDS = 0 # any non-wait tool resets idle timer
|
|
@@ -6090,6 +6123,54 @@ def meshcode_agent_stop(name: str) -> Dict[str, Any]:
|
|
|
6090
6123
|
})
|
|
6091
6124
|
|
|
6092
6125
|
|
|
6126
|
+
@mcp.tool()
|
|
6127
|
+
@with_working_status
|
|
6128
|
+
def meshcode_recycle_agent(name: str, visible: bool = False) -> Dict[str, Any]:
|
|
6129
|
+
"""Recycle (close + relaunch FRESH) another agent in YOUR meshwork — to
|
|
6130
|
+
clear a degraded/high-context session and bring it back clean.
|
|
6131
|
+
|
|
6132
|
+
FLEET-RECYCLE (task 798dd1bd). Requests a recycle: the agent consumes it at
|
|
6133
|
+
a task-edge, exits, and the host daemon respawns it fresh with its handoff
|
|
6134
|
+
restored (SessionStart). Cloud-driven so it works remotely. Commander agent
|
|
6135
|
+
(or human owner) only, scoped to your own meshwork — enforced server-side by
|
|
6136
|
+
mc_recycle_as_agent (same authz as mc_agent_power_as_agent).
|
|
6137
|
+
|
|
6138
|
+
Args:
|
|
6139
|
+
name: target agent name in your meshwork.
|
|
6140
|
+
visible: True = respawn as a visible focused window; default False =
|
|
6141
|
+
preserve the agent's current headless/visible state.
|
|
6142
|
+
"""
|
|
6143
|
+
return be.sb_rpc("mc_recycle_as_agent", {
|
|
6144
|
+
"p_api_key": _get_api_key(),
|
|
6145
|
+
"p_project_id": _PROJECT_ID,
|
|
6146
|
+
"p_agent": name,
|
|
6147
|
+
"p_visible": bool(visible),
|
|
6148
|
+
})
|
|
6149
|
+
|
|
6150
|
+
|
|
6151
|
+
@mcp.tool()
|
|
6152
|
+
@with_working_status
|
|
6153
|
+
def meshcode_recycle_fleet(visible: bool = False) -> Dict[str, Any]:
|
|
6154
|
+
"""Recycle (close + relaunch FRESH) ALL running agents in YOUR meshwork —
|
|
6155
|
+
a clean-slate sweep of the whole fleet.
|
|
6156
|
+
|
|
6157
|
+
FLEET-RECYCLE (task 798dd1bd). Each agent consumes the recycle at its own
|
|
6158
|
+
task-edge, exits, and respawns fresh with handoff restored — no agent is
|
|
6159
|
+
killed mid-task. Cloud-driven (remote-capable). Commander agent (or human
|
|
6160
|
+
owner) only, scoped to your own meshwork — enforced server-side.
|
|
6161
|
+
|
|
6162
|
+
Args:
|
|
6163
|
+
visible: True = respawn each as a visible focused window; default False
|
|
6164
|
+
= preserve each agent's current headless/visible state.
|
|
6165
|
+
"""
|
|
6166
|
+
return be.sb_rpc("mc_recycle_as_agent", {
|
|
6167
|
+
"p_api_key": _get_api_key(),
|
|
6168
|
+
"p_project_id": _PROJECT_ID,
|
|
6169
|
+
"p_agent": None, # NULL = whole fleet (all running agents)
|
|
6170
|
+
"p_visible": bool(visible),
|
|
6171
|
+
})
|
|
6172
|
+
|
|
6173
|
+
|
|
6093
6174
|
@mcp.tool()
|
|
6094
6175
|
@with_working_status
|
|
6095
6176
|
def meshcode_set_meshwork_autonomous_default(enabled: bool) -> Dict[str, Any]:
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|