Files
flying-hero 929459fd33 🔔 飞行侠实现:实例注册 + Webhook 推送
新功能:
- instances 应用:OpenClaw 实例管理
- Instance 模型:实例注册,Agent 列表,Webhook URL
- MeetingInstanceMap:会议 - 实例映射
- Webhook 推送:消息发送时自动通知相关实例

API 端点:
- POST /api/v1/instances/register/ - 实例注册
- POST /api/v1/instances/join-meeting/ - 加入会议
- GET  /api/v1/instances/ - 实例列表
- POST /api/v1/instances/webhook-test/ - Webhook 测试

集成:
- send_message API 自动触发 Webhook 推送
- 支持广播和定向推送

测试:
- test_webhook.py: 完整测试流程

使用场景:
1. 每台 OpenClaw 机器注册实例
2. Agent 加入会议时关联实例
3. 消息发送时推送到对应机器
4. 本机 OpenClaw 收到通知,触发 Agent 响应
2026-04-04 12:19:43 +08:00

148 lines
4.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Webhook 推送工具
"""
import requests
import logging
from typing import Dict, List, Optional
from .models import Instance, MeetingInstanceMap
logger = logging.getLogger(__name__)
def push_message_to_instances(meeting_id: str, message: Dict, target_agent_ids: Optional[List[str]] = None):
"""
推送消息到相关实例
Args:
meeting_id: 会议 ID
message: 消息数据(字典)
target_agent_ids: 目标 Agent ID 列表None 表示广播给所有)
"""
# 找到参与该会议的实例
maps = MeetingInstanceMap.objects.filter(
meeting_id=meeting_id,
left_at__isnull=True,
instance__is_active=True,
instance__webhook_enabled=True
).select_related('instance')
for mapping in maps:
instance = mapping.instance
# 确定要推送的 Agent
if target_agent_ids:
# 只推送给指定的 Agent
agents_to_notify = [aid for aid in mapping.agent_ids if aid in target_agent_ids]
else:
# 广播给所有
agents_to_notify = mapping.agent_ids
if not agents_to_notify:
continue
# 构建推送数据
payload = {
'event': 'new_message',
'meeting_id': str(meeting_id),
'message': message,
'target_agents': agents_to_notify,
'timestamp': message.get('created_at', '')
}
# 发送 webhook
try:
response = requests.post(
instance.webhook_url,
json=payload,
headers={'Content-Type': 'application/json'},
timeout=5
)
if response.status_code == 200:
logger.info(f"✅ Webhook 推送成功:{instance.instance_id} -> {agents_to_notify}")
else:
logger.warning(f"⚠️ Webhook 推送失败:{instance.instance_id}, status={response.status_code}")
except Exception as e:
logger.error(f"❌ Webhook 推送异常:{instance.instance_id}, error={e}")
def register_instance(instance_id: str, instance_name: str, agent_ids: List[str], webhook_url: str) -> Instance:
"""
注册或更新实例
Args:
instance_id: 实例 ID
instance_name: 实例名称
agent_ids: Agent ID 列表
webhook_url: Webhook URL
Returns:
Instance: 实例对象
"""
instance, created = Instance.objects.update_or_create(
instance_id=instance_id,
defaults={
'instance_name': instance_name,
'agent_ids': agent_ids,
'webhook_url': webhook_url,
'webhook_enabled': True,
'is_active': True
}
)
if created:
logger.info(f"✅ 新实例注册:{instance_id} ({instance_name})")
else:
logger.info(f"🔄 实例更新:{instance_id}")
return instance
def join_meeting(instance_id: str, meeting_id: str, agent_ids: List[str]):
"""
实例加入会议
Args:
instance_id: 实例 ID
meeting_id: 会议 ID
agent_ids: 参与的 Agent ID 列表
"""
try:
instance = Instance.objects.get(instance_id=instance_id)
MeetingInstanceMap.objects.update_or_create(
meeting_id=meeting_id,
instance=instance,
defaults={'agent_ids': agent_ids, 'left_at': None}
)
logger.info(f"✅ 实例 {instance_id} 加入会议 {meeting_id}, Agents: {agent_ids}")
except Instance.DoesNotExist:
logger.error(f"❌ 实例不存在:{instance_id}")
raise
def leave_meeting(instance_id: str, meeting_id: str):
"""
实例离开会议
Args:
instance_id: 实例 ID
meeting_id: 会议 ID
"""
try:
instance = Instance.objects.get(instance_id=instance_id)
mapping = MeetingInstanceMap.objects.get(meeting_id=meeting_id, instance=instance)
mapping.left_at = timezone.now()
mapping.save()
logger.info(f"✅ 实例 {instance_id} 离开会议 {meeting_id}")
except Exception as e:
logger.warning(f"⚠️ 离开会议失败:{e}")
# 导入 timezone
from django.utils import timezone