From ee4c80b4caf166b1ae645d3daa70a026c85b5b5a Mon Sep 17 00:00:00 2001 From: Ualerson Date: Sat, 18 Oct 2025 12:17:43 -0300 Subject: [PATCH 1/4] Refatora backend: adiciona logs/tarefas, API aprimorada e autolist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O que foi alterado (alto nível): - Novos modelos de banco de dados: TaskLog (logs de tarefas) e ApprovedProject (aprovações/ pagamentos). BackendTask ganha novos campos para suportar informações ricas de tarefa (title, description, category, price, technologies, deadline, early_bonus, estimated_hours, progress, allocated_agent e project_files em JSON), além de updated_at. Esses ajustes permitem rastreabilidade, auditoria e fluxo de aprovação. - API ampliada e reestruturada: criação de tarefas, listagem com filtros avançados, detalhes de tarefa com logs e arquivos de projeto, e novos endpoints para aprovar projetos e listar aprovações. A lógica de autenticação e tratamento de erros foi fortalecida. A listagem agora retorna dados enriquecidos (título, descrição, preço, tecnologias, deadline, etc.) com paginação; detalhes incluem logs históricos e estrutura de arquivos do projeto. - CodeBackEndAgent e integrações MCP: o CodeBackEndAgent passou a trabalhar com novas entradas de projeto (title, description, category, price, technologies, deadline, early_bonus) e habilita pensamento sequencial via MCP (sequential_thinking) e operações Git simuladas (git). Esses ajustes facilitam tarefas mais autônomas e rastreáveis. - Autolist local aprimorado: autolistlocalproject agora aceita show_contents (boolean) para controlar se apenas caminhos são retornados ou se conteúdos dos arquivos também são incluídos, reduzindo o tamanho de respostas quando necessário. - Docker e infraestrutura: Dockerfile atualizado para instalar Docker CLI, docker-compose plugin e Node.js, ampliando suporte a build/deploys locais e pipelines. - Backend/Celery: ajustes nos workers para passar novos argumentos das tarefas (inclui conteúdo do usuário) e manter consistência com o novo modelo de dados. - Limpeza/ajustes adicionais: remoção de um teste antigo, pequenas melhorias de formatação e compatibilidade entre módulos."} } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } }} --- backend/Agents/AppAI/CodeBackend/ai.py | 168 +++++++--- backend/Dockerfile | 22 ++ backend/Functions/Think/Think.py | 0 .../autocreaterepo/autocreaterepo.py | 45 +++ .../autolistlocalproject.py | 17 +- backend/Functions/gitcommit/gitcommit.py | 0 backend/Models/postgreSQL.py | 44 ++- backend/Modules/Savers/create_task.py | 9 +- backend/TestDiscovery/teste_mcp.py | 33 -- backend/_task_allocate_logs.py | 33 ++ backend/_task_allocate_task.py | 38 +++ backend/api.py | 316 +++++++++++++++--- backend/celery_app.py | 7 +- backend/celerybeat-schedule | Bin 3072 -> 3072 bytes backend/run_codeagent.py | 50 +++ backend/teste_mcp.py | 64 ++++ build_in_windows.py | 2 +- frontend/devflow-co | 1 + 18 files changed, 711 insertions(+), 138 deletions(-) create mode 100644 backend/Functions/Think/Think.py create mode 100644 backend/Functions/autocreaterepo/autocreaterepo.py create mode 100644 backend/Functions/gitcommit/gitcommit.py delete mode 100644 backend/TestDiscovery/teste_mcp.py create mode 100644 backend/_task_allocate_logs.py create mode 100644 backend/_task_allocate_task.py create mode 100644 backend/run_codeagent.py create mode 100644 backend/teste_mcp.py create mode 160000 frontend/devflow-co diff --git a/backend/Agents/AppAI/CodeBackend/ai.py b/backend/Agents/AppAI/CodeBackend/ai.py index b3568862d..c09e345e4 100644 --- a/backend/Agents/AppAI/CodeBackend/ai.py +++ b/backend/Agents/AppAI/CodeBackend/ai.py @@ -13,6 +13,11 @@ from dotenv import load_dotenv from openai import OpenAI +from typing_extensions import TypedDict, Any +from agents import Agent, ModelSettings, function_tool, FileSearchTool, WebSearchTool, Runner +from datetime import datetime +import requests +from agents.mcp import MCPServerStdio from Functions.autosave.autosave import autosave from Functions.autolistlocalproject.autolistlocalproject import autolistlocalproject @@ -23,20 +28,68 @@ logger = logging.getLogger("SprintsPlanner_logger") class CodeBackEndAgentOutput(BaseModel): - saved_files: List[str] - + saved_files: List[str] + +class ReflectionAgentAgentOutput(BaseModel): + should_retry: bool + suggested_changes: List[str] + confidence: float + +class ReflectionData(TypedDict): + task: str + last_output: str + history: List[str] + +async def _sequential_thinking(): + async with MCPServerStdio( + name="sequential-thinking", + params={ + "command": "docker", + "args": [ + "run", + "--rm", + "-i", + "mcp/sequentialthinking" + ], + }, + client_session_timeout_seconds=45 + ) as sequential_thinking: + return sequential_thinking + +async def _git_server(src): + async with MCPServerStdio( + name="git", + params={ + "command": "docker", + "args": ["run", + "--rm", + "-i", + "--mount", + f"type=bind,src={src},dst={src}", + "mcp/git" + ], + }, + client_session_timeout_seconds=45 + ) as git_: + return git_ async def CodeBackEndAgent( - OPENAI_API_KEY, - user_id, - tipo_app, - descricao, - user_content, - commit_language = 'pt', - model = "gpt-5-nano", - local_to_save = "./", - - ): + OPENAI_API_KEY, + user_id, + tipo_app, + title, + description, + category, + price, + technologies, + early_bonus, + deadline, + commit_language = 'pt', + model = "gpt-5-nano", + local_to_save = "./", + think=True, + self_reflection=True + ): os.environ['OPENAI_API_KEY'] = OPENAI_API_KEY os.makedirs(os.path.join(os.path.dirname(__file__), 'Sessions'), exist_ok=True) logger.info(f"CodeBackEndAgent Agent") @@ -56,49 +109,31 @@ async def CodeBackEndAgent( elif commit_language == 'pt': prompt_system_direct = f""" # IDENTIDADE E CONTEXTO -Você é um desenvolvedor backend sênior especializado em Python/Flask, responsável por implementar tarefas de backend de forma autônoma e eficiente. +Você é um desenvolvedor backend sênior especializado em Python/Flask, +responsável por implementar tarefas de backend de forma autônoma e eficiente. -## INFORMAÇÕES DO PROJETO -- **Tipo**: {tipo_app} -- **Descrição**: {descricao} -- **Diretório Base**: {local_to_save} -- **Stack Tecnológica**: Flask (blueprints), SQLAlchemy (PostgreSQL), MongoDB (logs), Celery+Redis, Pydantic Settings ---- # FLUXO DE TRABALHO OBRIGATÓRIO ## 1️⃣ ANÁLISE INICIAL (SEMPRE EXECUTAR PRIMEIRO) Antes de qualquer implementação, você DEVE: -### A) Listar Estado Atual do Projeto +Listar Estado Atual do Projeto {{ "autolistlocalproject": {{ "path_project": "{local_to_save}" + "show_contents" True }} }} Objetivo: Mapear arquivos existentes, estrutura de diretórios e evitar conflitos. -B) Consultar Base de Conhecimento -json{{ - "retrieve_backend_context": {{ - "query": "[descreva claramente o que precisa: ex: 'padrões de autenticação JWT', 'estrutura de models SQLAlchemy']", - "k": 8, - "path": "{chroma_store}", - "name": "{name_chroma_store}" - }} -}} -Quando usar: - -Antes de criar novos endpoints -Ao definir schemas de banco de dados -Para decisões arquiteturais (autenticação, validação, etc.) +--- 2️⃣ DESENVOLVIMENTO E SALVAMENTO Regras de Implementação -✅ SEMPRE: - +Use o pensamento sequencial para densenvolver codigos funcionais e sem erros Use nomenclatura clara e descritiva (snake_case para arquivos/funções) Implemente tratamento de erros com logging adequado Adicione docstrings em todas as funções/classes @@ -107,7 +142,6 @@ async def CodeBackEndAgent( Valide inputs com Pydantic models ❌ NUNCA: - Hardcode credenciais ou secrets Crie arquivos fora de {local_to_save} Sobrescreva arquivos sem verificar o conteúdo atual via autolistlocalproject @@ -134,14 +168,35 @@ async def CodeBackEndAgent( }} }} -3️⃣ ATUALIZAÇÃO DO MANIFEST -Após salvar arquivos, atualize {local_to_save}/manifest.json: -json{{ - "autosave": {{ - "code": "{{\n \"project_name\": \"{tipo_app}\",\n \"last_update\": \"2025-10-07T10:30:00Z\",\n \"files\": [\n {{\n \"path\": \"app/routes/auth.py\",\n \"size_bytes\": 1024,\n \"created_at\": \"2025-10-07T10:30:00Z\"\n }}\n ]\n}}", - "path": "{local_to_save}/manifest.json" - }} -}} + + +Ferramentas de operacoes git: + +Registra alterações no staging area +Entradas: +repo_path(string): Caminho para o repositório Git +message(string): Mensagem de confirmação +Retorna: Confirmação com novo hash de commit +git_add + +Registra alterações no repositório local +Entradas: +repo_path(string): Caminho para o repositório Git +target(string): Ramificação de destino ou commit para comparar com +Retorna: Saída diferente comparando o estado atual com o alvo +git_commit + + +Criar nova branch +Entradas: +repo_path(string): Caminho para o repositório Git +max_count(número, opcional): Número máximo de confirmações a serem exibidas (padrão: 10) +Retorna: Matriz de entradas de confirmação com hash, autor, data e mensagem +git_create_branch + + + + FORMATO DE RESPOSTA FINAL Após concluir todas as etapas, retorne SOMENTE este JSON (sem texto adicional): @@ -196,19 +251,38 @@ async def CodeBackEndAgent( COMECE AGORA: Execute autolistlocalproject e retrieve_backend_context antes de qualquer implementação. """ + user_content = f""" +## INFORMAÇÕES DO PROJETO +- Tipo: {tipo_app} +- Titulo: {title} +- Descrição: {description} +- Categoria: {category} +- Stack Tecnológica: {technologies} +- Preço Pago Pela Solucao: {price} +- Data Limite De Entrega: {deadline} +- Bonus Recebido Em Caso De Entrega Antecipada: {early_bonus} +- Diretório Base: {local_to_save} + """ imported_tools = [autosave, retrieve_backend_context, autolistlocalproject] session = SQLiteSession("agent_session_backend_01", db_path=os.path.join(os.path.dirname(__file__), 'Sessions', f"session_{user_id}.db")) - + + git_server = _git_server(local_to_save) + sequential_thinking = _sequential_thinking() agent = Agent( name="Agent Code BackEnd", instructions=prompt_system_direct, model=model, + mcp_servers=[ + sequential_thinking, + git_server + ], output_type=CodeBackEndAgentOutput, model_settings=ModelSettings(include_usage=True), tools=imported_tools ) + result = await Runner.run( agent, user_content, @@ -217,7 +291,7 @@ async def CodeBackEndAgent( ) final_output = result.final_output saved_files = final_output.saved_files - + usage = result.context_wrapper.usage total_usage["input"] = usage.input_tokens total_usage["cached"] = usage.input_tokens_details.cached_tokens diff --git a/backend/Dockerfile b/backend/Dockerfile index 1045f2848..9f5bf0b43 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -10,8 +10,30 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ g++ \ linux-headers-amd64 \ libssl-dev \ + docker-compose \ && rm -rf /var/lib/apt/lists/* +# Instalar Docker CLI +RUN apt-get update && apt-get install -y \ + ca-certificates \ + curl \ + gnupg \ + lsb-release && \ + mkdir -m 0755 -p /etc/apt/keyrings && \ + curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \ + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ + https://download.docker.com/linux/debian \ + $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null && \ + apt-get update && \ + apt-get install -y docker-ce-cli docker-compose-plugin + +RUN apt-get update && \ + apt-get install -y curl && \ + curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \ + apt-get install -y nodejs + + RUN apt-get update && apt-get install -y \ ca-certificates \ curl \ diff --git a/backend/Functions/Think/Think.py b/backend/Functions/Think/Think.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/Functions/autocreaterepo/autocreaterepo.py b/backend/Functions/autocreaterepo/autocreaterepo.py new file mode 100644 index 000000000..8fce6f034 --- /dev/null +++ b/backend/Functions/autocreaterepo/autocreaterepo.py @@ -0,0 +1,45 @@ +from typing_extensions import TypedDict, Any +from agents import Agent, ModelSettings, function_tool, FileSearchTool, WebSearchTool, Runner +from datetime import datetime +import requests + +class autocreaterepoData(TypedDict): + repo_owner: str + repo_name: str + description: str + githubtoken: str + private: bool + +@function_tool +def autocreaterepo(data: autocreaterepoData): + try: + data_FINAL = data["data"] + repo_owner = data_FINAL["repo_owner"] + repo_name = data_FINAL["repo_name"] + description = data_FINAL["description"] + githubtoken = data_FINAL["githubtoken"] + private = data_FINAL["private"] + except Exception as eroo1: + print(eroo1) + repo_owner = data["repo_owner"] + repo_name = data["repo_name"] + description = data["description"] + githubtoken = data["githubtoken"] + private = data["private"] + repo_url = f"https://api.github.com/orgs/{repo_owner}/repos" + headers = { + "Authorization": f"token {githubtoken}", + "Accept": "application/vnd.github.v3+json" + } + repo_data = { + "name": repo_name, + "description": description, + "private": private + } + response = requests.post(repo_url, json=repo_data, headers=headers) + if response.status_code == 201: + print(f"Repositório {repo_name} criado com sucesso na organização {repo_owner}") + return {"status": "success", "message": f"Repositório {repo_name} criado com sucesso na organização {repo_owner}"} + else: + print(f"Falha ao criar o repositório. Status: {response.status_code}, Resposta: {response.json()}") + return {"status": "error", "message": response.json()} \ No newline at end of file diff --git a/backend/Functions/autolistlocalproject/autolistlocalproject.py b/backend/Functions/autolistlocalproject/autolistlocalproject.py index a78f7602d..ec36c825b 100644 --- a/backend/Functions/autolistlocalproject/autolistlocalproject.py +++ b/backend/Functions/autolistlocalproject/autolistlocalproject.py @@ -29,19 +29,28 @@ def ler_conteudos_arquivos(caminhos_arquivos): class FunctionData(TypedDict): path_project: str + show_contents: bool @function_tool def autolistlocalproject(data: FunctionData): try: data_FINAL = data["data"] path_project = data_FINAL["path_project"] + show_contents = data_FINAL["show_contents"] except Exception as eroo1: print(eroo1) path_project = data["path_project"] + show_contents = data["show_contents"] caminhos_arquivos = listar_arquivos(path_project) conteudos_arquivos = ler_conteudos_arquivos(caminhos_arquivos) - data = { - "paths": caminhos_arquivos, - "contents": conteudos_arquivos, - } + if show_contents == False: + data = { + "paths": caminhos_arquivos, + } + else: + + data = { + "paths": caminhos_arquivos, + "contents": conteudos_arquivos, + } return data \ No newline at end of file diff --git a/backend/Functions/gitcommit/gitcommit.py b/backend/Functions/gitcommit/gitcommit.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/Models/postgreSQL.py b/backend/Models/postgreSQL.py index 077c2ea76..29710ae00 100644 --- a/backend/Models/postgreSQL.py +++ b/backend/Models/postgreSQL.py @@ -8,6 +8,7 @@ from enum import Enum as PyEnum from sqlalchemy import Text from sqlalchemy.sql import func +from sqlalchemy.dialects.postgresql import JSON TOKEN_DEFAULT_EXPIRES_DAYS = 30 @@ -51,23 +52,58 @@ class TaskStatus(PyEnum): DONE = "done" FAILED = "failed" +class TaskLog(db.Model): + __tablename__ = "task_logs" + + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + task_id = db.Column(db.Integer, db.ForeignKey("backend_tasks.id"), nullable=False) + type = db.Column(db.String(32), default="info") # info, success, warning, error + message = db.Column(db.Text, nullable=False) + task_metadata = db.Column(JSON, nullable=True) # Qualquer dado extra, opcional + created_at = db.Column(db.DateTime(timezone=True), default=func.now()) + +class ApprovedProject(db.Model): + __tablename__ = "approved_projects" + + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + task_id = db.Column(db.Integer, db.ForeignKey("backend_tasks.id"), nullable=False) + agent_id = db.Column(db.Integer, nullable=False) + approved_at = db.Column(db.DateTime(timezone=True), default=func.now()) + amount_paid = db.Column(db.Float, nullable=False) + bonus_paid = db.Column(db.Float, default=0.0) + status = db.Column(db.String(32), default="completed") # completed, pending, failed + + class BackendTask(db.Model): __tablename__ = "backend_tasks" id = db.Column(db.Integer, primary_key=True, autoincrement=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) - user_content = db.Column(db.Text, nullable=False) - category = db.Column(db.String(128), default="desenvolvimento") - commit_language = db.Column(db.String(128), default="pt") - priority = db.Column(db.Integer, default=1) + status = db.Column(db.String(128), default=TaskStatus.PENDING) created_at = db.Column(db.DateTime(timezone=True), default=func.now()) completed_at = db.Column(db.DateTime(timezone=True), default=func.now()) + commit_language = db.Column(db.String(128), default="pt") + + title = db.Column(db.Text, nullable=True) + description = db.Column(db.Text, nullable=True) + category = db.Column(db.String(128), default="desenvolvimento") + price = db.Column(db.String(128), nullable=True) + priority = db.Column(db.Integer, default=1) + technologies = db.Column(db.String(128), nullable=True) + deadline = db.Column(db.String(128), nullable=True) + early_bonus = db.Column(db.String(128), nullable=True) estimated_hours = db.Column(db.String(128), nullable=True) + progress = db.Column(db.Integer, default=0) + allocated_agent = db.Column(db.String(128), nullable=True) + project_files = db.Column(JSON, nullable=True, default=list) + + total_tokens = db.Column(db.String(128), nullable=True) result = db.Column(db.Text, nullable=True) eta_str = db.Column(db.String(328), nullable=True) + updated_at = db.Column(db.DateTime, default=datetime.utcnow) class CommitMessage(db.Model): __tablename__ = 'commit_messages' diff --git a/backend/Modules/Savers/create_task.py b/backend/Modules/Savers/create_task.py index 462e40c71..1890ab07e 100644 --- a/backend/Modules/Savers/create_task.py +++ b/backend/Modules/Savers/create_task.py @@ -3,7 +3,14 @@ from datetime import datetime import pytz -def create_task(user_id, content, priority=1, hours="1.2", lang="pt", eta_str='', EMPLOYER_CATEGORY=''): +def create_task(user_id, + content, + priority=1, + hours="1.2", + lang="pt", + eta_str='', + EMPLOYER_CATEGORY='' + ): """ Cria uma tarefa no banco Flask-SQLAlchemy. """ diff --git a/backend/TestDiscovery/teste_mcp.py b/backend/TestDiscovery/teste_mcp.py deleted file mode 100644 index 519bc5dc6..000000000 --- a/backend/TestDiscovery/teste_mcp.py +++ /dev/null @@ -1,33 +0,0 @@ -from pathlib import Path -from agents import Agent, Runner -from agents.mcp import MCPServerStdio -import asyncio -import os -from dotenv import load_dotenv - -os.chdir(os.path.join(os.path.dirname(__file__))) -load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), "Keys", 'keys.env')) - -import asyncio - -from agents import Agent, HostedMCPTool, Runner - -async def main() -> None: - agent = Agent( - name="Assistant", - tools=[ - HostedMCPTool( - tool_config={ - "type": "mcp", - "server_label": "gitmcp", - "server_url": "https://gitmcp.io/openai/codex", - "require_approval": "never", - } - ) - ], - ) - - result = await Runner.run(agent, "https://github.com/openai/openai-chatkit-advanced-samples Which language is this repository written in?") - print(result.final_output) - -asyncio.run(main()) \ No newline at end of file diff --git a/backend/_task_allocate_logs.py b/backend/_task_allocate_logs.py new file mode 100644 index 000000000..199d0676a --- /dev/null +++ b/backend/_task_allocate_logs.py @@ -0,0 +1,33 @@ +from datetime import datetime +from api import db, app +from Models.postgreSQL import BackendTask, TaskLog + +TASK_ID = 3 +NEW_LOGS = [ + {"id": "log1", "type": "info", "message": "Tarefa iniciada pelo agente CodAgent", "timestamp": datetime.utcnow()}, + {"id": "log2", "type": "success", "message": "Estrutura inicial do projeto carregada", "timestamp": datetime.utcnow()}, +] + +def allocate_logs(task_id: int, logs: list): + with app.app_context(): + task = db.session.query(BackendTask).filter_by(id=task_id).first() + if not task: + print(f"Tarefa {task_id} não encontrada.") + return + + for log in logs: + new_log = TaskLog( + task_id=task.id, + type=log["type"], + message=log["message"], + task_metadata={}, # se precisar de dados extras + created_at=log["timestamp"] + ) + db.session.add(new_log) + + task.updated_at = datetime.utcnow() if hasattr(task, "updated_at") else None + db.session.commit() + print(f"Logs alocados com sucesso na tarefa {task_id}!") + +if __name__ == "__main__": + allocate_logs(TASK_ID, NEW_LOGS) diff --git a/backend/_task_allocate_task.py b/backend/_task_allocate_task.py new file mode 100644 index 000000000..05d10b7c9 --- /dev/null +++ b/backend/_task_allocate_task.py @@ -0,0 +1,38 @@ +import os +import json +from datetime import datetime +from api import db, app +from Models.postgreSQL import BackendTask # importe seu modelo correto + +# Configurações iniciais +TASK_ID = 3 # ID da tarefa que você quer atualizar +AGENT_NAME = "CodAgent" # Nome do agente a ser alocado + +# Exemplo de project_files compatível com FileItem do front-end +PROJECT_FILES = [ + {"name": "server.js", "language": "javascript", "lines": 120, "status": "completed"}, + {"name": "auth.js", "language": "javascript", "lines": 45, "status": "in_progress"}, + {"name": "README.md", "language": "markdown", "lines": 10, "status": "completed"}, +] + +def allocate_task(task_id: int, agent_name: str, project_files: list): + with app.app_context(): + + task = db.session.query(BackendTask).filter_by(id=task_id).first() + if not task: + print(f"Tarefa {task_id} não encontrada.") + return + + task.allocated_agent = agent_name + task.project_files = project_files + task.status = "running" + task.progress = 20 + task.updated_at = datetime.utcnow() if hasattr(task, "updated_at") else None + + db.session.commit() + print(f"Tarefa {task_id} atualizada com sucesso!") + print(f"Agente: {agent_name}") + print(f"Arquivos: {json.dumps(project_files, indent=2)}") + +if __name__ == "__main__": + allocate_task(TASK_ID, AGENT_NAME, PROJECT_FILES) diff --git a/backend/api.py b/backend/api.py index 21e1e16ba..b7bffaad4 100644 --- a/backend/api.py +++ b/backend/api.py @@ -3,7 +3,10 @@ import threading import requests import json +import pytz import logging +import re +from sqlalchemy import or_ from dotenv import load_dotenv import asyncio import stripe @@ -43,7 +46,7 @@ from Modules.Geters.plans_data import get_plans_data from Modules.Resolvers.verify_signature import verify_signature from Modules.Resolvers.git_contex_layer_process import process_git_context_layer -from Modules.Savers.create_task import create_task +# from Modules.Savers.create_task import create_task diretorio_script = os.path.dirname(os.path.abspath(__file__)) logger = logging.getLogger(__name__) @@ -1471,35 +1474,67 @@ def add_task(): """ Endpoint para criar uma tarefa na fila process_dynamic_queue """ + data = request.get_json() if not data: return jsonify({"error": "Nenhum dado fornecido"}), 400 + + email = data.get("email") + password = data.get("password") + user, _, status = auth_user(logs_collection, + app, + email=email, + password=password + ) + if status != "success" or not user: + return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 - user_id = data.get('user_id') - content = data.get('content') - EMPLOYER_CATEGORY = data.get('category') + tz = pytz.timezone("America/Sao_Paulo") priority = data.get('priority', 1) - hours = data.get('hours', "1.0") lang = data.get('lang', "pt") - eta_str = data.get('eta') # Espera string ISO - - if not user_id or not content: - return jsonify({"error": "Campos 'user_id' e 'content' são obrigatórios"}), 400 + numeric_user_id = user.id + title = data.get('title') + description = data.get('description') + category = data.get('category') + price = data.get('price') + technologies = data.get('technologies') + deadline = data.get('deadline') + early_bonus = data.get('early_bonus') + eta_str = data.get('deadline') try: - - - task_id = create_task(user_id, content, priority, hours, lang, eta_str, EMPLOYER_CATEGORY) - - - return jsonify({"task_id": task_id, "status": "created"}), 201 + task = BackendTask( + user_id=numeric_user_id, + title=title, + description=description, + category=category, + price=price, + priority=priority, + technologies=technologies, + deadline=deadline, + early_bonus=early_bonus, + commit_language=lang, + status=TaskStatus.PENDING.value, + created_at=datetime.now(tz), + eta_str=eta_str + ) + db.session.add(task) + db.session.commit() + print(f"Tarefa criada: {task.id}") + return jsonify({"task_id": task.id, "status": "created"}), 201 except Exception as e: + print(f"error Tarefa : {e}") return jsonify({"error": str(e)}), 500 @app.route('/api/tasks/list', methods=['GET']) def list_tasks(): - """Listar tarefas criadas pelo usuário, com filtros e paginação""" - user, _, status = auth_user(logs_collection, app) + email = request.args.get("email") + password = request.args.get("password") + user, _, status = auth_user(logs_collection, + app, + email=email, + password=password + ) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -1513,7 +1548,9 @@ def list_tasks(): query = BackendTask.query.filter_by(user_id=numeric_user_id) if search_term: - query = query.filter(BackendTask.user_content.ilike(f'%{search_term}%')) + like = f"%{search_term}%" + query = query.filter(or_(BackendTask.title.ilike(like), BackendTask.description.ilike(like))) + if status_filter: query = query.filter(BackendTask.status == status_filter) @@ -1521,19 +1558,66 @@ def list_tasks(): page=page, per_page=limit, error_out=False ) + def parse_price(p): + if not p: + return None + nums = re.findall(r"[0-9]+(?:[.,][0-9]+)?", p.replace(',', '.')) + if not nums: + return None + try: + val = float(nums[0]) + if val.is_integer(): + return int(val) + return val + except: + return None + + def map_status(s): + if not s: + return "pending" + s = s.lower() + if s in ("running",): + return "in_progress" + if s in ("sheduled", "scheduled"): + return "scheduled" + if s == "done": + return "done" + if s == "failed": + return "failed" + return s + formatted_tasks = [] for task in tasks_paginated.items: + techs = [] + if task.technologies: + techs = [t.strip() for t in re.split(r"[;,]", task.technologies) if t.strip()] + deadline_iso = None + if task.deadline: + try: + deadline_iso = datetime.fromisoformat(task.deadline).isoformat() + except: + try: + parsed = datetime.strptime(task.deadline, "%Y-%m-%d") + deadline_iso = parsed.isoformat() + except: + deadline_iso = task.deadline formatted_tasks.append({ - 'id': str(task.id), - 'content': task.user_content, - 'priority': task.priority, - 'status': task.status, - 'createdAt': task.created_at.isoformat(), - 'completedAt': task.completed_at.isoformat() if task.completed_at else None, - 'eta_str': task.eta_str if task.eta_str else None, - 'estimatedCapacity': task.estimated_hours, - 'total_tokens': task.total_tokens, - 'commitLanguage': task.commit_language + "id": str(task.id), + "title": task.title or "", + "description": task.description or "", + "status": map_status(task.status), + "createdAt": task.created_at.isoformat() if task.created_at else None, + "completedAt": task.completed_at.isoformat() if task.completed_at else None, + "eta_str": task.eta_str or None, + "estimatedHours": task.estimated_hours or None, + "total_tokens": task.total_tokens or None, + "commitLanguage": task.commit_language or None, + "price": parse_price(task.price), + "priority": task.priority, + "technologies": techs, + "deadline": deadline_iso, + "progress": task.progress or 0, + "agentName": task.allocated_agent or None }) log_action(logs_collection, 'tasks_listed', { @@ -1544,7 +1628,12 @@ def list_tasks(): 'total_found': tasks_paginated.total }, user=numeric_user_id) - return jsonify(formatted_tasks) + return jsonify({ + "tasks": formatted_tasks, + "total": tasks_paginated.total, + "page": tasks_paginated.page, + "per_page": tasks_paginated.per_page + }) except Exception as e: log_action(logs_collection, 'tasks_list_error', {'error': str(e)}, user=numeric_user_id, level='error') @@ -1552,7 +1641,6 @@ def list_tasks(): @app.route('/api/tasks/details/', methods=['GET']) def get_task_details(task_id): - """Obter detalhes de uma tarefa específica""" user, _, status = auth_user(logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -1562,36 +1650,170 @@ def get_task_details(task_id): try: task = BackendTask.query.filter_by(id=task_id, user_id=numeric_user_id).first_or_404() + def parse_price(p): + if not p: + return None + nums = re.findall(r"[0-9]+(?:[.,][0-9]+)?", p.replace(',', '.')) + if not nums: + return None + try: + val = float(nums[0]) + return int(val) if val.is_integer() else val + except: + return None + + def map_status(s): + if not s: + return "pending" + s = s.lower() + return { + "running": "in_progress", + "sheduled": "scheduled", + "scheduled": "scheduled", + "done": "done", + "failed": "failed" + }.get(s, s) + + techs = [t.strip() for t in re.split(r"[;,]", task.technologies or "") if t.strip()] + + try: + deadline_iso = datetime.fromisoformat(task.deadline).isoformat() + except: + try: + deadline_iso = datetime.strptime(task.deadline, "%Y-%m-%d").isoformat() + except: + deadline_iso = task.deadline + + project_files = [] + if isinstance(task.project_files, list): + for f in task.project_files: + if isinstance(f, dict): + project_files.append({ + "name": f.get("name", "arquivo_desconhecido"), + "language": f.get("language", "txt"), + "lines": f.get("lines") if isinstance(f.get("lines"), int) else 0, + "status": "completed" if f.get("status") in ["completed", "done"] else "in_progress" + }) + task_details = { - 'id': str(task.id), - 'content': task.user_content, - 'priority': task.priority, - 'status': task.status, - 'createdAt': task.created_at.isoformat(), - 'completedAt': task.completed_at.isoformat() if task.completed_at else None, - 'eta_str': task.eta_str if task.eta_str else None, - 'estimatedHours': task.estimated_hours, - 'total_tokens': task.total_tokens, - 'commitLanguage': task.commit_language, - 'result': task.result if hasattr(task, 'result') else None # caso você armazene resultado + "id": str(task.id), + "title": task.title or "", + "description": task.description or "", + "status": map_status(task.status), + "createdAt": task.created_at.isoformat() if task.created_at else None, + "completedAt": task.completed_at.isoformat() if task.completed_at else None, + "eta_str": task.eta_str or None, + "estimatedHours": task.estimated_hours or None, + "total_tokens": task.total_tokens or None, + "commitLanguage": task.commit_language or None, + "price": parse_price(task.price), + "priority": task.priority, + "technologies": techs, + "deadline": deadline_iso, + "progress": task.progress or 0, + "agentName": task.allocated_agent or None, + "earlyBonus": parse_price(task.early_bonus), + "category": task.category or None, + "result": task.result or None, + "project_files": project_files } - log_action(logs_collection, 'task_details_accessed', { - 'task_id': task_id - }, user=numeric_user_id) + # Buscar logs do projeto + logs = TaskLog.query.filter_by(task_id=task.id).order_by(TaskLog.created_at.asc()).all() + logs_json = [ + { + "id": str(log.id), + "type": log.type, + "message": log.message, + "timestamp": log.created_at.isoformat(), + "metadata": log.task_metadata or {} + } + for log in logs + ] + task_details["logs"] = logs_json - return jsonify(task_details) + log_action(logs_collection, 'task_details_accessed', {'task_id': task_id}, user=numeric_user_id) + return jsonify(task_details), 200 except Exception as e: logger.info(f"task_details_error {str(e)}") - log_action(logs_collection, 'task_details_error', {'task_id': task_id, 'error': str(e)}, user=numeric_user_id, level='error') + log_action( + logs_collection, + 'task_details_error', + {'task_id': task_id, 'error': str(e)}, + user=numeric_user_id, + level='error' + ) return jsonify({'error': 'Falha ao buscar detalhes da tarefa'}), 500 - +@app.route('/api/projects/approve', methods=['POST']) +def approve_project(): + user, _, status = auth_user(logs_collection, app) + if status != "success" or not user: + return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 + + data = request.get_json() + if not data: + return jsonify({"error": "Nenhum dado fornecido"}), 400 + + task_id = data.get("task_id") + bonus = float(data.get("bonus", 0)) + + try: + task = BackendTask.query.filter_by(id=task_id).first_or_404() + if task.status.lower() != "done": + return jsonify({"error": "Tarefa ainda não concluída"}), 400 + + # registrar aprovação + approved = ApprovedProject( + task_id=task.id, + agent_id=task.user_id, + amount_paid=float(task.price) if task.price else 0, + bonus_paid=bonus, + status="completed" + ) + db.session.add(approved) + db.session.commit() + + log_action(logs_collection, "project_approved", {"task_id": task.id, "amount": task.price, "bonus": bonus}, user=user.id) + + return jsonify({"status": "approved", "task_id": task.id}), 200 + + except Exception as e: + log_action(logs_collection, "project_approval_error", {"error": str(e)}, user=user.id, level="error") + return jsonify({"error": str(e)}), 500 +@app.route('/api/projects/approved', methods=['GET']) +def list_approved_projects(): + user, _, status = auth_user(logs_collection, app) + if status != "success" or not user: + return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 + + try: + # Buscar todas as tarefas do usuário + user_tasks = BackendTask.query.filter_by(user_id=user.id).order_by(BackendTask.created_at.desc()).all() + approved_map = {ap.task_id: ap for ap in ApprovedProject.query.filter(ApprovedProject.task_id.in_([t.id for t in user_tasks])).all()} + + projects_list = [] + for task in user_tasks: + approved = approved_map.get(task.id) + projects_list.append({ + "task_id": task.id, + "title": task.title, + "agent_id": approved.agent_id if approved else None, + "amount_paid": task.price or 0, + "bonus_paid": approved.bonus_paid if approved else 0, + "approved_at": approved.approved_at.isoformat() if approved else None, + "status": "completed" if approved else "pending" + }) + log_action(logs_collection, "approved_projects_listed", {"total": len(projects_list)}, user=user.id) + return jsonify({"approved_projects": projects_list, "total": len(projects_list)}), 200 + except Exception as e: + log_action(logs_collection, "approved_projects_error", {"error": str(e)}, user=user.id, level="error") + return jsonify({"error": str(e)}), 500 diff --git a/backend/celery_app.py b/backend/celery_app.py index 1848f95c4..554f89887 100644 --- a/backend/celery_app.py +++ b/backend/celery_app.py @@ -88,7 +88,12 @@ async def execute_task(): if task.category == "desenvolvimento": result = run_backend_agent.apply_async( - args=(task.id, task.user_id, task.user_content, task.commit_language), + args=( + task.id, + task.user_id, + task.user_content, + task.commit_language + ), eta=eta_dt ) print(f"Tarefa #{task.id} enviada com sucesso.") diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index 1d683fe28193dae7618323aea9d7dca1e90035b4..44cffbb8912a8148a21e64e76a5d638f39459408 100644 GIT binary patch delta 31 ncmZpWXpq>jpNn0Hho6Cg(Q5L4wm4?rWckVQ?AnuqxYq#ygwF`0 delta 31 ncmZpWXpq>jpNn0P-;jZU@xtW)Y;nxKQPPv+*|jGJajydakvs`d diff --git a/backend/run_codeagent.py b/backend/run_codeagent.py new file mode 100644 index 000000000..3eb446542 --- /dev/null +++ b/backend/run_codeagent.py @@ -0,0 +1,50 @@ +import os +import threading +import requests +import json +import logging +from dotenv import load_dotenv +import asyncio +import stripe +from decimal import Decimal +from bson.json_util import dumps +from datetime import datetime, timedelta, timezone +import hmac +import hashlib +from flask import g, Flask, Response, request, jsonify, send_file, abort, redirect +from flask_cors import CORS +from asgiref.wsgi import WsgiToAsgi +from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity +from flask_limiter import Limiter +from flask_limiter.util import get_remote_address + +os.chdir(os.path.join(os.path.dirname(__file__))) +load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), 'Keys', 'keys.env')) +OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") +from Agents.AppAI.CodeBackend.ai import CodeBackEndAgent + +title = "" +description = "" +category = "" +price = "" +technologies = "" +early_bonus = "" +deadline = "" +user_id = 1 +tipo_app = "automacao" +local_to_save = os.path.join(os.path.dirname(__file__), 'WorkEnv') + +total_usage, saved_files = asyncio.run(CodeBackEndAgent( + OPENAI_API_KEY, + user_id, + tipo_app, + title, + description, + category, + price, + technologies, + early_bonus, + deadline, + local_to_save = local_to_save + )) +print(f"saved_files {saved_files}") \ No newline at end of file diff --git a/backend/teste_mcp.py b/backend/teste_mcp.py new file mode 100644 index 000000000..d13f10de5 --- /dev/null +++ b/backend/teste_mcp.py @@ -0,0 +1,64 @@ +from pathlib import Path +from agents import Agent, Runner +from agents.mcp import MCPServerStdio +import asyncio +import os +from dotenv import load_dotenv + +os.chdir(os.path.join(os.path.dirname(__file__))) +load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), "Keys", 'keys.env')) + +import asyncio +from pathlib import Path +from agents import Agent, Runner +from agents.mcp import MCPServerStdio + +current_dir = Path(__file__).parent +samples_dir = current_dir / "sample_files" + +from agents import Agent, HostedMCPTool, Runner + +async def gitmcp() -> None: + agent = Agent( + name="Assistant", + tools=[ + HostedMCPTool( + tool_config={ + "type": "mcp", + "server_label": "gitmcp", + "server_url": "https://gitmcp.io/openai/codex", + "require_approval": "never", + } + ) + ], + ) + + result = await Runner.run(agent, "https://github.com/openai/openai-chatkit-advanced-samples Which language is this repository written in?") + print(result.final_output) + +# asyncio.run(gitmcp()) + + +async def thinking() -> None: + async with MCPServerStdio( + name="sequential-thinking", + params={ + "command": "docker", + "args": [ + "run", + "--rm", + "-i", + "mcp/sequentialthinking" + ], + }, + client_session_timeout_seconds=45 + ) as server: + agent = Agent( + name="Assistant", + instructions="use o pensamento sequencial para responder o usuario", + mcp_servers=[server], + ) + result = await Runner.run(agent, "quanto é 900 divido por 7") + print(result.final_output) + +asyncio.run(thinking()) \ No newline at end of file diff --git a/build_in_windows.py b/build_in_windows.py index 027485d3f..5e17b0841 100644 --- a/build_in_windows.py +++ b/build_in_windows.py @@ -10,5 +10,5 @@ def executar_comando(comando): subprocess.run(comando, shell=True) -executar_comando("docker-compose up --build -d ") +executar_comando("docker-compose up --build -d pipeline-api") diff --git a/frontend/devflow-co b/frontend/devflow-co new file mode 160000 index 000000000..eb48a2f53 --- /dev/null +++ b/frontend/devflow-co @@ -0,0 +1 @@ +Subproject commit eb48a2f5342666dcef87e4ee16f3d20499b63df1 From 792a8f80793ac202a230a2afd56a6a16b639cb69 Mon Sep 17 00:00:00 2001 From: Ualerson Date: Sat, 18 Oct 2025 12:21:18 -0300 Subject: [PATCH 2/4] =?UTF-8?q?Atualiza=20prompt=20do=20CodeBackEndAgent?= =?UTF-8?q?=20e=20registra=20mudan=C3=A7as=20de=20subm=C3=B3dulos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resumo das mudanças: - backend/Agents/AppAI/CodeBackend/ai.py: atualização do prompt em PT para CodeBackEndAgent. Reorganização do fluxo, remoção de trechos redundantes, ajustes de formatação e inclusão de orientações explícitas sobre o processo de análise inicial, salvamento de arquivos e padrões de design. O objetivo é tornar o prompt mais claro, eliminando textos desnecessários e reforçando boas práticas (evitar secrets, uso de type hints, docstrings, validação com Pydantic, etc.). - backend/celerybeat-schedule: diff binário; não há alterações de código legíveis reportadas. Pode indicar mudança de binários ou geração pelo ambiente de build. - frontend/devflow-co: dif de submódulo com estado dirty (Subproject commit ...-dirty), indicando alterações locais não registradas. Recomenda-se sincronizar o submódulo com o repositório remoto ou recriar/limpar o estado para evitar inconsistências. Motivo: - alinhar o prompt do CodeBackEndAgent com diretrizes de implementação, melhoria de legibilidade e fluxo de trabalho, além de deixar explícita a necessidade de listar o estado do projeto antes de alterações. - identificar e registrar mudanças em submódulos/binaries para que a equipe possa validar o estado do código e evitar merges com estados não estáveis. Ações sugeridas: - revisar as mudanças no ai.py e validar se o fluxo proposto atende aos requisitos do projeto. - se a alteração nos submódulos for necessária, fixe um estado estável do frontend/devflow-co e registre em um commit separado; senão, restaure para o estado limpo. --- backend/Agents/AppAI/CodeBackend/ai.py | 89 ++++++++++++------------- backend/celerybeat-schedule | Bin 3072 -> 3072 bytes 2 files changed, 41 insertions(+), 48 deletions(-) diff --git a/backend/Agents/AppAI/CodeBackend/ai.py b/backend/Agents/AppAI/CodeBackend/ai.py index c09e345e4..77f6e9d2e 100644 --- a/backend/Agents/AppAI/CodeBackend/ai.py +++ b/backend/Agents/AppAI/CodeBackend/ai.py @@ -108,57 +108,50 @@ async def CodeBackEndAgent( elif commit_language == 'pt': prompt_system_direct = f""" -# IDENTIDADE E CONTEXTO Você é um desenvolvedor backend sênior especializado em Python/Flask, -responsável por implementar tarefas de backend de forma autônoma e eficiente. - - - -# FLUXO DE TRABALHO OBRIGATÓRIO - -## 1️⃣ ANÁLISE INICIAL (SEMPRE EXECUTAR PRIMEIRO) +responsável por implementar tarefas de backend de forma autônoma e eficiente.\n +# FLUXO DE TRABALHO OBRIGATÓRIO\n +1 ANÁLISE INICIAL (SEMPRE EXECUTAR PRIMEIRO)\n Antes de qualquer implementação, você DEVE: - -Listar Estado Atual do Projeto - -{{ - "autolistlocalproject": {{ - "path_project": "{local_to_save}" - "show_contents" True + Listar Estado Atual do Projeto + {{ + "autolistlocalproject": {{ + "path_project": "{local_to_save}" + "show_contents" True + }} }} -}} -Objetivo: Mapear arquivos existentes, estrutura de diretórios e evitar conflitos. - ---- - -2️⃣ DESENVOLVIMENTO E SALVAMENTO -Regras de Implementação -Use o pensamento sequencial para densenvolver codigos funcionais e sem erros -Use nomenclatura clara e descritiva (snake_case para arquivos/funções) -Implemente tratamento de erros com logging adequado -Adicione docstrings em todas as funções/classes -Siga padrões RESTful para APIs -Use type hints (Python 3.10+) -Valide inputs com Pydantic models - -❌ NUNCA: -Hardcode credenciais ou secrets -Crie arquivos fora de {local_to_save} -Sobrescreva arquivos sem verificar o conteúdo atual via autolistlocalproject - -Estrutura de Diretórios Padrão -{local_to_save}/ -├── app/ -│ ├── __init__.py -│ ├── models/ # SQLAlchemy models -│ ├── routes/ # Flask blueprints -│ ├── schemas/ # Pydantic schemas -│ ├── services/ # Business logic -│ └── utils/ # Helpers -├── tasks/ # Celery tasks -├── config/ # Settings (BaseSettings) -├── tests/ # Testes unitários -└── manifest.json # Metadata do projeto + Objetivo: Mapear arquivos existentes, estrutura de diretórios e evitar conflitos.\n +---\n +2 DESENVOLVIMENTO E SALVAMENTO\n + Regras de Implementação\n + Use o pensamento sequencial para densenvolver codigos funcionais e sem erros + Use operacoes git para adicionar os arquivos a area de staging e criar um commit profisional\n + \n + Regras de desing system\n + Use nomenclatura clara e descritiva (snake_case para arquivos/funções) + Implemente tratamento de erros com logging adequado + Adicione docstrings em todas as funções/classes + Siga padrões RESTful para APIs + Use type hints (Python 3.10+) + Valide inputs com Pydantic models\n + ❌ NUNCA: + Hardcode credenciais ou secrets + Crie arquivos fora de {local_to_save} + Sobrescreva arquivos sem verificar o conteúdo atual via autolistlocalproject + + Estrutura de Diretórios Padrão + {local_to_save}/ + ├── app/ + │ ├── __init__.py + │ ├── models/ # SQLAlchemy models + │ ├── routes/ # Flask blueprints + │ ├── schemas/ # Pydantic schemas + │ ├── services/ # Business logic + │ └── utils/ # Helpers + ├── tasks/ # Celery tasks + ├── config/ # Settings + ├── tests/ # Testes unitários + Salvamento de Arquivos Para cada arquivo implementado: json{{ diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index 44cffbb8912a8148a21e64e76a5d638f39459408..fe4584f6de1cd8a98661a67d7e6d438348c90d2d 100644 GIT binary patch delta 29 lcmZpWXpq=&kc(A_fq~)M Date: Sat, 18 Oct 2025 12:31:17 -0300 Subject: [PATCH 3/4] =?UTF-8?q?Atualiza=20fluxo=20de=20an=C3=A1lise=20inic?= =?UTF-8?q?ial=20e=20salvamento=20no=20CodeBackEndAgent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resumo Esta mudança introduz um fluxo obrigatório de trabalho no CodeBackEndAgent, reorganizando o guia de implementação, adicionando templates de salvamento e validações, e ajustando a forma de registrar alterações para facilitar auditoria e reduzir conflitos. O que foi alterado - ackend/Agents/AppAI/CodeBackend/ai.py - Reestruturado o fluxo de trabalho obrigatório do CodeBackEndAgent com uma formatação mais clara e padronizada: - 1) Análise inicial (SEMPRE EXECUTAR PRIMEIRO) com subtarefas numeradas (1.1) para orientar a listagem do estado do projeto via autolistlocalproject. - 2) Desenvolvimento e Salvamento com subitens (2.1 a 2.4): - 2.1 Regras de Implementação (pensamento sequencial, uso de git para staging e commits profissionais). - 2.2 Salvamento de Arquivos (template JSON autosave com código de exemplo e caminho de destino). - 2.3 Regras de design system (nomenclatura, tratamento de erros, docstrings, RESTful, type hints, validação com Pydantic). - 2.4 Itens que não devem ser feitos (hardcoding, criar fora do diretório, sobrescrever sem verificação). - 3) Formato de Resposta Final (3.1): exige retornar SOMENTE um JSON com as seções analysis_summary, implementation_details, saved_files e next_steps. - Introduziu um formato de autosave para cada arquivo implementado, com estrutura JSON clara contendo code e path (exemplo com {local_to_save}). - Ajustes no fluxo de geração de estado do projeto (autolistlocalproject) para suportar a verificação do conteúdo antes de salvar. - Observação de alinhamento com as práticas de design de código, incluindo seções de regras, consequências e passos novos para geração de commits mais consistentes. - backend/celerybeat-schedule - Arquivo binário alterado conforme a mudança de conteúdo/estado de dependências do scheduler (diff binário). - frontend/devflow-co - Atualização do submódulo (commit alterado para refletir mudanças locais), indicado pela linha de commit de submódulo para um estado diferente (dirty). Por que foi feito - Padronizar e clarificar o fluxo de trabalho do CodeBackEndAgent, reduzindo ambiguidades durante a implementação e evitando conflitos de código. - Introduzir templates de salvamento e validações claras para reduzir erros humanos e facilitar auditorias de mudanças. - Tornar o formato de saída de commits mais previsível, com JSON bem definido, facilitando automação e integração com pipelines. - Manter rastreabilidade de alterações em recursos binários (celerybeat-schedule) e manter o estado do submódulo frontend consistente com o repositório principal. Como funciona agora (alto nível) - O ai.py passa a orientar o fluxo com três grandes blocos: - 1) Análise inicial: listar estado atual do projeto via autolistlocalproject, com o caminho definido por {local_to_save}. - 2) Desenvolvimento e Salvamento: seguir regras de implementação, salvar arquivos com o template autosave contendo code e path, e aplicar as regras de design system. Também há um bloco explícito de coisas que não devem ser feitas. - 3) Formato de Resposta Final: ao concluir, retornar apenas um JSON com as seções de summary e detalhes, mantendo consistência para automação. - A estrutura de diretórios/arquivos para salvamento continua orientada pelo {local_to_save}, com uma sugestão explícita de salvar em caminhos como {local_to_save}/app/routes/... e similares. Impactos e considerações - Este commit não altera a lógica de negócio do backend, apenas o fluxo de trabalho e as diretrizes de implementação, com foco em documentação interna, qualidade de código e automação de commits. - Arquivos binários (celerybeat-schedule) e o estado do submódulo frontend (devflow-co) foram atualizados; é recomendável validar o ambiente de build para garantir que não haja impactos de compatibilidade. - Equipes devem seguir o novo formato de saída JSON para retornos de código, garantindo que pipelines e ferramentas de CI possam interpretar adequadamente as informações. Próximos passos sugeridos - Rodar a suíte de testes para garantir que as mudanças de fluxo não afetem a compatibilidade. - Validar a geração de autosave para novos arquivos, confirmando que os caminhos estão corretos e que o código de exemplo atende aos padrões do projeto. - Atualizar a documentação interna com o novo fluxo e exemplos de autosave. - Confirmar o estado do submódulo frontend após o pull, evitando divergências entre repositório principal e submódulo. --- backend/Agents/AppAI/CodeBackend/ai.py | 101 ++++++++++++------------- backend/celerybeat-schedule | Bin 3072 -> 3072 bytes 2 files changed, 49 insertions(+), 52 deletions(-) diff --git a/backend/Agents/AppAI/CodeBackend/ai.py b/backend/Agents/AppAI/CodeBackend/ai.py index 77f6e9d2e..75ee00eef 100644 --- a/backend/Agents/AppAI/CodeBackend/ai.py +++ b/backend/Agents/AppAI/CodeBackend/ai.py @@ -111,33 +111,37 @@ async def CodeBackEndAgent( Você é um desenvolvedor backend sênior especializado em Python/Flask, responsável por implementar tarefas de backend de forma autônoma e eficiente.\n # FLUXO DE TRABALHO OBRIGATÓRIO\n -1 ANÁLISE INICIAL (SEMPRE EXECUTAR PRIMEIRO)\n -Antes de qualquer implementação, você DEVE: - Listar Estado Atual do Projeto - {{ - "autolistlocalproject": {{ - "path_project": "{local_to_save}" - "show_contents" True +1) ANÁLISE INICIAL (SEMPRE EXECUTAR PRIMEIRO)\n + 1.1 - Antes de qualquer implementação, você DEVE: + Listar Estado Atual do Projeto + {{ + "autolistlocalproject": {{ + "path_project": "{local_to_save}" + "show_contents" True + }} }} - }} - Objetivo: Mapear arquivos existentes, estrutura de diretórios e evitar conflitos.\n + Objetivo: Mapear arquivos existentes, estrutura de diretórios e evitar conflitos.\n ---\n -2 DESENVOLVIMENTO E SALVAMENTO\n - Regras de Implementação\n +2) DESENVOLVIMENTO E SALVAMENTO\n + 2.1 - Regras de Implementação\n Use o pensamento sequencial para densenvolver codigos funcionais e sem erros Use operacoes git para adicionar os arquivos a area de staging e criar um commit profisional\n + 2.2 - Salvamento de Arquivos\n + Para cada arquivo implementado: + json{{ + "autosave": {{ + "code": "# Conteúdo completo do arquivo aqui\n# Inclua imports, docstrings, type hints\n\nfrom flask import Blueprint\n\nauth_bp = Blueprint('auth', __name__)\n\n@auth_bp.route('/login', methods=['POST'])\ndef login():\n \"\"\"Endpoint de autenticação.\"\"\"\n pass", + "path": "{local_to_save}/app/routes/auth.py" + }} + }} \n - Regras de desing system\n + 2.3 - Regras de desing system\n Use nomenclatura clara e descritiva (snake_case para arquivos/funções) Implemente tratamento de erros com logging adequado Adicione docstrings em todas as funções/classes Siga padrões RESTful para APIs Use type hints (Python 3.10+) Valide inputs com Pydantic models\n - ❌ NUNCA: - Hardcode credenciais ou secrets - Crie arquivos fora de {local_to_save} - Sobrescreva arquivos sem verificar o conteúdo atual via autolistlocalproject Estrutura de Diretórios Padrão {local_to_save}/ @@ -151,19 +155,35 @@ async def CodeBackEndAgent( ├── tasks/ # Celery tasks ├── config/ # Settings ├── tests/ # Testes unitários - -Salvamento de Arquivos -Para cada arquivo implementado: -json{{ - "autosave": {{ - "code": "# Conteúdo completo do arquivo aqui\n# Inclua imports, docstrings, type hints\n\nfrom flask import Blueprint\n\nauth_bp = Blueprint('auth', __name__)\n\n@auth_bp.route('/login', methods=['POST'])\ndef login():\n \"\"\"Endpoint de autenticação.\"\"\"\n pass", - "path": "{local_to_save}/app/routes/auth.py" - }} -}} - - - -Ferramentas de operacoes git: + 2.4 - NUNCA FAÇA ISSO:\n + Hardcode credenciais ou secrets + Criar arquivos fora de {local_to_save} + Sobrescrever arquivos sem verificar o conteúdo atual via autolistlocalproject\n +---\n +3) FORMATO DE RESPOSTA FINAL\n + 3.1 - Após concluir todas as etapas, retorne SOMENTE este JSON (sem texto adicional): + json{{ + "analysis_summary": {{ + "existing_files": ["lista de arquivos encontrados no autolistlocalproject"], + "knowledge_retrieved": "resumo breve do que foi consultado no retrieve_backend_context" + }}, + "implementation_details": {{ + "approach": "breve descrição da estratégia de implementação", + "stack_decisions": ["Flask blueprints", "SQLAlchemy models", "Pydantic validation"] + }}, + "saved_files": [ + "{local_to_save}/app/routes/auth.py", + "{local_to_save}/app/models/user.py", + "{local_to_save}/manifest.json" + ], + "next_steps": [ + "Configurar variáveis de ambiente no .env", + "Executar migrações do banco de dados" + ] + }} +---\n +---\n +Conhecimento de Ferramentas de operacoes git Registra alterações no staging area Entradas: @@ -190,29 +210,6 @@ async def CodeBackEndAgent( - -FORMATO DE RESPOSTA FINAL -Após concluir todas as etapas, retorne SOMENTE este JSON (sem texto adicional): -json{{ - "analysis_summary": {{ - "existing_files": ["lista de arquivos encontrados no autolistlocalproject"], - "knowledge_retrieved": "resumo breve do que foi consultado no retrieve_backend_context" - }}, - "implementation_details": {{ - "approach": "breve descrição da estratégia de implementação", - "stack_decisions": ["Flask blueprints", "SQLAlchemy models", "Pydantic validation"] - }}, - "saved_files": [ - "{local_to_save}/app/routes/auth.py", - "{local_to_save}/app/models/user.py", - "{local_to_save}/manifest.json" - ], - "next_steps": [ - "Configurar variáveis de ambiente no .env", - "Executar migrações do banco de dados" - ] -}} - EXEMPLOS DE USO DAS FERRAMENTAS Exemplo 1: Criar Endpoint de Autenticação Sequência: diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index fe4584f6de1cd8a98661a67d7e6d438348c90d2d..2b1bf70bdaee6c2704ad500a2952bc7fc50ced84 100644 GIT binary patch delta 29 lcmZpWXpq=&kc(B0fq^M>@_)8CX5S+D$?@#klY_X|0RVt`2)_UT delta 29 lcmZpWXpq=&kc(A_fq~)M Date: Sat, 18 Oct 2025 14:48:56 -0300 Subject: [PATCH 4/4] Atualiza CodeBackEndAgent com pensamento sequencial MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visão geral: este commit introduz um fluxo de pensamento sequencial para o CodeBackEndAgent, adiciona a ferramenta sequentialthinking e reestrutura a execução para permitir planejamento/reflexão, branches de pensamento, logging aprimorado e uso de RunConfig para paralelismo opcional. O QUE MUDOU (principais alterações) - Backend AI (backend/Agents/AppAI/CodeBackend/ai.py) - Ajustes de imports para suportar RunConfig, FunctionTool, FileSearchTool, WebSearchTool e Runner. - Implementação de novos modelos e classes: ThoughtArgs, SequentialThinkingState, método de formatação deThought (_format_thought) e processamento assíncrono com lock para segurança de concorrência. - Criação do tool_sequentialthinking (FunctionTool) com descrição, schema JSON e handler on_invoke que utiliza o estado global _state para registrar e formatar pensamentos. - Substituição de código legado (_sequential_thinking, _git_server) por integração com MCPServerStdio via Docker para o fluxo de pensamento sequencial. - Código reorganizado para inicializar o ambiente: mudança de diretório para local_to_save, criação do diretório Sessions, e registro de histórico de pensamentos com suporte a ramos (branches). - Atualização do fluxo do CodeBackEndAgent para retornar informações de uso, saved_files e histórico de pensamentos com suporte a logs formatados. - Runner e execução (backend/run_codeagent.py) - Adição de metadados do app em português (tipo_app, title, description, category, price, technologies, early_bonus, deadline). - Passagem do model para CodeBackEndAgent e ajuste de local_to_save para manter consistência com o novo ambiente. - Implementação de passagem de RunConfig com parallel_tool_calls habilitado, para permitir uso simultâneo de ferramentas quando apropriado. - Compatibilidade com a nova arquitetura de pensamento sequencial mantendo a saída com saved_files e total de tokens utilizados. - Arquivos de infraestrutura/integração - backend/celerybeat-schedule e frontend/devflow-co sofreram alterações relacionadas ao estado de submódulos, incluindo uma indicação de submódulo devflow-co dirty. Além disso, arquivos binários (session_1.db, celerybeat-schedule) foram atualizados/alterados pela compilação do backend. - Observação: alterações em arquivos binários não afetam o funcionamento do código fonte, porém refletem mudanças no estado de sessão e agendamento. - Impactos e considerações - Novo fluxo de pensamento sequencial aumenta a rastreabilidade das decisões (log formatado de pensamentos) e facilita o planejamento antes de ações. - Requisitos de ambiente: Docker/MCP para o sequentialthinking e configuração de RunConfig; manter dependências atualizadas (pydantic, asyncio, etc.). - Navegação de save/manifest e saved_files foi ajustada para refletir o novo fluxo de salvamento de arquivos modificados. Como validar - Executar CodeBackEndAgent com o novo fluxo de pensamento sequencial e confirmar que: - O tool sequentialthinking é invocado e registra pensamentos na formatação prevista. - saved_files é populado corretamente e refletido no resultado final. - O uso de tokens é reportado em total, input, reasoning e output conforme novo schema. - Verificar que o ambiente local é configurado para o local_to_save e que o diretório Sessions é criado. - Confirmar que o run_config com parallel_tool_calls funciona conforme esperado em cenários com múltiplas ferramentas. Observação final: o dif difere em vários arquivos além do código-fonte puro (binaries e submódulos). As mudanças não quebram a API esperada pelo código, mas exigem que o ambiente de execução tenha suporte para Docker/MCP e caminhos de diretório configurados corretamente. --- .../AppAI/CodeBackend/Sessions/session_1.db | Bin 61440 -> 77824 bytes backend/Agents/AppAI/CodeBackend/ai.py | 333 ++++++++++++------ backend/celerybeat-schedule | Bin 3072 -> 3072 bytes backend/run_codeagent.py | 20 +- 4 files changed, 228 insertions(+), 125 deletions(-) diff --git a/backend/Agents/AppAI/CodeBackend/Sessions/session_1.db b/backend/Agents/AppAI/CodeBackend/Sessions/session_1.db index b1229db1e36d71e9017017bc2c2b83db113701b6..c11e0bbeaa9c38dd7d5337eeaef84d23f398915f 100644 GIT binary patch literal 77824 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 B*E#?I diff --git a/backend/Agents/AppAI/CodeBackend/ai.py b/backend/Agents/AppAI/CodeBackend/ai.py index 75ee00eef..fe7c6a00f 100644 --- a/backend/Agents/AppAI/CodeBackend/ai.py +++ b/backend/Agents/AppAI/CodeBackend/ai.py @@ -14,7 +14,7 @@ from openai import OpenAI from typing_extensions import TypedDict, Any -from agents import Agent, ModelSettings, function_tool, FileSearchTool, WebSearchTool, Runner +from agents import Agent, ModelSettings, Runner, RunConfig, function_tool, FileSearchTool, WebSearchTool, Runner from datetime import datetime import requests from agents.mcp import MCPServerStdio @@ -39,39 +39,127 @@ class ReflectionData(TypedDict): task: str last_output: str history: List[str] +import asyncio +import json +import logging +from typing import Any, Dict, List, Optional -async def _sequential_thinking(): - async with MCPServerStdio( - name="sequential-thinking", - params={ - "command": "docker", - "args": [ - "run", - "--rm", - "-i", - "mcp/sequentialthinking" - ], - }, - client_session_timeout_seconds=45 - ) as sequential_thinking: - return sequential_thinking +from pydantic import BaseModel +from agents import FunctionTool, RunContextWrapper + +logger = logging.getLogger("sequentialthinking") +logging.basicConfig(level=logging.ERROR) + + +class ThoughtArgs(BaseModel): + thought: str + nextThoughtNeeded: bool + thoughtNumber: int + totalThoughts: int + isRevision: Optional[bool] = None + revisesThought: Optional[int] = None + branchFromThought: Optional[int] = None + branchId: Optional[str] = None + needsMoreThoughts: Optional[bool] = None + + +class SequentialThinkingState: + def __init__(self): + self.thought_history: List[ThoughtArgs] = [] + self.branches: Dict[str, List[ThoughtArgs]] = {} + self.lock = asyncio.Lock() + + def _format_thought(self, t: ThoughtArgs) -> str: + header_parts = [] + if t.isRevision: + header_parts.append("🔄 Revision") + elif t.branchFromThought: + header_parts.append("🌿 Branch") + else: + header_parts.append("💭 Thought") + header = f"{header_parts[0]} {t.thoughtNumber}/{t.totalThoughts}" + if t.isRevision and t.revisesThought: + header += f" (revising thought {t.revisesThought})" + if t.branchFromThought and t.branchId: + header += f" (from thought {t.branchFromThought}, ID: {t.branchId})" + body = t.thought + width = max(len(header), len(body)) + 4 + border = "─" * width + formatted = f"\n┌{border}┐\n│ {header.ljust(width-2)} │\n├{border}┤\n│ {body.ljust(width-2)} │\n└{border}┘" + return formatted + + async def process(self, data: ThoughtArgs) -> Dict[str, Any]: + async with self.lock: + if data.thoughtNumber > data.totalThoughts: + data.totalThoughts = data.thoughtNumber + self.thought_history.append(data) + if data.branchFromThought and data.branchId: + if data.branchId not in self.branches: + self.branches[data.branchId] = [] + self.branches[data.branchId].append(data) + formatted = self._format_thought(data) + logger.error(formatted) + return { + "thoughtNumber": data.thoughtNumber, + "totalThoughts": data.totalThoughts, + "nextThoughtNeeded": data.nextThoughtNeeded, + "branches": list(self.branches.keys()), + "thoughtHistoryLength": len(self.thought_history), + } + + +_state = SequentialThinkingState() + + +async def _on_invoke(ctx: RunContextWrapper[Any], args_json: str) -> str: + parsed = ThoughtArgs.model_validate_json(args_json) + result = await _state.process(parsed) + return json.dumps(result, indent=2) + + +tool_sequentialthinking = FunctionTool( + name="sequentialthinking", + description=( + "Ferramenta para pensamento sequencial/reflexivo. Aceita um objeto com os campos:" + " thought, nextThoughtNeeded, thoughtNumber, totalThoughts, isRevision, revisesThought," + " branchFromThought, branchId, needsMoreThoughts" + ), + params_json_schema=ThoughtArgs.model_json_schema(), + on_invoke_tool=_on_invoke, +) + +# async def _sequential_thinking(): +# async with MCPServerStdio( +# name="sequentialthinking", +# params={ +# "command": "docker", +# "args": [ +# "run", +# "--rm", +# "-i", +# "mcp/sequentialthinking" +# ], +# }, +# client_session_timeout_seconds=45 +# ) as sequential_thinking: +# return sequential_thinking -async def _git_server(src): - async with MCPServerStdio( - name="git", - params={ - "command": "docker", - "args": ["run", - "--rm", - "-i", - "--mount", - f"type=bind,src={src},dst={src}", - "mcp/git" - ], - }, - client_session_timeout_seconds=45 - ) as git_: - return git_ +# async def _git_server(src): +# async with MCPServerStdio( +# name="git", +# params={ +# "command": "docker", +# "args": ["run", +# "--rm", +# "-i", +# "--mount", +# f"type=bind,src={src},dst={src}", +# "mcp/git" +# ], +# }, +# client_session_timeout_seconds=45 +# ) as git_: +# return git_ async def CodeBackEndAgent( OPENAI_API_KEY, @@ -87,11 +175,11 @@ async def CodeBackEndAgent( commit_language = 'pt', model = "gpt-5-nano", local_to_save = "./", - think=True, - self_reflection=True + ): os.environ['OPENAI_API_KEY'] = OPENAI_API_KEY os.makedirs(os.path.join(os.path.dirname(__file__), 'Sessions'), exist_ok=True) + os.chdir(local_to_save) logger.info(f"CodeBackEndAgent Agent") name_chroma_store = "backend_skeleton" chroma_store = os.path.join(os.path.dirname(__file__), 'CodeKnowledge', 'chroma_store') @@ -121,11 +209,13 @@ async def CodeBackEndAgent( }} }} Objetivo: Mapear arquivos existentes, estrutura de diretórios e evitar conflitos.\n + 1.2 - sequentialthinking + Use o pensamento sequencial para pensar e planejar o desenvolvimento + ---\n 2) DESENVOLVIMENTO E SALVAMENTO\n 2.1 - Regras de Implementação\n - Use o pensamento sequencial para densenvolver codigos funcionais e sem erros - Use operacoes git para adicionar os arquivos a area de staging e criar um commit profisional\n + Use o pensamento sequencial para cada codigo funcional e sem erros 2.2 - Salvamento de Arquivos\n Para cada arquivo implementado: json{{ @@ -165,7 +255,6 @@ async def CodeBackEndAgent( json{{ "analysis_summary": {{ "existing_files": ["lista de arquivos encontrados no autolistlocalproject"], - "knowledge_retrieved": "resumo breve do que foi consultado no retrieve_backend_context" }}, "implementation_details": {{ "approach": "breve descrição da estratégia de implementação", @@ -173,7 +262,6 @@ async def CodeBackEndAgent( }}, "saved_files": [ "{local_to_save}/app/routes/auth.py", - "{local_to_save}/app/models/user.py", "{local_to_save}/manifest.json" ], "next_steps": [ @@ -181,66 +269,30 @@ async def CodeBackEndAgent( "Executar migrações do banco de dados" ] }} ----\n ----\n -Conhecimento de Ferramentas de operacoes git - -Registra alterações no staging area -Entradas: -repo_path(string): Caminho para o repositório Git -message(string): Mensagem de confirmação -Retorna: Confirmação com novo hash de commit -git_add - -Registra alterações no repositório local -Entradas: -repo_path(string): Caminho para o repositório Git -target(string): Ramificação de destino ou commit para comparar com -Retorna: Saída diferente comparando o estado atual com o alvo -git_commit - - -Criar nova branch -Entradas: -repo_path(string): Caminho para o repositório Git -max_count(número, opcional): Número máximo de confirmações a serem exibidas (padrão: 10) -Retorna: Matriz de entradas de confirmação com hash, autor, data e mensagem -git_create_branch + - o saved_files deve ser de arquivos salvos ou modificados - - - -EXEMPLOS DE USO DAS FERRAMENTAS +EXEMPLOS DE USO DAS FERRAMENTAS\n Exemplo 1: Criar Endpoint de Autenticação Sequência: - autolistlocalproject → Verificar se já existe app/routes/auth.py -retrieve_backend_context → query: "melhores práticas JWT Flask" +sequentialthinking → pensamento em como implementar o app/routes/auth.py com blueprint de forma proativa e eficiente autosave → Criar app/routes/auth.py com blueprint +sequentialthinking → pensamento em como desenvolver o app/schemas/auth.py com Pydantic models de forma proativa e eficiente autosave → Criar app/schemas/auth.py com Pydantic models -autosave → Atualizar manifest.json - Exemplo 2: Implementar Model de Usuário Sequência: - autolistlocalproject → Mapear models existentes -retrieve_backend_context → query: "schema usuário autenticação PostgreSQL" +sequentialthinking → pensamento em como desenvolver o app/models/user.py com SQLAlchemy de forma proativa e eficiente autosave → Criar app/models/user.py com SQLAlchemy -autosave → Atualizar manifest.json - - +---\n CHECKLIST PRÉ-RESPOSTA Antes de enviar o JSON final, confirme: - Executei autolistlocalproject? - Consultei retrieve_backend_context para decisões importantes? + Consultei o pensamento sequencial para decisões importantes? Todos os arquivos foram salvos via autosave? - O manifest.json foi atualizado? O JSON de resposta está válido e completo? - -COMECE AGORA: Execute autolistlocalproject e retrieve_backend_context antes de qualquer implementação. - """ + user_content = f""" ## INFORMAÇÕES DO PROJETO - Tipo: {tipo_app} @@ -253,46 +305,95 @@ async def CodeBackEndAgent( - Bonus Recebido Em Caso De Entrega Antecipada: {early_bonus} - Diretório Base: {local_to_save} """ - - imported_tools = [autosave, retrieve_backend_context, autolistlocalproject] +# ---\n +# Conhecimento de Ferramentas de operacoes git\n +# Registra alterações no staging area +# Entradas: +# repo_path(string): Caminho para o repositório Git +# message(string): Mensagem de confirmação +# Retorna: Confirmação com novo hash de commit +# git_add +# \n +# Registra alterações no repositório local\n +# Entradas: +# repo_path(string): Caminho para o repositório Git +# target(string): Ramificação de destino ou commit para comparar com +# Retorna: Saída diferente comparando o estado atual com o alvo +# git_commit +# \n +# Criar nova branch\n +# Entradas: +# repo_path(string): Caminho para o repositório Git +# max_count(número, opcional): Número máximo de confirmações a serem exibidas (padrão: 10) +# Retorna: Matriz de entradas de confirmação com hash, autor, data e mensagem +# git_create_branch +# ---\n + imported_tools = [ + autosave, + autolistlocalproject, + tool_sequentialthinking + ] session = SQLiteSession("agent_session_backend_01", db_path=os.path.join(os.path.dirname(__file__), 'Sessions', f"session_{user_id}.db")) - git_server = _git_server(local_to_save) - sequential_thinking = _sequential_thinking() - agent = Agent( - name="Agent Code BackEnd", - instructions=prompt_system_direct, - model=model, - mcp_servers=[ - sequential_thinking, - git_server - ], - output_type=CodeBackEndAgentOutput, - model_settings=ModelSettings(include_usage=True), - tools=imported_tools - ) - - result = await Runner.run( - agent, - user_content, - max_turns=300, - session=session - ) - final_output = result.final_output - saved_files = final_output.saved_files - - usage = result.context_wrapper.usage - total_usage["input"] = usage.input_tokens - total_usage["cached"] = usage.input_tokens_details.cached_tokens - total_usage["reasoning"] = usage.output_tokens_details.reasoning_tokens - total_usage["output"] = usage.output_tokens - total_usage["total"] = usage.total_tokens + # git_server = await _git_server(local_to_save) + # sequential_thinking = await _sequential_thinking() + async with MCPServerStdio( + name="sequentialthinking", + params={ + "command": "docker", + "args": [ + "run", + "--rm", + "-i", + "mcp/sequentialthinking" + ], + }, + client_session_timeout_seconds=45 + ) as sequential_thinking: + agent = Agent( + name="Agent Code BackEnd", + instructions=prompt_system_direct, + model=model, + # mcp_servers=[ + # sequential_thinking, + # # git_server + # ], + output_type=CodeBackEndAgentOutput, + model_settings=ModelSettings( + tool_choice="sequentialthinking", + include_usage=True + ), + tools=imported_tools + ) + + result = await Runner.run( + agent, + user_content, + max_turns=300, + session=session, + run_config=RunConfig( + model_settings=ModelSettings( + tool_choice="sequentialthinking", + include_usage=True, + parallel_tool_calls=True + ) + ), + ) + final_output = result.final_output + saved_files = final_output.saved_files + + usage = result.context_wrapper.usage + total_usage["input"] = usage.input_tokens + total_usage["cached"] = usage.input_tokens_details.cached_tokens + total_usage["reasoning"] = usage.output_tokens_details.reasoning_tokens + total_usage["output"] = usage.output_tokens + total_usage["total"] = usage.total_tokens - logger.info(f"Agent Final Usage: {total_usage['total']} total tokens.") + logger.info(f"Agent Final Usage: {total_usage['total']} total tokens.") - return total_usage["total"], saved_files + return total_usage["total"], saved_files diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index 2b1bf70bdaee6c2704ad500a2952bc7fc50ced84..6d705f15b8906bf573a5cc42ad57708a3639dfcb 100644 GIT binary patch delta 30 mcmZpWXpq=&fQyY!mw|zaee!>{IA-6U@{{A)wI>I0uLA&uuL%_Z delta 30 mcmZpWXpq=&fQyYsj)8$Gbn<_;IA-4>`N{F@+LMF0*8u>8FbK;4 diff --git a/backend/run_codeagent.py b/backend/run_codeagent.py index 3eb446542..6161ee851 100644 --- a/backend/run_codeagent.py +++ b/backend/run_codeagent.py @@ -23,16 +23,17 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") from Agents.AppAI.CodeBackend.ai import CodeBackEndAgent -title = "" -description = "" -category = "" -price = "" -technologies = "" -early_bonus = "" -deadline = "" +tipo_app = "automação" +title = "Automação de Backup de Arquivos para Nuvem" +description = "Criar uma aplicação em Python que monitore uma pasta local e faça backup automático de arquivos novos ou modificados para um serviço de nuvem (como Google Drive ou Dropbox). O sistema deve gerar logs das transferências, enviar notificações por e-mail em caso de erro, e permitir configuração de periodicidade e pastas monitoradas. Deve ser fácil de instalar e executar em segundo plano." +category = "automação e manutenção de sistemas" +price = "650" +technologies = "python, watchdog, smtplib, google-api-python-client" +early_bonus = "50" +deadline = "2025-10-19 18:12:00" user_id = 1 -tipo_app = "automacao" local_to_save = os.path.join(os.path.dirname(__file__), 'WorkEnv') +model = "gpt-5-nano" total_usage, saved_files = asyncio.run(CodeBackEndAgent( OPENAI_API_KEY, @@ -45,6 +46,7 @@ technologies, early_bonus, deadline, + model=model, local_to_save = local_to_save )) -print(f"saved_files {saved_files}") \ No newline at end of file +print(f"saved_files {saved_files}")