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.
- {meshcode-2.11.157 → meshcode-2.11.158}/PKG-INFO +1 -1
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/__init__.py +1 -1
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/hostd.py +38 -48
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/server.py +18 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/protocol_handler.py +24 -4
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/SOURCES.txt +1 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/pyproject.toml +1 -1
- meshcode-2.11.158/tests/test_no_appleevents_on_sweep.py +81 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/README.md +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/__main__.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/_launch_smoke.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/_update_guard.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/cli.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/compat.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/daemon.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/doctor.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/helper_visuals.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/hooks/__init__.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/hooks/push_guard.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/hooks/repo_path_lock.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/invites.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/launcher.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/swarm.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/meshcode_mcp/test_swarm.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/preferences.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/secrets.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/self_update.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/up.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode/upload.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/setup.cfg +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_core.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_doctor.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_ensure_boot_env_urgent_wake.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_fleet_reaper.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_helper_visuals.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_hostd_launch_pinned_env.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_hostd_serve_discovery_split.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_hostd_zombie_sessions.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_launch_smoke.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_live_mesh_guard.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_preflight_hb_gate.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_pretrust_claude.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_push_guard.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_replica_base_workspace_fallback.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_replica_boot_protocol_unconditional.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_rm_guard.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_session_replay_gate.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_stop_ghost_terminal.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_swarm_events.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_task_progress.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_terminal_lifecycle.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_up_launch_cmd.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_update_guard.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_urgent_wake_tmux.py +0 -0
- {meshcode-2.11.157 → meshcode-2.11.158}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -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
|
-
"""
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
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
|
-
"""
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
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,
|
|
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"],
|
|
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,
|
|
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,
|
|
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
|
|
@@ -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
|
|
@@ -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
|
|
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
|
|
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
|