meshcode 2.11.157__tar.gz → 2.11.158__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 (122) hide show
  1. {meshcode-2.11.157 → meshcode-2.11.158}/PKG-INFO +1 -1
  2. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/__init__.py +1 -1
  3. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/hostd.py +38 -48
  4. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/server.py +18 -0
  5. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/protocol_handler.py +24 -4
  6. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/SOURCES.txt +1 -0
  8. {meshcode-2.11.157 → meshcode-2.11.158}/pyproject.toml +1 -1
  9. meshcode-2.11.158/tests/test_no_appleevents_on_sweep.py +81 -0
  10. {meshcode-2.11.157 → meshcode-2.11.158}/README.md +0 -0
  11. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/__main__.py +0 -0
  12. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/_launch_smoke.py +0 -0
  13. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/_session_handoff_template.py +0 -0
  14. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/_stop_hook_template.py +0 -0
  15. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/_update_guard.py +0 -0
  16. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/ascii_art.py +0 -0
  17. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/atomic_push.py +0 -0
  18. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/claude_update.py +0 -0
  19. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/cli.py +0 -0
  20. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/comms_v4.py +0 -0
  21. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/compat.py +0 -0
  22. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/daemon.py +0 -0
  23. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/date_parse.py +0 -0
  24. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/doctor.py +0 -0
  25. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/error_hints.py +0 -0
  26. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/exceptions.py +0 -0
  27. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/helper_visuals.py +0 -0
  28. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/hooks/__init__.py +0 -0
  29. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/hooks/push_guard.py +0 -0
  30. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/hooks/repo_path_lock.py +0 -0
  31. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/invites.py +0 -0
  32. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/launcher.py +0 -0
  33. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/launcher_install.py +0 -0
  34. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/__init__.py +0 -0
  35. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/__main__.py +0 -0
  36. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/backend.py +0 -0
  37. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/realtime.py +0 -0
  38. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  39. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/swarm.py +0 -0
  40. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_backend.py +0 -0
  41. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  42. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  43. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  44. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  45. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  46. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_swarm.py +0 -0
  47. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/preferences.py +0 -0
  48. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/protocol_v2.py +0 -0
  49. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/quickstart.py +0 -0
  50. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/rpc_allowlist.py +0 -0
  51. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/run_agent.py +0 -0
  52. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/scripts/check_secrets.py +0 -0
  53. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/scripts/race_rate_harness.py +0 -0
  54. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/secrets.py +0 -0
  55. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/self_update.py +0 -0
  56. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/setup_clients.py +0 -0
  57. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/supervisor.py +0 -0
  58. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/up.py +0 -0
  59. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/upload.py +0 -0
  60. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/dependency_links.txt +0 -0
  61. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/entry_points.txt +0 -0
  62. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/requires.txt +0 -0
  63. {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/top_level.txt +0 -0
  64. {meshcode-2.11.157 → meshcode-2.11.158}/setup.cfg +0 -0
  65. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_auto_update_hardening.py +0 -0
  66. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_autonomous_closegap_1.py +0 -0
  67. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_autonomous_closegap_2.py +0 -0
  68. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_autonomous_closegap_3.py +0 -0
  69. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_autonomous_prompt_inject.py +0 -0
  70. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_boot_bug_regression.py +0 -0
  71. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_color_truecolor.py +0 -0
  72. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_core.py +0 -0
  73. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_cross_agent_messaging.py +0 -0
  74. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_date_parse.py +0 -0
  75. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_doctor.py +0 -0
  76. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_ensure_boot_env_urgent_wake.py +0 -0
  77. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_epistemic_v1_python_sdk.py +0 -0
  78. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  79. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_esc_deaf_state.py +0 -0
  80. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_exceptions.py +0 -0
  81. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_file_upload.py +0 -0
  82. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_fleet_reaper.py +0 -0
  83. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_helper_visuals.py +0 -0
  84. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_hostd_launch_pinned_env.py +0 -0
  85. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_hostd_serve_discovery_split.py +0 -0
  86. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_hostd_zombie_sessions.py +0 -0
  87. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_init_device_code.py +0 -0
  88. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_install_guard.py +0 -0
  89. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_launch_smoke.py +0 -0
  90. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_lease_sigterm_release.py +0 -0
  91. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_live_mesh_guard.py +0 -0
  92. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_mark_read_batch.py +0 -0
  93. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_marketplace_ratings.py +0 -0
  94. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_migration_integrity.py +0 -0
  95. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_preflight_hb_gate.py +0 -0
  96. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_pretrust_claude.py +0 -0
  97. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_push_guard.py +0 -0
  98. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_realtime_event_freshness.py +0 -0
  99. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_replica_base_workspace_fallback.py +0 -0
  100. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_replica_boot_protocol_unconditional.py +0 -0
  101. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_rls_cross_tenant.py +0 -0
  102. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_rm_guard.py +0 -0
  103. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_rpc_grants.py +0 -0
  104. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_rpc_migrations.py +0 -0
  105. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_run_agent_dry_run.py +0 -0
  106. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_run_agent_no_server_import.py +0 -0
  107. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_security_regressions.py +0 -0
  108. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_self_update_user_site.py +0 -0
  109. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_sentinel.py +0 -0
  110. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_session_replay_gate.py +0 -0
  111. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_setup_path.py +0 -0
  112. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_sleep_signals.py +0 -0
  113. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_status_enum_coverage.py +0 -0
  114. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_stay_on_loop_hook.py +0 -0
  115. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_stop_ghost_terminal.py +0 -0
  116. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_swarm_events.py +0 -0
  117. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_task_progress.py +0 -0
  118. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_terminal_lifecycle.py +0 -0
  119. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_up_launch_cmd.py +0 -0
  120. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_update_guard.py +0 -0
  121. {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_urgent_wake_tmux.py +0 -0
  122. {meshcode-2.11.157 → meshcode-2.11.158}/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.157
3
+ Version: 2.11.158
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.157"
2
+ __version__ = "2.11.158"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -1497,57 +1497,47 @@ def _terminal_window_marker(target: str) -> str:
1497
1497
 
1498
1498
 
1499
1499
  def _list_dead_terminal_windows(target: str) -> list:
1500
- """macOS: ids of Terminal windows that belonged to `target`'s launcher and
1501
- whose tab is NO LONGER BUSY — the '[Process completed]' orphans a rotation
1502
- leaves behind (task 70ac6d25). busy=false means no process runs in the tab,
1503
- so a live agent can never match; the marker filter keeps every non-meshcode
1504
- window (user shells, other apps' tabs) out of reach. The marker is built
1505
- from a sanitized charset ([A-Za-z0-9_.-]) so it can't inject AppleScript.
1506
- Returns [] on non-macOS, no Automation permission, or any error."""
1507
- if sys.platform != "darwin":
1508
- return []
1509
- marker = _terminal_window_marker(target)
1510
- if not marker:
1511
- return []
1512
- script = (
1513
- 'set out to ""\n'
1514
- 'tell application "Terminal"\n'
1515
- ' repeat with w in (every window)\n'
1516
- ' try\n'
1517
- f' if (busy of selected tab of w) is false and (name of w contains "{marker}") then\n'
1518
- ' set out to out & (id of w) & linefeed\n'
1519
- ' end if\n'
1520
- ' end try\n'
1521
- ' end repeat\n'
1522
- 'end tell\n'
1523
- 'return out')
1524
- try:
1525
- out = subprocess.run(["/usr/bin/osascript", "-e", script],
1526
- capture_output=True, text=True, timeout=10).stdout or ""
1527
- return [int(x) for x in out.split() if x.strip().isdigit()]
1528
- except Exception:
1529
- return []
1500
+ """DISABLED no-op (macOS TCC fix, task e0465408 / Prong 1 #2).
1501
+
1502
+ This used to enumerate Terminal windows via `osascript -e 'tell application
1503
+ "Terminal" ...'` to find '[Process completed]' rotation orphans (task
1504
+ 70ac6d25 / 2.11.157). That Apple Event fired from python3.11 on the hostd
1505
+ lifecycle sweep and was RC-1 of the recurring macOS consent prompt
1506
+ "python3.11 would like to access data from other apps"
1507
+ (kTCCServiceAppleEvents). Because the interpreter is ad-hoc signed (bare
1508
+ cdhash, no Designated Requirement) the grant never stuck across versions, so
1509
+ it re-prompted on every sweep. The `.command` launcher already self-closes
1510
+ its OWN tab on any exit (clean / recycle / crash) via a tty-targeted close
1511
+ run from the agent's shell (see protocol_handler._write_fleet_native_agent),
1512
+ so this hostd-side reaper was a redundant belt. Removing it takes Apple
1513
+ Events off the python sweep path with no dead-tab regression — only
1514
+ un-trappable SIGKILL orphans may linger, which are rare and cannot be closed
1515
+ without Apple Events anyway. Kept as a no-op so the call sites stay harmless.
1516
+ NEVER reintroduce osascript here — guarded by
1517
+ tests/test_no_appleevents_on_sweep.py."""
1518
+ return []
1530
1519
 
1531
1520
 
1532
1521
  def _close_dead_terminal_windows(target: str, window_ids) -> int:
1533
- """Close (saving no) a pre-collected set of dead launcher windows. The ids
1534
- come from _list_dead_terminal_windows BEFORE the fresh spawn, so the new
1535
- window is excluded by construction (same snapshot discipline as
1536
- _close_old_visible_recycle). 'every window whose id is N' no-ops harmlessly
1537
- if the user already closed it. Best-effort; returns count closed."""
1538
- n = 0
1539
- for wid in window_ids:
1540
- try:
1541
- r = subprocess.run(
1542
- ["/usr/bin/osascript", "-e",
1543
- f'tell application "Terminal" to close (every window whose id is {int(wid)}) saving no'],
1544
- capture_output=True, text=True, timeout=10)
1545
- if r.returncode == 0:
1546
- _log(f"DEAD-WINDOW-REAP {target}: closed dead terminal window id {wid} (rotation orphan)")
1547
- n += 1
1548
- except Exception:
1549
- pass
1550
- return n
1522
+ """DISABLED no-op (macOS TCC fix, task e0465408 / Prong 1 #2).
1523
+
1524
+ Previously closed dead launcher windows by firing
1525
+ `osascript -e 'tell application "Terminal" to close ...'` from python3.11.
1526
+ That Apple Event was RC-1 of the recurring TCC prompt
1527
+ ("python3.11 would like to access data from other apps") — the adhoc python
1528
+ interpreter has no stable Designated Requirement, so the kTCCServiceAppleEvents
1529
+ grant never persists across per-version cdhash changes and re-prompts forever.
1530
+
1531
+ The `.command` launcher already self-closes its OWN tab on any exit
1532
+ (clean/recycle/crash) via a tty-targeted close run from the agent's SHELL
1533
+ (not python — different TCC attribution), so this hostd-side reaper was a
1534
+ redundant belt. Removing it takes Apple Events off the python sweep path with
1535
+ no dead-tab regression only un-trappable SIGKILL orphans may linger, which
1536
+ are rare and cannot be closed without Apple Events anyway. Kept as a no-op so
1537
+ the call sites stay harmless.
1538
+ NEVER reintroduce osascript here — guarded by
1539
+ tests/test_no_appleevents_on_sweep.py."""
1540
+ return 0
1551
1541
 
1552
1542
 
1553
1543
  def _spawn_rate_ok(target: str):
@@ -528,6 +528,24 @@ def _try_auto_wake(from_agent: str, preview: str, urgent: bool = False) -> None:
528
528
  system = platform.system()
529
529
  try:
530
530
  if system == "Darwin":
531
+ # macOS TCC fix (task e0465408 / Prong 1 #3): the AppleScript
532
+ # keystroke fallback fires osascript from python3.11, which triggers
533
+ # the recurring "python3.11 would like to access data from other apps"
534
+ # consent prompt (kTCCServiceAppleEvents; adhoc python has no stable
535
+ # Designated Requirement so the grant never persists). tmux send-keys
536
+ # (the fast-path above) is the durable, prompt-free wake mechanism.
537
+ # The AppleScript fallback is OFF by default and only re-enabled by
538
+ # operators who explicitly opt into Apple Events:
539
+ # MESHCODE_AUTOWAKE_APPLESCRIPT=1
540
+ # When off and tmux was unavailable/failed, we skip keystroke
541
+ # injection entirely (no prompt, no wake) rather than re-prompt Samuel.
542
+ if os.environ.get("MESHCODE_AUTOWAKE_APPLESCRIPT", "").strip().lower() \
543
+ not in ("1", "true", "yes", "on"):
544
+ log.debug(
545
+ "auto-wake: tmux unavailable and AppleScript fallback "
546
+ "disabled (MESHCODE_AUTOWAKE_APPLESCRIPT off) — skipping "
547
+ "keystroke injection to avoid macOS TCC prompt")
548
+ return
531
549
  # Sanitize app_name — TERM_PROGRAM is an env var, not DB-sourced,
532
550
  # but defense-in-depth: only allow known terminal app names.
533
551
  parent_app = os.environ.get("TERM_PROGRAM", "Terminal")
@@ -356,8 +356,12 @@ def _fleet_attach_macos(tmux: str) -> tuple[bool, str]:
356
356
  ]
357
357
  try:
358
358
  launch_dir.mkdir(parents=True, exist_ok=True)
359
+ # macOS TCC fix (task e0465408) security guardrail: launcher dir +
360
+ # .command owner-only (0o700). These scripts are LaunchServices-executed
361
+ # via `open -a` and must never be world-readable/-writable.
362
+ _harden_launcher_dir(launch_dir)
359
363
  script_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
360
- os.chmod(script_path, 0o755)
364
+ os.chmod(script_path, 0o700)
361
365
  except Exception as e:
362
366
  return False, f"could not write fleet launcher: {e}"
363
367
  r = subprocess.run(["open", "-a", "Terminal", str(script_path)],
@@ -560,6 +564,19 @@ def _spawn_fleet_tab(cmd: str) -> tuple[bool, str]:
560
564
  _FLEET_NATIVE_DISABLED_TTL_S = 1800 # back off after a watcher "fallback"
561
565
 
562
566
 
567
+ def _harden_launcher_dir(d) -> None:
568
+ """macOS TCC fix (task e0465408) security guardrail: keep the launcher dir
569
+ owner-only (0o700). The `.command`/watcher scripts here are executed by
570
+ LaunchServices via `open -a` and may carry non-secret launch flags; they must
571
+ never be world-readable/-writable (was 0o755). Secrets are NEVER written into
572
+ these scripts (they come via .mcp.json / keychain / env-file). Best-effort:
573
+ a chmod failure must not wedge a launch."""
574
+ try:
575
+ os.chmod(str(d), 0o700)
576
+ except Exception:
577
+ pass
578
+
579
+
563
580
  def _fleet_native_paths() -> dict:
564
581
  d = Path.home() / ".meshcode" / "launchers"
565
582
  return {
@@ -713,10 +730,11 @@ while :; do
713
730
  done
714
731
  '''
715
732
  p["dir"].mkdir(parents=True, exist_ok=True)
733
+ _harden_launcher_dir(p["dir"]) # owner-only launcher dir (TCC fix e0465408)
716
734
  driver = p["dir"] / "fleet-native-driver.applescript"
717
735
  driver.write_text(_FLEET_NATIVE_DRIVER_AS + "\n", encoding="utf-8")
718
736
  p["watcher"].write_text(body, encoding="utf-8")
719
- os.chmod(p["watcher"], 0o755)
737
+ os.chmod(p["watcher"], 0o700)
720
738
  return p["watcher"]
721
739
 
722
740
 
@@ -796,8 +814,9 @@ def _write_fleet_native_agent(cmd: str) -> Path:
796
814
  'esac',
797
815
  ]
798
816
  p["dir"].mkdir(parents=True, exist_ok=True)
817
+ _harden_launcher_dir(p["dir"]) # owner-only launcher dir (TCC fix e0465408)
799
818
  script_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
800
- os.chmod(script_path, 0o755)
819
+ os.chmod(script_path, 0o700)
801
820
  return script_path
802
821
 
803
822
 
@@ -1022,8 +1041,9 @@ def _spawn_terminal_macos(cmd: str) -> tuple[bool, str]:
1022
1041
  lines.append('fi')
1023
1042
  try:
1024
1043
  launch_dir.mkdir(parents=True, exist_ok=True)
1044
+ _harden_launcher_dir(launch_dir) # owner-only launcher dir (TCC fix e0465408)
1025
1045
  script_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
1026
- os.chmod(script_path, 0o755)
1046
+ os.chmod(script_path, 0o700)
1027
1047
  except Exception as e:
1028
1048
  return False, f"could not write launcher {script_path}: {e}"
1029
1049
  # Clear any stale start-marker so the poll below can only see a FRESH one
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.157
3
+ Version: 2.11.158
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -89,6 +89,7 @@ tests/test_live_mesh_guard.py
89
89
  tests/test_mark_read_batch.py
90
90
  tests/test_marketplace_ratings.py
91
91
  tests/test_migration_integrity.py
92
+ tests/test_no_appleevents_on_sweep.py
92
93
  tests/test_preflight_hb_gate.py
93
94
  tests/test_pretrust_claude.py
94
95
  tests/test_push_guard.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.11.157"
7
+ version = "2.11.158"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,81 @@
1
+ """
2
+ No Apple Events on the python sweep path (task e0465408, Samuel P1 — TCC fix)
3
+ ============================================================================
4
+ RC-1 of the recurring macOS consent prompt
5
+ "python3.11 would like to access data from other apps"
6
+ was hostd's dead-window reaper firing `osascript -e 'tell application "Terminal"
7
+ to close ...'` from python3.11 on every lifecycle sweep (stop/kill/respawn). The
8
+ adhoc python interpreter has no stable Designated Requirement, so the
9
+ kTCCServiceAppleEvents grant never persists across per-version cdhash changes and
10
+ re-prompts forever.
11
+
12
+ The fix neutralizes the reaper (the `.command` launcher already self-closes its
13
+ OWN tab from the agent's SHELL — different TCC attribution). This guard locks the
14
+ fix in: the two reaper functions must stay Apple-Event-free, and the macOS
15
+ auto-wake keystroke fallback (RC-1's sibling in the MCP server) must stay gated
16
+ behind an opt-in env var so it never fires Apple Events by default.
17
+
18
+ If this test fails, someone reintroduced an Apple Event on a python code path and
19
+ the TCC prompt is back. Do NOT "fix" the test — remove the osascript.
20
+ """
21
+ import inspect
22
+ import sys
23
+ from pathlib import Path
24
+
25
+ sys.path.insert(0, str(Path(__file__).parent.parent))
26
+
27
+ import meshcode.hostd as hostd
28
+ from meshcode.meshcode_mcp import server as mcp_server
29
+
30
+
31
+ _FORBIDDEN = ("osascript", "tell application", "AppleScript", "System Events")
32
+
33
+
34
+ def _executable_source(fn):
35
+ """Function source with the docstring and comment lines removed, so a
36
+ forbidden token surviving in code (not just a 'NEVER reintroduce X' note)
37
+ is what trips the guard."""
38
+ src = inspect.getsource(fn)
39
+ doc = inspect.getdoc(fn) or ""
40
+ for line in doc.splitlines():
41
+ src = src.replace(line, "")
42
+ code_lines = [ln for ln in src.splitlines() if not ln.lstrip().startswith("#")]
43
+ return "\n".join(code_lines)
44
+
45
+
46
+ def _scan(fn):
47
+ src = _executable_source(fn).lower()
48
+ return [tok for tok in _FORBIDDEN if tok.lower() in src]
49
+
50
+
51
+ def test_list_dead_terminal_windows_has_no_apple_events():
52
+ hits = _scan(hostd._list_dead_terminal_windows)
53
+ assert not hits, f"_list_dead_terminal_windows reintroduced Apple Events: {hits}"
54
+
55
+
56
+ def test_close_dead_terminal_windows_has_no_apple_events():
57
+ hits = _scan(hostd._close_dead_terminal_windows)
58
+ assert not hits, f"_close_dead_terminal_windows reintroduced Apple Events: {hits}"
59
+
60
+
61
+ def test_list_dead_terminal_windows_returns_empty():
62
+ assert hostd._list_dead_terminal_windows("anything") == []
63
+
64
+
65
+ def test_close_dead_terminal_windows_returns_zero():
66
+ assert hostd._close_dead_terminal_windows("anything", ["@1", "@2"]) == 0
67
+
68
+
69
+ def test_autowake_applescript_is_env_gated():
70
+ """The macOS auto-wake keystroke fallback must be guarded by the opt-in
71
+ MESHCODE_AUTOWAKE_APPLESCRIPT env var — off by default = no Apple Events,
72
+ no TCC prompt. tmux send-keys is the durable wake mechanism."""
73
+ src = inspect.getsource(mcp_server._try_auto_wake)
74
+ assert "MESHCODE_AUTOWAKE_APPLESCRIPT" in src, (
75
+ "auto-wake AppleScript fallback is no longer gated behind "
76
+ "MESHCODE_AUTOWAKE_APPLESCRIPT — it will re-trigger the macOS TCC prompt")
77
+
78
+
79
+ if __name__ == "__main__":
80
+ import pytest
81
+ sys.exit(pytest.main([__file__, "-q"]))
File without changes
File without changes
File without changes
File without changes