""" API views for Agent Diary monitoring. """ from rest_framework.decorators import api_view from rest_framework.response import Response from datetime import datetime from agents.models import Agent, AgentDiary import docker @api_view(['GET']) def agent_list(request): """获取所有 Agent 状态""" agents = Agent.objects.all() result = [] for agent in agents: result.append({ 'id': agent.id, 'name': agent.name, 'emoji': agent.emoji, 'port': agent.port, 'specialty': agent.specialty, 'container': agent.container, 'app_name': agent.app_name, 'app_id': agent.app_id, 'status': 'healthy', 'last_check': datetime.now().isoformat() }) return Response(result) @api_view(['GET']) def agent_detail(request, agent_id): """获取单个 Agent 详情""" try: agent = Agent.objects.get(id=agent_id) return Response({ 'id': agent.id, 'name': agent.name, 'emoji': agent.emoji, 'port': agent.port, 'specialty': agent.specialty, 'container': agent.container, 'app_name': agent.app_name, 'app_id': agent.app_id, 'status': 'healthy', 'workspace': f'/home/node/.openclaw/workspace/{agent.workspace}', 'last_check': datetime.now().isoformat() }) except Agent.DoesNotExist: return Response({'error': 'Agent 不存在'}, status=404) @api_view(['GET']) def tools_list(request): """获取工具列表""" tools = [ { 'name': 'Git 版本控制', 'status': 'running', 'description': '代码版本管理服务', 'url': 'https://xjp.datalibstar.com/flying-hero/openclaw-monitor.git' } ] return Response(tools) @api_view(['GET']) def agent_memory_dates(request, agent_id): """获取 Agent 有工作记忆的日期列表 - 从数据库读取""" try: agent = Agent.objects.get(id=agent_id) except Agent.DoesNotExist: return Response({'error': 'Agent 不存在'}, status=404) # 从数据库查询工作记忆 diaries = AgentDiary.objects.filter( agent=agent, category='memory' ).values_list('date', flat=True).distinct().order_by('-date') dates = [str(date) for date in sorted(diaries, reverse=True)] return Response({'dates': dates}) @api_view(['GET']) def agent_memory_detail(request, agent_id, date): """获取指定日期的工作记忆内容 - 从数据库读取""" try: agent = Agent.objects.get(id=agent_id) except Agent.DoesNotExist: return Response({'error': 'Agent 不存在'}, status=404) # 从数据库查询工作记忆 try: diary = AgentDiary.objects.get( agent=agent, date=date, category='memory' ) return Response({ 'date': str(diary.date), 'content': diary.content, 'title': diary.title, 'tags': diary.tags, }) except AgentDiary.DoesNotExist: return Response({'error': '该日期没有工作记忆'}, status=404) @api_view(['GET']) def agent_diary_dates(request, agent_id): """获取 Agent 有日记(成长之路)的日期列表 - 从数据库读取""" try: agent = Agent.objects.get(id=agent_id) except Agent.DoesNotExist: return Response({'error': 'Agent 不存在'}, status=404) # 从数据库查询日记日期 diaries = AgentDiary.objects.filter( agent=agent, category='chengcai' ).values_list('date', flat=True).distinct().order_by('-date') dates = [str(date) for date in sorted(diaries, reverse=True)] return Response({'dates': dates}) @api_view(['GET']) def agent_diary_detail(request, agent_id, date): """获取指定日期的日记内容(成长之路) - 从数据库读取""" try: agent = Agent.objects.get(id=agent_id) except Agent.DoesNotExist: return Response({'error': 'Agent 不存在'}, status=404) # 从数据库查询日记 try: diary = AgentDiary.objects.get( agent=agent, date=date, category='chengcai' ) return Response({ 'date': str(diary.date), 'content': diary.content, 'title': diary.title, 'tags': diary.tags, }) except AgentDiary.DoesNotExist: return Response({'error': '该日期没有日记'}, status=404) @api_view(['POST']) def scan_agents(request): """自动扫描本机 Agent 实例(Docker 容器 + 宿主机进程)""" import subprocess import re scanned = [] created_count = 0 updated_count = 0 # 专长和 Emoji 映射 specialty_map = { 'instance2': '主力/通用', 'daotong': '道德经注解', 'coder': '代码专家', 'web': '网站制作', 'physics': '物理研究', 'secretary': '秘书/助理', 'ditin': '情报/监听', 'watcher': '舰队监控', } emoji_map = { 'instance2': '🦸', 'daotong': '☯️', 'coder': '🔧', 'web': '🕸️', 'physics': '⚛️', 'secretary': '🦄', 'ditin': '👂', 'watcher': '👁️', } def get_agent_info(name): """根据名称推断专长和 Emoji""" specialty = '通用' emoji = '🤖' for key, value in specialty_map.items(): if key in name.lower(): specialty = value break for key, value in emoji_map.items(): if key in name.lower(): emoji = value break return specialty, emoji # 1. 扫描 Docker 容器 try: client = docker.from_env() containers = client.containers.list() for container in containers: container_name = container.name # 只处理 openclaw 相关的容器 if 'openclaw' not in container_name.lower(): continue # 跳过数据库、网关、监控等辅助容器 if any(skip in container_name.lower() for skip in ['postgres', 'db', 'redis', 'gateway', 'watcher', 'monitor']): continue # 提取端口 ports = container.ports port = None if '18789/tcp' in ports and ports['18789/tcp']: port = ports['18789/tcp'][0]['HostPort'] # 推断名称(处理复杂的容器名) name = container_name name = name.replace('openclaw-', '').replace('openclaw_', '') name = name.replace('-openclaw-cn-gateway-1', '').replace('_openclaw_cn_gateway_1', '') name = name.replace('-gateway-1', '').replace('_gateway_1', '') name = name.split('-')[0].split('_')[0].capitalize() specialty, emoji = get_agent_info(container_name) # 创建或更新 Agent agent, created = Agent.objects.update_or_create( container=container_name, defaults={ 'name': name, 'emoji': emoji, 'port': port or 0, 'specialty': specialty, 'workspace': name.lower(), } ) scanned.append({ 'id': agent.id, 'name': agent.name, 'emoji': agent.emoji, 'container': container_name, 'port': port, 'type': 'docker', 'created': created, }) if created: created_count += 1 else: updated_count += 1 except docker.errors.DockerException: pass # Docker 不可用时跳过 except Exception as e: print(f'Docker 扫描失败:{e}') # 2. 扫描宿主机进程 try: # 使用 ps 命令查找 openclaw 相关进程 result = subprocess.run( ['ps', 'aux'], capture_output=True, text=True, timeout=5 ) for line in result.stdout.split('\n'): if 'openclaw' not in line.lower() or 'grep' in line.lower(): continue # 提取进程信息 parts = line.split() if len(parts) < 11: continue pid = parts[1] # 从命令行提取容器名或实例名 cmd = ' '.join(parts[10:]) # 尝试从命令行提取名称 container_name = None port = None # 查找端口(假设端口格式为 --port 18789 或 -p 18789) port_match = re.search(r'(?:--port|-p)[=\s]+(\d+)', cmd) if port_match: port = port_match.group(1) # 尝试从路径或参数提取名称 name_match = re.search(r'openclaw[-_]([\w-]+)', cmd) if name_match: container_name = f'openclaw-{name_match.group(1)}' # 如果没有找到名称,使用 PID if not container_name: container_name = f'openclaw-process-{pid}' # 推断名称和专长 name = container_name.replace('openclaw-', '').replace('openclaw_', '').capitalize() specialty, emoji = get_agent_info(container_name) # 只添加有端口的进程(过滤掉石头) if port and int(port) > 0: # 创建或更新 Agent agent, created = Agent.objects.update_or_create( container=container_name, defaults={ 'name': name, 'emoji': emoji, 'port': int(port), 'specialty': specialty, 'workspace': name.lower(), } ) scanned.append({ 'id': agent.id, 'name': agent.name, 'emoji': agent.emoji, 'container': container_name, 'port': port, 'type': 'process', 'created': created, }) if created: created_count += 1 else: updated_count += 1 except Exception as e: print(f'进程扫描失败:{e}') return Response({ 'success': True, 'scanned': scanned, 'docker_count': len([s for s in scanned if s.get('type') == 'docker']), 'process_count': len([s for s in scanned if s.get('type') == 'process']), 'created': created_count, 'updated': updated_count, 'total': len(scanned), }) @api_view(['POST']) def sync_agent(request): """同步 Agent 到数据库(放虾归海)""" try: data = request.data agent_id = data.get('agent_id') action = data.get('action') if not agent_id: return Response({ 'success': False, 'error': '缺少 agent_id', }, status=400) # 获取 Agent try: agent = Agent.objects.get(id=agent_id) except Agent.DoesNotExist: return Response({ 'success': False, 'error': 'Agent 不存在', }, status=404) # 放虾归海 - 更新状态为已同步 if action == 'add_to_pool': # 这里可以添加额外的同步逻辑 # 比如标记为已激活、添加到特定分组等 pass return Response({ 'success': True, 'message': f'{agent.name} 已放虾归海!', 'agent': { 'id': agent.id, 'name': agent.name, 'emoji': agent.emoji, }, }) except Exception as e: return Response({ 'success': False, 'error': f'同步失败:{str(e)}', }, status=500)