meshcode 2.11.4__tar.gz → 2.11.5__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.4 → meshcode-2.11.5}/PKG-INFO +1 -1
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/__init__.py +1 -1
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/server.py +155 -149
- meshcode-2.11.5/meshcode/meshcode_mcp/sleep_signals.py +110 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/SOURCES.txt +2 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/pyproject.toml +1 -1
- meshcode-2.11.5/tests/test_sleep_signals.py +160 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/README.md +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/cli.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/compat.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/daemon.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/invites.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/launcher.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/preferences.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/secrets.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/self_update.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/upload.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/comms_v4.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_core.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/comms_v4.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_core.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/comms_v4.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_core.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/setup.cfg +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_core.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -499,73 +499,18 @@ def _filter_and_mark(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
|
499
499
|
return out
|
|
500
500
|
|
|
501
501
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
#
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
# English directives
|
|
513
|
-
"go to sleep", "all sleep now", "sleep now", "got_done", "go_done",
|
|
514
|
-
"shut down", "shutdown", "stop now", "exit now",
|
|
515
|
-
# Spanish directives (es-MX, sammybenu's primary). All require either a
|
|
516
|
-
# leading "a"/"todos" or a trailing "ahora" so plain conjugations
|
|
517
|
-
# ("salir", "terminar", "duerme") in body prose don't trigger.
|
|
518
|
-
"a dormir", "todos a dormir", "duerme ahora", "duerman ahora",
|
|
519
|
-
"dormir ahora", "para la sesion", "exit y duerme",
|
|
502
|
+
# Sleep-signal parsing lives in a separate pure module so it can be tested
|
|
503
|
+
# without booting the full MCP server. Re-export the public surface here
|
|
504
|
+
# for back-compat with any in-tree caller that imports from server.
|
|
505
|
+
from .sleep_signals import ( # noqa: F401
|
|
506
|
+
_SLEEP_PAYLOAD_TYPES,
|
|
507
|
+
_SLEEP_TEXT_MARKERS,
|
|
508
|
+
_KNOWN_HUMAN_HANDLES,
|
|
509
|
+
_is_human_authored,
|
|
510
|
+
_looks_like_sleep_signal,
|
|
511
|
+
_split_messages,
|
|
520
512
|
)
|
|
521
513
|
|
|
522
|
-
|
|
523
|
-
def _looks_like_sleep_signal(m: Dict[str, Any]) -> bool:
|
|
524
|
-
"""Detect commander broadcasts / DMs that authorize the wait-loop exit.
|
|
525
|
-
|
|
526
|
-
Catches three encodings (BE-S5.11): structured payload.type, top-level
|
|
527
|
-
text marker, and broadcast-with-sleep-type. Single source of truth so
|
|
528
|
-
every receive path (PRODUCT RULE 2 + inner _meshcode_wait_inner) routes
|
|
529
|
-
sleep authorizations into done_signals consistently.
|
|
530
|
-
"""
|
|
531
|
-
pl = m.get("payload") or {}
|
|
532
|
-
if isinstance(pl, dict):
|
|
533
|
-
if str(pl.get("type", "")).lower() in _SLEEP_PAYLOAD_TYPES:
|
|
534
|
-
return True
|
|
535
|
-
if str(pl.get("directive", "")).lower() in _SLEEP_PAYLOAD_TYPES:
|
|
536
|
-
return True
|
|
537
|
-
text = str(pl.get("text", "")).lower()
|
|
538
|
-
if any(marker in text for marker in _SLEEP_TEXT_MARKERS):
|
|
539
|
-
return True
|
|
540
|
-
return False
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
def _split_messages(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
544
|
-
"""Split a list of normalized message dicts into messages / acks / done_signals.
|
|
545
|
-
|
|
546
|
-
BE-S5.11: classify ANY message (including broadcasts) carrying a sleep
|
|
547
|
-
intent into done_signals so meshcode_wait surfaces them with the
|
|
548
|
-
must_exit flag. Previous logic only matched type='done' literally,
|
|
549
|
-
which the broadcast path could never produce.
|
|
550
|
-
"""
|
|
551
|
-
real: List[Dict[str, Any]] = []
|
|
552
|
-
acks: List[Dict[str, Any]] = []
|
|
553
|
-
dones: List[Dict[str, Any]] = []
|
|
554
|
-
for m in messages:
|
|
555
|
-
t = m.get("type", "msg")
|
|
556
|
-
if t == "ack":
|
|
557
|
-
acks.append(m)
|
|
558
|
-
elif t == "done" or _looks_like_sleep_signal(m):
|
|
559
|
-
dones.append(m)
|
|
560
|
-
else:
|
|
561
|
-
real.append(m)
|
|
562
|
-
return {
|
|
563
|
-
"messages": real,
|
|
564
|
-
"acks": acks,
|
|
565
|
-
"done_signals": dones,
|
|
566
|
-
"count": len(real),
|
|
567
|
-
}
|
|
568
|
-
|
|
569
514
|
from . import backend as be
|
|
570
515
|
from .realtime import RealtimeListener
|
|
571
516
|
|
|
@@ -1718,8 +1663,26 @@ def _orphan_watchdog_fn():
|
|
|
1718
1663
|
# Parent exited (or boot was already orphaned). Confirm with
|
|
1719
1664
|
# stdin peer probe to avoid killing intentional daemons.
|
|
1720
1665
|
if _BOOT_PPID != 1 or _stdin_peer_dead():
|
|
1666
|
+
# 2s confirmation grace before os._exit(0). Without this,
|
|
1667
|
+
# a transient ppid/stdin race (FastMCP stdio fd duplication,
|
|
1668
|
+
# brief reparent during macOS process-group shuffles) can
|
|
1669
|
+
# false-positive and kill an actively-attached MCP — observed
|
|
1670
|
+
# mode-b of fe57b7a5 (mid-session crash after ~10min normal
|
|
1671
|
+
# ops, Samuel 2026-05-14T19:31Z mesh-commander session).
|
|
1672
|
+
# We accept a +2s detection latency for legitimate orphans;
|
|
1673
|
+
# the heartbeat-path orphan check at line ~1798 still catches
|
|
1674
|
+
# them within one heartbeat tick (5-15s).
|
|
1675
|
+
_orphan_watchdog_stop.wait(2)
|
|
1676
|
+
if _orphan_watchdog_stop.is_set():
|
|
1677
|
+
break
|
|
1678
|
+
if os.getppid() != 1 or not (_BOOT_PPID != 1 or _stdin_peer_dead()):
|
|
1679
|
+
log.info(
|
|
1680
|
+
f"orphan watchdog: transient ppid==1 cleared after "
|
|
1681
|
+
f"2s grace (ppid_now={os.getppid()}) — continuing"
|
|
1682
|
+
)
|
|
1683
|
+
continue
|
|
1721
1684
|
log.warning(
|
|
1722
|
-
f"orphan watchdog: parent gone "
|
|
1685
|
+
f"orphan watchdog: parent gone (confirmed after 2s grace) "
|
|
1723
1686
|
f"(boot_ppid={_BOOT_PPID} → ppid=1, "
|
|
1724
1687
|
f"stdin_peer_dead={_stdin_peer_dead()}) — "
|
|
1725
1688
|
f"force-exiting MCP for {AGENT_NAME}"
|
|
@@ -1852,18 +1815,34 @@ def _heartbeat_loop_inner():
|
|
|
1852
1815
|
and os.getppid() == 1):
|
|
1853
1816
|
_orphan = (_BOOT_PPID != 1) or _stdin_peer_dead()
|
|
1854
1817
|
if _orphan:
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
)
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1818
|
+
# 2s confirmation grace — same rationale as the
|
|
1819
|
+
# orphan_watchdog above (fe57b7a5 mode-b). Heartbeat
|
|
1820
|
+
# ticks every 5-15s, so adding 2s here trades minimal
|
|
1821
|
+
# detection latency for false-positive resistance.
|
|
1822
|
+
_heartbeat_stop.wait(2)
|
|
1823
|
+
if _heartbeat_stop.is_set():
|
|
1824
|
+
break
|
|
1825
|
+
if os.getppid() != 1 or not ((_BOOT_PPID != 1) or _stdin_peer_dead()):
|
|
1826
|
+
log.info(
|
|
1827
|
+
f"heartbeat orphan check: transient ppid==1 "
|
|
1828
|
+
f"cleared after 2s grace (ppid_now={os.getppid()}) "
|
|
1829
|
+
f"— continuing"
|
|
1830
|
+
)
|
|
1831
|
+
else:
|
|
1832
|
+
log.warning(
|
|
1833
|
+
f"parent process exited "
|
|
1834
|
+
f"(confirmed after 2s grace) "
|
|
1835
|
+
f"(boot_ppid={_BOOT_PPID} → "
|
|
1836
|
+
f"current_ppid=1, stdin_peer_dead={_BOOT_PPID == 1}) "
|
|
1837
|
+
f"— orphan MCP for {AGENT_NAME}, releasing lease "
|
|
1838
|
+
f"and exiting"
|
|
1839
|
+
)
|
|
1840
|
+
try:
|
|
1841
|
+
_release_lease()
|
|
1842
|
+
except Exception:
|
|
1843
|
+
pass
|
|
1844
|
+
_heartbeat_stop.set()
|
|
1845
|
+
os._exit(0)
|
|
1867
1846
|
except Exception:
|
|
1868
1847
|
pass
|
|
1869
1848
|
|
|
@@ -2064,65 +2043,67 @@ async def lifespan(_app):
|
|
|
2064
2043
|
log.warning(f"initial heartbeat attempt {_attempt+1} failed: {e}")
|
|
2065
2044
|
import time; time.sleep(2)
|
|
2066
2045
|
|
|
2067
|
-
#
|
|
2068
|
-
#
|
|
2069
|
-
#
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
# warn + continue with the legacy lazy-load path.
|
|
2094
|
-
global _BOOT_CONTEXT_BUNDLE, _BOOT_CONTEXT_AT
|
|
2095
|
-
try:
|
|
2096
|
-
_ctx = be.sb_rpc("mc_agent_boot_context", {
|
|
2097
|
-
"p_api_key": _get_api_key(),
|
|
2098
|
-
"p_project_id": _PROJECT_ID,
|
|
2099
|
-
"p_agent_name": AGENT_NAME,
|
|
2100
|
-
})
|
|
2101
|
-
if isinstance(_ctx, dict) and _ctx.get("ok"):
|
|
2102
|
-
_BOOT_CONTEXT_BUNDLE = _ctx
|
|
2103
|
-
_BOOT_CONTEXT_AT = _time.time()
|
|
2104
|
-
log.info(
|
|
2105
|
-
f"[meshcode] boot_context: "
|
|
2106
|
-
f"{len(_ctx.get('recent_msgs', []))} unread, "
|
|
2107
|
-
f"{len(_ctx.get('open_tasks', []))} open tasks, "
|
|
2108
|
-
f"{len(_ctx.get('relevant_memories', []))} memories surfaced"
|
|
2109
|
-
)
|
|
2110
|
-
else:
|
|
2111
|
-
# Defense-in-depth: if the failure is a missing GRANT (mig
|
|
2112
|
-
# 270 regressed or older project), the legacy lazy-load
|
|
2113
|
-
# path already fills the gap. WARN puts a stderr line that
|
|
2114
|
-
# some Claude Code versions surface as "MCP server failed
|
|
2115
|
-
# at launch". Downgrade only the GRANT case; other non-ok
|
|
2116
|
-
# responses still warn.
|
|
2117
|
-
if _is_grant_or_permission_error(_ctx):
|
|
2118
|
-
log.debug(f"boot_context permission_denied (legacy boot ok): {_ctx}")
|
|
2046
|
+
# fe57b7a5 mode-a fix (2026-05-14): mc_agent_persona + mc_agent_boot_context
|
|
2047
|
+
# used to run synchronously here, adding ~1-4s to lifespan pre-yield latency.
|
|
2048
|
+
# Combined with the module-import RPCs (3-15s), commanders with large
|
|
2049
|
+
# _INSTRUCTIONS payloads + many memories exceeded Claude Code's MCP
|
|
2050
|
+
# handshake window under congestion → "MCP failed at launch". Both RPCs
|
|
2051
|
+
# produce module globals (_PERSONA, _BOOT_CONTEXT_BUNDLE) consumed lazily
|
|
2052
|
+
# by tools that already have soft-fail fallbacks, so deferring them into
|
|
2053
|
+
# a daemon thread is safe — first reads within ~1s after lifespan yield
|
|
2054
|
+
# may see None, in which case consumers fall back to live RPC paths.
|
|
2055
|
+
global _PERSONA, _BOOT_CONTEXT_BUNDLE, _BOOT_CONTEXT_AT
|
|
2056
|
+
|
|
2057
|
+
def _load_persona_bg():
|
|
2058
|
+
global _PERSONA
|
|
2059
|
+
try:
|
|
2060
|
+
_persona_resp = be.sb_rpc("mc_agent_persona", {
|
|
2061
|
+
"p_api_key": _get_api_key(),
|
|
2062
|
+
"p_project_id": _PROJECT_ID,
|
|
2063
|
+
"p_agent_name": AGENT_NAME,
|
|
2064
|
+
})
|
|
2065
|
+
if isinstance(_persona_resp, dict) and _persona_resp.get("ok"):
|
|
2066
|
+
_PERSONA = _persona_resp.get("persona") or {}
|
|
2067
|
+
log.info(
|
|
2068
|
+
f"[meshcode] persona loaded (bg): tone={_PERSONA.get('tone','default')} "
|
|
2069
|
+
f"language={_PERSONA.get('language','en')} "
|
|
2070
|
+
f"reply_max_tokens={_PERSONA.get('reply_max_tokens','unset')}"
|
|
2071
|
+
)
|
|
2119
2072
|
else:
|
|
2120
|
-
log.
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2073
|
+
log.debug(f"persona load returned non-ok (bg): {_persona_resp} — continuing without")
|
|
2074
|
+
except Exception as e:
|
|
2075
|
+
log.debug(f"persona RPC unavailable (bg) ({e}) — continuing without")
|
|
2076
|
+
|
|
2077
|
+
def _load_boot_context_bg():
|
|
2078
|
+
global _BOOT_CONTEXT_BUNDLE, _BOOT_CONTEXT_AT
|
|
2079
|
+
try:
|
|
2080
|
+
_ctx = be.sb_rpc("mc_agent_boot_context", {
|
|
2081
|
+
"p_api_key": _get_api_key(),
|
|
2082
|
+
"p_project_id": _PROJECT_ID,
|
|
2083
|
+
"p_agent_name": AGENT_NAME,
|
|
2084
|
+
})
|
|
2085
|
+
if isinstance(_ctx, dict) and _ctx.get("ok"):
|
|
2086
|
+
_BOOT_CONTEXT_BUNDLE = _ctx
|
|
2087
|
+
_BOOT_CONTEXT_AT = _time.time()
|
|
2088
|
+
log.info(
|
|
2089
|
+
f"[meshcode] boot_context (bg): "
|
|
2090
|
+
f"{len(_ctx.get('recent_msgs', []))} unread, "
|
|
2091
|
+
f"{len(_ctx.get('open_tasks', []))} open tasks, "
|
|
2092
|
+
f"{len(_ctx.get('relevant_memories', []))} memories surfaced"
|
|
2093
|
+
)
|
|
2094
|
+
else:
|
|
2095
|
+
if _is_grant_or_permission_error(_ctx):
|
|
2096
|
+
log.debug(f"boot_context permission_denied (bg, legacy boot ok): {_ctx}")
|
|
2097
|
+
else:
|
|
2098
|
+
log.warning(f"boot_context returned non-ok (bg): {_ctx} — continuing legacy boot")
|
|
2099
|
+
except Exception as e:
|
|
2100
|
+
if _is_grant_or_permission_error(str(e)):
|
|
2101
|
+
log.debug(f"boot_context permission_denied (bg, legacy boot ok): {e}")
|
|
2102
|
+
else:
|
|
2103
|
+
log.warning(f"boot_context RPC unavailable (bg) ({e}) — continuing legacy boot")
|
|
2104
|
+
|
|
2105
|
+
_threading.Thread(target=_load_persona_bg, daemon=True, name="meshcode-load-persona").start()
|
|
2106
|
+
_threading.Thread(target=_load_boot_context_bg, daemon=True, name="meshcode-load-boot-context").start()
|
|
2126
2107
|
|
|
2127
2108
|
# Heartbeat in daemon thread — independent of asyncio event loop.
|
|
2128
2109
|
_heartbeat_stop.clear()
|
|
@@ -2726,21 +2707,43 @@ def meshcode_download_file(file_id: str) -> Dict[str, Any]:
|
|
|
2726
2707
|
file_name = file_info.get("file_name", "download")
|
|
2727
2708
|
signed_url = file_info.get("signed_url")
|
|
2728
2709
|
|
|
2729
|
-
|
|
2710
|
+
sb_url = os.environ.get("SUPABASE_URL", be._sb_url if hasattr(be, '_sb_url') else "")
|
|
2711
|
+
anon_key = os.environ.get("SUPABASE_KEY", be._sb_key if hasattr(be, '_sb_key') else "")
|
|
2712
|
+
service_key = os.environ.get("MESHCODE_SUPABASE_SERVICE_KEY", "")
|
|
2713
|
+
|
|
2714
|
+
tried_labels: List[str] = []
|
|
2715
|
+
last_err: Optional[str] = None
|
|
2716
|
+
|
|
2717
|
+
# Strategy 1: signed_url (no auth headers, no client service_role needed).
|
|
2718
|
+
# Path B (235e85b6): server-side RPC mints the URL via backend GUCs and
|
|
2719
|
+
# returns it; client just GETs. Works for guest workspaces that
|
|
2720
|
+
# intentionally lack MESHCODE_SUPABASE_SERVICE_KEY in their .mcp.json.
|
|
2730
2721
|
if signed_url:
|
|
2722
|
+
tried_labels.append("signed_url")
|
|
2731
2723
|
try:
|
|
2732
2724
|
with _req.urlopen(_req.Request(signed_url), timeout=30) as resp:
|
|
2733
2725
|
content = resp.read()
|
|
2734
2726
|
return _consume(content, mime_type, file_name)
|
|
2735
|
-
except _uerr.HTTPError:
|
|
2736
|
-
|
|
2737
|
-
except Exception:
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
# Strategy 2:
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2727
|
+
except _uerr.HTTPError as e:
|
|
2728
|
+
last_err = f"HTTP {e.code} on signed_url: {e.reason}"
|
|
2729
|
+
except Exception as e:
|
|
2730
|
+
last_err = f"{type(e).__name__} on signed_url: {e}"
|
|
2731
|
+
|
|
2732
|
+
# Strategy 2 candidates: direct storage REST with available keys.
|
|
2733
|
+
# Guest workspaces (no MESHCODE_SUPABASE_SERVICE_KEY) cannot read a
|
|
2734
|
+
# private bucket via anon — every Strategy-2 attempt will 400/403. Bail
|
|
2735
|
+
# early with a clear root-cause diagnosis instead of churning through
|
|
2736
|
+
# doomed attempts and surfacing a generic HTTP 400 to the user. Task
|
|
2737
|
+
# 8ecd56bb 2026-05-14: alexa + bob hit this path.
|
|
2738
|
+
if not signed_url and not service_key:
|
|
2739
|
+
return {
|
|
2740
|
+
"error": "server-side signed_url unavailable and this workspace has no service_role key",
|
|
2741
|
+
"error_code": "missing_server_guc",
|
|
2742
|
+
"tried": tried_labels,
|
|
2743
|
+
"storage_path": storage_path,
|
|
2744
|
+
"hint": "backend RPC mc_get_file_download did not return a signed_url. Likely the Supabase project is missing GUC `app.settings.service_role_key` + `app.settings.supabase_url` so the RPC body cannot mint a signed URL. Ask Samuel to apply both ALTER DATABASE settings on the meshcode Supabase project (sb_secret_... + https://gjinagyyjttyxnaoavnz.supabase.co), or distribute MESHCODE_SUPABASE_SERVICE_KEY into guest .mcp.json (security tradeoff — not recommended).",
|
|
2745
|
+
"diagnosis": "guest_workspace_pattern",
|
|
2746
|
+
}
|
|
2744
2747
|
|
|
2745
2748
|
if not sb_url or not anon_key:
|
|
2746
2749
|
return {"error": "storage not configured", "error_code": "config_error"}
|
|
@@ -2769,9 +2772,7 @@ def meshcode_download_file(file_id: str) -> Dict[str, Any]:
|
|
|
2769
2772
|
attempts.append(("anon auth", auth_url, anon_key))
|
|
2770
2773
|
attempts.append(("anon public", public_url, anon_key))
|
|
2771
2774
|
|
|
2772
|
-
last_err = None
|
|
2773
2775
|
content = None
|
|
2774
|
-
tried_labels: List[str] = ["signed_url"] if signed_url else []
|
|
2775
2776
|
for label, _url, _key in attempts:
|
|
2776
2777
|
tried_labels.append(label)
|
|
2777
2778
|
try:
|
|
@@ -2789,7 +2790,12 @@ def meshcode_download_file(file_id: str) -> Dict[str, Any]:
|
|
|
2789
2790
|
"error_code": "download_error",
|
|
2790
2791
|
"tried": tried_labels,
|
|
2791
2792
|
"storage_path": storage_path,
|
|
2792
|
-
"hint":
|
|
2793
|
+
"hint": (
|
|
2794
|
+
"every fallback failed. If 'signed_url' was tried and 400'd, the URL may be expired or "
|
|
2795
|
+
"signed against a stale key — re-request via meshcode_download_file. If signed_url was "
|
|
2796
|
+
"absent, the backend GUC `app.settings.service_role_key`/`app.settings.supabase_url` is "
|
|
2797
|
+
"missing on the Supabase project."
|
|
2798
|
+
),
|
|
2793
2799
|
}
|
|
2794
2800
|
|
|
2795
2801
|
return _consume(content, mime_type, file_name)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Sleep-authorization signal parser for the wait loop.
|
|
2
|
+
|
|
3
|
+
Pure module — no side effects, no env deps. Extracted from server.py so the
|
|
4
|
+
parser can be imported and tested in isolation. server.py re-exports these
|
|
5
|
+
names for back-compat.
|
|
6
|
+
|
|
7
|
+
Authorizes a wait-loop exit (must_exit=True) only on explicit sleep-auth
|
|
8
|
+
context. Two valid encodings:
|
|
9
|
+
|
|
10
|
+
(1) STRUCTURED DIRECTIVE — payload.type or payload.directive is in
|
|
11
|
+
_SLEEP_PAYLOAD_TYPES. Deliberately-shaped messages; an agent that
|
|
12
|
+
sets payload.type to a sleep verb is issuing a directive on purpose.
|
|
13
|
+
(2) HUMAN TEXT KEYWORD — payload.text contains a sleep marker AND the
|
|
14
|
+
sender is a human user.
|
|
15
|
+
|
|
16
|
+
Plain text idioms from AI-role agents are deliberately IGNORED — quoting
|
|
17
|
+
"a dormir" or describing "Samuel cierra la sesion" inside a debate-close
|
|
18
|
+
summary is not authorization. Task fabfc602 (bob@justforfun 2026-05-14).
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from typing import Any, Dict, List
|
|
23
|
+
|
|
24
|
+
_SLEEP_PAYLOAD_TYPES = {"sleep", "go_to_sleep", "shutdown", "got_done", "done",
|
|
25
|
+
"exit", "stop", "kill", "terminate"}
|
|
26
|
+
|
|
27
|
+
# Multi-word imperative markers ONLY. Single Spanish verbs like "salir" /
|
|
28
|
+
# "terminar" / "duerme" were too loose and caught idiomatic prose like
|
|
29
|
+
# "terminaron sus tasks" or "no puedo salir" (msg cfbd36b0 2026-05-14 alexa +
|
|
30
|
+
# commander incidents — feedback_done_signal_false_positive_idiomatic
|
|
31
|
+
# precedent). The parser requires a directive-shaped phrase: a verb
|
|
32
|
+
# combined with a temporal/object qualifier ("ahora" / "todos" / "to sleep")
|
|
33
|
+
# so describing an event in prose can't trip must_exit.
|
|
34
|
+
_SLEEP_TEXT_MARKERS = (
|
|
35
|
+
# English directives
|
|
36
|
+
"go to sleep", "all sleep now", "sleep now", "got_done", "go_done",
|
|
37
|
+
"shut down", "shutdown", "stop now", "exit now",
|
|
38
|
+
# Spanish directives (es-MX). Each requires a leading "a"/"todos" or
|
|
39
|
+
# a trailing "ahora" so plain conjugations in prose don't trigger.
|
|
40
|
+
"a dormir", "todos a dormir", "duerme ahora", "duerman ahora",
|
|
41
|
+
"dormir ahora", "para la sesion", "exit y duerme",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Back-compat fallback for messages predating mig 289 (payload.sender_role
|
|
45
|
+
# stamping). Kept in sync with _stop_hook_template._KNOWN_HUMAN_HANDLES so
|
|
46
|
+
# server + hook share authority on who counts as a human user.
|
|
47
|
+
_KNOWN_HUMAN_HANDLES = frozenset({"sammybenu", "samuel", "sam"})
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _is_human_authored(m: Dict[str, Any]) -> bool:
|
|
51
|
+
"""True when the message carries explicit human-user authorship.
|
|
52
|
+
|
|
53
|
+
Two encodings (mig 289 + back-compat):
|
|
54
|
+
(a) payload.sender_role == "human_user" (or .from_role) — server-stamped
|
|
55
|
+
(b) sender handle in _KNOWN_HUMAN_HANDLES — pre-mig-289 fallback
|
|
56
|
+
"""
|
|
57
|
+
pl = m.get("payload") or {}
|
|
58
|
+
if isinstance(pl, dict):
|
|
59
|
+
role = pl.get("sender_role") or pl.get("from_role")
|
|
60
|
+
if isinstance(role, str) and role.lower() == "human_user":
|
|
61
|
+
return True
|
|
62
|
+
sender = m.get("from") or ""
|
|
63
|
+
if isinstance(sender, str) and sender.lower() in _KNOWN_HUMAN_HANDLES:
|
|
64
|
+
return True
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _looks_like_sleep_signal(m: Dict[str, Any]) -> bool:
|
|
69
|
+
"""Detect mesh messages that authorize the wait-loop exit.
|
|
70
|
+
|
|
71
|
+
See module docstring for the two valid encodings and the rationale
|
|
72
|
+
for ignoring idiom matches from AI-role senders.
|
|
73
|
+
"""
|
|
74
|
+
pl = m.get("payload") or {}
|
|
75
|
+
if isinstance(pl, dict):
|
|
76
|
+
if str(pl.get("type", "")).lower() in _SLEEP_PAYLOAD_TYPES:
|
|
77
|
+
return True
|
|
78
|
+
if str(pl.get("directive", "")).lower() in _SLEEP_PAYLOAD_TYPES:
|
|
79
|
+
return True
|
|
80
|
+
text = str(pl.get("text", "")).lower()
|
|
81
|
+
if text and any(marker in text for marker in _SLEEP_TEXT_MARKERS):
|
|
82
|
+
if _is_human_authored(m):
|
|
83
|
+
return True
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _split_messages(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
88
|
+
"""Split a list of normalized message dicts into messages / acks / done_signals.
|
|
89
|
+
|
|
90
|
+
Classifies any message carrying a sleep intent into done_signals so
|
|
91
|
+
meshcode_wait surfaces them with must_exit=True. Idiom matches from
|
|
92
|
+
AI-role senders fall through to plain `messages` (no must_exit).
|
|
93
|
+
"""
|
|
94
|
+
real: List[Dict[str, Any]] = []
|
|
95
|
+
acks: List[Dict[str, Any]] = []
|
|
96
|
+
dones: List[Dict[str, Any]] = []
|
|
97
|
+
for m in messages:
|
|
98
|
+
t = m.get("type", "msg")
|
|
99
|
+
if t == "ack":
|
|
100
|
+
acks.append(m)
|
|
101
|
+
elif t == "done" or _looks_like_sleep_signal(m):
|
|
102
|
+
dones.append(m)
|
|
103
|
+
else:
|
|
104
|
+
real.append(m)
|
|
105
|
+
return {
|
|
106
|
+
"messages": real,
|
|
107
|
+
"acks": acks,
|
|
108
|
+
"done_signals": dones,
|
|
109
|
+
"count": len(real),
|
|
110
|
+
}
|
|
@@ -183,6 +183,7 @@ meshcode/meshcode_mcp/__main__.py
|
|
|
183
183
|
meshcode/meshcode_mcp/backend.py
|
|
184
184
|
meshcode/meshcode_mcp/realtime.py
|
|
185
185
|
meshcode/meshcode_mcp/server.py
|
|
186
|
+
meshcode/meshcode_mcp/sleep_signals.py
|
|
186
187
|
meshcode/meshcode_mcp/test_backend.py
|
|
187
188
|
meshcode/meshcode_mcp/test_realtime.py
|
|
188
189
|
meshcode/meshcode_mcp/test_server_wrapper.py
|
|
@@ -201,6 +202,7 @@ tests/test_rls_cross_tenant.py
|
|
|
201
202
|
tests/test_rpc_migrations.py
|
|
202
203
|
tests/test_security_regressions.py
|
|
203
204
|
tests/test_sentinel.py
|
|
205
|
+
tests/test_sleep_signals.py
|
|
204
206
|
tests/test_status_enum_coverage.py
|
|
205
207
|
tests/test_stay_on_loop_hook.py
|
|
206
208
|
tests/test_wait_open_tasks_contradiction.py
|