meshcode 2.11.136__tar.gz → 2.11.137__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 (105) hide show
  1. {meshcode-2.11.136 → meshcode-2.11.137}/PKG-INFO +2 -11
  2. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/__init__.py +1 -1
  3. meshcode-2.11.137/meshcode/helper_visuals.py +142 -0
  4. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/hostd.py +201 -35
  5. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/server.py +41 -0
  6. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/swarm.py +60 -0
  7. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/test_swarm.py +43 -0
  8. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/protocol_handler.py +36 -1
  9. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/run_agent.py +72 -0
  10. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode.egg-info/PKG-INFO +2 -11
  11. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode.egg-info/SOURCES.txt +3 -0
  12. {meshcode-2.11.136 → meshcode-2.11.137}/pyproject.toml +1 -1
  13. meshcode-2.11.137/tests/test_helper_visuals.py +199 -0
  14. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_hostd_zombie_sessions.py +203 -0
  15. meshcode-2.11.137/tests/test_pretrust_claude.py +86 -0
  16. {meshcode-2.11.136 → meshcode-2.11.137}/README.md +0 -0
  17. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/__main__.py +0 -0
  18. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/_session_handoff_template.py +0 -0
  19. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/_stop_hook_template.py +0 -0
  20. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/ascii_art.py +0 -0
  21. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/atomic_push.py +0 -0
  22. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/claude_update.py +0 -0
  23. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/cli.py +0 -0
  24. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/comms_v4.py +0 -0
  25. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/compat.py +0 -0
  26. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/daemon.py +0 -0
  27. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/date_parse.py +0 -0
  28. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/doctor.py +0 -0
  29. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/error_hints.py +0 -0
  30. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/exceptions.py +0 -0
  31. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/hooks/__init__.py +0 -0
  32. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/hooks/repo_path_lock.py +0 -0
  33. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/invites.py +0 -0
  34. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/launcher.py +0 -0
  35. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/launcher_install.py +0 -0
  36. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/__init__.py +0 -0
  37. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/__main__.py +0 -0
  38. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/backend.py +0 -0
  39. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/realtime.py +0 -0
  40. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  41. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/test_backend.py +0 -0
  42. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  43. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  44. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  45. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  46. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  47. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/preferences.py +0 -0
  48. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/protocol_v2.py +0 -0
  49. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/quickstart.py +0 -0
  50. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/rpc_allowlist.py +0 -0
  51. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/scripts/check_secrets.py +0 -0
  52. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/scripts/race_rate_harness.py +0 -0
  53. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/secrets.py +0 -0
  54. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/self_update.py +0 -0
  55. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/setup_clients.py +0 -0
  56. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/supervisor.py +0 -0
  57. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/up.py +0 -0
  58. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode/upload.py +0 -0
  59. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode.egg-info/dependency_links.txt +0 -0
  60. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode.egg-info/entry_points.txt +0 -0
  61. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode.egg-info/requires.txt +0 -0
  62. {meshcode-2.11.136 → meshcode-2.11.137}/meshcode.egg-info/top_level.txt +0 -0
  63. {meshcode-2.11.136 → meshcode-2.11.137}/setup.cfg +0 -0
  64. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_auto_update_hardening.py +0 -0
  65. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_autonomous_closegap_1.py +0 -0
  66. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_autonomous_closegap_2.py +0 -0
  67. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_autonomous_closegap_3.py +0 -0
  68. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_autonomous_prompt_inject.py +0 -0
  69. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_boot_bug_regression.py +0 -0
  70. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_color_truecolor.py +0 -0
  71. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_core.py +0 -0
  72. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_cross_agent_messaging.py +0 -0
  73. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_date_parse.py +0 -0
  74. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_doctor.py +0 -0
  75. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_epistemic_v1_python_sdk.py +0 -0
  76. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  77. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_esc_deaf_state.py +0 -0
  78. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_exceptions.py +0 -0
  79. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_file_upload.py +0 -0
  80. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_init_device_code.py +0 -0
  81. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_install_guard.py +0 -0
  82. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_lease_sigterm_release.py +0 -0
  83. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_live_mesh_guard.py +0 -0
  84. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_mark_read_batch.py +0 -0
  85. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_marketplace_ratings.py +0 -0
  86. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_migration_integrity.py +0 -0
  87. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_realtime_event_freshness.py +0 -0
  88. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_rls_cross_tenant.py +0 -0
  89. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_rpc_grants.py +0 -0
  90. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_rpc_migrations.py +0 -0
  91. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_run_agent_dry_run.py +0 -0
  92. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_run_agent_no_server_import.py +0 -0
  93. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_security_regressions.py +0 -0
  94. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_self_update_user_site.py +0 -0
  95. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_sentinel.py +0 -0
  96. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_session_replay_gate.py +0 -0
  97. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_setup_path.py +0 -0
  98. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_sleep_signals.py +0 -0
  99. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_status_enum_coverage.py +0 -0
  100. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_stay_on_loop_hook.py +0 -0
  101. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_stop_ghost_terminal.py +0 -0
  102. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_swarm_events.py +0 -0
  103. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_task_progress.py +0 -0
  104. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_terminal_lifecycle.py +0 -0
  105. {meshcode-2.11.136 → meshcode-2.11.137}/tests/test_wait_open_tasks_contradiction.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: meshcode
3
- Version: 2.11.136
3
+ Version: 2.11.137
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -18,17 +18,8 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Operating System :: OS Independent
19
19
  Requires-Python: >=3.9
20
20
  Description-Content-Type: text/markdown
21
- Requires-Dist: mcp[cli]>=1.0.0
22
- Requires-Dist: websockets>=12.0
23
- Requires-Dist: realtime>=2.0.0
24
- Requires-Dist: keyring>=24.0
25
- Requires-Dist: cryptography>=41.0
26
21
  Provides-Extra: test
27
- Requires-Dist: pytest>=8; extra == "test"
28
22
  Provides-Extra: dev
29
- Requires-Dist: build>=1.0; extra == "dev"
30
- Requires-Dist: twine>=4; extra == "dev"
31
- Requires-Dist: pytest>=8; extra == "dev"
32
23
 
33
24
  # MeshCode
34
25
 
@@ -1,5 +1,5 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.11.136"
2
+ __version__ = "2.11.137"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -0,0 +1,142 @@
1
+ """Helper terminal visuals (task d8f8e325 — Samuel msgs edf0ed8d + 68c7fee0).
2
+
3
+ ENJAMBRE helpers must open VISUALLY DISTINCT from top-level agents on every
4
+ platform, so a wall of fleet tabs reads instantly: amber = ephemeral helper,
5
+ normal = real agent. Two layers (both silent-degrade — a visuals failure must
6
+ NEVER break a spawn):
7
+
8
+ [1] UNIVERSAL — `meshcode run` emits OSC 0/2 (title "helper:<name>") +
9
+ OSC 11 (dark-amber background) at session start. Works in Windows
10
+ Terminal, iTerm2 and most xterm-compatible Linux terminals regardless
11
+ of which launcher opened the window; terminals that ignore OSC 11
12
+ (e.g. macOS Terminal.app) still get the title.
13
+ [2] PER-PLATFORM at spawn — Windows Terminal gets `--tabColor <amber>` +
14
+ `--title helper:<name>` on the fleet tab; the Linux/macOS launcher
15
+ scripts prepend the same OSC prelude so the tint applies even before
16
+ `meshcode run` boots.
17
+
18
+ HOW A SPAWNER KNOWS IT'S A HELPER (no agent_kind in the spawn payloads —
19
+ probed live 2026-06-12: mc_agents_needing_respawn candidates and
20
+ mc_host_config_get agents carry no kind field):
21
+
22
+ - MARKER FILE: swarm.spawn_helper() runs on the PARENT's host, which is the
23
+ same host the helper spawns on (W5: helper row inherits the parent's
24
+ host/repo). It stamps ~/.meshcode/helpers/<name>.json with the helper's
25
+ TTL; spawners treat a FRESH marker as "helper". TTL expiry is the
26
+ cleanup (helpers are ephemeral by contract, TTL <= 86400).
27
+ - NAME PREFIX fallback: a name starting with 'helper' counts even without
28
+ a marker (covers cross-host edge cases + the observed naming convention).
29
+
30
+ Tradeoff (documented): a top-level agent that shares a helper's bare name on
31
+ the same host within the marker TTL would render amber — cosmetic only, and
32
+ name collisions are already rejected server-side within a meshwork.
33
+ """
34
+ from __future__ import annotations
35
+
36
+ import json
37
+ import re
38
+ import sys
39
+ import time
40
+ from pathlib import Path
41
+
42
+ # Spec colors (task d8f8e325): amber tab; dark-amber background tint that
43
+ # keeps default light-on-dark text readable.
44
+ HELPER_TAB_COLOR = "#E8A33D"
45
+ HELPER_BG_COLOR = "#3A2310"
46
+ HELPER_TITLE_PREFIX = "helper:"
47
+
48
+ _MARKER_TTL_CAP_S = 86400 # mirror of the server-side helper TTL ceiling
49
+
50
+
51
+ def _helpers_dir() -> Path:
52
+ """Marker directory, behind a function so tests can patch it."""
53
+ return Path.home() / ".meshcode" / "helpers"
54
+
55
+
56
+ def _safe_name(name: str) -> str:
57
+ return re.sub(r"[^A-Za-z0-9_.-]", "_", (name or "").strip()).strip("_")[:80]
58
+
59
+
60
+ def mark_helper(name: str, ttl_seconds: int = 3600) -> None:
61
+ """Stamp <name> as a helper on THIS host (called by swarm.spawn_helper).
62
+ Best-effort: never raises. Opportunistically prunes expired markers."""
63
+ try:
64
+ safe = _safe_name(name)
65
+ if not safe:
66
+ return
67
+ d = _helpers_dir()
68
+ d.mkdir(parents=True, exist_ok=True)
69
+ ttl = max(60, min(int(ttl_seconds or 3600), _MARKER_TTL_CAP_S))
70
+ (d / f"{safe}.json").write_text(
71
+ json.dumps({"name": name, "expires_at": time.time() + ttl}),
72
+ encoding="utf-8")
73
+ for p in d.glob("*.json"):
74
+ try:
75
+ if float(json.loads(p.read_text(encoding="utf-8"))
76
+ .get("expires_at", 0)) < time.time():
77
+ p.unlink()
78
+ except Exception:
79
+ continue
80
+ except Exception:
81
+ pass
82
+
83
+
84
+ def unmark_helper(name: str) -> None:
85
+ """Drop the marker (helper retired). Best-effort."""
86
+ try:
87
+ safe = _safe_name(name)
88
+ if safe:
89
+ (_helpers_dir() / f"{safe}.json").unlink(missing_ok=True)
90
+ except Exception:
91
+ pass
92
+
93
+
94
+ def is_helper(target: str) -> bool:
95
+ """True when `target` (bare 'name' or 'project/name') is a swarm helper:
96
+ fresh marker on this host OR the 'helper' name prefix. Never raises."""
97
+ try:
98
+ agent = (target or "").rsplit("/", 1)[-1].strip()
99
+ if not agent:
100
+ return False
101
+ if agent.lower().startswith("helper"):
102
+ return True
103
+ p = _helpers_dir() / f"{_safe_name(agent)}.json"
104
+ if not p.exists():
105
+ return False
106
+ data = json.loads(p.read_text(encoding="utf-8"))
107
+ return float(data.get("expires_at", 0)) >= time.time()
108
+ except Exception:
109
+ return False
110
+
111
+
112
+ def helper_title(target: str) -> str:
113
+ """Tab/window title for a helper: 'helper:<bare name>' (sanitized)."""
114
+ agent = _safe_name((target or "").rsplit("/", 1)[-1])
115
+ return f"{HELPER_TITLE_PREFIX}{agent or 'agent'}"[:48]
116
+
117
+
118
+ def osc_prelude(target: str) -> str:
119
+ """Escape string: OSC 0 title + OSC 11 background. Terminals that don't
120
+ support either sequence ignore it (that IS the degradation path)."""
121
+ return (f"\x1b]0;{helper_title(target)}\x07"
122
+ f"\x1b]11;{HELPER_BG_COLOR}\x07")
123
+
124
+
125
+ def osc_prelude_shell(target: str) -> str:
126
+ """The same prelude as a POSIX-shell `printf` command (for launcher
127
+ scripts / bash -lc command strings). Title is sanitized [A-Za-z0-9_.-:]
128
+ so it can never break out of the single quotes."""
129
+ return ("printf '\\033]0;%s\\007\\033]11;%s\\007' 2>/dev/null || true"
130
+ % (helper_title(target), HELPER_BG_COLOR))
131
+
132
+
133
+ def emit_osc(target: str) -> None:
134
+ """Universal layer: write the prelude to the live terminal from
135
+ `meshcode run`. Best-effort, TTY-gated (a piped/CI stdout never sees
136
+ escape garbage)."""
137
+ try:
138
+ if sys.stdout.isatty():
139
+ sys.stdout.write(osc_prelude(target))
140
+ sys.stdout.flush()
141
+ except Exception:
142
+ pass
@@ -41,6 +41,7 @@ import signal as _signal
41
41
  import subprocess
42
42
  import sys
43
43
  import time
44
+ import urllib.error
44
45
  import urllib.request
45
46
  import uuid
46
47
  from pathlib import Path
@@ -170,6 +171,14 @@ LOG_PATH = STATE_DIR / "hostd.log"
170
171
  _HOSTD_STARTED_AT = time.time()
171
172
  _BOOT_AUTOSTART = os.environ.get("MESHCODE_BOOT_AUTOSTART", "").strip().lower() in ("1", "true", "yes", "on")
172
173
 
174
+ # task 89d50a14 [B] FAIL-CLOSED anti-self-spawn: when PID discovery ERRORS (psutil
175
+ # broken AND the native fallback raised — e.g. wmic removed on Win11 24H2 before
176
+ # the Get-CimInstance fallback existed), an empty result does NOT mean "no live
177
+ # session"; spawning on it is exactly the double-session zombie. Default: skip
178
+ # the spawn that sweep. This env opts back into the old spawn-blind behavior —
179
+ # escape hatch for a host where discovery can never work but respawn must.
180
+ _DISCOVERY_FAIL_OPEN = os.environ.get("MESHCODE_DISCOVERY_FAIL_OPEN", "").strip().lower() in ("1", "true", "yes", "on")
181
+
173
182
  def _env_poll_interval() -> int:
174
183
  # task 399d7b51 SPEED: poll faster so click->desired_state->spawn latency drops (was 45s).
175
184
  # Env-configurable (survives upgrade); floor 3s so we never hammer the DB.
@@ -468,6 +477,16 @@ def _rpc(fn: str, payload: dict) -> Optional[dict]:
468
477
  )
469
478
  with urllib.request.urlopen(req, timeout=15) as resp:
470
479
  return json.loads(resp.read().decode("utf-8"))
480
+ except urllib.error.HTTPError as e:
481
+ # task 89d50a14 [E]: log the PostgREST error BODY, not just the status line.
482
+ # mc_log_respawn_event failed 400 for DAYS and the cause (42804 host_id
483
+ # uuid-vs-text) was undiagnosable from "HTTP Error 400: Bad Request" alone.
484
+ try:
485
+ body = e.read().decode("utf-8", "replace").strip()[:300]
486
+ except Exception:
487
+ body = ""
488
+ _log(f"WARN: rpc {fn} failed: {e}" + (f" — {body}" if body else ""))
489
+ return None
471
490
  except Exception as e:
472
491
  _log(f"WARN: rpc {fn} failed: {e}")
473
492
  return None
@@ -628,6 +647,18 @@ _MAX_SPAWNS_PER_SWEEP = 3
628
647
  # session, so the SKIP line prints once per agent instead of every ~10s sweep.
629
648
  _TOMBSTONE_LOGGED: set = set()
630
649
 
650
+ # task 89d50a14 [E]: boot-stale leftovers (desired_state='running' from a PRIOR
651
+ # session, e.g. concreta-app/studio fleets never explicitly stopped) re-qualify
652
+ # as candidates EVERY ~10s sweep forever — the skip line printed ~15-19 times
653
+ # per sweep and grew hostd.log to ~40MB. Same once-per-target-per-session
654
+ # discipline as _TOMBSTONE_LOGGED. The skip itself still happens every sweep.
655
+ _BOOTSTALE_LOGGED: set = set()
656
+
657
+ # task 89d50a14 [B]: discovery fail-closed already logged this session (per
658
+ # target) — the FAIL-CLOSED skip repeats every sweep while the candidate
659
+ # persists; log + telemetry only on the first one, clear on recovery.
660
+ _DISCOVERY_ERR_LOGGED: set = set()
661
+
631
662
 
632
663
  def _do_respawns(api_key: str, host_id: str) -> int:
633
664
  """One respawn sweep. Returns number relaunched."""
@@ -695,10 +726,20 @@ def _do_respawns(api_key: str, host_id: str) -> int:
695
726
  _hb_age = c.get("heartbeat_age_s")
696
727
  _alive_on_our_watch = _hb_age is not None and _hb_age < _hostd_uptime
697
728
  if (_spawn_age is None or _spawn_age >= _hostd_uptime) and not _alive_on_our_watch:
698
- _log(f"BOOT-AUTOSTART OFF: skip auto-respawn {proj}/{agent} (desired_state=running set "
699
- f"before this hostd start boot-stale leftover; explicit launch required). "
700
- f"Set MESHCODE_BOOT_AUTOSTART=1 to auto-launch at boot.")
729
+ # log-once per target per daemon session (task 89d50a14 [E]) — this
730
+ # skip repeats every ~10s sweep for every stale fleet and was the
731
+ # main feeder of the ~40MB hostd.log.
732
+ _bs_target = f"{proj}/{agent}"
733
+ if _bs_target not in _BOOTSTALE_LOGGED:
734
+ _BOOTSTALE_LOGGED.add(_bs_target)
735
+ _log(f"BOOT-AUTOSTART OFF: skip auto-respawn {_bs_target} (desired_state=running set "
736
+ f"before this hostd start — boot-stale leftover; explicit launch required). "
737
+ f"Set MESHCODE_BOOT_AUTOSTART=1 to auto-launch at boot. "
738
+ f"(logged once per agent per session; skip repeats silently)")
701
739
  continue
740
+ # an explicit launch / live heartbeat got this target past the gate —
741
+ # if it ever goes boot-stale again, the skip deserves a fresh line.
742
+ _BOOTSTALE_LOGGED.discard(f"{proj}/{agent}")
702
743
  # RECYCLE FAST-PATH (task c0fc5597): a recycle-exited agent (recycle_fast) is relaunched
703
744
  # PROMPTLY (the RPC returned it at a 15s stale gate, not STALE_SECONDS) and recorded as a
704
745
  # RECYCLE (mc_record_recycle), NEVER against the crash respawn cap.
@@ -786,7 +827,20 @@ def _do_respawns(api_key: str, host_id: str) -> int:
786
827
  _cv.pop("blocked_ts", None)
787
828
  _cv["count"] = 0 # TTL elapsed -> one clean retry; re-blocks if it storms again
788
829
  # LIVE-SESSION GUARD: never stack a second terminal on a still-alive session.
789
- _live = [p for p in _discover_agent_pids(_target) if _pid_alive(p)]
830
+ # task 89d50a14 [B]: discovery that ERRORED returns an untrustworthy empty
831
+ # list — fail closed (skip this sweep) instead of concluding "no live session".
832
+ _live_d = _discover_agent_pids_ex(_target)
833
+ if _live_d["errored"] and not _DISCOVERY_FAIL_OPEN:
834
+ if _target not in _DISCOVERY_ERR_LOGGED:
835
+ _DISCOVERY_ERR_LOGGED.add(_target)
836
+ _log(f"SPAWN-AUDIT {_target}: DISCOVERY ERRORED (path={_live_d['path']}; "
837
+ f"{_live_d['error']}) — FAIL-CLOSED: cannot prove no live session, NOT "
838
+ f"spawning; retrying every sweep (set MESHCODE_DISCOVERY_FAIL_OPEN=1 to "
839
+ f"spawn blind). [respawn_blocked_reason=discovery_error] (logged once)")
840
+ _log_respawn_event(api_key, host_id, c, "respawn", "failed", "discovery_error",
841
+ detail=f"live-guard path={_live_d['path']}: {_live_d['error'][:300]}")
842
+ continue
843
+ _live = [p for p in _live_d["pids"] if _pid_alive(p)]
790
844
  if _live:
791
845
  _hold_all = dict(_st2.get("respawn_hold") or {})
792
846
  _hold = dict(_hold_all.get(_target) or {})
@@ -884,8 +938,27 @@ def _do_respawns(api_key: str, host_id: str) -> int:
884
938
  # MESHCODE_NO_SPAWN_RECONCILE), spawning anyway IS the double-session bug —
885
939
  # skip this sweep and retry on the next one (the survivor either dies then
886
940
  # or reconnects and stops qualifying as a respawn candidate).
887
- _still_alive = [p for p in (_discover_agent_pids(_target) + _discover_serve_pids(_target))
888
- if _pid_alive(p)]
941
+ _ag_d = _discover_agent_pids_ex(_target)
942
+ _sv_d = _discover_serve_pids_ex(_target)
943
+ # task 89d50a14 [B] FAIL-CLOSED: an errored discovery proves NOTHING about
944
+ # live sessions — spawning on its empty result is the double-session bug
945
+ # with extra steps. Skip the spawn; the candidate re-qualifies next sweep.
946
+ if (_ag_d["errored"] or _sv_d["errored"]) and not _DISCOVERY_FAIL_OPEN:
947
+ if _target not in _DISCOVERY_ERR_LOGGED:
948
+ _DISCOVERY_ERR_LOGGED.add(_target)
949
+ _log(f"SPAWN-AUDIT {_target}: DISCOVERY ERRORED (agent path={_ag_d['path']}; "
950
+ f"{_ag_d['error'] or '-'} | serve: {_sv_d['error'] or '-'}) — FAIL-CLOSED: "
951
+ f"cannot prove no live session, NOT spawning; retrying every sweep "
952
+ f"(set MESHCODE_DISCOVERY_FAIL_OPEN=1 to spawn blind). "
953
+ f"[respawn_blocked_reason=discovery_error] (logged once)")
954
+ _log_respawn_event(api_key, host_id, c,
955
+ "recycle_respawn" if _is_recycle else "respawn",
956
+ "failed", "discovery_error",
957
+ detail=(f"pre-spawn path={_ag_d['path']}: "
958
+ f"{(_ag_d['error'] or _sv_d['error'])[:300]}"))
959
+ continue
960
+ _DISCOVERY_ERR_LOGGED.discard(_target) # discovery healthy again → re-arm the log-once
961
+ _still_alive = [p for p in (_ag_d["pids"] + _sv_d["pids"]) if _pid_alive(p)]
889
962
  if _still_alive:
890
963
  _log(f"SPAWN-AUDIT {_target}: SKIP spawn — live session still present after "
891
964
  f"reconcile (pids {_still_alive}); retry next sweep")
@@ -911,7 +984,8 @@ def _do_respawns(api_key: str, host_id: str) -> int:
911
984
  _log(f"SPAWN-AUDIT {_target}: spawning — reason={'recycle_fast' if _is_recycle else 'crash_respawn'} "
912
985
  f"desired_state=running hb_age={c.get('heartbeat_age_s')}s "
913
986
  f"spawned_age={c.get('spawned_age_s')}s respawn_count={c.get('respawn_count')} "
914
- f"reconciled={_reconciled} host={host_id}")
987
+ f"reconciled={_reconciled} discovery={_ag_d['path']}/{_sv_d['path']} "
988
+ f"(agent_pids={len(_ag_d['pids'])} serve_pids={len(_sv_d['pids'])}) host={host_id}")
915
989
  _log(f"{'RECYCLE-RESPAWN' if _is_recycle else 'RESPAWN'} {proj}/{agent} "
916
990
  f"(stale {c.get('heartbeat_age_s')}s, count={c.get('respawn_count')})")
917
991
  if _spawn_agent(proj, agent, repo_path=c.get("repo_path")):
@@ -1273,13 +1347,54 @@ def _gc_headless_pids() -> None:
1273
1347
  pass
1274
1348
 
1275
1349
 
1350
+ def _cim_pid_cmdline(pid: int) -> str:
1351
+ """Windows cmdline via Get-CimInstance (task 89d50a14 [B]): wmic is deprecated
1352
+ and REMOVED by default on Win11 24H2+ — every wmic call there raises
1353
+ FileNotFoundError and discovery silently went blind. powershell.exe (Windows
1354
+ PowerShell 5.1) ships with every supported Windows. '' on failure."""
1355
+ try:
1356
+ out = subprocess.run(
1357
+ ["powershell", "-NoProfile", "-NonInteractive", "-Command",
1358
+ f"(Get-CimInstance Win32_Process -Filter \"ProcessId={int(pid)}\").CommandLine"],
1359
+ capture_output=True, text=True, timeout=15).stdout
1360
+ return out or ""
1361
+ except Exception:
1362
+ return ""
1363
+
1364
+
1365
+ def _cim_python_pid_cmdlines() -> list:
1366
+ """(pid, cmdline) of every python.exe via Get-CimInstance — the Windows native
1367
+ discovery fallback when psutil is unavailable and wmic is gone (task 89d50a14
1368
+ [B]). One 'pid|cmdline' line per process; partition('|') keeps any '|' inside
1369
+ the cmdline intact. Raises on subprocess failure (caller decides fail-closed)."""
1370
+ script = ("Get-CimInstance Win32_Process -Filter \"Name='python.exe'\" | "
1371
+ "ForEach-Object { '{0}|{1}' -f $_.ProcessId, $_.CommandLine }")
1372
+ out = subprocess.run(
1373
+ ["powershell", "-NoProfile", "-NonInteractive", "-Command", script],
1374
+ capture_output=True, text=True, timeout=15).stdout
1375
+ rows = []
1376
+ for line in (out or "").splitlines():
1377
+ pid_s, sep, cl = line.partition("|")
1378
+ if not sep:
1379
+ continue
1380
+ try:
1381
+ rows.append((int(pid_s.strip()), cl))
1382
+ except ValueError:
1383
+ continue
1384
+ return rows
1385
+
1386
+
1276
1387
  def _pid_cmdline(pid: int) -> str:
1277
1388
  """Best-effort command line for a pid (to avoid killing a reused PID). '' on failure."""
1278
1389
  try:
1279
1390
  if sys.platform == "win32":
1280
- out = subprocess.run(
1281
- ["wmic", "process", "where", f"ProcessId={pid}", "get", "CommandLine"],
1282
- capture_output=True, text=True, timeout=5).stdout
1391
+ try:
1392
+ out = subprocess.run(
1393
+ ["wmic", "process", "where", f"ProcessId={pid}", "get", "CommandLine"],
1394
+ capture_output=True, text=True, timeout=5).stdout
1395
+ except (FileNotFoundError, OSError):
1396
+ # wmic removed (Win11 24H2+) — CIM fallback (task 89d50a14 [B])
1397
+ out = _cim_pid_cmdline(pid)
1283
1398
  else:
1284
1399
  out = subprocess.run(
1285
1400
  ["ps", "-o", "args=", "-p", str(pid)],
@@ -1401,7 +1516,26 @@ def _discover_agent_pids(target: str) -> list:
1401
1516
  mesh (psutil path only; the native fallbacks accept the regex as-is, documented
1402
1517
  tradeoff: a zombie that survives every stop is the live bug, the cross-mesh
1403
1518
  same-name collision is the rare one)."""
1519
+ return _discover_agent_pids_ex(target)["pids"]
1520
+
1521
+
1522
+ def _discover_agent_pids_ex(target: str) -> dict:
1523
+ """Instrumented discovery (task 89d50a14 [B]) — same matching as
1524
+ _discover_agent_pids (see its docstring for every safety rule), but the
1525
+ result distinguishes 'found nothing' from 'COULD NOT LOOK':
1526
+
1527
+ {"pids": [..], "errored": bool, "path": str, "error": str}
1528
+
1529
+ errored=True means the path that produced the final result RAISED — the
1530
+ empty pid list is untrustworthy and spawn decisions must FAIL CLOSED
1531
+ (an empty-but-clean result stays spawn-safe). path is the mechanism that
1532
+ produced the result: 'psutil' | 'native-wmic' | 'native-cim' |
1533
+ 'native-pgrep'. Windows native goes wmic → Get-CimInstance (wmic is
1534
+ removed by default on Win11 24H2+; it raising FileNotFoundError and the
1535
+ sweep silently treating that as 'no live session' was a double-session
1536
+ feeder)."""
1404
1537
  pids = []
1538
+ err_notes = []
1405
1539
  tok = _run_token_rx(target)
1406
1540
  ps = _psutil()
1407
1541
  if ps is not None:
@@ -1428,30 +1562,51 @@ def _discover_agent_pids(target: str) -> list:
1428
1562
  # `return pids` skipped the POSIX launcher-children fallback whenever
1429
1563
  # psutil was importable — re-defeating the '0 stopped forever' hole on
1430
1564
  # every box with psutil. Fall through to the shared tail instead.
1431
- return pids + ([p for p in _discover_launcher_child_pids(target)
1432
- if p not in pids] if sys.platform != "win32" else [])
1433
- except Exception:
1565
+ return {"pids": pids + ([p for p in _discover_launcher_child_pids(target)
1566
+ if p not in pids] if sys.platform != "win32" else []),
1567
+ "errored": False, "path": "psutil", "error": ""}
1568
+ except Exception as e:
1569
+ err_notes.append(f"psutil: {e!r}")
1434
1570
  pids = [] # psutil enumeration itself broke — fall through to native
1571
+ path = "native"
1572
+ errored = False
1435
1573
  try:
1436
1574
  if sys.platform == "win32":
1437
- out = subprocess.run(
1438
- ["wmic", "process", "where", "name='python.exe'", "get", "ProcessId,CommandLine", "/FORMAT:LIST"],
1439
- capture_output=True, text=True, timeout=8).stdout
1440
- block_pid = None
1441
- for line in out.splitlines():
1442
- line = line.strip()
1443
- if line.startswith("CommandLine="):
1444
- block_pid = ("meshcode" in line and bool(tok.search(line)))
1445
- elif line.startswith("ProcessId=") and block_pid:
1446
- try:
1447
- pids.append(int(line.split("=", 1)[1]))
1448
- except Exception:
1449
- pass
1450
- block_pid = None
1575
+ wmic_out = None
1576
+ try:
1577
+ cp = subprocess.run(
1578
+ ["wmic", "process", "where", "name='python.exe'", "get", "ProcessId,CommandLine", "/FORMAT:LIST"],
1579
+ capture_output=True, text=True, timeout=8)
1580
+ if cp.returncode == 0 or (cp.stdout or "").strip():
1581
+ wmic_out = cp.stdout or ""
1582
+ else:
1583
+ err_notes.append(f"wmic: rc={cp.returncode}")
1584
+ except (FileNotFoundError, OSError) as e:
1585
+ # wmic deprecated/removed (Win11 24H2+) — Get-CimInstance fallback
1586
+ err_notes.append(f"wmic: {e!r}")
1587
+ if wmic_out is not None:
1588
+ path = "native-wmic"
1589
+ block_pid = None
1590
+ for line in wmic_out.splitlines():
1591
+ line = line.strip()
1592
+ if line.startswith("CommandLine="):
1593
+ block_pid = ("meshcode" in line and bool(tok.search(line)))
1594
+ elif line.startswith("ProcessId=") and block_pid:
1595
+ try:
1596
+ pids.append(int(line.split("=", 1)[1]))
1597
+ except Exception:
1598
+ pass
1599
+ block_pid = None
1600
+ else:
1601
+ path = "native-cim"
1602
+ for cim_pid, cl in _cim_python_pid_cmdlines():
1603
+ if cl and "meshcode" in cl and tok.search(cl):
1604
+ pids.append(cim_pid)
1451
1605
  else:
1452
1606
  # task 3ed8781d: pattern WITHOUT the target so bare-name spawns
1453
1607
  # (`run "agent"`) are candidates too; the exact tok filter below
1454
1608
  # keeps the kill set tight.
1609
+ path = "native-pgrep"
1455
1610
  out = subprocess.run(
1456
1611
  ["pgrep", "-f", "meshcode.* run "],
1457
1612
  capture_output=True, text=True, timeout=8).stdout
@@ -1463,8 +1618,10 @@ def _discover_agent_pids(target: str) -> list:
1463
1618
  # pgrep matched a substring; keep ONLY if the full cmdline has the exact token.
1464
1619
  if tok.search(_pid_cmdline(p)):
1465
1620
  pids.append(p)
1466
- except Exception:
1467
- pass
1621
+ except Exception as e:
1622
+ # the LAST mechanism available raised — the empty result is a lie, not a fact
1623
+ err_notes.append(f"{path}: {e!r}")
1624
+ errored = True
1468
1625
  # GHOST-TERMINAL fix (task 91201315): on POSIX `meshcode run` EXECVP's claude, so the
1469
1626
  # live agent's cmdline is `claude …` — no 'meshcode run <target>' survives and the
1470
1627
  # pgrep above sees NOTHING for a visible macOS agent (the '0 stopped forever' hole).
@@ -1477,7 +1634,7 @@ def _discover_agent_pids(target: str) -> list:
1477
1634
  pids += [p for p in _discover_launcher_child_pids(target) if p not in pids]
1478
1635
  except Exception:
1479
1636
  pass
1480
- return pids
1637
+ return {"pids": pids, "errored": errored, "path": path, "error": "; ".join(err_notes)}
1481
1638
 
1482
1639
 
1483
1640
  def _discover_launcher_child_pids(target: str) -> list:
@@ -1543,12 +1700,21 @@ def _discover_serve_pids(target: str) -> list:
1543
1700
  relaunch reconcile) matches on MESHCODE_AGENT alone — the relaunch click means
1544
1701
  'fresh session of THIS agent on this box', so any project's orphan of that name
1545
1702
  is a survivor to clear. Qualified targets keep the exact two-field match."""
1703
+ return _discover_serve_pids_ex(target)["pids"]
1704
+
1705
+
1706
+ def _discover_serve_pids_ex(target: str) -> dict:
1707
+ """Instrumented serve discovery (task 89d50a14 [B]) — same result contract as
1708
+ _discover_agent_pids_ex. psutil-missing is errored=False (degraded BY DESIGN,
1709
+ documented since 14e0760c: the session-root tree kill still covers parented
1710
+ serves); the enumeration itself raising is errored=True (empty result is
1711
+ untrustworthy → spawn decisions fail closed)."""
1546
1712
  ps = _psutil()
1547
1713
  if ps is None:
1548
- return []
1714
+ return {"pids": [], "errored": False, "path": "none(psutil-missing)", "error": ""}
1549
1715
  proj, _, agent = target.rpartition("/")
1550
1716
  if not agent:
1551
- return []
1717
+ return {"pids": [], "errored": False, "path": "psutil", "error": ""}
1552
1718
  own = _own_pid()
1553
1719
  out = []
1554
1720
  try:
@@ -1565,9 +1731,9 @@ def _discover_serve_pids(target: str) -> list:
1565
1731
  out.append(p.pid)
1566
1732
  except Exception:
1567
1733
  continue # process vanished / access denied — never block the sweep
1568
- except Exception:
1569
- return []
1570
- return out
1734
+ except Exception as e:
1735
+ return {"pids": [], "errored": True, "path": "psutil", "error": f"psutil: {e!r}"}
1736
+ return {"pids": out, "errored": False, "path": "psutil", "error": ""}
1571
1737
 
1572
1738
 
1573
1739
  def _kill_heartbeat_fork(target: str) -> None:
@@ -6723,6 +6723,47 @@ def meshcode_tray_claim(lease_seconds: int = 900,
6723
6723
  lease_seconds=lease_seconds)
6724
6724
 
6725
6725
 
6726
+ @mcp.tool()
6727
+ @with_working_status
6728
+ def meshcode_tray_seed(title: str,
6729
+ description: str = "",
6730
+ priority: str = "normal",
6731
+ swarm_id: Optional[str] = None,
6732
+ parent_task_id: Optional[str] = None,
6733
+ depends_on: Optional[list] = None,
6734
+ queue_position: Optional[int] = None) -> Dict[str, Any]:
6735
+ """PARENT loop step: seed ONE task into your swarm tray so helpers drain
6736
+ it via meshcode_tray_claim (work-stealing, DAG-gated, priority-ordered).
6737
+
6738
+ ENJAMBRE (mig 524, task d4af9eb7 — before this, nothing could create
6739
+ swarm_id-stamped tasks: lanes went by direct assignment and lost
6740
+ work-stealing + the mig 471 barrier-on-drain). Canonical flow:
6741
+ meshcode_helper_spawn (mints the tray + stamps YOUR swarm_id) →
6742
+ meshcode_tray_seed xN → helpers tray_claim/complete → barrier fires when
6743
+ the tray drains. Seed BEFORE or AFTER spawning — claims only see
6744
+ status='open' rows, there is no race.
6745
+
6746
+ Identity-bound (agent-scoped api key) and member-only: you can seed only
6747
+ the swarm you belong to. The task lands assignee=you (concrete-owner
6748
+ rule); stealing is via claimed_by.
6749
+
6750
+ Args:
6751
+ title: task title (required, <=200 chars).
6752
+ description: full lane instructions (helpers see it at claim time).
6753
+ priority: low|normal|medium|high|urgent (claim order).
6754
+ swarm_id: override tray; omit and the SERVER resolves your own row's swarm.
6755
+ parent_task_id: umbrella task — seeded tasks participate in its
6756
+ barrier-on-drain (mig 471) automatically.
6757
+ depends_on: task UUIDs that DAG-gate this one (validated same-project).
6758
+ queue_position: tiebreaker within same priority (lower first).
6759
+ """
6760
+ from . import swarm as _swarm
6761
+ return _swarm.tray_seed(_get_api_key(), _PROJECT_ID, title,
6762
+ description=description, priority=priority,
6763
+ swarm_id=swarm_id, parent_task_id=parent_task_id,
6764
+ depends_on=depends_on, queue_position=queue_position)
6765
+
6766
+
6726
6767
  @mcp.tool()
6727
6768
  @with_working_status
6728
6769
  def meshcode_helper_retire(reason: str = "tray_drained") -> Dict[str, Any]:
@@ -184,6 +184,16 @@ def spawn_helper(api_key: str, project_id: str, name: str, *,
184
184
  "error": _explain_spawn_refusal(code, err),
185
185
  **({"error_code": code} if code else {})}
186
186
 
187
+ # HELPER VISUALS (task d8f8e325): stamp the local marker SO THE SPAWNER
188
+ # (hostd / protocol_handler, same host by W5 inheritance) can render the
189
+ # helper's terminal distinct (amber tab + helper: title). Best-effort —
190
+ # never blocks the spawn.
191
+ try:
192
+ from meshcode import helper_visuals as _hv
193
+ _hv.mark_helper(name, ttl_seconds=int(ttl_seconds))
194
+ except Exception:
195
+ pass
196
+
187
197
  out: Dict[str, Any] = {"ok": True, "helper": name, "launched": bool(launch)}
188
198
  if isinstance(reg, dict):
189
199
  out.update({k: reg[k] for k in
@@ -217,6 +227,45 @@ def spawn_helper(api_key: str, project_id: str, name: str, *,
217
227
  return out
218
228
 
219
229
 
230
+ RPC_TRAY_SEED = "mc_tray_seed_as_agent"
231
+
232
+
233
+ def tray_seed(api_key: str, project_id: str, title: str, *,
234
+ description: str = "",
235
+ priority: str = "normal",
236
+ swarm_id: Optional[str] = None,
237
+ parent_task_id: Optional[str] = None,
238
+ depends_on: Optional[list] = None,
239
+ queue_position: Optional[int] = None) -> Dict[str, Any]:
240
+ """Seed ONE task into the swarm tray (task d4af9eb7 — closes the gap where
241
+ mc_task_claim_from_tray could only CLAIM and nothing could create
242
+ swarm_id-stamped tasks, so lanes fell back to direct assignment and lost
243
+ work-stealing + DAG gate + barrier-on-drain).
244
+
245
+ swarm_id=None lets the SERVER resolve the caller's own mc_agents.swarm_id
246
+ (same contract as tray_claim — the SDK never reads mc_agents directly).
247
+ Identity-bound: requires an agent-scoped api key; the caller must belong
248
+ to the swarm. The seeded task lands status='open' (exactly what
249
+ tray_claim picks up), assignee=caller (mig 213 concrete-owner rule;
250
+ stealing is via claimed_by). depends_on ids are validated server-side
251
+ against the same project so a typo can never DAG-block the tray forever."""
252
+ result = be.sb_rpc(RPC_TRAY_SEED, {
253
+ "p_api_key": api_key,
254
+ "p_project_id": project_id,
255
+ "p_title": title,
256
+ "p_description": description,
257
+ "p_priority": priority,
258
+ "p_swarm_id": swarm_id,
259
+ "p_parent_task_id": parent_task_id,
260
+ "p_depends_on": depends_on,
261
+ "p_queue_position": queue_position,
262
+ })
263
+ err = _err(result)
264
+ if err:
265
+ return {"ok": False, "error": err}
266
+ return result if isinstance(result, dict) else {"ok": False, "error": "bad response shape"}
267
+
268
+
220
269
  def tray_claim(api_key: str, project_id: str, *,
221
270
  swarm_id: Optional[str] = None,
222
271
  lease_seconds: int = DEFAULT_LEASE_SECONDS) -> Dict[str, Any]:
@@ -255,6 +304,17 @@ def retire_self(api_key: str, project_id: str, *,
255
304
  err = _err(result)
256
305
  if err:
257
306
  return {"ok": False, "error": err}
307
+ # HELPER VISUALS (task d8f8e325): retired — drop this host's marker so a
308
+ # later same-named top-level agent doesn't render amber. Self-identify via
309
+ # the MCP env (.mcp.json always sets MESHCODE_AGENT). Best-effort.
310
+ try:
311
+ import os as _os
312
+ from meshcode import helper_visuals as _hv
313
+ _own = _os.environ.get("MESHCODE_AGENT", "")
314
+ if _own:
315
+ _hv.unmark_helper(_own)
316
+ except Exception:
317
+ pass
258
318
  return result if isinstance(result, dict) else {"ok": False, "error": "bad response shape"}
259
319
 
260
320