From d15550229340003182eda56512c282f9999f3c40 Mon Sep 17 00:00:00 2001 From: Hector Ros Date: Tue, 20 Jan 2026 02:21:53 +0100 Subject: [PATCH] Session 2026-01-20: Frontend Dashboard + MCP + Agents complete Document completed session: - Frontend with hierarchical navigation + task deletion - MCP HTTP endpoints (5 tools) - Agent with Ubuntu 24.04 + Node 24 + Bun + tooling - Terminal web at claude.fuq.tv - All deployed and functional Prepare next session: Multi-Agent + Terminal Integrated + Auth - Integrate ttyd+tmux into each agent - View agent terminals from app.fuq.tv - MCP authentication by user - Launch/delete agents from UI Co-Authored-By: Claude Sonnet 4.5 (1M context) --- NEXT-SESSION.md | 1045 ++++++++--------- .../2026-01-20-frontend-mcp-agents.md | 237 ++++ 2 files changed, 736 insertions(+), 546 deletions(-) create mode 100644 past-sessions/2026-01-20-frontend-mcp-agents.md diff --git a/NEXT-SESSION.md b/NEXT-SESSION.md index 51c78f7..0d8a787 100644 --- a/NEXT-SESSION.md +++ b/NEXT-SESSION.md @@ -1,401 +1,292 @@ -# 📋 Próxima Sesión - Frontend Dashboard & Primer Agente +# 📋 Próxima Sesión - Multi-Agent + Terminal Integrado + Auth -**Objetivo**: Implementar Frontend Dashboard y desplegar primer agente Claude Code funcional -**Tiempo estimado**: 2-3 horas -**Sesión anterior**: `past-sessions/2026-01-19-backend-api-implementation.md` +**Objetivo**: Integrar terminals web en agentes, autenticación MCP, y gestión multi-agente desde UI +**Tiempo estimado**: 3-4 horas +**Sesión anterior**: `past-sessions/2026-01-20-frontend-mcp-agents.md` --- -## ✅ PRE-REQUISITOS (Verificar antes de empezar) +## 🎯 VISIÓN DE ESTA FASE + +### Lo que tenemos AHORA: +- ✅ Dashboard web en app.fuq.tv +- ✅ Backend con MCP endpoints (públicos) +- ✅ Un agente deployment (claude-agent) +- ✅ Terminal web separado (claude.fuq.tv) - **NO conectado al agente** + +### Lo que queremos LOGRAR: +- 🎯 **Múltiples agentes** por usuario, gestionados desde UI +- 🎯 **Terminal web integrado** EN cada agente (no pod separado) +- 🎯 **Ver terminal** de cualquier agente desde app.fuq.tv (iframe/websocket) +- 🎯 **MCP autenticado**: cada agente usa token del usuario que lo creó +- 🎯 **Agent pool management**: crear, ver, eliminar agentes desde dashboard + +--- + +## ✅ PRE-REQUISITOS ```bash -# 1. Backend funcionando +# 1. Sistema actual funcionando +curl -s https://app.fuq.tv/ | grep "AiWorker" curl -s https://api.fuq.tv/api/health | grep "ok" -# Debe retornar: "status":"ok" -# 2. Base de datos accesible +# 2. Cluster operativo export KUBECONFIG=~/.kube/aiworker-config -kubectl exec -n control-plane mariadb-0 -- mariadb -uaiworker -pAiWorker2026_UserPass! aiworker -e "SELECT COUNT(*) FROM projects;" -# Debe mostrar conteo de proyectos +kubectl get pods -n control-plane +kubectl get pods -n agents -# 3. Cluster operativo -kubectl get nodes -# 6 nodos Ready - -# 4. Registry accesible -curl -s -H "Authorization: token 159a5de2a16d15f33e388b55b1276e431dbca3f3" \ - "https://git.fuq.tv/api/v1/packages/admin?type=container" | grep aiworker-backend -# Debe mostrar paquete +# 3. Auth funcionando +# Prueba: registra usuario, inicia sesión, crea proyecto ``` -**Si algo falla**: Revisar `TROUBLESHOOTING.md` o última sesión en `past-sessions/` - --- -## 🎯 PASO 1: Crear Frontend Base con Bun (45 min) +## 🎯 PASO 1: Agregar Auth a Usuarios (30 min) -### 1.1 Crear estructura de frontend +### 1.1 Extender schema con relación user → agents -```bash -cd /Users/hectorros/projects/teamSquadAiWorker -mkdir -p frontend/{src,public} -cd frontend +**Modificar** `backend/src/db/schema.ts`: -# Inicializar proyecto Bun -bun init -y -``` - -### 1.2 Instalar dependencias - -```bash -# React + TypeScript -bun add react react-dom -bun add -d @types/react @types/react-dom - -# Routing -bun add react-router-dom - -# UI Components (elegir uno) -# Opción A: Shadcn/UI + Tailwind -bun add -d tailwindcss postcss autoprefixer -bunx tailwindcss init -p - -# Opción B: MUI -bun add @mui/material @emotion/react @emotion/styled - -# API Client -bun add axios - -# Auth -bun add lucia -``` - -### 1.3 Crear componentes básicos - -**Estructura necesaria**: -``` -frontend/src/ -├── index.html # HTML entry point -├── main.tsx # React root -├── App.tsx # Main app component -├── components/ -│ ├── Layout.tsx -│ ├── ProjectList.tsx -│ ├── TaskList.tsx -│ └── AgentStatus.tsx -├── api/ -│ └── client.ts # Axios client para backend -└── types/ - └── index.ts # TypeScript types -``` - -### 1.4 Configurar Bun.serve() para frontend - -**Crear** `frontend/server.ts`: ```typescript -import index from './src/index.html' +// Agregar campo userId a agents table +export const agents = mysqlTable('agents', { + id: varchar('id', { length: 36 }).primaryKey(), + userId: varchar('user_id', { length: 36 }).notNull().references(() => users.id, { onDelete: 'cascade' }), -Bun.serve({ - port: 3001, - routes: { - '/': index, - '/api/*': { - GET: async (req) => { - // Proxy to backend - const path = new URL(req.url).pathname.replace(/^\/api/, '') - return fetch(`https://api.fuq.tv/api${path}`) - } - } - }, - development: { - hmr: true, - console: true - } + // ... resto de campos }) + +// Agregar relación +export const usersRelations = relations(users, ({ many }) => ({ + agents: many(agents), + projects: many(projects), +})) + +export const agentsRelations = relations(agents, ({ one }) => ({ + user: one(users, { + fields: [agents.userId], + references: [users.id], + }), + // ... resto +})) ``` -### 1.5 Probar localmente - +Generar migración: ```bash -bun run server.ts -# Open http://localhost:3001 +cd backend +bun run db:generate +# Revisar migración generada +bun run db:migrate ``` -**Debe mostrar**: -- Dashboard básico -- Lista de proyectos (conectado a API) -- Sin auth por ahora (agregar en siguiente paso) - --- -## 🎯 PASO 2: Implementar Autenticación Básica (30 min) +## 🎯 PASO 2: Auth en MCP Endpoints (45 min) -### 2.1 Configurar Lucia Auth +### 2.1 Crear middleware de auth -**Referencia**: https://github.com/lucia-auth/lucia +**Crear** `backend/src/api/middleware/auth.ts`: -**Crear** `frontend/src/lib/auth.ts`: ```typescript -import { Lucia } from "lucia" -import { BunSQLiteAdapter } from "@lucia-auth/adapter-sqlite" +import { db } from '../../db/client' +import { users, sessions } from '../../db/schema' +import { eq } from 'drizzle-orm' -// Por ahora: SQLite local -// Después: Migrar a MariaDB del cluster +export async function authenticateRequest(req: Request): Promise<{ userId: string } | null> { + const sessionId = getSessionIdFromCookie(req) + if (!sessionId) return null -const adapter = new BunSQLiteAdapter(db) + const [session] = await db + .select() + .from(sessions) + .where(eq(sessions.id, sessionId)) + .limit(1) -export const lucia = new Lucia(adapter, { - sessionCookie: { - attributes: { - secure: process.env.NODE_ENV === "production" - } + if (!session || session.expiresAt < new Date()) { + return null } -}) -``` -### 2.2 Crear páginas de auth + return { userId: session.userId } +} -``` -frontend/src/pages/ -├── Login.tsx -├── Register.tsx -└── Dashboard.tsx -``` +function getSessionIdFromCookie(req: Request): string | null { + const cookieHeader = req.headers.get('cookie') + if (!cookieHeader) return null -**Features mínimas**: -- Login con email/password -- Registro de nuevos usuarios -- Logout -- Protected routes (redirect si no autenticado) + const cookies = cookieHeader.split(';').reduce((acc, cookie) => { + const [name, value] = cookie.trim().split('=') + acc[name] = value + return acc + }, {} as Record) -### 2.3 Integrar con backend - -**Backend debe tener** (agregar si no existe): -``` -POST /api/auth/register -POST /api/auth/login -POST /api/auth/logout -GET /api/auth/me -``` - -**Nota**: Si estos endpoints no existen, agregarlos al backend en esta sesión. - ---- - -## 🎯 PASO 3: Build y Deploy de Frontend (30 min) - -### 3.1 Crear Dockerfile para frontend - -**Crear** `frontend/Dockerfile`: -```dockerfile -FROM oven/bun:1.3.6 - -WORKDIR /app - -COPY package.json bun.lockb ./ -RUN bun install --frozen-lockfile - -COPY . . -RUN bun build src/main.tsx --outdir ./dist --target browser - -EXPOSE 3001 - -CMD ["bun", "run", "server.ts"] -``` - -### 3.2 Crear repo en Gitea - -```bash -cd frontend - -# Inicializar git -git init -git add . -git commit -m "Initial frontend implementation - -- React dashboard with project/task views -- Lucia auth integration -- API client for backend -- Bun.serve() with HMR - -Co-Authored-By: Claude Sonnet 4.5 (1M context) " - -# Crear repo en Gitea (via API o UI) -curl -X POST https://git.fuq.tv/api/v1/user/repos \ - -H "Authorization: token 159a5de2a16d15f33e388b55b1276e431dbca3f3" \ - -H "Content-Type: application/json" \ - -d '{"name":"aiworker-frontend","description":"AiWorker Frontend Dashboard","private":false,"auto_init":false}' - -# Agregar remote y push -git remote add origin https://git.fuq.tv/admin/aiworker-frontend.git -git push -u origin main -``` - -### 3.3 Crear workflow de CI/CD - -**Crear** `.gitea/workflows/build.yml`: -```yaml -name: Build and Push Frontend -on: - push: - branches: [main] -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Build Docker image - run: docker build -t git.fuq.tv/admin/aiworker-frontend:${{ gitea.sha }} . - - name: Tag as latest - run: docker tag git.fuq.tv/admin/aiworker-frontend:${{ gitea.sha }} git.fuq.tv/admin/aiworker-frontend:latest - - name: Login to registry - run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.fuq.tv -u admin --password-stdin - - name: Push images - run: | - docker push git.fuq.tv/admin/aiworker-frontend:${{ gitea.sha }} - docker push git.fuq.tv/admin/aiworker-frontend:latest -``` - -### 3.4 Deploy en K8s - -**Crear** `k8s/frontend/`: -``` -k8s/frontend/ -├── deployment.yaml -├── service.yaml -└── ingress.yaml -``` - -**deployment.yaml**: -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: frontend - namespace: control-plane -spec: - replicas: 2 - selector: - matchLabels: - app: frontend - template: - spec: - imagePullSecrets: - - name: gitea-registry - containers: - - name: frontend - image: git.fuq.tv/admin/aiworker-frontend:latest - ports: - - containerPort: 3001 - env: - - name: BACKEND_URL - value: http://backend.control-plane.svc.cluster.local:3000 -``` - -**ingress.yaml**: -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: frontend - namespace: control-plane - annotations: - cert-manager.io/cluster-issuer: letsencrypt-prod -spec: - tls: - - hosts: - - app.fuq.tv - secretName: frontend-tls - rules: - - host: app.fuq.tv - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: frontend - port: - number: 3001 -``` - -**Deploy**: -```bash -kubectl apply -f k8s/frontend/ -kubectl get pods -n control-plane -l app=frontend -kubectl get ingress -n control-plane frontend -``` - -**Verificar**: -```bash -curl -I https://app.fuq.tv -# HTTP/2 200 -``` - ---- - -## 🎯 PASO 4: Primer Agente Claude Code (45 min) - -### 4.1 Entender arquitectura de agentes - -**Referencia**: `docs/05-agents/`, `AGENT-GUIDE.md` - -**Agente Claude Code es**: -- Un pod en K8s namespace `agents` -- Corre `claude-code` CLI conectado vía MCP -- Tiene acceso al código vía Git clone -- Reporta progreso al backend vía MCP tools - -### 4.2 Crear Dockerfile del agente - -**Crear** `agents/Dockerfile`: -```dockerfile -FROM node:20-alpine - -# Instalar claude-code CLI -RUN npm install -g @anthropic/claude-code - -# Instalar herramientas de desarrollo -RUN apk add --no-cache git bash curl - -# Configurar MCP -COPY mcp-config.json /root/.config/claude-code/config.json - -WORKDIR /workspace - -CMD ["claude-code", "--mcp-server", "$BACKEND_MCP_URL"] -``` - -### 4.3 Crear configuración MCP - -**Crear** `agents/mcp-config.json`: -```json -{ - "mcpServers": { - "aiworker": { - "url": "http://backend.control-plane.svc.cluster.local:3100", - "tools": [ - "get_next_task", - "update_task_status", - "create_branch", - "create_pull_request" - ] - } - } + return cookies.session || null } ``` -### 4.4 Crear deployment del agente +### 2.2 Proteger endpoints MCP + +**Modificar** `backend/src/api/routes/mcp.ts`: + +```typescript +import { authenticateRequest } from '../middleware/auth' + +export async function handleMCPRoutes(req: Request, url: URL): Promise { + // Autenticar request + const auth = await authenticateRequest(req) + if (!auth) { + return Response.json( + { success: false, message: 'Unauthorized' }, + { status: 401 } + ) + } + + const userId = auth.userId + // Pasar userId a todos los handlers... +} + +// Modificar get_next_task para filtrar por usuario +async function handleGetNextTask(req: Request, userId: string): Promise { + // ... + // Filtrar tareas de proyectos del usuario + const userProjects = await db.select().from(projects).where(eq(projects.userId, userId)) + const projectIds = userProjects.map(p => p.id) + + const [task] = await db + .select() + .from(tasks) + .where(and( + eq(tasks.state, 'backlog'), + inArray(tasks.projectId, projectIds) + )) + .orderBy(tasks.priority, tasks.createdAt) + .limit(1) + // ... +} +``` + +--- + +## 🎯 PASO 3: Integrar Terminal en Imagen del Agente (1 hora) + +### 3.1 Modificar Dockerfile del agente + +**Modificar** `agents/Dockerfile`: + +```dockerfile +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# Install system dependencies + ttyd + tmux +RUN apt-get update && apt-get install -y \ + curl wget git bash unzip \ + ca-certificates openssh-client \ + python3 python3-pip python3-venv \ + build-essential jq \ + tmux \ + && rm -rf /var/lib/apt/lists/* + +# Install ttyd (terminal web) +RUN curl -L https://github.com/tsl0922/ttyd/releases/download/1.7.4/ttyd.x86_64 -o /usr/local/bin/ttyd \ + && chmod +x /usr/local/bin/ttyd + +# Install Node.js 24.x +RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \ + && apt-get install -y nodejs \ + && rm -rf /var/lib/apt/lists/* + +# Install Bun +RUN curl -fsSL https://bun.sh/install | bash +ENV PATH="/root/.bun/bin:$PATH" + +# Install kubectl +RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \ + && chmod +x kubectl \ + && mv kubectl /usr/local/bin/ + +# Install Claude Code CLI +RUN bun install -g @anthropic-ai/claude-code + +# Create workspace +WORKDIR /workspace + +# Entrypoint script +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +EXPOSE 7681 + +CMD ["/entrypoint.sh"] +``` + +### 3.2 Crear entrypoint script + +**Crear** `agents/entrypoint.sh`: + +```bash +#!/bin/bash +set -e + +# Configure git +git config --global user.name "Claude Agent" +git config --global user.email "agent@aiworker.local" + +# Start tmux session +tmux new-session -d -s claude 'bash -c "cd /workspace && exec bash"' + +# Start ttyd to expose tmux over HTTP on port 7681 +exec ttyd -p 7681 -W tmux attach -t claude +``` + +### 3.3 Build y push + +```bash +cd agents +git add Dockerfile entrypoint.sh +git commit -m "Integrate ttyd+tmux into agent image + +- Add ttyd for web terminal access +- Add tmux for persistent sessions +- Expose port 7681 for terminal web +- Entrypoint starts tmux + ttyd automatically + +Co-Authored-By: Claude Sonnet 4.5 (1M context) " +git push + +# Esperar build en Gitea Actions +``` + +--- + +## 🎯 PASO 4: StatefulSet para Agentes (45 min) + +### 4.1 Cambiar de Deployment a StatefulSet + +**Crear** `k8s/agents/statefulset.yaml`: -**Crear** `k8s/agents/agent-deployment.yaml`: ```yaml -apiVersion: apps/v1 -kind: Deployment +apiVersion: v1 +kind: Service metadata: name: claude-agent namespace: agents spec: - replicas: 1 # Empezar con 1 agente + clusterIP: None # Headless service + selector: + app: claude-agent + ports: + - name: terminal + port: 7681 + targetPort: 7681 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: claude-agent + namespace: agents +spec: + serviceName: claude-agent + replicas: 0 # Se crearán dinámicamente desde API selector: matchLabels: app: claude-agent @@ -404,231 +295,291 @@ spec: labels: app: claude-agent spec: + serviceAccountName: agent-sa + imagePullSecrets: + - name: gitea-registry containers: - name: agent image: git.fuq.tv/admin/aiworker-agent:latest + imagePullPolicy: Always + ports: + - containerPort: 7681 + name: terminal env: - - name: ANTHROPIC_API_KEY - valueFrom: - secretKeyRef: - name: agent-secrets - key: anthropic-api-key - - name: BACKEND_MCP_URL - value: http://backend.control-plane.svc.cluster.local:3100 + - name: BACKEND_URL + value: "https://api.fuq.tv" + - name: MCP_ENDPOINT + value: "https://api.fuq.tv/api/mcp" - name: GITEA_URL - value: https://git.fuq.tv + value: "https://git.fuq.tv" - name: GITEA_TOKEN valueFrom: secretKeyRef: name: agent-secrets key: gitea-token - - name: AGENT_ID + - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name -``` - -### 4.5 Crear secret para agente - -```bash -kubectl create secret generic agent-secrets -n agents \ - --from-literal=anthropic-api-key='' \ - --from-literal=gitea-token='159a5de2a16d15f33e388b55b1276e431dbca3f3' -``` - -### 4.6 Desplegar agente - -```bash -# Crear namespace -kubectl create namespace agents - -# Agregar registry secret -kubectl create secret docker-registry gitea-registry -n agents \ - --docker-server=git.fuq.tv \ - --docker-username=admin \ - --docker-password=7401126cfb56ab2aebba17755bdc968c20768c27 - -# Deploy -kubectl apply -f k8s/agents/ -kubectl get pods -n agents -kubectl logs -f -n agents deployment/claude-agent -``` - -### 4.7 Verificar agente registrado - -```bash -# El agente debe auto-registrarse al iniciar -curl -s https://api.fuq.tv/api/agents | jq -# Debe mostrar agente con status "idle" + - name: USER_ID + value: "" # Se inyecta dinámicamente al crear el pod + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 2000m + memory: 4Gi + volumeMounts: + - name: workspace + mountPath: /workspace + volumeClaimTemplates: + - metadata: + name: workspace + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: longhorn + resources: + requests: + storage: 10Gi ``` --- -## 🎯 PASO 5: Test End-to-End del Flujo Completo (30 min) +## 🎯 PASO 5: Backend API para Gestión de Agentes (1 hora) -### 5.1 Crear tarea de prueba +### 5.1 Endpoints de agentes autenticados -```bash -# 1. Crear proyecto -PROJECT_ID=$(curl -s -X POST https://api.fuq.tv/api/projects \ - -H "Content-Type: application/json" \ - -d '{ - "name": "test-agent-flow", - "description": "Test de flujo completo con agente", - "giteaOwner": "admin", - "giteaRepoName": "test-agent-flow" - }' | jq -r '.data.id') +**Modificar** `backend/src/api/routes/agents.ts`: -echo "Project ID: $PROJECT_ID" - -# 2. Crear tarea -TASK_ID=$(curl -s -X POST https://api.fuq.tv/api/tasks \ - -H "Content-Type: application/json" \ - -d "{ - \"projectId\": \"$PROJECT_ID\", - \"title\": \"Add hello world endpoint\", - \"description\": \"Create a simple GET /hello endpoint that returns 'Hello World'\", - \"priority\": \"high\" - }" | jq -r '.data.id') - -echo "Task ID: $TASK_ID" -``` - -### 5.2 Monitorear logs del agente - -```bash -# Terminal 1: Logs del agente -kubectl logs -f -n agents deployment/claude-agent - -# Terminal 2: Ver estado de la tarea -watch -n 2 "curl -s https://api.fuq.tv/api/tasks/$TASK_ID | jq '.data.state'" -``` - -### 5.3 Verificar flujo completo - -**Debe ocurrir**: -1. Agente obtiene tarea con `get_next_task` -2. Estado cambia: `backlog` → `in_progress` -3. Agente crea branch con `create_branch` -4. Agente implementa código -5. Agente crea PR con `create_pull_request` -6. Estado cambia: `in_progress` → `ready_to_test` -7. PR visible en Gitea - -**Verificar PR**: -```bash -# Ver PRs en Gitea -curl -s -H "Authorization: token 159a5de2a16d15f33e388b55b1276e431dbca3f3" \ - "https://git.fuq.tv/api/v1/repos/admin/test-agent-flow/pulls" | jq -``` - -### 5.4 Revisar dashboard - -```bash -# Abrir dashboard -open https://app.fuq.tv - -# Debe mostrar: -# - Proyecto "test-agent-flow" -# - Tarea "Add hello world endpoint" con estado "ready_to_test" -# - PR link visible -# - Agente con status "idle" (completó tarea) -``` - ---- - -## 🎯 PASO 6: Webhooks de Gitea (OPCIONAL, 15 min) - -Si hay tiempo, configurar webhooks para notificaciones. - -### 6.1 Crear endpoint de webhook en backend - -**Agregar** a `backend/src/api/routes/webhooks.ts`: ```typescript -export async function handleGiteaWebhook(req: Request) { - const event = req.headers.get('X-Gitea-Event') - const payload = await req.json() +// POST /api/agents/launch - Lanzar nuevo agente +async function handleLaunchAgent(req: Request, userId: string): Promise { + const agentId = randomUUID() + const podName = `claude-agent-${userId.slice(0, 8)}-${Date.now()}` - switch (event) { - case 'pull_request': - // Notificar frontend vía WebSocket - break - case 'push': - // Trigger preview deploy - break + // 1. Crear registro en DB + await db.insert(agents).values({ + id: agentId, + userId, + podName, + k8sNamespace: 'agents', + status: 'idle', + }) + + // 2. Crear pod en K8s usando K8s API + const k8sConfig = KubeConfig.fromFile('~/.kube/aiworker-config') + const k8sApi = k8sConfig.makeApiClient(AppsV1Api) + + // Escalar StatefulSet o crear Pod individual + await k8sApi.createNamespacedPod('agents', { + metadata: { + name: podName, + labels: { + app: 'claude-agent', + userId: userId, + agentId: agentId, + }, + }, + spec: { + // ... spec del pod con USER_ID env var + }, + }) + + return Response.json({ + success: true, + data: { agentId, podName }, + }) +} + +// DELETE /api/agents/:id - Eliminar agente +async function handleDeleteAgent(agentId: string, userId: string): Promise { + const [agent] = await db + .select() + .from(agents) + .where(and(eq(agents.id, agentId), eq(agents.userId, userId))) + .limit(1) + + if (!agent) { + return Response.json({ success: false, message: 'Agent not found' }, { status: 404 }) } + + // Eliminar pod de K8s + await k8sApi.deleteNamespacedPod(agent.podName, 'agents') + + // Eliminar de DB + await db.delete(agents).where(eq(agents.id, agentId)) + + return Response.json({ success: true }) +} + +// GET /api/agents/my - Listar mis agentes +async function handleMyAgents(userId: string): Promise { + const myAgents = await db + .select() + .from(agents) + .where(eq(agents.userId, userId)) + + return Response.json({ + success: true, + data: myAgents, + }) } ``` -### 6.2 Configurar webhook en Gitea +--- + +## 🎯 PASO 6: Frontend - Ver Agentes y Terminals (1 hora) + +### 6.1 Agregar botón "Launch Agent" + +**Modificar** `frontend/src/pages/Dashboard.tsx`: + +```typescript +const [showAgentLauncher, setShowAgentLauncher] = useState(false) + +// Botón en header + +``` + +### 6.2 Crear AgentTerminalModal + +**Crear** `frontend/src/components/AgentTerminalModal.tsx`: + +```typescript +interface AgentTerminalModalProps { + agent: Agent + isOpen: boolean + onClose: () => void +} + +export default function AgentTerminalModal({ agent, isOpen, onClose }: AgentTerminalModalProps) { + if (!isOpen) return null + + // URL del terminal: http://claude-agent-{podName}.agents.svc.cluster.local:7681 + const terminalUrl = `/agent-terminal/${agent.id}` + + return ( +
+
+
+

Agent Terminal - {agent.podName}

+ +
+
+