meshcode 2.11.159__tar.gz → 2.11.160__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.159 → meshcode-2.11.160}/PKG-INFO +1 -1
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/__init__.py +1 -1
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/comms_v4.py +22 -2
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/hostd.py +116 -24
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/self_update.py +35 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.159 → meshcode-2.11.160}/pyproject.toml +1 -1
- {meshcode-2.11.159 → meshcode-2.11.160}/README.md +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/__main__.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/_launch_smoke.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/_update_guard.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/cli.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/compat.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/daemon.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/doctor.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/hooks/__init__.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/hooks/push_guard.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/hooks/repo_path_lock.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/invites.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/launcher.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/preferences.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/secrets.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/up.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode/upload.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/setup.cfg +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_core.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_doctor.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_ensure_boot_env_urgent_wake.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_fleet_reaper.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_hostd_launch_pinned_env.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_hostd_serve_discovery_split.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_hostd_zombie_sessions.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_launch_smoke.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_live_mesh_guard.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_no_appleevents_on_sweep.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_preflight_hb_gate.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_pretrust_claude.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_push_guard.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_replica_base_workspace_fallback.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_replica_boot_protocol_unconditional.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_rm_guard.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_session_replay_gate.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_stop_ghost_terminal.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_task_progress.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_terminal_lifecycle.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_up_launch_cmd.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_update_guard.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_urgent_wake_tmux.py +0 -0
- {meshcode-2.11.159 → meshcode-2.11.160}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -2519,6 +2519,7 @@ DIAGNOSTICS:
|
|
|
2519
2519
|
doctor [--fix] Diagnose setup issues
|
|
2520
2520
|
compat Claude Code version compatibility
|
|
2521
2521
|
upgrade Upgrade meshcode to latest version
|
|
2522
|
+
self-upgrade Unattended deferral-safe upgrade (used by the hostd launcher)
|
|
2522
2523
|
|
|
2523
2524
|
ADMIN:
|
|
2524
2525
|
clear <proj> <name> Clear inbox
|
|
@@ -3103,7 +3104,7 @@ if __name__ == "__main__":
|
|
|
3103
3104
|
|
|
3104
3105
|
# Auth guard: commands that talk to Supabase need a valid API key.
|
|
3105
3106
|
# doctor, help, version, login, prefs, launcher don't need auth.
|
|
3106
|
-
_NO_AUTH_CMDS = {"doctor", "compat", "upgrade", "help", "--help", "-h", "login",
|
|
3107
|
+
_NO_AUTH_CMDS = {"doctor", "compat", "upgrade", "self-upgrade", "help", "--help", "-h", "login",
|
|
3107
3108
|
"init", "prefs", "launcher", "--version", "-V", "version",
|
|
3108
3109
|
"whoami", "profiles", "scan", "setup-path"}
|
|
3109
3110
|
if cmd not in _NO_AUTH_CMDS:
|
|
@@ -4010,6 +4011,25 @@ if __name__ == "__main__":
|
|
|
4010
4011
|
print(f" Upgrade cancelled.")
|
|
4011
4012
|
sys.exit(0)
|
|
4012
4013
|
|
|
4014
|
+
elif cmd == "self-upgrade":
|
|
4015
|
+
# meshcode self-upgrade — UNATTENDED, deferral-safe self-upgrade for launcher
|
|
4016
|
+
# pre-steps (task d84cbfa1, version-split self-heal). Distinct from:
|
|
4017
|
+
# - `meshcode upgrade` (INTERACTIVE force escape-hatch; cancels on no-TTY and
|
|
4018
|
+
# overwrites the env even while live agents share it), and
|
|
4019
|
+
# - `meshcode update` (comms STATUS update — unrelated).
|
|
4020
|
+
# Uses the BLOCKING updater with force=False so it DEFERS when a live agent serve
|
|
4021
|
+
# shares the env (never clobbers a running MCP — the spec's live-agent deferral
|
|
4022
|
+
# constraint). Honors MESHCODE_NO_UPDATE / MESHCODE_NO_AUTO_UPDATE (the hostd
|
|
4023
|
+
# launcher clears them for this one explicit step). Never raises; always exits 0
|
|
4024
|
+
# so a failed/offline upgrade can never wedge the launcher.
|
|
4025
|
+
import importlib
|
|
4026
|
+
try:
|
|
4027
|
+
_su = importlib.import_module("meshcode.self_update")
|
|
4028
|
+
_su.check_and_maybe_update_blocking(verbose=True, force=False)
|
|
4029
|
+
except Exception as _e:
|
|
4030
|
+
print(f"[meshcode] self-upgrade skipped: {_e}", file=sys.stderr)
|
|
4031
|
+
sys.exit(0)
|
|
4032
|
+
|
|
4013
4033
|
elif cmd == "compat":
|
|
4014
4034
|
from meshcode.compat import check as cc_check, format_report, RECOMMENDED_VERSION
|
|
4015
4035
|
version, status, entry = cc_check()
|
|
@@ -4134,7 +4154,7 @@ if __name__ == "__main__":
|
|
|
4134
4154
|
"history", "clear", "unregister", "connect", "disconnect",
|
|
4135
4155
|
"setup", "add-agent", "run", "go", "invite", "join", "invites", "members",
|
|
4136
4156
|
"revoke-invite", "revoke-member", "login", "prefs", "launcher",
|
|
4137
|
-
"help", "init", "doctor", "compat", "upgrade", "profile", "validate-sessions", "wake-headless",
|
|
4157
|
+
"help", "init", "doctor", "compat", "upgrade", "self-upgrade", "profile", "validate-sessions", "wake-headless",
|
|
4138
4158
|
"supervisor", "hostd", "up", "upload", "quickstart", "patch-hooks", "wake-all",
|
|
4139
4159
|
"install-protocol", "launch-url", "launch-batch",
|
|
4140
4160
|
]
|
|
@@ -97,33 +97,56 @@ def _maybe_self_restart_on_version_drift() -> None:
|
|
|
97
97
|
_su.check_and_maybe_update() # non-blocking, detached, TTL-guarded internally
|
|
98
98
|
except Exception:
|
|
99
99
|
pass
|
|
100
|
+
# Resolve the best target to converge UP to: the NEWER of
|
|
101
|
+
# (a) the pip-installed site-packages METADATA version, and
|
|
102
|
+
# (b) the newest FINALIZED immutable env under ~/.meshcode/envs/<v>
|
|
103
|
+
# (task d84cbfa1 converge-UP — adopt a version boot-always-latest
|
|
104
|
+
# already built locally even if site-packages is stale).
|
|
105
|
+
# target_py is the interpreter we'd re-exec onto: our own for the metadata
|
|
106
|
+
# path, the env's python for the env-adoption path (so the new code actually
|
|
107
|
+
# loads — the supervisor would relaunch site-packages, not the env).
|
|
108
|
+
target = None
|
|
100
109
|
try:
|
|
101
110
|
import importlib.metadata as _ilmd
|
|
102
|
-
|
|
111
|
+
target = _ilmd.version("meshcode") # reads dist-info METADATA on disk (pip-updated)
|
|
103
112
|
except Exception:
|
|
113
|
+
target = None
|
|
114
|
+
target_py = sys.executable
|
|
115
|
+
via_env = False
|
|
116
|
+
try:
|
|
117
|
+
from meshcode import self_update as _su
|
|
118
|
+
_env = _su.newest_installed_env() # (version, python_path) or None
|
|
119
|
+
if _env:
|
|
120
|
+
_env_ver, _env_py = _env
|
|
121
|
+
if target is None or _su._is_newer(_env_ver, target):
|
|
122
|
+
target, target_py, via_env = _env_ver, str(_env_py), True
|
|
123
|
+
except Exception:
|
|
124
|
+
pass
|
|
125
|
+
if target is None:
|
|
104
126
|
return
|
|
105
127
|
try:
|
|
106
128
|
from meshcode import self_update as _su
|
|
107
|
-
newer = _su._is_newer(
|
|
129
|
+
newer = _su._is_newer(target, _RUNNING_VERSION)
|
|
108
130
|
except Exception:
|
|
109
131
|
def _vt(v):
|
|
110
132
|
return tuple(int(x) for x in v.split(".") if x.isdigit())
|
|
111
133
|
try:
|
|
112
|
-
newer = _vt(
|
|
134
|
+
newer = _vt(target) > _vt(_RUNNING_VERSION)
|
|
113
135
|
except Exception:
|
|
114
136
|
newer = False
|
|
115
137
|
if not newer:
|
|
116
138
|
return
|
|
117
|
-
# L3 update-guard (task 2e85c6a8): before re-exec'ing onto the
|
|
118
|
-
# smoke-gate it
|
|
119
|
-
#
|
|
120
|
-
#
|
|
139
|
+
# L3 update-guard (task 2e85c6a8): before re-exec'ing onto the candidate,
|
|
140
|
+
# smoke-gate it (the env python when adopting an env, else our interpreter).
|
|
141
|
+
# enforce -> if the candidate fails launch smoke, do NOT restart (stay on
|
|
142
|
+
# known-good), log + alert all commanders. dry_run -> log+alert only, restart
|
|
143
|
+
# proceeds. off (default) -> no-op. Fail-open so the guard can't wedge hostd.
|
|
121
144
|
try:
|
|
122
145
|
from meshcode import _update_guard as _ug
|
|
123
|
-
if _ug.guard_blocks_candidate(
|
|
146
|
+
if _ug.guard_blocks_candidate(target_py, to_version=target,
|
|
124
147
|
from_version=_RUNNING_VERSION,
|
|
125
148
|
context="hostd_version_drift_restart"):
|
|
126
|
-
_log(f"UPDATE-GUARD:
|
|
149
|
+
_log(f"UPDATE-GUARD: candidate {target} failed launch smoke -> NOT restarting; "
|
|
127
150
|
f"staying on known-good {_RUNNING_VERSION} (MESHCODE_UPDATE_GUARD=enforce).")
|
|
128
151
|
return
|
|
129
152
|
except Exception:
|
|
@@ -131,42 +154,46 @@ def _maybe_self_restart_on_version_drift() -> None:
|
|
|
131
154
|
# Loop-guard (backend2 finding): in a source/dev run, importlib.metadata can report a
|
|
132
155
|
# pip-installed wheel NEWER than the __init__.py actually executing, so the drift would
|
|
133
156
|
# PERSIST across restart -> storm. Persist the attempt; if we already tried to reach this
|
|
134
|
-
# exact
|
|
157
|
+
# exact target recently and didn't advance, skip until the guard window passes.
|
|
135
158
|
global _REEXEC_GUARD_LOGGED
|
|
136
159
|
try:
|
|
137
160
|
_st = _load_state()
|
|
138
161
|
except Exception:
|
|
139
162
|
_st = {}
|
|
140
163
|
_att = _st.get("reexec_attempt") or {}
|
|
141
|
-
if _att.get("target") ==
|
|
164
|
+
if _att.get("target") == target and (time.time() - float(_att.get("at", 0) or 0)) < 600:
|
|
142
165
|
if not _REEXEC_GUARD_LOGGED:
|
|
143
166
|
_REEXEC_GUARD_LOGGED = True
|
|
144
|
-
_log(f"version drift {_RUNNING_VERSION}->{
|
|
167
|
+
_log(f"version drift {_RUNNING_VERSION}->{target} but a recent restart didn't advance the "
|
|
145
168
|
f"running version (source/dev run?). Skipping to avoid a restart storm; retry after 600s.")
|
|
146
169
|
return
|
|
147
|
-
_st["reexec_attempt"] = {"target":
|
|
170
|
+
_st["reexec_attempt"] = {"target": target, "at": time.time()}
|
|
148
171
|
try:
|
|
149
172
|
_save_state(_st)
|
|
150
173
|
except Exception:
|
|
151
174
|
pass
|
|
152
|
-
_log(f"VERSION DRIFT: running {_RUNNING_VERSION},
|
|
175
|
+
_log(f"VERSION DRIFT: running {_RUNNING_VERSION}, target {target}"
|
|
176
|
+
f"{' (env adopt)' if via_env else ' (on-disk)'} -> restart to load new code "
|
|
153
177
|
f"(Stop kill sweep + daemon fixes). headless_pids persisted.")
|
|
154
178
|
# Prefer supervisor-restart where one manages hostd: field data (Samuel Mac) showed os.execv
|
|
155
179
|
# is BLOCKED in the sandboxed runtime and the os._exit->KeepAlive fallback is what actually
|
|
156
180
|
# restarts. So when a supervisor exists, exit cleanly and let it relaunch on the new wheel.
|
|
157
|
-
|
|
181
|
+
# EXCEPTION: when ADOPTING a versioned env, the supervisor would relaunch site-packages (NOT
|
|
182
|
+
# the env), so skip the clean-exit shortcut and re-exec the env's python directly below.
|
|
183
|
+
if not via_env and _has_supervisor():
|
|
158
184
|
_log("supervisor present -> clean exit; launchd/systemd/schtasks relaunches on new code")
|
|
159
185
|
os._exit(0)
|
|
160
|
-
# No supervisor (e.g. dev foreground run / sandboxed Mac terminal):
|
|
161
|
-
# try execv first (fastest, in-place
|
|
162
|
-
#
|
|
163
|
-
# from us — clean slate on the
|
|
186
|
+
# No supervisor (e.g. dev foreground run / sandboxed Mac terminal) OR env adoption:
|
|
187
|
+
# try execv first (fastest, in-place; same PID so a supervisor's KeepAlive doesn't
|
|
188
|
+
# double-fire); if blocked (macOS sandbox), spawn a DETACHED hostd on the target and
|
|
189
|
+
# exit. The new process inherits nothing from us — clean slate on the target version;
|
|
190
|
+
# the hostd.lock singleton resolves any race with a supervisor relaunch.
|
|
164
191
|
try:
|
|
165
|
-
os.execv(
|
|
192
|
+
os.execv(target_py, [target_py, "-m", "meshcode"] + sys.argv[1:])
|
|
166
193
|
except Exception as e:
|
|
167
194
|
_log(f"execv blocked ({e}); attempting detached self-relaunch via subprocess")
|
|
168
195
|
try:
|
|
169
|
-
argv = _hostd_run_argv()
|
|
196
|
+
argv = [target_py, "-m", "meshcode", "hostd", "run"] if via_env else _hostd_run_argv()
|
|
170
197
|
if sys.platform == "win32":
|
|
171
198
|
# DETACHED_PROCESS: no console inheritance, survives our exit.
|
|
172
199
|
subprocess.Popen(argv, creationflags=subprocess.DETACHED_PROCESS,
|
|
@@ -2580,13 +2607,44 @@ def _hostd_plist_path():
|
|
|
2580
2607
|
return Path.home() / "Library" / "LaunchAgents" / f"{_HOSTD_PLIST_LABEL}.plist"
|
|
2581
2608
|
|
|
2582
2609
|
|
|
2610
|
+
def _hostd_launch_command_path():
|
|
2611
|
+
return STATE_DIR / "hostd-launch.command"
|
|
2612
|
+
|
|
2613
|
+
|
|
2583
2614
|
def _hostd_plist_xml() -> str:
|
|
2584
2615
|
import shutil
|
|
2585
|
-
mc = shutil.which("meshcode")
|
|
2586
|
-
|
|
2587
|
-
args_xml = "\n".join(f" <string>{a}</string>" for a in args)
|
|
2616
|
+
mc = shutil.which("meshcode")
|
|
2617
|
+
mc_inv = shlex.quote(mc) if mc else f"{shlex.quote(sys.executable)} -m meshcode"
|
|
2588
2618
|
logdir = STATE_DIR / "logs"
|
|
2589
2619
|
logdir.mkdir(parents=True, exist_ok=True)
|
|
2620
|
+
bindir = os.path.dirname(sys.executable)
|
|
2621
|
+
# task d84cbfa1 (version-split self-heal): TODAY ProgramArguments ran `meshcode
|
|
2622
|
+
# hostd run` DIRECTLY. Now generate a /bin/sh wrapper that self-upgrades to the
|
|
2623
|
+
# latest version BEFORE exec'ing hostd, so a machine frozen on an OLD version
|
|
2624
|
+
# autocures on the next launch without depending on the broken in-process upgrade
|
|
2625
|
+
# path (anti chicken-egg). The plist keeps MESHCODE_NO_AUTO_UPDATE=1 in
|
|
2626
|
+
# EnvironmentVariables (in-loop auto-update stays deferral-safe); the wrapper
|
|
2627
|
+
# clears the guards ONLY for the explicit pre-flight upgrade, then restores them.
|
|
2628
|
+
# `self-upgrade` uses the BLOCKING updater with force=False -> it DEFERS when a
|
|
2629
|
+
# live agent serve shares the env, so it never clobbers a running MCP.
|
|
2630
|
+
wrapper = _hostd_launch_command_path()
|
|
2631
|
+
wrapper.write_text(
|
|
2632
|
+
"#!/bin/sh\n"
|
|
2633
|
+
"# MeshCode hostd launcher (task d84cbfa1: version-split self-heal). Generated\n"
|
|
2634
|
+
"# by `meshcode hostd install` — do not edit; it is overwritten on reinstall.\n"
|
|
2635
|
+
f'PATH="{bindir}:$PATH"; export PATH\n'
|
|
2636
|
+
"unset MESHCODE_NO_UPDATE MESHCODE_NO_AUTO_UPDATE\n"
|
|
2637
|
+
f'{mc_inv} self-upgrade >> "{logdir / "hostd-update.log"}" 2>&1 || true\n'
|
|
2638
|
+
"MESHCODE_NO_AUTO_UPDATE=1; export MESHCODE_NO_AUTO_UPDATE\n"
|
|
2639
|
+
f"exec {mc_inv} hostd run\n",
|
|
2640
|
+
encoding="utf-8",
|
|
2641
|
+
)
|
|
2642
|
+
try:
|
|
2643
|
+
os.chmod(wrapper, 0o755)
|
|
2644
|
+
except Exception:
|
|
2645
|
+
pass
|
|
2646
|
+
args = ["/bin/sh", str(wrapper)]
|
|
2647
|
+
args_xml = "\n".join(f" <string>{a}</string>" for a in args)
|
|
2590
2648
|
return f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
2591
2649
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2592
2650
|
<plist version="1.0">
|
|
@@ -2638,6 +2696,17 @@ def _hostd_install_windows() -> int:
|
|
|
2638
2696
|
'set "MESHCODE_NO_UPDATE=1"\r\n' # var honored pre-2.11.74 (belt-and-suspenders)
|
|
2639
2697
|
'set "MESHCODE_NO_AUTO_UPDATE=1"\r\n' # unified var (honored 2.11.74+)
|
|
2640
2698
|
'set "MESHCODE_HOSTD_POLL_SEC=10"\r\n' # faster click->spawn (default 10s, floor 3)
|
|
2699
|
+
# task d84cbfa1 (version-split self-heal): self-upgrade to the latest version
|
|
2700
|
+
# BEFORE starting hostd so a machine frozen on an OLD version autocures on the
|
|
2701
|
+
# next launch (anti chicken-egg). Clear the two NO_*UPDATE guards for THIS one
|
|
2702
|
+
# explicit step (they're re-set right after), then run the deferral-safe
|
|
2703
|
+
# `self-upgrade` (force=False -> skips the overwrite if a live agent serve
|
|
2704
|
+
# shares the env). The in-loop auto-update stays guarded by the re-set vars.
|
|
2705
|
+
'set "MESHCODE_NO_UPDATE="\r\n'
|
|
2706
|
+
'set "MESHCODE_NO_AUTO_UPDATE="\r\n'
|
|
2707
|
+
f'"{mc}" self-upgrade\r\n'
|
|
2708
|
+
'set "MESHCODE_NO_UPDATE=1"\r\n'
|
|
2709
|
+
'set "MESHCODE_NO_AUTO_UPDATE=1"\r\n'
|
|
2641
2710
|
# F1a (task 48c3f294): launch the SUPERVISOR, not `hostd run` directly.
|
|
2642
2711
|
# The supervisor owns + revives hostd on CRASH — the recovery the HKCU\Run
|
|
2643
2712
|
# tier (login-only) lacks (that was the dead launch button on Samuel's box).
|
|
@@ -2720,6 +2789,17 @@ def _hostd_install_linux() -> int:
|
|
|
2720
2789
|
(STATE_DIR / "logs").mkdir(parents=True, exist_ok=True)
|
|
2721
2790
|
argv = _hostd_run_argv()
|
|
2722
2791
|
execstart = " ".join(shlex.quote(a) for a in argv)
|
|
2792
|
+
# task d84cbfa1 (version-split self-heal): self-upgrade BEFORE ExecStart so a host
|
|
2793
|
+
# frozen on an OLD version autocures on the next launch (anti chicken-egg). The unit
|
|
2794
|
+
# sets MESHCODE_NO_*UPDATE=1 globally (keeps the in-loop auto-update deferral-safe),
|
|
2795
|
+
# so the pre-step CLEARS them for this one explicit, deferral-safe `self-upgrade`
|
|
2796
|
+
# (force=False -> skips the overwrite if a live agent serve shares the env). `|| true`
|
|
2797
|
+
# so a failed/offline upgrade never blocks ExecStart.
|
|
2798
|
+
_mc = shutil.which("meshcode")
|
|
2799
|
+
_selfupg = [_mc, "self-upgrade"] if _mc else [sys.executable, "-m", "meshcode", "self-upgrade"]
|
|
2800
|
+
_selfupg_str = " ".join(shlex.quote(a) for a in _selfupg)
|
|
2801
|
+
preexec = "/bin/sh -c " + shlex.quote(
|
|
2802
|
+
f"MESHCODE_NO_UPDATE= MESHCODE_NO_AUTO_UPDATE= {_selfupg_str} || true")
|
|
2723
2803
|
if shutil.which("systemctl"):
|
|
2724
2804
|
unit_dir = Path.home() / ".config" / "systemd" / "user"
|
|
2725
2805
|
unit_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -2728,6 +2808,7 @@ def _hostd_install_linux() -> int:
|
|
|
2728
2808
|
"[Service]\nType=simple\n"
|
|
2729
2809
|
"Environment=MESHCODE_NO_UPDATE=1\nEnvironment=MESHCODE_NO_AUTO_UPDATE=1\n"
|
|
2730
2810
|
"Environment=MESHCODE_HOSTD_POLL_SEC=10\n"
|
|
2811
|
+
f"ExecStartPre={preexec}\n"
|
|
2731
2812
|
f"ExecStart={execstart}\nRestart=on-failure\nRestartSec=10\n\n"
|
|
2732
2813
|
"[Install]\nWantedBy=default.target\n",
|
|
2733
2814
|
encoding="utf-8",
|
|
@@ -3187,6 +3268,17 @@ def cmd_hostd(args: list) -> int:
|
|
|
3187
3268
|
f"(budget {_WEDGE_MAX_RESTARTS}/{_WEDGE_WINDOW_S}s)")
|
|
3188
3269
|
except Exception as e:
|
|
3189
3270
|
_log(f"WARN: wedge watchdog failed to arm ({e}) — daemon runs unprotected")
|
|
3271
|
+
# task d84cbfa1: converge-UP ONCE at run-startup. A hostd launched on STALE
|
|
3272
|
+
# bytecode (launcher self-upgrade absent on an old machine, or a versioned env
|
|
3273
|
+
# newer than this interpreter) re-execs onto the newest installed version HERE,
|
|
3274
|
+
# BEFORE the poll loop spawns any agent — so the live-agent deferral is never at
|
|
3275
|
+
# risk (no serve children exist yet). No-op when already current; smoke-gated +
|
|
3276
|
+
# loop-guarded inside. Previously this fired only mid-sweep, so a daemon started
|
|
3277
|
+
# stale stayed stale until the first drift sweep.
|
|
3278
|
+
try:
|
|
3279
|
+
_maybe_self_restart_on_version_drift()
|
|
3280
|
+
except Exception as e:
|
|
3281
|
+
_log(f"WARN: startup converge-up check failed ({e}); continuing on {_RUNNING_VERSION}")
|
|
3190
3282
|
# uptime-since-spawn (core's suggestion): if the daemon dies <2min, the last alive log +
|
|
3191
3283
|
# the uptime stamped on the FATAL line reveal the <2min pattern for RC.
|
|
3192
3284
|
_spawn_mono = time.monotonic()
|
|
@@ -543,6 +543,41 @@ def _env_python(version: str) -> Path:
|
|
|
543
543
|
return ENVS_DIR / version / sub / exe
|
|
544
544
|
|
|
545
545
|
|
|
546
|
+
def newest_installed_env() -> Optional[tuple]:
|
|
547
|
+
"""Return ``(version, python_path)`` for the newest FINALIZED immutable env
|
|
548
|
+
under ``ENVS_DIR`` — one whose ``.build-ok`` marker exists and whose
|
|
549
|
+
python interpreter is present — or None when no built env exists.
|
|
550
|
+
|
|
551
|
+
Used by hostd startup converge-UP (task d84cbfa1): a daemon launched on
|
|
552
|
+
STALE bytecode can adopt the newest version that boot-always-latest already
|
|
553
|
+
built locally, without depending on the in-process upgrade path or a fresh
|
|
554
|
+
PyPI probe (anti chicken-egg). Skips in-flight ``.tmp-*`` builds. Never
|
|
555
|
+
raises."""
|
|
556
|
+
try:
|
|
557
|
+
if not ENVS_DIR.exists():
|
|
558
|
+
return None
|
|
559
|
+
best_ver = None
|
|
560
|
+
best_py = None
|
|
561
|
+
for d in ENVS_DIR.iterdir():
|
|
562
|
+
try:
|
|
563
|
+
if not d.is_dir() or d.name.startswith(".tmp-"):
|
|
564
|
+
continue
|
|
565
|
+
if not (d / _ENV_OK_MARKER).exists():
|
|
566
|
+
continue
|
|
567
|
+
py = _env_python(d.name)
|
|
568
|
+
if not py.exists():
|
|
569
|
+
continue
|
|
570
|
+
if best_ver is None or _is_newer(d.name, best_ver):
|
|
571
|
+
best_ver, best_py = d.name, py
|
|
572
|
+
except Exception:
|
|
573
|
+
continue
|
|
574
|
+
if best_ver is None:
|
|
575
|
+
return None
|
|
576
|
+
return best_ver, best_py
|
|
577
|
+
except Exception:
|
|
578
|
+
return None
|
|
579
|
+
|
|
580
|
+
|
|
546
581
|
def _prune_stale_tmp_envs(max_age_sec: int = 3600) -> None:
|
|
547
582
|
"""Best-effort removal of leftover .tmp-<ver>-<pid> env dirs left by
|
|
548
583
|
crashed or locked-rename builds — the Windows version-split litter
|
|
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
|
|
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
|