meshcode 2.11.75__tar.gz → 2.11.76__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.75 → meshcode-2.11.76}/PKG-INFO +1 -1
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/__init__.py +1 -1
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/hostd.py +18 -11
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/run_agent.py +6 -5
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode.egg-info/PKG-INFO +1 -1
- meshcode-2.11.75/meshcode.egg-info/SOURCES 2.txt → meshcode-2.11.76/meshcode.egg-info/SOURCES.txt +0 -12
- {meshcode-2.11.75 → meshcode-2.11.76}/pyproject.toml +1 -1
- meshcode-2.11.75/meshcode/hostd 3.py +0 -509
- meshcode-2.11.75/meshcode.egg-info/PKG-INFO 2 +0 -452
- meshcode-2.11.75/meshcode.egg-info/SOURCES.txt +0 -266
- meshcode-2.11.75/meshcode.egg-info/dependency_links 2.txt +0 -1
- meshcode-2.11.75/meshcode.egg-info/entry_points 2.txt +0 -3
- meshcode-2.11.75/meshcode.egg-info/requires 2.txt +0 -13
- meshcode-2.11.75/meshcode.egg-info/top_level 2.txt +0 -4
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 10.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 11.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 12.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 13.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 14.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 15.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 16.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 17.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 18.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 19.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 2.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 20.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 21.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 22.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 3.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 4.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 5.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 6.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 7.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 8.py +0 -126
- meshcode-2.11.75/tests/test_autonomous_prompt_inject 9.py +0 -126
- {meshcode-2.11.75 → meshcode-2.11.76}/README.md +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/__main__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/cli.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/compat.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/daemon.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/doctor.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/invites.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/launcher.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/preferences.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/secrets.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/self_update.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/up.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/upload.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/comms_v4.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_core.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/comms_v4.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_core.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/comms_v4.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_core.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/setup.cfg +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_core.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_doctor.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -212,10 +212,13 @@ def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
|
|
|
212
212
|
# background, NO terminal — UNIVERSAL macOS/Linux/Windows (task c1a6c6a8, mesh-dev specs).
|
|
213
213
|
# Clean env (a stale CLAUDECODE aborts `meshcode run`); keep crash logs in a per-agent logfile
|
|
214
214
|
# (NOT DEVNULL — we want to debug headless agents that die on boot).
|
|
215
|
+
# task 14782bb4: spawned agents DO auto-update (non-blocking) — strip any inherited
|
|
216
|
+
# NO_*UPDATE so the agent's launch runs the bg updater (the daemon launcher stays disabled).
|
|
217
|
+
# No hang: run_agent now uses the NON-blocking updater + we spawn via `python -m meshcode`
|
|
218
|
+
# (not the .exe shim) so a bg `pip install -U` can replace meshcode.exe on Windows.
|
|
215
219
|
env = {k: v for k, v in os.environ.items()
|
|
216
|
-
if k not in ("CLAUDECODE", "CLAUDE_CODE_SESSION"
|
|
217
|
-
|
|
218
|
-
env["MESHCODE_NO_UPDATE"] = "1" # belt-and-suspenders: var honored pre-2.11.74 (no launch-hang)
|
|
220
|
+
if k not in ("CLAUDECODE", "CLAUDE_CODE_SESSION",
|
|
221
|
+
"MESHCODE_NO_UPDATE", "MESHCODE_NO_AUTO_UPDATE")}
|
|
219
222
|
log_dir = STATE_DIR / "logs"
|
|
220
223
|
try:
|
|
221
224
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -236,11 +239,11 @@ def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
|
|
|
236
239
|
}
|
|
237
240
|
if sys.platform == "win32":
|
|
238
241
|
# CREATE_NO_WINDOW (0x08000000) | DETACHED_PROCESS (0x08): no console window, fully
|
|
239
|
-
# detached from this daemon.
|
|
240
|
-
#
|
|
242
|
+
# detached from this daemon. NEVER pythonw (swallows stderr). Ensure venv Scripts +
|
|
243
|
+
# System32 on PATH (Windows PATH cap ~32k).
|
|
241
244
|
kwargs["creationflags"] = 0x08000000 | 0x00000008
|
|
242
245
|
try:
|
|
243
|
-
scripts = str(Path(
|
|
246
|
+
scripts = str(Path(sys.executable).resolve().parent) # venv Scripts dir
|
|
244
247
|
except Exception:
|
|
245
248
|
scripts = ""
|
|
246
249
|
sysroot = os.environ.get("SystemRoot", r"C:\Windows")
|
|
@@ -250,7 +253,9 @@ def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
|
|
|
250
253
|
# POSIX: detach into its own session so it survives the daemon + has no controlling tty.
|
|
251
254
|
kwargs["start_new_session"] = True
|
|
252
255
|
try:
|
|
253
|
-
|
|
256
|
+
# `python -m meshcode` (NOT the meshcode.exe shim) so the .exe isn't held open by the
|
|
257
|
+
# agent -> a background `pip install -U` can replace it on Windows (task 14782bb4 #4).
|
|
258
|
+
subprocess.Popen([sys.executable, "-m", "meshcode", "run", target], **kwargs)
|
|
254
259
|
_log(f"spawned {target} HEADLESS (no window, {sys.platform}; log={log_path})")
|
|
255
260
|
return True
|
|
256
261
|
except Exception as e:
|
|
@@ -267,13 +272,15 @@ def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
|
|
|
267
272
|
# command PER-PLATFORM (mesh-core FIX1, 0x80070002): cmd.exe uses & (NOT ';'), set "V=" to clear
|
|
268
273
|
# (NOT bash unset/export), double-quotes (NOT shlex POSIX), no exec — else Windows Terminal splits
|
|
269
274
|
# on ';' -> file-not-found. Layered ON TOP of the 2.11.72 headless-flags fix (visible branch only).
|
|
275
|
+
# task 14782bb4: CLEAR any inherited NO_*UPDATE (so the agent auto-updates non-blocking) and run
|
|
276
|
+
# via `python -m meshcode` (NOT the .exe shim) so a bg `pip install -U` can replace meshcode.exe.
|
|
270
277
|
if sys.platform == "win32":
|
|
271
278
|
cmd = (f'set "CLAUDECODE=" & set "CLAUDE_CODE_SESSION=" & '
|
|
272
|
-
f'set "
|
|
273
|
-
f'"{
|
|
279
|
+
f'set "MESHCODE_NO_UPDATE=" & set "MESHCODE_NO_AUTO_UPDATE=" & '
|
|
280
|
+
f'"{sys.executable}" -m meshcode run "{target}"')
|
|
274
281
|
else:
|
|
275
|
-
cmd = (f"unset CLAUDECODE CLAUDE_CODE_SESSION
|
|
276
|
-
f"
|
|
282
|
+
cmd = (f"unset CLAUDECODE CLAUDE_CODE_SESSION MESHCODE_NO_UPDATE MESHCODE_NO_AUTO_UPDATE; "
|
|
283
|
+
f"exec {shlex.quote(sys.executable)} -m meshcode run {shlex.quote(target)}")
|
|
277
284
|
try:
|
|
278
285
|
from meshcode import protocol_handler as _ph
|
|
279
286
|
ok, info = _ph._spawn_terminal(cmd)
|
|
@@ -698,13 +698,14 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
698
698
|
# Propagate autonomous flag to the editor + MCP child via env var.
|
|
699
699
|
if autonomous:
|
|
700
700
|
os.environ["MESHCODE_AUTONOMOUS"] = "1"
|
|
701
|
-
#
|
|
702
|
-
#
|
|
703
|
-
#
|
|
704
|
-
#
|
|
701
|
+
# NON-BLOCKING self-update (task 14782bb4): kick a detached background `pip install -U meshcode`
|
|
702
|
+
# and return IMMEDIATELY — the launch NEVER waits/hangs (Samuel req; the old blocking variant hung
|
|
703
|
+
# every Windows launch on WinError 32). This launch runs the current version; the next spawn/recycle
|
|
704
|
+
# picks up the new one (two-launch async). Skip in dry-run (CI/pre-publish smokes must not fork pip).
|
|
705
|
+
# Fully opt-out via MESHCODE_NO_UPDATE / MESHCODE_NO_AUTO_UPDATE (handled inside check_and_maybe_update).
|
|
705
706
|
if not dry_run:
|
|
706
707
|
try:
|
|
707
|
-
self_update.
|
|
708
|
+
self_update.check_and_maybe_update()
|
|
708
709
|
except Exception:
|
|
709
710
|
pass
|
|
710
711
|
|
meshcode-2.11.75/meshcode.egg-info/SOURCES 2.txt → meshcode-2.11.76/meshcode.egg-info/SOURCES.txt
RENAMED
|
@@ -5,9 +5,6 @@ meshcode/__main__.py
|
|
|
5
5
|
meshcode/_stop_hook_template.py
|
|
6
6
|
meshcode/ascii_art.py
|
|
7
7
|
meshcode/atomic_push.py
|
|
8
|
-
meshcode/claude_update 2.py
|
|
9
|
-
meshcode/claude_update 3.py
|
|
10
|
-
meshcode/claude_update 4.py
|
|
11
8
|
meshcode/claude_update.py
|
|
12
9
|
meshcode/cli.py
|
|
13
10
|
meshcode/comms_v4.py
|
|
@@ -17,9 +14,6 @@ meshcode/date_parse.py
|
|
|
17
14
|
meshcode/doctor.py
|
|
18
15
|
meshcode/error_hints.py
|
|
19
16
|
meshcode/exceptions.py
|
|
20
|
-
meshcode/hostd 2.py
|
|
21
|
-
meshcode/hostd 3.py
|
|
22
|
-
meshcode/hostd 4.py
|
|
23
17
|
meshcode/hostd.py
|
|
24
18
|
meshcode/invites.py
|
|
25
19
|
meshcode/launcher.py
|
|
@@ -34,8 +28,6 @@ meshcode/secrets.py
|
|
|
34
28
|
meshcode/self_update.py
|
|
35
29
|
meshcode/setup_clients.py
|
|
36
30
|
meshcode/supervisor.py
|
|
37
|
-
meshcode/up 2.py
|
|
38
|
-
meshcode/up 3.py
|
|
39
31
|
meshcode/up.py
|
|
40
32
|
meshcode/upload.py
|
|
41
33
|
meshcode-backend-wt/comms_v4.py
|
|
@@ -212,10 +204,6 @@ tests/test_auto_update_hardening.py
|
|
|
212
204
|
tests/test_autonomous_closegap_1.py
|
|
213
205
|
tests/test_autonomous_closegap_2.py
|
|
214
206
|
tests/test_autonomous_closegap_3.py
|
|
215
|
-
tests/test_autonomous_prompt_inject 2.py
|
|
216
|
-
tests/test_autonomous_prompt_inject 3.py
|
|
217
|
-
tests/test_autonomous_prompt_inject 4.py
|
|
218
|
-
tests/test_autonomous_prompt_inject 5.py
|
|
219
207
|
tests/test_autonomous_prompt_inject.py
|
|
220
208
|
tests/test_boot_bug_regression.py
|
|
221
209
|
tests/test_color_truecolor.py
|
|
@@ -1,509 +0,0 @@
|
|
|
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).
|
|
255
|
-
# Prefix the shell command so the spawned terminal starts with a clean env.
|
|
256
|
-
cmd = (f"unset CLAUDECODE CLAUDE_CODE_SESSION; export MESHCODE_NO_AUTO_UPDATE=1; "
|
|
257
|
-
f"exec {shlex.quote(bin_)} run {shlex.quote(target)}")
|
|
258
|
-
try:
|
|
259
|
-
from meshcode import protocol_handler as _ph
|
|
260
|
-
ok, info = _ph._spawn_terminal(cmd)
|
|
261
|
-
if ok:
|
|
262
|
-
_log(f"spawned {target} in visible terminal ({info})")
|
|
263
|
-
return True
|
|
264
|
-
_log(f"WARN: visible spawn {target} FAILED ({info}) — agent NOT launched. "
|
|
265
|
-
f"macOS: grant Terminal Automation (System Settings > Privacy & Security "
|
|
266
|
-
f"> Automation), or ensure `open` works. NOT falling back to headless.")
|
|
267
|
-
return False
|
|
268
|
-
except Exception as e:
|
|
269
|
-
_log(f"WARN: spawn {target} failed: {e}")
|
|
270
|
-
return False
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
def _do_respawns(api_key: str, host_id: str) -> int:
|
|
274
|
-
"""One respawn sweep. Returns number relaunched."""
|
|
275
|
-
res = _rpc("mc_agents_needing_respawn",
|
|
276
|
-
{"p_api_key": api_key, "p_host_id": host_id, "p_stale_seconds": STALE_SECONDS})
|
|
277
|
-
if not res or not res.get("ok"):
|
|
278
|
-
return 0
|
|
279
|
-
n = 0
|
|
280
|
-
for c in res.get("candidates", []):
|
|
281
|
-
proj, agent = c.get("project_name"), c.get("agent")
|
|
282
|
-
if not proj or not agent:
|
|
283
|
-
continue
|
|
284
|
-
if not c.get("respawn_allowed", True):
|
|
285
|
-
# mig404: not allowed = rate-limited (<60s since last respawn) or at the cap.
|
|
286
|
-
# mc_record_respawn v2 sets desired_state='crashed' ATOMICALLY at the cap, so we
|
|
287
|
-
# do NOT re-record here (that would inflate the count on a mere rate-limit skip).
|
|
288
|
-
_log(f"SKIP respawn {proj}/{agent}: not allowed (count={c.get('respawn_count')}, rate-limited/at-cap)")
|
|
289
|
-
continue
|
|
290
|
-
_log(f"RESPAWN {proj}/{agent} (stale {c.get('heartbeat_age_s')}s, count={c.get('respawn_count')})")
|
|
291
|
-
if _spawn_agent(proj, agent, headless=bool(c.get("headless"))):
|
|
292
|
-
rec = _rpc("mc_record_respawn",
|
|
293
|
-
{"p_api_key": api_key, "p_project_id": c.get("project_id"), "p_agent_name": agent})
|
|
294
|
-
# mig404: give-up is ATOMIC inside mc_record_respawn (sets desired_state='crashed').
|
|
295
|
-
# The old mc_set_desired_state(p_state=crashed) call passed NO agent id = no-op; dropped.
|
|
296
|
-
if rec and rec.get("give_up"):
|
|
297
|
-
_log(f"ALERT {proj}/{agent}: respawn cap hit — marked crashed (atomic). No more respawns.")
|
|
298
|
-
n += 1
|
|
299
|
-
return n
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
# ------------------------------------------------------------------
|
|
303
|
-
# Recycle — relaunch a managed agent at a TASK BOUNDARY when it exceeds
|
|
304
|
-
# its recycle policy. Uptime-recycle is daemon-driven (needs mig370's
|
|
305
|
-
# project_name in the roster). Context-recycle is agent-cooperative (the
|
|
306
|
-
# agent self-persists handoff + exits at a boundary; our respawn path then
|
|
307
|
-
# relaunches it). Recorded via mc_record_recycle — NEVER counted against
|
|
308
|
-
# the crash respawn cap.
|
|
309
|
-
# ------------------------------------------------------------------
|
|
310
|
-
|
|
311
|
-
_HOSTD_STATE_PATH = STATE_DIR / "hostd_state.json"
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
def _load_state() -> dict:
|
|
315
|
-
try:
|
|
316
|
-
if _HOSTD_STATE_PATH.exists():
|
|
317
|
-
return json.loads(_HOSTD_STATE_PATH.read_text(encoding="utf-8"))
|
|
318
|
-
except Exception:
|
|
319
|
-
pass
|
|
320
|
-
return {}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
def _save_state(st: dict) -> None:
|
|
324
|
-
try:
|
|
325
|
-
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
326
|
-
_HOSTD_STATE_PATH.write_text(json.dumps(st), encoding="utf-8")
|
|
327
|
-
except Exception:
|
|
328
|
-
pass
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def _do_recycles(api_key: str, host_id: str) -> int:
|
|
332
|
-
"""Uptime-based recycle at task boundary. Returns number recycled."""
|
|
333
|
-
cfg = _rpc("mc_host_config_get", {"p_api_key": api_key, "p_host_id": host_id})
|
|
334
|
-
if not cfg or not cfg.get("ok"):
|
|
335
|
-
return 0
|
|
336
|
-
pol = _rpc("mc_host_recycle_policy", {"p_api_key": api_key, "p_host_id": host_id})
|
|
337
|
-
mode = (pol or {}).get("recycle_mode")
|
|
338
|
-
value = (pol or {}).get("recycle_value")
|
|
339
|
-
if mode != "time" or not value:
|
|
340
|
-
return 0 # context-recycle is agent-cooperative; only time is daemon-driven
|
|
341
|
-
st = _load_state()
|
|
342
|
-
now = time.time()
|
|
343
|
-
n = 0
|
|
344
|
-
seen = set()
|
|
345
|
-
for a in cfg.get("agents", []):
|
|
346
|
-
if a.get("desired_state") != "running":
|
|
347
|
-
continue
|
|
348
|
-
proj, agent = a.get("project_name"), a.get("name")
|
|
349
|
-
if not proj or not agent:
|
|
350
|
-
continue
|
|
351
|
-
key = f"{a.get('project_id')}/{agent}"
|
|
352
|
-
seen.add(key)
|
|
353
|
-
first = st.get(key)
|
|
354
|
-
if first is None:
|
|
355
|
-
st[key] = now # start the uptime clock on first observation
|
|
356
|
-
continue
|
|
357
|
-
if (a.get("status") or "") in BUSY_STATUSES:
|
|
358
|
-
continue # task boundary only — never mid-task
|
|
359
|
-
if (now - first) >= float(value) * 3600.0:
|
|
360
|
-
_log(f"RECYCLE {proj}/{agent} (uptime {(now-first)/3600:.1f}h >= {value}h)")
|
|
361
|
-
# Server-authorized clean-exit (task 548c863e, mig 364): SIGNAL the
|
|
362
|
-
# recycle instead of spawning. A direct _spawn_agent here duplicates
|
|
363
|
-
# the still-alive process. mc_request_recycle sets a flag; the
|
|
364
|
-
# agent's wait-loop consumes it, returns must_exit/reason=recycle,
|
|
365
|
-
# exits CLEAN (Stop-hook writes the handoff), then the respawn path
|
|
366
|
-
# (_do_respawns) relaunches it fresh. Recorded via mc_record_recycle
|
|
367
|
-
# so it's NEVER counted against the crash respawn cap.
|
|
368
|
-
req = _rpc("mc_request_recycle",
|
|
369
|
-
{"p_api_key": api_key, "p_project_id": a.get("project_id"), "p_agent_name": agent})
|
|
370
|
-
if req and req.get("ok") and req.get("requested"):
|
|
371
|
-
_rpc("mc_record_recycle",
|
|
372
|
-
{"p_api_key": api_key, "p_project_id": a.get("project_id"), "p_agent_name": agent})
|
|
373
|
-
st[key] = now # reset clock after signaling recycle
|
|
374
|
-
n += 1
|
|
375
|
-
elif req and req.get("ok") and not req.get("requested"):
|
|
376
|
-
# Agent flipped busy between the roster read and the request —
|
|
377
|
-
# skip; retry next sweep at the next task boundary.
|
|
378
|
-
_log(f"SKIP recycle {proj}/{agent}: {req.get('reason','not_requested')}")
|
|
379
|
-
# prune state for agents no longer managed on this host
|
|
380
|
-
for k in [k for k in st if k not in seen]:
|
|
381
|
-
st.pop(k, None)
|
|
382
|
-
_save_state(st)
|
|
383
|
-
return n
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
# ------------------------------------------------------------------
|
|
387
|
-
# launchd trigger-start (macOS) — extends supervisor.py pattern.
|
|
388
|
-
# The hostd daemon is NOT autostart-at-login: RunAtLoad=false. It is
|
|
389
|
-
# kicked off on demand (button -> meshcode:// -> `launchctl kickstart`)
|
|
390
|
-
# and KeepAlive keeps it alive while it runs. One job per host.
|
|
391
|
-
# ------------------------------------------------------------------
|
|
392
|
-
|
|
393
|
-
_HOSTD_PLIST_LABEL = "io.meshcode.hostd"
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
def _hostd_plist_path():
|
|
397
|
-
return Path.home() / "Library" / "LaunchAgents" / f"{_HOSTD_PLIST_LABEL}.plist"
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
def _hostd_plist_xml() -> str:
|
|
401
|
-
import shutil
|
|
402
|
-
mc = shutil.which("meshcode") or f"{sys.executable} -m meshcode"
|
|
403
|
-
args = (mc.split() + ["hostd", "run"]) if " -m " in mc else [mc, "hostd", "run"]
|
|
404
|
-
args_xml = "\n".join(f" <string>{a}</string>" for a in args)
|
|
405
|
-
logdir = STATE_DIR / "logs"
|
|
406
|
-
logdir.mkdir(parents=True, exist_ok=True)
|
|
407
|
-
return f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
408
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
409
|
-
<plist version="1.0">
|
|
410
|
-
<dict>
|
|
411
|
-
<key>Label</key><string>{_HOSTD_PLIST_LABEL}</string>
|
|
412
|
-
<key>ProgramArguments</key>
|
|
413
|
-
<array>
|
|
414
|
-
{args_xml}
|
|
415
|
-
</array>
|
|
416
|
-
<key>RunAtLoad</key><false/>
|
|
417
|
-
<key>KeepAlive</key><true/>
|
|
418
|
-
<key>ThrottleInterval</key><integer>10</integer>
|
|
419
|
-
<key>StandardOutPath</key><string>{logdir / "hostd.stdout.log"}</string>
|
|
420
|
-
<key>StandardErrorPath</key><string>{logdir / "hostd.stderr.log"}</string>
|
|
421
|
-
<key>EnvironmentVariables</key>
|
|
422
|
-
<dict>
|
|
423
|
-
<key>PATH</key><string>/usr/local/bin:/usr/bin:/bin:{os.path.dirname(sys.executable)}</string>
|
|
424
|
-
<key>MESHCODE_NO_AUTO_UPDATE</key><string>1</string>
|
|
425
|
-
</dict>
|
|
426
|
-
</dict>
|
|
427
|
-
</plist>
|
|
428
|
-
"""
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
def _hostd_install() -> int:
|
|
432
|
-
import platform
|
|
433
|
-
if platform.system() != "Darwin":
|
|
434
|
-
print("[hostd] launchd install is macOS only (Linux: use systemd --user; or run `meshcode hostd run` under your own supervisor).", file=sys.stderr)
|
|
435
|
-
return 2
|
|
436
|
-
plist = _hostd_plist_path()
|
|
437
|
-
plist.parent.mkdir(parents=True, exist_ok=True)
|
|
438
|
-
plist.write_text(_hostd_plist_xml(), encoding="utf-8")
|
|
439
|
-
subprocess.run(["launchctl", "unload", str(plist)], capture_output=True, text=True)
|
|
440
|
-
r = subprocess.run(["launchctl", "load", str(plist)], capture_output=True, text=True)
|
|
441
|
-
if r.returncode != 0:
|
|
442
|
-
print(f"[hostd] launchctl load failed: {r.stderr.strip()}", file=sys.stderr)
|
|
443
|
-
return 1
|
|
444
|
-
print(f"[hostd] installed (RunAtLoad=false, trigger-start). plist: {plist}")
|
|
445
|
-
print(f"[hostd] start now: launchctl kickstart gui/$(id -u)/{_HOSTD_PLIST_LABEL}")
|
|
446
|
-
return 0
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
def _hostd_uninstall() -> int:
|
|
450
|
-
plist = _hostd_plist_path()
|
|
451
|
-
if not plist.exists():
|
|
452
|
-
print("[hostd] not installed")
|
|
453
|
-
return 0
|
|
454
|
-
subprocess.run(["launchctl", "unload", str(plist)], capture_output=True, text=True)
|
|
455
|
-
plist.unlink(missing_ok=True)
|
|
456
|
-
print("[hostd] uninstalled")
|
|
457
|
-
return 0
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
def cmd_hostd(args: list) -> int:
|
|
461
|
-
"""Entry point for `meshcode hostd ...`."""
|
|
462
|
-
if not args or args[0] in ("-h", "--help"):
|
|
463
|
-
print(__doc__)
|
|
464
|
-
return 0
|
|
465
|
-
sub = args[0]
|
|
466
|
-
host_id = get_host_id()
|
|
467
|
-
api_key = _api_key()
|
|
468
|
-
|
|
469
|
-
if sub == "status":
|
|
470
|
-
if not api_key:
|
|
471
|
-
print("[hostd] no api key configured", file=sys.stderr)
|
|
472
|
-
return 1
|
|
473
|
-
res = _rpc("mc_agents_needing_respawn",
|
|
474
|
-
{"p_api_key": api_key, "p_host_id": host_id, "p_stale_seconds": STALE_SECONDS})
|
|
475
|
-
print(json.dumps({"host_id": host_id, "needing_respawn": res}, indent=2))
|
|
476
|
-
return 0
|
|
477
|
-
|
|
478
|
-
if sub == "install":
|
|
479
|
-
return _hostd_install()
|
|
480
|
-
|
|
481
|
-
if sub == "uninstall":
|
|
482
|
-
return _hostd_uninstall()
|
|
483
|
-
|
|
484
|
-
if sub == "run":
|
|
485
|
-
if not api_key:
|
|
486
|
-
_log("FATAL: no api key — run `meshcode login` (key is read from the keychain)")
|
|
487
|
-
return 1
|
|
488
|
-
# Register this host in mc_host_config so the dashboard can list it as a
|
|
489
|
-
# launch target (Path2 canonical: dashboard -> mc_host_set_agents ->
|
|
490
|
-
# mc_agents.desired_state='running' -> this daemon polls + spawns).
|
|
491
|
-
# mc_host_config_set upserts (host_id, owner_user_id) idempotently.
|
|
492
|
-
_reg = _rpc("mc_host_config_set", {"p_api_key": api_key, "p_host_id": host_id})
|
|
493
|
-
if _reg and _reg.get("ok"):
|
|
494
|
-
_log(f"registered host {host_id} in mc_host_config")
|
|
495
|
-
else:
|
|
496
|
-
_log(f"WARN: host registration failed (dashboard may not list this host): {_reg}")
|
|
497
|
-
_log(f"hostd starting — host_id={host_id} interval={POLL_INTERVAL_SEC}s stale={STALE_SECONDS}s")
|
|
498
|
-
while True:
|
|
499
|
-
try:
|
|
500
|
-
relaunched = _do_respawns(api_key, host_id)
|
|
501
|
-
recycled = _do_recycles(api_key, host_id)
|
|
502
|
-
if relaunched or recycled:
|
|
503
|
-
_log(f"sweep done — {relaunched} respawned, {recycled} recycled")
|
|
504
|
-
except Exception as e:
|
|
505
|
-
_log(f"WARN: sweep error: {e}")
|
|
506
|
-
time.sleep(POLL_INTERVAL_SEC)
|
|
507
|
-
|
|
508
|
-
print(f"[hostd] unknown subcommand: {sub}", file=sys.stderr)
|
|
509
|
-
return 1
|