meshcode 2.11.114rc1__tar.gz → 2.11.115__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.114rc1 → meshcode-2.11.115}/PKG-INFO +1 -1
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/__init__.py +1 -1
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/hostd.py +0 -236
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/server.py +111 -7
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/protocol_handler.py +34 -1
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/run_agent.py +0 -17
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode.egg-info/SOURCES.txt +37 -7
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/pyproject.toml +1 -1
- meshcode-2.11.115/tests/test_auto_update_hardening.py +295 -0
- meshcode-2.11.115/tests/test_autonomous_closegap_1.py +164 -0
- meshcode-2.11.115/tests/test_autonomous_closegap_2.py +210 -0
- meshcode-2.11.115/tests/test_autonomous_closegap_3.py +163 -0
- meshcode-2.11.115/tests/test_autonomous_prompt_inject.py +126 -0
- meshcode-2.11.115/tests/test_boot_bug_regression.py +205 -0
- meshcode-2.11.115/tests/test_color_truecolor.py +83 -0
- meshcode-2.11.115/tests/test_core.py +216 -0
- meshcode-2.11.115/tests/test_cross_agent_messaging.py +366 -0
- meshcode-2.11.115/tests/test_date_parse.py +112 -0
- meshcode-2.11.115/tests/test_doctor.py +123 -0
- meshcode-2.11.115/tests/test_epistemic_v1_python_sdk.py +177 -0
- meshcode-2.11.115/tests/test_epistemic_v1_stop_conditions.py +158 -0
- meshcode-2.11.115/tests/test_esc_deaf_state.py +361 -0
- meshcode-2.11.115/tests/test_exceptions.py +107 -0
- meshcode-2.11.115/tests/test_file_upload.py +171 -0
- meshcode-2.11.115/tests/test_init_device_code.py +68 -0
- meshcode-2.11.115/tests/test_install_guard.py +170 -0
- meshcode-2.11.115/tests/test_lease_sigterm_release.py +299 -0
- meshcode-2.11.115/tests/test_mark_read_batch.py +200 -0
- meshcode-2.11.115/tests/test_marketplace_ratings.py +174 -0
- meshcode-2.11.115/tests/test_migration_integrity.py +176 -0
- meshcode-2.11.115/tests/test_realtime_event_freshness.py +236 -0
- meshcode-2.11.115/tests/test_rls_cross_tenant.py +255 -0
- meshcode-2.11.115/tests/test_rpc_grants.py +76 -0
- meshcode-2.11.115/tests/test_rpc_migrations.py +452 -0
- meshcode-2.11.115/tests/test_run_agent_dry_run.py +128 -0
- meshcode-2.11.115/tests/test_run_agent_no_server_import.py +85 -0
- meshcode-2.11.115/tests/test_security_regressions.py +228 -0
- meshcode-2.11.115/tests/test_self_update_user_site.py +139 -0
- meshcode-2.11.115/tests/test_sentinel.py +148 -0
- meshcode-2.11.115/tests/test_setup_path.py +66 -0
- meshcode-2.11.115/tests/test_sleep_signals.py +160 -0
- meshcode-2.11.115/tests/test_status_enum_coverage.py +231 -0
- meshcode-2.11.115/tests/test_stay_on_loop_hook.py +302 -0
- meshcode-2.11.115/tests/test_wait_open_tasks_contradiction.py +87 -0
- meshcode-2.11.114rc1/meshcode/_session_handoff_template 2.py +0 -296
- meshcode-2.11.114rc1/meshcode/_session_handoff_template 3.py +0 -296
- meshcode-2.11.114rc1/meshcode/claude_update 2.py +0 -258
- meshcode-2.11.114rc1/meshcode/claude_update 3.py +0 -258
- meshcode-2.11.114rc1/meshcode/hostd 2.py +0 -1269
- meshcode-2.11.114rc1/meshcode/up 2.py +0 -257
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/README.md +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/__main__.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/cli.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/compat.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/daemon.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/doctor.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/hooks/__init__.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/hooks/repo_path_lock.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/invites.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/launcher.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/preferences.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/secrets.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/self_update.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/up.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode/upload.py +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.114rc1 → meshcode-2.11.115}/setup.cfg +0 -0
|
@@ -816,32 +816,6 @@ def _pid_cmdline(pid: int) -> str:
|
|
|
816
816
|
return ""
|
|
817
817
|
|
|
818
818
|
|
|
819
|
-
def _pid_alive(pid: int) -> bool:
|
|
820
|
-
"""True iff `pid` is a live process. Signal-0 probe on POSIX; tasklist on Windows.
|
|
821
|
-
Best-effort — on any unexpected error returns False (treat unknown as not-alive so
|
|
822
|
-
the ghost detector never mis-reports). PermissionError means the process exists but
|
|
823
|
-
isn't ours = alive; ProcessLookupError means dead."""
|
|
824
|
-
try:
|
|
825
|
-
pid = int(pid)
|
|
826
|
-
if pid <= 0:
|
|
827
|
-
return False
|
|
828
|
-
if sys.platform == "win32":
|
|
829
|
-
# /FO CSV so the pid is a quoted field — anchor on `"<pid>"` instead of a bare
|
|
830
|
-
# substring (a bare pid can match a memory/session column → false-positive).
|
|
831
|
-
out = subprocess.run(["tasklist", "/FI", f"PID eq {pid}", "/NH", "/FO", "CSV"],
|
|
832
|
-
capture_output=True, text=True, timeout=5).stdout or ""
|
|
833
|
-
return f'"{pid}"' in out
|
|
834
|
-
try:
|
|
835
|
-
os.kill(pid, 0)
|
|
836
|
-
except ProcessLookupError:
|
|
837
|
-
return False
|
|
838
|
-
except PermissionError:
|
|
839
|
-
return True
|
|
840
|
-
return True
|
|
841
|
-
except Exception:
|
|
842
|
-
return False
|
|
843
|
-
|
|
844
|
-
|
|
845
819
|
def _discover_agent_pids(target: str) -> list:
|
|
846
820
|
"""Fallback PID discovery by command line, for agents spawned before this hostd
|
|
847
821
|
(no recorded PID) or after a state-file loss. Matches `meshcode run <target>`.
|
|
@@ -1228,215 +1202,6 @@ def _do_recycle_enforce(api_key: str, host_id: str) -> int:
|
|
|
1228
1202
|
return n
|
|
1229
1203
|
|
|
1230
1204
|
|
|
1231
|
-
# ------------------------------------------------------------------
|
|
1232
|
-
# FOCUS-1 GHOST SESSIONS (back-2, task 09fca8fe) — dead-MCP detector.
|
|
1233
|
-
#
|
|
1234
|
-
# OS-ghost = a `claude` agent process still ALIVE but its `meshcode_mcp serve`
|
|
1235
|
-
# child DEAD → no heartbeat → the dashboard shows it offline/invisible while a
|
|
1236
|
-
# terminal window is still open ("connected-but-invisible"). Two local pidfiles
|
|
1237
|
-
# correlate it, no roster RPC needed for detection:
|
|
1238
|
-
# S3 claude alive — ~/.meshcode/pids/<project>__<agent>.pid, stamped by
|
|
1239
|
-
# run_agent right before os.execvp (the pid survives exec => == claude pid).
|
|
1240
|
-
# S2 mcp dead — {tempdir}/meshcode_mcp_<project>_<agent>.pid, written by the
|
|
1241
|
-
# MCP server (meshcode_mcp/server.py). If that pid is dead/missing, mcp died.
|
|
1242
|
-
# A ghost = S3 alive AND S2 dead. The eventual reap (kill the orphaned claude so
|
|
1243
|
-
# the respawn sweep relaunches it clean) ALSO needs S1 = the server's is_ghost /
|
|
1244
|
-
# effective_status='offline' (prod mc_agent_liveness) as a third independent
|
|
1245
|
-
# guard, plus the cmdline reuse-guard — wired when arming.
|
|
1246
|
-
#
|
|
1247
|
-
# SHIPS LOG-ONLY (_GHOST_REAP_DRYRUN=True): logs GHOST-DRYRUN candidates so we can
|
|
1248
|
-
# confirm ZERO false positives across the live fleet BEFORE flipping to a real
|
|
1249
|
-
# kill (commander-mandated staged arming). Kills NOTHING while dry-run.
|
|
1250
|
-
#
|
|
1251
|
-
# GUARDS folded from back's cross-review (HIGH_1/HIGH_2) — all enforced even in the
|
|
1252
|
-
# DRY-RUN classification so the logs only ever show TRUE ghosts:
|
|
1253
|
-
# - boot-grace: skip until spawned_age (pidfile mtime) > GHOST_BOOT_GRACE_SEC, so a
|
|
1254
|
-
# freshly-booting agent (claude up, MCP still connecting) is NOT mis-flagged.
|
|
1255
|
-
# - cwd-guard: the live pid's working dir MUST equal the agent's recorded launch cwd
|
|
1256
|
-
# (pidfile {cwd}, or the workspace as legacy fallback). Anti PID-reuse mis-kill —
|
|
1257
|
-
# post-execvp claude's cmdline loses <target>, so cwd is the stable correlator. If
|
|
1258
|
-
# cwd is unreadable on this platform → fail-SAFE (skip, never reap on uncertainty).
|
|
1259
|
-
# ------------------------------------------------------------------
|
|
1260
|
-
_GHOST_REAP_DRYRUN = True
|
|
1261
|
-
GHOST_BOOT_GRACE_SEC = _env_int("MESHCODE_GHOST_BOOT_GRACE_SEC", 120, 30) # mac boots slow; >> _REAP grace + MCP reconnect
|
|
1262
|
-
GHOST_PERSIST_SEC = _env_int("MESHCODE_GHOST_PERSIST_SEC", 90, 30) # ghost must hold this long → a brief mcp restart doesn't count (MED_restart)
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
def _mcp_serve_pid(project: str, agent: str) -> Optional[int]:
|
|
1266
|
-
"""Read the MCP server's recorded pid for this agent (meshcode_mcp/server.py
|
|
1267
|
-
lockfile). Returns the pid, or None if the lockfile is missing/unparseable.
|
|
1268
|
-
Path + JSON|bare-int format mirror _pid_lockfile_path()/_read_pid_lockfile()."""
|
|
1269
|
-
try:
|
|
1270
|
-
import tempfile as _tf
|
|
1271
|
-
safe = f"meshcode_mcp_{project}_{agent}.pid".replace("/", "_").replace(" ", "_")
|
|
1272
|
-
path = os.path.join(_tf.gettempdir(), safe)
|
|
1273
|
-
if not os.path.exists(path):
|
|
1274
|
-
return None
|
|
1275
|
-
raw = open(path, "r").read().strip()
|
|
1276
|
-
if not raw:
|
|
1277
|
-
return None
|
|
1278
|
-
try:
|
|
1279
|
-
data = json.loads(raw)
|
|
1280
|
-
if isinstance(data, dict) and "pid" in data:
|
|
1281
|
-
return int(data["pid"])
|
|
1282
|
-
except (ValueError, TypeError):
|
|
1283
|
-
pass
|
|
1284
|
-
return int(raw)
|
|
1285
|
-
except Exception:
|
|
1286
|
-
return None
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
def _agent_launcher_record(project: str, agent: str):
|
|
1290
|
-
"""Read run_agent's pre-execvp pidfile (~/.meshcode/pids/<project>__<agent>.pid).
|
|
1291
|
-
Returns (pid:int|None, cwd:str|None). New format is JSON {"pid","cwd"}; tolerates
|
|
1292
|
-
the legacy bare-int (no recorded cwd)."""
|
|
1293
|
-
try:
|
|
1294
|
-
safe = f"{project}__{agent}".replace("/", "_").replace("\\", "_").replace(" ", "_")
|
|
1295
|
-
path = STATE_DIR / "pids" / f"{safe}.pid"
|
|
1296
|
-
if not path.exists():
|
|
1297
|
-
return None, None
|
|
1298
|
-
raw = path.read_text(encoding="utf-8").strip()
|
|
1299
|
-
if not raw:
|
|
1300
|
-
return None, None
|
|
1301
|
-
try:
|
|
1302
|
-
data = json.loads(raw)
|
|
1303
|
-
if isinstance(data, dict) and "pid" in data:
|
|
1304
|
-
return int(data["pid"]), (data.get("cwd") or None)
|
|
1305
|
-
except (ValueError, TypeError):
|
|
1306
|
-
pass
|
|
1307
|
-
return int(raw), None
|
|
1308
|
-
except Exception:
|
|
1309
|
-
return None, None
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
def _read_pid_cwd(pid: int) -> Optional[str]:
|
|
1313
|
-
"""Best-effort working directory of a live pid (time-boxed). None if unreadable.
|
|
1314
|
-
linux : /proc/<pid>/cwd symlink
|
|
1315
|
-
macOS : lsof -a -p <pid> -d cwd -Fn → the 'n' line (no Full Disk Access needed)
|
|
1316
|
-
win : None (run_agent does NOT execvp there; cmdline keeps `run <target>`, so
|
|
1317
|
-
the existing cmdline token-guard already correlates — cwd not needed)."""
|
|
1318
|
-
try:
|
|
1319
|
-
if sys.platform == "win32":
|
|
1320
|
-
return None
|
|
1321
|
-
if sys.platform == "darwin":
|
|
1322
|
-
out = subprocess.run(["lsof", "-a", "-p", str(int(pid)), "-d", "cwd", "-Fn"],
|
|
1323
|
-
capture_output=True, text=True, timeout=5).stdout or ""
|
|
1324
|
-
for line in out.splitlines():
|
|
1325
|
-
if line.startswith("n"):
|
|
1326
|
-
return line[1:].strip() or None
|
|
1327
|
-
return None
|
|
1328
|
-
return os.readlink(f"/proc/{int(pid)}/cwd") # linux
|
|
1329
|
-
except Exception:
|
|
1330
|
-
return None
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
def _pid_cwd_matches(pid: int, expected_cwd: Optional[str], project: str, agent: str):
|
|
1334
|
-
"""Tri-state: True if the pid's live cwd == the agent's launch cwd; False if it
|
|
1335
|
-
differs; None if unreadable. `expected_cwd` is the pidfile-recorded cwd; when absent
|
|
1336
|
-
(legacy pidfile) fall back to the canonical workspace dir. realpath both sides
|
|
1337
|
-
(mac /Users vs /private symlinks, --repo dirs). Autonomous reap requires True."""
|
|
1338
|
-
live = _read_pid_cwd(pid)
|
|
1339
|
-
if not live:
|
|
1340
|
-
return None # unreadable → unknown → fail-safe skip
|
|
1341
|
-
exp = set()
|
|
1342
|
-
if expected_cwd:
|
|
1343
|
-
try: exp.add(os.path.realpath(os.path.expanduser(expected_cwd)))
|
|
1344
|
-
except Exception: pass
|
|
1345
|
-
try:
|
|
1346
|
-
exp.add(os.path.realpath(os.path.join(os.path.expanduser("~"), "meshcode", f"{project}-{agent}")))
|
|
1347
|
-
except Exception:
|
|
1348
|
-
pass
|
|
1349
|
-
try:
|
|
1350
|
-
return os.path.realpath(live) in exp
|
|
1351
|
-
except Exception:
|
|
1352
|
-
return None
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
def _do_ghost_reap(api_key: str, host_id: str) -> int:
|
|
1356
|
-
"""Detect dead-MCP ghosts (claude ALIVE + mcp_serve DEAD) from local pidfiles.
|
|
1357
|
-
LOG-ONLY while _GHOST_REAP_DRYRUN — confirm zero false positives in the fleet logs
|
|
1358
|
-
before arming. Returns the count that WOULD be reaped.
|
|
1359
|
-
|
|
1360
|
-
Self-contained: enumerates this host's launcher pidfiles (agents WE launched), so
|
|
1361
|
-
no roster RPC is needed for detection. All HIGH guards (boot-grace + cwd-match) are
|
|
1362
|
-
enforced here so DRYRUN logs only ever show TRUE ghosts. Fail-open at the top level
|
|
1363
|
-
so a bug here can never wedge the sweep."""
|
|
1364
|
-
n = 0
|
|
1365
|
-
try:
|
|
1366
|
-
pid_dir = STATE_DIR / "pids"
|
|
1367
|
-
if not pid_dir.is_dir():
|
|
1368
|
-
return 0
|
|
1369
|
-
now = time.time()
|
|
1370
|
-
st = _load_state()
|
|
1371
|
-
seen = dict(st.get("ghost_seen") or {}) # {target: first_candidate_ts} — MED_restart persistence
|
|
1372
|
-
still = set() # targets that are candidates THIS sweep (others get cleared)
|
|
1373
|
-
for pf in sorted(pid_dir.glob("*.pid")):
|
|
1374
|
-
try:
|
|
1375
|
-
stem = pf.stem # "<project>__<agent>"
|
|
1376
|
-
if "__" not in stem:
|
|
1377
|
-
continue
|
|
1378
|
-
project, agent = stem.split("__", 1)
|
|
1379
|
-
target = f"{project}/{agent}"
|
|
1380
|
-
claude_pid, rec_cwd = _agent_launcher_record(project, agent)
|
|
1381
|
-
if not claude_pid or not _pid_alive(claude_pid):
|
|
1382
|
-
# MED_pidfile_stale: the agent exited (run_agent execvp'd, can't self-clean) →
|
|
1383
|
-
# the launcher pid is dead. Prune the stale pidfile so a future reused pid can't FP.
|
|
1384
|
-
try:
|
|
1385
|
-
pf.unlink()
|
|
1386
|
-
except Exception:
|
|
1387
|
-
pass
|
|
1388
|
-
continue # S3 fail: no live claude → not a ghost
|
|
1389
|
-
# HIGH_2 boot-grace: pidfile mtime ≈ spawn time (written just before execvp).
|
|
1390
|
-
try:
|
|
1391
|
-
spawned_age = now - pf.stat().st_mtime
|
|
1392
|
-
except Exception:
|
|
1393
|
-
spawned_age = 1e9
|
|
1394
|
-
if spawned_age < GHOST_BOOT_GRACE_SEC:
|
|
1395
|
-
continue # still booting / MCP may be connecting → never reap
|
|
1396
|
-
mcp_pid = _mcp_serve_pid(project, agent)
|
|
1397
|
-
if mcp_pid and _pid_alive(mcp_pid):
|
|
1398
|
-
continue # S2 fail: mcp_serve alive → healthy
|
|
1399
|
-
mcp_state = (f"pid {mcp_pid} DEAD" if mcp_pid else "lockfile MISSING")
|
|
1400
|
-
# HIGH_1 cwd-guard: the live pid MUST be running in this agent's launch cwd.
|
|
1401
|
-
live_cwd = _read_pid_cwd(claude_pid)
|
|
1402
|
-
cwd_match = _pid_cwd_matches(claude_pid, rec_cwd, project, agent)
|
|
1403
|
-
if cwd_match is not True:
|
|
1404
|
-
_log(f"GHOST-SKIP {target}: claude pid {claude_pid} + mcp {mcp_state} but cwd-guard "
|
|
1405
|
-
f"{'UNREADABLE' if cwd_match is None else 'MISMATCH'} "
|
|
1406
|
-
f"(live_cwd={live_cwd!r} expected~{rec_cwd!r}) — NOT a confirmed ghost, no reap.")
|
|
1407
|
-
continue # fail-SAFE: never reap on cwd uncertainty/mismatch
|
|
1408
|
-
# Candidate (S3 alive ∧ boot-grace ∧ S2 dead ∧ cwd-match). MED_restart persistence:
|
|
1409
|
-
# only COUNT after the ghost has held GHOST_PERSIST_SEC, so a brief mcp restart
|
|
1410
|
-
# (dead for one sweep) never registers.
|
|
1411
|
-
still.add(target)
|
|
1412
|
-
first_ts = seen.get(target) or now
|
|
1413
|
-
seen[target] = first_ts
|
|
1414
|
-
held = now - first_ts
|
|
1415
|
-
if held < GHOST_PERSIST_SEC:
|
|
1416
|
-
_log(f"GHOST-PENDING {target}: dead-MCP candidate held {int(held)}s (< {GHOST_PERSIST_SEC}s) "
|
|
1417
|
-
f"— waiting for persistence before counting (guards against a brief mcp restart).")
|
|
1418
|
-
continue
|
|
1419
|
-
# CONFIRMED, PERSISTENT dead-MCP ghost.
|
|
1420
|
-
n += 1
|
|
1421
|
-
cmdline = _pid_cmdline(claude_pid).strip()[:120]
|
|
1422
|
-
_log(f"GHOST-DRYRUN {target}: claude pid {claude_pid} ALIVE (cwd={live_cwd!r}, "
|
|
1423
|
-
f"spawned_age={int(spawned_age)}s, ghost_held={int(held)}s) but meshcode_mcp serve "
|
|
1424
|
-
f"{mcp_state} — connected-but-invisible. WOULD reap claude → respawn relaunches clean "
|
|
1425
|
-
f"[log-only; arming also needs S1 is_ghost + human-defer + spawn-breaker]. cmdline={cmdline!r}")
|
|
1426
|
-
# ARMED PATH (not yet enabled): AND S1 (roster is_ghost) AND not _human_recently_active(target)
|
|
1427
|
-
# AND _spawn_rate_ok(target), then _kill_headless_pid(target, claude_pid) → respawn relaunches.
|
|
1428
|
-
except Exception:
|
|
1429
|
-
continue
|
|
1430
|
-
# Clear persistence for targets that recovered (mcp back, agent gone) so a future
|
|
1431
|
-
# transient doesn't inherit a stale clock.
|
|
1432
|
-
seen = {t: ts for t, ts in seen.items() if t in still}
|
|
1433
|
-
st["ghost_seen"] = seen
|
|
1434
|
-
_save_state(st)
|
|
1435
|
-
except Exception:
|
|
1436
|
-
pass
|
|
1437
|
-
return n
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
1205
|
def _do_recycles(api_key: str, host_id: str) -> int:
|
|
1441
1206
|
"""Uptime-based recycle at task boundary. Returns number recycled."""
|
|
1442
1207
|
# DEAD-FEATURE (task 222b1b02, Samuel 2026-06-04): the RECYCLE feature is disabled in
|
|
@@ -1994,7 +1759,6 @@ def cmd_hostd(args: list) -> int:
|
|
|
1994
1759
|
stopped = _do_stops(api_key, host_id)
|
|
1995
1760
|
force_killed = _do_force_kills(api_key, host_id) # 38523a98 Gap1: visible explicit human stop
|
|
1996
1761
|
reaped = _do_reap(api_key, host_id) # 38523a98: kill ghosts/dup-PIDs/crashed-orphans
|
|
1997
|
-
ghosts = _do_ghost_reap(api_key, host_id) # FOCUS-1 09fca8fe: dead-MCP ghost detector (LOG-ONLY)
|
|
1998
1762
|
_gc_headless_pids() # cb90b058: drop dead PIDs (stale entry can't mask a live agent)
|
|
1999
1763
|
_up = int(time.monotonic() - _spawn_mono)
|
|
2000
1764
|
if relaunched or recycled or ver_recycled or stopped or enforced or reaped or force_killed:
|
|
@@ -2898,6 +2898,13 @@ def meshcode_send(to: Union[str, List[str]], message: Any, in_reply_to: Optional
|
|
|
2898
2898
|
msg_ids[], count}. Cross-mesh '@' is single-recipient only (mixed lists rejected v1).
|
|
2899
2899
|
sensitive=True hides from exports. encrypted=True for secrets (AES-256-GCM, cross-mesh only).
|
|
2900
2900
|
type= optional typed-catalog tag; soft-validates, never refuses."""
|
|
2901
|
+
# mode-2 soft-fail (R2-3): a malformed in_reply_to (not a valid uuid) would hit
|
|
2902
|
+
# the uuid cast at the RPC boundary and reject the WHOLE message. Drop it so the
|
|
2903
|
+
# message still sends (pairs with mig463's nonexistent-but-valid-uuid soft-fail).
|
|
2904
|
+
if in_reply_to is not None and not re.fullmatch(
|
|
2905
|
+
r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}",
|
|
2906
|
+
str(in_reply_to)):
|
|
2907
|
+
in_reply_to = None
|
|
2901
2908
|
# ── Normalize `to` into a recipients list ──────────────────────────────
|
|
2902
2909
|
if isinstance(to, list):
|
|
2903
2910
|
recipients: List[str] = [str(t).strip() for t in to if str(t).strip()]
|
|
@@ -3514,6 +3521,58 @@ def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
|
|
|
3514
3521
|
return None
|
|
3515
3522
|
|
|
3516
3523
|
|
|
3524
|
+
def _pending_filter(tasks: List[Dict[str, Any]]) -> Optional[List[Dict[str, str]]]:
|
|
3525
|
+
"""The _get_pending_tasks_summary filter, applied to a task list we already
|
|
3526
|
+
have (e.g. from mc_wait_poll) so we don't re-fetch. Identical output shape."""
|
|
3527
|
+
pending = [
|
|
3528
|
+
{"id": t["id"][:8], "title": t["title"][:80], "priority": t.get("priority", "normal"), "status": t["status"]}
|
|
3529
|
+
for t in tasks
|
|
3530
|
+
if t.get("status") in ("open", "in_progress")
|
|
3531
|
+
and (t.get("claimed_by") == AGENT_NAME or t.get("assignee") == AGENT_NAME)
|
|
3532
|
+
]
|
|
3533
|
+
if _is_leader_agent():
|
|
3534
|
+
pending.extend([
|
|
3535
|
+
{"id": t["id"][:8], "title": t["title"][:80], "priority": t.get("priority", "normal"), "status": t["status"]}
|
|
3536
|
+
for t in tasks
|
|
3537
|
+
if t.get("status") == "open" and t.get("assignee") == "*" and not t.get("claimed_by")
|
|
3538
|
+
])
|
|
3539
|
+
_PRIORITY_ORDER = {"urgent": 0, "high": 1, "normal": 2, "low": 3}
|
|
3540
|
+
pending.sort(key=lambda t: _PRIORITY_ORDER.get(t.get("priority", "normal"), 2))
|
|
3541
|
+
return pending if pending else None
|
|
3542
|
+
|
|
3543
|
+
|
|
3544
|
+
def _wait_poll_or_legacy() -> Dict[str, Any]:
|
|
3545
|
+
"""R2-1: ONE mc_wait_poll replaces _check_recycle_request + _check_stop_request
|
|
3546
|
+
+ _get_pending_tasks_summary (3 RPCs + 3 api_key resolves -> 1). Semantics are
|
|
3547
|
+
identical (recycle = mig446 guard, stop = desired_state, tasks via the same
|
|
3548
|
+
filter). Falls back to the 3 legacy RPCs on ANY error so a mixed old-SDK/new-RPC
|
|
3549
|
+
fleet never breaks. recycle-first order is preserved by the caller."""
|
|
3550
|
+
try:
|
|
3551
|
+
api_key = _get_api_key()
|
|
3552
|
+
if api_key:
|
|
3553
|
+
resp = be.sb_rpc("mc_wait_poll", {
|
|
3554
|
+
"p_api_key": api_key,
|
|
3555
|
+
"p_project_id": _PROJECT_ID,
|
|
3556
|
+
"p_agent_name": AGENT_NAME,
|
|
3557
|
+
})
|
|
3558
|
+
if isinstance(resp, dict) and resp.get("ok"):
|
|
3559
|
+
return {
|
|
3560
|
+
"recycle": bool(resp.get("recycle")),
|
|
3561
|
+
"stop": bool(resp.get("stop")),
|
|
3562
|
+
"tasks": _pending_filter(resp.get("tasks") or []),
|
|
3563
|
+
"_via": "mc_wait_poll",
|
|
3564
|
+
}
|
|
3565
|
+
except Exception as e:
|
|
3566
|
+
log.debug(f"[meshcode] mc_wait_poll failed; legacy fallback: {e}")
|
|
3567
|
+
# Legacy fallback — the 3 separate RPCs (old behavior, never breaks).
|
|
3568
|
+
return {
|
|
3569
|
+
"recycle": _check_recycle_request(),
|
|
3570
|
+
"stop": _check_stop_request(),
|
|
3571
|
+
"tasks": _get_pending_tasks_summary(),
|
|
3572
|
+
"_via": "legacy",
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
|
|
3517
3576
|
def _check_recycle_request() -> bool:
|
|
3518
3577
|
"""Server-authorized recycle (task 548c863e, mig 364).
|
|
3519
3578
|
|
|
@@ -3598,7 +3657,9 @@ def _try_auto_claim_self_assigned_tasks(max_claims: int = 20) -> List[Dict[str,
|
|
|
3598
3657
|
for target in candidates[:max_claims]:
|
|
3599
3658
|
claim_result = be.sb_rpc("mc_task_claim", {
|
|
3600
3659
|
"p_api_key": api_key,
|
|
3660
|
+
"p_project_id": _PROJECT_ID,
|
|
3601
3661
|
"p_task_id": target["id"],
|
|
3662
|
+
"p_claiming_agent": AGENT_NAME,
|
|
3602
3663
|
})
|
|
3603
3664
|
if isinstance(claim_result, dict) and claim_result.get("ok"):
|
|
3604
3665
|
log.info(f"[meshcode] auto-claimed self-assigned task: {target['title'][:60]}")
|
|
@@ -3643,7 +3704,9 @@ def _try_auto_claim_task() -> Optional[Dict[str, str]]:
|
|
|
3643
3704
|
# Claim it
|
|
3644
3705
|
claim_result = be.sb_rpc("mc_task_claim", {
|
|
3645
3706
|
"p_api_key": api_key,
|
|
3707
|
+
"p_project_id": _PROJECT_ID,
|
|
3646
3708
|
"p_task_id": target["id"],
|
|
3709
|
+
"p_claiming_agent": AGENT_NAME,
|
|
3647
3710
|
})
|
|
3648
3711
|
if isinstance(claim_result, dict) and claim_result.get("ok"):
|
|
3649
3712
|
log.info(f"[meshcode] auto-claimed task: {target['title'][:60]}")
|
|
@@ -3758,7 +3821,9 @@ def _try_auto_claim_open_by_heuristic(max_load: int = 2) -> Optional[Dict[str, A
|
|
|
3758
3821
|
score, target = scored[0]
|
|
3759
3822
|
claim_result = be.sb_rpc("mc_task_claim", {
|
|
3760
3823
|
"p_api_key": api_key,
|
|
3824
|
+
"p_project_id": _PROJECT_ID,
|
|
3761
3825
|
"p_task_id": target["id"],
|
|
3826
|
+
"p_claiming_agent": AGENT_NAME,
|
|
3762
3827
|
})
|
|
3763
3828
|
if not (isinstance(claim_result, dict) and claim_result.get("ok")):
|
|
3764
3829
|
log.debug(f"[meshcode] heuristic claim lost race on {target['id'][:8]}: {claim_result}")
|
|
@@ -4196,11 +4261,11 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
|
|
|
4196
4261
|
"count": len(pending_tasks),
|
|
4197
4262
|
"you_are_autonomous": _get_autonomous_mode(),
|
|
4198
4263
|
"task_pull_active": True,
|
|
4199
|
-
"hint": "Work
|
|
4264
|
+
"hint": "Work highest-priority task; wait surfaces the next one.",
|
|
4200
4265
|
}
|
|
4201
4266
|
if _auto_started:
|
|
4202
4267
|
out["auto_started_task"] = _auto_started
|
|
4203
|
-
out["hint"] = f"Task '{_auto_started['title'][:60]}' auto-started
|
|
4268
|
+
out["hint"] = f"Task '{_auto_started['title'][:60]}' auto-started — work it, then task_complete."
|
|
4204
4269
|
if _wait_entry_auto_claimed:
|
|
4205
4270
|
out["auto_claimed_at_wait_entry"] = _wait_entry_auto_claimed
|
|
4206
4271
|
return out
|
|
@@ -4223,6 +4288,11 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
|
|
|
4223
4288
|
pass # All messages already seen — fall through to wait loop
|
|
4224
4289
|
else:
|
|
4225
4290
|
split = _split_messages(deduped)
|
|
4291
|
+
# L5-P3 (task 96d5f2e9): honor include_acks on the refused path too —
|
|
4292
|
+
# this was the only delivery path that leaked acks into the payload,
|
|
4293
|
+
# billing a turn's worth of "X read your message" noise to every agent.
|
|
4294
|
+
if not include_acks:
|
|
4295
|
+
split["acks"] = []
|
|
4226
4296
|
# Only refuse for real messages — ack-only batches should not block wait
|
|
4227
4297
|
if split["messages"] or split["done_signals"]:
|
|
4228
4298
|
resp = {
|
|
@@ -4299,7 +4369,8 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
|
|
|
4299
4369
|
# us fresh. Checked BEFORE pending_tasks: the fresh session
|
|
4300
4370
|
# re-attacks any open tasks on boot, so an outstanding backlog
|
|
4301
4371
|
# must not pin a stale, context-heavy session alive.
|
|
4302
|
-
|
|
4372
|
+
_wp = _wait_poll_or_legacy() # R2-1: 3 RPCs -> 1 (legacy fallback inside)
|
|
4373
|
+
if _wp["recycle"]:
|
|
4303
4374
|
_set_state("sleeping", "recycle")
|
|
4304
4375
|
result["must_exit"] = True
|
|
4305
4376
|
result["exit_reason"] = (
|
|
@@ -4312,7 +4383,7 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
|
|
|
4312
4383
|
# toggled this agent OFF (desired_state='stopped'). Exit CLEAN — sticky
|
|
4313
4384
|
# 'stopped' means hostd will NOT respawn us (unlike recycle). Checked
|
|
4314
4385
|
# right after recycle so a power-off wins over auto-claim/pending-tasks.
|
|
4315
|
-
if
|
|
4386
|
+
if _wp["stop"]:
|
|
4316
4387
|
_set_state("sleeping", "stopped")
|
|
4317
4388
|
result["must_exit"] = True
|
|
4318
4389
|
result["exit_reason"] = (
|
|
@@ -4322,8 +4393,8 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
|
|
|
4322
4393
|
result["stopped"] = True
|
|
4323
4394
|
break
|
|
4324
4395
|
|
|
4325
|
-
#
|
|
4326
|
-
pending_tasks =
|
|
4396
|
+
# New tasks that appeared while we waited (from the same poll)
|
|
4397
|
+
pending_tasks = _wp["tasks"]
|
|
4327
4398
|
if pending_tasks:
|
|
4328
4399
|
result["pending_tasks"] = pending_tasks
|
|
4329
4400
|
break # Return so agent can work tasks
|
|
@@ -4405,7 +4476,8 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
|
|
|
4405
4476
|
# `you_are_autonomous` is the canonical field per commander memo
|
|
4406
4477
|
# decisions msg 29cae7f9; `autonomous_mode` retained for back-compat.
|
|
4407
4478
|
if isinstance(result, dict):
|
|
4408
|
-
|
|
4479
|
+
# L5-P4: `you_are_autonomous` is canonical (commander memo 29cae7f9);
|
|
4480
|
+
# the duplicate `autonomous_mode` alias was dropped from the envelope.
|
|
4409
4481
|
result["you_are_autonomous"] = _autonomous
|
|
4410
4482
|
return result
|
|
4411
4483
|
finally:
|
|
@@ -6056,6 +6128,19 @@ def meshcode_boot() -> Dict[str, Any]:
|
|
|
6056
6128
|
# Non-fatal: surface that the hints are unavailable but boot succeeds.
|
|
6057
6129
|
resp.setdefault("time_context_available", False)
|
|
6058
6130
|
|
|
6131
|
+
# L5-P4 (task 96d5f2e9): offline agents' mc_agents.task often holds their
|
|
6132
|
+
# FULL registration role text (150+ tokens each), re-billed on every boot.
|
|
6133
|
+
# The peer roster only needs a glanceable line.
|
|
6134
|
+
try:
|
|
6135
|
+
_mesh = resp.get("mesh_status")
|
|
6136
|
+
if isinstance(_mesh, list):
|
|
6137
|
+
for _row in _mesh:
|
|
6138
|
+
_ct = _row.get("current_task") if isinstance(_row, dict) else None
|
|
6139
|
+
if isinstance(_ct, str) and len(_ct) > 120:
|
|
6140
|
+
_row["current_task"] = _ct[:117] + "..."
|
|
6141
|
+
except Exception:
|
|
6142
|
+
pass
|
|
6143
|
+
|
|
6059
6144
|
# c0ab14c1 Phase 2: open-promise time-awareness (what I owe / what I'm
|
|
6060
6145
|
# waiting on, with overdue/elapsed minutes). Soft-fail; only added when
|
|
6061
6146
|
# there's something open.
|
|
@@ -7247,6 +7332,25 @@ def meshcode_recall_search(query: str) -> Dict[str, Any]:
|
|
|
7247
7332
|
query: Plain English search query.
|
|
7248
7333
|
"""
|
|
7249
7334
|
api_key = _get_api_key()
|
|
7335
|
+
# L6 M4 (task 84c426d4): hybrid semantic+FTS recall via the mc-embed edge
|
|
7336
|
+
# function (embeds the query with gte-small, then calls mc_memory_search v2
|
|
7337
|
+
# with p_query_embedding — RRF fusion server-side). Soft-fail to the direct
|
|
7338
|
+
# RPC (FTS+substring arms of the same v2 fn) if the edge fn is unreachable.
|
|
7339
|
+
try:
|
|
7340
|
+
import urllib.request as _url
|
|
7341
|
+
_req = _url.Request(
|
|
7342
|
+
f"{be.SUPABASE_URL}/functions/v1/mc-embed",
|
|
7343
|
+
data=json.dumps({"search": {
|
|
7344
|
+
"api_key": api_key, "agent_name": AGENT_NAME,
|
|
7345
|
+
"query": query, "project_name": PROJECT_NAME, "limit": 20,
|
|
7346
|
+
}}).encode(),
|
|
7347
|
+
headers={"Content-Type": "application/json"}, method="POST")
|
|
7348
|
+
with _url.urlopen(_req, timeout=8) as _resp:
|
|
7349
|
+
_out = json.loads(_resp.read().decode())
|
|
7350
|
+
if isinstance(_out, dict) and _out.get("ok"):
|
|
7351
|
+
return _out
|
|
7352
|
+
except Exception as _e:
|
|
7353
|
+
log.debug(f"mc-embed search fallback to direct RPC: {_e}")
|
|
7250
7354
|
return be.sb_rpc("mc_memory_search", {
|
|
7251
7355
|
"p_api_key": api_key,
|
|
7252
7356
|
"p_agent_name": AGENT_NAME,
|
|
@@ -207,7 +207,40 @@ def _spawn_terminal_macos(cmd: str) -> tuple[bool, str]:
|
|
|
207
207
|
'cd "$HOME" 2>/dev/null || cd /'] # neutral, non-TCC-protected cwd
|
|
208
208
|
if venv_bin:
|
|
209
209
|
lines.append(f'export PATH={shlex.quote(venv_bin)}:"$PATH"')
|
|
210
|
-
|
|
210
|
+
# RECYCLE-KILL-OLD-TERMINAL (task 2094ff3d, Samuel "que se cierre la terminal
|
|
211
|
+
# pasada"). A recycle/stop ends the agent cleanly but macOS Terminal leaves the
|
|
212
|
+
# window open as "[Process completed]" — an orphan. Run the agent WITHOUT `exec`
|
|
213
|
+
# so bash survives its exit and can close THIS window. `cmd` ends in
|
|
214
|
+
# `exec … meshcode run …`; strip the leading `exec ` so the agent runs as a child
|
|
215
|
+
# (it still execvp's claude internally — that becomes the window's foreground proc).
|
|
216
|
+
lines.append('MC_TTY="$(tty 2>/dev/null)"')
|
|
217
|
+
# `cmd` is a compound line (`unset …; export PATH=…; exec <py> -m meshcode run …`),
|
|
218
|
+
# so the `exec` is NOT leading. Drop the LAST `exec ` token (the one before the
|
|
219
|
+
# interpreter) so the agent runs as a CHILD and bash survives to close the window.
|
|
220
|
+
if "exec " in cmd:
|
|
221
|
+
_head, _, _tail = cmd.rpartition("exec ")
|
|
222
|
+
_run = _head + _tail
|
|
223
|
+
else:
|
|
224
|
+
_run = cmd
|
|
225
|
+
lines.append(_run)
|
|
226
|
+
lines.append('MC_RC=$?')
|
|
227
|
+
# Close THIS Terminal window ONLY on a clean exit (recycle/stop => 0). On a CRASH
|
|
228
|
+
# (non-zero) leave it OPEN so the scrollback is available for debugging (commander
|
|
229
|
+
# condition: debugging > clean window). The own-$MC_TTY filter closes ONLY this
|
|
230
|
+
# window — never another agent's. saving no => no "close?" prompt. macOS-only path.
|
|
231
|
+
lines.append('if [ "$MC_RC" = "0" ] && [ -n "$MC_TTY" ]; then')
|
|
232
|
+
lines.append(
|
|
233
|
+
" /usr/bin/osascript"
|
|
234
|
+
" -e 'tell application \"Terminal\"'"
|
|
235
|
+
" -e 'repeat with w in windows'"
|
|
236
|
+
" -e 'try'"
|
|
237
|
+
" -e \"if (tty of selected tab of w) is \\\"$MC_TTY\\\" then\""
|
|
238
|
+
" -e 'close w saving no'"
|
|
239
|
+
" -e 'end if'"
|
|
240
|
+
" -e 'end try'"
|
|
241
|
+
" -e 'end repeat'"
|
|
242
|
+
" -e 'end tell' >/dev/null 2>&1")
|
|
243
|
+
lines.append('fi')
|
|
211
244
|
try:
|
|
212
245
|
launch_dir.mkdir(parents=True, exist_ok=True)
|
|
213
246
|
script_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
@@ -1246,23 +1246,6 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
1246
1246
|
result = _sp.run(cmd, **_kwargs)
|
|
1247
1247
|
sys.exit(result.returncode)
|
|
1248
1248
|
else:
|
|
1249
|
-
# FOCUS-1 ghost reap (back-2, 09fca8fe): stamp THIS pid so hostd can correlate
|
|
1250
|
-
# the agent's visible session for dead-MCP / ghost detection + reap-by-PID. execvp
|
|
1251
|
-
# PRESERVES the pid, so this file's pid == the claude session pid exactly. Without a
|
|
1252
|
-
# recorded visible pid, hostd's reap/enforce/close-old can't anchor a visible agent
|
|
1253
|
-
# (the root gap behind orphaned recycle windows + connected-but-invisible ghosts).
|
|
1254
|
-
# Record {pid, cwd}: cwd is the exact launch dir (os.chdir(ws) above, or the
|
|
1255
|
-
# --repo dir) so hostd's kill-time cwd-guard can compare the LIVE pid's cwd to
|
|
1256
|
-
# THIS recorded cwd — anti PID-reuse mis-kill (a reused pid running a different
|
|
1257
|
-
# agent's claude has a different cwd → no match → never killed).
|
|
1258
|
-
try:
|
|
1259
|
-
_pdir = Path.home() / ".meshcode" / "pids"
|
|
1260
|
-
_pdir.mkdir(parents=True, exist_ok=True)
|
|
1261
|
-
_safe = f"{resolved_project}__{agent}".replace("/", "_").replace("\\", "_").replace(" ", "_")
|
|
1262
|
-
(_pdir / f"{_safe}.pid").write_text(
|
|
1263
|
-
json.dumps({"pid": os.getpid(), "cwd": os.getcwd()}), encoding="utf-8")
|
|
1264
|
-
except Exception:
|
|
1265
|
-
pass
|
|
1266
1249
|
# Unix: replace this process with the editor
|
|
1267
1250
|
os.execvp(cmd[0], cmd)
|
|
1268
1251
|
except FileNotFoundError:
|
|
@@ -2,14 +2,10 @@ README.md
|
|
|
2
2
|
pyproject.toml
|
|
3
3
|
meshcode/__init__.py
|
|
4
4
|
meshcode/__main__.py
|
|
5
|
-
meshcode/_session_handoff_template 2.py
|
|
6
|
-
meshcode/_session_handoff_template 3.py
|
|
7
5
|
meshcode/_session_handoff_template.py
|
|
8
6
|
meshcode/_stop_hook_template.py
|
|
9
7
|
meshcode/ascii_art.py
|
|
10
8
|
meshcode/atomic_push.py
|
|
11
|
-
meshcode/claude_update 2.py
|
|
12
|
-
meshcode/claude_update 3.py
|
|
13
9
|
meshcode/claude_update.py
|
|
14
10
|
meshcode/cli.py
|
|
15
11
|
meshcode/comms_v4.py
|
|
@@ -19,7 +15,6 @@ meshcode/date_parse.py
|
|
|
19
15
|
meshcode/doctor.py
|
|
20
16
|
meshcode/error_hints.py
|
|
21
17
|
meshcode/exceptions.py
|
|
22
|
-
meshcode/hostd 2.py
|
|
23
18
|
meshcode/hostd.py
|
|
24
19
|
meshcode/invites.py
|
|
25
20
|
meshcode/launcher.py
|
|
@@ -34,7 +29,6 @@ meshcode/secrets.py
|
|
|
34
29
|
meshcode/self_update.py
|
|
35
30
|
meshcode/setup_clients.py
|
|
36
31
|
meshcode/supervisor.py
|
|
37
|
-
meshcode/up 2.py
|
|
38
32
|
meshcode/up.py
|
|
39
33
|
meshcode/upload.py
|
|
40
34
|
meshcode.egg-info/PKG-INFO
|
|
@@ -58,4 +52,40 @@ meshcode/meshcode_mcp/test_prefs_claude_version.py
|
|
|
58
52
|
meshcode/meshcode_mcp/test_realtime.py
|
|
59
53
|
meshcode/meshcode_mcp/test_server_wrapper.py
|
|
60
54
|
meshcode/scripts/check_secrets.py
|
|
61
|
-
meshcode/scripts/race_rate_harness.py
|
|
55
|
+
meshcode/scripts/race_rate_harness.py
|
|
56
|
+
tests/test_auto_update_hardening.py
|
|
57
|
+
tests/test_autonomous_closegap_1.py
|
|
58
|
+
tests/test_autonomous_closegap_2.py
|
|
59
|
+
tests/test_autonomous_closegap_3.py
|
|
60
|
+
tests/test_autonomous_prompt_inject.py
|
|
61
|
+
tests/test_boot_bug_regression.py
|
|
62
|
+
tests/test_color_truecolor.py
|
|
63
|
+
tests/test_core.py
|
|
64
|
+
tests/test_cross_agent_messaging.py
|
|
65
|
+
tests/test_date_parse.py
|
|
66
|
+
tests/test_doctor.py
|
|
67
|
+
tests/test_epistemic_v1_python_sdk.py
|
|
68
|
+
tests/test_epistemic_v1_stop_conditions.py
|
|
69
|
+
tests/test_esc_deaf_state.py
|
|
70
|
+
tests/test_exceptions.py
|
|
71
|
+
tests/test_file_upload.py
|
|
72
|
+
tests/test_init_device_code.py
|
|
73
|
+
tests/test_install_guard.py
|
|
74
|
+
tests/test_lease_sigterm_release.py
|
|
75
|
+
tests/test_mark_read_batch.py
|
|
76
|
+
tests/test_marketplace_ratings.py
|
|
77
|
+
tests/test_migration_integrity.py
|
|
78
|
+
tests/test_realtime_event_freshness.py
|
|
79
|
+
tests/test_rls_cross_tenant.py
|
|
80
|
+
tests/test_rpc_grants.py
|
|
81
|
+
tests/test_rpc_migrations.py
|
|
82
|
+
tests/test_run_agent_dry_run.py
|
|
83
|
+
tests/test_run_agent_no_server_import.py
|
|
84
|
+
tests/test_security_regressions.py
|
|
85
|
+
tests/test_self_update_user_site.py
|
|
86
|
+
tests/test_sentinel.py
|
|
87
|
+
tests/test_setup_path.py
|
|
88
|
+
tests/test_sleep_signals.py
|
|
89
|
+
tests/test_status_enum_coverage.py
|
|
90
|
+
tests/test_stay_on_loop_hook.py
|
|
91
|
+
tests/test_wait_open_tasks_contradiction.py
|