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.
Files changed (96) hide show
  1. {meshcode-2.11.125 → meshcode-2.11.126}/PKG-INFO +1 -1
  2. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/__init__.py +1 -1
  3. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/hostd.py +110 -0
  4. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/PKG-INFO +1 -1
  5. {meshcode-2.11.125 → meshcode-2.11.126}/pyproject.toml +1 -1
  6. {meshcode-2.11.125 → meshcode-2.11.126}/README.md +0 -0
  7. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/__main__.py +0 -0
  8. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/_session_handoff_template.py +0 -0
  9. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/_stop_hook_template.py +0 -0
  10. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/ascii_art.py +0 -0
  11. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/atomic_push.py +0 -0
  12. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/claude_update.py +0 -0
  13. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/cli.py +0 -0
  14. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/comms_v4.py +0 -0
  15. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/compat.py +0 -0
  16. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/daemon.py +0 -0
  17. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/date_parse.py +0 -0
  18. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/doctor.py +0 -0
  19. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/error_hints.py +0 -0
  20. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/exceptions.py +0 -0
  21. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/hooks/__init__.py +0 -0
  22. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/hooks/repo_path_lock.py +0 -0
  23. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/invites.py +0 -0
  24. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/launcher.py +0 -0
  25. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/launcher_install.py +0 -0
  26. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/__init__.py +0 -0
  27. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/__main__.py +0 -0
  28. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/backend.py +0 -0
  29. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/realtime.py +0 -0
  30. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/server.py +0 -0
  31. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  32. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/swarm.py +0 -0
  33. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_backend.py +0 -0
  34. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  35. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  36. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  37. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  38. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  39. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/meshcode_mcp/test_swarm.py +0 -0
  40. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/preferences.py +0 -0
  41. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/protocol_handler.py +0 -0
  42. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/protocol_v2.py +0 -0
  43. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/quickstart.py +0 -0
  44. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/rpc_allowlist.py +0 -0
  45. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/run_agent.py +0 -0
  46. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/scripts/check_secrets.py +0 -0
  47. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/scripts/race_rate_harness.py +0 -0
  48. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/secrets.py +0 -0
  49. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/self_update.py +0 -0
  50. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/setup_clients.py +0 -0
  51. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/supervisor.py +0 -0
  52. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/up.py +0 -0
  53. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode/upload.py +0 -0
  54. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/SOURCES.txt +0 -0
  55. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/dependency_links.txt +0 -0
  56. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/entry_points.txt +0 -0
  57. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/requires.txt +0 -0
  58. {meshcode-2.11.125 → meshcode-2.11.126}/meshcode.egg-info/top_level.txt +0 -0
  59. {meshcode-2.11.125 → meshcode-2.11.126}/setup.cfg +0 -0
  60. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_auto_update_hardening.py +0 -0
  61. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_autonomous_closegap_1.py +0 -0
  62. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_autonomous_closegap_2.py +0 -0
  63. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_autonomous_closegap_3.py +0 -0
  64. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_autonomous_prompt_inject.py +0 -0
  65. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_boot_bug_regression.py +0 -0
  66. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_color_truecolor.py +0 -0
  67. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_core.py +0 -0
  68. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_cross_agent_messaging.py +0 -0
  69. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_date_parse.py +0 -0
  70. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_doctor.py +0 -0
  71. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_epistemic_v1_python_sdk.py +0 -0
  72. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  73. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_esc_deaf_state.py +0 -0
  74. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_exceptions.py +0 -0
  75. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_file_upload.py +0 -0
  76. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_init_device_code.py +0 -0
  77. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_install_guard.py +0 -0
  78. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_lease_sigterm_release.py +0 -0
  79. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_mark_read_batch.py +0 -0
  80. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_marketplace_ratings.py +0 -0
  81. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_migration_integrity.py +0 -0
  82. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_realtime_event_freshness.py +0 -0
  83. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_rls_cross_tenant.py +0 -0
  84. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_rpc_grants.py +0 -0
  85. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_rpc_migrations.py +0 -0
  86. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_run_agent_dry_run.py +0 -0
  87. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_run_agent_no_server_import.py +0 -0
  88. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_security_regressions.py +0 -0
  89. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_self_update_user_site.py +0 -0
  90. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_sentinel.py +0 -0
  91. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_setup_path.py +0 -0
  92. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_sleep_signals.py +0 -0
  93. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_status_enum_coverage.py +0 -0
  94. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_stay_on_loop_hook.py +0 -0
  95. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_swarm_events.py +0 -0
  96. {meshcode-2.11.125 → meshcode-2.11.126}/tests/test_wait_open_tasks_contradiction.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.125
3
+ Version: 2.11.126
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -1,5 +1,5 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.11.125"
2
+ __version__ = "2.11.126"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -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})")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.125
3
+ Version: 2.11.126
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.11.125"
7
+ version = "2.11.126"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes
File without changes
File without changes