meshcode 2.11.72__tar.gz → 2.11.73__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.72 → meshcode-2.11.73}/PKG-INFO +1 -1
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/__init__.py +1 -1
- meshcode-2.11.73/meshcode/hostd.py +562 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/protocol_handler.py +15 -3
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/SOURCES.txt +2 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/pyproject.toml +1 -1
- meshcode-2.11.73/tests/test_autonomous_prompt_inject.py +126 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/README.md +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/__main__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/cli.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/compat.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/daemon.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/doctor.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/exceptions.py +0 -0
- /meshcode-2.11.72/meshcode/hostd.py → /meshcode-2.11.73/meshcode/hostd 3.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/invites.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/launcher.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/preferences.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/secrets.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/self_update.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/up.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode/upload.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/comms_v4.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_core.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/comms_v4.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_core.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/comms_v4.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_core.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/PKG-INFO 2 +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/SOURCES 2.txt +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/dependency_links 2.txt +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/entry_points 2.txt +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/requires 2.txt +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/top_level 2.txt +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/setup.cfg +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 10.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 11.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 12.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 13.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 14.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 15.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 16.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 17.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 18.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 19.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 2.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 20.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_autonomous_prompt_inject 21.py +0 -0
- /meshcode-2.11.72/tests/test_autonomous_prompt_inject 3.py → /meshcode-2.11.73/tests/test_autonomous_prompt_inject 22.py +0 -0
- /meshcode-2.11.72/tests/test_autonomous_prompt_inject 4.py → /meshcode-2.11.73/tests/test_autonomous_prompt_inject 3.py +0 -0
- /meshcode-2.11.72/tests/test_autonomous_prompt_inject 5.py → /meshcode-2.11.73/tests/test_autonomous_prompt_inject 4.py +0 -0
- /meshcode-2.11.72/tests/test_autonomous_prompt_inject 6.py → /meshcode-2.11.73/tests/test_autonomous_prompt_inject 5.py +0 -0
- /meshcode-2.11.72/tests/test_autonomous_prompt_inject 7.py → /meshcode-2.11.73/tests/test_autonomous_prompt_inject 6.py +0 -0
- /meshcode-2.11.72/tests/test_autonomous_prompt_inject 8.py → /meshcode-2.11.73/tests/test_autonomous_prompt_inject 7.py +0 -0
- /meshcode-2.11.72/tests/test_autonomous_prompt_inject 9.py → /meshcode-2.11.73/tests/test_autonomous_prompt_inject 8.py +0 -0
- /meshcode-2.11.72/tests/test_autonomous_prompt_inject.py → /meshcode-2.11.73/tests/test_autonomous_prompt_inject 9.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_core.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_doctor.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.72 → meshcode-2.11.73}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
"""meshcode hostd — host-side daemon: respawn + recycle managed agents.
|
|
2
|
+
|
|
3
|
+
Complements supervisor.py (launchd KeepAlive = process-exit restart only).
|
|
4
|
+
hostd polls the CLOUD for staleness (heartbeat) + recycle triggers that
|
|
5
|
+
launchd cannot see:
|
|
6
|
+
|
|
7
|
+
- RESPAWN: agent's heartbeat is stale (process/daemon-thread dead) and
|
|
8
|
+
desired_state='running' → relaunch. Loop-guard cap 3/10min → 'crashed'.
|
|
9
|
+
- RECYCLE: managed agent exceeds its recycle policy (context% or uptime)
|
|
10
|
+
→ relaunch at a task boundary (never mid-task). Recorded separately
|
|
11
|
+
from the crash cap.
|
|
12
|
+
|
|
13
|
+
Contract (mig366/367/368/369, DBA-cleared):
|
|
14
|
+
mc_register_host, mc_agents_needing_respawn, mc_record_respawn,
|
|
15
|
+
mc_record_recycle, mc_set_desired_state, mc_host_recycle_policy.
|
|
16
|
+
|
|
17
|
+
Source of truth is the cloud (mc_agents / mc_host_config), never local
|
|
18
|
+
folders. host_id lives in ~/.meshcode/host_id (generated once).
|
|
19
|
+
|
|
20
|
+
meshcode hostd run — run the daemon loop (foreground)
|
|
21
|
+
meshcode hostd status — show this host's managed agents
|
|
22
|
+
meshcode hostd install — install launchd trigger-start plist
|
|
23
|
+
meshcode hostd uninstall — remove it
|
|
24
|
+
|
|
25
|
+
Design invariants:
|
|
26
|
+
- NEVER respawn desired_state != 'running' (NULL=unmanaged, stopped=
|
|
27
|
+
intentional, crashed=gave-up). The cloud RPC enforces this; we trust it.
|
|
28
|
+
- Single retry / capped respawn — never an infinite relaunch loop.
|
|
29
|
+
- Recycle only at task boundary: we relaunch a recycle candidate only
|
|
30
|
+
when it is NOT actively working (status not in working/online-busy) OR
|
|
31
|
+
the agent has self-flagged 'recycling'. Mid-task agents are skipped.
|
|
32
|
+
"""
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import json
|
|
36
|
+
import os
|
|
37
|
+
import shlex
|
|
38
|
+
import subprocess
|
|
39
|
+
import sys
|
|
40
|
+
import time
|
|
41
|
+
import urllib.request
|
|
42
|
+
import uuid
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
from typing import Optional
|
|
45
|
+
|
|
46
|
+
STATE_DIR = Path.home() / ".meshcode"
|
|
47
|
+
HOST_ID_PATH = STATE_DIR / "host_id"
|
|
48
|
+
LOG_PATH = STATE_DIR / "hostd.log"
|
|
49
|
+
|
|
50
|
+
POLL_INTERVAL_SEC = 45
|
|
51
|
+
STALE_SECONDS = 150 # heartbeat liveness cutoff (QA #3)
|
|
52
|
+
RESPAWN_TIMEOUT_SEC = 30
|
|
53
|
+
# statuses that mean "actively working" → never recycle mid-task
|
|
54
|
+
BUSY_STATUSES = {"working", "online", "busy"}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _log(msg: str) -> None:
|
|
58
|
+
line = f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}"
|
|
59
|
+
print(line, file=sys.stderr)
|
|
60
|
+
try:
|
|
61
|
+
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
with open(LOG_PATH, "a", encoding="utf-8") as f:
|
|
63
|
+
f.write(line + "\n")
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_host_id() -> str:
|
|
69
|
+
"""Read ~/.meshcode/host_id, generating a stable UUID on first use."""
|
|
70
|
+
try:
|
|
71
|
+
if HOST_ID_PATH.exists():
|
|
72
|
+
v = HOST_ID_PATH.read_text(encoding="utf-8").strip()
|
|
73
|
+
if v:
|
|
74
|
+
return v
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
77
|
+
new_id = str(uuid.uuid4())
|
|
78
|
+
try:
|
|
79
|
+
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
HOST_ID_PATH.write_text(new_id, encoding="utf-8")
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
return new_id
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ------------------------------------------------------------------
|
|
87
|
+
# Cloud RPC (PostgREST) — self-contained urllib, no internal client dep
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def _supabase_cfg() -> tuple:
|
|
91
|
+
# env first (explicit override), then fall back to the SAME resolution the
|
|
92
|
+
# rest of the CLI uses (comms_v4: env -> ~/.meshcode/env -> baked publishable
|
|
93
|
+
# default). hostd runs under launchd with NO env, so the old env-only lookup
|
|
94
|
+
# left it unable to reach the cloud at all ("SUPABASE_URL/KEY not set").
|
|
95
|
+
url = os.environ.get("SUPABASE_URL", "").rstrip("/")
|
|
96
|
+
key = os.environ.get("SUPABASE_KEY") or os.environ.get("MESHCODE_SUPABASE_ANON_KEY") or ""
|
|
97
|
+
if url and key:
|
|
98
|
+
return url, key
|
|
99
|
+
try:
|
|
100
|
+
from meshcode import comms_v4 as _c # type: ignore
|
|
101
|
+
return (url or _c.SUPABASE_URL).rstrip("/"), (key or _c.SUPABASE_KEY)
|
|
102
|
+
except Exception:
|
|
103
|
+
return url, key
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _api_key() -> Optional[str]:
|
|
107
|
+
"""The meshcode agent/user api key, used as p_api_key in mc_ RPCs.
|
|
108
|
+
|
|
109
|
+
Resolution mirrors comms_v4._load_api_key_for_cli so hostd authenticates
|
|
110
|
+
the same way `meshcode run` does. CRITICAL: launchd starts hostd with NO
|
|
111
|
+
environment, so the key MUST be resolvable from the keychain (where
|
|
112
|
+
`meshcode login` stores it). The old code only checked env + a
|
|
113
|
+
wrong-shaped profiles.json, so launchd-started hostd always FATAL'd
|
|
114
|
+
("no api key") and never registered -> dashboard launch had no host.
|
|
115
|
+
"""
|
|
116
|
+
# 1) explicit env override
|
|
117
|
+
k = os.environ.get("MESHCODE_API_KEY")
|
|
118
|
+
if k:
|
|
119
|
+
return k
|
|
120
|
+
# 2) keychain — the actual store `meshcode login` writes to. Same source
|
|
121
|
+
# comms_v4._load_api_key_for_cli() uses. This is the launchd fix.
|
|
122
|
+
try:
|
|
123
|
+
from meshcode import secrets as _secrets # type: ignore
|
|
124
|
+
profile = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or _secrets.DEFAULT_PROFILE
|
|
125
|
+
key = _secrets.get_api_key(profile=profile) or ""
|
|
126
|
+
if key:
|
|
127
|
+
return key
|
|
128
|
+
except Exception:
|
|
129
|
+
pass
|
|
130
|
+
# 3) legacy per-profile state file fallback. Handles BOTH the flat
|
|
131
|
+
# {name: {api_key}} and nested {"profiles": {name: {api_key}}} shapes
|
|
132
|
+
# (the flat-only scan silently missed the nested writer).
|
|
133
|
+
try:
|
|
134
|
+
prof = STATE_DIR / "profiles.json"
|
|
135
|
+
if prof.exists():
|
|
136
|
+
data = json.loads(prof.read_text(encoding="utf-8"))
|
|
137
|
+
roots = []
|
|
138
|
+
if isinstance(data, dict):
|
|
139
|
+
roots.append(data)
|
|
140
|
+
if isinstance(data.get("profiles"), dict):
|
|
141
|
+
roots.append(data["profiles"])
|
|
142
|
+
for root in roots:
|
|
143
|
+
for v in root.values():
|
|
144
|
+
if isinstance(v, dict) and v.get("api_key"):
|
|
145
|
+
return v["api_key"]
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _rpc(fn: str, payload: dict) -> Optional[dict]:
|
|
152
|
+
"""Call a PostgREST RPC. Returns parsed JSON or None on any failure."""
|
|
153
|
+
url, key = _supabase_cfg()
|
|
154
|
+
if not url or not key:
|
|
155
|
+
_log("WARN: SUPABASE_URL/KEY not set — cannot reach cloud")
|
|
156
|
+
return None
|
|
157
|
+
try:
|
|
158
|
+
req = urllib.request.Request(
|
|
159
|
+
f"{url}/rest/v1/rpc/{fn}",
|
|
160
|
+
data=json.dumps(payload).encode("utf-8"),
|
|
161
|
+
headers={
|
|
162
|
+
"apikey": key,
|
|
163
|
+
"Authorization": f"Bearer {key}",
|
|
164
|
+
"Content-Type": "application/json",
|
|
165
|
+
},
|
|
166
|
+
method="POST",
|
|
167
|
+
)
|
|
168
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
169
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
170
|
+
except Exception as e:
|
|
171
|
+
_log(f"WARN: rpc {fn} failed: {e}")
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ------------------------------------------------------------------
|
|
176
|
+
# Respawn
|
|
177
|
+
# ------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
def _meshcode_bin() -> str:
|
|
180
|
+
cand = Path(sys.argv[0]).resolve()
|
|
181
|
+
if cand.exists() and cand.name.startswith("meshcode"):
|
|
182
|
+
return str(cand)
|
|
183
|
+
return "meshcode"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
|
|
187
|
+
"""Relaunch `meshcode run <project>/<agent>`.
|
|
188
|
+
|
|
189
|
+
headless=False (default): VISIBLE terminal window. Samuel must SEE it — we
|
|
190
|
+
delegate to protocol_handler._spawn_terminal, the canonical visible spawner
|
|
191
|
+
(macOS: osascript-if-Automation-granted else `open` a .command wrapper, no TCC
|
|
192
|
+
needed; Linux/Windows: native terminals). On failure we WARN loudly and return
|
|
193
|
+
False — we NEVER silently fall back to headless when visible was asked.
|
|
194
|
+
|
|
195
|
+
headless=True (Fleet Control mig404 per-agent flag): background process, NO
|
|
196
|
+
window — for fleet agents that don't need a terminal (like the qa launch).
|
|
197
|
+
"""
|
|
198
|
+
target = f"{project}/{agent}"
|
|
199
|
+
bin_ = _meshcode_bin()
|
|
200
|
+
if headless:
|
|
201
|
+
# background, NO terminal — UNIVERSAL macOS/Linux/Windows (task c1a6c6a8, mesh-dev specs).
|
|
202
|
+
# Clean env (a stale CLAUDECODE aborts `meshcode run`); keep crash logs in a per-agent logfile
|
|
203
|
+
# (NOT DEVNULL — we want to debug headless agents that die on boot).
|
|
204
|
+
env = {k: v for k, v in os.environ.items()
|
|
205
|
+
if k not in ("CLAUDECODE", "CLAUDE_CODE_SESSION")}
|
|
206
|
+
env["MESHCODE_NO_AUTO_UPDATE"] = "1"
|
|
207
|
+
log_dir = STATE_DIR / "logs"
|
|
208
|
+
try:
|
|
209
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
210
|
+
except Exception:
|
|
211
|
+
pass
|
|
212
|
+
safe = f"{project}__{agent}".replace("/", "_").replace("\\", "_")
|
|
213
|
+
log_path = log_dir / f"{safe}.headless.log"
|
|
214
|
+
logf = None
|
|
215
|
+
try:
|
|
216
|
+
logf = open(log_path, "ab")
|
|
217
|
+
except Exception:
|
|
218
|
+
logf = None
|
|
219
|
+
kwargs = {
|
|
220
|
+
"stdin": subprocess.DEVNULL,
|
|
221
|
+
"stdout": (logf if logf is not None else subprocess.DEVNULL),
|
|
222
|
+
"stderr": subprocess.STDOUT,
|
|
223
|
+
"env": env,
|
|
224
|
+
}
|
|
225
|
+
if sys.platform == "win32":
|
|
226
|
+
# CREATE_NO_WINDOW (0x08000000) | DETACHED_PROCESS (0x08): no console window, fully
|
|
227
|
+
# detached from this daemon. Use the console entry-point (meshcode.exe), NEVER pythonw
|
|
228
|
+
# (it swallows stderr). Ensure venv Scripts + System32 on PATH (Windows PATH cap ~32k).
|
|
229
|
+
kwargs["creationflags"] = 0x08000000 | 0x00000008
|
|
230
|
+
try:
|
|
231
|
+
scripts = str(Path(bin_).resolve().parent)
|
|
232
|
+
except Exception:
|
|
233
|
+
scripts = ""
|
|
234
|
+
sysroot = os.environ.get("SystemRoot", r"C:\Windows")
|
|
235
|
+
base_path = os.pathsep.join(p for p in (scripts, sysroot + r"\System32", sysroot) if p)
|
|
236
|
+
env["PATH"] = (base_path + os.pathsep + env.get("PATH", ""))[:30000]
|
|
237
|
+
else:
|
|
238
|
+
# POSIX: detach into its own session so it survives the daemon + has no controlling tty.
|
|
239
|
+
kwargs["start_new_session"] = True
|
|
240
|
+
try:
|
|
241
|
+
subprocess.Popen([bin_, "run", target], **kwargs)
|
|
242
|
+
_log(f"spawned {target} HEADLESS (no window, {sys.platform}; log={log_path})")
|
|
243
|
+
return True
|
|
244
|
+
except Exception as e:
|
|
245
|
+
_log(f"WARN: headless spawn {target} failed: {e}")
|
|
246
|
+
return False
|
|
247
|
+
finally:
|
|
248
|
+
# child inherited its own fd; safe to drop the daemon's handle.
|
|
249
|
+
if logf is not None:
|
|
250
|
+
try:
|
|
251
|
+
logf.close()
|
|
252
|
+
except Exception:
|
|
253
|
+
pass
|
|
254
|
+
# env hygiene (item1 RC): a stale CLAUDECODE aborts `meshcode run` (exit2). Build the terminal
|
|
255
|
+
# command PER-PLATFORM (mesh-core FIX1, 0x80070002): cmd.exe uses & (NOT ';'), set "V=" to clear
|
|
256
|
+
# (NOT bash unset/export), double-quotes (NOT shlex POSIX), no exec — else Windows Terminal splits
|
|
257
|
+
# on ';' -> file-not-found. Layered ON TOP of the 2.11.72 headless-flags fix (visible branch only).
|
|
258
|
+
if sys.platform == "win32":
|
|
259
|
+
cmd = (f'set "CLAUDECODE=" & set "CLAUDE_CODE_SESSION=" & '
|
|
260
|
+
f'set "MESHCODE_NO_AUTO_UPDATE=1" & "{bin_}" run "{target}"')
|
|
261
|
+
else:
|
|
262
|
+
cmd = (f"unset CLAUDECODE CLAUDE_CODE_SESSION; export MESHCODE_NO_AUTO_UPDATE=1; "
|
|
263
|
+
f"exec {shlex.quote(bin_)} run {shlex.quote(target)}")
|
|
264
|
+
try:
|
|
265
|
+
from meshcode import protocol_handler as _ph
|
|
266
|
+
ok, info = _ph._spawn_terminal(cmd)
|
|
267
|
+
if ok:
|
|
268
|
+
_log(f"spawned {target} in visible terminal ({info})")
|
|
269
|
+
return True
|
|
270
|
+
_log(f"WARN: visible spawn {target} FAILED ({info}) — agent NOT launched. "
|
|
271
|
+
f"macOS: grant Terminal Automation (System Settings > Privacy & Security "
|
|
272
|
+
f"> Automation), or ensure `open` works. NOT falling back to headless.")
|
|
273
|
+
return False
|
|
274
|
+
except Exception as e:
|
|
275
|
+
_log(f"WARN: spawn {target} failed: {e}")
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _do_respawns(api_key: str, host_id: str) -> int:
|
|
280
|
+
"""One respawn sweep. Returns number relaunched."""
|
|
281
|
+
res = _rpc("mc_agents_needing_respawn",
|
|
282
|
+
{"p_api_key": api_key, "p_host_id": host_id, "p_stale_seconds": STALE_SECONDS})
|
|
283
|
+
if not res or not res.get("ok"):
|
|
284
|
+
return 0
|
|
285
|
+
n = 0
|
|
286
|
+
for c in res.get("candidates", []):
|
|
287
|
+
proj, agent = c.get("project_name"), c.get("agent")
|
|
288
|
+
if not proj or not agent:
|
|
289
|
+
continue
|
|
290
|
+
if not c.get("respawn_allowed", True):
|
|
291
|
+
# mig404: not allowed = rate-limited (<60s since last respawn) or at the cap.
|
|
292
|
+
# mc_record_respawn v2 sets desired_state='crashed' ATOMICALLY at the cap, so we
|
|
293
|
+
# do NOT re-record here (that would inflate the count on a mere rate-limit skip).
|
|
294
|
+
_log(f"SKIP respawn {proj}/{agent}: not allowed (count={c.get('respawn_count')}, rate-limited/at-cap)")
|
|
295
|
+
continue
|
|
296
|
+
_log(f"RESPAWN {proj}/{agent} (stale {c.get('heartbeat_age_s')}s, count={c.get('respawn_count')})")
|
|
297
|
+
if _spawn_agent(proj, agent, headless=bool(c.get("headless"))):
|
|
298
|
+
rec = _rpc("mc_record_respawn",
|
|
299
|
+
{"p_api_key": api_key, "p_project_id": c.get("project_id"), "p_agent_name": agent})
|
|
300
|
+
# mig404: give-up is ATOMIC inside mc_record_respawn (sets desired_state='crashed').
|
|
301
|
+
# The old mc_set_desired_state(p_state=crashed) call passed NO agent id = no-op; dropped.
|
|
302
|
+
if rec and rec.get("give_up"):
|
|
303
|
+
_log(f"ALERT {proj}/{agent}: respawn cap hit — marked crashed (atomic). No more respawns.")
|
|
304
|
+
n += 1
|
|
305
|
+
return n
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# ------------------------------------------------------------------
|
|
309
|
+
# Recycle — relaunch a managed agent at a TASK BOUNDARY when it exceeds
|
|
310
|
+
# its recycle policy. Uptime-recycle is daemon-driven (needs mig370's
|
|
311
|
+
# project_name in the roster). Context-recycle is agent-cooperative (the
|
|
312
|
+
# agent self-persists handoff + exits at a boundary; our respawn path then
|
|
313
|
+
# relaunches it). Recorded via mc_record_recycle — NEVER counted against
|
|
314
|
+
# the crash respawn cap.
|
|
315
|
+
# ------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
_HOSTD_STATE_PATH = STATE_DIR / "hostd_state.json"
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _load_state() -> dict:
|
|
321
|
+
try:
|
|
322
|
+
if _HOSTD_STATE_PATH.exists():
|
|
323
|
+
return json.loads(_HOSTD_STATE_PATH.read_text(encoding="utf-8"))
|
|
324
|
+
except Exception:
|
|
325
|
+
pass
|
|
326
|
+
return {}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _save_state(st: dict) -> None:
|
|
330
|
+
try:
|
|
331
|
+
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
332
|
+
_HOSTD_STATE_PATH.write_text(json.dumps(st), encoding="utf-8")
|
|
333
|
+
except Exception:
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _do_recycles(api_key: str, host_id: str) -> int:
|
|
338
|
+
"""Uptime-based recycle at task boundary. Returns number recycled."""
|
|
339
|
+
cfg = _rpc("mc_host_config_get", {"p_api_key": api_key, "p_host_id": host_id})
|
|
340
|
+
if not cfg or not cfg.get("ok"):
|
|
341
|
+
return 0
|
|
342
|
+
pol = _rpc("mc_host_recycle_policy", {"p_api_key": api_key, "p_host_id": host_id})
|
|
343
|
+
mode = (pol or {}).get("recycle_mode")
|
|
344
|
+
value = (pol or {}).get("recycle_value")
|
|
345
|
+
if mode != "time" or not value:
|
|
346
|
+
return 0 # context-recycle is agent-cooperative; only time is daemon-driven
|
|
347
|
+
st = _load_state()
|
|
348
|
+
now = time.time()
|
|
349
|
+
n = 0
|
|
350
|
+
seen = set()
|
|
351
|
+
for a in cfg.get("agents", []):
|
|
352
|
+
if a.get("desired_state") != "running":
|
|
353
|
+
continue
|
|
354
|
+
proj, agent = a.get("project_name"), a.get("name")
|
|
355
|
+
if not proj or not agent:
|
|
356
|
+
continue
|
|
357
|
+
key = f"{a.get('project_id')}/{agent}"
|
|
358
|
+
seen.add(key)
|
|
359
|
+
first = st.get(key)
|
|
360
|
+
if first is None:
|
|
361
|
+
st[key] = now # start the uptime clock on first observation
|
|
362
|
+
continue
|
|
363
|
+
if (a.get("status") or "") in BUSY_STATUSES:
|
|
364
|
+
continue # task boundary only — never mid-task
|
|
365
|
+
if (now - first) >= float(value) * 3600.0:
|
|
366
|
+
_log(f"RECYCLE {proj}/{agent} (uptime {(now-first)/3600:.1f}h >= {value}h)")
|
|
367
|
+
# Server-authorized clean-exit (task 548c863e, mig 364): SIGNAL the
|
|
368
|
+
# recycle instead of spawning. A direct _spawn_agent here duplicates
|
|
369
|
+
# the still-alive process. mc_request_recycle sets a flag; the
|
|
370
|
+
# agent's wait-loop consumes it, returns must_exit/reason=recycle,
|
|
371
|
+
# exits CLEAN (Stop-hook writes the handoff), then the respawn path
|
|
372
|
+
# (_do_respawns) relaunches it fresh. Recorded via mc_record_recycle
|
|
373
|
+
# so it's NEVER counted against the crash respawn cap.
|
|
374
|
+
req = _rpc("mc_request_recycle",
|
|
375
|
+
{"p_api_key": api_key, "p_project_id": a.get("project_id"), "p_agent_name": agent})
|
|
376
|
+
if req and req.get("ok") and req.get("requested"):
|
|
377
|
+
_rpc("mc_record_recycle",
|
|
378
|
+
{"p_api_key": api_key, "p_project_id": a.get("project_id"), "p_agent_name": agent})
|
|
379
|
+
st[key] = now # reset clock after signaling recycle
|
|
380
|
+
n += 1
|
|
381
|
+
elif req and req.get("ok") and not req.get("requested"):
|
|
382
|
+
# Agent flipped busy between the roster read and the request —
|
|
383
|
+
# skip; retry next sweep at the next task boundary.
|
|
384
|
+
_log(f"SKIP recycle {proj}/{agent}: {req.get('reason','not_requested')}")
|
|
385
|
+
# prune state for agents no longer managed on this host
|
|
386
|
+
for k in [k for k in st if k not in seen]:
|
|
387
|
+
st.pop(k, None)
|
|
388
|
+
_save_state(st)
|
|
389
|
+
return n
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
# ------------------------------------------------------------------
|
|
393
|
+
# launchd trigger-start (macOS) — extends supervisor.py pattern.
|
|
394
|
+
# The hostd daemon is NOT autostart-at-login: RunAtLoad=false. It is
|
|
395
|
+
# kicked off on demand (button -> meshcode:// -> `launchctl kickstart`)
|
|
396
|
+
# and KeepAlive keeps it alive while it runs. One job per host.
|
|
397
|
+
# ------------------------------------------------------------------
|
|
398
|
+
|
|
399
|
+
_HOSTD_PLIST_LABEL = "io.meshcode.hostd"
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _hostd_plist_path():
|
|
403
|
+
return Path.home() / "Library" / "LaunchAgents" / f"{_HOSTD_PLIST_LABEL}.plist"
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _hostd_plist_xml() -> str:
|
|
407
|
+
import shutil
|
|
408
|
+
mc = shutil.which("meshcode") or f"{sys.executable} -m meshcode"
|
|
409
|
+
args = (mc.split() + ["hostd", "run"]) if " -m " in mc else [mc, "hostd", "run"]
|
|
410
|
+
args_xml = "\n".join(f" <string>{a}</string>" for a in args)
|
|
411
|
+
logdir = STATE_DIR / "logs"
|
|
412
|
+
logdir.mkdir(parents=True, exist_ok=True)
|
|
413
|
+
return f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
414
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
415
|
+
<plist version="1.0">
|
|
416
|
+
<dict>
|
|
417
|
+
<key>Label</key><string>{_HOSTD_PLIST_LABEL}</string>
|
|
418
|
+
<key>ProgramArguments</key>
|
|
419
|
+
<array>
|
|
420
|
+
{args_xml}
|
|
421
|
+
</array>
|
|
422
|
+
<key>RunAtLoad</key><false/>
|
|
423
|
+
<key>KeepAlive</key><true/>
|
|
424
|
+
<key>ThrottleInterval</key><integer>10</integer>
|
|
425
|
+
<key>StandardOutPath</key><string>{logdir / "hostd.stdout.log"}</string>
|
|
426
|
+
<key>StandardErrorPath</key><string>{logdir / "hostd.stderr.log"}</string>
|
|
427
|
+
<key>EnvironmentVariables</key>
|
|
428
|
+
<dict>
|
|
429
|
+
<key>PATH</key><string>/usr/local/bin:/usr/bin:/bin:{os.path.dirname(sys.executable)}</string>
|
|
430
|
+
<key>MESHCODE_NO_AUTO_UPDATE</key><string>1</string>
|
|
431
|
+
</dict>
|
|
432
|
+
</dict>
|
|
433
|
+
</plist>
|
|
434
|
+
"""
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
_HOSTD_TASK_NAME = "MeshCode hostd"
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _hostd_install_windows() -> int:
|
|
441
|
+
"""Windows Task Scheduler job (mesh-core ITEM2): at-logon, hidden, restart 3x/1min, no
|
|
442
|
+
time-limit, runs `meshcode hostd run` with auto-update off. Proven model on Samuel's box
|
|
443
|
+
(.meshcode/hostd-launch.cmd + scheduled task 'MeshCode hostd')."""
|
|
444
|
+
mc = shutil.which("meshcode") or "meshcode"
|
|
445
|
+
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
446
|
+
(STATE_DIR / "logs").mkdir(parents=True, exist_ok=True)
|
|
447
|
+
launch_cmd = STATE_DIR / "hostd-launch.cmd"
|
|
448
|
+
launch_cmd.write_text(
|
|
449
|
+
"@echo off\r\n"
|
|
450
|
+
'set "MESHCODE_NO_AUTO_UPDATE=1"\r\n'
|
|
451
|
+
f'"{mc}" hostd run\r\n',
|
|
452
|
+
encoding="utf-8",
|
|
453
|
+
)
|
|
454
|
+
# Register-ScheduledTask (PowerShell) for fidelity: hidden + restart + unlimited + at-logon.
|
|
455
|
+
ps = (
|
|
456
|
+
'$a = New-ScheduledTaskAction -Execute "cmd.exe" -Argument \'/c "' + str(launch_cmd) + '"\';'
|
|
457
|
+
'$t = New-ScheduledTaskTrigger -AtLogOn;'
|
|
458
|
+
'$s = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries'
|
|
459
|
+
' -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 3'
|
|
460
|
+
' -RestartInterval (New-TimeSpan -Minutes 1) -Hidden;'
|
|
461
|
+
'$p = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Limited;'
|
|
462
|
+
"Register-ScheduledTask -TaskName '" + _HOSTD_TASK_NAME + "' -Action $a -Trigger $t"
|
|
463
|
+
' -Settings $s -Principal $p -Force | Out-Null'
|
|
464
|
+
)
|
|
465
|
+
r = subprocess.run(["powershell", "-NoProfile", "-Command", ps], capture_output=True, text=True)
|
|
466
|
+
if r.returncode != 0:
|
|
467
|
+
print(f"[hostd] Register-ScheduledTask failed: {r.stderr.strip()}", file=sys.stderr)
|
|
468
|
+
return 1
|
|
469
|
+
print(f"[hostd] installed Windows Task Scheduler job '{_HOSTD_TASK_NAME}' (at-logon, hidden, restart). launcher: {launch_cmd}")
|
|
470
|
+
print(f'[hostd] start now: schtasks /Run /TN "{_HOSTD_TASK_NAME}"')
|
|
471
|
+
return 0
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _hostd_install() -> int:
|
|
475
|
+
import platform
|
|
476
|
+
if platform.system() == "Windows":
|
|
477
|
+
return _hostd_install_windows()
|
|
478
|
+
if platform.system() != "Darwin":
|
|
479
|
+
print("[hostd] launchd install is macOS only (Linux: use systemd --user; or run `meshcode hostd run` under your own supervisor).", file=sys.stderr)
|
|
480
|
+
return 2
|
|
481
|
+
plist = _hostd_plist_path()
|
|
482
|
+
plist.parent.mkdir(parents=True, exist_ok=True)
|
|
483
|
+
plist.write_text(_hostd_plist_xml(), encoding="utf-8")
|
|
484
|
+
subprocess.run(["launchctl", "unload", str(plist)], capture_output=True, text=True)
|
|
485
|
+
r = subprocess.run(["launchctl", "load", str(plist)], capture_output=True, text=True)
|
|
486
|
+
if r.returncode != 0:
|
|
487
|
+
print(f"[hostd] launchctl load failed: {r.stderr.strip()}", file=sys.stderr)
|
|
488
|
+
return 1
|
|
489
|
+
print(f"[hostd] installed (RunAtLoad=false, trigger-start). plist: {plist}")
|
|
490
|
+
print(f"[hostd] start now: launchctl kickstart gui/$(id -u)/{_HOSTD_PLIST_LABEL}")
|
|
491
|
+
return 0
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _hostd_uninstall() -> int:
|
|
495
|
+
if sys.platform == "win32":
|
|
496
|
+
r = subprocess.run(["schtasks", "/Delete", "/TN", _HOSTD_TASK_NAME, "/F"],
|
|
497
|
+
capture_output=True, text=True)
|
|
498
|
+
if r.returncode != 0 and "cannot find" not in (r.stderr + r.stdout).lower():
|
|
499
|
+
print(f"[hostd] schtasks /Delete failed: {r.stderr.strip()}", file=sys.stderr)
|
|
500
|
+
return 1
|
|
501
|
+
print("[hostd] uninstalled (Windows Task Scheduler)")
|
|
502
|
+
return 0
|
|
503
|
+
plist = _hostd_plist_path()
|
|
504
|
+
if not plist.exists():
|
|
505
|
+
print("[hostd] not installed")
|
|
506
|
+
return 0
|
|
507
|
+
subprocess.run(["launchctl", "unload", str(plist)], capture_output=True, text=True)
|
|
508
|
+
plist.unlink(missing_ok=True)
|
|
509
|
+
print("[hostd] uninstalled")
|
|
510
|
+
return 0
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def cmd_hostd(args: list) -> int:
|
|
514
|
+
"""Entry point for `meshcode hostd ...`."""
|
|
515
|
+
if not args or args[0] in ("-h", "--help"):
|
|
516
|
+
print(__doc__)
|
|
517
|
+
return 0
|
|
518
|
+
sub = args[0]
|
|
519
|
+
host_id = get_host_id()
|
|
520
|
+
api_key = _api_key()
|
|
521
|
+
|
|
522
|
+
if sub == "status":
|
|
523
|
+
if not api_key:
|
|
524
|
+
print("[hostd] no api key configured", file=sys.stderr)
|
|
525
|
+
return 1
|
|
526
|
+
res = _rpc("mc_agents_needing_respawn",
|
|
527
|
+
{"p_api_key": api_key, "p_host_id": host_id, "p_stale_seconds": STALE_SECONDS})
|
|
528
|
+
print(json.dumps({"host_id": host_id, "needing_respawn": res}, indent=2))
|
|
529
|
+
return 0
|
|
530
|
+
|
|
531
|
+
if sub == "install":
|
|
532
|
+
return _hostd_install()
|
|
533
|
+
|
|
534
|
+
if sub == "uninstall":
|
|
535
|
+
return _hostd_uninstall()
|
|
536
|
+
|
|
537
|
+
if sub == "run":
|
|
538
|
+
if not api_key:
|
|
539
|
+
_log("FATAL: no api key — run `meshcode login` (key is read from the keychain)")
|
|
540
|
+
return 1
|
|
541
|
+
# Register this host in mc_host_config so the dashboard can list it as a
|
|
542
|
+
# launch target (Path2 canonical: dashboard -> mc_host_set_agents ->
|
|
543
|
+
# mc_agents.desired_state='running' -> this daemon polls + spawns).
|
|
544
|
+
# mc_host_config_set upserts (host_id, owner_user_id) idempotently.
|
|
545
|
+
_reg = _rpc("mc_host_config_set", {"p_api_key": api_key, "p_host_id": host_id})
|
|
546
|
+
if _reg and _reg.get("ok"):
|
|
547
|
+
_log(f"registered host {host_id} in mc_host_config")
|
|
548
|
+
else:
|
|
549
|
+
_log(f"WARN: host registration failed (dashboard may not list this host): {_reg}")
|
|
550
|
+
_log(f"hostd starting — host_id={host_id} interval={POLL_INTERVAL_SEC}s stale={STALE_SECONDS}s")
|
|
551
|
+
while True:
|
|
552
|
+
try:
|
|
553
|
+
relaunched = _do_respawns(api_key, host_id)
|
|
554
|
+
recycled = _do_recycles(api_key, host_id)
|
|
555
|
+
if relaunched or recycled:
|
|
556
|
+
_log(f"sweep done — {relaunched} respawned, {recycled} recycled")
|
|
557
|
+
except Exception as e:
|
|
558
|
+
_log(f"WARN: sweep error: {e}")
|
|
559
|
+
time.sleep(POLL_INTERVAL_SEC)
|
|
560
|
+
|
|
561
|
+
print(f"[hostd] unknown subcommand: {sub}", file=sys.stderr)
|
|
562
|
+
return 1
|
|
@@ -108,10 +108,17 @@ def _spawn_terminal_linux(cmd: str) -> tuple[bool, str]:
|
|
|
108
108
|
|
|
109
109
|
def _spawn_terminal_windows(cmd: str) -> tuple[bool, str]:
|
|
110
110
|
"""Spawn `cmd` in Windows Terminal (preferred) or cmd.exe."""
|
|
111
|
-
|
|
111
|
+
# mesh-core ITEM3: wt.exe is a Store-app shim under %LOCALAPPDATA%\Microsoft\WindowsApps and is
|
|
112
|
+
# frequently NOT on PATH, so shutil.which misses it — resolve that location explicitly.
|
|
113
|
+
wt = shutil.which("wt.exe")
|
|
114
|
+
if not wt:
|
|
115
|
+
cand = Path(os.environ.get("LOCALAPPDATA", "")) / "Microsoft" / "WindowsApps" / "wt.exe"
|
|
116
|
+
if cand.exists():
|
|
117
|
+
wt = str(cand)
|
|
118
|
+
if wt:
|
|
112
119
|
try:
|
|
113
120
|
# `wt -w 0 nt` opens a new tab in existing window
|
|
114
|
-
subprocess.Popen([
|
|
121
|
+
subprocess.Popen([wt, "-w", "0", "nt", "cmd", "/k", cmd])
|
|
115
122
|
return True, "wt"
|
|
116
123
|
except Exception as e:
|
|
117
124
|
return False, f"wt.exe: {e}"
|
|
@@ -155,7 +162,12 @@ def cmd_launch_batch(agent_names: Iterable[str]) -> int:
|
|
|
155
162
|
mc_bin = shutil.which("meshcode") or "meshcode"
|
|
156
163
|
|
|
157
164
|
for name in names:
|
|
158
|
-
|
|
165
|
+
# PER-PLATFORM quoting (mesh-core FIX2): cmd.exe wants double-quotes, not POSIX shlex
|
|
166
|
+
# single-quotes (cmd.exe passes single-quotes through literally -> file-not-found).
|
|
167
|
+
if sys.platform == "win32":
|
|
168
|
+
cmd = f'"{mc_bin}" run "{name}"'
|
|
169
|
+
else:
|
|
170
|
+
cmd = f"{shlex.quote(mc_bin)} run {shlex.quote(name)}"
|
|
159
171
|
ok, info = _spawn_terminal(cmd)
|
|
160
172
|
if ok:
|
|
161
173
|
launched.append(name)
|
|
@@ -14,6 +14,7 @@ meshcode/date_parse.py
|
|
|
14
14
|
meshcode/doctor.py
|
|
15
15
|
meshcode/error_hints.py
|
|
16
16
|
meshcode/exceptions.py
|
|
17
|
+
meshcode/hostd 3.py
|
|
17
18
|
meshcode/hostd.py
|
|
18
19
|
meshcode/invites.py
|
|
19
20
|
meshcode/launcher.py
|
|
@@ -223,6 +224,7 @@ tests/test_autonomous_prompt_inject 19.py
|
|
|
223
224
|
tests/test_autonomous_prompt_inject 2.py
|
|
224
225
|
tests/test_autonomous_prompt_inject 20.py
|
|
225
226
|
tests/test_autonomous_prompt_inject 21.py
|
|
227
|
+
tests/test_autonomous_prompt_inject 22.py
|
|
226
228
|
tests/test_autonomous_prompt_inject 3.py
|
|
227
229
|
tests/test_autonomous_prompt_inject 4.py
|
|
228
230
|
tests/test_autonomous_prompt_inject 5.py
|