meshcode 2.11.83__tar.gz → 2.11.85__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.83 → meshcode-2.11.85}/PKG-INFO +1 -1
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/__init__.py +1 -1
- meshcode-2.11.85/meshcode/_session_handoff_template.py +245 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/hostd.py +155 -4
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/protocol_handler.py +6 -3
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/setup_clients.py +131 -61
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode.egg-info/SOURCES.txt +1 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/pyproject.toml +1 -1
- {meshcode-2.11.83 → meshcode-2.11.85}/README.md +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/__main__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/claude_update 2.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/cli.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/compat.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/daemon.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/doctor.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/hostd 2.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/invites.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/launcher.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/preferences.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/secrets.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/self_update.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/up 2.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/up.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode/upload.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/comms_v4.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_core.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/comms_v4.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_core.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/comms_v4.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_core.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/setup.cfg +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_autonomous_prompt_inject 2.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_core.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_doctor.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.83 → meshcode-2.11.85}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Session-handoff hook templates — single source of truth for the
|
|
2
|
+
PreCompact (write) + SessionStart (read) hooks.
|
|
3
|
+
|
|
4
|
+
CTX-CLOSE-RELAUNCH (task bcd157a9, backend2): when a commander/agent session
|
|
5
|
+
gets recycled at high context — either by Claude Code's in-place autocompact
|
|
6
|
+
or by a clean close+relaunch — the IN-FLIGHT working context (which task I was
|
|
7
|
+
mid-executing, what I just edited/decided this session) is lost. meshcode_boot
|
|
8
|
+
restores mesh state (tasks, inbox, memory) authoritatively, but NOT the
|
|
9
|
+
conversational thread. These two hooks bridge that gap:
|
|
10
|
+
|
|
11
|
+
* PreCompact -> session_handoff_write.py: dump a compact snapshot of the
|
|
12
|
+
recent transcript tail to .claude/handoff.json BEFORE the context is
|
|
13
|
+
compacted/the session exits. Pure local file I/O — no creds, no network,
|
|
14
|
+
no MCP — so it is safe to run in any hook subprocess on any platform.
|
|
15
|
+
* SessionStart -> session_handoff_read.py: on the next (fresh) session,
|
|
16
|
+
inject that snapshot back as additionalContext so the agent resumes the
|
|
17
|
+
thread, then archive it so it is injected exactly once.
|
|
18
|
+
|
|
19
|
+
Stored as string constants (same pattern as _stop_hook_template.py) so
|
|
20
|
+
`meshcode patch-hooks` can backfill existing workspaces without a full
|
|
21
|
+
re-scaffold. The recycle TRIGGER itself (mc_request_recycle / wait-loop
|
|
22
|
+
must_exit=recycle / hostd context branch) is owned by the daemon/server side
|
|
23
|
+
and is intentionally NOT in these scripts — handoff preservation is decoupled
|
|
24
|
+
from whatever causes the restart, so it works for autocompact, clean recycle,
|
|
25
|
+
crash-respawn, or a manual relaunch alike.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Shared constants kept in sync between the two bodies.
|
|
29
|
+
_HANDOFF_FILENAME = "handoff.json"
|
|
30
|
+
_HANDOFF_MAX_TURNS = 24 # transcript tail entries to preserve
|
|
31
|
+
_HANDOFF_MAX_CHARS_PER_TURN = 1200
|
|
32
|
+
_HANDOFF_MAX_AGE_S = 24 * 3600 # ignore stale handoffs on read
|
|
33
|
+
|
|
34
|
+
HANDOFF_WRITE_BODY = '''#!/usr/bin/env python3
|
|
35
|
+
"""PreCompact hook: snapshot the recent transcript tail to .claude/handoff.json
|
|
36
|
+
so a fresh post-recycle session can resume the in-flight thread.
|
|
37
|
+
|
|
38
|
+
CTX-CLOSE-RELAUNCH (task bcd157a9). Pure local file I/O — never touches the
|
|
39
|
+
network, never imports meshcode, never blocks. Any failure is swallowed and the
|
|
40
|
+
hook exits 0 so it can NEVER stall or break a Claude Code compaction/exit.
|
|
41
|
+
|
|
42
|
+
Claude Code passes a JSON event on stdin:
|
|
43
|
+
{"hook_event_name":"PreCompact","session_id":..,"transcript_path":..,
|
|
44
|
+
"trigger":"auto"|"manual", ...}
|
|
45
|
+
"""
|
|
46
|
+
import json
|
|
47
|
+
import os
|
|
48
|
+
import sys
|
|
49
|
+
from pathlib import Path
|
|
50
|
+
|
|
51
|
+
MAX_TURNS = 24
|
|
52
|
+
MAX_CHARS_PER_TURN = 1200
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _project_dir() -> Path:
|
|
56
|
+
# Claude Code exports CLAUDE_PROJECT_DIR for hooks; fall back to cwd.
|
|
57
|
+
return Path(os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd())
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _text_from_message(msg) -> str:
|
|
61
|
+
"""Flatten a transcript message's content into plain text + tool markers."""
|
|
62
|
+
if isinstance(msg, str):
|
|
63
|
+
return msg
|
|
64
|
+
if not isinstance(msg, dict):
|
|
65
|
+
return ""
|
|
66
|
+
content = msg.get("content")
|
|
67
|
+
if isinstance(content, str):
|
|
68
|
+
return content
|
|
69
|
+
parts = []
|
|
70
|
+
if isinstance(content, list):
|
|
71
|
+
for block in content:
|
|
72
|
+
if not isinstance(block, dict):
|
|
73
|
+
continue
|
|
74
|
+
btype = block.get("type")
|
|
75
|
+
if btype == "text" and block.get("text"):
|
|
76
|
+
parts.append(str(block["text"]))
|
|
77
|
+
elif btype == "tool_use":
|
|
78
|
+
parts.append(f"[tool_use:{block.get('name','?')}]")
|
|
79
|
+
elif btype == "tool_result":
|
|
80
|
+
parts.append("[tool_result]")
|
|
81
|
+
return " ".join(parts).strip()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _extract_tail(transcript_path: str):
|
|
85
|
+
"""Return up to MAX_TURNS (role, text) tuples from the end of the JSONL
|
|
86
|
+
transcript, skipping empty / meta-only entries."""
|
|
87
|
+
p = Path(transcript_path) if transcript_path else None
|
|
88
|
+
if not p or not p.exists():
|
|
89
|
+
return []
|
|
90
|
+
rows = []
|
|
91
|
+
try:
|
|
92
|
+
with p.open("r", encoding="utf-8", errors="replace") as fh:
|
|
93
|
+
for line in fh:
|
|
94
|
+
line = line.strip()
|
|
95
|
+
if not line:
|
|
96
|
+
continue
|
|
97
|
+
try:
|
|
98
|
+
obj = json.loads(line)
|
|
99
|
+
except (json.JSONDecodeError, ValueError):
|
|
100
|
+
continue
|
|
101
|
+
msg = obj.get("message") if isinstance(obj, dict) else None
|
|
102
|
+
role = (obj.get("type") or (msg or {}).get("role") or "").strip()
|
|
103
|
+
if role not in ("user", "assistant", "human"):
|
|
104
|
+
continue
|
|
105
|
+
text = _text_from_message(msg if msg is not None else obj)
|
|
106
|
+
if not text:
|
|
107
|
+
continue
|
|
108
|
+
if len(text) > MAX_CHARS_PER_TURN:
|
|
109
|
+
text = text[:MAX_CHARS_PER_TURN] + " …[truncated]"
|
|
110
|
+
rows.append(("user" if role in ("user", "human") else "assistant", text))
|
|
111
|
+
except OSError:
|
|
112
|
+
return []
|
|
113
|
+
return rows[-MAX_TURNS:]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def main() -> int:
|
|
117
|
+
try:
|
|
118
|
+
raw = sys.stdin.read()
|
|
119
|
+
event = json.loads(raw) if raw.strip() else {}
|
|
120
|
+
except (json.JSONDecodeError, ValueError):
|
|
121
|
+
event = {}
|
|
122
|
+
|
|
123
|
+
tail = _extract_tail(event.get("transcript_path", ""))
|
|
124
|
+
handoff = {
|
|
125
|
+
"schema": 1,
|
|
126
|
+
"session_id": event.get("session_id"),
|
|
127
|
+
"trigger": event.get("trigger") or event.get("hook_event_name") or "precompact",
|
|
128
|
+
"agent": os.environ.get("MESHCODE_AGENT"),
|
|
129
|
+
"project": os.environ.get("MESHCODE_PROJECT"),
|
|
130
|
+
"turns": [{"role": r, "text": t} for (r, t) in tail],
|
|
131
|
+
}
|
|
132
|
+
try:
|
|
133
|
+
d = _project_dir() / ".claude"
|
|
134
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
135
|
+
tmp = d / "handoff.json.tmp"
|
|
136
|
+
tmp.write_text(json.dumps(handoff, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
137
|
+
tmp.replace(d / "handoff.json")
|
|
138
|
+
except OSError as e:
|
|
139
|
+
sys.stderr.write(f"[session_handoff_write] skipped: {e}\\n")
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
try:
|
|
145
|
+
sys.exit(main())
|
|
146
|
+
except Exception as e: # never break compaction
|
|
147
|
+
sys.stderr.write(f"[session_handoff_write] error: {e}\\n")
|
|
148
|
+
sys.exit(0)
|
|
149
|
+
'''
|
|
150
|
+
|
|
151
|
+
HANDOFF_READ_BODY = '''#!/usr/bin/env python3
|
|
152
|
+
"""SessionStart hook: if a handoff snapshot exists, inject it as
|
|
153
|
+
additionalContext so a freshly-recycled session resumes the in-flight thread,
|
|
154
|
+
then archive it so it is injected exactly once.
|
|
155
|
+
|
|
156
|
+
CTX-CLOSE-RELAUNCH (task bcd157a9). Pure local file I/O — no network, no
|
|
157
|
+
meshcode import, never blocks. meshcode_boot remains authoritative for mesh
|
|
158
|
+
state; this only restores the conversational thread.
|
|
159
|
+
|
|
160
|
+
Claude Code passes a JSON event on stdin:
|
|
161
|
+
{"hook_event_name":"SessionStart","source":"startup"|"resume"|"compact",..}
|
|
162
|
+
and reads our additionalContext from this hook's stdout JSON:
|
|
163
|
+
{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":..}}
|
|
164
|
+
"""
|
|
165
|
+
import json
|
|
166
|
+
import os
|
|
167
|
+
import sys
|
|
168
|
+
import time
|
|
169
|
+
from pathlib import Path
|
|
170
|
+
|
|
171
|
+
MAX_AGE_S = 24 * 3600
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _project_dir() -> Path:
|
|
175
|
+
return Path(os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd())
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _emit(context: str) -> None:
|
|
179
|
+
sys.stdout.write(json.dumps({
|
|
180
|
+
"hookSpecificOutput": {
|
|
181
|
+
"hookEventName": "SessionStart",
|
|
182
|
+
"additionalContext": context,
|
|
183
|
+
}
|
|
184
|
+
}))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _render(handoff: dict) -> str:
|
|
188
|
+
turns = handoff.get("turns") or []
|
|
189
|
+
if not turns:
|
|
190
|
+
return ""
|
|
191
|
+
lines = [
|
|
192
|
+
"## Recovered session handoff (CTX-CLOSE-RELAUNCH)",
|
|
193
|
+
"",
|
|
194
|
+
"Your previous session was recycled at high context. meshcode_boot has "
|
|
195
|
+
"the authoritative mesh state (tasks/inbox/memory); the thread tail "
|
|
196
|
+
"below is the in-flight context that boot does not restore. Use it to "
|
|
197
|
+
"resume what you were mid-doing, then run the normal boot loop.",
|
|
198
|
+
"",
|
|
199
|
+
f"_trigger: {handoff.get('trigger','?')} · turns preserved: {len(turns)}_",
|
|
200
|
+
"",
|
|
201
|
+
]
|
|
202
|
+
for t in turns:
|
|
203
|
+
role = (t.get("role") or "?").upper()
|
|
204
|
+
text = (t.get("text") or "").strip()
|
|
205
|
+
if text:
|
|
206
|
+
lines.append(f"**{role}:** {text}")
|
|
207
|
+
lines.append("")
|
|
208
|
+
return "\\n".join(lines).strip()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def main() -> int:
|
|
212
|
+
path = _project_dir() / ".claude" / "handoff.json"
|
|
213
|
+
if not path.exists():
|
|
214
|
+
return 0
|
|
215
|
+
try:
|
|
216
|
+
handoff = json.loads(path.read_text(encoding="utf-8"))
|
|
217
|
+
except (json.JSONDecodeError, ValueError, OSError):
|
|
218
|
+
return 0
|
|
219
|
+
|
|
220
|
+
# Ignore stale snapshots (mtime-based; avoids resurrecting old threads).
|
|
221
|
+
try:
|
|
222
|
+
if (time.time() - path.stat().st_mtime) > MAX_AGE_S:
|
|
223
|
+
path.replace(path.with_suffix(".stale.json"))
|
|
224
|
+
return 0
|
|
225
|
+
except OSError:
|
|
226
|
+
pass
|
|
227
|
+
|
|
228
|
+
context = _render(handoff)
|
|
229
|
+
if context:
|
|
230
|
+
_emit(context)
|
|
231
|
+
# Archive so the handoff is injected exactly once.
|
|
232
|
+
try:
|
|
233
|
+
path.replace(path.with_suffix(".consumed.json"))
|
|
234
|
+
except OSError:
|
|
235
|
+
pass
|
|
236
|
+
return 0
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if __name__ == "__main__":
|
|
240
|
+
try:
|
|
241
|
+
sys.exit(main())
|
|
242
|
+
except Exception as e:
|
|
243
|
+
sys.stderr.write(f"[session_handoff_read] error: {e}\\n")
|
|
244
|
+
sys.exit(0)
|
|
245
|
+
'''
|
|
@@ -36,6 +36,7 @@ import json
|
|
|
36
36
|
import os
|
|
37
37
|
import shlex
|
|
38
38
|
import shutil
|
|
39
|
+
import signal as _signal
|
|
39
40
|
import subprocess
|
|
40
41
|
import sys
|
|
41
42
|
import time
|
|
@@ -260,8 +261,12 @@ def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
|
|
|
260
261
|
try:
|
|
261
262
|
# `python -m meshcode` (NOT the meshcode.exe shim) so the .exe isn't held open by the
|
|
262
263
|
# agent -> a background `pip install -U` can replace it on Windows (task 14782bb4 #4).
|
|
263
|
-
subprocess.Popen([sys.executable, "-m", "meshcode", "run", target], **kwargs)
|
|
264
|
-
|
|
264
|
+
proc = subprocess.Popen([sys.executable, "-m", "meshcode", "run", target], **kwargs)
|
|
265
|
+
# LAUNCH-NO-WINDOW (task 35bee961): record the headless PID so the Stop sweep
|
|
266
|
+
# (_do_stops) can hard-kill it — a headless agent has NO window for Samuel to
|
|
267
|
+
# close, so desired_state='stopped' must be enforced by killing the process.
|
|
268
|
+
_record_headless_pid(target, proc.pid)
|
|
269
|
+
_log(f"spawned {target} HEADLESS (no window, {sys.platform}; pid={proc.pid}; log={log_path})")
|
|
265
270
|
return True
|
|
266
271
|
except Exception as e:
|
|
267
272
|
_log(f"WARN: headless spawn {target} failed: {e}")
|
|
@@ -369,6 +374,151 @@ def _save_state(st: dict) -> None:
|
|
|
369
374
|
pass
|
|
370
375
|
|
|
371
376
|
|
|
377
|
+
# ------------------------------------------------------------------
|
|
378
|
+
# Stop — hard-kill a HEADLESS agent (task 35bee961). A headless agent has no
|
|
379
|
+
# window Samuel can close, so a Stop (desired_state='stopped') that the agent
|
|
380
|
+
# doesn't honor cooperatively (must_exit) would otherwise run forever. We record
|
|
381
|
+
# each headless spawn's PID (persisted across hostd restarts) and kill it when the
|
|
382
|
+
# cloud says it should be stopped but it's still heartbeating.
|
|
383
|
+
# ------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
def _record_headless_pid(target: str, pid: int) -> None:
|
|
386
|
+
try:
|
|
387
|
+
st = _load_state()
|
|
388
|
+
pids = st.get("headless_pids") or {}
|
|
389
|
+
pids[target] = pid
|
|
390
|
+
st["headless_pids"] = pids
|
|
391
|
+
_save_state(st)
|
|
392
|
+
except Exception:
|
|
393
|
+
pass
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _pid_cmdline(pid: int) -> str:
|
|
397
|
+
"""Best-effort command line for a pid (to avoid killing a reused PID). '' on failure."""
|
|
398
|
+
try:
|
|
399
|
+
if sys.platform == "win32":
|
|
400
|
+
out = subprocess.run(
|
|
401
|
+
["wmic", "process", "where", f"ProcessId={pid}", "get", "CommandLine"],
|
|
402
|
+
capture_output=True, text=True, timeout=5).stdout
|
|
403
|
+
else:
|
|
404
|
+
out = subprocess.run(
|
|
405
|
+
["ps", "-o", "args=", "-p", str(pid)],
|
|
406
|
+
capture_output=True, text=True, timeout=5).stdout
|
|
407
|
+
return out or ""
|
|
408
|
+
except Exception:
|
|
409
|
+
return ""
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _discover_agent_pids(target: str) -> list:
|
|
413
|
+
"""Fallback PID discovery by command line, for agents spawned before this hostd
|
|
414
|
+
(no recorded PID) or after a state-file loss. Matches `meshcode run <target>`.
|
|
415
|
+
Best-effort; returns [] on any failure."""
|
|
416
|
+
pids = []
|
|
417
|
+
try:
|
|
418
|
+
if sys.platform == "win32":
|
|
419
|
+
out = subprocess.run(
|
|
420
|
+
["wmic", "process", "where", "name='python.exe'", "get", "ProcessId,CommandLine", "/FORMAT:LIST"],
|
|
421
|
+
capture_output=True, text=True, timeout=8).stdout
|
|
422
|
+
block_pid = None
|
|
423
|
+
for line in out.splitlines():
|
|
424
|
+
line = line.strip()
|
|
425
|
+
if line.startswith("CommandLine="):
|
|
426
|
+
block_pid = ("meshcode" in line and f"run {target}" in line)
|
|
427
|
+
elif line.startswith("ProcessId=") and block_pid:
|
|
428
|
+
try:
|
|
429
|
+
pids.append(int(line.split("=", 1)[1]))
|
|
430
|
+
except Exception:
|
|
431
|
+
pass
|
|
432
|
+
block_pid = None
|
|
433
|
+
else:
|
|
434
|
+
out = subprocess.run(
|
|
435
|
+
["pgrep", "-f", f"meshcode run {target}"],
|
|
436
|
+
capture_output=True, text=True, timeout=8).stdout
|
|
437
|
+
for ln in out.split():
|
|
438
|
+
try:
|
|
439
|
+
pids.append(int(ln))
|
|
440
|
+
except Exception:
|
|
441
|
+
pass
|
|
442
|
+
except Exception:
|
|
443
|
+
pass
|
|
444
|
+
return pids
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _kill_headless_pid(target: str, pid: int) -> bool:
|
|
448
|
+
"""Hard-kill a recorded headless PID + its child tree. Guards against PID reuse by
|
|
449
|
+
confirming the cmdline still looks like this meshcode agent. Returns True if killed."""
|
|
450
|
+
if not pid:
|
|
451
|
+
return False
|
|
452
|
+
cl = _pid_cmdline(pid).lower()
|
|
453
|
+
# Reuse guard: a headless agent's process is either `python -m meshcode run ...`
|
|
454
|
+
# (Windows: parent stays alive) or `claude ...` (POSIX: execvp'd to the editor). If
|
|
455
|
+
# we got a cmdline and it's NEITHER, the PID was reused by something unrelated — skip.
|
|
456
|
+
if cl and ("meshcode" not in cl and "claude" not in cl):
|
|
457
|
+
_log(f"STOP {target}: pid {pid} cmdline no longer matches (reused) — skip kill")
|
|
458
|
+
return False
|
|
459
|
+
try:
|
|
460
|
+
if sys.platform == "win32":
|
|
461
|
+
# /T kills the whole tree (python parent + cmd.exe + claude child), /F forces.
|
|
462
|
+
subprocess.run(["taskkill", "/PID", str(pid), "/T", "/F"],
|
|
463
|
+
capture_output=True, timeout=10)
|
|
464
|
+
else:
|
|
465
|
+
try:
|
|
466
|
+
os.killpg(os.getpgid(pid), _signal.SIGTERM) # start_new_session => own group
|
|
467
|
+
except Exception:
|
|
468
|
+
os.kill(pid, _signal.SIGTERM)
|
|
469
|
+
time.sleep(1.5)
|
|
470
|
+
try:
|
|
471
|
+
os.kill(pid, 0) # still alive?
|
|
472
|
+
os.killpg(os.getpgid(pid), _signal.SIGKILL)
|
|
473
|
+
except ProcessLookupError:
|
|
474
|
+
pass
|
|
475
|
+
except Exception:
|
|
476
|
+
try:
|
|
477
|
+
os.kill(pid, _signal.SIGKILL)
|
|
478
|
+
except Exception:
|
|
479
|
+
pass
|
|
480
|
+
_log(f"STOP {target}: hard-killed headless pid {pid}")
|
|
481
|
+
return True
|
|
482
|
+
except Exception as e:
|
|
483
|
+
_log(f"WARN: STOP {target} kill pid {pid} failed: {e}")
|
|
484
|
+
return False
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _do_stops(api_key: str, host_id: str) -> int:
|
|
488
|
+
"""One stop sweep: hard-kill headless agents the cloud marked stopped but that are
|
|
489
|
+
still alive (cooperative must_exit didn't land). Returns number killed."""
|
|
490
|
+
res = _rpc("mc_agents_to_stop", {"p_api_key": api_key, "p_host_id": host_id})
|
|
491
|
+
if not res or not res.get("ok"):
|
|
492
|
+
return 0
|
|
493
|
+
agents = res.get("agents") or []
|
|
494
|
+
if not agents:
|
|
495
|
+
return 0
|
|
496
|
+
st = _load_state()
|
|
497
|
+
pids = st.get("headless_pids") or {}
|
|
498
|
+
n = 0
|
|
499
|
+
for a in agents:
|
|
500
|
+
proj, agent = a.get("project_name"), a.get("agent")
|
|
501
|
+
if not proj or not agent:
|
|
502
|
+
continue
|
|
503
|
+
target = f"{proj}/{agent}"
|
|
504
|
+
pid = pids.get(target)
|
|
505
|
+
killed = bool(pid) and _kill_headless_pid(target, pid)
|
|
506
|
+
if pid and killed:
|
|
507
|
+
pids.pop(target, None)
|
|
508
|
+
if not killed:
|
|
509
|
+
# No recorded PID (spawned by an older hostd / state lost) OR the recorded
|
|
510
|
+
# PID was stale — discover by command line and kill.
|
|
511
|
+
for dpid in _discover_agent_pids(target):
|
|
512
|
+
if _kill_headless_pid(target, dpid):
|
|
513
|
+
killed = True
|
|
514
|
+
pids.pop(target, None)
|
|
515
|
+
if killed:
|
|
516
|
+
n += 1
|
|
517
|
+
st["headless_pids"] = pids
|
|
518
|
+
_save_state(st)
|
|
519
|
+
return n
|
|
520
|
+
|
|
521
|
+
|
|
372
522
|
def _do_recycles(api_key: str, host_id: str) -> int:
|
|
373
523
|
"""Uptime-based recycle at task boundary. Returns number recycled."""
|
|
374
524
|
cfg = _rpc("mc_host_config_get", {"p_api_key": api_key, "p_host_id": host_id})
|
|
@@ -833,9 +983,10 @@ def cmd_hostd(args: list) -> int:
|
|
|
833
983
|
relaunched = _do_respawns(api_key, host_id)
|
|
834
984
|
recycled = _do_recycles(api_key, host_id)
|
|
835
985
|
ver_recycled = _do_version_recycles(api_key, host_id)
|
|
986
|
+
stopped = _do_stops(api_key, host_id)
|
|
836
987
|
_up = int(time.monotonic() - _spawn_mono)
|
|
837
|
-
if relaunched or recycled or ver_recycled:
|
|
838
|
-
_log(f"sweep done (uptime={_up}s) — {relaunched} respawned, {recycled} recycled, {ver_recycled} version-recycled")
|
|
988
|
+
if relaunched or recycled or ver_recycled or stopped:
|
|
989
|
+
_log(f"sweep done (uptime={_up}s) — {relaunched} respawned, {recycled} recycled, {ver_recycled} version-recycled, {stopped} stopped")
|
|
839
990
|
elif time.monotonic() - _last_alive_log >= 60:
|
|
840
991
|
_log(f"alive — uptime={_up}s")
|
|
841
992
|
_last_alive_log = time.monotonic()
|
|
@@ -117,9 +117,12 @@ def _spawn_terminal_windows(cmd: str) -> tuple[bool, str]:
|
|
|
117
117
|
wt = str(cand)
|
|
118
118
|
if wt:
|
|
119
119
|
try:
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
|
|
120
|
+
# LAUNCH-NO-WINDOW (task 35bee961, commander-corrected): `-w new` forces a
|
|
121
|
+
# BRAND-NEW Windows Terminal window (freshly created => FOREGROUND/focused), which
|
|
122
|
+
# is what Samuel wants ("ventana nueva ENFOCADA"). The old `-w 0 nt` opened a tab in
|
|
123
|
+
# the most-recently-used window — frequently minimized/background, so the launch
|
|
124
|
+
# looked like "nada pasó".
|
|
125
|
+
subprocess.Popen([wt, "-w", "new", "nt", "cmd", "/k", cmd])
|
|
123
126
|
return True, "wt"
|
|
124
127
|
except Exception as e:
|
|
125
128
|
return False, f"wt.exe: {e}"
|