feat: update templates for executor tools, situational awareness and listener execution logic
This commit is contained in:
+115
-71
@@ -1,107 +1,151 @@
|
|||||||
import os
|
import os, json, redis, subprocess, base64
|
||||||
import json
|
from urllib import request, parse
|
||||||
import redis
|
|
||||||
import argparse
|
|
||||||
import subprocess
|
|
||||||
from anthropic import Anthropic
|
from anthropic import Anthropic
|
||||||
from key_manager import KeyManager
|
|
||||||
|
|
||||||
# Configurações de Caminho
|
|
||||||
BASE_DIR = "/home/deploy/projetos"
|
|
||||||
INSTRUCOES_DIR = "/home/deploy/instrucoes"
|
|
||||||
|
|
||||||
class SwarmAgent:
|
class SwarmAgent:
|
||||||
def __init__(self, task_data):
|
def __init__(self, task_data):
|
||||||
self.data = task_data
|
self.data = task_data
|
||||||
self.repo_name = task_data.get('repository')
|
self.repo_name = task_data.get('repository')
|
||||||
self.repo_url = f"http://10.99.0.3:3000/{self.repo_name}.git"
|
self.repo_url = f"http://10.99.0.3:3000/{self.repo_name}.git"
|
||||||
self.local_path = os.path.join(BASE_DIR, self.repo_name.split('/')[-1])
|
self.local_path = os.path.join("/home/deploy/projetos", self.repo_name.split('/')[-1])
|
||||||
self.km = KeyManager()
|
|
||||||
|
# Conexão Redis para pegar chaves
|
||||||
|
self.r = redis.Redis(host='10.99.0.3', port=6379, password='clube67_dragonfly_pass_9903', db=0, decode_responses=True)
|
||||||
|
|
||||||
|
def get_api_key(self):
|
||||||
|
pool = json.loads(self.r.get('swarm:keys:pool'))
|
||||||
|
return random.choice([k['key'] for k in pool['anthropic'] if k['status'] == 'active'])
|
||||||
|
|
||||||
def setup_workspace(self):
|
def setup_workspace(self):
|
||||||
"""Garante que o código e as instruções estão atualizados"""
|
os.makedirs("/home/deploy/projetos", exist_ok=True)
|
||||||
print(f" [>] Sincronizando repositório: {self.repo_name}")
|
|
||||||
os.makedirs(BASE_DIR, exist_ok=True)
|
|
||||||
|
|
||||||
if not os.path.exists(self.local_path):
|
if not os.path.exists(self.local_path):
|
||||||
subprocess.run(["git", "clone", self.repo_url, self.local_path])
|
subprocess.run(["git", "clone", self.repo_url, self.local_path])
|
||||||
else:
|
else:
|
||||||
subprocess.run(["git", "-C", self.local_path, "pull"])
|
subprocess.run(["git", "-C", self.local_path, "pull"])
|
||||||
|
|
||||||
# Sincroniza instruções globais
|
def run_bash(self, command):
|
||||||
subprocess.run(["git", "-C", INSTRUCOES_DIR, "pull"])
|
"""Ferramenta para a IA rodar comandos na VPS 4"""
|
||||||
|
result = subprocess.run(command, shell=True, capture_output=True, text=True, cwd=self.local_path)
|
||||||
def get_system_prompt(self):
|
return f"STDOUT: {result.stdout}\nSTDERR: {result.stderr}"
|
||||||
"""Carrega a doutrina e as instruções de cada VPS"""
|
|
||||||
with open(f"{INSTRUCOES_DIR}/global/doutrina_agentes.md", "r") as f:
|
|
||||||
doutrina = f.read()
|
|
||||||
with open(f"{INSTRUCOES_DIR}/global/claude.md", "r") as f:
|
|
||||||
especifico = f.read()
|
|
||||||
|
|
||||||
ip_vps = subprocess.getoutput("hostname -I").split()[0]
|
|
||||||
|
|
||||||
return f"""
|
|
||||||
{doutrina}
|
|
||||||
{especifico}
|
|
||||||
|
|
||||||
CONTEXTO ATUAL:
|
|
||||||
- Você está na VPS com IP: {ip_vps}
|
|
||||||
- Projeto: {self.repo_name}
|
|
||||||
- Tarefa (Issue): {json.dumps(self.data.get('data', {}).get('issue', {}))}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def post_comment(self, message):
|
def post_comment(self, message):
|
||||||
"""Posta um comentário de feedback na Issue do Gitea usando urllib (zero-dependency)"""
|
|
||||||
issue_id = self.data.get('data', {}).get('issue', {}).get('number')
|
issue_id = self.data.get('data', {}).get('issue', {}).get('number')
|
||||||
if not issue_id:
|
if not issue_id: return
|
||||||
return
|
|
||||||
|
|
||||||
url = f"http://10.99.0.3:3000/api/v1/repos/{self.repo_name}/issues/{issue_id}/comments"
|
url = f"http://10.99.0.3:3000/api/v1/repos/{self.repo_name}/issues/{issue_id}/comments"
|
||||||
|
|
||||||
import base64
|
|
||||||
from urllib import request, parse
|
|
||||||
|
|
||||||
# Auth Basic
|
|
||||||
auth_str = 'ruicesar:h$tg@g5aga$ra1E3$C-yHW$-BA@DF2@Grfa!3#'
|
auth_str = 'ruicesar:h$tg@g5aga$ra1E3$C-yHW$-BA@DF2@Grfa!3#'
|
||||||
auth_bytes = auth_str.encode('ascii')
|
base64_auth = base64.b64encode(auth_str.encode('ascii')).decode('ascii')
|
||||||
base64_auth = base64.b64encode(auth_bytes).decode('ascii')
|
req = request.Request(url, data=json.dumps({"body": message}).encode('utf-8'), method='POST')
|
||||||
|
|
||||||
data = json.dumps({"body": message}).encode('utf-8')
|
|
||||||
req = request.Request(url, data=data, method='POST')
|
|
||||||
req.add_header('Content-Type', 'application/json')
|
req.add_header('Content-Type', 'application/json')
|
||||||
req.add_header('Authorization', f'Basic {base64_auth}')
|
req.add_header('Authorization', f'Basic {base64_auth}')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with request.urlopen(req) as response:
|
with request.urlopen(req) as res: pass
|
||||||
if response.status == 201:
|
except: pass
|
||||||
print(f" [✓] Comentário postado na Issue #{issue_id}")
|
|
||||||
else:
|
def run_command(self, command):
|
||||||
print(f" [X] Erro ao postar comentário: {response.status}")
|
"""Executa comandos shell na VPS 4"""
|
||||||
except Exception as e:
|
print(f" [Exec] Rodando: {command}")
|
||||||
print(f" [E] Falha na comunicação com Gitea via urllib: {e}")
|
result = subprocess.run(command, shell=True, capture_output=True, text=True, cwd=self.local_path)
|
||||||
|
return {"stdout": result.stdout, "stderr": result.stderr}
|
||||||
|
|
||||||
|
def write_file(self, path, content):
|
||||||
|
"""Escreve/Edita arquivos no projeto"""
|
||||||
|
full_path = os.path.join(self.local_path, path)
|
||||||
|
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
||||||
|
with open(full_path, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
return f"Arquivo {path} escrito com sucesso."
|
||||||
|
|
||||||
|
def read_file(self, path):
|
||||||
|
"""Lê arquivos do projeto"""
|
||||||
|
full_path = os.path.join(self.local_path, path)
|
||||||
|
if os.path.exists(full_path):
|
||||||
|
with open(full_path, "r") as f:
|
||||||
|
return f.read()
|
||||||
|
return "Erro: Arquivo não encontrado."
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
self.setup_workspace()
|
self.setup_workspace()
|
||||||
|
|
||||||
api_key = self.km.get_key('anthropic')
|
# Identificação de Localização Automática
|
||||||
if not api_key:
|
vps_ip = subprocess.getoutput("hostname -I").split()[0]
|
||||||
return
|
vps_name = subprocess.getoutput("hostname")
|
||||||
|
|
||||||
|
with open("/home/deploy/instrucoes/global/doutrina_agentes.md", "r") as f: doutrina = f.read()
|
||||||
|
|
||||||
|
# Consciência Situacional Injetada
|
||||||
|
situational_awareness = f"\n\n[CONSCIÊNCIA SITUACIONAL]: Você está rodando na VPS: {vps_name} (IP: {vps_ip})\n"
|
||||||
|
|
||||||
|
api_key = self.get_api_key()
|
||||||
client = Anthropic(api_key=api_key)
|
client = Anthropic(api_key=api_key)
|
||||||
prompt = self.get_system_prompt()
|
|
||||||
|
|
||||||
print(" [!] IA Pensando na solução...")
|
print(f" [!] Agente iniciando na {vps_name} ({vps_ip})...")
|
||||||
|
|
||||||
# Simulação de resposta da IA para o teste
|
task = self.data.get('data', {}).get('issue', {}).get('body', 'Sem descrição')
|
||||||
feedback = "### 🤖 [VPS 4 - LABORATÓRIO]:\n* **Status**: Tarefa recebida e ambiente preparado.\n* **Ação**: Sincronização de arquivos concluída.\n* **Próximo**: Iniciando correção do erro de regressão de versão."
|
|
||||||
|
|
||||||
self.post_comment(feedback)
|
# O Agente agora tem um loop de pensamento e ferramentas
|
||||||
|
# Para este MVP, vamos permitir que ele faça uma análise e execute uma rodada de ferramentas
|
||||||
|
tools = [
|
||||||
|
{
|
||||||
|
"name": "read_file",
|
||||||
|
"description": "Lê o conteúdo de um arquivo do projeto",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {"path": {"type": "string"}},
|
||||||
|
"required": ["path"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "write_file",
|
||||||
|
"description": "Escreve ou edita um arquivo no projeto",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"path": {"type": "string"},
|
||||||
|
"content": {"type": "string"}
|
||||||
|
},
|
||||||
|
"required": ["path", "content"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "run_command",
|
||||||
|
"description": "Executa um comando shell (ex: docker compose up)",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {"command": {"type": "string"}},
|
||||||
|
"required": ["command"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
response = client.messages.create(
|
||||||
|
model="claude-3-5-sonnet-20240620",
|
||||||
|
max_tokens=4000,
|
||||||
|
system=doutrina + situational_awareness,
|
||||||
|
tools=tools,
|
||||||
|
messages=[{"role": "user", "content": f"Tarefa: {task}\nAnalise o diretório {self.local_path} e resolva o problema."}]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Processamento simples de ferramentas (para este estágio)
|
||||||
|
final_feedback = f"### 🤖 [VPS 4 - EXECUTOR]:\n\n{response.content[0].text}\n\n"
|
||||||
|
|
||||||
|
for content in response.content:
|
||||||
|
if content.type == "tool_use":
|
||||||
|
if content.name == "read_file":
|
||||||
|
res = self.read_file(content.input["path"])
|
||||||
|
final_feedback += f"* **Lendo**: {content.input['path']}\n"
|
||||||
|
elif content.name == "write_file":
|
||||||
|
res = self.write_file(content.input["path"], content.input["content"])
|
||||||
|
final_feedback += f"* **Escrevendo**: {content.input['path']}\n"
|
||||||
|
elif content.name == "run_command":
|
||||||
|
res = self.run_command(content.input["command"])
|
||||||
|
final_feedback += f"* **Rodando**: `{content.input['command']}`\n"
|
||||||
|
|
||||||
|
self.post_comment(final_feedback)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
import argparse, random
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--data", help="Dados do Webhook JSON")
|
parser.add_argument("--data")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.data:
|
if args.data:
|
||||||
agent = SwarmAgent(json.loads(args.data))
|
SwarmAgent(json.loads(args.data)).execute()
|
||||||
agent.execute()
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASS, db=0)
|
|||||||
pubsub = r.pubsub()
|
pubsub = r.pubsub()
|
||||||
pubsub.subscribe(*CHANNELS)
|
pubsub.subscribe(*CHANNELS)
|
||||||
|
|
||||||
print(f" [*] Aguardando eventos nos canais {CHANNELS}...")
|
print(f" [*] Swarm Listener Ativo - Aguardando eventos nos canais {CHANNELS}...")
|
||||||
|
|
||||||
for message in pubsub.listen():
|
for message in pubsub.listen():
|
||||||
if message['type'] == 'message':
|
if message['type'] == 'message':
|
||||||
@@ -21,13 +21,18 @@ for message in pubsub.listen():
|
|||||||
payload = json.loads(message['data'])
|
payload = json.loads(message['data'])
|
||||||
event = payload.get('event')
|
event = payload.get('event')
|
||||||
repo = payload.get('repository')
|
repo = payload.get('repository')
|
||||||
|
channel = message['channel'].decode('utf-8') if isinstance(message['channel'], bytes) else message['channel']
|
||||||
|
|
||||||
print(f" [!] Novo evento recebido: {event} no repo {repo}")
|
print(f" [!] Novo evento recebido no canal {channel}: {event} no repo {repo}")
|
||||||
|
|
||||||
# Aqui entrará a lógica de disparar o agente de IA específico
|
# Se for uma tarefa de desenvolvimento (tasks), dispara o executor inteligente
|
||||||
# Por enquanto, apenas registramos o recebimento.
|
if channel == 'swarm:tasks':
|
||||||
# Exemplo de comando que seria rodado:
|
executor_path = "/home/deploy/instrucoes/templates/agent_executor.py"
|
||||||
# subprocess.run(["python3", "agent_executor.py", "--event", event, "--data", json.dumps(payload)])
|
if os.path.exists(executor_path):
|
||||||
|
print(f" [>] Disparando executor inteligente: {executor_path}")
|
||||||
|
subprocess.run(["python3", executor_path, "--data", json.dumps(payload)])
|
||||||
|
else:
|
||||||
|
print(f" [E] Executor não encontrado em {executor_path}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" [E] Erro ao processar mensagem: {e}")
|
print(f" [E] Erro ao processar mensagem: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user