meshcode 2.11.125__tar.gz → 2.11.126__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.125 → meshcode-2.11.126}/PKG-INFO +1 -1
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/__init__.py +1 -1
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/hostd.py +110 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.125 → meshcode-2.11.126}/pyproject.toml +1 -1
- {meshcode-2.11.125 → meshcode-2.11.126}/README.md +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/__main__.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/cli.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/compat.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/daemon.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/doctor.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/hooks/__init__.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/hooks/repo_path_lock.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/invites.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/launcher.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/swarm.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_swarm.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/preferences.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/secrets.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/self_update.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/up.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/upload.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/setup.cfg +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_core.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_doctor.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_swarm_events.py +0 -0
- {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -209,6 +209,34 @@ SPAWN_MIN_INTERVAL_SEC = _env_int("MESHCODE_SPAWN_MIN_INTERVAL_SEC", 45, 5) #
|
|
|
209
209
|
SPAWN_BURST_CAP = _env_int("MESHCODE_SPAWN_BURST_CAP", 5, 2) # max spawns ...
|
|
210
210
|
SPAWN_BURST_WINDOW_SEC = _env_int("MESHCODE_SPAWN_BURST_WINDOW_SEC", 600, 60) # ... within this rolling window before tripping
|
|
211
211
|
|
|
212
|
+
# ------------------------------------------------------------------
|
|
213
|
+
# Plain-respawn guards (Samuel P0 2026-06-10, terminal storm): the burst
|
|
214
|
+
# breaker above NEVER saw the overnight storm — hostd respawned zylo-bemate
|
|
215
|
+
# every ~16 min for 13 h (125 terminals): slow boots under load kept resetting
|
|
216
|
+
# the server-side respawn cap (the agent heartbeats briefly, count -> 0), the
|
|
217
|
+
# ~16 min cadence stays under 5 spawns/10 min, and every respawn STACKED a new
|
|
218
|
+
# window on top of the still-alive previous session, adding load that made the
|
|
219
|
+
# next boot even slower. Two guards on the plain-respawn path in _do_respawns:
|
|
220
|
+
#
|
|
221
|
+
# LIVE-SESSION GUARD — never open a second terminal while the previous
|
|
222
|
+
# session's process is still alive: HOLD (spawn nothing), hard-kill the stuck
|
|
223
|
+
# session after _RESPAWN_STUCK_KILL_S, and relaunch only once it is fully
|
|
224
|
+
# gone. An explicit Start (fresh spawned_at) kills the old session at once —
|
|
225
|
+
# a restart the human asked for must first fully close the old terminal.
|
|
226
|
+
#
|
|
227
|
+
# CONVERGENCE GUARD — mirror of the recycle guard (451d33a0) for plain
|
|
228
|
+
# respawns, keyed on time BETWEEN consecutive respawns so it catches ANY
|
|
229
|
+
# cadence: _RESPAWN_CONVERGE_MAX consecutive respawns each started less than
|
|
230
|
+
# _RESPAWN_CONVERGE_LIFETIME_S after the previous one -> BLOCK further
|
|
231
|
+
# respawns for _RESPAWN_CONVERGE_BLOCK_TTL_S + resolve the pending launch as
|
|
232
|
+
# failed (FE toast). An explicit Start clears the block.
|
|
233
|
+
# ------------------------------------------------------------------
|
|
234
|
+
_RESPAWN_STUCK_KILL_S = _env_int("MESHCODE_RESPAWN_STUCK_KILL_SEC", 600, 60) # kill a stale-but-alive session after this hold
|
|
235
|
+
_RESPAWN_CONVERGE_MAX = _env_int("MESHCODE_RESPAWN_CONVERGE_MAX", 3, 2) # consecutive short-lived respawns before blocking
|
|
236
|
+
_RESPAWN_CONVERGE_LIFETIME_S = _env_int("MESHCODE_RESPAWN_CONVERGE_LIFETIME_SEC", 1800, 120) # "short-lived" = next respawn needed within this
|
|
237
|
+
_RESPAWN_CONVERGE_BLOCK_TTL_S = _env_int("MESHCODE_RESPAWN_CONVERGE_BLOCK_TTL_SEC", 21600, 600) # block duration before one clean retry
|
|
238
|
+
_RESPAWN_FRESH_CLICK_S = _env_int("MESHCODE_RESPAWN_FRESH_CLICK_SEC", 90, 30) # spawned_age_s under this = explicit Start click
|
|
239
|
+
|
|
212
240
|
|
|
213
241
|
def _log(msg: str) -> None:
|
|
214
242
|
line = f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}"
|
|
@@ -535,6 +563,88 @@ def _do_respawns(api_key: str, host_id: str) -> int:
|
|
|
535
563
|
_rrall[_target] = _rr
|
|
536
564
|
_rst["recyrespawn"] = _rrall
|
|
537
565
|
_save_state(_rst)
|
|
566
|
+
# ---- plain-respawn guards (Samuel P0 2026-06-10, terminal storm; see
|
|
567
|
+
# ---- constants block above for the full incident rationale) ----
|
|
568
|
+
if not _is_recycle:
|
|
569
|
+
_now2 = time.time()
|
|
570
|
+
_st2 = _load_state()
|
|
571
|
+
try:
|
|
572
|
+
_fresh_click = float(c.get("spawned_age_s")) < _RESPAWN_FRESH_CLICK_S
|
|
573
|
+
except (TypeError, ValueError):
|
|
574
|
+
_fresh_click = False
|
|
575
|
+
_cv_all = dict(_st2.get("respawn_conv") or {})
|
|
576
|
+
_cv = dict(_cv_all.get(_target) or {})
|
|
577
|
+
if _fresh_click and (_cv or _target in (_st2.get("respawn_hold") or {})):
|
|
578
|
+
# explicit Start (fresh spawned_at) = human permission: clear guard state
|
|
579
|
+
_cv = {}
|
|
580
|
+
_cv_all.pop(_target, None)
|
|
581
|
+
_hh = dict(_st2.get("respawn_hold") or {})
|
|
582
|
+
_hh.pop(_target, None)
|
|
583
|
+
_st2["respawn_conv"] = _cv_all
|
|
584
|
+
_st2["respawn_hold"] = _hh
|
|
585
|
+
_save_state(_st2)
|
|
586
|
+
_log(f"RESPAWN-GUARD {_target}: explicit Start — cleared hold/convergence state")
|
|
587
|
+
elif _cv.get("blocked_ts"):
|
|
588
|
+
if (_now2 - float(_cv["blocked_ts"])) < _RESPAWN_CONVERGE_BLOCK_TTL_S:
|
|
589
|
+
_log(f"SKIP respawn {_target}: BLOCKED (respawn_no_converge, "
|
|
590
|
+
f"{_cv.get('count')} short-lived respawns) — holding until TTL or a manual Start")
|
|
591
|
+
continue
|
|
592
|
+
_cv.pop("blocked_ts", None)
|
|
593
|
+
_cv["count"] = 0 # TTL elapsed -> one clean retry; re-blocks if it storms again
|
|
594
|
+
# LIVE-SESSION GUARD: never stack a second terminal on a still-alive session.
|
|
595
|
+
_live = [p for p in _discover_agent_pids(_target) if _pid_alive(p)]
|
|
596
|
+
if _live:
|
|
597
|
+
_hold_all = dict(_st2.get("respawn_hold") or {})
|
|
598
|
+
_hold = dict(_hold_all.get(_target) or {})
|
|
599
|
+
_first = float(_hold.get("first_ts") or _now2)
|
|
600
|
+
if _fresh_click or (_now2 - _first) >= _RESPAWN_STUCK_KILL_S:
|
|
601
|
+
_killed = sum(1 for _p in _live if _kill_headless_pid(_target, _p))
|
|
602
|
+
_hold_all.pop(_target, None)
|
|
603
|
+
_log(f"RESPAWN-STUCK-KILL {_target}: closed {_killed}/{len(_live)} stale-but-alive "
|
|
604
|
+
f"session pid(s) {_live} "
|
|
605
|
+
f"({'explicit Start' if _fresh_click else 'stuck > ' + str(_RESPAWN_STUCK_KILL_S) + 's'}) "
|
|
606
|
+
f"— relaunch on next sweep once fully gone")
|
|
607
|
+
else:
|
|
608
|
+
_hold["first_ts"] = _first
|
|
609
|
+
_hold_all[_target] = _hold
|
|
610
|
+
_log(f"RESPAWN-HOLD {_target}: previous session still ALIVE (pids {_live}) — "
|
|
611
|
+
f"NOT opening another terminal; stuck-kill in "
|
|
612
|
+
f"{int(_RESPAWN_STUCK_KILL_S - (_now2 - _first))}s unless it heartbeats")
|
|
613
|
+
_st2["respawn_hold"] = _hold_all
|
|
614
|
+
_save_state(_st2)
|
|
615
|
+
continue
|
|
616
|
+
# previous session fully gone — count this relaunch against convergence
|
|
617
|
+
_hold_all = dict(_st2.get("respawn_hold") or {})
|
|
618
|
+
if _target in _hold_all:
|
|
619
|
+
_hold_all.pop(_target, None)
|
|
620
|
+
_st2["respawn_hold"] = _hold_all
|
|
621
|
+
if _cv.get("last_ts") and (_now2 - float(_cv["last_ts"])) <= _RESPAWN_CONVERGE_LIFETIME_S:
|
|
622
|
+
_cv["count"] = int(_cv.get("count", 0)) + 1
|
|
623
|
+
else:
|
|
624
|
+
_cv = {"count": 1} # converged/idle long enough -> fresh count
|
|
625
|
+
_cv["last_ts"] = _now2
|
|
626
|
+
if _cv["count"] >= _RESPAWN_CONVERGE_MAX:
|
|
627
|
+
_cv["blocked_ts"] = _now2
|
|
628
|
+
_cv_all[_target] = _cv
|
|
629
|
+
_st2["respawn_conv"] = _cv_all
|
|
630
|
+
_save_state(_st2)
|
|
631
|
+
_log(f"ALERT {_target}: respawn NOT CONVERGING — {_cv['count']} consecutive respawns "
|
|
632
|
+
f"each <{_RESPAWN_CONVERGE_LIFETIME_S}s apart; BLOCKING respawns for "
|
|
633
|
+
f"{_RESPAWN_CONVERGE_BLOCK_TTL_S}s (a manual Start clears the block). "
|
|
634
|
+
f"[respawn_blocked_reason=respawn_no_converge]")
|
|
635
|
+
try: # stop the dashboard's eternal 'launching…' spinner + actionable toast
|
|
636
|
+
_rpc("mc_resolve_launch", {
|
|
637
|
+
"p_api_key": api_key, "p_project_id": c.get("project_id"), "p_agent": agent,
|
|
638
|
+
"p_status": "failed", "p_reason": "respawn_no_converge",
|
|
639
|
+
"p_detail": f"agent kept dying or booting too slowly {_cv['count']}x in a row — "
|
|
640
|
+
f"respawns paused to protect your machine and tokens; press Start "
|
|
641
|
+
f"to retry when ready"})
|
|
642
|
+
except Exception:
|
|
643
|
+
pass
|
|
644
|
+
continue
|
|
645
|
+
_cv_all[_target] = _cv
|
|
646
|
+
_st2["respawn_conv"] = _cv_all
|
|
647
|
+
_save_state(_st2)
|
|
538
648
|
_ok, _burst, _why = _spawn_rate_ok(_target)
|
|
539
649
|
if not _ok:
|
|
540
650
|
_log(f"SKIP {'recycle-' if _is_recycle else ''}respawn {_target}: rate-limited ({_why})")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|