meshcode 2.11.88__tar.gz → 2.11.91__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 (91) hide show
  1. {meshcode-2.11.88 → meshcode-2.11.91}/PKG-INFO +1 -1
  2. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/__init__.py +1 -1
  3. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/hostd.py +101 -10
  4. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode.egg-info/PKG-INFO +1 -1
  5. {meshcode-2.11.88 → meshcode-2.11.91}/pyproject.toml +1 -1
  6. {meshcode-2.11.88 → meshcode-2.11.91}/README.md +0 -0
  7. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/__main__.py +0 -0
  8. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/_session_handoff_template.py +0 -0
  9. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/_stop_hook_template.py +0 -0
  10. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/ascii_art.py +0 -0
  11. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/atomic_push.py +0 -0
  12. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/claude_update.py +0 -0
  13. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/cli.py +0 -0
  14. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/comms_v4.py +0 -0
  15. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/compat.py +0 -0
  16. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/daemon.py +0 -0
  17. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/date_parse.py +0 -0
  18. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/doctor.py +0 -0
  19. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/error_hints.py +0 -0
  20. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/exceptions.py +0 -0
  21. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/invites.py +0 -0
  22. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/launcher.py +0 -0
  23. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/launcher_install.py +0 -0
  24. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/__init__.py +0 -0
  25. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/__main__.py +0 -0
  26. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/backend.py +0 -0
  27. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/realtime.py +0 -0
  28. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/server.py +0 -0
  29. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  30. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/test_backend.py +0 -0
  31. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  32. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  33. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  34. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  35. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  36. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/preferences.py +0 -0
  37. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/protocol_handler.py +0 -0
  38. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/protocol_v2.py +0 -0
  39. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/quickstart.py +0 -0
  40. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/rpc_allowlist.py +0 -0
  41. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/run_agent.py +0 -0
  42. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/scripts/check_secrets.py +0 -0
  43. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/scripts/race_rate_harness.py +0 -0
  44. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/secrets.py +0 -0
  45. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/self_update.py +0 -0
  46. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/setup_clients.py +0 -0
  47. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/supervisor.py +0 -0
  48. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/up.py +0 -0
  49. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode/upload.py +0 -0
  50. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode.egg-info/SOURCES.txt +0 -0
  51. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode.egg-info/dependency_links.txt +0 -0
  52. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode.egg-info/entry_points.txt +0 -0
  53. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode.egg-info/requires.txt +0 -0
  54. {meshcode-2.11.88 → meshcode-2.11.91}/meshcode.egg-info/top_level.txt +0 -0
  55. {meshcode-2.11.88 → meshcode-2.11.91}/setup.cfg +0 -0
  56. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_auto_update_hardening.py +0 -0
  57. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_autonomous_closegap_1.py +0 -0
  58. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_autonomous_closegap_2.py +0 -0
  59. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_autonomous_closegap_3.py +0 -0
  60. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_autonomous_prompt_inject.py +0 -0
  61. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_boot_bug_regression.py +0 -0
  62. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_color_truecolor.py +0 -0
  63. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_core.py +0 -0
  64. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_cross_agent_messaging.py +0 -0
  65. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_date_parse.py +0 -0
  66. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_doctor.py +0 -0
  67. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_epistemic_v1_python_sdk.py +0 -0
  68. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  69. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_esc_deaf_state.py +0 -0
  70. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_exceptions.py +0 -0
  71. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_file_upload.py +0 -0
  72. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_init_device_code.py +0 -0
  73. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_install_guard.py +0 -0
  74. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_lease_sigterm_release.py +0 -0
  75. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_mark_read_batch.py +0 -0
  76. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_marketplace_ratings.py +0 -0
  77. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_migration_integrity.py +0 -0
  78. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_realtime_event_freshness.py +0 -0
  79. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_rls_cross_tenant.py +0 -0
  80. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_rpc_grants.py +0 -0
  81. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_rpc_migrations.py +0 -0
  82. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_run_agent_dry_run.py +0 -0
  83. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_run_agent_no_server_import.py +0 -0
  84. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_security_regressions.py +0 -0
  85. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_self_update_user_site.py +0 -0
  86. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_sentinel.py +0 -0
  87. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_setup_path.py +0 -0
  88. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_sleep_signals.py +0 -0
  89. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_status_enum_coverage.py +0 -0
  90. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_stay_on_loop_hook.py +0 -0
  91. {meshcode-2.11.88 → meshcode-2.11.91}/tests/test_wait_open_tasks_contradiction.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.88
3
+ Version: 2.11.91
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,5 +1,5 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.11.88"
2
+ __version__ = "2.11.91"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -55,6 +55,27 @@ try:
55
55
  except Exception:
56
56
  _RUNNING_VERSION = "0.0.0"
57
57
  _LAST_UPDATE_KICK_MONO = 0.0
58
+ _REEXEC_GUARD_LOGGED = False
59
+
60
+
61
+ def _has_supervisor() -> bool:
62
+ """True if an OS supervisor (launchd/systemd/Task Scheduler) manages this hostd and will
63
+ relaunch it on clean exit. Field data (Samuel Mac): os.execv is BLOCKED in the sandboxed
64
+ runtime, so where a supervisor exists we PREFER exit-and-relaunch over execv."""
65
+ import platform
66
+ try:
67
+ s = platform.system()
68
+ if s == "Darwin":
69
+ return _hostd_plist_path().exists()
70
+ if s == "Windows":
71
+ return subprocess.run(["schtasks", "/Query", "/TN", _HOSTD_TASK_NAME],
72
+ capture_output=True, text=True).returncode == 0
73
+ if shutil.which("systemctl"):
74
+ return subprocess.run(["systemctl", "--user", "is-enabled", _HOSTD_SYSTEMD_UNIT],
75
+ capture_output=True, text=True).returncode == 0
76
+ except Exception:
77
+ pass
78
+ return False
58
79
 
59
80
 
60
81
  def _maybe_self_restart_on_version_drift() -> None:
@@ -96,13 +117,42 @@ def _maybe_self_restart_on_version_drift() -> None:
96
117
  newer = False
97
118
  if not newer:
98
119
  return
99
- _log(f"VERSION DRIFT: running {_RUNNING_VERSION}, on-disk {ondisk} -> self-reexec to load "
100
- f"new code (Stop kill sweep + daemon fixes). headless_pids persisted; KeepAlive re-attaches.")
120
+ # Loop-guard (backend2 finding): in a source/dev run, importlib.metadata can report a
121
+ # pip-installed wheel NEWER than the __init__.py actually executing, so the drift would
122
+ # PERSIST across restart -> storm. Persist the attempt; if we already tried to reach this
123
+ # exact on-disk target recently and didn't advance, skip until the guard window passes.
124
+ global _REEXEC_GUARD_LOGGED
125
+ try:
126
+ _st = _load_state()
127
+ except Exception:
128
+ _st = {}
129
+ _att = _st.get("reexec_attempt") or {}
130
+ if _att.get("target") == ondisk and (time.time() - float(_att.get("at", 0) or 0)) < 600:
131
+ if not _REEXEC_GUARD_LOGGED:
132
+ _REEXEC_GUARD_LOGGED = True
133
+ _log(f"version drift {_RUNNING_VERSION}->{ondisk} but a recent restart didn't advance the "
134
+ f"running version (source/dev run?). Skipping to avoid a restart storm; retry after 600s.")
135
+ return
136
+ _st["reexec_attempt"] = {"target": ondisk, "at": time.time()}
137
+ try:
138
+ _save_state(_st)
139
+ except Exception:
140
+ pass
141
+ _log(f"VERSION DRIFT: running {_RUNNING_VERSION}, on-disk {ondisk} -> restart to load new code "
142
+ f"(Stop kill sweep + daemon fixes). headless_pids persisted.")
143
+ # Prefer supervisor-restart where one manages hostd: field data (Samuel Mac) showed os.execv
144
+ # is BLOCKED in the sandboxed runtime and the os._exit->KeepAlive fallback is what actually
145
+ # restarts. So when a supervisor exists, exit cleanly and let it relaunch on the new wheel.
146
+ if _has_supervisor():
147
+ _log("supervisor present -> clean exit; launchd/systemd/schtasks relaunches on new code")
148
+ os._exit(0)
149
+ # No supervisor (e.g. dev foreground run): execv is the only in-place restart. If it's blocked,
150
+ # do NOT self-destruct (nothing would relaunch us) — stay on old code, retry after the guard window.
101
151
  try:
102
152
  os.execv(sys.executable, [sys.executable, "-m", "meshcode"] + sys.argv[1:])
103
153
  except Exception as e:
104
- _log(f"WARN: self-reexec failed ({e}); exiting so the supervisor restarts us")
105
- os._exit(0)
154
+ _log(f"WARN: no supervisor + execv failed ({e}); staying on {_RUNNING_VERSION}, retry after guard window")
155
+ return
106
156
 
107
157
  STATE_DIR = Path.home() / ".meshcode"
108
158
  HOST_ID_PATH = STATE_DIR / "host_id"
@@ -881,28 +931,58 @@ def _hostd_bootstrap_if_needed(ttl_sec: int = 3600, verbose: bool = False) -> bo
881
931
  return ok
882
932
 
883
933
 
934
+ def _running_daemon_is_stale() -> bool:
935
+ """ca947d7f: True ONLY if hostd_state.json records a running_version that is STRICTLY older
936
+ than the on-disk installed version. Absent / unknown / equal / newer -> False (FAIL-SAFE:
937
+ never bounce a healthy or ambiguous daemon on the login-critical path — commander's safety
938
+ condition). NOTE: a daemon predating the running_version stamp (<2.11.90) wrote no marker, so
939
+ this returns False for it — that one-time transition still needs a manual restart; this guards
940
+ FUTURE transitions as defense-in-depth (the daemon's own self-reexec is the primary path)."""
941
+ try:
942
+ rv = (_load_state() or {}).get("running_version")
943
+ if not rv:
944
+ return False
945
+ import importlib.metadata as _ilmd
946
+ ondisk = _ilmd.version("meshcode")
947
+ from meshcode import self_update as _su
948
+ return bool(_su._is_newer(ondisk, rv))
949
+ except Exception:
950
+ return False
951
+
952
+
884
953
  def _hostd_bootstrap(verbose: bool = False) -> bool:
885
954
  """Idempotent auto-install + start of hostd (task f12b9d36) — called from `meshcode login`
886
955
  so onboarding is just `pip install meshcode` + `meshcode login` (no manual `hostd install`).
887
- If the OS job already exists, just ensure it's running (don't bounce a live daemon); else
888
- install + start. Best-effort: NEVER raises login must not fail on a bootstrap hiccup."""
956
+ If the OS job already exists, just ensure it's running (don't bounce a live daemon) UNLESS the
957
+ running daemon is provably STALE (ca947d7f), in which case force-restart it so Stop's kill
958
+ sweep + launch fixes land. Best-effort: NEVER raises — login must not fail on a hiccup."""
889
959
  import platform
890
960
  try:
961
+ force = _running_daemon_is_stale() # bounce ONLY a provably-stale daemon
962
+ if force:
963
+ _log(f"hostd bootstrap: running daemon is STALE vs on-disk -> force-restart")
891
964
  sysname = platform.system()
892
965
  if sysname == "Darwin":
893
966
  if not _hostd_plist_path().exists():
894
967
  _hostd_install() # writes plist + launchctl load
895
968
  try:
896
969
  uid = str(os.getuid())
897
- subprocess.run(["launchctl", "kickstart", f"gui/{uid}/{_HOSTD_PLIST_LABEL}"],
898
- capture_output=True, text=True) # start if not already running
970
+ # `kickstart -k` restarts a running job; plain `kickstart` only starts if down.
971
+ args = ["launchctl", "kickstart", f"gui/{uid}/{_HOSTD_PLIST_LABEL}"]
972
+ if force:
973
+ args.insert(2, "-k")
974
+ subprocess.run(args, capture_output=True, text=True)
899
975
  except Exception:
900
976
  pass
901
977
  elif sysname == "Windows":
902
978
  q = subprocess.run(["schtasks", "/Query", "/TN", _HOSTD_TASK_NAME],
903
979
  capture_output=True, text=True)
904
980
  if q.returncode == 0:
905
- # task exists -> just ensure running (IgnoreNew => harmless if already up; no double-spawn)
981
+ if force:
982
+ # stale -> stop the running instance, then start fresh on the new wheel.
983
+ subprocess.run(["schtasks", "/End", "/TN", _HOSTD_TASK_NAME],
984
+ capture_output=True, text=True)
985
+ # task exists -> ensure running (IgnoreNew => harmless if already up; no double-spawn)
906
986
  subprocess.run(["schtasks", "/Run", "/TN", _HOSTD_TASK_NAME],
907
987
  capture_output=True, text=True)
908
988
  else:
@@ -910,6 +990,10 @@ def _hostd_bootstrap(verbose: bool = False) -> bool:
910
990
  _hostd_install_windows()
911
991
  else: # Linux + others
912
992
  if shutil.which("systemctl"):
993
+ if force:
994
+ subprocess.run(["systemctl", "--user", "restart", _HOSTD_SYSTEMD_UNIT],
995
+ capture_output=True, text=True)
996
+ return True
913
997
  act = subprocess.run(["systemctl", "--user", "is-active", _HOSTD_SYSTEMD_UNIT],
914
998
  capture_output=True, text=True)
915
999
  if act.stdout.strip() == "active":
@@ -1024,7 +1108,14 @@ def cmd_hostd(args: list) -> int:
1024
1108
  _log(f"registered host {host_id} in mc_host_config")
1025
1109
  else:
1026
1110
  _log(f"WARN: host registration failed (dashboard may not list this host): {_reg}")
1027
- _log(f"hostd starting — host_id={host_id} interval={POLL_INTERVAL_SEC}s stale={STALE_SECONDS}s")
1111
+ _log(f"hostd starting — host_id={host_id} interval={POLL_INTERVAL_SEC}s stale={STALE_SECONDS}s v{_RUNNING_VERSION}")
1112
+ # ca947d7f: stamp the version THIS daemon is running into hostd_state.json so
1113
+ # `meshcode login` (_hostd_bootstrap) can detect a STALE running daemon and force-
1114
+ # restart it (the one-time transition onto a self-reexec-capable build). Best-effort.
1115
+ try:
1116
+ _st = _load_state(); _st["running_version"] = _RUNNING_VERSION; _save_state(_st)
1117
+ except Exception:
1118
+ pass
1028
1119
  # uptime-since-spawn (core's suggestion): if the daemon dies <2min, the last alive log +
1029
1120
  # the uptime stamped on the FATAL line reveal the <2min pattern for RC.
1030
1121
  _spawn_mono = time.monotonic()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.88
3
+ Version: 2.11.91
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.11.88"
7
+ version = "2.11.91"
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
File without changes