🔔 飞行侠实现:实例注册 + 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 响应
This commit is contained in:
2026-04-04 12:19:43 +08:00
parent 09f2bb9b6c
commit 929459fd33
12 changed files with 574 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
from django.db import models
import uuid
class Instance(models.Model):
"""
OpenClaw 实例注册
每台运行 OpenClaw 的机器注册一个实例,关联多个 Agent
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
instance_id = models.CharField(max_length=100, unique=True, verbose_name='实例 ID')
instance_name = models.CharField(max_length=200, verbose_name='实例名称')
# Agent 列表JSON 存储)
agent_ids = models.JSONField(default=list, verbose_name='Agent ID 列表')
# Webhook 配置
webhook_url = models.URLField(verbose_name='Webhook URL', help_text='消息推送地址')
webhook_enabled = models.BooleanField(default=True, verbose_name='启用 Webhook')
# 状态
is_active = models.BooleanField(default=True, verbose_name='是否活跃')
last_heartbeat = models.DateTimeField(null=True, blank=True, verbose_name='最后心跳')
# 元数据
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'instances'
verbose_name = 'OpenClaw 实例'
verbose_name_plural = 'OpenClaw 实例'
ordering = ['-created_at']
def __str__(self):
return f"{self.instance_name} ({self.instance_id})"
def has_agent(self, agent_id: str) -> bool:
"""检查是否包含某个 Agent"""
return agent_id in self.agent_ids
class MeetingInstanceMap(models.Model):
"""
会议 - 实例映射
记录哪些实例参与了哪些会议
"""
meeting_id = models.UUIDField(verbose_name='会议 ID')
instance = models.ForeignKey(Instance, on_delete=models.CASCADE, related_name='meetings')
agent_ids = models.JSONField(default=list, verbose_name='参与的 Agent ID 列表')
joined_at = models.DateTimeField(auto_now_add=True)
left_at = models.DateTimeField(null=True, blank=True)
class Meta:
db_table = 'meeting_instance_maps'
unique_together = ['meeting_id', 'instance']
indexes = [
models.Index(fields=['meeting_id', 'left_at']),
]
def __str__(self):
return f"Meeting {self.meeting_id} - {self.instance.instance_name}"