meshcode 2.11.85__tar.gz → 2.11.87__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.85 → meshcode-2.11.87}/PKG-INFO +1 -1
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/__init__.py +1 -1
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/_session_handoff_template.py +51 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/server.py +49 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/setup_clients.py +66 -1
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/SOURCES.txt +0 -3
- {meshcode-2.11.85 → meshcode-2.11.87}/pyproject.toml +1 -1
- meshcode-2.11.85/meshcode/claude_update 2.py +0 -258
- meshcode-2.11.85/meshcode/hostd 2.py +0 -849
- meshcode-2.11.85/meshcode/up 2.py +0 -257
- {meshcode-2.11.85 → meshcode-2.11.87}/README.md +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/__main__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/cli.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/compat.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/daemon.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/doctor.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/hostd.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/invites.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/launcher.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/preferences.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/secrets.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/self_update.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/up.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/upload.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/comms_v4.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_core.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/comms_v4.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_core.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/comms_v4.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_core.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/setup.cfg +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_autonomous_prompt_inject 2.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_core.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_doctor.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -113,6 +113,54 @@ def _extract_tail(transcript_path: str):
|
|
|
113
113
|
return rows[-MAX_TURNS:]
|
|
114
114
|
|
|
115
115
|
|
|
116
|
+
def _request_recycle_if_marked(project_dir) -> None:
|
|
117
|
+
"""CTX-CLOSE-RELAUNCH (task 400fc536): commander-tier sessions ask the
|
|
118
|
+
server to recycle (close+relaunch fresh) at the next task-edge, right after
|
|
119
|
+
the handoff is snapshotted. Gated by the scaffold-baked
|
|
120
|
+
.claude/meshcode_hook_ctx.json recycle_on_compact flag (commander-only v1).
|
|
121
|
+
|
|
122
|
+
Best-effort in every dimension: missing marker/flag, missing creds, import
|
|
123
|
+
failure, network error -> silently skip. handoff.json is already written, so
|
|
124
|
+
a context-recycle still relaunches WITH context; and the actual exit is the
|
|
125
|
+
server's call (mc_consume_recycle at a task boundary), never this hook.
|
|
126
|
+
"""
|
|
127
|
+
try:
|
|
128
|
+
ctx_path = project_dir / ".claude" / "meshcode_hook_ctx.json"
|
|
129
|
+
if not ctx_path.exists():
|
|
130
|
+
return
|
|
131
|
+
if not json.loads(ctx_path.read_text(encoding="utf-8")).get("recycle_on_compact"):
|
|
132
|
+
return # non-commander -> in-place autocompact, no recycle
|
|
133
|
+
mcp = json.loads((project_dir / ".mcp.json").read_text(encoding="utf-8"))
|
|
134
|
+
env = (next(iter((mcp.get("mcpServers") or {}).values()), {}) or {}).get("env", {}) or {}
|
|
135
|
+
url = env.get("SUPABASE_URL"); key = env.get("SUPABASE_KEY")
|
|
136
|
+
pid = env.get("MESHCODE_PROJECT_ID"); agent = env.get("MESHCODE_AGENT")
|
|
137
|
+
if not (url and key and pid and agent):
|
|
138
|
+
return
|
|
139
|
+
api_key = os.environ.get("MESHCODE_API_KEY")
|
|
140
|
+
if not api_key:
|
|
141
|
+
try:
|
|
142
|
+
import importlib
|
|
143
|
+
api_key = importlib.import_module("meshcode.secrets").get_api_key(
|
|
144
|
+
profile=env.get("MESHCODE_KEYCHAIN_PROFILE") or "default")
|
|
145
|
+
except Exception:
|
|
146
|
+
api_key = None
|
|
147
|
+
if not api_key:
|
|
148
|
+
return
|
|
149
|
+
import urllib.request as _u
|
|
150
|
+
body = json.dumps({
|
|
151
|
+
"p_api_key": api_key, "p_project_id": pid,
|
|
152
|
+
"p_agent_name": agent, "p_allow_busy": True, # flag-now; exit deferred to wait-loop
|
|
153
|
+
}).encode("utf-8")
|
|
154
|
+
req = _u.Request(
|
|
155
|
+
url.rstrip("/") + "/rest/v1/rpc/mc_request_recycle",
|
|
156
|
+
data=body, method="POST",
|
|
157
|
+
headers={"apikey": key, "Authorization": "Bearer " + key,
|
|
158
|
+
"Content-Type": "application/json"})
|
|
159
|
+
_u.urlopen(req, timeout=5).read() # best-effort; ignore result per backend contract
|
|
160
|
+
except Exception as e: # noqa: BLE001 — never block compaction
|
|
161
|
+
sys.stderr.write(f"[session_handoff_write] recycle-request skipped: {e}\\n")
|
|
162
|
+
|
|
163
|
+
|
|
116
164
|
def main() -> int:
|
|
117
165
|
try:
|
|
118
166
|
raw = sys.stdin.read()
|
|
@@ -137,6 +185,9 @@ def main() -> int:
|
|
|
137
185
|
tmp.replace(d / "handoff.json")
|
|
138
186
|
except OSError as e:
|
|
139
187
|
sys.stderr.write(f"[session_handoff_write] skipped: {e}\\n")
|
|
188
|
+
# CTX-CLOSE-RELAUNCH (task 400fc536): now that the thread is snapshotted,
|
|
189
|
+
# commander-tier sessions ask the server to recycle at the next task-edge.
|
|
190
|
+
_request_recycle_if_marked(_project_dir())
|
|
140
191
|
return 0
|
|
141
192
|
|
|
142
193
|
|
|
@@ -6041,6 +6041,55 @@ def meshcode_add_agent(name: str, role: str = "", autonomous: bool = False) -> D
|
|
|
6041
6041
|
return result or {"error": "failed to add agent"}
|
|
6042
6042
|
|
|
6043
6043
|
|
|
6044
|
+
@mcp.tool()
|
|
6045
|
+
@with_working_status
|
|
6046
|
+
def meshcode_agent_launch(name: str, headless: bool = False) -> Dict[str, Any]:
|
|
6047
|
+
"""Launch (or restart) another agent in YOUR meshwork — open it on command
|
|
6048
|
+
or by your own judgment (e.g. spin an agent up when task load rises).
|
|
6049
|
+
|
|
6050
|
+
COMMANDER-AGENT-LIFECYCLE (task 11feedd1). Sets desired_state=running so the
|
|
6051
|
+
host daemon spawns the agent; visible by default, headless opt-in. Only the
|
|
6052
|
+
project's commander agent (or the human owner) may power other agents, and
|
|
6053
|
+
only within their own meshwork — enforced server-side by
|
|
6054
|
+
mc_agent_power_as_agent (api_key identity + scope_meshwork_id check).
|
|
6055
|
+
|
|
6056
|
+
Args:
|
|
6057
|
+
name: target agent name in your meshwork.
|
|
6058
|
+
headless: True = run in the background with no visible window;
|
|
6059
|
+
default False = visible focused window.
|
|
6060
|
+
"""
|
|
6061
|
+
return be.sb_rpc("mc_agent_power_as_agent", {
|
|
6062
|
+
"p_api_key": _get_api_key(),
|
|
6063
|
+
"p_project_id": _PROJECT_ID,
|
|
6064
|
+
"p_agent": name,
|
|
6065
|
+
"p_state": "running",
|
|
6066
|
+
"p_headless": bool(headless),
|
|
6067
|
+
})
|
|
6068
|
+
|
|
6069
|
+
|
|
6070
|
+
@mcp.tool()
|
|
6071
|
+
@with_working_status
|
|
6072
|
+
def meshcode_agent_stop(name: str) -> Dict[str, Any]:
|
|
6073
|
+
"""Stop another agent in YOUR meshwork — close it on command or by your own
|
|
6074
|
+
judgment (e.g. spin an idle agent down).
|
|
6075
|
+
|
|
6076
|
+
COMMANDER-AGENT-LIFECYCLE (task 11feedd1). Sets desired_state=stopped; the
|
|
6077
|
+
host daemon kills the process via the stop backstop (task 94603f18), so a
|
|
6078
|
+
headless agent with no window to close is actually terminated. Commander
|
|
6079
|
+
agent (or human owner) only, scoped to your own meshwork — enforced
|
|
6080
|
+
server-side by mc_agent_power_as_agent.
|
|
6081
|
+
|
|
6082
|
+
Args:
|
|
6083
|
+
name: target agent name in your meshwork.
|
|
6084
|
+
"""
|
|
6085
|
+
return be.sb_rpc("mc_agent_power_as_agent", {
|
|
6086
|
+
"p_api_key": _get_api_key(),
|
|
6087
|
+
"p_project_id": _PROJECT_ID,
|
|
6088
|
+
"p_agent": name,
|
|
6089
|
+
"p_state": "stopped",
|
|
6090
|
+
})
|
|
6091
|
+
|
|
6092
|
+
|
|
6044
6093
|
@mcp.tool()
|
|
6045
6094
|
@with_working_status
|
|
6046
6095
|
def meshcode_set_meshwork_autonomous_default(enabled: bool) -> Dict[str, Any]:
|
|
@@ -288,6 +288,49 @@ def _stop_hook_command() -> str:
|
|
|
288
288
|
return _hook_command("stay_on_loop.py")
|
|
289
289
|
|
|
290
290
|
|
|
291
|
+
# Mirror of server.py _LEADER_KEYWORDS / _is_leader_agent — the canonical
|
|
292
|
+
# agent-side commander/leader signal. Kept in sync intentionally (small, stable).
|
|
293
|
+
_LEADER_KEYWORDS = (
|
|
294
|
+
'commander', 'lead', 'orchestrat', 'coordinator', 'coordinat',
|
|
295
|
+
'coordinad', 'jefe', 'líder', 'lider', 'director', 'manager',
|
|
296
|
+
'chief', 'captain', 'boss', 'head agent',
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _is_commander_role(name: str, role: str = "") -> bool:
|
|
301
|
+
"""True if this agent is commander/leader tier (name+role keyword match).
|
|
302
|
+
|
|
303
|
+
CTX-CLOSE-RELAUNCH (task 400fc536): the PreCompact handoff hook self-requests
|
|
304
|
+
a context-recycle ONLY for commander-tier sessions (v1 = commander-only).
|
|
305
|
+
The hook subprocess cannot import server.py's _is_leader_agent (its module
|
|
306
|
+
globals aren't set there), so we BAKE this decision into the workspace at
|
|
307
|
+
scaffold time (commander directive) using the SAME heuristic the SDK already
|
|
308
|
+
uses everywhere for leader detection. This is a client-side BEHAVIOUR gate,
|
|
309
|
+
not authorization — the server's owner check in mc_request_recycle is the
|
|
310
|
+
real security boundary — so the name/role heuristic is safe here.
|
|
311
|
+
"""
|
|
312
|
+
hay = ((name or "") + " " + (role or "")).lower()
|
|
313
|
+
return any(k in hay for k in _LEADER_KEYWORDS)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _write_hook_ctx_marker(ws: Path, is_commander: bool, agent: str = "", project: str = "") -> None:
|
|
317
|
+
"""Write .claude/meshcode_hook_ctx.json — the scaffold-baked signal the
|
|
318
|
+
PreCompact handoff hook reads to decide whether to self-request a
|
|
319
|
+
context-recycle (CTX-CLOSE-RELAUNCH). Best-effort."""
|
|
320
|
+
try:
|
|
321
|
+
(ws / ".claude").mkdir(parents=True, exist_ok=True)
|
|
322
|
+
(ws / ".claude" / "meshcode_hook_ctx.json").write_text(
|
|
323
|
+
json.dumps({
|
|
324
|
+
"recycle_on_compact": bool(is_commander),
|
|
325
|
+
"agent": agent,
|
|
326
|
+
"project": project,
|
|
327
|
+
}, indent=2) + "\n",
|
|
328
|
+
encoding="utf-8",
|
|
329
|
+
)
|
|
330
|
+
except Exception as _e: # noqa: BLE001 — never block scaffold on this marker
|
|
331
|
+
print(f"[meshcode] WARNING: could not write hook ctx marker: {_e}", file=sys.stderr)
|
|
332
|
+
|
|
333
|
+
|
|
291
334
|
# The meshcode hooks (event -> hook-script filename). Each is matched in
|
|
292
335
|
# settings.json by its script filename so the heal touches ONLY meshcode's own
|
|
293
336
|
# hooks and never a user-added hook on the same event.
|
|
@@ -1487,6 +1530,10 @@ Call `meshcode_wait` now.
|
|
|
1487
1530
|
except OSError:
|
|
1488
1531
|
pass
|
|
1489
1532
|
(ws / ".claude" / "settings.json").write_text(settings_body, encoding="utf-8")
|
|
1533
|
+
# CTX-CLOSE-RELAUNCH (task 400fc536): bake the commander-tier signal the
|
|
1534
|
+
# PreCompact handoff hook reads to decide whether to self-request a
|
|
1535
|
+
# context-recycle (commander-only v1).
|
|
1536
|
+
_write_hook_ctx_marker(ws, _is_commander_role(agent, role), agent=agent, project=project)
|
|
1490
1537
|
except Exception as _e:
|
|
1491
1538
|
print(f"[meshcode] WARNING: could not write /meshcode-wait command + hooks: {_e}", file=sys.stderr)
|
|
1492
1539
|
|
|
@@ -1749,7 +1796,25 @@ def patch_hooks(dry_run: bool = False) -> int:
|
|
|
1749
1796
|
except Exception as e: # noqa: BLE001 — never let one ws abort the sweep
|
|
1750
1797
|
settings_changed, settings_note = False, f"settings heal failed: {e}"
|
|
1751
1798
|
|
|
1752
|
-
|
|
1799
|
+
# CTX-CLOSE-RELAUNCH (task 400fc536): backfill the commander-tier marker
|
|
1800
|
+
# the PreCompact hook reads. Recompute is_commander from the workspace's
|
|
1801
|
+
# baked .mcp.json (MESHCODE_AGENT + MESHCODE_ROLE) so existing commanders
|
|
1802
|
+
# get the recycle trigger without a full re-scaffold.
|
|
1803
|
+
marker_note = "marker skipped"
|
|
1804
|
+
if not dry_run:
|
|
1805
|
+
try:
|
|
1806
|
+
mcp = json.loads((ws / ".mcp.json").read_text(encoding="utf-8"))
|
|
1807
|
+
env = (next(iter((mcp.get("mcpServers") or {}).values()), {}) or {}).get("env", {}) or {}
|
|
1808
|
+
_agent = env.get("MESHCODE_AGENT") or ws.name
|
|
1809
|
+
_role = env.get("MESHCODE_ROLE") or ""
|
|
1810
|
+
_write_hook_ctx_marker(ws, _is_commander_role(_agent, _role), agent=_agent, project=env.get("MESHCODE_PROJECT") or "")
|
|
1811
|
+
marker_note = "marker written"
|
|
1812
|
+
except Exception as e: # noqa: BLE001
|
|
1813
|
+
marker_note = f"marker skipped ({e})"
|
|
1814
|
+
else:
|
|
1815
|
+
marker_note = "marker would write"
|
|
1816
|
+
|
|
1817
|
+
record = f"{ws.name} ({body_note}; {settings_note}; {marker_note})"
|
|
1753
1818
|
(patched if (body_changed or settings_changed) else skipped).append(record)
|
|
1754
1819
|
|
|
1755
1820
|
verb = "would patch" if dry_run else "patched"
|
|
@@ -6,7 +6,6 @@ meshcode/_session_handoff_template.py
|
|
|
6
6
|
meshcode/_stop_hook_template.py
|
|
7
7
|
meshcode/ascii_art.py
|
|
8
8
|
meshcode/atomic_push.py
|
|
9
|
-
meshcode/claude_update 2.py
|
|
10
9
|
meshcode/claude_update.py
|
|
11
10
|
meshcode/cli.py
|
|
12
11
|
meshcode/comms_v4.py
|
|
@@ -16,7 +15,6 @@ meshcode/date_parse.py
|
|
|
16
15
|
meshcode/doctor.py
|
|
17
16
|
meshcode/error_hints.py
|
|
18
17
|
meshcode/exceptions.py
|
|
19
|
-
meshcode/hostd 2.py
|
|
20
18
|
meshcode/hostd.py
|
|
21
19
|
meshcode/invites.py
|
|
22
20
|
meshcode/launcher.py
|
|
@@ -31,7 +29,6 @@ meshcode/secrets.py
|
|
|
31
29
|
meshcode/self_update.py
|
|
32
30
|
meshcode/setup_clients.py
|
|
33
31
|
meshcode/supervisor.py
|
|
34
|
-
meshcode/up 2.py
|
|
35
32
|
meshcode/up.py
|
|
36
33
|
meshcode/upload.py
|
|
37
34
|
meshcode-backend-wt/comms_v4.py
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
"""Synchronous Claude Code CLI auto-update for `meshcode run`.
|
|
2
|
-
|
|
3
|
-
Companion to self_update.py (which handles the `meshcode` Python package).
|
|
4
|
-
When `meshcode run` launches an agent, this module probes the npm registry
|
|
5
|
-
for the latest @anthropic-ai/claude-code release and runs `npm install -g`
|
|
6
|
-
inline if the installed version is older. The launch BLOCKS until the
|
|
7
|
-
upgrade finishes (or times out).
|
|
8
|
-
|
|
9
|
-
This is intentional: Samuel's requirement is "siempre arrancar con la
|
|
10
|
-
ultima version". The two-launch async model in self_update.py cannot
|
|
11
|
-
satisfy that — it always uses the old binary on launch N. The blocking
|
|
12
|
-
model trades ~5-30s extra latency on launches where an upgrade is
|
|
13
|
-
available for the guarantee that the freshly-spawned editor uses the
|
|
14
|
-
newest CLI.
|
|
15
|
-
|
|
16
|
-
Skip conditions (any one → no-op):
|
|
17
|
-
- MESHCODE_NO_UPDATE=1 / --no-update on argv
|
|
18
|
-
- Inside an existing Claude Code session (CLAUDECODE=1)
|
|
19
|
-
- User pinned a specific version via env / prefs (let _resolve_pinned_claude handle it)
|
|
20
|
-
- Editor is not `claude` (cursor / code / codex / windsurf)
|
|
21
|
-
- npm not in PATH
|
|
22
|
-
- Network unreachable / npm registry down
|
|
23
|
-
- Already at latest version
|
|
24
|
-
|
|
25
|
-
Failures are best-effort: any error prints a single WARN line and lets
|
|
26
|
-
launch continue with whatever version is installed.
|
|
27
|
-
"""
|
|
28
|
-
from __future__ import annotations
|
|
29
|
-
|
|
30
|
-
import json
|
|
31
|
-
import os
|
|
32
|
-
import re
|
|
33
|
-
import shutil
|
|
34
|
-
import subprocess
|
|
35
|
-
import sys
|
|
36
|
-
import time
|
|
37
|
-
from pathlib import Path
|
|
38
|
-
from typing import Optional, Tuple
|
|
39
|
-
|
|
40
|
-
NPM_PKG = "@anthropic-ai/claude-code"
|
|
41
|
-
NPM_REGISTRY_URL = f"https://registry.npmjs.org/{NPM_PKG}/latest"
|
|
42
|
-
NETWORK_TIMEOUT_SEC = 2.5
|
|
43
|
-
INSTALL_TIMEOUT_SEC = 90 # npm i -g can be slow on cold-cache machines
|
|
44
|
-
|
|
45
|
-
STATE_DIR = Path.home() / ".meshcode"
|
|
46
|
-
LOG_PATH = STATE_DIR / "claude_update.log"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# ============================================================
|
|
50
|
-
# Skip-condition helpers
|
|
51
|
-
# ============================================================
|
|
52
|
-
|
|
53
|
-
def _is_claude_session() -> bool:
|
|
54
|
-
"""True if we're being called from inside an existing Claude Code session.
|
|
55
|
-
|
|
56
|
-
Replacing the npm binary while claude is running is unsafe and pointless
|
|
57
|
-
— the running claude won't pick up the new code anyway.
|
|
58
|
-
"""
|
|
59
|
-
return os.environ.get("CLAUDECODE") == "1" or bool(os.environ.get("CLAUDE_CODE_SESSION"))
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _update_disabled() -> bool:
|
|
63
|
-
# task 399d7b51 FIX A (unify): honor MESHCODE_NO_AUTO_UPDATE too. hostd launcher / run_agent /
|
|
64
|
-
# schtasks set that var to disable ALL launch-time auto-update; previously only the meshcode-pip
|
|
65
|
-
# path checked MESHCODE_NO_UPDATE, so the claude-CLI blocking update still fired (extra hang).
|
|
66
|
-
if os.environ.get("MESHCODE_NO_UPDATE") == "1":
|
|
67
|
-
return True
|
|
68
|
-
if os.environ.get("MESHCODE_NO_AUTO_UPDATE") == "1":
|
|
69
|
-
return True
|
|
70
|
-
if "--no-update" in sys.argv or "--no-auto-update" in sys.argv:
|
|
71
|
-
return True
|
|
72
|
-
return False
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def _resolve_user_pin() -> Optional[str]:
|
|
76
|
-
"""Return a specific version pin if set; None if 'latest' / unset.
|
|
77
|
-
|
|
78
|
-
Sources checked (first non-empty wins):
|
|
79
|
-
1. MESHCODE_CLAUDE_VERSION env var
|
|
80
|
-
2. prefs['claude_version']
|
|
81
|
-
|
|
82
|
-
Note: 'latest' / 'none' / 'skip' values are treated as "no pin" — the
|
|
83
|
-
user wants whatever is current. We do NOT consult the global admin
|
|
84
|
-
config from mc_global_config here; if an admin pins a specific
|
|
85
|
-
version, _resolve_pinned_claude in run_agent.py will route through
|
|
86
|
-
npx regardless of what's installed locally.
|
|
87
|
-
"""
|
|
88
|
-
pinned = (os.environ.get("MESHCODE_CLAUDE_VERSION") or "").strip()
|
|
89
|
-
if not pinned:
|
|
90
|
-
try:
|
|
91
|
-
from .preferences import load_prefs
|
|
92
|
-
pinned = (load_prefs().get("claude_version") or "").strip()
|
|
93
|
-
except Exception:
|
|
94
|
-
pinned = ""
|
|
95
|
-
if not pinned:
|
|
96
|
-
return None
|
|
97
|
-
if pinned.lower() in ("latest", "none", "skip"):
|
|
98
|
-
return None
|
|
99
|
-
return pinned
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# ============================================================
|
|
103
|
-
# Version probes
|
|
104
|
-
# ============================================================
|
|
105
|
-
|
|
106
|
-
def _get_installed_version(editor_cmd: str) -> Optional[str]:
|
|
107
|
-
"""Run `<editor_cmd> --version` and pull the first semver from stdout."""
|
|
108
|
-
try:
|
|
109
|
-
use_shell = sys.platform == "win32" and editor_cmd.lower().endswith((".cmd", ".bat"))
|
|
110
|
-
r = subprocess.run(
|
|
111
|
-
[editor_cmd, "--version"],
|
|
112
|
-
capture_output=True, text=True, timeout=10,
|
|
113
|
-
shell=use_shell,
|
|
114
|
-
)
|
|
115
|
-
m = re.search(r"\d+\.\d+\.\d+", r.stdout or "")
|
|
116
|
-
return m.group(0) if m else None
|
|
117
|
-
except Exception:
|
|
118
|
-
return None
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def _fetch_npm_latest() -> Optional[str]:
|
|
122
|
-
"""GET https://registry.npmjs.org/<pkg>/latest — returns the JSON manifest
|
|
123
|
-
of the version tagged `latest`, with a `version` field.
|
|
124
|
-
|
|
125
|
-
Do NOT send the `application/vnd.npm.install-v1+json` Accept header
|
|
126
|
-
here: the slim-manifest content type is only valid on the package
|
|
127
|
-
ROOT (/<pkg>), not on /<pkg>/<tag>. Sending it on the tag endpoint
|
|
128
|
-
returns HTTP 406. Default Accept (*/*) gets the canonical JSON.
|
|
129
|
-
"""
|
|
130
|
-
try:
|
|
131
|
-
import urllib.request
|
|
132
|
-
req = urllib.request.Request(NPM_REGISTRY_URL)
|
|
133
|
-
with urllib.request.urlopen(req, timeout=NETWORK_TIMEOUT_SEC) as resp:
|
|
134
|
-
data = json.loads(resp.read().decode("utf-8"))
|
|
135
|
-
v = data.get("version")
|
|
136
|
-
if isinstance(v, str):
|
|
137
|
-
return v
|
|
138
|
-
except Exception:
|
|
139
|
-
pass
|
|
140
|
-
return None
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def _version_tuple(v: str) -> Tuple[int, ...]:
|
|
144
|
-
parts = []
|
|
145
|
-
for p in v.split("."):
|
|
146
|
-
digits = "".join(c for c in p if c.isdigit())
|
|
147
|
-
parts.append(int(digits) if digits else 0)
|
|
148
|
-
return tuple(parts)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def _is_newer(remote: str, local: str) -> bool:
|
|
152
|
-
try:
|
|
153
|
-
return _version_tuple(remote) > _version_tuple(local)
|
|
154
|
-
except Exception:
|
|
155
|
-
return False
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# ============================================================
|
|
159
|
-
# Editor-stem detection (only act when editor is claude)
|
|
160
|
-
# ============================================================
|
|
161
|
-
|
|
162
|
-
def _editor_is_claude(editor_cmd: str) -> bool:
|
|
163
|
-
name = os.path.basename(editor_cmd).lower()
|
|
164
|
-
if sys.platform == "win32":
|
|
165
|
-
name = name.rsplit(".", 1)[0] if "." in name else name
|
|
166
|
-
return name == "claude"
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
# ============================================================
|
|
170
|
-
# Public entrypoint
|
|
171
|
-
# ============================================================
|
|
172
|
-
|
|
173
|
-
def check_and_maybe_update_claude_blocking(
|
|
174
|
-
editor_cmd: str,
|
|
175
|
-
verbose: bool = True,
|
|
176
|
-
timeout_sec: int = INSTALL_TIMEOUT_SEC,
|
|
177
|
-
) -> Optional[str]:
|
|
178
|
-
"""Probe npm + foreground-install latest claude if newer.
|
|
179
|
-
|
|
180
|
-
Returns the new version on successful upgrade, None otherwise.
|
|
181
|
-
Never raises — all errors are swallowed and logged via WARN line.
|
|
182
|
-
"""
|
|
183
|
-
if _update_disabled():
|
|
184
|
-
return None
|
|
185
|
-
if _is_claude_session():
|
|
186
|
-
return None
|
|
187
|
-
if _resolve_user_pin():
|
|
188
|
-
return None
|
|
189
|
-
if not _editor_is_claude(editor_cmd):
|
|
190
|
-
return None
|
|
191
|
-
|
|
192
|
-
current = _get_installed_version(editor_cmd)
|
|
193
|
-
latest = _fetch_npm_latest()
|
|
194
|
-
if not current:
|
|
195
|
-
if verbose:
|
|
196
|
-
print(f"[meshcode] (Could not read claude --version; skipping auto-update)", file=sys.stderr)
|
|
197
|
-
return None
|
|
198
|
-
if not latest:
|
|
199
|
-
if verbose:
|
|
200
|
-
print(f"[meshcode] (Could not reach npm registry; staying on claude {current})", file=sys.stderr)
|
|
201
|
-
return None
|
|
202
|
-
if not _is_newer(latest, current):
|
|
203
|
-
if verbose:
|
|
204
|
-
print(f"[meshcode] claude {current} (already latest)", file=sys.stderr)
|
|
205
|
-
return None
|
|
206
|
-
|
|
207
|
-
npm = shutil.which("npm")
|
|
208
|
-
if not npm:
|
|
209
|
-
if verbose:
|
|
210
|
-
print(
|
|
211
|
-
f"[meshcode] WARN: claude {current} < {latest} on npm, but `npm` not in PATH; "
|
|
212
|
-
f"continuing on {current}",
|
|
213
|
-
file=sys.stderr,
|
|
214
|
-
)
|
|
215
|
-
return None
|
|
216
|
-
|
|
217
|
-
if verbose:
|
|
218
|
-
print(
|
|
219
|
-
f"[meshcode] Upgrading claude {current} -> {latest} (blocking, can take ~30s)...",
|
|
220
|
-
file=sys.stderr,
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
cmd = [npm, "install", "-g", f"{NPM_PKG}@{latest}"]
|
|
224
|
-
try:
|
|
225
|
-
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
226
|
-
with open(LOG_PATH, "ab") as logf:
|
|
227
|
-
logf.write(
|
|
228
|
-
f"\n=== {time.strftime('%Y-%m-%d %H:%M:%S')} blocking "
|
|
229
|
-
f"npm i -g {NPM_PKG}@{latest} ===\n".encode()
|
|
230
|
-
)
|
|
231
|
-
logf.flush()
|
|
232
|
-
proc = subprocess.run(cmd, stdout=logf, stderr=logf, timeout=timeout_sec)
|
|
233
|
-
if proc.returncode == 0:
|
|
234
|
-
if verbose:
|
|
235
|
-
print(f"[meshcode] claude upgraded to {latest}", file=sys.stderr)
|
|
236
|
-
return latest
|
|
237
|
-
if verbose:
|
|
238
|
-
print(
|
|
239
|
-
f"[meshcode] WARN: npm install exit {proc.returncode}; "
|
|
240
|
-
f"continuing on {current} (log: {LOG_PATH})",
|
|
241
|
-
file=sys.stderr,
|
|
242
|
-
)
|
|
243
|
-
return None
|
|
244
|
-
except subprocess.TimeoutExpired:
|
|
245
|
-
if verbose:
|
|
246
|
-
print(
|
|
247
|
-
f"[meshcode] WARN: npm install timed out after {timeout_sec}s; "
|
|
248
|
-
f"continuing on {current}",
|
|
249
|
-
file=sys.stderr,
|
|
250
|
-
)
|
|
251
|
-
return None
|
|
252
|
-
except Exception as e:
|
|
253
|
-
if verbose:
|
|
254
|
-
print(
|
|
255
|
-
f"[meshcode] WARN: npm install failed: {e}; continuing on {current}",
|
|
256
|
-
file=sys.stderr,
|
|
257
|
-
)
|
|
258
|
-
return None
|