meshcode 2.10.95__tar.gz → 2.10.98__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.10.95 → meshcode-2.10.98}/PKG-INFO +1 -1
- meshcode-2.10.98/meshcode/__init__.py +82 -0
- meshcode-2.10.98/meshcode/daemon.py +492 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/server.py +197 -56
- meshcode-2.10.98/meshcode/protocol_handler.py +375 -0
- meshcode-2.10.98/meshcode/setup_clients.py +1420 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode.egg-info/PKG-INFO +1 -1
- meshcode-2.10.95/meshcode.egg-info/SOURCES 2.txt → meshcode-2.10.98/meshcode.egg-info/SOURCES.txt +2 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/pyproject.toml +1 -1
- meshcode-2.10.95/meshcode/__init__.py +0 -82
- meshcode-2.10.95/meshcode/setup_clients.py +0 -926
- meshcode-2.10.95/meshcode.egg-info/PKG-INFO 2 +0 -446
- meshcode-2.10.95/meshcode.egg-info/SOURCES.txt +0 -204
- meshcode-2.10.95/meshcode.egg-info/dependency_links 2.txt +0 -1
- meshcode-2.10.95/meshcode.egg-info/entry_points 2.txt +0 -3
- meshcode-2.10.95/meshcode.egg-info/requires 2.txt +0 -5
- meshcode-2.10.95/meshcode.egg-info/top_level 2.txt +0 -4
- {meshcode-2.10.95 → meshcode-2.10.98}/README.md +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/cli.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/comms_v4.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/compat.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/error_hints.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/exceptions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/invites.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/launcher.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/preferences.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/quickstart.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/run_agent.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/secrets.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/self_update.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/supervisor.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/upload.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/comms_v4.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/__init__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/cli.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/compat.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/invites.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/launcher.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/preferences.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/secrets.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/self_update.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/upload.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/scripts/sentinel.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_core.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/comms_v4.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/__init__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/cli.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/compat.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/invites.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/launcher.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/preferences.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/secrets.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/self_update.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/upload.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/scripts/sentinel.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_core.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/comms_v4.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/cli.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/compat.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/invites.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/upload.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_core.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/setup.cfg +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_core.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_exceptions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_security_regressions.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_sentinel.py +0 -0
- {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_status_enum_coverage.py +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
+
__version__ = "2.10.98"
|
|
3
|
+
|
|
4
|
+
# Exception hierarchy — eagerly imported (lightweight, no deps)
|
|
5
|
+
from meshcode.exceptions import ( # noqa: F401
|
|
6
|
+
MeshCodeError,
|
|
7
|
+
AuthError,
|
|
8
|
+
RPCError,
|
|
9
|
+
MeshCodeTimeoutError,
|
|
10
|
+
MeshCodeConnectionError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# Public API — lazy imports to avoid heavy deps at import time
|
|
14
|
+
def __getattr__(name):
|
|
15
|
+
if name == "backend":
|
|
16
|
+
from meshcode.meshcode_mcp import backend
|
|
17
|
+
return backend
|
|
18
|
+
if name in _BACKEND_EXPORTS:
|
|
19
|
+
from meshcode.meshcode_mcp import backend
|
|
20
|
+
return getattr(backend, name)
|
|
21
|
+
if name in _SECRETS_EXPORTS:
|
|
22
|
+
from meshcode import secrets
|
|
23
|
+
return getattr(secrets, name)
|
|
24
|
+
raise AttributeError(f"module 'meshcode' has no attribute {name!r}")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Backend: core messaging & agent management
|
|
28
|
+
_BACKEND_EXPORTS = {
|
|
29
|
+
"send_message",
|
|
30
|
+
"read_inbox",
|
|
31
|
+
"count_pending",
|
|
32
|
+
"get_board",
|
|
33
|
+
"heartbeat",
|
|
34
|
+
"set_status",
|
|
35
|
+
"register_agent",
|
|
36
|
+
"get_project_id",
|
|
37
|
+
"sb_rpc",
|
|
38
|
+
"task_create",
|
|
39
|
+
"task_list",
|
|
40
|
+
"encrypt_payload",
|
|
41
|
+
"decrypt_payload",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Secrets: credential management
|
|
45
|
+
_SECRETS_EXPORTS = {
|
|
46
|
+
"get_api_key",
|
|
47
|
+
"set_api_key",
|
|
48
|
+
"list_profiles",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"__version__",
|
|
53
|
+
"backend",
|
|
54
|
+
# Exceptions
|
|
55
|
+
"MeshCodeError",
|
|
56
|
+
"AuthError",
|
|
57
|
+
"RPCError",
|
|
58
|
+
"MeshCodeTimeoutError",
|
|
59
|
+
"MeshCodeConnectionError",
|
|
60
|
+
# Messaging
|
|
61
|
+
"send_message",
|
|
62
|
+
"read_inbox",
|
|
63
|
+
"count_pending",
|
|
64
|
+
# Agent management
|
|
65
|
+
"register_agent",
|
|
66
|
+
"get_project_id",
|
|
67
|
+
"get_board",
|
|
68
|
+
"heartbeat",
|
|
69
|
+
"set_status",
|
|
70
|
+
# Tasks
|
|
71
|
+
"task_create",
|
|
72
|
+
"task_list",
|
|
73
|
+
# Low-level
|
|
74
|
+
"sb_rpc",
|
|
75
|
+
# Encryption
|
|
76
|
+
"encrypt_payload",
|
|
77
|
+
"decrypt_payload",
|
|
78
|
+
# Credentials
|
|
79
|
+
"get_api_key",
|
|
80
|
+
"set_api_key",
|
|
81
|
+
"list_profiles",
|
|
82
|
+
]
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
"""MeshCode autonomy v1 — headless wake daemon.
|
|
2
|
+
|
|
3
|
+
Per-agent background daemon that polls `mc_agent_should_wake` every ~60s and
|
|
4
|
+
spawns `meshcode run <project> <agent>` only when the backend reports real
|
|
5
|
+
work (urgent task, scheduled action, unread P0 broadcast, kicked status).
|
|
6
|
+
|
|
7
|
+
Layout:
|
|
8
|
+
Darwin: ~/Library/LaunchAgents/io.meshcode.daemon.<project>.<agent>.plist
|
|
9
|
+
Linux: ~/.config/systemd/user/meshcode-daemon-<project>-<agent>.{service,timer}
|
|
10
|
+
|
|
11
|
+
The plist/timer fires `meshcode daemon-tick <project> <agent>` every 60s. The
|
|
12
|
+
tick is short-lived: it asks the RPC, checks anti-flap, and either exits or
|
|
13
|
+
spawns a detached agent process. The agent runs its own session and exits
|
|
14
|
+
when meshcode_wait returns must_exit=True. The daemon stays asleep between
|
|
15
|
+
ticks — zero LLM tokens consumed during idle.
|
|
16
|
+
|
|
17
|
+
Anti-flap:
|
|
18
|
+
- PID file at ~/.meshcode/daemons/<project>.<agent>.json carries last_spawn_at,
|
|
19
|
+
consecutive_failures.
|
|
20
|
+
- 30s hold-down: never re-spawn within 30s of the last spawn.
|
|
21
|
+
- Failure backoff: after 3 consecutive spawn failures inside 5 min, the
|
|
22
|
+
daemon suspends itself for 15 min before retrying.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import json
|
|
27
|
+
import os
|
|
28
|
+
import platform
|
|
29
|
+
import subprocess
|
|
30
|
+
import sys
|
|
31
|
+
import time
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
HOME = Path.home()
|
|
35
|
+
DAEMON_STATE_DIR = HOME / ".meshcode" / "daemons"
|
|
36
|
+
CREDS_PATH = HOME / ".meshcode" / "credentials.json"
|
|
37
|
+
|
|
38
|
+
LAUNCHD_DIR = HOME / "Library" / "LaunchAgents"
|
|
39
|
+
SYSTEMD_DIR = HOME / ".config" / "systemd" / "user"
|
|
40
|
+
|
|
41
|
+
POLL_INTERVAL_SECONDS = 60
|
|
42
|
+
HOLD_DOWN_SECONDS = 30
|
|
43
|
+
FAILURE_WINDOW_SECONDS = 300
|
|
44
|
+
FAILURE_BACKOFF_SECONDS = 900
|
|
45
|
+
MAX_FAILURES = 3
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _label(project: str, agent: str) -> str:
|
|
49
|
+
return f"io.meshcode.daemon.{project}.{agent}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _systemd_unit_name(project: str, agent: str) -> str:
|
|
53
|
+
return f"meshcode-daemon-{project}-{agent}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _plist_path(project: str, agent: str) -> Path:
|
|
57
|
+
return LAUNCHD_DIR / f"{_label(project, agent)}.plist"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _systemd_service_path(project: str, agent: str) -> Path:
|
|
61
|
+
return SYSTEMD_DIR / f"{_systemd_unit_name(project, agent)}.service"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _systemd_timer_path(project: str, agent: str) -> Path:
|
|
65
|
+
return SYSTEMD_DIR / f"{_systemd_unit_name(project, agent)}.timer"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _state_path(project: str, agent: str) -> Path:
|
|
69
|
+
return DAEMON_STATE_DIR / f"{project}.{agent}.json"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _read_state(project: str, agent: str) -> dict:
|
|
73
|
+
p = _state_path(project, agent)
|
|
74
|
+
if not p.exists():
|
|
75
|
+
return {}
|
|
76
|
+
try:
|
|
77
|
+
return json.loads(p.read_text())
|
|
78
|
+
except Exception:
|
|
79
|
+
return {}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _write_state(project: str, agent: str, state: dict) -> None:
|
|
83
|
+
DAEMON_STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
_state_path(project, agent).write_text(json.dumps(state, indent=2))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _meshcode_bin() -> str:
|
|
88
|
+
"""Best-effort: prefer the meshcode console_script on PATH; fall back to
|
|
89
|
+
`python -m meshcode`."""
|
|
90
|
+
from shutil import which
|
|
91
|
+
|
|
92
|
+
found = which("meshcode")
|
|
93
|
+
if found:
|
|
94
|
+
return found
|
|
95
|
+
return f"{sys.executable} -m meshcode"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _plist_xml(project: str, agent: str) -> str:
|
|
99
|
+
label = _label(project, agent)
|
|
100
|
+
log_dir = HOME / ".meshcode" / "logs"
|
|
101
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
log_out = log_dir / f"daemon.{project}.{agent}.out.log"
|
|
103
|
+
log_err = log_dir / f"daemon.{project}.{agent}.err.log"
|
|
104
|
+
bin_parts = _meshcode_bin().split()
|
|
105
|
+
program_args = "\n ".join(f"<string>{p}</string>" for p in bin_parts) + (
|
|
106
|
+
f"\n <string>daemon-tick</string>\n <string>{project}</string>\n <string>{agent}</string>"
|
|
107
|
+
)
|
|
108
|
+
# Bake current env into the plist so the headless tick can reach the
|
|
109
|
+
# backend without relying on the user's shell rc files.
|
|
110
|
+
sb_url = os.environ.get("SUPABASE_URL", "")
|
|
111
|
+
sb_key = os.environ.get("SUPABASE_KEY") or os.environ.get("SUPABASE_ANON_KEY") or ""
|
|
112
|
+
extra_env = ""
|
|
113
|
+
if sb_url:
|
|
114
|
+
extra_env += f"\n <key>SUPABASE_URL</key><string>{sb_url}</string>"
|
|
115
|
+
if sb_key:
|
|
116
|
+
extra_env += f"\n <key>SUPABASE_KEY</key><string>{sb_key}</string>"
|
|
117
|
+
return f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
118
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
119
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
120
|
+
<plist version="1.0">
|
|
121
|
+
<dict>
|
|
122
|
+
<key>Label</key><string>{label}</string>
|
|
123
|
+
<key>ProgramArguments</key>
|
|
124
|
+
<array>
|
|
125
|
+
{program_args}
|
|
126
|
+
</array>
|
|
127
|
+
<key>StartInterval</key><integer>{POLL_INTERVAL_SECONDS}</integer>
|
|
128
|
+
<key>RunAtLoad</key><true/>
|
|
129
|
+
<key>KeepAlive</key><false/>
|
|
130
|
+
<key>StandardOutPath</key><string>{log_out}</string>
|
|
131
|
+
<key>StandardErrorPath</key><string>{log_err}</string>
|
|
132
|
+
<key>EnvironmentVariables</key>
|
|
133
|
+
<dict>
|
|
134
|
+
<key>PATH</key><string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
135
|
+
<key>HOME</key><string>{HOME}</string>
|
|
136
|
+
<key>PYTHONUNBUFFERED</key><string>1</string>
|
|
137
|
+
<key>PYTHONIOENCODING</key><string>utf-8</string>{extra_env}
|
|
138
|
+
</dict>
|
|
139
|
+
</dict>
|
|
140
|
+
</plist>
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _systemd_service(project: str, agent: str) -> str:
|
|
145
|
+
bin_cmd = _meshcode_bin()
|
|
146
|
+
sb_url = os.environ.get("SUPABASE_URL", "")
|
|
147
|
+
sb_key = os.environ.get("SUPABASE_KEY") or os.environ.get("SUPABASE_ANON_KEY") or ""
|
|
148
|
+
env_lines = ""
|
|
149
|
+
if sb_url:
|
|
150
|
+
env_lines += f"\nEnvironment=SUPABASE_URL={sb_url}"
|
|
151
|
+
if sb_key:
|
|
152
|
+
env_lines += f"\nEnvironment=SUPABASE_KEY={sb_key}"
|
|
153
|
+
return f"""[Unit]
|
|
154
|
+
Description=MeshCode autonomy daemon ({project}/{agent})
|
|
155
|
+
|
|
156
|
+
[Service]
|
|
157
|
+
Type=oneshot{env_lines}
|
|
158
|
+
ExecStart={bin_cmd} daemon-tick {project} {agent}
|
|
159
|
+
StandardOutput=append:{HOME}/.meshcode/logs/daemon.{project}.{agent}.out.log
|
|
160
|
+
StandardError=append:{HOME}/.meshcode/logs/daemon.{project}.{agent}.err.log
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _systemd_timer(project: str, agent: str) -> str:
|
|
165
|
+
return f"""[Unit]
|
|
166
|
+
Description=MeshCode autonomy daemon timer ({project}/{agent})
|
|
167
|
+
|
|
168
|
+
[Timer]
|
|
169
|
+
OnBootSec=30
|
|
170
|
+
OnUnitActiveSec={POLL_INTERVAL_SECONDS}
|
|
171
|
+
AccuracySec=10
|
|
172
|
+
|
|
173
|
+
[Install]
|
|
174
|
+
WantedBy=default.target
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _run(cmd: list[str]) -> tuple[int, str, str]:
|
|
179
|
+
try:
|
|
180
|
+
p = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
|
|
181
|
+
return p.returncode, p.stdout, p.stderr
|
|
182
|
+
except Exception as e:
|
|
183
|
+
return 1, "", str(e)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def cmd_install(project: str, agent: str) -> int:
|
|
187
|
+
if not project or not agent:
|
|
188
|
+
print("[daemon] ERROR: usage: meshcode daemon install <project> <agent>", file=sys.stderr)
|
|
189
|
+
return 2
|
|
190
|
+
DAEMON_STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
(HOME / ".meshcode" / "logs").mkdir(parents=True, exist_ok=True)
|
|
192
|
+
|
|
193
|
+
sysname = platform.system()
|
|
194
|
+
if sysname == "Darwin":
|
|
195
|
+
LAUNCHD_DIR.mkdir(parents=True, exist_ok=True)
|
|
196
|
+
plist = _plist_path(project, agent)
|
|
197
|
+
plist.write_text(_plist_xml(project, agent))
|
|
198
|
+
print(f"[daemon] wrote {plist}")
|
|
199
|
+
_run(["launchctl", "unload", str(plist)])
|
|
200
|
+
rc, _, err = _run(["launchctl", "load", "-w", str(plist)])
|
|
201
|
+
if rc != 0:
|
|
202
|
+
print(f"[daemon] launchctl load failed: {err}", file=sys.stderr)
|
|
203
|
+
return rc
|
|
204
|
+
print(f"[daemon] launchd registered: {_label(project, agent)} (poll {POLL_INTERVAL_SECONDS}s)")
|
|
205
|
+
return 0
|
|
206
|
+
elif sysname == "Linux":
|
|
207
|
+
SYSTEMD_DIR.mkdir(parents=True, exist_ok=True)
|
|
208
|
+
svc = _systemd_service_path(project, agent)
|
|
209
|
+
tmr = _systemd_timer_path(project, agent)
|
|
210
|
+
svc.write_text(_systemd_service(project, agent))
|
|
211
|
+
tmr.write_text(_systemd_timer(project, agent))
|
|
212
|
+
print(f"[daemon] wrote {svc} + {tmr}")
|
|
213
|
+
_run(["systemctl", "--user", "daemon-reload"])
|
|
214
|
+
rc, _, err = _run(["systemctl", "--user", "enable", "--now", tmr.name])
|
|
215
|
+
if rc != 0:
|
|
216
|
+
print(f"[daemon] systemctl enable failed: {err}", file=sys.stderr)
|
|
217
|
+
return rc
|
|
218
|
+
print(f"[daemon] systemd timer enabled: {tmr.name}")
|
|
219
|
+
return 0
|
|
220
|
+
else:
|
|
221
|
+
print(f"[daemon] ERROR: unsupported platform '{sysname}'. Darwin/Linux only.", file=sys.stderr)
|
|
222
|
+
return 2
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def cmd_uninstall(project: str, agent: str) -> int:
|
|
226
|
+
if not project or not agent:
|
|
227
|
+
print("[daemon] ERROR: usage: meshcode daemon uninstall <project> <agent>", file=sys.stderr)
|
|
228
|
+
return 2
|
|
229
|
+
sysname = platform.system()
|
|
230
|
+
if sysname == "Darwin":
|
|
231
|
+
plist = _plist_path(project, agent)
|
|
232
|
+
if plist.exists():
|
|
233
|
+
_run(["launchctl", "unload", str(plist)])
|
|
234
|
+
plist.unlink()
|
|
235
|
+
print(f"[daemon] removed {plist}")
|
|
236
|
+
else:
|
|
237
|
+
print("[daemon] plist not present")
|
|
238
|
+
return 0
|
|
239
|
+
elif sysname == "Linux":
|
|
240
|
+
svc = _systemd_service_path(project, agent)
|
|
241
|
+
tmr = _systemd_timer_path(project, agent)
|
|
242
|
+
_run(["systemctl", "--user", "disable", "--now", tmr.name])
|
|
243
|
+
for p in (svc, tmr):
|
|
244
|
+
if p.exists():
|
|
245
|
+
p.unlink()
|
|
246
|
+
print(f"[daemon] removed {p}")
|
|
247
|
+
_run(["systemctl", "--user", "daemon-reload"])
|
|
248
|
+
return 0
|
|
249
|
+
else:
|
|
250
|
+
print(f"[daemon] ERROR: unsupported platform '{sysname}'.", file=sys.stderr)
|
|
251
|
+
return 2
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def cmd_status(project: str, agent: str) -> int:
|
|
255
|
+
if not project or not agent:
|
|
256
|
+
print("[daemon] ERROR: usage: meshcode daemon status <project> <agent>", file=sys.stderr)
|
|
257
|
+
return 2
|
|
258
|
+
state = _read_state(project, agent)
|
|
259
|
+
sysname = platform.system()
|
|
260
|
+
print(f"[daemon] {project}/{agent}")
|
|
261
|
+
if sysname == "Darwin":
|
|
262
|
+
plist = _plist_path(project, agent)
|
|
263
|
+
if plist.exists():
|
|
264
|
+
rc, out, _ = _run(["launchctl", "list", _label(project, agent)])
|
|
265
|
+
print(f" launchd: {'loaded' if rc == 0 else 'not loaded'}")
|
|
266
|
+
else:
|
|
267
|
+
print(" launchd: not installed")
|
|
268
|
+
elif sysname == "Linux":
|
|
269
|
+
tmr = _systemd_timer_path(project, agent)
|
|
270
|
+
if tmr.exists():
|
|
271
|
+
rc, out, _ = _run(["systemctl", "--user", "is-active", tmr.name])
|
|
272
|
+
print(f" systemd timer: {out.strip() or 'unknown'}")
|
|
273
|
+
else:
|
|
274
|
+
print(" systemd timer: not installed")
|
|
275
|
+
if state:
|
|
276
|
+
last_spawn = state.get("last_spawn_at", 0)
|
|
277
|
+
since = time.time() - last_spawn if last_spawn else None
|
|
278
|
+
print(f" last_spawn: {since:.0f}s ago" if since else " last_spawn: never")
|
|
279
|
+
print(f" consecutive_failures: {state.get('consecutive_failures', 0)}")
|
|
280
|
+
if state.get("backoff_until", 0) > time.time():
|
|
281
|
+
print(f" backoff_until: {int(state['backoff_until'] - time.time())}s remaining")
|
|
282
|
+
return 0
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _load_api_key() -> str:
|
|
286
|
+
env_key = os.environ.get("MESHCODE_API_KEY", "")
|
|
287
|
+
if env_key:
|
|
288
|
+
return env_key
|
|
289
|
+
if CREDS_PATH.exists():
|
|
290
|
+
try:
|
|
291
|
+
return json.loads(CREDS_PATH.read_text()).get("api_key", "") or ""
|
|
292
|
+
except Exception:
|
|
293
|
+
return ""
|
|
294
|
+
return ""
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _should_wake(api_key: str, project_id: str, agent: str) -> dict:
|
|
298
|
+
"""Call mc_agent_should_wake via Supabase REST. Lightweight — no SDK
|
|
299
|
+
dependencies inside the daemon hot path."""
|
|
300
|
+
import urllib.request
|
|
301
|
+
import urllib.error
|
|
302
|
+
|
|
303
|
+
supabase_url = os.environ.get("SUPABASE_URL") or _supabase_url_from_env()
|
|
304
|
+
supabase_key = os.environ.get("SUPABASE_KEY") or _supabase_key_from_env()
|
|
305
|
+
if not (supabase_url and supabase_key):
|
|
306
|
+
return {"ok": False, "should_wake": False, "error": "no_supabase_env"}
|
|
307
|
+
body = json.dumps({
|
|
308
|
+
"p_api_key": api_key,
|
|
309
|
+
"p_project_id": project_id,
|
|
310
|
+
"p_agent_name": agent,
|
|
311
|
+
}).encode()
|
|
312
|
+
req = urllib.request.Request(
|
|
313
|
+
f"{supabase_url}/rest/v1/rpc/mc_agent_should_wake",
|
|
314
|
+
data=body,
|
|
315
|
+
method="POST",
|
|
316
|
+
headers={
|
|
317
|
+
"Content-Type": "application/json",
|
|
318
|
+
"apikey": supabase_key,
|
|
319
|
+
"Authorization": f"Bearer {supabase_key}",
|
|
320
|
+
},
|
|
321
|
+
)
|
|
322
|
+
try:
|
|
323
|
+
with urllib.request.urlopen(req, timeout=10) as r:
|
|
324
|
+
return json.loads(r.read().decode())
|
|
325
|
+
except urllib.error.HTTPError as e:
|
|
326
|
+
return {"ok": False, "should_wake": False, "http": e.code, "error": e.reason}
|
|
327
|
+
except Exception as e:
|
|
328
|
+
return {"ok": False, "should_wake": False, "error": str(e)}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _supabase_url_from_env() -> str:
|
|
332
|
+
"""No hardcoded fallback — wrong project ref would silently misroute the
|
|
333
|
+
tick. Caller must export SUPABASE_URL or have it baked into the launchd /
|
|
334
|
+
systemd EnvironmentVariables."""
|
|
335
|
+
return os.environ.get("SUPABASE_URL", "")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _supabase_key_from_env() -> str:
|
|
339
|
+
# Daemon avoids embedding service-role; use the anon publishable key.
|
|
340
|
+
return os.environ.get("SUPABASE_KEY") or os.environ.get("SUPABASE_ANON_KEY") or ""
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _resolve_project_id(api_key: str, project_name: str) -> str:
|
|
344
|
+
"""Resolve project name → uuid via mc_resolve_project (read-only, gated by
|
|
345
|
+
api_key). Cached in daemon state to avoid an extra round-trip per tick."""
|
|
346
|
+
state = _read_state(project_name, "_resolver")
|
|
347
|
+
cached = state.get("project_id")
|
|
348
|
+
if cached:
|
|
349
|
+
return cached
|
|
350
|
+
import urllib.request
|
|
351
|
+
|
|
352
|
+
supabase_url = _supabase_url_from_env()
|
|
353
|
+
supabase_key = _supabase_key_from_env()
|
|
354
|
+
if not (supabase_url and supabase_key):
|
|
355
|
+
return ""
|
|
356
|
+
body = json.dumps({"p_api_key": api_key, "p_project_name": project_name}).encode()
|
|
357
|
+
req = urllib.request.Request(
|
|
358
|
+
f"{supabase_url}/rest/v1/rpc/mc_resolve_project",
|
|
359
|
+
data=body,
|
|
360
|
+
method="POST",
|
|
361
|
+
headers={
|
|
362
|
+
"Content-Type": "application/json",
|
|
363
|
+
"apikey": supabase_key,
|
|
364
|
+
"Authorization": f"Bearer {supabase_key}",
|
|
365
|
+
},
|
|
366
|
+
)
|
|
367
|
+
try:
|
|
368
|
+
with urllib.request.urlopen(req, timeout=10) as r:
|
|
369
|
+
payload = json.loads(r.read().decode())
|
|
370
|
+
project_id = payload.get("project_id") or payload.get("id") or ""
|
|
371
|
+
if project_id:
|
|
372
|
+
_write_state(project_name, "_resolver", {"project_id": project_id})
|
|
373
|
+
return project_id
|
|
374
|
+
except Exception:
|
|
375
|
+
return ""
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _spawn_agent(project: str, agent: str) -> bool:
|
|
379
|
+
"""Detached `meshcode run <agent>` so the agent owns its own lifetime.
|
|
380
|
+
Daemon does NOT wait — the launchd/systemd tick just kicked off the spawn
|
|
381
|
+
and exits."""
|
|
382
|
+
bin_cmd = _meshcode_bin().split()
|
|
383
|
+
args = bin_cmd + ["run", project, agent]
|
|
384
|
+
log_dir = HOME / ".meshcode" / "logs"
|
|
385
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
386
|
+
out = open(log_dir / f"agent.{project}.{agent}.out.log", "ab", buffering=0)
|
|
387
|
+
err = open(log_dir / f"agent.{project}.{agent}.err.log", "ab", buffering=0)
|
|
388
|
+
try:
|
|
389
|
+
subprocess.Popen(
|
|
390
|
+
args,
|
|
391
|
+
stdout=out,
|
|
392
|
+
stderr=err,
|
|
393
|
+
stdin=subprocess.DEVNULL,
|
|
394
|
+
start_new_session=True,
|
|
395
|
+
close_fds=True,
|
|
396
|
+
)
|
|
397
|
+
return True
|
|
398
|
+
except Exception as e:
|
|
399
|
+
print(f"[daemon] spawn failed: {e}", file=sys.stderr)
|
|
400
|
+
return False
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def cmd_tick(project: str, agent: str) -> int:
|
|
404
|
+
"""One iteration of the wake loop. Designed to be called by launchd /
|
|
405
|
+
systemd at POLL_INTERVAL_SECONDS cadence."""
|
|
406
|
+
if not project or not agent:
|
|
407
|
+
print("[daemon-tick] ERROR: usage: meshcode daemon-tick <project> <agent>", file=sys.stderr)
|
|
408
|
+
return 2
|
|
409
|
+
now = time.time()
|
|
410
|
+
state = _read_state(project, agent)
|
|
411
|
+
|
|
412
|
+
backoff_until = state.get("backoff_until", 0)
|
|
413
|
+
if backoff_until and now < backoff_until:
|
|
414
|
+
return 0
|
|
415
|
+
|
|
416
|
+
api_key = _load_api_key()
|
|
417
|
+
if not api_key:
|
|
418
|
+
print("[daemon-tick] no api_key (run `meshcode login <key>` first)", file=sys.stderr)
|
|
419
|
+
return 1
|
|
420
|
+
|
|
421
|
+
project_id = _resolve_project_id(api_key, project)
|
|
422
|
+
if not project_id:
|
|
423
|
+
print(f"[daemon-tick] could not resolve project '{project}'", file=sys.stderr)
|
|
424
|
+
return 1
|
|
425
|
+
|
|
426
|
+
resp = _should_wake(api_key, project_id, agent)
|
|
427
|
+
should = bool(resp.get("should_wake"))
|
|
428
|
+
if not should:
|
|
429
|
+
return 0
|
|
430
|
+
|
|
431
|
+
last_spawn = state.get("last_spawn_at", 0)
|
|
432
|
+
if now - last_spawn < HOLD_DOWN_SECONDS:
|
|
433
|
+
return 0
|
|
434
|
+
|
|
435
|
+
spawned = _spawn_agent(project, agent)
|
|
436
|
+
state["last_spawn_at"] = now
|
|
437
|
+
if spawned:
|
|
438
|
+
state["consecutive_failures"] = 0
|
|
439
|
+
state.pop("backoff_until", None)
|
|
440
|
+
state["last_reasons"] = resp.get("reasons", [])
|
|
441
|
+
_write_state(project, agent, state)
|
|
442
|
+
print(f"[daemon-tick] spawned {project}/{agent} reasons={resp.get('reasons')}")
|
|
443
|
+
return 0
|
|
444
|
+
|
|
445
|
+
fails = state.get("consecutive_failures", 0) + 1
|
|
446
|
+
# If the prior burst is older than the failure window, treat this as a
|
|
447
|
+
# fresh first failure so backoff math is monotonic.
|
|
448
|
+
first = state.get("first_failure_at", 0)
|
|
449
|
+
if fails == 1 or (first and now - first > FAILURE_WINDOW_SECONDS):
|
|
450
|
+
state["first_failure_at"] = now
|
|
451
|
+
first = now
|
|
452
|
+
fails = 1
|
|
453
|
+
state["consecutive_failures"] = fails
|
|
454
|
+
state["last_failure_at"] = now
|
|
455
|
+
if fails >= MAX_FAILURES and now - first < FAILURE_WINDOW_SECONDS:
|
|
456
|
+
state["backoff_until"] = now + FAILURE_BACKOFF_SECONDS
|
|
457
|
+
# Clear the burst so the next failure cycle starts fresh.
|
|
458
|
+
state.pop("first_failure_at", None)
|
|
459
|
+
state["consecutive_failures"] = 0
|
|
460
|
+
print(f"[daemon-tick] entering {FAILURE_BACKOFF_SECONDS}s backoff after {fails} failures")
|
|
461
|
+
_write_state(project, agent, state)
|
|
462
|
+
return 1
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def main(argv: list[str]) -> int:
|
|
466
|
+
if not argv:
|
|
467
|
+
print(
|
|
468
|
+
"usage:\n"
|
|
469
|
+
" meshcode daemon install <project> <agent>\n"
|
|
470
|
+
" meshcode daemon uninstall <project> <agent>\n"
|
|
471
|
+
" meshcode daemon status <project> <agent>\n"
|
|
472
|
+
" meshcode daemon-tick <project> <agent> # internal, fired by launchd/systemd",
|
|
473
|
+
file=sys.stderr,
|
|
474
|
+
)
|
|
475
|
+
return 2
|
|
476
|
+
sub = argv[0]
|
|
477
|
+
proj = argv[1] if len(argv) > 1 else ""
|
|
478
|
+
name = argv[2] if len(argv) > 2 else ""
|
|
479
|
+
if sub == "install":
|
|
480
|
+
return cmd_install(proj, name)
|
|
481
|
+
if sub == "uninstall":
|
|
482
|
+
return cmd_uninstall(proj, name)
|
|
483
|
+
if sub == "status":
|
|
484
|
+
return cmd_status(proj, name)
|
|
485
|
+
if sub == "tick":
|
|
486
|
+
return cmd_tick(proj, name)
|
|
487
|
+
print(f"[daemon] unknown subcommand: {sub}", file=sys.stderr)
|
|
488
|
+
return 2
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
if __name__ == "__main__":
|
|
492
|
+
sys.exit(main(sys.argv[1:]))
|