Spaces:
Runtime error
Runtime error
| # Mission Control Phase 3: Agent Heartbeat Script | |
| # Called by OpenClaw cron every 15 minutes to wake agents and check for work | |
| # | |
| # Usage: | |
| # scripts/agent-heartbeat.sh [agent_name] | |
| # | |
| # If no agent specified, checks all agents with session keys | |
| set -e | |
| # Configuration | |
| MISSION_CONTROL_URL="${MISSION_CONTROL_URL:-http://localhost:3000}" | |
| LOG_DIR="${LOG_DIR:-$HOME/.mission-control/logs}" | |
| LOG_FILE="$LOG_DIR/agent-heartbeat-$(date +%Y-%m-%d).log" | |
| MAX_CONCURRENT=3 # Max agents to check concurrently | |
| OPENCLAW_CMD="${OPENCLAW_CMD:-openclaw}" | |
| # Ensure log directory exists | |
| mkdir -p "$LOG_DIR" | |
| # Logging function | |
| log() { | |
| local level="$1" | |
| shift | |
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE" | |
| } | |
| # Check if Mission Control is running | |
| check_mission_control() { | |
| if ! curl -s "$MISSION_CONTROL_URL/api/status" > /dev/null 2>&1; then | |
| log "ERROR" "Mission Control not accessible at $MISSION_CONTROL_URL" | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| # Check heartbeat for specific agent | |
| check_agent_heartbeat() { | |
| local agent_name="$1" | |
| local agent_id="$2" | |
| log "INFO" "Checking heartbeat for agent: $agent_name" | |
| # Call heartbeat endpoint | |
| local response | |
| response=$(curl -s -w "HTTP_STATUS:%{http_code}" "$MISSION_CONTROL_URL/api/agents/$agent_id/heartbeat" 2>/dev/null) | |
| local http_code | |
| http_code=$(echo "$response" | grep -o "HTTP_STATUS:[0-9]*" | cut -d: -f2) | |
| local body | |
| body=$(echo "$response" | sed 's/HTTP_STATUS:[0-9]*$//') | |
| if [[ "$http_code" != "200" ]]; then | |
| log "ERROR" "Heartbeat failed for $agent_name: HTTP $http_code" | |
| return 1 | |
| fi | |
| # Parse response | |
| local status | |
| status=$(echo "$body" | jq -r '.status // "unknown"' 2>/dev/null || echo "parse_error") | |
| if [[ "$status" == "HEARTBEAT_OK" ]]; then | |
| log "INFO" "Agent $agent_name: No work items found" | |
| return 0 | |
| elif [[ "$status" == "WORK_ITEMS_FOUND" ]]; then | |
| local total_items | |
| total_items=$(echo "$body" | jq -r '.total_items // 0' 2>/dev/null || echo "0") | |
| log "INFO" "Agent $agent_name: Found $total_items work items" | |
| # If work items found and agent has session key, send wake notification | |
| local session_key | |
| session_key=$(get_agent_session_key "$agent_name") | |
| if [[ -n "$session_key" && "$session_key" != "null" ]]; then | |
| send_wake_notification "$agent_name" "$session_key" "$total_items" "$body" | |
| else | |
| log "WARN" "Agent $agent_name has work items but no session key configured" | |
| fi | |
| return 0 | |
| else | |
| log "ERROR" "Unexpected heartbeat response for $agent_name: $status" | |
| return 1 | |
| fi | |
| } | |
| # Get agent session key from database | |
| get_agent_session_key() { | |
| local agent_name="$1" | |
| # Query agents API to get session key | |
| local agent_data | |
| agent_data=$(curl -s "$MISSION_CONTROL_URL/api/agents?limit=100" 2>/dev/null | jq -r ".agents[] | select(.name == \"$agent_name\") | .session_key" 2>/dev/null || echo "") | |
| echo "$agent_data" | |
| } | |
| # Send wake notification to agent session | |
| send_wake_notification() { | |
| local agent_name="$1" | |
| local session_key="$2" | |
| local work_items_count="$3" | |
| local heartbeat_data="$4" | |
| log "INFO" "Sending wake notification to $agent_name (session: $session_key)" | |
| # Format wake message | |
| local wake_message="🤖 **Mission Control Heartbeat**\n\n" | |
| wake_message+="Agent: $agent_name\n" | |
| wake_message+="Work items found: $work_items_count\n\n" | |
| wake_message+="🔔 You have notifications or tasks that need attention.\n" | |
| wake_message+="Use Mission Control to view details: $MISSION_CONTROL_URL\n\n" | |
| wake_message+="⏰ $(date '+%Y-%m-%d %H:%M:%S')" | |
| # Send via OpenClaw sessions_send | |
| if "$OPENCLAW_CMD" gateway sessions_send --session "$session_key" --message "$wake_message" >> "$LOG_FILE" 2>&1; then | |
| log "INFO" "Wake notification sent successfully to $agent_name" | |
| else | |
| log "ERROR" "Failed to send wake notification to $agent_name" | |
| fi | |
| } | |
| # Get list of agents to check | |
| get_agents_to_check() { | |
| local filter_agent="$1" | |
| if [[ -n "$filter_agent" ]]; then | |
| # Check specific agent | |
| echo "$filter_agent" | |
| return | |
| fi | |
| # Get all agents with session keys | |
| curl -s "$MISSION_CONTROL_URL/api/agents?limit=100" 2>/dev/null | \ | |
| jq -r '.agents[] | select(.session_key != null and .session_key != "") | .name' 2>/dev/null || \ | |
| echo "" | |
| } | |
| # Main execution | |
| main() { | |
| local target_agent="$1" | |
| log "INFO" "Starting agent heartbeat check (PID: $$)" | |
| # Check if Mission Control is running | |
| if ! check_mission_control; then | |
| log "ERROR" "Aborting: Mission Control not accessible" | |
| exit 1 | |
| fi | |
| # Get agents to check | |
| local agents | |
| agents=$(get_agents_to_check "$target_agent") | |
| if [[ -z "$agents" ]]; then | |
| log "WARN" "No agents found with session keys configured" | |
| exit 0 | |
| fi | |
| local total_agents | |
| total_agents=$(echo "$agents" | wc -l) | |
| log "INFO" "Checking heartbeat for $total_agents agent(s)" | |
| # Process agents (limit concurrency) | |
| local processed=0 | |
| local successful=0 | |
| local failed=0 | |
| local pids=() | |
| while IFS= read -r agent_name; do | |
| [[ -z "$agent_name" ]] && continue | |
| # Wait if we've reached max concurrent processes | |
| while [[ ${#pids[@]} -ge $MAX_CONCURRENT ]]; do | |
| for i in "${!pids[@]}"; do | |
| if ! kill -0 "${pids[$i]}" 2>/dev/null; then | |
| unset "pids[$i]" | |
| fi | |
| done | |
| pids=("${pids[@]}") # Reindex array | |
| if [[ ${#pids[@]} -ge $MAX_CONCURRENT ]]; then | |
| sleep 1 | |
| fi | |
| done | |
| # Start heartbeat check in background | |
| ( | |
| if check_agent_heartbeat "$agent_name" "$agent_name"; then | |
| echo "SUCCESS:$agent_name" | |
| else | |
| echo "FAILED:$agent_name" | |
| fi | |
| ) & | |
| pids+=($!) | |
| ((processed++)) | |
| done <<< "$agents" | |
| # Wait for all background processes | |
| for pid in "${pids[@]}"; do | |
| if wait "$pid"; then | |
| ((successful++)) | |
| else | |
| ((failed++)) | |
| fi | |
| done | |
| log "INFO" "Heartbeat check completed: $processed processed, $successful successful, $failed failed" | |
| # Return appropriate exit code | |
| if [[ $failed -gt 0 ]]; then | |
| exit 1 | |
| else | |
| exit 0 | |
| fi | |
| } | |
| # Handle script arguments | |
| case "${1:-}" in | |
| --help|-h) | |
| echo "Mission Control Agent Heartbeat Script" | |
| echo "" | |
| echo "Usage: $0 [agent_name]" | |
| echo "" | |
| echo "Options:" | |
| echo " agent_name Check specific agent only" | |
| echo " --help, -h Show this help message" | |
| echo "" | |
| echo "Environment variables:" | |
| echo " MISSION_CONTROL_URL Mission Control base URL (default: http://localhost:3005)" | |
| echo "" | |
| exit 0 | |
| ;; | |
| *) | |
| main "$@" | |
| ;; | |
| esac | |