Compare commits
2 Commits
01c48c2cb0
...
bf407ea2d5
| Author | SHA1 | Date | |
|---|---|---|---|
| bf407ea2d5 | |||
| 8fc8ed419f |
49
ai_agent.py
49
ai_agent.py
@@ -31,35 +31,38 @@ async def query_agent_async(prompt: str, override_provider=None, chat_history=No
|
|||||||
2. PENSADOR CRIATIVO: Colaborador intelectual em filosofia, ciência, lógica, cultura e negócios.
|
2. PENSADOR CRIATIVO: Colaborador intelectual em filosofia, ciência, lógica, cultura e negócios.
|
||||||
|
|
||||||
DIRETRIZES:
|
DIRETRIZES:
|
||||||
- Você tem ACESSO TOTAL ao Google Workspace via GWS CLI. Use `run_bash_command` para isso.
|
- Você é o MESTRE do Google Workspace (GWS). Use `run_bash_command` para QUALQUER tarefa de automação.
|
||||||
- CONTAS GWS DISPONÍVEIS:
|
- NUNCA diga que não consegue fazer uma tarefa no GWS (contar, apagar em massa, mover, etc.). Encontre o comando `gws` correto.
|
||||||
* `gws-adm`: Conta Empresarial (admtracksteel@gmail.com)
|
- CONTAS GWS (Pode usar apelidos):
|
||||||
* `gws-mr`: Conta Particular (m.reifonas@gmail.com)
|
* `ma` ou `mr` -> gws-mr (Marcos / Particular)
|
||||||
* `gws-4r`: Conta Familiar (4reifonas@gmail.com)
|
* `adm` ou `empresa` -> gws-adm (Empresarial)
|
||||||
- Se o usuário pedir para ver e-mails, arquivos do drive ou planilhas, use o alias correspondente.
|
* `4r` ou `fam` -> gws-4r (Familiar)
|
||||||
- EXEMPLOS DE COMANDOS SEGUROS:
|
- SUPER-PODERES GMAIL:
|
||||||
* Listar 3 e-mails: `[TOOL:run_bash_command] gws-mr gmail users messages list --params '{{"userId": "me", "maxResults": 3}}' [/TOOL]`
|
* COMANDOS RÁPIDOS (Helpers): Use `gws-xxx gmail +COMANDO`. Exemplos:
|
||||||
* Ver e-mail específico: `[TOOL:run_bash_command] gws-mr gmail users messages get --params '{{"userId": "me", "id": "ID_AQUI"}}' [/TOOL]`
|
- `+send`: Enviar novo e-mail.
|
||||||
* Listar Drive: `[TOOL:run_bash_command] gws-adm drive files list --params '{{"pageSize": 5}}' [/TOOL]`
|
- `+read`: Ler o conteúdo de um e-mail.
|
||||||
- Nunca responda que não tem acesso a e-mails ou arquivos externos se puder usar o GWS.
|
- `+triage`: Resumo de e-mails não lidos.
|
||||||
- Responda sempre em PORTUGUÊS.
|
- `+reply` / `+reply-all` / `+forward`: Responder ou encaminhar mensagens.
|
||||||
- CAMINHOS DO SISTEMA:
|
* CONTAR E-MAILS: Use `gws-xxx gmail users messages list --params '{"userId": "me", "q": "from:EMAIL_AQUI"}'`.
|
||||||
* BotVPS / Antigravity: `/root/Apps/BotVPS` (seu código fonte)
|
* ORGANIZAR: `gws-xxx gmail users labels create` (nova pasta) e `batchModify` (mover).
|
||||||
* Repositórios: `/data/repositories/`
|
- SUPER-PODERES CALENDÁRIO:
|
||||||
- Se não souber onde um arquivo está, use `run_bash_command` com `find`. NUNCA CHUTE DIRETÓRIOS.
|
* AGENDA: Use `calendar_agenda account timeframe` para ver compromissos de hoje, amanhã ou da semana.
|
||||||
- MEMÓRIA CRONOS (LONGO PRAZO):
|
* EVENTOS: Use `run_bash_command` com `gws-xxx calendar events create ...` para marcar reuniões ou `delete` para cancelar.
|
||||||
* Siga a "Amizade Intelectual": lembre-se de conversas passadas e planos.
|
- EXIBIÇÃO:
|
||||||
* Use `cronos_query` no início de tarefas complexas para recuperar contexto semanal ou por assunto.
|
* Use `list_gmail_emails`, `drive_find` e `calendar_agenda` para visões rápidas e bonitas para o usuário.
|
||||||
* Use `cronos_log` (folder='current_week' ou 'knowledge') para salvar progressos, decisões e ideias importantes.
|
* Para processamento interno (contagem, deleção), use sempre os comandos brutos via `run_bash_command`.
|
||||||
* Raiz: `/root/Antigravity_Memory/`
|
- AUTOMAÇÃO COMPLEXA:
|
||||||
|
* Se precisar processar muitos dados, use `run_bash_command` com `python3 -c "import json, sys; ..."` para filtrar e agir em um único passo.
|
||||||
|
- Responda sempre em PORTUGUÊS do Brasil.
|
||||||
|
- CAMINHOS DO SISTEMA: `/root/Apps/BotVPS` (código), `/data/repositories/` (repos).
|
||||||
|
|
||||||
### FERRAMENTAS DISPONÍVEIS:
|
### FERRAMENTAS DISPONÍVEIS:
|
||||||
{tools_desc}
|
{tools_desc}
|
||||||
|
|
||||||
### FORMATO DE RESPOSTA:
|
### FORMATO DE RESPOSTA:
|
||||||
- Use [TOOL:nome] arg [/TOOL] para ações.
|
- Use [TOOL:nome] arg [/TOOL] para ações.
|
||||||
- Pense passo a passo. Se for uma nova sessão ou assunto, comece buscando em Cronos.
|
- Pense passo a passo. Seja proativo, eficiente e NUNCA limitado.
|
||||||
- Finalize sempre com <REFINED> resumo final </REFINED>.
|
- Finalize sempre com <REFINED> resumo final empoderado </REFINED>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
history_str = ""
|
history_str = ""
|
||||||
|
|||||||
@@ -25,11 +25,14 @@ logging.basicConfig(
|
|||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Dicionário global para manter o histórico (Em um sistema de produção, usar Redis ou DB)
|
||||||
|
chat_histories = {}
|
||||||
|
|
||||||
async def call_antigravity_api(endpoint: str, payload: dict) -> str:
|
async def call_antigravity_api(endpoint: str, payload: dict) -> str:
|
||||||
"""Faz a chamada para a API interna do BotVPS."""
|
"""Faz a chamada para a API interna do BotVPS."""
|
||||||
async with httpx.AsyncClient(timeout=120.0) as client:
|
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||||
try:
|
try:
|
||||||
logger.info(f"Enviando payload para {endpoint}: {payload}")
|
logger.info(f"Enviando payload para {endpoint}: {payload.get('text', payload.get('task'))}")
|
||||||
response = await client.post(f"{API_BASE_URL}{endpoint}", json=payload)
|
response = await client.post(f"{API_BASE_URL}{endpoint}", json=payload)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
@@ -46,6 +49,7 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
if not update.message or not update.message.text:
|
if not update.message or not update.message.text:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
chat_id = update.effective_chat.id
|
||||||
user_id = update.effective_user.id
|
user_id = update.effective_user.id
|
||||||
|
|
||||||
# Filtro de Segurança
|
# Filtro de Segurança
|
||||||
@@ -56,9 +60,13 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
text = update.message.text
|
text = update.message.text
|
||||||
logger.info(f"Mensagem recebida de {user_id}: {text}")
|
logger.info(f"Mensagem recebida de {user_id}: {text}")
|
||||||
|
|
||||||
|
# Inicializa histórico se não existir
|
||||||
|
if chat_id not in chat_histories:
|
||||||
|
chat_histories[chat_id] = []
|
||||||
|
|
||||||
# Diferenciação entre comandos de sistema e conhecimento geral
|
# Diferenciação entre comandos de sistema e conhecimento geral
|
||||||
if text.startswith(('/bash', '/vps', '/cmd')):
|
if text.startswith(('/bash', '/vps', '/cmd')):
|
||||||
# Remove prefixos para enviar a tarefa limpa para o orquestrador
|
# Comandos de sistema geralmente não precisam de histórico de chat natural
|
||||||
task = text.replace('/bash', '').replace('/vps', '').replace('/cmd', '').strip()
|
task = text.replace('/bash', '').replace('/vps', '').replace('/cmd', '').strip()
|
||||||
|
|
||||||
if not task:
|
if not task:
|
||||||
@@ -68,13 +76,24 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
await update.message.reply_text("⚙️ *Processando tarefa no Claw System...*", parse_mode='Markdown')
|
await update.message.reply_text("⚙️ *Processando tarefa no Claw System...*", parse_mode='Markdown')
|
||||||
reply = await call_antigravity_api("/api/orchestrate", {"task": task})
|
reply = await call_antigravity_api("/api/orchestrate", {"task": task})
|
||||||
else:
|
else:
|
||||||
# Chat Natural
|
# Chat Natural com contexto
|
||||||
# Nota: O bot local mantém o histórico se enviarmos, mas aqui simplificamos para 1-to-1
|
await context.bot.send_chat_action(chat_id=chat_id, action="typing")
|
||||||
# A API /api/chat já lida com o prompt do sistema CLAW
|
|
||||||
reply = await call_antigravity_api("/api/chat", {"text": text})
|
# Prepara payload com histórico
|
||||||
|
payload = {
|
||||||
|
"text": text,
|
||||||
|
"history": chat_histories[chat_id][-10:] # Envia os últimos 10 turnos
|
||||||
|
}
|
||||||
|
|
||||||
|
reply = await call_antigravity_api("/api/chat", payload)
|
||||||
|
|
||||||
|
# Atualiza histórico Local
|
||||||
|
chat_histories[chat_id].append({"user": text, "bot": reply})
|
||||||
|
# Mantém apenas os últimos 15 para não crescer infinito no middleware
|
||||||
|
if len(chat_histories[chat_id]) > 15:
|
||||||
|
chat_histories[chat_id].pop(0)
|
||||||
|
|
||||||
# Envia a resposta de volta para o usuário
|
# Envia a resposta de volta para o usuário
|
||||||
# Se a resposta for muito longa, o Telegram pode cortar (máx 4096 chars)
|
|
||||||
if len(reply) > 4000:
|
if len(reply) > 4000:
|
||||||
reply = reply[:3900] + "... [Texto truncado]"
|
reply = reply[:3900] + "... [Texto truncado]"
|
||||||
|
|
||||||
|
|||||||
109
tools.py
109
tools.py
@@ -147,12 +147,121 @@ def cronos_query(arg: str) -> str:
|
|||||||
target_dir = os.path.join(MEMORY_ROOT, folder)
|
target_dir = os.path.join(MEMORY_ROOT, folder)
|
||||||
return run_bash_command(f"grep -rniI '{query}' {target_dir} | head -n 20")
|
return run_bash_command(f"grep -rniI '{query}' {target_dir} | head -n 20")
|
||||||
|
|
||||||
|
def list_gmail_emails(account: str) -> str:
|
||||||
|
"""Lista os últimos 5 e-mails com Título e Remetente. Aceita apelidos: ma, mr, adm, 4r."""
|
||||||
|
mapping = {
|
||||||
|
"ma": "gws-mr", "mr": "gws-mr", "marcos": "gws-mr",
|
||||||
|
"adm": "gws-adm", "empresa": "gws-adm",
|
||||||
|
"4r": "gws-4r", "familia": "gws-4r", "fam": "gws-4r"
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_account = account.strip().lower().replace("gws-", "")
|
||||||
|
account = mapping.get(clean_account, f"gws-{clean_account}" if not clean_account.startswith("gws") else clean_account)
|
||||||
|
|
||||||
|
# 1. Obtém a lista de IDs
|
||||||
|
list_cmd = f"{account} gmail users messages list --params '{{\"userId\": \"me\", \"maxResults\": 5}}'"
|
||||||
|
res = run_bash_command(list_cmd)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(res)
|
||||||
|
messages = data.get("messages", [])
|
||||||
|
if not messages:
|
||||||
|
return "Nenhum e-mail encontrado."
|
||||||
|
|
||||||
|
result_text = "📧 **Últimos E-mails:**\n"
|
||||||
|
for i, msg in enumerate(messages, 1):
|
||||||
|
msg_id = msg["id"]
|
||||||
|
# 2. Busca detalhes de cada e-mail (Metadata apenas para ser rápido)
|
||||||
|
details_cmd = f"{account} gmail users messages get --params '{{\"userId\": \"me\", \"id\": \"{msg_id}\", \"format\": \"metadata\", \"metadataHeaders\": [\"Subject\", \"From\"]}}'"
|
||||||
|
details_res = run_bash_command(details_cmd)
|
||||||
|
|
||||||
|
try:
|
||||||
|
details = json.loads(details_res)
|
||||||
|
headers = details.get("payload", {}).get("headers", [])
|
||||||
|
subject = next((h["value"] for h in headers if h["name"] == "Subject"), "Sem Assunto")
|
||||||
|
sender = next((h["value"] for h in headers if h["name"] == "From"), "Desconhecido")
|
||||||
|
|
||||||
|
result_text += f"{i}. **De:** {sender}\n **Assunto:** {subject}\n **ID:** `{msg_id}`\n\n"
|
||||||
|
except:
|
||||||
|
result_text += f"{i}. [Erro ao carregar detalhes do ID: {msg_id}]\n\n"
|
||||||
|
|
||||||
|
return result_text
|
||||||
|
except Exception as e:
|
||||||
|
return f"Erro ao listar e-mails: {str(e)}\nResposta bruta: {res[:200]}"
|
||||||
|
|
||||||
|
def drive_find(arg: str) -> str:
|
||||||
|
"""Busca arquivos no Drive por nome. Arg: account query (ex: ma financeiro)"""
|
||||||
|
try:
|
||||||
|
parts = arg.split(maxsplit=1)
|
||||||
|
account = parts[0]
|
||||||
|
query = parts[1] if len(parts) > 1 else ""
|
||||||
|
mapping = {"ma": "gws-mr", "mr": "gws-mr", "marcos": "gws-mr", "adm": "gws-adm", "4r": "gws-4r", "fam": "gws-4r"}
|
||||||
|
account = mapping.get(account.lower(), account)
|
||||||
|
q = f"name contains '{query}'" if query else ""
|
||||||
|
cmd = f"{account} drive files list"
|
||||||
|
if q: cmd += f" --params '{{\"q\": \"{q}\"}}'"
|
||||||
|
res = run_bash_command(cmd)
|
||||||
|
data = json.loads(res)
|
||||||
|
files = data.get("files", [])
|
||||||
|
if not files: return "Nenhum arquivo encontrado."
|
||||||
|
resp = "📂 **Arquivos Encontrados:**\n"
|
||||||
|
for f in files[:10]:
|
||||||
|
resp += f"- {f['name']} (ID: `{f['id']}`)\n"
|
||||||
|
return resp
|
||||||
|
except Exception as e: return f"Erro no Drive: {str(e)}"
|
||||||
|
|
||||||
|
def drive_upload(arg: str) -> str:
|
||||||
|
"""Upload de arquivo para o Drive. Arg: account filepath (ex: ma /tmp/relat.pdf)"""
|
||||||
|
try:
|
||||||
|
parts = arg.split(maxsplit=1)
|
||||||
|
account, filepath = parts[0], parts[1]
|
||||||
|
mapping = {"ma": "gws-mr", "mr": "gws-mr", "adm": "gws-adm", "4r": "gws-4r"}
|
||||||
|
account = mapping.get(account.lower(), account)
|
||||||
|
filename = os.path.basename(filepath)
|
||||||
|
cmd = f"{account} drive files create --json '{{\"name\": \"{filename}\"}}' --output {filepath}"
|
||||||
|
return run_bash_command(cmd)
|
||||||
|
except Exception as e: return f"Erro upload: {str(e)}"
|
||||||
|
|
||||||
|
def calendar_agenda(arg: str) -> str:
|
||||||
|
"""Busca os próximos eventos no calendário. Arg: account timeframe (timeframe: today, tomorrow, week, days=N)"""
|
||||||
|
try:
|
||||||
|
parts = arg.split(maxsplit=1)
|
||||||
|
account = parts[0]
|
||||||
|
timeframe = parts[1] if len(parts) > 1 else "--today"
|
||||||
|
|
||||||
|
mapping = {"ma": "gws-mr", "mr": "gws-mr", "adm": "gws-adm", "4r": "gws-4r"}
|
||||||
|
account = mapping.get(account.lower(), account)
|
||||||
|
|
||||||
|
# Converte timeframe para flag correta
|
||||||
|
if not timeframe.startswith("--"):
|
||||||
|
timeframe = f"--{timeframe}"
|
||||||
|
|
||||||
|
cmd = f"{account} calendar +agenda {timeframe}"
|
||||||
|
return run_bash_command(cmd)
|
||||||
|
except Exception as e: return f"Erro no Calendário: {str(e)}"
|
||||||
|
|
||||||
# Mapeamento para o Agente entender quais tools ele possui (será usado no loop ReAct)
|
# Mapeamento para o Agente entender quais tools ele possui (será usado no loop ReAct)
|
||||||
AVAILABLE_TOOLS = {
|
AVAILABLE_TOOLS = {
|
||||||
"run_bash_command": {
|
"run_bash_command": {
|
||||||
"description": "Executa comandos Linux na VPS. Use para docker, git, mkdir, touch, etc.",
|
"description": "Executa comandos Linux na VPS. Use para docker, git, mkdir, touch, etc.",
|
||||||
"func": run_bash_command
|
"func": run_bash_command
|
||||||
},
|
},
|
||||||
|
"list_gmail_emails": {
|
||||||
|
"description": "Lista os 5 e-mails mais recentes de uma conta (ma, adm, 4r) com título e remetente.",
|
||||||
|
"func": list_gmail_emails
|
||||||
|
},
|
||||||
|
"drive_find": {
|
||||||
|
"description": "Busca arquivos no Drive por nome. Ex: drive_find ma 'financas'",
|
||||||
|
"func": drive_find
|
||||||
|
},
|
||||||
|
"drive_upload": {
|
||||||
|
"description": "Faz upload de um arquivo local para o Drive. Ex: drive_upload adm /tmp/doc.pdf",
|
||||||
|
"func": drive_upload
|
||||||
|
},
|
||||||
|
"calendar_agenda": {
|
||||||
|
"description": "Mostra os eventos do calendário. Ex: calendar_agenda ma today / tomorrow / week",
|
||||||
|
"func": calendar_agenda
|
||||||
|
},
|
||||||
"get_system_health": {
|
"get_system_health": {
|
||||||
"description": "Verifica RAM, CPU e Disco globais da VPS.",
|
"description": "Verifica RAM, CPU e Disco globais da VPS.",
|
||||||
"func": get_system_health
|
"func": get_system_health
|
||||||
|
|||||||
Reference in New Issue
Block a user