meshcode 2.11.121__tar.gz → 2.11.122__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.121 → meshcode-2.11.122}/PKG-INFO +1 -1
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/__init__.py +1 -1
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/hostd.py +55 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/server.py +28 -19
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.121 → meshcode-2.11.122}/pyproject.toml +1 -1
- {meshcode-2.11.121 → meshcode-2.11.122}/README.md +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/__main__.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/cli.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/compat.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/daemon.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/doctor.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/hooks/__init__.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/hooks/repo_path_lock.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/invites.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/launcher.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/preferences.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/secrets.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/self_update.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/up.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode/upload.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/setup.cfg +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_core.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_doctor.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_swarm_events.py +0 -0
- {meshcode-2.11.121 → meshcode-2.11.122}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -1762,6 +1762,52 @@ def _do_version_recycles(api_key: str, host_id: str) -> int:
|
|
|
1762
1762
|
return n
|
|
1763
1763
|
|
|
1764
1764
|
|
|
1765
|
+
# c301be69: hold the singleton lock handle for the daemon's lifetime — module
|
|
1766
|
+
# global so it's never GC'd (GC would close the fd and release the lock).
|
|
1767
|
+
_HOSTD_LOCK_FH = None
|
|
1768
|
+
|
|
1769
|
+
|
|
1770
|
+
def _acquire_hostd_singleton():
|
|
1771
|
+
"""One hostd per machine (task c301be69 — duplicate-daemon race observed
|
|
1772
|
+
live 2026-06-09: a manual `nohup meshcode hostd run` raced auto-bootstrap,
|
|
1773
|
+
TWO daemons ran ~30s = double respawn/recycle sweeps = storm class).
|
|
1774
|
+
|
|
1775
|
+
Exclusive flock on ~/.meshcode/hostd.lock, held for the process lifetime.
|
|
1776
|
+
The OS releases it on ANY death (crash, kill -9), so there is no
|
|
1777
|
+
stale-pidfile problem; the pid written inside is informational only.
|
|
1778
|
+
Python opens fds CLOEXEC (PEP 446), so the self-reexec-on-version-drift
|
|
1779
|
+
path releases the lock at exec time and the fresh image re-acquires it.
|
|
1780
|
+
|
|
1781
|
+
Returns (fh, status): status in 'acquired' | 'held' | 'error'.
|
|
1782
|
+
'error' means the lock could not be evaluated — callers FAIL OPEN
|
|
1783
|
+
(one possibly-duplicate daemon beats no daemon at all)."""
|
|
1784
|
+
try:
|
|
1785
|
+
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
1786
|
+
fh = open(STATE_DIR / "hostd.lock", "a+")
|
|
1787
|
+
try:
|
|
1788
|
+
if sys.platform == "win32":
|
|
1789
|
+
import msvcrt
|
|
1790
|
+
fh.seek(0)
|
|
1791
|
+
msvcrt.locking(fh.fileno(), msvcrt.LK_NBLCK, 1)
|
|
1792
|
+
else:
|
|
1793
|
+
import fcntl
|
|
1794
|
+
fcntl.flock(fh.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
1795
|
+
except OSError:
|
|
1796
|
+
fh.close()
|
|
1797
|
+
return (None, "held")
|
|
1798
|
+
try:
|
|
1799
|
+
fh.seek(0)
|
|
1800
|
+
fh.truncate()
|
|
1801
|
+
fh.write(str(os.getpid()))
|
|
1802
|
+
fh.flush()
|
|
1803
|
+
except Exception:
|
|
1804
|
+
pass
|
|
1805
|
+
return (fh, "acquired")
|
|
1806
|
+
except Exception as e:
|
|
1807
|
+
_log(f"WARN singleton lock unavailable ({e}) — proceeding unlocked (fail-open)")
|
|
1808
|
+
return (None, "error")
|
|
1809
|
+
|
|
1810
|
+
|
|
1765
1811
|
def cmd_hostd(args: list) -> int:
|
|
1766
1812
|
"""Entry point for `meshcode hostd ...`."""
|
|
1767
1813
|
if not args or args[0] in ("-h", "--help"):
|
|
@@ -1790,6 +1836,15 @@ def cmd_hostd(args: list) -> int:
|
|
|
1790
1836
|
if not api_key:
|
|
1791
1837
|
_log("FATAL: no api key — run `meshcode login` (key is read from the keychain)")
|
|
1792
1838
|
return 1
|
|
1839
|
+
# SINGLETON (task c301be69): second instance exits 0 with one log line.
|
|
1840
|
+
# Covers the manual-run vs auto-bootstrap race; _hostd_bootstrap_if_needed
|
|
1841
|
+
# honors it implicitly — its spawned `hostd run` hits this gate and exits.
|
|
1842
|
+
global _HOSTD_LOCK_FH
|
|
1843
|
+
_lk, _lk_status = _acquire_hostd_singleton()
|
|
1844
|
+
if _lk_status == "held":
|
|
1845
|
+
_log("hostd already running on this machine (hostd.lock held) — exiting (singleton c301be69)")
|
|
1846
|
+
return 0
|
|
1847
|
+
_HOSTD_LOCK_FH = _lk # may be None on 'error' (fail-open)
|
|
1793
1848
|
# P1 hostd_CRASH_LOOP: the detached Windows daemon has NO console, so crashes (incl hard
|
|
1794
1849
|
# faults / uncaught exceptions) went to INVISIBLE stderr — hostd.log showed nothing. Capture
|
|
1795
1850
|
# stderr + faulthandler tracebacks to a file + install an excepthook so the NEXT crash is
|
|
@@ -4109,11 +4109,14 @@ def _try_auto_complete_on_ship(payload: Dict[str, Any]) -> Optional[Dict[str, An
|
|
|
4109
4109
|
return None
|
|
4110
4110
|
|
|
4111
4111
|
|
|
4112
|
-
def
|
|
4113
|
-
"""CLOSEGAP-3
|
|
4114
|
-
in_progress
|
|
4115
|
-
|
|
4116
|
-
|
|
4112
|
+
def _try_auto_park_on_done_status(task_hint: str, status: str = "done") -> Optional[Dict[str, Any]]:
|
|
4113
|
+
"""CLOSEGAP-3 v2 (task 53767993): when set_status='done'/'sleeping' with
|
|
4114
|
+
exactly ONE in_progress claim, PARK it — roll back to 'claimed' with a
|
|
4115
|
+
checkpoint note (mc_task_checkpoint_park, mig 476) — NEVER complete it.
|
|
4116
|
+
Live incident 2026-06-09T23:30Z: the old auto-COMPLETE closed 250b7c1e as
|
|
4117
|
+
done with zero work at an ordered exit-for-respawn (board lied to Samuel;
|
|
4118
|
+
re-opened as 7a467894). status='done' may now ONLY come from an explicit
|
|
4119
|
+
meshcode_task_complete. No-op for 0 or >1 active claims."""
|
|
4117
4120
|
try:
|
|
4118
4121
|
api_key = _get_api_key()
|
|
4119
4122
|
if not api_key:
|
|
@@ -4128,24 +4131,30 @@ def _try_auto_complete_on_done_status(task_hint: str) -> Optional[Dict[str, Any]
|
|
|
4128
4131
|
if len(active) != 1:
|
|
4129
4132
|
return None
|
|
4130
4133
|
target = active[0]
|
|
4131
|
-
|
|
4132
|
-
f"
|
|
4133
|
-
f"Task hint: {(task_hint or '(none)')[:200]}. "
|
|
4134
|
-
f"
|
|
4134
|
+
note = (
|
|
4135
|
+
f"CHECKPOINT (CLOSEGAP-3 park): agent flipped set_status='{status}' "
|
|
4136
|
+
f"mid-claim (rotation/sleep). Task hint: {(task_hint or '(none)')[:200]}. "
|
|
4137
|
+
f"Work NOT verified complete — resuming agent must task_complete explicitly."
|
|
4135
4138
|
)
|
|
4136
|
-
result = be.
|
|
4139
|
+
result = be.sb_rpc("mc_task_checkpoint_park", {
|
|
4140
|
+
"p_api_key": api_key,
|
|
4141
|
+
"p_project_id": _PROJECT_ID,
|
|
4142
|
+
"p_task_id": target["id"],
|
|
4143
|
+
"p_agent": AGENT_NAME,
|
|
4144
|
+
"p_note": note,
|
|
4145
|
+
})
|
|
4137
4146
|
if not (isinstance(result, dict) and result.get("ok")):
|
|
4138
4147
|
return None
|
|
4139
4148
|
try:
|
|
4140
4149
|
_log_activity_bg(
|
|
4141
4150
|
"auto_checkpoint",
|
|
4142
|
-
f"{AGENT_NAME}
|
|
4151
|
+
f"{AGENT_NAME} PARKED task {target['id'][:8]} on set_status={status} (claimed again, NOT done)",
|
|
4143
4152
|
)
|
|
4144
4153
|
except Exception:
|
|
4145
4154
|
pass
|
|
4146
|
-
return {"task_id": target["id"][:8], "title": str(target.get("title") or "")[:80]}
|
|
4155
|
+
return {"task_id": target["id"][:8], "title": str(target.get("title") or "")[:80], "parked": True}
|
|
4147
4156
|
except Exception as e:
|
|
4148
|
-
log.debug(f"[meshcode] auto-
|
|
4157
|
+
log.debug(f"[meshcode] auto-park on done-status failed: {e}")
|
|
4149
4158
|
return None
|
|
4150
4159
|
|
|
4151
4160
|
|
|
@@ -5079,15 +5088,15 @@ def meshcode_set_status(status: str, task: str = "") -> Dict[str, Any]:
|
|
|
5079
5088
|
}
|
|
5080
5089
|
except Exception:
|
|
5081
5090
|
pass # best-effort; never block set_status
|
|
5082
|
-
# CLOSEGAP-3 (task
|
|
5083
|
-
#
|
|
5084
|
-
#
|
|
5091
|
+
# CLOSEGAP-3 v2 (task 53767993, was 69dc5a62): when the agent flips
|
|
5092
|
+
# set_status='done'/'sleeping' with exactly one active in_progress claim,
|
|
5093
|
+
# PARK that claim (back to 'claimed' + checkpoint note) — never complete.
|
|
5085
5094
|
# Baseline (NOT autonomous-gated). Best-effort.
|
|
5086
|
-
if status
|
|
5095
|
+
if status in ("done", "sleeping") and isinstance(resp, dict) and resp.get("ok"):
|
|
5087
5096
|
try:
|
|
5088
|
-
_cp =
|
|
5097
|
+
_cp = _try_auto_park_on_done_status(task, status)
|
|
5089
5098
|
if _cp:
|
|
5090
|
-
resp["
|
|
5099
|
+
resp["auto_parked_task"] = _cp
|
|
5091
5100
|
except Exception:
|
|
5092
5101
|
pass
|
|
5093
5102
|
return resp
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|