meshcode 2.11.93__tar.gz → 2.11.96__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 (91) hide show
  1. {meshcode-2.11.93 → meshcode-2.11.96}/PKG-INFO +1 -1
  2. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/__init__.py +1 -1
  3. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/hostd.py +35 -3
  4. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/server.py +81 -0
  5. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/PKG-INFO +1 -1
  6. {meshcode-2.11.93 → meshcode-2.11.96}/pyproject.toml +1 -1
  7. {meshcode-2.11.93 → meshcode-2.11.96}/README.md +0 -0
  8. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/__main__.py +0 -0
  9. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/_session_handoff_template.py +0 -0
  10. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/_stop_hook_template.py +0 -0
  11. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/ascii_art.py +0 -0
  12. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/atomic_push.py +0 -0
  13. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/claude_update.py +0 -0
  14. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/cli.py +0 -0
  15. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/comms_v4.py +0 -0
  16. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/compat.py +0 -0
  17. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/daemon.py +0 -0
  18. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/date_parse.py +0 -0
  19. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/doctor.py +0 -0
  20. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/error_hints.py +0 -0
  21. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/exceptions.py +0 -0
  22. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/invites.py +0 -0
  23. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/launcher.py +0 -0
  24. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/launcher_install.py +0 -0
  25. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/__init__.py +0 -0
  26. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/__main__.py +0 -0
  27. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/backend.py +0 -0
  28. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/realtime.py +0 -0
  29. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  30. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_backend.py +0 -0
  31. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  32. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  33. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  34. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  35. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  36. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/preferences.py +0 -0
  37. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/protocol_handler.py +0 -0
  38. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/protocol_v2.py +0 -0
  39. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/quickstart.py +0 -0
  40. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/rpc_allowlist.py +0 -0
  41. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/run_agent.py +0 -0
  42. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/scripts/check_secrets.py +0 -0
  43. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/scripts/race_rate_harness.py +0 -0
  44. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/secrets.py +0 -0
  45. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/self_update.py +0 -0
  46. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/setup_clients.py +0 -0
  47. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/supervisor.py +0 -0
  48. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/up.py +0 -0
  49. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode/upload.py +0 -0
  50. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/SOURCES.txt +0 -0
  51. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/dependency_links.txt +0 -0
  52. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/entry_points.txt +0 -0
  53. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/requires.txt +0 -0
  54. {meshcode-2.11.93 → meshcode-2.11.96}/meshcode.egg-info/top_level.txt +0 -0
  55. {meshcode-2.11.93 → meshcode-2.11.96}/setup.cfg +0 -0
  56. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_auto_update_hardening.py +0 -0
  57. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_autonomous_closegap_1.py +0 -0
  58. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_autonomous_closegap_2.py +0 -0
  59. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_autonomous_closegap_3.py +0 -0
  60. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_autonomous_prompt_inject.py +0 -0
  61. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_boot_bug_regression.py +0 -0
  62. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_color_truecolor.py +0 -0
  63. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_core.py +0 -0
  64. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_cross_agent_messaging.py +0 -0
  65. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_date_parse.py +0 -0
  66. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_doctor.py +0 -0
  67. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_epistemic_v1_python_sdk.py +0 -0
  68. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  69. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_esc_deaf_state.py +0 -0
  70. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_exceptions.py +0 -0
  71. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_file_upload.py +0 -0
  72. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_init_device_code.py +0 -0
  73. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_install_guard.py +0 -0
  74. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_lease_sigterm_release.py +0 -0
  75. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_mark_read_batch.py +0 -0
  76. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_marketplace_ratings.py +0 -0
  77. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_migration_integrity.py +0 -0
  78. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_realtime_event_freshness.py +0 -0
  79. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_rls_cross_tenant.py +0 -0
  80. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_rpc_grants.py +0 -0
  81. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_rpc_migrations.py +0 -0
  82. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_run_agent_dry_run.py +0 -0
  83. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_run_agent_no_server_import.py +0 -0
  84. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_security_regressions.py +0 -0
  85. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_self_update_user_site.py +0 -0
  86. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_sentinel.py +0 -0
  87. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_setup_path.py +0 -0
  88. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_sleep_signals.py +0 -0
  89. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_status_enum_coverage.py +0 -0
  90. {meshcode-2.11.93 → meshcode-2.11.96}/tests/test_stay_on_loop_hook.py +0 -0
  91. {meshcode-2.11.93 → meshcode-2.11.96}/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.93
3
+ Version: 2.11.96
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.93"
2
+ __version__ = "2.11.96"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -446,9 +446,15 @@ def _do_respawns(api_key: str, host_id: str) -> int:
446
446
  # PROMPTLY (the RPC returned it at a 15s stale gate, not STALE_SECONDS) and recorded as a
447
447
  # RECYCLE (mc_record_recycle), NEVER against the crash respawn cap.
448
448
  _is_recycle = bool(c.get("recycle_fast"))
449
+ # 798dd1bd: on-demand recycle with recycle_visible -> reopen a FRESH VISIBLE window
450
+ # (Samuel's "terminals reopen fresh"); else PRESERVE the agent's headless state
451
+ # (don't silently un-headless a headless fleet — backend2 A2). One-shot: mc_record_recycle
452
+ # clears recycle_visible so subsequent respawns use the normal headless flag.
453
+ _visible = _is_recycle and bool(c.get("recycle_visible"))
454
+ _hl = False if _visible else bool(c.get("headless"))
449
455
  _log(f"{'RECYCLE-RESPAWN' if _is_recycle else 'RESPAWN'} {proj}/{agent} "
450
- f"(stale {c.get('heartbeat_age_s')}s, count={c.get('respawn_count')})")
451
- if _spawn_agent(proj, agent, headless=bool(c.get("headless"))):
456
+ f"({'VISIBLE ' if _visible else ''}stale {c.get('heartbeat_age_s')}s, count={c.get('respawn_count')})")
457
+ if _spawn_agent(proj, agent, headless=_hl):
452
458
  if _is_recycle:
453
459
  _rpc("mc_record_recycle",
454
460
  {"p_api_key": api_key, "p_project_id": c.get("project_id"), "p_agent_name": agent})
@@ -515,13 +521,38 @@ def _record_headless_pid(target: str, pid: int) -> None:
515
521
  try:
516
522
  st = _load_state()
517
523
  pids = st.get("headless_pids") or {}
518
- pids[target] = pid
524
+ pids[target] = pid # overwrite any stale entry for this target (refresh on respawn)
519
525
  st["headless_pids"] = pids
520
526
  _save_state(st)
521
527
  except Exception:
522
528
  pass
523
529
 
524
530
 
531
+ def _gc_headless_pids() -> None:
532
+ """GC dead PIDs from headless_pids (cb90b058) so a stale entry can't mask a live agent +
533
+ the recorded PID stays accurate for enforce. Cheap; runs once per sweep. Best-effort."""
534
+ try:
535
+ st = _load_state()
536
+ pids = st.get("headless_pids") or {}
537
+ if not pids:
538
+ return
539
+ live = {}
540
+ for target, pid in pids.items():
541
+ try:
542
+ os.kill(int(pid), 0) # raises ProcessLookupError if the PID is gone
543
+ live[target] = pid
544
+ except (ProcessLookupError, ValueError):
545
+ pass # dead / bad -> drop
546
+ except Exception:
547
+ live[target] = pid # alive (PermissionError) or unknown -> keep (reuse-guard covers kill-time)
548
+ if len(live) != len(pids):
549
+ st["headless_pids"] = live
550
+ _save_state(st)
551
+ _log(f"GC headless_pids: dropped {len(pids)-len(live)} dead PID(s)")
552
+ except Exception:
553
+ pass
554
+
555
+
525
556
  def _pid_cmdline(pid: int) -> str:
526
557
  """Best-effort command line for a pid (to avoid killing a reused PID). '' on failure."""
527
558
  try:
@@ -1206,6 +1237,7 @@ def cmd_hostd(args: list) -> int:
1206
1237
  recycled = _do_recycles(api_key, host_id)
1207
1238
  ver_recycled = _do_version_recycles(api_key, host_id)
1208
1239
  stopped = _do_stops(api_key, host_id)
1240
+ _gc_headless_pids() # cb90b058: drop dead PIDs (stale entry can't mask a live agent)
1209
1241
  _up = int(time.monotonic() - _spawn_mono)
1210
1242
  if relaunched or recycled or ver_recycled or stopped or enforced:
1211
1243
  _log(f"sweep done (uptime={_up}s) — {relaunched} respawned, {recycled} recycled, {ver_recycled} version-recycled, {stopped} stopped, {enforced} recycle-enforced")
@@ -621,6 +621,37 @@ def _record_event_bg(event_type: str, payload: dict = None) -> None:
621
621
  pass
622
622
 
623
623
 
624
+ # FLEET-RECYCLE P0 (task 798dd1bd): stamp mc_agents.last_active_at on FOREGROUND
625
+ # model activity (every tool call, incl meshcode_wait) so the recycle enforce
626
+ # sweep (mc_recycle_enforce_candidates, mig419) can distinguish a genuinely-HUNG
627
+ # agent — model stopped calling tools -> last_active_at goes STALE -> enforce —
628
+ # from an attended/busy-looping one — still cycling -> FRESH -> defer. CRUX
629
+ # (commander hard requirement): this MUST be triggered by foreground tool calls,
630
+ # NEVER the bg heartbeat thread (which beats even when the model is hung; that
631
+ # would keep last_active_at fresh forever and defeat the whole defer). Client-
632
+ # throttled to <=1/30s to avoid write amplification on rapid tool bursts; fired
633
+ # in a daemon thread so it adds zero latency to the tool call.
634
+ _last_active_stamp_mono: float = 0.0
635
+
636
+
637
+ def _touch_active_bg() -> None:
638
+ """Fire-and-forget foreground last_active_at stamp, throttled to <=1 per 30s."""
639
+ global _last_active_stamp_mono
640
+ try:
641
+ now = _time.monotonic()
642
+ if now - _last_active_stamp_mono < 30:
643
+ return
644
+ _last_active_stamp_mono = now
645
+ import threading
646
+ threading.Thread(
647
+ target=be.sb_rpc,
648
+ args=("mc_touch_active", {"p_project_id": _PROJECT_ID, "p_agent_name": AGENT_NAME}),
649
+ daemon=True,
650
+ ).start()
651
+ except Exception:
652
+ pass
653
+
654
+
624
655
  # ============================================================
625
656
  # Hot-reload: detect when backend.py changes on disk (e.g. after
626
657
  # pip install --upgrade meshcode) and reload without restart.
@@ -1005,6 +1036,7 @@ def with_working_status(func):
1005
1036
  async def awrapper(*args, **kwargs):
1006
1037
  _check_hot_reload()
1007
1038
  _capture_session() # stash session on first tool call for silent auto-wake
1039
+ _touch_active_bg() # FLEET-RECYCLE P0: foreground last_active_at stamp (covers wait too, before skip)
1008
1040
  if not skip:
1009
1041
  global _CONSECUTIVE_IDLE_SECONDS
1010
1042
  _CONSECUTIVE_IDLE_SECONDS = 0 # any non-wait tool resets idle timer
@@ -1052,6 +1084,7 @@ def with_working_status(func):
1052
1084
  def swrapper(*args, **kwargs):
1053
1085
  _check_hot_reload()
1054
1086
  _capture_session() # stash session on first tool call for silent auto-wake
1087
+ _touch_active_bg() # FLEET-RECYCLE P0: foreground last_active_at stamp (covers wait too, before skip)
1055
1088
  if not skip:
1056
1089
  global _CONSECUTIVE_IDLE_SECONDS
1057
1090
  _CONSECUTIVE_IDLE_SECONDS = 0 # any non-wait tool resets idle timer
@@ -6090,6 +6123,54 @@ def meshcode_agent_stop(name: str) -> Dict[str, Any]:
6090
6123
  })
6091
6124
 
6092
6125
 
6126
+ @mcp.tool()
6127
+ @with_working_status
6128
+ def meshcode_recycle_agent(name: str, visible: bool = False) -> Dict[str, Any]:
6129
+ """Recycle (close + relaunch FRESH) another agent in YOUR meshwork — to
6130
+ clear a degraded/high-context session and bring it back clean.
6131
+
6132
+ FLEET-RECYCLE (task 798dd1bd). Requests a recycle: the agent consumes it at
6133
+ a task-edge, exits, and the host daemon respawns it fresh with its handoff
6134
+ restored (SessionStart). Cloud-driven so it works remotely. Commander agent
6135
+ (or human owner) only, scoped to your own meshwork — enforced server-side by
6136
+ mc_recycle_as_agent (same authz as mc_agent_power_as_agent).
6137
+
6138
+ Args:
6139
+ name: target agent name in your meshwork.
6140
+ visible: True = respawn as a visible focused window; default False =
6141
+ preserve the agent's current headless/visible state.
6142
+ """
6143
+ return be.sb_rpc("mc_recycle_as_agent", {
6144
+ "p_api_key": _get_api_key(),
6145
+ "p_project_id": _PROJECT_ID,
6146
+ "p_agent": name,
6147
+ "p_visible": bool(visible),
6148
+ })
6149
+
6150
+
6151
+ @mcp.tool()
6152
+ @with_working_status
6153
+ def meshcode_recycle_fleet(visible: bool = False) -> Dict[str, Any]:
6154
+ """Recycle (close + relaunch FRESH) ALL running agents in YOUR meshwork —
6155
+ a clean-slate sweep of the whole fleet.
6156
+
6157
+ FLEET-RECYCLE (task 798dd1bd). Each agent consumes the recycle at its own
6158
+ task-edge, exits, and respawns fresh with handoff restored — no agent is
6159
+ killed mid-task. Cloud-driven (remote-capable). Commander agent (or human
6160
+ owner) only, scoped to your own meshwork — enforced server-side.
6161
+
6162
+ Args:
6163
+ visible: True = respawn each as a visible focused window; default False
6164
+ = preserve each agent's current headless/visible state.
6165
+ """
6166
+ return be.sb_rpc("mc_recycle_as_agent", {
6167
+ "p_api_key": _get_api_key(),
6168
+ "p_project_id": _PROJECT_ID,
6169
+ "p_agent": None, # NULL = whole fleet (all running agents)
6170
+ "p_visible": bool(visible),
6171
+ })
6172
+
6173
+
6093
6174
  @mcp.tool()
6094
6175
  @with_working_status
6095
6176
  def meshcode_set_meshwork_autonomous_default(enabled: bool) -> Dict[str, Any]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.93
3
+ Version: 2.11.96
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.93"
7
+ version = "2.11.96"
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