meshcode 2.11.116__tar.gz → 2.11.118__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.116 → meshcode-2.11.118}/PKG-INFO +1 -1
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/__init__.py +1 -1
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/comms_v4.py +1 -1
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/server.py +18 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/protocol_handler.py +7 -1
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/run_agent.py +15 -5
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/self_update.py +148 -2
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.116 → meshcode-2.11.118}/pyproject.toml +1 -1
- {meshcode-2.11.116 → meshcode-2.11.118}/README.md +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/__main__.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/cli.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/compat.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/daemon.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/doctor.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/hooks/__init__.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/hooks/repo_path_lock.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/hostd.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/invites.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/launcher.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/preferences.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/secrets.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/up.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode/upload.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/setup.cfg +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_core.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_doctor.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.116 → meshcode-2.11.118}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -1886,7 +1886,7 @@ def _start_heartbeat_daemon(project, name, agent_pid=None):
|
|
|
1886
1886
|
" if not check_still_leased(pid):\n"
|
|
1887
1887
|
" sys.exit(0)\n"
|
|
1888
1888
|
" post('/rest/v1/rpc/mc_heartbeat', {'p_project_id':pid,'p_agent_name':name})\n"
|
|
1889
|
-
" time.sleep(
|
|
1889
|
+
" time.sleep(30)\n" # .117: back to 30s (commander 2026-06-09 — 10s tripled hb RPC volume; Fix A already bounds the stale window)
|
|
1890
1890
|
)
|
|
1891
1891
|
# Windows: start_new_session kwarg doesn't exist. Use creationflags.
|
|
1892
1892
|
_popen_kwargs = {
|
|
@@ -7612,6 +7612,24 @@ def run_server():
|
|
|
7612
7612
|
)
|
|
7613
7613
|
# Stash for the heartbeat thread to relay to commander on first beat.
|
|
7614
7614
|
os.environ["_MESHCODE_BOOT_DRIFT"] = f"{_installed}->{_loaded}"
|
|
7615
|
+
# R2-5 (2.11.117) boot drift self-check: the launcher stamps the version it
|
|
7616
|
+
# RESOLVED for this agent (pin else latest PyPI) into the .mcp.json env as
|
|
7617
|
+
# MESHCODE_EXPECTED_VERSION. If what actually loaded differs, the spawn env
|
|
7618
|
+
# is stale/mismatched — report it to the mesh (first-heartbeat relay picks
|
|
7619
|
+
# up the sentinel) instead of degrading into "weird behavior".
|
|
7620
|
+
_expected = os.environ.get("MESHCODE_EXPECTED_VERSION", "").strip()
|
|
7621
|
+
if _expected and _expected != _SDK_VERSION:
|
|
7622
|
+
print(
|
|
7623
|
+
f"[meshcode] WARN boot_version_drift: expected={_expected} "
|
|
7624
|
+
f"loaded={_SDK_VERSION} — this agent's spawn env does not serve the "
|
|
7625
|
+
f"version the launcher resolved. Check ~/.meshcode/envs/{_expected}.",
|
|
7626
|
+
file=sys.stderr,
|
|
7627
|
+
)
|
|
7628
|
+
_prev = os.environ.get("_MESHCODE_BOOT_DRIFT", "")
|
|
7629
|
+
os.environ["_MESHCODE_BOOT_DRIFT"] = (
|
|
7630
|
+
(_prev + " " if _prev else "")
|
|
7631
|
+
+ f"expected:{_expected}->loaded:{_SDK_VERSION}"
|
|
7632
|
+
)
|
|
7615
7633
|
if sys.platform == "win32":
|
|
7616
7634
|
# Windows can't execv-preserve stdio; auto-update defers to next boot.
|
|
7617
7635
|
# Always announce so Windows users know why their first launch may
|
|
@@ -298,7 +298,13 @@ def _spawn_terminal_windows(cmd: str) -> tuple[bool, str]:
|
|
|
298
298
|
# is what Samuel wants ("ventana nueva ENFOCADA"). The old `-w 0 nt` opened a tab in
|
|
299
299
|
# the most-recently-used window — frequently minimized/background, so the launch
|
|
300
300
|
# looked like "nada pasó".
|
|
301
|
-
|
|
301
|
+
# mesh-core patch-wt-semicolon (e9786e2d, verified on Samuel's box
|
|
302
|
+
# 2026-06-07): wt treats an unescaped ';' as a pane/tab delimiter,
|
|
303
|
+
# and the hostd PATH-inject (`set "PATH={bindir};%PATH%"`) always
|
|
304
|
+
# carries one → the cmdline splits and wt fails with 0x80070002.
|
|
305
|
+
# Escape ONLY here; the cmd.exe fallback below stays unescaped
|
|
306
|
+
# (cmd.exe does not split on ';').
|
|
307
|
+
subprocess.Popen([wt, "-w", "new", "nt", "cmd", "/k", cmd.replace(";", "\\;")])
|
|
302
308
|
return True, "wt"
|
|
303
309
|
except Exception as e:
|
|
304
310
|
return False, f"wt.exe: {e}"
|
|
@@ -886,12 +886,22 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
886
886
|
print(f"[meshcode] Run `meshcode setup {resolved_project} {agent}` to fix.", file=sys.stderr)
|
|
887
887
|
return 2
|
|
888
888
|
|
|
889
|
-
#
|
|
890
|
-
#
|
|
891
|
-
#
|
|
892
|
-
#
|
|
893
|
-
#
|
|
889
|
+
# R2-5 (2.11.117) boot-always-latest: resolve target version (explicit pin
|
|
890
|
+
# else latest PyPI — owner directive sammybenu 2026-06-09), guarantee an
|
|
891
|
+
# immutable ~/.meshcode/envs/<target>/ env serving exactly it, and point
|
|
892
|
+
# this workspace's .mcp.json at it. Live agents keep their old env until
|
|
893
|
+
# recycle (no in-place overwrite → no class#2 clobber). Soft-fail → legacy
|
|
894
|
+
# sync_agent_env path (2.11.109 env-mismatch storm fix) below.
|
|
895
|
+
_boot_env_ver = None
|
|
894
896
|
if not dry_run:
|
|
897
|
+
try:
|
|
898
|
+
_boot_env_ver = self_update.ensure_boot_env(mcp_json_path)
|
|
899
|
+
if _boot_env_ver:
|
|
900
|
+
print(f"[meshcode] agent env: meshcode {_boot_env_ver} "
|
|
901
|
+
f"(boot-always-latest)", file=sys.stderr)
|
|
902
|
+
except Exception:
|
|
903
|
+
_boot_env_ver = None
|
|
904
|
+
if not dry_run and not _boot_env_ver:
|
|
895
905
|
try:
|
|
896
906
|
_doc = json.loads(mcp_json_path.read_text(encoding="utf-8"))
|
|
897
907
|
for _srv in (_doc.get("mcpServers") or {}).values():
|
|
@@ -456,10 +456,18 @@ def check_and_maybe_update(verbose: bool = False) -> None:
|
|
|
456
456
|
|
|
457
457
|
|
|
458
458
|
def _env_version(python_exe: str) -> Optional[str]:
|
|
459
|
-
"""meshcode.__version__ as seen by ANOTHER python env (the agent's MCP server).
|
|
459
|
+
"""meshcode.__version__ as seen by ANOTHER python env (the agent's MCP server).
|
|
460
|
+
|
|
461
|
+
-I (isolated mode): `python -c` prepends CWD to sys.path, so probing from a
|
|
462
|
+
dir that contains a meshcode/ package (the SDK repo, any vendored copy)
|
|
463
|
+
reports THAT tree's version instead of the env's installed one (caught live
|
|
464
|
+
2026-06-09: env build verify refused a correct env because the probe ran
|
|
465
|
+
from the .117 worktree). Isolated mode answers the only question this
|
|
466
|
+
function asks: what does THIS env serve.
|
|
467
|
+
"""
|
|
460
468
|
try:
|
|
461
469
|
out = subprocess.run(
|
|
462
|
-
[python_exe, "-c", "import meshcode,sys; sys.stdout.write(meshcode.__version__)"],
|
|
470
|
+
[python_exe, "-I", "-c", "import meshcode,sys; sys.stdout.write(meshcode.__version__)"],
|
|
463
471
|
capture_output=True, text=True, timeout=10).stdout.strip()
|
|
464
472
|
return out or None
|
|
465
473
|
except Exception:
|
|
@@ -495,6 +503,144 @@ def sync_agent_env(mcp_python: str, verbose: bool = False) -> None:
|
|
|
495
503
|
pass
|
|
496
504
|
|
|
497
505
|
|
|
506
|
+
# ============================================================
|
|
507
|
+
# R2-5 (2.11.117): versioned immutable envs + boot-always-latest
|
|
508
|
+
#
|
|
509
|
+
# Owner directive (sammybenu 2026-06-09T22:54Z): "al boot SIEMPRE
|
|
510
|
+
# auto-update meshcode a la versión más nueva de PyPI" — per-agent pin
|
|
511
|
+
# only when explicitly set.
|
|
512
|
+
#
|
|
513
|
+
# Instead of upgrading a SHARED live env in place (class#2 clobber —
|
|
514
|
+
# the reason the 8e4f93f9 guard defers forever on a 24/7 fleet), each
|
|
515
|
+
# version gets its own immutable venv under ~/.meshcode/envs/<version>/
|
|
516
|
+
# built exactly once and never touched again. Running agents keep
|
|
517
|
+
# importing their old env until recycle; new spawns point at the
|
|
518
|
+
# resolved target. No overwrite → no clobber → no deferral.
|
|
519
|
+
# ============================================================
|
|
520
|
+
|
|
521
|
+
ENVS_DIR = STATE_DIR / "envs"
|
|
522
|
+
ENV_BUILD_TIMEOUT_SEC = 240
|
|
523
|
+
_ENV_OK_MARKER = ".build-ok"
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def resolve_target_version() -> Optional[str]:
|
|
527
|
+
"""Version an agent spawn should run. Resolution order:
|
|
528
|
+
|
|
529
|
+
1. MESHCODE_VERSION_PIN env var (explicit pin wins)
|
|
530
|
+
2. prefs key "version_pin" (explicit pin wins)
|
|
531
|
+
3. latest PyPI (1.5s probe) (the owner-directive default)
|
|
532
|
+
4. None → caller keeps current behavior (offline-safe)
|
|
533
|
+
"""
|
|
534
|
+
pin = os.environ.get("MESHCODE_VERSION_PIN") or _read_prefs().get("version_pin")
|
|
535
|
+
if pin:
|
|
536
|
+
return str(pin).strip()
|
|
537
|
+
return fetch_latest_version()
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def _env_python(version: str) -> Path:
|
|
541
|
+
sub = "Scripts" if sys.platform == "win32" else "bin"
|
|
542
|
+
exe = "python.exe" if sys.platform == "win32" else "python3"
|
|
543
|
+
return ENVS_DIR / version / sub / exe
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def ensure_versioned_env(version: str, verbose: bool = True) -> Optional[Path]:
|
|
547
|
+
"""Create-once immutable venv for `version`; return its python, or None.
|
|
548
|
+
|
|
549
|
+
Build is atomic: venv + pip install + import-verify happen in a
|
|
550
|
+
.tmp-<version>-<pid> dir which is renamed into place only after the
|
|
551
|
+
installed version is VERIFIED == target (refuse-stale-spawn at the
|
|
552
|
+
source). Concurrent builders race benignly — first rename wins, the
|
|
553
|
+
loser falls back to the winner's env. Never raises.
|
|
554
|
+
"""
|
|
555
|
+
try:
|
|
556
|
+
final = ENVS_DIR / version
|
|
557
|
+
py = _env_python(version)
|
|
558
|
+
if py.exists() and (final / _ENV_OK_MARKER).exists():
|
|
559
|
+
return py
|
|
560
|
+
|
|
561
|
+
tmp = ENVS_DIR / f".tmp-{version}-{os.getpid()}"
|
|
562
|
+
tmp_py = Path(str(py).replace(str(final), str(tmp), 1))
|
|
563
|
+
ENVS_DIR.mkdir(parents=True, exist_ok=True)
|
|
564
|
+
if verbose:
|
|
565
|
+
print(f"[meshcode] building env for meshcode {version} (one-time, ~30s)...",
|
|
566
|
+
file=sys.stderr)
|
|
567
|
+
import shutil
|
|
568
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
569
|
+
if subprocess.run([sys.executable, "-m", "venv", str(tmp)],
|
|
570
|
+
capture_output=True, timeout=ENV_BUILD_TIMEOUT_SEC).returncode != 0:
|
|
571
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
572
|
+
return None
|
|
573
|
+
proc = subprocess.run(
|
|
574
|
+
[str(tmp_py), "-m", "pip", "install", "--no-cache-dir", "--quiet",
|
|
575
|
+
"--disable-pip-version-check", f"{PKG_NAME}=={version}"],
|
|
576
|
+
capture_output=True, timeout=ENV_BUILD_TIMEOUT_SEC)
|
|
577
|
+
if proc.returncode != 0:
|
|
578
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
579
|
+
return None
|
|
580
|
+
got = _env_version(str(tmp_py))
|
|
581
|
+
if got != version: # refuse-stale-spawn: never bless a wrong env
|
|
582
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
583
|
+
return None
|
|
584
|
+
(tmp / _ENV_OK_MARKER).write_text(version, encoding="utf-8")
|
|
585
|
+
try:
|
|
586
|
+
os.rename(tmp, final)
|
|
587
|
+
except OSError:
|
|
588
|
+
shutil.rmtree(tmp, ignore_errors=True) # concurrent builder won
|
|
589
|
+
if py.exists() and (final / _ENV_OK_MARKER).exists():
|
|
590
|
+
return py
|
|
591
|
+
return None
|
|
592
|
+
except Exception:
|
|
593
|
+
return None
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def ensure_boot_env(mcp_json_path, verbose: bool = True) -> Optional[str]:
|
|
597
|
+
"""Boot-always-latest engine, called by `meshcode run` pre-editor-exec.
|
|
598
|
+
|
|
599
|
+
Resolves the target version (pin else latest PyPI), guarantees an
|
|
600
|
+
immutable env serving EXACTLY it, points the workspace .mcp.json
|
|
601
|
+
command there, and stamps MESHCODE_EXPECTED_VERSION into the server
|
|
602
|
+
env block (the serve compares it against its loaded __version__ at
|
|
603
|
+
boot and reports boot_version_drift to the mesh on mismatch).
|
|
604
|
+
|
|
605
|
+
Returns the target version on success, None on ANY failure — the
|
|
606
|
+
caller then keeps the legacy sync_agent_env path. Never raises.
|
|
607
|
+
"""
|
|
608
|
+
try:
|
|
609
|
+
if update_disabled() or is_editable_install():
|
|
610
|
+
return None
|
|
611
|
+
mcp_json_path = Path(mcp_json_path)
|
|
612
|
+
target = resolve_target_version()
|
|
613
|
+
if not target:
|
|
614
|
+
return None
|
|
615
|
+
doc = json.loads(mcp_json_path.read_text(encoding="utf-8"))
|
|
616
|
+
servers = doc.get("mcpServers") or {}
|
|
617
|
+
if not servers:
|
|
618
|
+
return None
|
|
619
|
+
srv = next(iter(servers.values()))
|
|
620
|
+
cur_cmd = srv.get("command") or ""
|
|
621
|
+
cur_ver = _env_version(cur_cmd) if cur_cmd else None
|
|
622
|
+
|
|
623
|
+
changed = False
|
|
624
|
+
if cur_ver != target:
|
|
625
|
+
py = ensure_versioned_env(target, verbose=verbose)
|
|
626
|
+
if py is None:
|
|
627
|
+
return None
|
|
628
|
+
if cur_cmd != str(py):
|
|
629
|
+
srv["command"] = str(py)
|
|
630
|
+
changed = True
|
|
631
|
+
env_block = srv.setdefault("env", {})
|
|
632
|
+
if env_block.get("MESHCODE_EXPECTED_VERSION") != target:
|
|
633
|
+
env_block["MESHCODE_EXPECTED_VERSION"] = target
|
|
634
|
+
changed = True
|
|
635
|
+
if changed:
|
|
636
|
+
tmp = mcp_json_path.with_name(mcp_json_path.name + ".tmp")
|
|
637
|
+
tmp.write_text(json.dumps(doc, indent=2), encoding="utf-8")
|
|
638
|
+
tmp.replace(mcp_json_path)
|
|
639
|
+
return target
|
|
640
|
+
except Exception:
|
|
641
|
+
return None
|
|
642
|
+
|
|
643
|
+
|
|
498
644
|
# ============================================================
|
|
499
645
|
# Blocking variant — used by `meshcode run` to guarantee the editor
|
|
500
646
|
# subprocess inherits the latest meshcode_mcp/server.py on disk.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|