From ad034f725b2cfeb2229954fb3fde872c060891ef Mon Sep 17 00:00:00 2001 From: devshift-stack Date: Wed, 31 Dec 2025 23:29:38 +0100 Subject: [PATCH 1/2] feat: Add Ops Dashboard API endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: - GET /api/ops/events - Unified event stream from tasks & audit - GET /api/ops/tasks/history - Task history with duration & audit data - GET /api/ops/stats - Aggregated statistics (Admin only) Features: - Filter events by since, limit, agentId, type - Filter task history by status, assignee, includeAudit - Aggregate stats per agent with completion/stop rates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/api/ops.ts | 321 +++++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 7 ++ 2 files changed, 328 insertions(+) create mode 100644 src/api/ops.ts diff --git a/src/api/ops.ts b/src/api/ops.ts new file mode 100644 index 0000000..ac02537 --- /dev/null +++ b/src/api/ops.ts @@ -0,0 +1,321 @@ +/** + * Ops Router - Operations Dashboard API + * + * Provides unified event stream and task history for the Ops Dashboard. + * Aggregates data from tasks, audit entries, and agent activity. + */ + +import { Router } from "express"; +import type { Database, AuditEntry } from "../db/database.js"; +import { requireAuth, requireAdmin } from "../auth/middleware.js"; + +/** + * Unified event type for the ops dashboard + */ +export interface OpsEvent { + id: string; + type: "task_created" | "task_started" | "task_completed" | "task_stopped" | "audit_decision" | "agent_activity"; + timestamp: string; + agentId?: string; + taskId?: string; + title?: string; + status?: string; + stopScore?: number; + riskLevel?: string; + decision?: string; + metadata?: Record; +} + +/** + * Task history entry with enriched data + */ +export interface TaskHistoryEntry { + id: string; + title: string; + description?: string; + status: string; + priority: string; + assignee: string; + stopScore?: number; + createdAt: string; + updatedAt?: string; + duration?: number; // ms between created and updated + auditEntries?: AuditEntry[]; +} + +export function createOpsRouter(db: Database): Router { + const router = Router(); + + /** + * GET /api/ops/events + * + * Returns a unified stream of events from multiple sources. + * + * Query params: + * - since: ISO timestamp, only events after this time (default: 24h ago) + * - limit: max events to return (default: 100, max: 500) + * - agentId: filter by agent/assignee + * - type: filter by event type (task_created, task_completed, audit_decision, etc.) + * + * Response: { events: OpsEvent[], count: number, since: string } + */ + router.get("/events", requireAuth, (req, res) => { + try { + const limit = Math.min(parseInt(req.query.limit as string) || 100, 500); + const agentId = req.query.agentId as string | undefined; + const eventType = req.query.type as string | undefined; + + // Default: last 24 hours + const defaultSince = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); + const since = (req.query.since as string) || defaultSince; + + const events: OpsEvent[] = []; + + // 1. Collect events from tasks + const tasks = db.listTasks({ limit: limit * 2 }); // Get more, will filter + + for (const task of tasks) { + // Filter by time + if (task.created_at < since) continue; + + // Filter by agent + if (agentId && task.assignee !== agentId) continue; + + // Task created event + if (!eventType || eventType === "task_created") { + events.push({ + id: `task-created-${task.id}`, + type: "task_created", + timestamp: task.created_at, + taskId: task.id, + agentId: task.assignee, + title: task.title, + status: task.status, + metadata: { priority: task.priority }, + }); + } + + // Task completed/stopped event (if updated) + if (task.updated_at && task.updated_at >= since) { + if (task.status === "completed" && (!eventType || eventType === "task_completed")) { + events.push({ + id: `task-completed-${task.id}`, + type: "task_completed", + timestamp: task.updated_at, + taskId: task.id, + agentId: task.assignee, + title: task.title, + status: task.status, + stopScore: task.stop_score, + }); + } else if (task.status === "stopped" && (!eventType || eventType === "task_stopped")) { + events.push({ + id: `task-stopped-${task.id}`, + type: "task_stopped", + timestamp: task.updated_at, + taskId: task.id, + agentId: task.assignee, + title: task.title, + status: task.status, + stopScore: task.stop_score, + }); + } + } + } + + // 2. Collect events from audit entries + if (!eventType || eventType === "audit_decision") { + const auditEntries = db.listAuditEntries(limit * 2); + + for (const entry of auditEntries) { + // Filter by time + if (entry.created_at < since) continue; + + // Filter by agent (if audit has agent info) + if (agentId && entry.agent && entry.agent !== agentId) continue; + + events.push({ + id: `audit-${entry.id}`, + type: "audit_decision", + timestamp: entry.created_at, + taskId: entry.task_id, + agentId: entry.agent, + decision: entry.decision, + riskLevel: entry.risk_level, + stopScore: entry.stop_score, + metadata: { + finalStatus: entry.final_status, + requiredNextAction: entry.required_next_action, + }, + }); + } + } + + // Sort by timestamp (newest first) + events.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + + // Apply limit + const limitedEvents = events.slice(0, limit); + + res.json({ + success: true, + events: limitedEvents, + count: limitedEvents.length, + total: events.length, + since, + filters: { + agentId: agentId ?? null, + type: eventType ?? null, + limit, + }, + }); + } catch (error) { + console.error("[Ops] Failed to get events:", error); + res.status(500).json({ error: "Internal server error" }); + } + }); + + /** + * GET /api/ops/tasks/history + * + * Returns task history with enriched data (duration, audit entries). + * + * Query params: + * - limit: max tasks to return (default: 50, max: 200) + * - status: filter by status (pending, in_progress, completed, stopped) + * - assignee: filter by assignee/agent + * - includeAudit: include related audit entries (default: false) + * + * Response: { tasks: TaskHistoryEntry[], count: number } + */ + router.get("/tasks/history", requireAuth, (req, res) => { + try { + const limit = Math.min(parseInt(req.query.limit as string) || 50, 200); + const status = req.query.status as string | undefined; + const assignee = req.query.assignee as string | undefined; + const includeAudit = req.query.includeAudit === "true"; + + // Get tasks with optional status filter + let tasks = db.listTasks({ state: status, limit: limit * 2 }); + + // Filter by assignee if specified + if (assignee) { + tasks = tasks.filter((t) => t.assignee === assignee); + } + + // Limit results + tasks = tasks.slice(0, limit); + + // Get audit entries if requested + let auditEntriesMap: Map | null = null; + if (includeAudit) { + const allAuditEntries = db.listAuditEntries(500); + auditEntriesMap = new Map(); + + for (const entry of allAuditEntries) { + if (entry.task_id) { + if (!auditEntriesMap.has(entry.task_id)) { + auditEntriesMap.set(entry.task_id, []); + } + auditEntriesMap.get(entry.task_id)!.push(entry); + } + } + } + + // Build response + const history: TaskHistoryEntry[] = tasks.map((task) => { + const entry: TaskHistoryEntry = { + id: task.id, + title: task.title, + description: task.description, + status: task.status, + priority: task.priority, + assignee: task.assignee, + stopScore: task.stop_score, + createdAt: task.created_at, + updatedAt: task.updated_at, + }; + + // Calculate duration if task has been updated + if (task.updated_at) { + const created = new Date(task.created_at).getTime(); + const updated = new Date(task.updated_at).getTime(); + entry.duration = updated - created; + } + + // Add audit entries if requested + if (includeAudit && auditEntriesMap) { + entry.auditEntries = auditEntriesMap.get(task.id) ?? []; + } + + return entry; + }); + + res.json({ + success: true, + tasks: history, + count: history.length, + filters: { + status: status ?? null, + assignee: assignee ?? null, + includeAudit, + limit, + }, + }); + } catch (error) { + console.error("[Ops] Failed to get task history:", error); + res.status(500).json({ error: "Internal server error" }); + } + }); + + /** + * GET /api/ops/stats + * + * Returns aggregated statistics for the ops dashboard. + * Admin only. + * + * Response: { tasks: {...}, audit: {...}, agents: {...} } + */ + router.get("/stats", requireAdmin, (_req, res) => { + try { + // Task stats + const allTasks = db.listTasks({ limit: 1000 }); + const taskStats = { + total: allTasks.length, + pending: allTasks.filter((t) => t.status === "pending").length, + inProgress: allTasks.filter((t) => t.status === "in_progress").length, + completed: allTasks.filter((t) => t.status === "completed").length, + stopped: allTasks.filter((t) => t.status === "stopped").length, + }; + + // Audit/STOP score stats + const stopScoreStats = db.getStopScoreStats(); + + // Agent activity (group by assignee) + const agentActivity: Record = {}; + for (const task of allTasks) { + if (!agentActivity[task.assignee]) { + agentActivity[task.assignee] = { total: 0, completed: 0, stopped: 0 }; + } + agentActivity[task.assignee].total++; + if (task.status === "completed") agentActivity[task.assignee].completed++; + if (task.status === "stopped") agentActivity[task.assignee].stopped++; + } + + res.json({ + success: true, + stats: { + tasks: taskStats, + stopScores: stopScoreStats, + agents: agentActivity, + }, + timestamp: new Date().toISOString(), + }); + } catch (error) { + console.error("[Ops] Failed to get stats:", error); + res.status(500).json({ error: "Internal server error" }); + } + }); + + return router; +} diff --git a/src/index.ts b/src/index.ts index 53230f4..61915ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,6 +41,7 @@ import { handleSlackEvents } from "./api/slack-events.js"; import { ChatStorage } from "./chat/storage.js"; import { ChatManager } from "./chat/manager.js"; import { createAgentTasksRouter } from "./api/agent-tasks.js"; +import { createOpsRouter } from "./api/ops.js"; import { initializeLiveUpdates } from "./agents/live-updates.js"; import { agentWorker } from "./agents/agent-worker.js"; import { WebSocketManager } from "./websocket/server.js"; @@ -167,6 +168,7 @@ async function main() { app.use("/api/chat", createChatRouter(chatManager)); app.use("/api/brain", createBrainRouter(db)); app.use("/api/agent-tasks", createAgentTasksRouter()); + app.use("/api/ops", createOpsRouter(db)); // Slack Events (Mujo Interactive Bot) app.post("/api/slack/events", handleSlackEvents); @@ -299,6 +301,11 @@ async function main() { console.log(" GET /api/brain/docs - List documents"); console.log(" GET /api/brain/stats - Statistics"); console.log(""); + console.log("📊 Ops Dashboard:"); + console.log(" GET /api/ops/events - Unified event stream"); + console.log(" GET /api/ops/tasks/history - Task history with details"); + console.log(" GET /api/ops/stats - Aggregated statistics (Admin)"); + console.log(""); console.log("🔌 WebSocket Real-time:"); console.log(" WS ws://localhost:" + PORT + "/ws?token=YOUR_TOKEN"); console.log(" Messages: agent_status, chat_message, notification, user_presence"); From e0df026acda1c74d8f8a3b941ec820667e980ebd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 31 Dec 2025 22:31:37 +0000 Subject: [PATCH 2/2] style: auto-fix linting and formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Auto-fixed by GitHub Actions --- .claude/commands/agent-status.md | 3 + .claude/commands/brain.md | 4 + .claude/commands/design.md | 5 + .claude/commands/docs.md | 16 +- .claude/commands/fix.md | 12 +- .claude/commands/implement.md | 5 + .claude/commands/review.md | 4 + .claude/commands/security.md | 5 + .claude/commands/test.md | 4 + .claude/commands/workflow.md | 17 +- AGENTS.md | 1 + AGENT_0_CODE_REVIEW_REPORT_2025-12-26.md | 46 +- AGENT_0_DEPENDENCY_FIX_REPORT_2025-12-26.md | 193 +- AGENT_3_ABSCHLUSSBERICHT.md | 76 +- AGENT_3_ZWISCHENBERICHT.md | 41 + AGENT_4_ABSCHLUSSBERICHT.md | 59 +- AGENT_4_ZWISCHENBERICHT.md | 81 +- AGENT_GIT_WORKFLOW.md | 10 + CLAUDE.md | 75 +- DEPLOYMENT_ABSCHLUSSBERICHT.md | 65 + DEPLOYMENT_GIT_SETUP.md | 6 + DEPLOYMENT_REPORT_2025-12-26.md | 75 +- INTEGRATIONS_SETUP.md | 73 +- MUJO_INTERACTIVE_BOT_SETUP.md | 134 +- README.md | 21 + SETUP_ISSUES.md | 35 +- TASKS.md | 26 +- agents/agent_training_prompts.md | 1 - agents/supervisor_dashboard_mock.json | 2 +- docs/AGENT_SETUP.md | 57 +- docs/API.md | 52 +- docs/API_CURL_EXAMPLES.md | 21 +- docs/ARCHITECTURE.md | 113 +- docs/CONTRIBUTING.md | 70 +- docs/CRM_WORKFLOW.md | 64 +- docs/DESIGN_SPECIFICATION.md | 247 +- docs/DEVELOPER_GUIDE.md | 54 +- docs/FRONTEND_IMPLEMENTATION.md | 430 +-- docs/IMPLEMENTATION_GUIDE.md | 280 +- docs/MEMORY.md | 125 +- docs/POSTMAN.md | 62 +- docs/POSTMAN_GUIDE.md | 72 +- docs/SETTINGS.md | 87 +- docs/WEBSOCKET.md | 122 +- docs/api-client-setup.md | 44 +- docs/architecture/decision-flow.md | 4 + docs/architecture/system-overview.md | 44 +- docs/otop-standard.md | 25 +- docs/otop/blueprints/ui-blueprint.md | 28 + docs/retrofit-guide.md | 25 +- docs/runbooks/live-runbook.md | 19 +- docs/runbooks/production-launch-checklist.md | 1 + .../engineering_lead_supervisor.system.md | 2 + docs/supervisor/meta_supervisor.system.md | 1 + e2e/smoke.spec.ts | 22 +- electron/main.js | 86 +- hetzner.config.js | 18 +- otop.audit.env.md | 342 +-- otop.audit.uiids.md | 22 +- playwright.config.ts | 14 +- postcss.config.js | 2 +- scripts/brain-cli.ts | 32 +- send-access-email.js | 28 +- src/App.tsx | 324 +-- src/Attributions.md | 2 +- src/agents/agent-worker.ts | 252 +- src/agents/live-updates.ts | 114 +- src/agents/task-queue.ts | 171 +- src/api/agent-control.ts | 245 +- src/api/agent-tasks.ts | 52 +- src/api/auth.ts | 127 +- src/api/billing.ts | 448 ++-- src/api/brain.ts | 19 +- src/api/chat.ts | 138 +- src/api/demo.ts | 215 +- src/api/email-verification.ts | 137 +- src/api/enforcement.ts | 21 +- src/api/github.ts | 3 +- src/api/linear.ts | 7 +- src/api/memory.ts | 14 +- src/api/modules.ts | 8 +- src/api/ops.ts | 35 +- src/api/password-reset.ts | 110 +- src/api/settings.ts | 242 +- src/api/slack-events.ts | 10 +- src/api/tasks.ts | 58 +- src/api/users.ts | 382 +-- src/api/webhooks.ts | 8 +- src/audit/claimVerifier.ts | 34 +- src/audit/enforcementGate.ts | 8 +- src/auth/jwt.ts | 14 +- src/auth/middleware.ts | 70 +- src/auth/rate-limiter.ts | 41 +- src/billing/costTracker.ts | 38 +- src/billing/index.ts | 13 +- src/billing/modelSelector.ts | 25 +- src/billing/pricing.ts | 5 +- src/brain/index.ts | 7 +- src/brain/manager.ts | 41 +- src/brain/search.ts | 80 +- src/chat/manager.ts | 202 +- src/chat/storage.ts | 32 +- src/chat/tools.ts | 45 +- src/components/ActivityLog.tsx | 24 +- src/components/AgentCard.tsx | 44 +- src/components/BrainMemoryPage.tsx | 75 +- src/components/ChatInterface.tsx | 136 +- src/components/CreateAgentDialog.tsx | 62 +- src/components/DemoDashboard.tsx | 110 +- src/components/DemoPage.tsx | 14 +- src/components/DemoRegistration.tsx | 109 +- src/components/LoginPage.tsx | 41 +- src/components/SettingsPanel.tsx | 128 +- src/components/StatsCard.tsx | 29 +- src/components/StatusDashboard.tsx | 42 +- src/components/TaskBoard.tsx | 220 +- src/components/TeamAgentCard.tsx | 16 +- src/components/TeamAgentList.tsx | 46 +- src/components/figma/ImageWithFallback.tsx | 36 +- src/components/ui/button.tsx | 2 +- src/components/ui/progress.tsx | 9 +- src/components/ui/select.tsx | 6 +- src/db/audit-events.ts | 50 +- src/db/database.ts | 73 +- src/db/email-verification.ts | 14 +- src/db/password-reset.ts | 10 +- src/db/settings.ts | 37 +- src/db/users.ts | 46 +- src/demo/inviteManager.ts | 159 +- src/email/mailer.ts | 37 +- src/generated/api-types.ts | 2308 ++++++++--------- src/index.css | 737 ++++-- src/index.ts | 162 +- src/integrations/github/README.md | 17 +- src/integrations/github/client.ts | 46 +- src/integrations/github/example.ts | 19 +- src/integrations/google/client.ts | 31 +- src/integrations/icloud/client.ts | 39 +- src/integrations/index.ts | 40 +- src/integrations/linear/README.md | 35 +- src/integrations/linear/client.ts | 87 +- src/integrations/linear/example.ts | 4 +- src/integrations/pinecone/client.ts | 25 +- src/integrations/slack/MUJO_HUMOR.md | 57 +- src/integrations/slack/README.md | 23 +- src/integrations/slack/bot-responses.ts | 10 +- src/integrations/slack/client.ts | 67 +- src/integrations/slack/example.ts | 4 +- src/integrations/slack/humor.ts | 53 +- src/integrations/voice/client.ts | 19 +- src/integrations/webhooks/example.ts | 10 +- src/integrations/webhooks/manager.ts | 54 +- src/integrations/webhooks/security.ts | 12 +- src/integrations/whatsapp/client.ts | 11 +- src/lib/api-client.ts | 16 +- src/lib/api-example.tsx | 44 +- src/lib/events.ts | 30 +- src/main.tsx | 6 +- src/memory/embeddings.ts | 29 +- src/memory/manager.ts | 30 +- src/memory/search.ts | 53 +- src/meta/metaSupervisor.ts | 16 +- src/modules/moduleStatus.ts | 9 +- src/monitoring/sentry.ts | 94 +- src/queue/queue.ts | 18 +- src/queue/retry.ts | 28 +- src/queue/workers/github.ts | 26 +- src/queue/workers/index.ts | 5 +- src/queue/workers/linear.ts | 12 +- src/shared/types/agent.ts | 7 +- src/shared/types/common.ts | 14 +- src/shared/types/task.ts | 8 +- src/styles/globals.css | 2 +- src/supervisor/README.md | 75 +- src/supervisor/engineeringLead.ts | 20 +- src/supervisor/example.ts | 28 +- src/supervisor/index.ts | 34 +- src/supervisor/notifications.ts | 26 +- src/swagger/index.ts | 6 +- src/types/agent.ts | 2 +- src/webhooks/github.ts | 60 +- src/webhooks/linear.ts | 48 +- src/websocket/client-example.ts | 7 +- src/websocket/server.ts | 22 +- tailwind.config.js | 7 +- test-integrations.js | 11 +- test-mujo-humor.js | 2 +- test-mujo-paris.js | 8 +- test-mujo-top5.js | 8 +- test-results/.last-run.json | 2 +- test-slack-message.js | 20 +- test-supervisor-slack.js | 8 +- tests/agent-control.test.ts | 95 +- tests/auth-improvements.test.ts | 42 +- tests/auth-middleware.test.ts | 4 +- tests/billing-api.test.ts | 53 +- tests/brain-manager.test.ts | 96 +- tests/chat-provider.test.ts | 57 +- tests/enforcementGate.test.ts | 5 +- tests/engineering-lead.test.ts | 10 +- tests/github-api.test.ts | 52 +- tests/jwt-auth.test.ts | 14 +- tests/linear-api.test.ts | 58 +- tests/memory-search.test.ts | 32 +- tests/meta-supervisor.test.ts | 9 +- tests/rate-limiter.test.ts | 38 +- tests/scorecards/scorecard_01.json | 2 +- tests/scorecards/scorecard_02.json | 2 +- tests/scorecards/scorecard_03.json | 2 +- tests/scorecards/scorecard_04.json | 2 +- tests/scorecards/scorecard_05.json | 2 +- tests/scorecards/scorecard_06.json | 2 +- tests/scorecards/scorecard_07.json | 2 +- tests/scorecards/scorecard_08.json | 2 +- tests/scorecards/scorecard_09.json | 2 +- tests/scorecards/scorecard_10.json | 2 +- tests/stopScorer.test.ts | 6 +- tests/tasks-api.test.ts | 18 +- tests/user-management.test.ts | 38 +- tests/webhook-workers.test.ts | 7 +- vite.config.ts | 116 +- 221 files changed, 9179 insertions(+), 5384 deletions(-) diff --git a/.claude/commands/agent-status.md b/.claude/commands/agent-status.md index f59b473..3442665 100644 --- a/.claude/commands/agent-status.md +++ b/.claude/commands/agent-status.md @@ -10,16 +10,19 @@ Zeige Status für: $ARGUMENTS (oder alle Agents) ## Prüfungen ### 1. Agent-Konfiguration + - Supervisor-Hierarchie intakt? - STOP-Score Berechnung aktiv? - Evidence-Logging aktiviert? ### 2. Aktive Sessions + - Laufende Aufträge - Pending Approvals - STOP_REQUIRED Events ### 3. Performance + - Durchschnittlicher STOP-Score - Erfolgsrate - Letzte Aktivität diff --git a/.claude/commands/brain.md b/.claude/commands/brain.md index e6a1fe6..443f051 100644 --- a/.claude/commands/brain.md +++ b/.claude/commands/brain.md @@ -5,12 +5,14 @@ Nutze die Brain API für Wissensmanagement. ## Verfügbare Aktionen: ### Suchen + ```bash curl -s -H "Authorization: Bearer $TOKEN" \ "http://localhost:3000/api/brain/search?q=QUERY" ``` ### Text hinzufügen + ```bash curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ @@ -19,12 +21,14 @@ curl -s -X POST -H "Authorization: Bearer $TOKEN" \ ``` ### Dokumente auflisten + ```bash curl -s -H "Authorization: Bearer $TOKEN" \ "http://localhost:3000/api/brain/docs" ``` ### Statistiken + ```bash curl -s -H "Authorization: Bearer $TOKEN" \ "http://localhost:3000/api/brain/stats" diff --git a/.claude/commands/design.md b/.claude/commands/design.md index c69e4f5..8e88bec 100644 --- a/.claude/commands/design.md +++ b/.claude/commands/design.md @@ -10,6 +10,7 @@ Führe ein UI/UX Review durch für: $ARGUMENTS ## Prüfungen ### 1. Visual Design + - [ ] Konsistente Farbpalette - [ ] Typography einheitlich - [ ] Spacing/Padding konsistent @@ -17,6 +18,7 @@ Führe ein UI/UX Review durch für: $ARGUMENTS - [ ] Bilder optimiert ### 2. User Experience + - [ ] Intuitive Navigation - [ ] Klare Call-to-Actions - [ ] Feedback bei Aktionen (Loading, Success, Error) @@ -24,6 +26,7 @@ Führe ein UI/UX Review durch für: $ARGUMENTS - [ ] Error Messages verständlich ### 3. Accessibility (A11y) + - [ ] Kontrast ausreichend (WCAG 2.1) - [ ] Alt-Texte für Bilder - [ ] Keyboard Navigation @@ -31,12 +34,14 @@ Führe ein UI/UX Review durch für: $ARGUMENTS - [ ] Focus States sichtbar ### 4. Responsive Design + - [ ] Mobile (< 640px) - [ ] Tablet (640px - 1024px) - [ ] Desktop (> 1024px) - [ ] Touch-friendly auf Mobile ### 5. Performance UX + - [ ] Ladezeit < 3 Sekunden - [ ] Skeleton Loaders - [ ] Lazy Loading für Bilder diff --git a/.claude/commands/docs.md b/.claude/commands/docs.md index 2a8432b..5841c3e 100644 --- a/.claude/commands/docs.md +++ b/.claude/commands/docs.md @@ -10,6 +10,7 @@ Erstelle/Aktualisiere Dokumentation für: $ARGUMENTS ## Dokumentations-Typen ### 1. README.md + - Projekt-Beschreibung - Quick Start Guide - Installation @@ -17,6 +18,7 @@ Erstelle/Aktualisiere Dokumentation für: $ARGUMENTS - Contributing Guidelines ### 2. API Documentation + - Endpoints Liste - Request/Response Beispiele - Authentication @@ -24,11 +26,13 @@ Erstelle/Aktualisiere Dokumentation für: $ARGUMENTS - Rate Limits ### 3. Code Documentation + - JSDoc für alle Funktionen - Inline Kommentare für komplexe Logik - Type Definitions ### 4. User Guide + - Getting Started - Features Übersicht - Step-by-Step Tutorials @@ -36,6 +40,7 @@ Erstelle/Aktualisiere Dokumentation für: $ARGUMENTS - Troubleshooting ### 5. Architecture Docs + - System Overview - Component Diagram - Data Flow @@ -48,22 +53,27 @@ Erstelle/Aktualisiere Dokumentation für: $ARGUMENTS # [Titel] ## Übersicht + [Kurze Beschreibung] ## Inhaltsverzeichnis + 1. [Section 1] 2. [Section 2] -... + ... ## [Section 1] + [Inhalt] ## [Section 2] + [Inhalt] --- -*Erstellt am: [Datum]* -*Version: [Version]* + +_Erstellt am: [Datum]_ +_Version: [Version]_ ``` ## Regeln diff --git a/.claude/commands/fix.md b/.claude/commands/fix.md index 56bedb4..26c5fb7 100644 --- a/.claude/commands/fix.md +++ b/.claude/commands/fix.md @@ -10,28 +10,32 @@ Analysiere und behebe: $ARGUMENTS ## Vorgehen (Root Cause First) ### 1. Error-Analyse + - Exakte Error-Message lesen - Stack Trace analysieren - Betroffene Dateien identifizieren ### 2. Root Cause finden + - Nicht Symptome fixen, sondern Ursache - Abhängigkeiten prüfen - Kontext zur Gesamtarchitektur beachten ### 3. Fix implementieren + - Kleinster möglicher Fix - TypeScript-konform - Keine neuen `any` Types ### 4. Verifikation + - Fehler reproduzieren - Fix testen - Keine Regression ## Output Format -``` +```` ## Fehler-Analyse **Error**: [Exakte Message] @@ -46,10 +50,14 @@ Analysiere und behebe: $ARGUMENTS // Nachher ... -``` +```` ### Verifikation + - [ ] Fehler behoben - [ ] Keine Regression - [ ] Types korrekt + +``` + ``` diff --git a/.claude/commands/implement.md b/.claude/commands/implement.md index 47b24ba..cf349c8 100644 --- a/.claude/commands/implement.md +++ b/.claude/commands/implement.md @@ -10,16 +10,19 @@ Implementiere: $ARGUMENTS ## Vorgehen (nach Coding Standards) ### 1. Analyse + - Verstehe den Kontext zur Gesamtarchitektur - Identifiziere Abhängigkeiten zwischen Modulen - Prüfe bestehende Patterns im Code ### 2. Planung + - Kleine, testbare Schritte definieren - Types zuerst (shared types wenn Frontend+Backend) - State-Management vor UI ### 3. Implementation + - TypeScript strict mode - Keine `any` Types - JSDoc für alle Funktionen @@ -29,11 +32,13 @@ Implementiere: $ARGUMENTS - SCREAMING_SNAKE_CASE (Konstanten) ### 4. Integration + - Error-Handling (try/catch, Loading, Error States) - Input-Validierung Frontend + Backend - API-Types teilen ### 5. Verifikation + - Nach jedem Schritt testen - Bei Fehlern: Root Cause zuerst fixen diff --git a/.claude/commands/review.md b/.claude/commands/review.md index fad59a4..aa864e2 100644 --- a/.claude/commands/review.md +++ b/.claude/commands/review.md @@ -10,21 +10,25 @@ Führe ein Code Review durch für: $ARGUMENTS ## Prüfe folgende Aspekte: ### 1. TypeScript Standards + - [ ] Strikte Typisierung (keine `any`) - [ ] Korrekte Namenskonventionen - [ ] JSDoc Dokumentation vorhanden ### 2. Sicherheit + - [ ] Input-Validierung - [ ] Keine hardgecodeten Secrets - [ ] XSS/Injection Prevention ### 3. Code-Qualität + - [ ] DRY-Prinzip eingehalten - [ ] Single Responsibility - [ ] Aussagekräftige Namen ### 4. Integration + - [ ] API-Types geteilt - [ ] Error-Handling vollständig - [ ] State vor UI diff --git a/.claude/commands/security.md b/.claude/commands/security.md index 76b51f5..8a408bd 100644 --- a/.claude/commands/security.md +++ b/.claude/commands/security.md @@ -10,29 +10,34 @@ Führe ein Sicherheits-Audit durch für: $ARGUMENTS ## Prüfungen ### 1. Secrets & Credentials + - [ ] Keine hardgecodeten API-Keys - [ ] Keine Passwörter im Code - [ ] .env Dateien in .gitignore - [ ] Keine Secrets in Git History ### 2. Input Validierung + - [ ] Frontend: Alle User-Inputs validiert - [ ] Backend: Alle API-Inputs validiert - [ ] SQL Injection Prevention - [ ] XSS Prevention ### 3. Authentication & Authorization + - [ ] Auth auf allen geschützten Routen - [ ] Token Expiration konfiguriert - [ ] CORS richtig konfiguriert - [ ] Rate Limiting aktiv ### 4. Dependencies + - [ ] `npm audit` ausführen - [ ] Keine bekannten Vulnerabilities - [ ] Dependencies aktuell ### 5. OWASP Top 10 + - [ ] Injection - [ ] Broken Authentication - [ ] Sensitive Data Exposure diff --git a/.claude/commands/test.md b/.claude/commands/test.md index bfb1048..ce18ca3 100644 --- a/.claude/commands/test.md +++ b/.claude/commands/test.md @@ -10,22 +10,26 @@ Schreibe/Führe Tests aus für: $ARGUMENTS ## Test-Typen ### 1. Unit Tests + - Einzelne Funktionen testen - Mocking für Dependencies - Edge Cases abdecken - 80%+ Coverage Ziel ### 2. Integration Tests + - API Endpoints testen - Database Operations - External Services (mocked) ### 3. E2E Tests + - User Flows testen - Critical Paths - Cross-Browser (wenn relevant) ### 4. Component Tests (Frontend) + - React Component Rendering - User Interactions - State Changes diff --git a/.claude/commands/workflow.md b/.claude/commands/workflow.md index a07d0a1..49ca4e5 100644 --- a/.claude/commands/workflow.md +++ b/.claude/commands/workflow.md @@ -10,24 +10,27 @@ Analysiere und plane Automatisierung für: $ARGUMENTS ## Analyse-Schritte ### 1. Aktuellen Workflow verstehen + - Welche manuellen Schritte gibt es? - Wer macht was? - Wie lange dauert jeder Schritt? - Wo sind Bottlenecks? ### 2. Automatisierungs-Potenzial + - Welche Schritte sind repetitiv? - Welche Schritte brauchen keine menschliche Entscheidung? - Welche können Agents übernehmen? ### 3. Agent-Zuweisung -| Task | Agent | Trigger | -|------|-------|---------| -| Code Review | /review | PR erstellt | -| Security Check | /security | Vor Deploy | -| Tests | /test | Nach Code Change | -| Docs | /docs | Nach Feature Complete | -| Design Check | /design | Vor Release | + +| Task | Agent | Trigger | +| -------------- | --------- | --------------------- | +| Code Review | /review | PR erstellt | +| Security Check | /security | Vor Deploy | +| Tests | /test | Nach Code Change | +| Docs | /docs | Nach Feature Complete | +| Design Check | /design | Vor Release | ## Workflow-Template diff --git a/AGENTS.md b/AGENTS.md index 067761b..6907844 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,5 @@ ## OTOP Rules (MUST) + - Add `data-otop-id` + `data-testid` to every interactive UI component (Web). - React Native: add `testID` + `accessibilityLabel="otop:"`. - Do not hardcode API URLs; use ENV/proxy. diff --git a/AGENT_0_CODE_REVIEW_REPORT_2025-12-26.md b/AGENT_0_CODE_REVIEW_REPORT_2025-12-26.md index b55c406..87bf2f6 100644 --- a/AGENT_0_CODE_REVIEW_REPORT_2025-12-26.md +++ b/AGENT_0_CODE_REVIEW_REPORT_2025-12-26.md @@ -14,16 +14,17 @@ ### Aktuelle Situation (Stand: 1a626d8) **Repository Status:** + - Main Branch: `1a626d8` - feat(agent-3): Implement webhook event workers with retry logic (#11) - Letzte Merges: Agent 3 PR #9 + #11, Agent 2 PR #12 (reverted), Agent 4 Swagger/Postman **Agent Status:** -| Agent | Tasks | PRs | Status | Rating | -|-------|-------|-----|--------|--------| -| **Agent 3** | 5/7 (71%) | #9 ✅ #11 ✅ | Merged & Live | ⭐⭐⭐⭐⭐ 5/5 | -| **Agent 2** | Setup ✅ | #12 Reverted | Dependencies fehlen | ⭐⭐⭐☆☆ 3/5 | -| **Agent 4** | 2/2 (100%) | Docs ✅ | Complete | ⭐⭐⭐⭐⭐ 5/5 | +| Agent | Tasks | PRs | Status | Rating | +| ----------- | ---------- | ------------ | ------------------- | -------------- | +| **Agent 3** | 5/7 (71%) | #9 ✅ #11 ✅ | Merged & Live | ⭐⭐⭐⭐⭐ 5/5 | +| **Agent 2** | Setup ✅ | #12 Reverted | Dependencies fehlen | ⭐⭐⭐☆☆ 3/5 | +| **Agent 4** | 2/2 (100%) | Docs ✅ | Complete | ⭐⭐⭐⭐⭐ 5/5 | --- @@ -32,6 +33,7 @@ ### Status: ✅ **EXCELLENT - IN PRODUCTION** **Commits auf Main:** + - `3a54591` - feat(agent-3): Complete all 3 PRIO 1 API implementations (#9) - `1a626d8` - feat(agent-3): Implement webhook event workers with retry logic (#11) @@ -40,17 +42,20 @@ #### 1. REST APIs - 27 Endpoints ✅ **GitHub API (9 Endpoints):** + - GET /api/github/status, /repos, /repos/:owner/:repo - GET /api/github/issues, /pulls, /comments - POST /api/github/issues, /pulls, /comments **Linear API (10 Endpoints):** + - GET /api/linear/status, /teams, /issues, /projects - GET /api/linear/states, /labels, /users - POST /api/linear/issues, /projects, /comments - PATCH /api/linear/issues/:id (Bonus!) **Agent Control API (8 Endpoints):** + - GET /api/agents, /api/agents/:id - POST /api/agents/:id/start, /stop - PATCH /api/agents/:id/state @@ -60,16 +65,19 @@ #### 2. Webhooks - Production Ready ✅ **GitHub Webhook:** + - POST /api/webhooks/github - Events: ping, push, pull_request, issues, issue_comment - HMAC SHA-256 Signature Verification ✅ **Linear Webhook:** + - POST /api/webhooks/linear -- Events: Issue.*, Comment.*, Project.* +- Events: Issue._, Comment._, Project.\* - HMAC SHA-256 Signature Verification ✅ **NEW: Event Workers (#11) ✅** + - GitHub Event Workers (push, PR, issues, comments) - Linear Event Workers (issues, comments, projects) - Retry Logic mit Exponential Backoff @@ -93,6 +101,7 @@ ### 📊 Statistics **Code:** + - ~4,302 Zeilen Production Code - ~1,690 Zeilen Dokumentation - 30 REST/Webhook Endpoints @@ -100,6 +109,7 @@ - 5 Branches merged **Quality:** ⭐⭐⭐⭐⭐ (5/5) + - Code Quality: Excellent - Security: Production-ready - Documentation: Comprehensive @@ -119,12 +129,14 @@ ### Status: ⚠️ **DEPENDENCIES FEHLEN** **Commits:** + - `8c9c074` - feat(agent-2): Implement User Management API with RBAC (#12) - MERGED - `aa3a636` - Revert PR #12 - REVERTED ### ✅ Erfolge **Setup Tasks (6/6) komplett:** + - ✅ .env aus .env.example erstellt - ✅ data/ Ordner für SQLite DB - ✅ API Keys gesetzt @@ -133,6 +145,7 @@ - ✅ Dokumentation erstellt **User Management (vor Revert):** + - ✅ JWT Authentication System - ✅ User Management API mit RBAC - ✅ bcrypt Password Hashing @@ -142,6 +155,7 @@ ### ❌ Critical Issues **1. Missing Dependencies:** + ``` ❌ better-sqlite3 fehlt ❌ @types/better-sqlite3 fehlt @@ -149,12 +163,14 @@ ``` **Impact:** + - Backend startet nicht - Database funktioniert nicht - Build schlägt fehl - Tests schlagen fehl **Solution:** + ```bash npm install better-sqlite3 npm install --save-dev @types/better-sqlite3 @types/express @@ -163,10 +179,12 @@ npm test ``` **2. TypeScript Build Errors:** + - 100+ TypeScript strict mode Fehler - Backend Build schlägt fehl **3. Tests schlagen fehl:** + - ERR_MODULE_NOT_FOUND - Keine kompilierten JS-Dateien @@ -181,6 +199,7 @@ npm test ### 🎯 Empfehlung **Action Items:** + 1. 🔴 CRITICAL: Dependencies installieren 2. 🔴 HIGH: TypeScript Build fixen 3. 🔴 HIGH: Tests zum Laufen bringen @@ -193,6 +212,7 @@ npm test ### Status: ✅ **COMPLETE & EXCELLENT** **Commits:** + - `02324db` - feat(agent-4): implement OpenAPI/Swagger documentation - `0c09d9e` - feat(agent-4): implement Postman Collection Export @@ -201,11 +221,13 @@ npm test #### 1. OpenAPI/Swagger **Files:** + - swagger.yaml (~800 lines) - src/swagger/index.ts (Integration) - docs/API.md (~350 lines) **Features:** + - ✅ OpenAPI 3.0.3 Specification - ✅ 60+ API Endpoints dokumentiert - ✅ Request/Response Schemas @@ -219,12 +241,14 @@ npm test #### 2. Postman Collection **Files:** + - postman/collection.json (~400 lines, 25+ requests) - postman/environment.json (Production) - postman/local-environment.json (Local) - docs/POSTMAN.md (~450 lines) **Features:** + - ✅ Postman Collection v2.1.0 - ✅ 25+ Requests in 9 Folders - ✅ Bearer Token Auth @@ -249,11 +273,13 @@ npm test ### Overall Progress **Completed:** + - ✅ Agent 3: 5/7 Tasks (71%) - ✅ Agent 4: 2/2 Tasks (100%) - ✅ Agent 2: Setup 6/6 (100%) **In Progress:** + - ⏳ Agent 3: Tasks 6-7 remaining - ⚠️ Agent 2: Dependency fixes needed - ⏳ Agent 1: Not started @@ -263,6 +289,7 @@ npm test ### Production Status **Live auf http://178.156.178.70:3000:** + - ✅ GitHub REST API (9 endpoints) - ✅ Linear REST API (10 endpoints) - ✅ Agent Control API (8 endpoints) @@ -280,15 +307,18 @@ npm test ### Immediate (PRIO 1) #### 1. Fix Agent 2 Dependencies + ```bash npm install better-sqlite3 @types/better-sqlite3 @types/express npm run backend:build npm test ``` + **Priority:** 🔴 CRITICAL **Time:** ~30min #### 2. Verify Production Deployment + ```bash ssh root@178.156.178.70 cd /root/cloud-agents @@ -296,6 +326,7 @@ git pull origin main npm install --legacy-peer-deps pm2 restart all ``` + **Priority:** 🔴 HIGH **Time:** ~20min @@ -318,6 +349,7 @@ pm2 restart all ### Project Health: ⭐⭐⭐⭐☆ (4/5) **Strengths:** + - ✅ Agent 3's excellent API work (5/5) - ✅ Agent 4's comprehensive docs (5/5) - ✅ Security production-ready @@ -325,12 +357,14 @@ pm2 restart all - ✅ Good progress (55-60%) **Weaknesses:** + - ⚠️ Agent 2 dependency issues - ⚠️ Missing test coverage - ⏳ Agent 1 not started - ⏳ Agent 3 still 29% to go **Recommendation:** + 1. Fix Agent 2 dependencies immediately 2. Continue Agent 3's remaining tasks 3. Start Agent 1 frontend work diff --git a/AGENT_0_DEPENDENCY_FIX_REPORT_2025-12-26.md b/AGENT_0_DEPENDENCY_FIX_REPORT_2025-12-26.md index 9c6013d..6871995 100644 --- a/AGENT_0_DEPENDENCY_FIX_REPORT_2025-12-26.md +++ b/AGENT_0_DEPENDENCY_FIX_REPORT_2025-12-26.md @@ -13,13 +13,16 @@ ## 📋 EXECUTIVE SUMMARY ### Mission + Agent 2's User Management & Authentication System konnte nicht starten aufgrund fehlender Dependencies. Aufgabe war: + 1. Git Workspace nach AGENT_1_EINWEISUNG aufräumen 2. Kritische Dependency-Fehler analysieren und beheben 3. Backend zum Laufen bringen 4. Änderungen committen und pushen ### Ergebnis + ✅ **Alle 5 kritischen Probleme gelöst** ✅ **Backend startet fehlerfrei** ✅ **User Management API funktionsfähig** @@ -46,30 +49,35 @@ Agent 2 hatte in PR #12 User Management mit JWT Auth implementiert, aber die PR ### Root Cause Analysis **Problem 1: Missing Runtime Dependencies (CRITICAL)** + - **Was:** better-sqlite3 und jsonwebtoken wurden im Code verwendet, aber nie installiert - **Wo:** src/db/database.ts, src/auth/jwt.ts - **Warum:** Agent 2 hat den Code geschrieben, aber `npm install` vergessen - **Impact:** Backend konnte nicht starten (ERR_MODULE_NOT_FOUND) **Problem 2: Missing Type Definitions (HIGH)** -- **Was:** @types/* Packages für TypeScript fehlten + +- **Was:** @types/\* Packages für TypeScript fehlten - **Wo:** Alle Files die bcrypt, express, jsonwebtoken, swagger, yamljs nutzen - **Warum:** TypeScript braucht Type Definitions für JavaScript Libraries - **Impact:** Build-Fehler, keine Autocomplete, keine Type-Safety **Problem 3: Wrong Middleware Imports (HIGH)** + - **Was:** src/api/users.ts importierte requireJWT/requireJWTAdmin die nicht existieren - **Wo:** src/api/users.ts:8 - **Warum:** Nach Revert von PR #12 waren die JWT-Middlewares weg, aber users.ts referenzierte sie noch - **Impact:** Backend konnte nicht starten (SyntaxError: export not found) **Problem 4: npm Peer Dependency Conflict (MEDIUM)** + - **Was:** date-fns Version Konflikt zwischen react-day-picker und anderen Packages - **Wo:** npm install Prozess - **Warum:** react-day-picker@8.10.1 will date-fns ^2.28.0 || ^3.0.0 - **Impact:** npm install schlug fehl ohne --legacy-peer-deps Flag **Problem 5: Git Workspace Chaos (LOW)** + - **Was:** Mehrere alte Feature-Branches, unklarer Branch-Status - **Wo:** Lokales Git Repository - **Warum:** Mehrere PRs wurden erstellt/reverted ohne Cleanup @@ -82,6 +90,7 @@ Agent 2 hatte in PR #12 User Management mit JWT Auth implementiert, aber die PR ### Fix 1: Git Workspace Cleanup **Befehle:** + ```bash # Zu main branch wechseln git checkout main @@ -101,6 +110,7 @@ git branch -u dsactivi2/main ``` **Ergebnis:** + ``` ✅ On branch main ✅ Your branch is up to date with 'dsactivi2/main' @@ -112,16 +122,19 @@ git branch -u dsactivi2/main ### Fix 2: Runtime Dependencies installieren **Befehle:** + ```bash npm install better-sqlite3 --legacy-peer-deps npm install jsonwebtoken --legacy-peer-deps ``` **Installierte Packages:** + - `better-sqlite3@12.5.0` - SQLite database library für Node.js - `jsonwebtoken@9.0.3` - JWT token generation & verification **Warum --legacy-peer-deps?** + - react-day-picker@8.10.1 erwartet date-fns ^2.28.0 oder ^3.0.0 - Projekt nutzt date-fns@4.1.0 - --legacy-peer-deps überspringt peer dependency checks (wie npm v6) @@ -131,6 +144,7 @@ npm install jsonwebtoken --legacy-peer-deps ### Fix 3: TypeScript Type Definitions installieren **Befehle:** + ```bash npm install --save-dev @types/better-sqlite3 --legacy-peer-deps npm install --save-dev @types/express --legacy-peer-deps @@ -151,9 +165,10 @@ npm install --save-dev @types/yamljs --legacy-peer-deps | @types/yamljs | 0.2.34 | YAML Parser Types | **Warum notwendig?** + - TypeScript ist eine typed language - JavaScript Libraries haben keine Types -- @types/* Packages sind Community-maintained TypeScript Definitions +- @types/\* Packages sind Community-maintained TypeScript Definitions - Ohne sie: `any` Types, keine Autocomplete, keine Compile-Time Safety --- @@ -161,6 +176,7 @@ npm install --save-dev @types/yamljs --legacy-peer-deps ### Fix 4: Middleware Import Fehler beheben **Problem:** + ```typescript // src/api/users.ts (ALT - FALSCH): import { requireJWT, requireJWTAdmin, type AuthenticatedRequest } from "../auth/middleware.js"; @@ -170,19 +186,38 @@ router.get("/me", requireJWT, async (req: AuthenticatedRequest, res: Response) = ``` **Ursache:** + - requireJWT und requireJWTAdmin existieren NICHT in src/auth/middleware.ts - Diese Funktionen waren Teil von Agent 2's JWT Auth Implementierung - Nach Revert von PR #12 sind sie weg **Verfügbare Middlewares (src/auth/middleware.ts):** + ```typescript -export function requireAuth(req: AuthenticatedRequest, res: Response, next: NextFunction): void -export function requireAdmin(req: AuthenticatedRequest, res: Response, next: NextFunction): void -export function optionalAuth(req: AuthenticatedRequest, _res: Response, next: NextFunction): void -export function requireCronAuth(req: Request, res: Response, next: NextFunction): void +export function requireAuth( + req: AuthenticatedRequest, + res: Response, + next: NextFunction, +): void; +export function requireAdmin( + req: AuthenticatedRequest, + res: Response, + next: NextFunction, +): void; +export function optionalAuth( + req: AuthenticatedRequest, + _res: Response, + next: NextFunction, +): void; +export function requireCronAuth( + req: Request, + res: Response, + next: NextFunction, +): void; ``` **Fix:** + ```typescript // src/api/users.ts (NEU - KORREKT): import { requireAdmin, requireAuth, type AuthenticatedRequest } from "../auth/middleware.js"; @@ -192,6 +227,7 @@ router.get("/me", requireAuth, async (req: AuthenticatedRequest, res: Response) ``` **Geänderte Stellen (7 Edits):** + 1. Import Statement (Zeile 8) 2. GET /api/users (Zeile 31) - requireJWTAdmin → requireAdmin 3. GET /api/users/stats (Zeile 57) - requireJWTAdmin → requireAdmin @@ -206,6 +242,7 @@ router.get("/me", requireAuth, async (req: AuthenticatedRequest, res: Response) ### Fix 5: Git Commit & Push **Commit Message:** + ```bash fix(deps): add missing dependencies for Agent 2 (Backend) @@ -262,6 +299,7 @@ Co-Authored-By: Claude Sonnet 4.5 ``` **Push:** + ```bash git add package.json package-lock.json src/api/users.ts git commit -m "..." @@ -269,6 +307,7 @@ git push dsactivi2 main ``` **Ergebnis:** + ``` ✅ Commit 0e36692 erstellt ✅ Zu github.com/dsactivi-2/code-cloud-agents.git gepusht @@ -286,6 +325,7 @@ npm run backend:dev ``` **Output:** + ``` ✅ Server running on http://localhost:3000 ✅ Database initialized at data/cloud-agents.db @@ -299,6 +339,7 @@ npm run backend:dev ``` **Verfügbare Services:** + - ✅ User Management API (10 endpoints) - ✅ GitHub Webhooks (POST /api/webhooks/github) - ✅ Linear Webhooks (POST /api/webhooks/linear) @@ -316,6 +357,7 @@ npm run backend:build **Backend Dependencies:** ✅ **ALLE OK** **Remaining Warnings (nicht kritisch):** + ``` ⚠️ src/App.tsx - Missing @radix-ui/* types (Agent 1) ⚠️ src/components/* - Missing @radix-ui/* types (Agent 1) @@ -325,6 +367,7 @@ npm run backend:build ``` **Fazit:** + - ✅ Backend dependencies komplett OK - ⚠️ Frontend UI warnings sind Agent 1's Verantwortung - ⚠️ Minor type mismatches in webhooks (funktioniert trotzdem) @@ -336,6 +379,7 @@ npm run backend:build ### 1. User Management API (src/api/users.ts) **Überblick:** + - 377 Zeilen Production Code - 10 REST Endpoints - RBAC (Role-Based Access Control) @@ -344,37 +388,41 @@ npm run backend:build **Endpoints:** -| Method | Path | Auth | Beschreibung | -|--------|------|------|--------------| -| GET | /api/users | Admin | Liste aller User | -| GET | /api/users/stats | Admin | User Statistiken | -| GET | /api/users/me | Auth | Eigenes Profil | -| GET | /api/users/:id | Auth | User by ID (Admin oder self) | -| POST | /api/users | Admin | Neuen User erstellen | -| PATCH | /api/users/:id | Auth | User updaten (Admin oder self) | -| POST | /api/users/:id/password | Auth | Passwort ändern | -| DELETE | /api/users/:id | Admin | User löschen | +| Method | Path | Auth | Beschreibung | +| ------ | ----------------------- | ----- | ------------------------------ | +| GET | /api/users | Admin | Liste aller User | +| GET | /api/users/stats | Admin | User Statistiken | +| GET | /api/users/me | Auth | Eigenes Profil | +| GET | /api/users/:id | Auth | User by ID (Admin oder self) | +| POST | /api/users | Admin | Neuen User erstellen | +| PATCH | /api/users/:id | Auth | User updaten (Admin oder self) | +| POST | /api/users/:id/password | Auth | Passwort ändern | +| DELETE | /api/users/:id | Admin | User löschen | **Security Features:** ✅ **Authentication & Authorization:** + - JWT Token Verification via requireAuth/requireAdmin (src/api/users.ts:8) - Users können nur eigenes Profil sehen (src/api/users.ts:111-115) - Non-Admins können role/isActive nicht ändern (src/api/users.ts:214-219) - Admin kann nicht eigenes Konto löschen (src/api/users.ts:346-352) ✅ **Password Security:** + - bcrypt Hashing mit 10 rounds (src/db/users.ts) - Password hash wird NIEMALS zurückgegeben (src/api/users.ts:88, 127, 181, 245) - Current password required für non-admins (src/api/users.ts:293-306) - Min 8 characters Validation (src/api/users.ts:278-282) ✅ **Input Validation:** + - Email & Password required (src/api/users.ts:150-154) - Role muss "admin", "user" oder "demo" sein (src/api/users.ts:156-160) - Email uniqueness check (src/api/users.ts:163-168) ✅ **Error Handling:** + - try/catch um alle async operations - 400 Bad Request für Validation Errors - 401 Unauthorized für Auth Fehler @@ -384,6 +432,7 @@ npm run backend:build - 500 Internal Server Error mit Logging **Code Quality:** ⭐⭐⭐⭐⭐ (5/5) + - Clean Code, gut strukturiert - Vollständige Error-Handling - Security Best Practices @@ -395,6 +444,7 @@ npm run backend:build ### 2. GitHub Webhook Handler (src/webhooks/github.ts) **Überblick:** + - 253 Zeilen Production Code - HMAC SHA-256 Signature Verification - 5 Event Types: push, pull_request, issues, issue_comment, ping @@ -403,8 +453,13 @@ npm run backend:build **Security Features:** ✅ **HMAC Signature Verification (src/webhooks/github.ts:45-60):** + ```typescript -export function verifyGitHubSignature(payload: string, signature: string, secret: string): boolean { +export function verifyGitHubSignature( + payload: string, + signature: string, + secret: string, +): boolean { if (!signature || !signature.startsWith("sha256=")) { return false; } @@ -415,7 +470,10 @@ export function verifyGitHubSignature(payload: string, signature: string, secret // Use crypto.timingSafeEqual to prevent timing attacks try { - return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(calculatedSignature)); + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(calculatedSignature), + ); } catch { return false; } @@ -423,12 +481,14 @@ export function verifyGitHubSignature(payload: string, signature: string, secret ``` **Security Highlights:** + - ✅ HMAC SHA-256 mit shared secret - ✅ crypto.timingSafeEqual prevents timing attacks - ✅ Raw body parsing für korrekte Signature (src/webhooks/github.ts:192) - ✅ Signature skip nur für ping events in dev (src/webhooks/github.ts:190) **Event Processing:** + - ✅ push events → Queue job "github_push" (src/webhooks/github.ts:84-99) - ✅ pull_request events → Queue job "github_pull_request" (src/webhooks/github.ts:104-119) - ✅ issues events → Queue job "github_issues" (src/webhooks/github.ts:124-139) @@ -436,10 +496,12 @@ export function verifyGitHubSignature(payload: string, signature: string, secret - ✅ ping events → Return pong (src/webhooks/github.ts:208-214) **Database Audit:** + - ✅ Alle Events werden in audit_log gespeichert (src/webhooks/github.ts:65-79) - ✅ Tracking: event type, repository, action, sender **Code Quality:** ⭐⭐⭐⭐⭐ (5/5) + - Production-ready Security - Comprehensive Event Handling - Proper Error Logging @@ -450,16 +512,22 @@ export function verifyGitHubSignature(payload: string, signature: string, secret ### 3. Linear Webhook Handler (src/webhooks/linear.ts) **Überblick:** + - 259 Zeilen Production Code - HMAC SHA-256 Signature Verification -- Event Types: Issue.*, Comment.*, Project.* +- Event Types: Issue._, Comment._, Project.\* - Queue-based Processing **Security Features:** ✅ **HMAC Signature Verification (src/webhooks/linear.ts:69-84):** + ```typescript -export function verifyLinearSignature(payload: string, signature: string, secret: string): boolean { +export function verifyLinearSignature( + payload: string, + signature: string, + secret: string, +): boolean { if (!signature) { return false; } @@ -470,7 +538,10 @@ export function verifyLinearSignature(payload: string, signature: string, secret // Use crypto.timingSafeEqual to prevent timing attacks try { - return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(calculatedSignature)); + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(calculatedSignature), + ); } catch { return false; } @@ -478,20 +549,24 @@ export function verifyLinearSignature(payload: string, signature: string, secret ``` **Security Highlights:** + - ✅ HMAC SHA-256 mit shared secret - ✅ crypto.timingSafeEqual prevents timing attacks - ✅ Raw body parsing (src/webhooks/linear.ts:189) - ✅ Signature header: "linear-signature" (src/webhooks/linear.ts:183) **Event Processing:** + - ✅ Issue events → Queue job "linear_issue" (src/webhooks/linear.ts:116-130) - ✅ Comment events → Queue job "linear_comment" (src/webhooks/linear.ts:135-147) - ✅ Project events → Queue job "linear_project" (src/webhooks/linear.ts:152-165) **Additional Features:** + - ✅ Test endpoint GET /api/webhooks/linear/test (src/webhooks/linear.ts:249-255) **Code Quality:** ⭐⭐⭐⭐⭐ (5/5) + - Same security standards wie GitHub - Clean architecture - Well-documented @@ -501,6 +576,7 @@ export function verifyLinearSignature(payload: string, signature: string, secret ### 4. WebSocket Real-time System (src/websocket/client-example.ts) **Überblick:** + - 225 Zeilen Example Code + Documentation - Client-Side Connection Examples - Message Type System @@ -508,27 +584,30 @@ export function verifyLinearSignature(payload: string, signature: string, secret - Server-Side Broadcasting Examples **Message Types:** + ```typescript type MessageType = - | "auth" // Authentication result - | "ping" | "pong" // Heartbeat - | "agent_status" // Agent status updates - | "chat_message" // New chat messages - | "notification" // User notifications - | "user_presence" // Online/Away/Busy/Offline - | "error"; // Error messages + | "auth" // Authentication result + | "ping" + | "pong" // Heartbeat + | "agent_status" // Agent status updates + | "chat_message" // New chat messages + | "notification" // User notifications + | "user_presence" // Online/Away/Busy/Offline + | "error"; // Error messages ``` **Client Example:** + ```typescript // Browser WebSocket Client const ws = new WebSocket(`ws://localhost:3000/ws?token=${token}`); // Authentication via URL Query Parameter -ws://localhost:3000/ws?token=YOUR_JWT_TOKEN +//localhost:3000/ws?token=YOUR_JWT_TOKEN // Send ping every 30s -setInterval(() => { +ws: setInterval(() => { ws.send(JSON.stringify({ type: "ping" })); }, 30000); @@ -539,6 +618,7 @@ ws.addEventListener("close", () => { ``` **React Hook Example:** + ```typescript const { agentStatus, notifications, isConnected } = useWebSocket(token); @@ -550,6 +630,7 @@ useEffect(() => { ``` **Server Broadcasting:** + ```typescript // Broadcast agent status wsManager.broadcastAgentStatus({ @@ -567,6 +648,7 @@ wsManager.sendNotification("error", "Your task failed", "user_123"); ``` **Code Quality:** ⭐⭐⭐⭐☆ (4/5) + - Comprehensive examples - Good documentation - Production-ready patterns @@ -579,27 +661,29 @@ wsManager.sendNotification("error", "Your task failed", "user_123"); ### Dependencies Status **Runtime Dependencies (Installiert ✅):** + ```json { - "better-sqlite3": "^12.5.0", // SQLite database - "jsonwebtoken": "^9.0.3", // JWT authentication - "bcrypt": "^6.0.0", // Password hashing - "express": "^5.2.1", // Web framework - "cors": "^2.8.5", // CORS middleware - "ws": "^8.18.3", // WebSocket server - "zod": "^3.23.8", // Schema validation + "better-sqlite3": "^12.5.0", // SQLite database + "jsonwebtoken": "^9.0.3", // JWT authentication + "bcrypt": "^6.0.0", // Password hashing + "express": "^5.2.1", // Web framework + "cors": "^2.8.5", // CORS middleware + "ws": "^8.18.3", // WebSocket server + "zod": "^3.23.8", // Schema validation "swagger-ui-express": "^5.0.1", // API docs - "yamljs": "^0.3.0", // YAML parser + "yamljs": "^0.3.0", // YAML parser "@anthropic-ai/sdk": "^0.71.2", // Claude AI "@google/generative-ai": "^0.24.1", // Gemini AI - "@linear/sdk": "^68.1.0", // Linear API - "@octokit/rest": "^22.0.1", // GitHub API - "@slack/web-api": "^7.13.0", // Slack API - "openai": "^6.15.0" // OpenAI API + "@linear/sdk": "^68.1.0", // Linear API + "@octokit/rest": "^22.0.1", // GitHub API + "@slack/web-api": "^7.13.0", // Slack API + "openai": "^6.15.0" // OpenAI API } ``` **Development Dependencies (Installiert ✅):** + ```json { "@types/better-sqlite3": "^7.6.13", @@ -659,17 +743,20 @@ wsManager.sendNotification("error", "Your task failed", "user_123"); ### Agent Status **Agent 0 (Lead Developer & Orchestrator):** 🟢 **ACTIVE** + - ✅ Code Review Reports erstellt - ✅ Dependency Fixes durchgeführt - ✅ Git Workflow etabliert - ✅ Coordination aller Agents **Agent 1 (Frontend/UI Developer):** 🟡 **PARTIALLY STARTED** + - ✅ Status Dashboard implementiert (agent-a1-status-dashboard branch) - ⏳ Weitere Frontend Tasks ausstehend - ⏳ @radix-ui Type Definitions fehlen noch **Agent 2 (Setup & Infrastructure):** 🟢 **DEPENDENCIES FIXED** + - ✅ Setup Tasks (6/6) komplett - ✅ User Management API implementiert - ✅ JWT Authentication System @@ -677,6 +764,7 @@ wsManager.sendNotification("error", "Your task failed", "user_123"); - 🎯 Ready für re-merge von User Management **Agent 3 (Integrations & APIs):** 🟢 **EXCELLENT** + - ✅ 5/7 Tasks completed (71%) - ✅ GitHub REST API (9 endpoints) - ✅ Linear REST API (10 endpoints) @@ -688,6 +776,7 @@ wsManager.sendNotification("error", "Your task failed", "user_123"); - ⏳ Memory System (Task 7) **Agent 4 (Documentation):** 🟢 **COMPLETE** + - ✅ OpenAPI/Swagger Documentation - ✅ Postman Collection Export - ✅ API.md comprehensive docs @@ -703,6 +792,7 @@ wsManager.sendNotification("error", "Your task failed", "user_123"); **Working Tree:** Clean ✅ **Recent Commits:** + ``` 0e36692 - fix(deps): add missing dependencies for Agent 2 (Backend) ← DAS IST DIESER FIX b5d5aca - docs(agent-0): Add comprehensive code review report @@ -712,15 +802,17 @@ b5d5aca - docs(agent-0): Add comprehensive code review report ``` **Cleaned Branches:** + - ❌ agent-a1-status-dashboard (deleted locally, existiert noch remote) - ❌ agent-a3-github-rest-api-v3 (deleted) - ❌ feature/complete-react-setup (deleted) - ❌ jwt-auth (deleted) **Active Branches (Remote):** + - ✅ main (HEAD) -- ✅ agent-a2-* (mehrere PR branches) -- ✅ agent-a3-* (mehrere PR branches) +- ✅ agent-a2-\* (mehrere PR branches) +- ✅ agent-a3-\* (mehrere PR branches) - ✅ agent-a4-swagger (merged) --- @@ -765,7 +857,7 @@ b5d5aca - docs(agent-0): Add comprehensive code review report - → Bei Revert ALLE References prüfen und anpassen 4. **Fehlende Type Definitions** - - TypeScript braucht @types/* für JavaScript Libraries + - TypeScript braucht @types/\* für JavaScript Libraries - → Bei npm install auch npm install --save-dev @types/ 5. **Kein Testing vor Commit** @@ -823,6 +915,7 @@ b5d5aca - docs(agent-0): Add comprehensive code review report pm2 restart all pm2 logs ``` + - Priorität: 🔴 HIGH - Zeit: ~20min @@ -875,6 +968,7 @@ b5d5aca - docs(agent-0): Add comprehensive code review report ### Overall Project Health: ⭐⭐⭐⭐⭐ (5/5) **Strengths:** + - ✅ Agent 2 Dependencies KOMPLETT gefixed - ✅ Backend läuft fehlerfrei - ✅ User Management API production-ready @@ -885,6 +979,7 @@ b5d5aca - docs(agent-0): Add comprehensive code review report - ✅ Gute Code-Qualität durchgehend **Fixed Issues:** + - ✅ 5/5 Critical Dependency Problems gelöst - ✅ Backend startet ohne Fehler - ✅ TypeScript Build OK (Backend) @@ -892,11 +987,13 @@ b5d5aca - docs(agent-0): Add comprehensive code review report - ✅ Alle Änderungen committed & gepusht **Minor Issues (nicht kritisch):** + - ⚠️ Frontend UI type warnings (Agent 1) - ⚠️ Webhook audit log type mismatch (minor) - ⚠️ Unused variables in example files (non-blocking) **Recommendation:** + 1. ✅ **Agent 2 Dependencies sind PRODUCTION READY** 2. 🚀 **Backend kann deployed werden** 3. ⏩ **Nächste Schritte: Agent 3 Tasks 6-7, dann Agent 1 Frontend** @@ -909,15 +1006,18 @@ b5d5aca - docs(agent-0): Add comprehensive code review report ### Modified Files **1. package.json** + - Added runtime dependencies: better-sqlite3, jsonwebtoken - Added dev dependencies: @types/better-sqlite3, @types/express, @types/jsonwebtoken, @types/bcrypt, @types/swagger-ui-express, @types/yamljs - Total: +12 dependencies **2. package-lock.json** + - Updated lockfile mit neuen Dependencies - ~500+ Zeilen geändert (auto-generated) **3. src/api/users.ts** + - Zeile 8: Import statement geändert - requireJWT → requireAuth - requireJWTAdmin → requireAdmin @@ -939,6 +1039,7 @@ b5d5aca - docs(agent-0): Add comprehensive code review report Alle 5 kritischen Dependency-Probleme wurden identifiziert, analysiert und gelöst. Agent 2's Backend ist jetzt production-ready und läuft fehlerfrei. Die User Management API mit JWT Authentication und RBAC ist voll funktionsfähig. **Key Achievements:** + - 🎯 **100% Success Rate** - Alle Probleme gelöst - ⚡ **Zero Downtime** - Backend startet sofort - 🔒 **Security** - Alle Best Practices eingehalten @@ -946,6 +1047,7 @@ Alle 5 kritischen Dependency-Probleme wurden identifiziert, analysiert und gelö - 📝 **Documentation** - Comprehensive Report **Time Investment:** + - Problem Analysis: ~30min - Git Cleanup: ~10min - Dependency Fixes: ~20min @@ -956,6 +1058,7 @@ Alle 5 kritischen Dependency-Probleme wurden identifiziert, analysiert und gelö - **Total: ~2h 45min** **Value Delivered:** + - Backend functionality restored - User Management API operational - Clear path forward for Agent 3 Tasks 6-7 diff --git a/AGENT_3_ABSCHLUSSBERICHT.md b/AGENT_3_ABSCHLUSSBERICHT.md index 5f5245b..501a451 100644 --- a/AGENT_3_ABSCHLUSSBERICHT.md +++ b/AGENT_3_ABSCHLUSSBERICHT.md @@ -9,6 +9,7 @@ ## 🎯 EXECUTIVE SUMMARY ### Session-Ergebnis: + - ✅ **3 Tasks komplett** (43%) - ⏳ **4 Tasks offen** (57%) - **Zeitaufwand:** ~5-6h @@ -17,6 +18,7 @@ - **Branches:** 3 gepusht (ready to merge) ### Was funktioniert jetzt: + ✅ **GitHub Integration** - 9 REST Endpoints ✅ **Linear Integration** - 10 REST Endpoints ✅ **GitHub Webhooks** - 5 Event-Types mit Signature Verification @@ -24,6 +26,7 @@ ✅ **Event Processing** - Queue-basiert, Audit-Log Storage ### Verbleibend: + ⏳ **WebSocket Real-time** (6-8h) ⏳ **Agent Control API** (8-10h) ⏳ **Settings Management API** (4-6h) @@ -41,6 +44,7 @@ **Code:** 524 Zeilen **Implementierung:** + ``` Datei: src/api/github.ts @@ -57,6 +61,7 @@ Datei: src/api/github.ts ``` **Features:** + - Octokit-Integration für erweiterte GitHub Features - Zod-Schema Validation für POST-Requests - Query-Parameter Support (filters, pagination) @@ -75,6 +80,7 @@ Datei: src/api/github.ts **Code:** 503 Zeilen **Implementierung:** + ``` Datei: src/api/linear.ts @@ -91,6 +97,7 @@ Datei: src/api/linear.ts ``` **Features:** + - Linear SDK-Integration für erweiterte Linear Features - Zod-Schema Validation für POST-Requests - Query-Parameter Support (filters, pagination) @@ -111,6 +118,7 @@ Datei: src/api/linear.ts **Implementierung:** #### GitHub Webhook Handler (`src/webhooks/github.ts` - 261 Zeilen) + ``` POST /api/webhooks/github @@ -133,6 +141,7 @@ Processing: ``` #### Linear Webhook Handler (`src/webhooks/linear.ts` - 262 Zeilen) + ``` POST /api/webhooks/linear GET /api/webhooks/linear/test @@ -158,6 +167,7 @@ Processing: ``` **Architecture:** + ``` Webhook Incoming ↓ @@ -171,17 +181,27 @@ Event Handlers (Async) ``` **Express Integration:** + ```typescript // src/index.ts // Webhook routes FIRST (need raw body) -app.use("/api/webhooks/github", express.text({ type: "application/json" }), createGitHubWebhookRouter(db, queue)); -app.use("/api/webhooks/linear", express.text({ type: "application/json" }), createLinearWebhookRouter(db, queue)); +app.use( + "/api/webhooks/github", + express.text({ type: "application/json" }), + createGitHubWebhookRouter(db, queue), +); +app.use( + "/api/webhooks/linear", + express.text({ type: "application/json" }), + createLinearWebhookRouter(db, queue), +); // All other routes use JSON parsing app.use(express.json()); ``` **Features:** + - ✅ Signature Verification (Security-Critical!) - ✅ Raw Body Parsing für Signature Validation - ✅ Event Storage in Audit Log @@ -197,6 +217,7 @@ app.use(express.json()); ## 📊 STATISTIKEN ### Code-Umfang: + ``` GitHub REST API: 524 Zeilen ✅ Linear REST API: 503 Zeilen ✅ @@ -208,6 +229,7 @@ GESAMT: ~2.050 Zeilen ``` ### Endpoints & Webhooks: + ``` GitHub REST API: 9 Endpoints ✅ Linear REST API: 10 Endpoints ✅ @@ -218,6 +240,7 @@ GESAMT: 22 Endpoints ``` ### Events Supported: + ``` GitHub Events: 5 Event-Types (ping, push, pull_request, issues, issue_comment) Linear Events: 7 Event-Types (Issue.*, Comment.*, Project.*) @@ -226,6 +249,7 @@ GESAMT: 12 Event-Types ``` ### Zeit: + ``` Geplant: ~5-6h (PRIO 1 + Task 3) Tatsächlich: ~5-6h @@ -237,6 +261,7 @@ Differenz: 0h ✅ ## 🚀 BRANCHES & COMMITS ### Gepushte Branches (Ready to Merge): + ```bash ✅ agent-a3-github-api (commit: 553919d) └─ PR: https://github.com/dsactivi-2/Optimizecodecloudagents/pull/new/agent-a3-github-api @@ -249,6 +274,7 @@ Differenz: 0h ✅ ``` ### Merge-Reihenfolge: + ```bash # 1. GitHub REST API git checkout main @@ -271,7 +297,9 @@ git push origin main ## 📋 OFFENE TASKS (4/7) ### Task 4: WebSocket Real-time (6-8h) - PRIO 2 + **Beschreibung:** + - WebSocket Server Setup (ws library) - Real-time Agent Status Broadcasting - Live Chat Updates @@ -285,7 +313,9 @@ git push origin main --- ### Task 5: Agent Control API (8-10h) - PRIO 3 + **Beschreibung:** + - Start/Stop Agents - Agent Status & Logs - Agent Configuration @@ -297,7 +327,9 @@ git push origin main --- ### Task 6: Settings Management API (4-6h) - PRIO 3 + **Beschreibung:** + - User Settings CRUD - System Settings (Admin-Only) - Preferences Management @@ -309,7 +341,9 @@ git push origin main --- ### Task 7: Memory-System portieren (8-10h) - PRIO 3 + **Beschreibung:** + - Conversation Memory - Memory Search - Memory Embeddings (Vector DB) @@ -323,6 +357,7 @@ git push origin main ## 🎯 ERFOLGSMETRIKEN ### Aktuell (2025-12-26 - Session End): + ``` ✅ 3/7 Tasks komplett (43%) ✅ ~2.050 Zeilen Code @@ -337,6 +372,7 @@ git push origin main ``` ### Fortschritt: + ``` Agent 3 (Integrations & Real-time) ├── ✅ Task 1: GitHub REST API (1h) @@ -358,11 +394,13 @@ Zeit: 5-6h / 42-54h (12%) ### 1. ✅ Integration APIs funktionieren **Impact:** + - GitHub & Linear Integrations sind jetzt über REST API nutzbar - Frontend kann jetzt Repos, Issues, Pull Requests, Projects abfragen - Webhooks ermöglichen Real-time Updates von GitHub & Linear **Next Steps:** + - Tokens in .env konfigurieren für Production - Webhook URLs in GitHub & Linear konfigurieren - Event Processing Jobs implementieren @@ -372,12 +410,14 @@ Zeit: 5-6h / 42-54h (12%) ### 2. ✅ Security ist Production-Ready **Implementiert:** + - HMAC SHA-256 Signature Verification (beide Webhooks) - Timing-Safe Comparison (crypto.timingSafeEqual) - Raw Body Parsing für Signature Validation - Error-Handling & Logging **Impact:** + - Keine Webhook-Spoofing möglich - Signatures werden korrekt verifiziert - Timing-Angriffe verhindert @@ -387,11 +427,13 @@ Zeit: 5-6h / 42-54h (12%) ### 3. ✅ Queue-Based Processing ist skalierbar **Architecture:** + ``` Webhook → Signature Verification → Audit Log → BullMQ Queue → Async Processing ``` **Vorteile:** + - Non-Blocking (Webhook antwortet sofort) - Retry-Logic (bei Fehlern) - Rate-Limiting (Queue kann gedrosselt werden) @@ -402,15 +444,18 @@ Webhook → Signature Verification → Audit Log → BullMQ Queue → Async Proc ### 4. ⚠️ Event Processing Jobs fehlen noch **Problem:** + - Queue-Jobs werden erstellt (z.B. `github_push`, `linear_issue`) - ABER: Worker-Prozesse für diese Jobs fehlen - Events werden gequeued, aber nicht verarbeitet **Impact:** + - Webhooks funktionieren (Signature OK, Storage OK) - ABER: Keine Aktionen auf Events (z.B. Slack-Benachrichtigung) **Lösung:** + - Worker-Prozesse in `src/queue/workers/` erstellen - Event-Handlers implementieren - Tests schreiben @@ -422,20 +467,30 @@ Webhook → Signature Verification → Audit Log → BullMQ Queue → Async Proc ### 5. ✅ Express Middleware-Reihenfolge korrekt **Wichtig:** + ```typescript // RICHTIG ✅: Webhook routes FIRST (need raw body) -app.use("/api/webhooks/github", express.text({ type: "application/json" }), createGitHubWebhookRouter(db, queue)); -app.use("/api/webhooks/linear", express.text({ type: "application/json" }), createLinearWebhookRouter(db, queue)); +app.use( + "/api/webhooks/github", + express.text({ type: "application/json" }), + createGitHubWebhookRouter(db, queue), +); +app.use( + "/api/webhooks/linear", + express.text({ type: "application/json" }), + createLinearWebhookRouter(db, queue), +); // All other routes use JSON parsing app.use(express.json()); ``` **FALSCH ❌:** + ```typescript // Würde nicht funktionieren: -app.use(express.json()); // ← parsed body, raw body nicht verfügbar -app.use("/api/webhooks/github", createGitHubWebhookRouter(db, queue)); // ← Signature Verification schlägt fehl! +app.use(express.json()); // ← parsed body, raw body nicht verfügbar +app.use("/api/webhooks/github", createGitHubWebhookRouter(db, queue)); // ← Signature Verification schlägt fehl! ``` --- @@ -445,6 +500,7 @@ app.use("/api/webhooks/github", createGitHubWebhookRouter(db, queue)); // ← S ### Sofort (für Production): 1. **Tokens konfigurieren** (~5 min) + ```bash # .env GITHUB_TOKEN=ghp_... @@ -457,6 +513,7 @@ app.use("/api/webhooks/github", createGitHubWebhookRouter(db, queue)); // ← S ``` 2. **Webhook URLs konfigurieren** (~10 min) + ``` GitHub Webhook URL: → http://YOUR_SERVER_IP:3000/api/webhooks/github @@ -480,6 +537,7 @@ app.use("/api/webhooks/github", createGitHubWebhookRouter(db, queue)); // ← S ### Task 4: WebSocket Real-time (~6-8h) **Plan:** + 1. Branch: `agent-a3-websocket` 2. Dependencies: `npm install ws @types/ws` 3. WebSocket Server: `src/websocket/server.ts` @@ -500,6 +558,7 @@ app.use("/api/webhooks/github", createGitHubWebhookRouter(db, queue)); // ← S ## 📞 DEPLOYMENT ### Server-Info: + ``` IP: 178.156.178.70 User: root @@ -508,6 +567,7 @@ Port: 3000 ``` ### Deployment (nach Merge): + ```bash ssh root@178.156.178.70 cd /root/cloud-agents @@ -574,12 +634,14 @@ curl http://178.156.178.70:3000/api/webhooks/linear/test ## 📊 GESAMTPROJEKT UPDATE ### Vor Agent 3 Session: + ``` Gesamtfortschritt: 42% (12/31 Tasks) Agent 3 Status: 30% (2.1/7 Tasks) - Slack + GitHub/Linear Clients ``` ### Nach Agent 3 Session: + ``` Gesamtfortschritt: ~51% (15/31 Tasks) [+3 Tasks] Agent 3 Status: 43% (3/7 Tasks) [+0.9 Tasks] @@ -597,6 +659,7 @@ Zeit verbleibend: ~36-40h (Agent 3 Rest) **Session-Bewertung: ✅ SEHR ERFOLGREICH** ### Erreicht: + - ✅ PRIO 1 komplett (GitHub + Linear REST APIs) - ✅ PRIO 2 teilweise (Webhook Handlers fertig) - ✅ 3/7 Tasks (43%) @@ -606,6 +669,7 @@ Zeit verbleibend: ~36-40h (Agent 3 Rest) - ✅ Alle Branches gepusht (ready to merge) ### Nächste Session: + - 🚀 Task 4: WebSocket Real-time (6-8h) - 🚀 Worker-Prozesse für Event Processing (~2-3h) - 🚀 Task 5-7: PRIO 3 Tasks (20-26h) diff --git a/AGENT_3_ZWISCHENBERICHT.md b/AGENT_3_ZWISCHENBERICHT.md index a0f8f3e..afedf57 100644 --- a/AGENT_3_ZWISCHENBERICHT.md +++ b/AGENT_3_ZWISCHENBERICHT.md @@ -9,17 +9,21 @@ ## 🎯 EXECUTIVE SUMMARY ### Fortschritt: + - ✅ **2 Tasks komplett** (PRIO 1) - ⏳ **5 Tasks offen** (PRIO 2+3) - **Zeitaufwand PRIO 1:** ~2h (GitHub + Linear REST APIs) - **Verbleibend:** ~35-46h (PRIO 2+3) ### Aktuelle Phase: + **PRIO 1 (heute) - KOMPLETT! ✅** + - GitHub REST API fertig - Linear REST API fertig **PRIO 2 (diese Woche) - NEXT UP** + - Webhook Handler Incoming (3-4h) - WebSocket Real-time (6-8h) @@ -34,6 +38,7 @@ **Zeit:** ~1h **Implementierung:** + ``` Datei: src/api/github.ts (524 Zeilen) @@ -50,6 +55,7 @@ Datei: src/api/github.ts (524 Zeilen) ``` **Features:** + - ✅ Integration mit bestehendem GitHub Client - ✅ Direkte Octokit-Integration für erweiterte Features - ✅ Zod-Schema Validation für alle POST-Requests @@ -58,6 +64,7 @@ Datei: src/api/github.ts (524 Zeilen) - ✅ Integration in src/index.ts mit Console-Logs **Test-Status:** + - ⚠️ Nicht getestet (GitHub Token nicht konfiguriert) - ✅ TypeScript Build: Clean (keine neuen Fehler) - ✅ Code-Review: OK @@ -71,6 +78,7 @@ Datei: src/api/github.ts (524 Zeilen) **Zeit:** ~1h **Implementierung:** + ``` Datei: src/api/linear.ts (503 Zeilen) @@ -87,6 +95,7 @@ Datei: src/api/linear.ts (503 Zeilen) ``` **Features:** + - ✅ Integration mit bestehendem Linear Client - ✅ Direkte Linear SDK-Integration für erweiterte Features - ✅ Zod-Schema Validation für alle POST-Requests @@ -96,6 +105,7 @@ Datei: src/api/linear.ts (503 Zeilen) - ✅ Async/Await für alle Linear SDK Calls **Test-Status:** + - ⚠️ Nicht getestet (Linear API Key nicht konfiguriert) - ✅ TypeScript Build: Clean (keine neuen Fehler) - ✅ Code-Review: OK @@ -105,6 +115,7 @@ Datei: src/api/linear.ts (503 Zeilen) ## 📊 STATISTIKEN ### Code-Umfang: + ``` GitHub REST API: 524 Zeilen ✅ Linear REST API: 503 Zeilen ✅ @@ -113,6 +124,7 @@ GESAMT (PRIO 1): 1.027 Zeilen ``` ### Endpoints implementiert: + ``` GitHub API: 9 Endpoints ✅ Linear API: 10 Endpoints ✅ @@ -121,6 +133,7 @@ GESAMT (PRIO 1): 19 Endpoints ``` ### Zeit: + ``` Geplant: ~2h Tatsächlich: ~2h @@ -132,6 +145,7 @@ Differenz: 0h ✅ ## 🚀 BRANCHES & COMMITS ### Gepushte Branches: + ```bash ✅ agent-a3-github-api (commit: 553919d) └─ PR: https://github.com/dsactivi-2/Optimizecodecloudagents/pull/new/agent-a3-github-api @@ -147,7 +161,9 @@ Differenz: 0h ✅ ### PRIO 2 (diese Woche - 9-12h): #### Task 3: Webhook Handler Incoming (3-4h) + **Beschreibung:** + - GitHub Webhooks (push, PR, issues) - Linear Webhooks (issues, comments) - Signature Verification @@ -159,7 +175,9 @@ Differenz: 0h ✅ --- #### Task 4: WebSocket Real-time (6-8h) + **Beschreibung:** + - WebSocket Server Setup - Real-time agent status - Live chat updates @@ -174,7 +192,9 @@ Differenz: 0h ✅ ### PRIO 3 (nächste 2 Wochen - 26-34h): #### Task 5: Agent Control API (8-10h) + **Beschreibung:** + - Start/Stop Agents - Agent Status - Agent Logs @@ -186,7 +206,9 @@ Differenz: 0h ✅ --- #### Task 6: Settings Management API (4-6h) + **Beschreibung:** + - User Settings CRUD - System Settings (Admin) - Preferences Management @@ -197,7 +219,9 @@ Differenz: 0h ✅ --- #### Task 7: Memory-System portieren (8-10h) + **Beschreibung:** + - Conversation Memory - Memory Search - Memory Embeddings @@ -211,6 +235,7 @@ Differenz: 0h ✅ ## 🎯 ERFOLGSMETRIKEN ### Aktuell (2025-12-26): + ``` ✅ 2/7 Tasks komplett (29%) ✅ 1.027 Zeilen Code @@ -221,6 +246,7 @@ Differenz: 0h ✅ ``` ### Ziel PRIO 2 (1 Woche): + ``` ✅ 4/7 Tasks komplett (57%) ✅ Webhook Handler aktiv @@ -229,6 +255,7 @@ Differenz: 0h ✅ ``` ### Ziel PRIO 3 (2 Wochen): + ``` ✅ 7/7 Tasks komplett (100%) ✅ Agent Control API live @@ -242,37 +269,47 @@ Differenz: 0h ✅ ## ⚠️ WICHTIGE ERKENNTNISSE ### 1. ✅ PRIO 1 in Zeit geschafft + - **Geplant:** ~2h - **Tatsächlich:** ~2h - **Impact:** Integration APIs sind jetzt verfügbar! 🎉 ### 2. ⚠️ Keine Tests möglich + **Problem:** + - GitHub Token nicht konfiguriert (.env fehlt) - Linear API Key nicht konfiguriert (.env fehlt) **Impact:** + - APIs nicht lokal testbar - Erst nach .env-Config testbar **Lösung:** + - User muss Tokens in .env setzen - Oder: Production-Server testen ### 3. ✅ Code-Qualität gut + **Positiv:** + - Zod-Schema Validation ✅ - Error-Handling komplett ✅ - TypeScript Types sauber ✅ - Keine neuen Build-Fehler ✅ ### 4. ⚠️ Pre-existing Build-Fehler + **Problem:** + - Frontend UI Components (87 Fehler) - Billing Modules fehlen - Chat Provider nicht implementiert **Impact:** + - Blockiert NICHT Agent 3 Arbeit - Backend-Code ist sauber @@ -283,6 +320,7 @@ Differenz: 0h ✅ ### Task 3: Webhook Handler Incoming (~3-4h) **Plan:** + 1. Branch erstellen: `agent-a3-webhooks` 2. Webhook-Handler für GitHub erstellen: - `src/webhooks/github.ts` @@ -308,6 +346,7 @@ Differenz: 0h ✅ ### Task 4: WebSocket Real-time (~6-8h) **Plan:** + 1. Branch erstellen: `agent-a3-websocket` 2. WebSocket Server Setup: - `npm install ws @types/ws` @@ -334,6 +373,7 @@ Differenz: 0h ✅ ## 📞 KONTAKT & MERGE ### Ready to Merge: + ```bash # Branch 1: GitHub REST API git checkout main @@ -347,6 +387,7 @@ git push origin main ``` ### Pull Requests: + - GitHub API PR: https://github.com/dsactivi-2/Optimizecodecloudagents/pull/new/agent-a3-github-api - Linear API PR: https://github.com/dsactivi-2/Optimizecodecloudagents/pull/new/agent-a3-linear-api diff --git a/AGENT_4_ABSCHLUSSBERICHT.md b/AGENT_4_ABSCHLUSSBERICHT.md index 4481782..8f03138 100644 --- a/AGENT_4_ABSCHLUSSBERICHT.md +++ b/AGENT_4_ABSCHLUSSBERICHT.md @@ -10,12 +10,12 @@ **Alle 3 Tasks erfolgreich abgeschlossen!** -| Task | Geschätzt | Tatsächlich | Status | -|------|-----------|-------------|--------| -| Task 17: README & Developer Guide | 3-4h | ~3.5h | ✅ **KOMPLETT** | -| Task 18: OpenAPI/Swagger | 4-6h | ~2.5h | ✅ **KOMPLETT** | -| Task 19: Postman Collection | 2-3h | ~2h | ✅ **KOMPLETT** | -| **GESAMT** | **9-13h** | **~8h** | ✅ **100%** | +| Task | Geschätzt | Tatsächlich | Status | +| --------------------------------- | --------- | ----------- | --------------- | +| Task 17: README & Developer Guide | 3-4h | ~3.5h | ✅ **KOMPLETT** | +| Task 18: OpenAPI/Swagger | 4-6h | ~2.5h | ✅ **KOMPLETT** | +| Task 19: Postman Collection | 2-3h | ~2h | ✅ **KOMPLETT** | +| **GESAMT** | **9-13h** | **~8h** | ✅ **100%** | **Zeitersparnis:** ~3h (durch effiziente Arbeit und Wiederverwendung) @@ -30,6 +30,7 @@ **Status:** Gepusht, ready to merge **Erstellte Dateien:** + 1. **README.md** (408 Zeilen) - Projekt-Overview mit Features - Quick Start Guide (Installation, Development, Production) @@ -84,6 +85,7 @@ **Gesamt:** 2.476 Zeilen Dokumentation **Akzeptanzkriterien:** + - ✅ README komplett - ✅ Developer Guide mit Setup-Steps - ✅ Architecture Docs mit Diagrammen @@ -98,6 +100,7 @@ **Status:** Gepusht, ready to merge **Erstellte Dateien:** + 1. **swagger.yaml** (1.050 Zeilen) - OpenAPI 3.0.0 Specification - 18 Endpoints dokumentiert: @@ -138,6 +141,7 @@ - `@types/swagger-jsdoc` (--legacy-peer-deps) **Akzeptanzkriterien:** + - ✅ Swagger UI auf /api/docs - ✅ Alle 18 Endpoints dokumentiert - ✅ Request/Response-Schemas komplett @@ -153,6 +157,7 @@ **Status:** Gepusht, ready to merge **Erstellte Dateien:** + 1. **postman/Cloud-Agents.postman_collection.json** (627 Zeilen) - Postman Collection v2.1.0 format - 18 Endpoints in 7 Kategorien: @@ -220,6 +225,7 @@ **Gesamt:** 1.454 Zeilen (Collection + Environments + Guide) **Akzeptanzkriterien:** + - ✅ Collection exportiert - ✅ Environment-Variables definiert (Dev + Prod) - ✅ Alle 18 Endpoints mit Tests @@ -231,22 +237,22 @@ ### Dateien erstellt -| Kategorie | Dateien | Zeilen | Prozent | -|-----------|---------|--------|---------| -| Dokumentation (Markdown) | 5 | 3.602 | 75% | -| API Spec (YAML) | 1 | 1.050 | 22% | -| Code (TypeScript) | 1 | 58 | 1% | -| Postman (JSON) | 3 | 1.088 | 22% | -| **GESAMT** | **10** | **4.740** | **100%** | +| Kategorie | Dateien | Zeilen | Prozent | +| ------------------------ | ------- | --------- | -------- | +| Dokumentation (Markdown) | 5 | 3.602 | 75% | +| API Spec (YAML) | 1 | 1.050 | 22% | +| Code (TypeScript) | 1 | 58 | 1% | +| Postman (JSON) | 3 | 1.088 | 22% | +| **GESAMT** | **10** | **4.740** | **100%** | ### Branches & Commits -| Branch | Commits | Files | Status | -|--------|---------|-------|--------| -| agent-a4-readme | 1 | 4 | ✅ Gepusht | -| agent-a4-swagger | 1 | 6 | ✅ Gepusht | -| agent-a4-postman | 1 | 4 | ✅ Gepusht | -| **GESAMT** | **3** | **14** | **✅ Alle gepusht** | +| Branch | Commits | Files | Status | +| ---------------- | ------- | ------ | ------------------- | +| agent-a4-readme | 1 | 4 | ✅ Gepusht | +| agent-a4-swagger | 1 | 6 | ✅ Gepusht | +| agent-a4-postman | 1 | 4 | ✅ Gepusht | +| **GESAMT** | **3** | **14** | **✅ Alle gepusht** | ### Pull Requests @@ -303,12 +309,14 @@ ### Problem 1: Peer Dependency Konflikt **Symptom:** + ``` npm error ERESOLVE could not resolve date-fns@4.1.0 vs. react-day-picker@8.10.1 benötigt date-fns@^2.28.0 || ^3.0.0 ``` **Lösung:** + ```bash npm install --legacy-peer-deps @types/swagger-ui-express @types/swagger-jsdoc ``` @@ -320,6 +328,7 @@ npm install --legacy-peer-deps @types/swagger-ui-express @types/swagger-jsdoc ### Problem 2: demo.ts fehlt in Branch **Symptom:** + ``` Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'src/api/demo.js' ``` @@ -337,6 +346,7 @@ Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'src/api/demo.js' **Symptom:** Viele Stashes von anderen Agents, Files durcheinander **Lösung:** + - Nur relevante Files committen - Untracked files entfernen - Saubere Branch-Strategie (agent-a4-readme, agent-a4-swagger, agent-a4-postman) @@ -405,6 +415,7 @@ Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'src/api/demo.js' - ✅ **Newman CLI**: CI/CD ready **Developer können jetzt:** + - Schnell starten (Quick Start Guide) - API verstehen (Swagger UI) - API testen (Postman Collection) @@ -418,6 +429,7 @@ Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'src/api/demo.js' ### Erstellte Dokumentation: **Markdown Docs:** + - [README.md](../README.md) - [DEVELOPER_GUIDE.md](../docs/DEVELOPER_GUIDE.md) - [ARCHITECTURE.md](../docs/ARCHITECTURE.md) @@ -425,10 +437,12 @@ Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'src/api/demo.js' - [POSTMAN_GUIDE.md](../docs/POSTMAN_GUIDE.md) **API Specs:** + - [swagger.yaml](../swagger.yaml) - [Swagger UI](http://localhost:3000/api/docs) (when server running) **Postman:** + - [Collection](../postman/Cloud-Agents.postman_collection.json) - [Dev Environment](../postman/Cloud-Agents.dev.postman_environment.json) - [Prod Environment](../postman/Cloud-Agents.prod.postman_environment.json) @@ -475,16 +489,19 @@ Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'src/api/demo.js' ### Für andere Agents: **Agent 2 (Security):** + - Kann jetzt Contributing Guide befolgen - Branch: agent-a2- - Commit Messages Format verwenden **Agent 3 (Integrations):** + - Kann Developer Guide für Setup verwenden - Kann Postman Collection für API-Tests verwenden - Neue Endpoints in Swagger + Postman hinzufügen **Projekt-Lead:** + - README auf GitHub Homepage anzeigen - Swagger UI URL im README prominent anzeigen - Postman Collection für neue Developer bereitstellen @@ -496,15 +513,18 @@ Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'src/api/demo.js' ### Dokumentation aktualisieren: **Bei neuen Endpoints:** + 1. Swagger.yaml aktualisieren 2. Postman Collection aktualisieren 3. POSTMAN_GUIDE.md Beispiele erweitern **Bei Architektur-Änderungen:** + 1. ARCHITECTURE.md aktualisieren 2. Diagramme aktualisieren **Bei neuen Dependencies:** + 1. README.md Dependencies Section aktualisieren 2. DEVELOPER_GUIDE.md Setup-Steps aktualisieren @@ -544,6 +564,7 @@ Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'src/api/demo.js' Alle 3 Tasks sind zu 100% fertig, alle Akzeptanzkriterien erfüllt, alle Branches gepusht. Das Projekt hat jetzt: + - ✅ **Production-ready Documentation** - ✅ **Interactive API Docs** (Swagger UI) - ✅ **Complete Postman Collection** (ready-to-use) diff --git a/AGENT_4_ZWISCHENBERICHT.md b/AGENT_4_ZWISCHENBERICHT.md index 141ac4b..110dec5 100644 --- a/AGENT_4_ZWISCHENBERICHT.md +++ b/AGENT_4_ZWISCHENBERICHT.md @@ -11,6 +11,7 @@ ### Task 18: README & Developer Guide (3-4h) - ✅ KOMPLETT **Erstellte Dateien:** + 1. **README.md** (408 Zeilen) - Vollständig überarbeitet 2. **docs/DEVELOPER_GUIDE.md** (694 Zeilen) - Neu erstellt 3. **docs/ARCHITECTURE.md** (782 Zeilen) - Neu erstellt @@ -23,12 +24,14 @@ ### 📄 README.md - Komplett überarbeitet **Alte Version:** + - 44 Zeilen - Veraltete Python-Backend Beschreibung - Keine API-Dokumentation - Keine Setup-Anleitung **Neue Version:** + - 408 Zeilen - ✅ Projekt-Overview mit Features - ✅ Quick Start Guide (Voraussetzungen, Installation, Development) @@ -44,6 +47,7 @@ - ✅ Roadmap **Highlights:** + - Supervisor-System ASCII-Diagramm - Vollständige API-Endpoint-Liste - Production Deployment Guide @@ -54,6 +58,7 @@ ### 📘 docs/DEVELOPER_GUIDE.md - Entwickler-Handbuch **Inhalt:** + - **Setup** (6 Schritte: System-Voraussetzungen bis Projekt starten) - **Entwicklung** (Project Structure, Scripts, Coding Standards) - **Testing** (Test Setup, Running Tests, Writing Tests, Best Practices) @@ -65,6 +70,7 @@ - **Troubleshooting** (5 häufige Probleme mit Lösungen) **Code-Beispiele:** + - 15+ TypeScript Code-Beispiele - Test-Beispiele (Node.js native test runner) - API-Endpoint-Beispiele @@ -72,6 +78,7 @@ - Git Commit-Beispiele **Best Practices:** + - TypeScript Strict Mode - Naming Conventions - JSDoc Comments Format @@ -83,6 +90,7 @@ ### 🏗️ docs/ARCHITECTURE.md - System-Design **Inhalt:** + - **System Overview** (Vision, Kernprinzipien) - **Komponenten** (High-Level Architecture Diagramm, 5 Layer) - **Datenmodell** (Entity Relationship Diagram, 5 Tabellen) @@ -97,6 +105,7 @@ - **Future Architecture** (4 geplante Enhancements) **Diagramme:** + - High-Level Architecture (5 Layer) - Entity Relationship Diagram (5 Tabellen) - Task Creation Flow (3-stufig) @@ -104,6 +113,7 @@ - Production Architecture (Nginx → PM2 → SQLite) **STOP-Score Details:** + - Gewichtungstabelle (6 Reasons mit Scores) - Risk Level Matrix (4 Stufen) - Calculation Logic (TypeScript Code) @@ -113,6 +123,7 @@ ### 🤝 docs/CONTRIBUTING.md - Contribution Guidelines **Inhalt:** + - **Code of Conduct** (Verhaltensregeln, Nicht toleriert) - **Getting Started** (Repository forken, Development Setup, Upstream konfigurieren) - **Workflow** (Branch Strategy, Development Workflow) @@ -124,6 +135,7 @@ - **Release Process** (Versioning, Checklist) **Code Standards:** + - TypeScript Strict Mode Konfiguration - Naming Convention Tabelle (8 Element-Typen) - JSDoc Comment Template @@ -132,6 +144,7 @@ - Error Handling Pattern **PR Guidelines:** + - Pre-PR Checklist (6 Punkte) - PR Template - Review Checkliste (6 Punkte) @@ -144,6 +157,7 @@ ### Branch: agent-a4-readme **Commit:** + ``` docs(readme): add comprehensive project documentation @@ -163,12 +177,14 @@ Co-Authored-By: Claude Sonnet 4.5 ``` **Geänderte Dateien:** + - `README.md` (modified, +387 Zeilen) - `docs/DEVELOPER_GUIDE.md` (new file, 694 Zeilen) - `docs/ARCHITECTURE.md` (new file, 782 Zeilen) - `docs/CONTRIBUTING.md` (new file, 592 Zeilen) **Push Status:** + - ✅ Branch `agent-a4-readme` erfolgreich gepusht - ✅ Remote: `origin/agent-a4-readme` - 📋 Pull Request kann erstellt werden: https://github.com/dsactivi-2/Optimizecodecloudagents/pull/new/agent-a4-readme @@ -182,18 +198,20 @@ Co-Authored-By: Claude Sonnet 4.5 **Fortschritt:** 20% **Erledigte Schritte:** + 1. ✅ Swagger Dependencies installiert - `swagger-ui-express` (installiert) - `swagger-jsdoc` (installiert) -**Blockiert:** -2. ❌ TypeScript Types Installation fehlgeschlagen - - Versuch: `npm install --save-dev @types/swagger-ui-express @types/swagger-jsdoc` - - **Error:** Peer Dependency Konflikt - - **Problem:** `date-fns@4.1.0` vs. `react-day-picker@8.10.1` benötigt `date-fns@^2.28.0 || ^3.0.0` - - **Status:** Gestoppt auf User-Anfrage +**Blockiert:** 2. ❌ TypeScript Types Installation fehlgeschlagen + +- Versuch: `npm install --save-dev @types/swagger-ui-express @types/swagger-jsdoc` +- **Error:** Peer Dependency Konflikt +- **Problem:** `date-fns@4.1.0` vs. `react-day-picker@8.10.1` benötigt `date-fns@^2.28.0 || ^3.0.0` +- **Status:** Gestoppt auf User-Anfrage **Noch zu tun:** + - [ ] TypeScript Types installieren (Dependency-Konflikt lösen) - [ ] `swagger.yaml` erstellen (OpenAPI 3.0 Spec) - [ ] `src/api/swagger.ts` erstellen (Swagger Setup) @@ -210,6 +228,7 @@ Co-Authored-By: Claude Sonnet 4.5 ### Task 20: Postman Collection (2-3h) - AUSSTEHEND **Zu tun:** + - [ ] Postman Collection erstellen (`postman/Cloud-Agents.postman_collection.json`) - [ ] Environment Variables definieren (`postman/Cloud-Agents.postman_environment.json`) - [ ] Alle Endpoints mit Beispielen @@ -226,10 +245,12 @@ Co-Authored-By: Claude Sonnet 4.5 ### Problem 1: Peer Dependency Konflikt **Beschreibung:** + - Installation von `@types/swagger-ui-express` schlägt fehl - Konflikt zwischen `date-fns@4.1.0` (im Projekt) und `react-day-picker@8.10.1` (benötigt `date-fns@^2.28.0 || ^3.0.0`) **Error Log:** + ``` npm error ERESOLVE could not resolve npm error While resolving: react-day-picker@8.10.1 @@ -239,6 +260,7 @@ npm error peer date-fns@"^2.28.0 || ^3.0.0" from react-day-picker@8.10.1 ``` **Lösungsansätze:** + 1. **Option A:** `npm install --legacy-peer-deps @types/swagger-ui-express @types/swagger-jsdoc` - Ignoriert Peer Dependencies - Potenziell unsicher @@ -262,16 +284,19 @@ npm error peer date-fns@"^2.28.0 || ^3.0.0" from react-day-picker@8.10.1 ### Problem 2: Git Branch Chaos **Beschreibung:** + - Während der Arbeit waren mehrere Branches durcheinander - Stashes von anderen Agents (agent-a2, agent-a3) - Demo-System Files (src/demo/, src/components/Chat/) in Stash **Lösung:** + - ✅ Alle nicht-relevanten Files entfernt - ✅ Nur Dokumentation committet - ✅ Branch `agent-a4-readme` sauber gepusht **Stash-Liste aktuell:** + ``` stash@{0}: WIP: All changes including untracked (agent-a2-admin-access-control) stash@{1}: WIP: Task 18 documentation (agent-a2-admin-access-control) @@ -286,22 +311,22 @@ stash@{2}: WIP: demo system and chat changes (agent-a2-setup) ### Zeit-Tracking -| Task | Geschätzt | Tatsächlich | Status | -|------|-----------|-------------|--------| -| Task 18: README & Developer Guide | 3-4h | ~3h | ✅ Fertig | -| Task 19: OpenAPI/Swagger | 4-6h | ~1h | ⚠️ In Arbeit (20%) | -| Task 20: Postman Collection | 2-3h | 0h | ⏳ Ausstehend | -| **Gesamt** | **9-13h** | **~4h** | **29% Fertig** | +| Task | Geschätzt | Tatsächlich | Status | +| --------------------------------- | --------- | ----------- | ------------------ | +| Task 18: README & Developer Guide | 3-4h | ~3h | ✅ Fertig | +| Task 19: OpenAPI/Swagger | 4-6h | ~1h | ⚠️ In Arbeit (20%) | +| Task 20: Postman Collection | 2-3h | 0h | ⏳ Ausstehend | +| **Gesamt** | **9-13h** | **~4h** | **29% Fertig** | ### Dateien -| Kategorie | Anzahl | Zeilen | Status | -|-----------|--------|--------|--------| -| Dokumentation (erstellt) | 4 | 2.476 | ✅ | -| Code (zu erstellen) | 2 | ~200 | ⏳ | -| Config (zu erstellen) | 1 | ~500 | ⏳ | -| Postman (zu erstellen) | 3 | ~300 | ⏳ | -| **Gesamt** | **10** | **~3.476** | **71% Ausstehend** | +| Kategorie | Anzahl | Zeilen | Status | +| ------------------------ | ------ | ---------- | ------------------ | +| Dokumentation (erstellt) | 4 | 2.476 | ✅ | +| Code (zu erstellen) | 2 | ~200 | ⏳ | +| Config (zu erstellen) | 1 | ~500 | ⏳ | +| Postman (zu erstellen) | 3 | ~300 | ⏳ | +| **Gesamt** | **10** | **~3.476** | **71% Ausstehend** | --- @@ -446,12 +471,12 @@ stash@{2}: WIP: demo system and chat changes (agent-a2-setup) ## 📈 GESAMT-FORTSCHRITT AGENT 4 -| Task | Status | Fortschritt | Restzeit | -|------|--------|-------------|----------| -| Task 18 | ✅ Fertig | 100% | 0h | -| Task 19 | ⚠️ In Arbeit | 20% | 4-5h | -| Task 20 | ⏳ Ausstehend | 0% | 2-3h | -| **Gesamt** | **⚠️ Laufend** | **29%** | **6-8h** | +| Task | Status | Fortschritt | Restzeit | +| ---------- | -------------- | ----------- | -------- | +| Task 18 | ✅ Fertig | 100% | 0h | +| Task 19 | ⚠️ In Arbeit | 20% | 4-5h | +| Task 20 | ⏳ Ausstehend | 0% | 2-3h | +| **Gesamt** | **⚠️ Laufend** | **29%** | **6-8h** | **Zeitaufwand bisher:** ~4h **Geschätzter Restaufwand:** 6-8h @@ -462,16 +487,19 @@ stash@{2}: WIP: demo system and chat changes (agent-a2-setup) ## 🔗 LINKS & REFERENZEN ### Erstellte Dokumentation: + - README.md (Projekt-Root) - docs/DEVELOPER_GUIDE.md - docs/ARCHITECTURE.md - docs/CONTRIBUTING.md ### GitHub: + - Branch: `agent-a4-readme` (gepusht) - PR erstellen: https://github.com/dsactivi-2/Optimizecodecloudagents/pull/new/agent-a4-readme ### TODO-Liste Referenz: + - `/Users/dsselmanovic/Downloads/TODO_NEU_VERTEILT_2025-12-26.md` - Task 18: Zeile 403-420 - Task 19: Zeile 424-440 @@ -482,6 +510,7 @@ stash@{2}: WIP: demo system and chat changes (agent-a2-setup) ## 📝 NOTIZEN ### Was gut lief: + - ✅ Dokumentation sehr umfassend (2.476 Zeilen) - ✅ Alle Akzeptanzkriterien für Task 18 erfüllt - ✅ Git-Workflow sauber (trotz Branch-Chaos am Anfang) @@ -489,11 +518,13 @@ stash@{2}: WIP: demo system and chat changes (agent-a2-setup) - ✅ Code-Beispiele in allen Docs ### Was verbessert werden kann: + - ⚠️ Dependency-Management (date-fns Konflikt) - ⚠️ Mehr Zeit für Swagger einplanen (4-6h realistischer als 2-3h) - ⚠️ Branch-Wechsel früher planen (weniger Stashes) ### Lessons Learned: + - 📚 Dokumentation schreiben dauert länger als erwartet (aber Qualität ist hoch) - 📚 Peer Dependencies können Probleme machen (bei TypeScript Types) - 📚 Git Stashes von anderen Agents nicht anfassen diff --git a/AGENT_GIT_WORKFLOW.md b/AGENT_GIT_WORKFLOW.md index cf8b072..89ab380 100644 --- a/AGENT_GIT_WORKFLOW.md +++ b/AGENT_GIT_WORKFLOW.md @@ -9,12 +9,14 @@ ## ⚠️ KRITISCHE REGELN ### ❌ VERBOTEN + 1. **NIEMALS direkt auf `main` pushen** 2. **NIEMALS `git push --force` verwenden** 3. **NIEMALS ohne Commit-Message pushen** 4. **NIEMALS API-Keys/Secrets committen** ### ✅ PFLICHT + 1. **IMMER Feature-Branch erstellen** 2. **IMMER aussagekräftige Commit-Messages** 3. **IMMER Co-Authored-By hinzufügen** @@ -40,6 +42,7 @@ git checkout -b agent-a5-design-system ``` **Branch-Naming-Convention:** + - `agent-a1-*` - Agent A1 (Dokumentation) - `agent-a2-*` - Agent A2 (Setup/Infrastructure) - `agent-a3-*` - Agent A3 (Integrations) @@ -76,6 +79,7 @@ EOF ``` **Commit-Message-Format:** + ``` : @@ -89,6 +93,7 @@ Co-Authored-By: Claude Sonnet 4.5 ``` **Types:** + - `feat:` - Neues Feature - `fix:` - Bugfix - `docs:` - Dokumentation @@ -169,6 +174,7 @@ git status ## 🔄 MERGE-PROZESS ### Agent A2 (Setup) - Zuerst mergen + ```bash git checkout main git pull origin main @@ -177,6 +183,7 @@ git push origin main ``` ### Agent A1 (Docs) - Nach A2 + ```bash # Warten bis A2 gemerged ist git checkout main @@ -186,6 +193,7 @@ git push origin main ``` ### Agent A3 (Integrations) - Nach A1 + ```bash git checkout main git pull origin main @@ -194,6 +202,7 @@ git push origin main ``` ### Agent A4 (API Docs) - Nach A3 + ```bash git checkout main git pull origin main @@ -202,6 +211,7 @@ git push origin main ``` **Merge-Reihenfolge:** + ``` A2 (Setup) → A1 (Docs) → A3 (Integrations) → A4 (API Docs) → A5 (Design) ``` diff --git a/CLAUDE.md b/CLAUDE.md index b857cd8..83b7501 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,7 @@ # Code Cloud Agents - Rules ## Sprache + - Antworte immer auf **Deutsch** - Code-Kommentare auf **Englisch** @@ -9,18 +10,21 @@ ## ⚠️ KRITISCHE VERHALTENSREGELN ### Keine Lügen, keine Halluzinationen + - **NIEMALS** Informationen erfinden - **NIEMALS** Code generieren der nicht funktioniert - Wenn unsicher → nachfragen oder recherchieren - Nur Fakten, die verifizierbar sind ### Zügig arbeiten, nicht warten + - **NICHT** auf User warten wenn nicht nötig - Wenn Task unabhängig von API/Input → sofort weitermachen - Beispiel: Agent braucht API-Key → trotzdem alles andere fertig bauen - Parallelisieren wo möglich ### Aktiv denken und prüfen + - **VOR** dem Coden: Plan erstellen - **WÄHREND** dem Coden: Fehler aktiv suchen - **NACH** dem Coden: Testen, verifizieren @@ -34,18 +38,21 @@ - [ ] Tests? ### Nichts vergessen + - **IMMER** vollständig implementieren - Keine halben Sachen - Keine "TODO später" ohne Grund - Integration Frontend ↔ Backend **NICHT** vergessen ### Wenig reden, viel coden + - Kurze Erklärungen - Schnell zum Code - Ergebnisse zeigen statt beschreiben - Bei Fragen: konkret und präzise ### Proaktiv Fehler melden & verbessern + - **UX-Fehler** sofort ansprechen (schlechte Usability, verwirrende UI) - **Code-Smells** aktiv melden (Duplikate, schlechte Namen, fehlende Types) - **Performance-Probleme** identifizieren und Lösung vorschlagen @@ -58,6 +65,7 @@ - Nicht nur ausführen → **mitdenken und verbessern** ### Nichts eigenmächtig ändern + - **KEINE** selbstständigen Design-Änderungen - **KEINE** unaufgeforderten Refactorings - **KEINE** "Verbesserungen" ohne Rücksprache @@ -70,19 +78,23 @@ ## Coding Standards ### TypeScript + - TypeScript verwenden, strikte Typisierung (`strict: true`) - **Keine `any` Types** – immer explizite Typen definieren - Modulare Architektur mit klaren Schnittstellen ### Namenskonventionen -| Element | Convention | Beispiel | -|---------|------------|----------| -| Variablen | camelCase | `userName`, `isLoading` | -| Komponenten/Klassen | PascalCase | `AgentCard`, `FileSearchService` | -| Konstanten | SCREAMING_SNAKE_CASE | `MAX_RETRIES`, `API_BASE_URL` | + +| Element | Convention | Beispiel | +| ------------------- | -------------------- | -------------------------------- | +| Variablen | camelCase | `userName`, `isLoading` | +| Komponenten/Klassen | PascalCase | `AgentCard`, `FileSearchService` | +| Konstanten | SCREAMING_SNAKE_CASE | `MAX_RETRIES`, `API_BASE_URL` | ### Dokumentation + - Jede Funktion/Komponente mit **JSDoc** dokumentieren + ```typescript /** * Searches files on disk based on query @@ -125,6 +137,7 @@ - [ ] Authentication/Authorization bei **jedem** Endpoint prüfen ### Verbotene Dateien + ``` .env .env.local @@ -139,17 +152,20 @@ credentials/ ## Code-Qualität ### DRY-Prinzip + Wiederholungen vermeiden, in Funktionen auslagern ### Single Responsibility + Eine Funktion = eine Aufgabe ### Früh returnen + ```typescript // Gut ✅ function process(data: Data | null): Result { if (!data) return null; - if (!data.isValid) return { error: 'Invalid' }; + if (!data.isValid) return { error: "Invalid" }; return processData(data); } @@ -160,7 +176,7 @@ function process(data: Data | null): Result { if (data.isValid) { return processData(data); } else { - return { error: 'Invalid' }; + return { error: "Invalid" }; } } return null; @@ -168,6 +184,7 @@ function process(data: Data | null): Result { ``` ### Aussagekräftige Namen + ```typescript // Gut ✅ const isUserAuthenticated = checkAuth(user); @@ -183,6 +200,7 @@ const getData = async (id) => { ... }; ## Supervisor-System (Cloud Agents) ### Hierarchie + ``` META_SUPERVISOR (Routing + Monitoring) ↓ @@ -192,23 +210,26 @@ CLOUD_ASSISTANT (Execute + Report + Evidence) ``` ### Kernprinzipien + 1. **Evidence-Based Verification**: Keine Behauptung ohne Beweis 2. **STOP is Success**: Bei Risiko ist STOP die richtige Entscheidung 3. **Cross-Layer Consistency**: Frontend ↔ Backend ↔ Database Alignment ### STOP-Score (0-100) -| Score | Risk Level | Aktion | -|-------|------------|--------| -| 0-19 | LOW | Weiter | -| 20-44 | MEDIUM | Review | -| 45-69 | HIGH | Approval nötig | -| 70-100 | CRITICAL | **STOP_REQUIRED** | + +| Score | Risk Level | Aktion | +| ------ | ---------- | ----------------- | +| 0-19 | LOW | Weiter | +| 20-44 | MEDIUM | Review | +| 45-69 | HIGH | Approval nötig | +| 70-100 | CRITICAL | **STOP_REQUIRED** | --- ## 🔐 GIT-WORKFLOW FÜR ALLE AGENTEN (PFLICHT!) ### Grundregeln + ```yaml # Server .env Konfiguration GIT_MODE=branch_push # Immer Feature-Branch erstellen @@ -218,6 +239,7 @@ REDACT_SECRETS=true # Secrets aus Logs entfernen ``` ### ❌ VERBOTEN + 1. **NIEMALS** direkt auf `main` branch pushen 2. **NIEMALS** auf `main` committen 3. **NIEMALS** force push (`git push --force`) @@ -227,6 +249,7 @@ REDACT_SECRETS=true # Secrets aus Logs entfernen ### ✅ PFLICHT-WORKFLOW #### Schritt 1: Feature-Branch erstellen + ```bash # Branch-Naming Convention: git checkout -b agent-aX-feature-name @@ -238,6 +261,7 @@ git checkout -b agent-fixes-and-features ``` #### Schritt 2: Änderungen committen + ```bash # Alle Änderungen stagen git add -A @@ -251,6 +275,7 @@ Co-Authored-By: Claude Sonnet 4.5 " ``` **Commit-Message Format:** + ``` : @@ -262,6 +287,7 @@ Co-Authored-By: Claude Sonnet 4.5 ``` **Types:** + - `feat:` - Neues Feature - `fix:` - Bug-Fix - `docs:` - Dokumentation @@ -271,6 +297,7 @@ Co-Authored-By: Claude Sonnet 4.5 - `chore:` - Build/Dependencies #### Schritt 3: Push zu origin + ```bash # Ersten Push mit -u flag git push -u origin agent-aX-feature-name @@ -280,6 +307,7 @@ git push ``` #### Schritt 4: Pull Request erstellen + ```bash # Mit GitHub CLI (gh) gh pr create --title "feat: implement feature X" --body "$(cat <<'EOF' @@ -303,6 +331,7 @@ EOF ``` #### Schritt 5: Merge-Reihenfolge einhalten + ``` A2 (Setup) → main (ZUERST) ↓ @@ -316,6 +345,7 @@ A5 (Design) → main (ZULETZT) ``` ### 🔍 Git-Status vor JEDEM Commit prüfen + ```bash # 1. Status prüfen git status @@ -333,6 +363,7 @@ git commit -m "..." ### 🚨 Wenn Hook fehlschlägt **Wenn Commit ABGELEHNT wurde (Hook rejected):** + ```bash # ❌ NICHT amend verwenden! # ✅ Problem fixen, dann NEUEN Commit erstellen @@ -341,6 +372,7 @@ git commit -m "fix: resolve hook issues" ``` **Wenn Commit ERFOLGREICH war, aber Hook Auto-Modifications gemacht hat:** + ```bash # Nur WENN: # 1. HEAD commit wurde von dir erstellt (git log -1) @@ -364,18 +396,21 @@ git commit --amend --no-edit ### 🔗 Troubleshooting #### Problem: "fatal: could not read Username" + ```bash # GitHub CLI authentifizieren gh auth login ``` #### Problem: "rejected: cannot push to main" + ```bash # Falscher Branch! Zurück zu Feature-Branch git checkout agent-aX-feature-name ``` #### Problem: "Your branch is behind" + ```bash # Erst pullen, dann pushen git pull origin agent-aX-feature-name @@ -383,6 +418,7 @@ git push ``` #### Problem: Merge-Konflikt + ```bash # Lokale Änderungen sichern git stash @@ -405,6 +441,7 @@ git push ## AI-Provider Integration ### Priorität + 1. **Cloud AI** (wenn Internet verfügbar): - Claude (Anthropic) - GPT-4 (OpenAI) @@ -415,6 +452,7 @@ git push - LM Studio ### API-Key Konfiguration + ```bash # .env.local (niemals committen!) ANTHROPIC_API_KEY=sk-... @@ -438,6 +476,7 @@ XAI_API_KEY=xai-... **Naming Convention:** `screenName_elementType_beschreibung` Beispiele: + - `login_button_submit` - `dashboard_list_agents` - `settings_toggle_darkmode` @@ -445,11 +484,11 @@ Beispiele: ### Test-Typen -| Typ | Wann | Beispiel | -|-----|------|----------| -| Unit Test | Jede Utility-Funktion | `auth.test.ts` | -| Integration Test | Jeder Screen/API | `github-api.test.ts` | -| E2E Test | Kritische Flows | `login-flow.test.ts` | +| Typ | Wann | Beispiel | +| ---------------- | --------------------- | -------------------- | +| Unit Test | Jede Utility-Funktion | `auth.test.ts` | +| Integration Test | Jeder Screen/API | `github-api.test.ts` | +| E2E Test | Kritische Flows | `login-flow.test.ts` | ### Vor jedem Commit - Checkliste diff --git a/DEPLOYMENT_ABSCHLUSSBERICHT.md b/DEPLOYMENT_ABSCHLUSSBERICHT.md index e548086..a098750 100644 --- a/DEPLOYMENT_ABSCHLUSSBERICHT.md +++ b/DEPLOYMENT_ABSCHLUSSBERICHT.md @@ -19,6 +19,7 @@ Das komplette **Code Cloud Agents System** mit allen Agent 3 Features wurde erfo ## ✅ Abgeschlossene Tasks ### 1. Hetzner Server Deployment ✅ + **Status:** COMPLETE - SSH-Verbindung hergestellt mit Schlüssel `id_ed25519_cloudagents` @@ -29,6 +30,7 @@ Das komplette **Code Cloud Agents System** mit allen Agent 3 Features wurde erfo - PM2 Service erfolgreich neu gestartet **Server-Details:** + ``` Hostname: Hetzner Cloud Server IP: 178.156.178.70 @@ -39,35 +41,41 @@ Working Dir: /root/cloud-agents ``` ### 2. Endpoint Verification ✅ + **Status:** COMPLETE Alle kritischen Endpoints wurden getestet und bestätigt: #### Health Check + ```bash GET http://178.156.178.70:3000/health ✅ {"status":"healthy","timestamp":"2025-12-26T14:47:08.835Z"} ``` #### API Info + ```bash GET http://178.156.178.70:3000/api ✅ {"name":"code-cloud-agents","version":"0.1.0","status":"running"} ``` #### Memory System + ```bash GET http://178.156.178.70:3000/api/memory/chats/test-user ✅ {"success":true,"chats":[],"count":0} ``` #### Settings System + ```bash GET http://178.156.178.70:3000/api/settings/user/test-user ✅ {"success":true,"settings":{...},"metadata":{...}} ``` ### 3. Dokumentation ✅ + **Status:** COMPLETE Drei umfassende Dokumentationsdateien erstellt und auf GitHub committed: @@ -99,11 +107,13 @@ Alle Dateien commited in: `7eefa71` (main branch) ## 🚀 Deployed Features ### Memory System (Agent 3 - Core Feature) + **Status:** ✅ Online & Operational Das komplette Memory System mit drei Hauptkomponenten ist deployed: #### 1. MemoryManager (manager.ts) + - ✅ Chat CRUD Operations - ✅ Message Storage & Retrieval - ✅ Context Management (recent messages) @@ -111,6 +121,7 @@ Das komplette Memory System mit drei Hauptkomponenten ist deployed: - ✅ User Statistics #### 2. MemorySearch (search.ts) + - ✅ Full-text Search (SQL LIKE) - ✅ Chat Search (Titel-basiert) - ✅ Similar Messages (keyword overlap) @@ -118,6 +129,7 @@ Das komplette Memory System mit drei Hauptkomponenten ist deployed: - ✅ Trending Topics #### 3. EmbeddingsManager (embeddings.ts) + - ✅ Semantic Search (Cosine Similarity) - ✅ Auto-Embedding Generation - ✅ Batch Processing @@ -125,6 +137,7 @@ Das komplette Memory System mit drei Hauptkomponenten ist deployed: - ⚠️ Requires OPENAI_API_KEY (siehe Task 2) **Memory Endpoints Deployed:** + ``` ✅ 21 Memory Endpoints - 5 Chat Management @@ -135,6 +148,7 @@ Das komplette Memory System mit drei Hauptkomponenten ist deployed: ``` ### Complete REST API + **Status:** ✅ 63 Endpoints Online ``` @@ -172,6 +186,7 @@ Das komplette Memory System mit drei Hauptkomponenten ist deployed: ``` ### WebSocket Server + **Status:** ✅ Online ``` @@ -183,6 +198,7 @@ Das komplette Memory System mit drei Hauptkomponenten ist deployed: ``` ### Database + **Status:** ✅ Operational ``` @@ -208,12 +224,14 @@ Das komplette Memory System mit drei Hauptkomponenten ist deployed: ## 🔄 Deployment Process Details ### Schritt 1: SSH-Verbindung + ```bash ssh -i ~/.ssh/id_ed25519_cloudagents root@178.156.178.70 ✅ Verbindung erfolgreich ``` ### Schritt 2: Backup erstellen + ```bash cd /root tar -czf cloud-agents-backup-20251226-154405.tar.gz cloud-agents/ @@ -221,6 +239,7 @@ tar -czf cloud-agents-backup-20251226-154405.tar.gz cloud-agents/ ``` ### Schritt 3: Git Remote korrigieren + ```bash cd cloud-agents git remote set-url origin https://github.com/dsactivi-2/code-cloud-agents.git @@ -228,6 +247,7 @@ git remote set-url origin https://github.com/dsactivi-2/code-cloud-agents.git ``` ### Schritt 4: Working Directory bereinigen + ```bash git reset --hard git clean -fd @@ -236,6 +256,7 @@ rm -f AGENT_3_FINAL_REPORT.md AGENT_A2_REPORT.md ... ``` ### Schritt 5: Main Branch checkout + ```bash git fetch origin main git checkout -b main origin/main @@ -243,18 +264,21 @@ git checkout -b main origin/main ``` ### Schritt 6: Dependencies installieren + ```bash npm install --legacy-peer-deps ✅ 758 packages installiert ``` ### Schritt 7: PM2 restart + ```bash pm2 restart cloud-agents-backend ✅ Server neu gestartet ``` ### Schritt 8: Verification + ```bash # Health check curl http://localhost:3000/health @@ -274,6 +298,7 @@ curl http://localhost:3000/api/settings/user/test-user ## ⚠️ Wichtige Hinweise ### OpenAI API Key fehlt (Expected) + ``` ⚠️ OPENAI_API_KEY not set - embeddings disabled ``` @@ -283,6 +308,7 @@ curl http://localhost:3000/api/settings/user/test-user **Code:** Vollständig implementiert, nur Environment Variable fehlt ### Peer Dependencies Warning + ``` npm warn Could not resolve dependency: peer date-fns@"^2.28.0 || ^3.0.0" ``` @@ -298,11 +324,13 @@ npm warn Could not resolve dependency: peer date-fns@"^2.28.0 || ^3.0.0" Der User hat folgende weitere Tasks angefordert: "2,3,4,5" ### Task 2: OpenAI API Key Setup + **Status:** 🟡 Bereit zur Implementierung **Code:** ✅ Vollständig implementiert **Benötigt:** Nur Environment Variable setzen **Schritte:** + 1. OpenAI API Key generieren (https://platform.openai.com/api-keys) 2. Auf Server setzen: ```bash @@ -315,6 +343,7 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" **Dokumentation:** `docs/IMPLEMENTATION_GUIDE.md` (Zeile 150-220) **Features nach Aktivierung:** + - ✅ Automatic embedding generation für neue Messages - ✅ Semantic search über alle Conversations - ✅ Similar message detection @@ -325,10 +354,12 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" --- ### Task 3: Frontend Integration + **Status:** 🟡 Guide erstellt, bereit zur Implementierung **Dokumentation:** `docs/IMPLEMENTATION_GUIDE.md` (Zeile 222-450) **Umfang:** + - Next.js 14+ Setup mit TypeScript - React Query für API-Calls - WebSocket Hooks für Real-time @@ -337,6 +368,7 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" - Settings Dashboard **Beispiel-Code verfügbar für:** + - Custom React Hooks (useChat, useMemorySearch, useSemanticSearch) - API Client (MemoryAPI class) - WebSocket Manager @@ -347,15 +379,18 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" --- ### Task 4: Performance Testing + **Status:** 🟡 Scripts bereit, kann sofort ausgeführt werden **Dokumentation:** `docs/IMPLEMENTATION_GUIDE.md` (Zeile 452-650) **Test-Tools:** + - k6 für Load Testing - Artillery für WebSocket Testing - Benchmark Scripts für Database **Vorbereitet Scripts:** + ``` ✅ Chat API Load Test (100 VUs) ✅ Memory Search Load Test (50 VUs) @@ -365,6 +400,7 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" ``` **KPIs zu messen:** + - Request Throughput (req/s) - Response Time (P95, P99) - Error Rate @@ -376,16 +412,19 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" --- ### Task 5: Monitoring & Analytics + **Status:** 🟡 Setup-Guide erstellt **Dokumentation:** `docs/IMPLEMENTATION_GUIDE.md` (Zeile 652-818) **Stack:** + - Prometheus (Metriken) - Grafana (Dashboards) - Sentry (Error Tracking) - Winston (Logging) **Metriken:** + ``` ✅ System Metrics (CPU, Memory, Disk) ✅ API Metrics (Request rate, Response time, Error rate) @@ -394,6 +433,7 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" ``` **Dashboards:** + - System Overview - API Performance - Memory System Analytics @@ -407,6 +447,7 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" ## 📊 Production Readiness ### ✅ Deployed & Ready + - [x] Core REST API (63 endpoints) - [x] Memory System (21 endpoints) - [x] WebSocket Server @@ -424,12 +465,14 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" - [x] CASCADE DELETE ### 🟡 Ready with OPENAI_API_KEY + - [ ] Semantic Search - [ ] Auto-Embedding Generation - [ ] Similar Message Detection - [ ] Batch Embedding Processing ### 🔜 Next Phase (Tasks 3-5) + - [ ] Frontend UI - [ ] Performance Testing - [ ] Monitoring Setup @@ -439,6 +482,7 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" ## 🔐 Security Status ### ✅ Implemented + - SQL Injection Prevention (Prepared Statements) - XSS Prevention (Content Sanitization) - User Isolation (userId filtering) @@ -448,6 +492,7 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" - Audit Logging (alle kritischen Operations) ### 🔒 Best Practices + - OPENAI_API_KEY nur server-side - Keine Secrets im Frontend - CASCADE DELETE für Datenintegrität @@ -459,6 +504,7 @@ Der User hat folgende weitere Tasks angefordert: "2,3,4,5" ## 📈 System Metrics ### Server Resources + ``` CPU: 8 vCPUs RAM: 16 GB @@ -467,6 +513,7 @@ Network: 1 Gbit/s ``` ### Application Stats + ``` Node Version: v25.2.1 npm Packages: 758 @@ -476,6 +523,7 @@ Total Endpoints: 63 REST + 1 WebSocket ``` ### Memory System Stats + ``` Chats: 0 (fresh deployment) Messages: 0 (fresh deployment) @@ -488,8 +536,10 @@ Search Indexes: ✅ Ready ## 📚 Referenz-Dokumentation ### 1. Deployment Report (1000+ Zeilen) + **Datei:** `DEPLOYMENT_REPORT_2025-12-26.md` **Inhalt:** + - Alle 7 Agent 3 Tasks im Detail - 63 REST Endpoints dokumentiert - Database Schema @@ -498,8 +548,10 @@ Search Indexes: ✅ Ready - Testing Guides ### 2. Implementation Guide (818 Zeilen) + **Datei:** `docs/IMPLEMENTATION_GUIDE.md` **Inhalt:** + - Hetzner Deployment Steps - OpenAI API Setup (Task 2) - Frontend Integration (Task 3) @@ -507,16 +559,20 @@ Search Indexes: ✅ Ready - Monitoring Setup (Task 5) ### 3. Final Status Report (637 Zeilen) + **Datei:** `FINAL_STATUS_REPORT.md` **Inhalt:** + - Manual Deployment Instructions - Post-Deployment Verification - Complete Feature List - Architecture Overview ### 4. Memory System Docs + **Datei:** `docs/MEMORY.md` **Inhalt:** + - Architecture Diagram - Database Schema - API Reference (21 endpoints) @@ -528,6 +584,7 @@ Search Indexes: ✅ Ready ## 🎯 Erfolgs-Metriken ### Deployment Success + - ✅ Zero Downtime Deployment - ✅ Alle Tests bestanden - ✅ Backup erstellt (257 MB) @@ -535,6 +592,7 @@ Search Indexes: ✅ Ready - ✅ PM2 Service läuft stabil ### Code Quality + - ✅ TypeScript strict mode - ✅ Zod validation überall - ✅ Error handling comprehensive @@ -542,6 +600,7 @@ Search Indexes: ✅ Ready - ✅ Input sanitization (XSS safe) ### Documentation Quality + - ✅ 3 umfassende Dokumentationsdateien - ✅ 2455+ Zeilen Dokumentation - ✅ Alle Endpoints dokumentiert @@ -553,6 +612,7 @@ Search Indexes: ✅ Ready ## 🚀 Go-Live Checklist ### Sofort Production-Ready ✅ + - [x] Server deployed & running - [x] Health checks passing - [x] API endpoints functional @@ -565,12 +625,14 @@ Search Indexes: ✅ Ready - [x] Audit logging enabled ### Nach OPENAI_API_KEY Setup 🔜 + - [ ] Semantic Search aktivieren - [ ] Auto-Embedding für neue Messages - [ ] Batch-Embedding für existing chats - [ ] Cost tracking implementieren ### Nach Frontend Integration 🔜 + - [ ] User Interface deployen - [ ] E2E Tests ausführen - [ ] User Acceptance Testing @@ -585,6 +647,7 @@ Das **Code Cloud Agents System** ist erfolgreich auf dem Hetzner Production Serv **Deployment-Status:** ✅ **ERFOLGREICH & VERIFIZIERT** **System ist bereit für:** + - ✅ Production Usage (ohne Semantic Search) - ✅ Task Management - ✅ Chat Operations @@ -594,6 +657,7 @@ Das **Code Cloud Agents System** ist erfolgreich auf dem Hetzner Production Serv - ✅ Demo User System **Nächste Schritte:** + 1. **Task 2:** OPENAI_API_KEY setzen → Semantic Search aktivieren 2. **Task 3:** Frontend implementieren → User Interface 3. **Task 4:** Performance Tests ausführen → Optimize @@ -613,6 +677,7 @@ Das **Code Cloud Agents System** ist erfolgreich auf dem Hetzner Production Serv ## 🤝 Support & Kontakt Bei Fragen oder Problemen: + - **Dokumentation:** Siehe Referenz-Dokumentation oben - **API Reference:** `DEPLOYMENT_REPORT_2025-12-26.md` - **Implementation Guide:** `docs/IMPLEMENTATION_GUIDE.md` diff --git a/DEPLOYMENT_GIT_SETUP.md b/DEPLOYMENT_GIT_SETUP.md index fbbf20c..88843b5 100644 --- a/DEPLOYMENT_GIT_SETUP.md +++ b/DEPLOYMENT_GIT_SETUP.md @@ -15,6 +15,7 @@ **SSH Config:** `/root/.ssh/config` konfiguriert für github.com **Test-Befehl:** + ```bash ssh -i ~/.ssh/activi_cloud_agent root@178.156.178.70 "ssh -T git@github.com" # Output: Hi dsactivi-2/code-cloud-agents! You've successfully authenticated... @@ -27,6 +28,7 @@ ssh -i ~/.ssh/activi_cloud_agent root@178.156.178.70 "ssh -T git@github.com" **Status:** Up to date mit origin/main **Verifikation:** + ```bash ssh -i ~/.ssh/activi_cloud_agent root@178.156.178.70 "cd /root/cloud-agents && git status" # On branch main @@ -43,6 +45,7 @@ ssh -i ~/.ssh/activi_cloud_agent root@178.156.178.70 "cd /root/cloud-agents && g Das Deployment-Script wurde von rsync auf git-basiert umgestellt: **Schritte:** + 1. **Local Checks** - Prüft auf uncommitted changes 2. **Push to GitHub** - Pusht zum main branch 3. **Pull on Server** - Fetcht und reset auf origin/main @@ -51,6 +54,7 @@ Das Deployment-Script wurde von rsync auf git-basiert umgestellt: 6. **Health Check** - Verifiziert Server-Status **Verwendung:** + ```bash cd /Users/dsselmanovic/activi-dev-repos/Optimizecodecloudagents ./deploy.sh @@ -127,11 +131,13 @@ Nach jedem Deployment: ## 🔐 Sicherheit **SSH Keys:** + - Server Key: `/root/.ssh/github_deploy` (privat, bleibt auf Server) - GitHub Deploy Key: Nur für dieses Repository, Write-Access - SSH Config: `StrictHostKeyChecking no` für automatisierte Deployments **Secrets Management:** + - Doppler für alle Secrets (ANTHROPIC_API_KEY, etc.) - Keine `.env` Dateien im Git-Repository - Server-seitige Doppler-Konfiguration unabhängig von local diff --git a/DEPLOYMENT_REPORT_2025-12-26.md b/DEPLOYMENT_REPORT_2025-12-26.md index 2e2d34b..51bbcce 100644 --- a/DEPLOYMENT_REPORT_2025-12-26.md +++ b/DEPLOYMENT_REPORT_2025-12-26.md @@ -32,6 +32,7 @@ Alle 7 Tasks von Agent 3 wurden erfolgreich implementiert, getestet und auf GitH **Status:** Up-to-date, alle Features deployed **Recent Commits:** + ``` 6c52403 - Merge branch 'agent-a3-memory-system' 14e3dda - feat(memory): Add comprehensive Memory System documentation @@ -46,6 +47,7 @@ baa6e83 - docs(readme): add comprehensive project documentation **Method:** Git pull + PM2 restart **Deployment Steps (Manual):** + ```bash # 1. SSH zum Server ssh root@178.156.178.70 @@ -77,10 +79,12 @@ pm2 logs code-cloud-agents --lines 50 ### 1. GitHub Integration (Task 1) ✅ **Dateien:** + - `src/api/github.ts` - REST API Router - `src/services/github.ts` - GitHub Service Layer **Endpoints (9):** + ``` GET /api/github/status - Connection status GET /api/github/repos - List repositories @@ -94,12 +98,14 @@ POST /api/github/comments - Create comment ``` **Features:** + - OAuth Token Authentication - Rate Limit Handling - Error Handling - Pagination Support **Environment Variables:** + ```bash GITHUB_TOKEN=ghp_xxxxxxxxxxxxx ``` @@ -109,10 +115,12 @@ GITHUB_TOKEN=ghp_xxxxxxxxxxxxx ### 2. Linear Integration (Task 2) ✅ **Dateien:** + - `src/api/linear.ts` - REST API Router - `src/services/linear.ts` - Linear Service Layer **Endpoints (10):** + ``` GET /api/linear/status - Connection status GET /api/linear/teams - List teams @@ -126,12 +134,14 @@ GET /api/linear/users - List users ``` **Features:** + - GraphQL API Integration - Team/Project/Issue Management - Custom Fields Support - Workflow States **Environment Variables:** + ```bash LINEAR_API_KEY=lin_api_xxxxxxxxxxxxx ``` @@ -141,10 +151,12 @@ LINEAR_API_KEY=lin_api_xxxxxxxxxxxxx ### 3. Webhook Handlers (Task 3) ✅ **Dateien:** + - `src/webhooks/github.ts` - GitHub Webhook Handler - `src/webhooks/linear.ts` - Linear Webhook Handler **Endpoints (3):** + ``` POST /api/webhooks/github - Receive GitHub webhooks POST /api/webhooks/linear - Receive Linear webhooks @@ -152,6 +164,7 @@ GET /api/webhooks/linear/test - Test Linear webhook ``` **Features:** + - **Signature Verification:** - GitHub: HMAC-SHA256 - Linear: HMAC-SHA256 @@ -162,12 +175,14 @@ GET /api/webhooks/linear/test - Test Linear webhook - **Raw Body Parsing:** For signature verification **Environment Variables:** + ```bash GITHUB_WEBHOOK_SECRET=xxxxxxxxxxxxx LINEAR_WEBHOOK_SECRET=xxxxxxxxxxxxx ``` **Setup:** + 1. GitHub Webhook URL: `https://your-domain.com/api/webhooks/github` 2. Linear Webhook URL: `https://your-domain.com/api/webhooks/linear` 3. Set secrets in repository settings @@ -177,14 +192,17 @@ LINEAR_WEBHOOK_SECRET=xxxxxxxxxxxxx ### 4. WebSocket Real-time (Task 4) ✅ **Datei:** + - `src/websocket/server.ts` - WebSocket Server **Endpoint:** + ``` WS ws://localhost:3000/ws?token=YOUR_TOKEN ``` **Features:** + - **Token Authentication** - **4 Message Types:** - `agent_status` - Agent status updates @@ -196,19 +214,22 @@ WS ws://localhost:3000/ws?token=YOUR_TOKEN - **Connection Management:** Auto-cleanup on disconnect **Client Example:** + ```javascript -const ws = new WebSocket('ws://localhost:3000/ws?token=YOUR_TOKEN'); +const ws = new WebSocket("ws://localhost:3000/ws?token=YOUR_TOKEN"); ws.onmessage = (event) => { const data = JSON.parse(event.data); - console.log('Received:', data.type, data.payload); + console.log("Received:", data.type, data.payload); }; // Send message -ws.send(JSON.stringify({ - type: 'chat_message', - payload: { message: 'Hello!' } -})); +ws.send( + JSON.stringify({ + type: "chat_message", + payload: { message: "Hello!" }, + }), +); ``` --- @@ -218,6 +239,7 @@ ws.send(JSON.stringify({ **Integration:** In `/api/tasks` Endpoints **Features:** + - **Agent Assignment:** - Engineer (Code development) - CloudAssistant (Deployment, DevOps) @@ -229,6 +251,7 @@ ws.send(JSON.stringify({ - **Enforcement Gate:** Blocking on STOP_REQUIRED **Endpoints:** + ``` POST /api/tasks - Create task (with agent assignment) GET /api/tasks/:id - Get task details (includes agent) @@ -241,11 +264,13 @@ GET /api/enforcement/blocked - Get blocked tasks (STOP score >= 70) ### 6. Settings Management API (Task 6) ✅ **Dateien:** + - `src/db/settings.ts` - Database Schema + SettingsDB Class - `src/api/settings.ts` - REST API Router - `docs/SETTINGS.md` - Comprehensive Documentation **Endpoints (10):** + ``` GET /api/settings/user/:userId - Get user settings PUT /api/settings/user/:userId - Update user settings @@ -260,6 +285,7 @@ GET /api/settings/history/system/:key - Get system setting history ``` **Features:** + - **User Settings:** Per-user configuration - **System Settings:** Global configuration (Admin only) - **Preferences:** UI/UX preferences per user @@ -267,6 +293,7 @@ GET /api/settings/history/system/:key - Get system setting history - **JSON Storage:** Flexible schema-less settings **Database Tables:** + ```sql - user_settings (current state) - system_settings (current state) @@ -279,6 +306,7 @@ GET /api/settings/history/system/:key - Get system setting history ### 7. Memory System (Task 7) ✅ **NEW!** **Dateien:** + - `src/memory/manager.ts` - Memory Manager (CRUD) - `src/memory/search.ts` - Full-text Search - `src/memory/embeddings.ts` - Semantic Search (OpenAI) @@ -291,6 +319,7 @@ GET /api/settings/history/system/:key - Get system setting history **Endpoints (21):** **Chat Management (5):** + ``` GET /api/memory/chats/:userId - List chats POST /api/memory/chats - Create chat @@ -300,6 +329,7 @@ DELETE /api/memory/chats/:chatId - Delete chat ``` **Message Management (4):** + ``` GET /api/memory/chats/:chatId/messages - Get messages POST /api/memory/chats/:chatId/messages - Add message (auto-embedding) @@ -308,6 +338,7 @@ DELETE /api/memory/chats/:chatId/messages/old - Clear old messages ``` **Search (5):** + ``` POST /api/memory/search - Full-text search GET /api/memory/search/chats/:userId - Search chats @@ -317,6 +348,7 @@ GET /api/memory/trending/:userId - Trending topics ``` **Semantic Search (3):** + ``` POST /api/memory/semantic/search - Semantic search (OpenAI) POST /api/memory/chats/:chatId/embeddings/generate - Generate embeddings @@ -324,12 +356,14 @@ GET /api/memory/embeddings/stats - Embedding statistics ``` **Export & Stats (2):** + ``` GET /api/memory/chats/:chatId/export - Export chat GET /api/memory/stats/:userId - User statistics ``` **Features:** + - **3 Core Components:** 1. MemoryManager - Chat/Message CRUD 2. MemorySearch - Full-text + Keyword Search @@ -341,6 +375,7 @@ GET /api/memory/stats/:userId - User statistics - **Context Retrieval:** Get messages before/after a specific message **Database Tables:** + ```sql - chats (conversation metadata) - chat_messages (message storage) @@ -350,6 +385,7 @@ GET /api/memory/stats/:userId - User statistics ``` **Environment Variables:** + ```bash OPENAI_API_KEY=sk-xxxxxxxxxxxxx # Required for semantic search ``` @@ -382,6 +418,7 @@ OPENAI_API_KEY=sk-xxxxxxxxxxxxx # Required for semantic search ``` **All Indexes Optimized:** + - Foreign Key Indexes - User ID Indexes - Timestamp Indexes @@ -392,6 +429,7 @@ OPENAI_API_KEY=sk-xxxxxxxxxxxxx # Required for semantic search ## 📚 DOKUMENTATION ### `docs/SETTINGS.md` ✅ + - **Status:** Complete - **Umfang:** 10 Endpoints dokumentiert - **Inhalt:** @@ -401,6 +439,7 @@ OPENAI_API_KEY=sk-xxxxxxxxxxxxx # Required for semantic search - Implementation Notes ### `docs/MEMORY.md` ✅ **NEW!** + - **Status:** Complete - **Umfang:** 659 Zeilen - **Inhalt:** @@ -420,28 +459,33 @@ OPENAI_API_KEY=sk-xxxxxxxxxxxxx # Required for semantic search ## 🔧 ENVIRONMENT VARIABLES REQUIRED ### GitHub Integration + ```bash GITHUB_TOKEN=ghp_xxxxxxxxxxxxx GITHUB_WEBHOOK_SECRET=xxxxxxxxxxxxx ``` ### Linear Integration + ```bash LINEAR_API_KEY=lin_api_xxxxxxxxxxxxx LINEAR_WEBHOOK_SECRET=xxxxxxxxxxxxx ``` ### OpenAI (Semantic Search) + ```bash OPENAI_API_KEY=sk-xxxxxxxxxxxxx # Optional, für Semantic Search ``` ### Database + ```bash SQLITE_PATH=./data/app.sqlite # Optional, default: ./data/app.sqlite ``` ### Server + ```bash PORT=3000 # Optional, default: 3000 ``` @@ -451,6 +495,7 @@ PORT=3000 # Optional, default: 3000 ## 📊 PERFORMANCE & STATISTICS ### Code Statistics + - **Total Files Created:** 22+ - **Total Lines of Code:** ~5,000+ - **REST Endpoints:** 63 @@ -459,6 +504,7 @@ PORT=3000 # Optional, default: 3000 - **Documentation Files:** 2 (SETTINGS.md, MEMORY.md) ### Git Statistics + - **Branches Created:** 7 - **Commits:** 20+ - **Merges to main:** 7 @@ -466,6 +512,7 @@ PORT=3000 # Optional, default: 3000 - **Latest Commit:** `6c52403` ### API Endpoints Breakdown + - Core APIs: 8 endpoints - GitHub Integration: 9 endpoints - Linear Integration: 10 endpoints @@ -479,6 +526,7 @@ PORT=3000 # Optional, default: 3000 ## 🔒 SECURITY FEATURES ### Implemented + ✅ **Input Validation** - Zod schemas für alle Endpoints ✅ **SQL Injection Prevention** - Prepared Statements ✅ **XSS Prevention** - Content Sanitization @@ -490,6 +538,7 @@ PORT=3000 # Optional, default: 3000 ✅ **CASCADE DELETE** - Automatische Cleanup ### Best Practices + - API Keys nur server-side - HTTPS für Production (empfohlen) - Rate Limiting (empfohlen für Production) @@ -500,6 +549,7 @@ PORT=3000 # Optional, default: 3000 ## 🚦 TESTING STATUS ### Local Development Server + ✅ **Status:** Running successfully ✅ **URL:** http://localhost:3000 ✅ **WebSocket:** ws://localhost:3000/ws @@ -507,6 +557,7 @@ PORT=3000 # Optional, default: 3000 ✅ **All Endpoints:** Tested and working ### Tests Performed + - ✅ API Endpoint Creation - ✅ WebSocket Connection - ✅ Database Initialization @@ -523,6 +574,7 @@ PORT=3000 # Optional, default: 3000 ### Immediate (Production Deployment) 1. **Hetzner Server Deployment** ⏸️ + ```bash ssh root@178.156.178.70 cd /path/to/code-cloud-agents @@ -584,6 +636,7 @@ PORT=3000 # Optional, default: 3000 ## 📈 COST ESTIMATES ### OpenAI Embeddings (Memory System) + - **Model:** text-embedding-3-small - **Cost:** ~$0.02 per 1M tokens - **Average:** ~150 tokens per message @@ -592,6 +645,7 @@ PORT=3000 # Optional, default: 3000 - **1,000,000 messages:** ~150M tokens = **$3.00** ### Database Storage + - **Messages:** ~1KB per message - **Embeddings:** ~6KB per message (1536 floats as JSON) - **Total:** ~7KB per message @@ -600,6 +654,7 @@ PORT=3000 # Optional, default: 3000 - **1,000,000 messages:** ~7GB ### Server Costs (Hetzner) + - Current plan covers all features - No additional costs for Agent 3 features @@ -608,6 +663,7 @@ PORT=3000 # Optional, default: 3000 ## ✅ QUALITY ASSURANCE ### Code Quality + ✅ **TypeScript Strict Mode** ✅ **ESLint Rules Applied** ✅ **No `any` Types** @@ -617,6 +673,7 @@ PORT=3000 # Optional, default: 3000 ✅ **Single Responsibility** (Modular architecture) ### Documentation Quality + ✅ **API Reference Complete** ✅ **Examples Provided** ✅ **Architecture Diagrams** @@ -629,6 +686,7 @@ PORT=3000 # Optional, default: 3000 ## 🎉 ACHIEVEMENTS ### Completed in Session + - ✅ 7/7 Agent 3 Tasks - ✅ 63 REST Endpoints - ✅ 1 WebSocket Server @@ -639,6 +697,7 @@ PORT=3000 # Optional, default: 3000 - ✅ 5,000+ Lines of Code ### Bonus Features Delivered + - ✅ Demo Invite System - ✅ Semantic Search mit OpenAI - ✅ Trending Topics Analysis @@ -650,12 +709,14 @@ PORT=3000 # Optional, default: 3000 ## 📞 SUPPORT & MAINTENANCE ### Documentation + - `docs/SETTINGS.md` - Settings API Reference - `docs/MEMORY.md` - Memory System Reference - `README.md` - Project Overview - API Logs - Server console output ### Troubleshooting + - Check server logs: `pm2 logs code-cloud-agents` - Database health: `GET /health` - API status: `GET /api` @@ -665,11 +726,13 @@ PORT=3000 # Optional, default: 3000 ## 📝 NOTES ### Known Limitations + - OpenAI API Key required für Semantic Search - SQLite für Development (PostgreSQL empfohlen für Production) - WebSocket auf Single-Server (Redis Pub/Sub für Multi-Server) ### Future Improvements + - Vector Database für bessere Embedding Performance - PostgreSQL für Production - Redis für Session Management diff --git a/INTEGRATIONS_SETUP.md b/INTEGRATIONS_SETUP.md index 8ce2bea..6677554 100644 --- a/INTEGRATIONS_SETUP.md +++ b/INTEGRATIONS_SETUP.md @@ -6,11 +6,11 @@ ## 📋 Übersicht -| Integration | Status | Setup Zeit | Features | -|-------------|--------|------------|----------| -| **GitHub** | ✅ Production Ready | 2 min | Issues, Repos, Status | -| **Slack** | ✅ Production Ready | 3 min | Messages, Webhooks, Threads | -| **Linear** | ✅ Production Ready | 2 min | Issues, Teams, Workflows | +| Integration | Status | Setup Zeit | Features | +| ----------- | ------------------- | ---------- | --------------------------- | +| **GitHub** | ✅ Production Ready | 2 min | Issues, Repos, Status | +| **Slack** | ✅ Production Ready | 3 min | Messages, Webhooks, Threads | +| **Linear** | ✅ Production Ready | 2 min | Issues, Teams, Workflows | --- @@ -19,12 +19,14 @@ ### 1. GitHub Setup **API Token erstellen:** + 1. https://github.com/settings/tokens 2. "Generate new token (classic)" 3. Scopes: ✅ `repo`, ✅ `write:org` (optional) 4. Token kopieren **In .env:** + ```bash GITHUB_ENABLED=true GITHUB_TOKEN=ghp_dein_token_hier @@ -32,6 +34,7 @@ GITHUB_ORG=deine-org # Optional ``` **Test:** + ```typescript import { createGitHubClient } from "./integrations/github/client.js"; const github = createGitHubClient(); @@ -61,6 +64,7 @@ await github.getStatus(); 4. URL kopieren **In .env:** + ```bash SLACK_ENABLED=true SLACK_TOKEN=xoxb-dein-token-hier # Option A @@ -68,6 +72,7 @@ SLACK_WEBHOOK_URL=https://hooks.slack... # Option B (oder beide) ``` **Test:** + ```typescript import { createSlackClient } from "./integrations/slack/client.js"; const slack = createSlackClient(); @@ -79,18 +84,21 @@ await slack.sendMessage({ channel: "#general", text: "Test!" }); ### 3. Linear Setup **API Key erstellen:** + 1. https://linear.app/settings/api 2. "Personal API keys" → "Create key" 3. Name: `Cloud Agents` 4. Key kopieren **In .env:** + ```bash LINEAR_ENABLED=true LINEAR_API_KEY=lin_api_dein_key_hier ``` **Test:** + ```typescript import { createLinearClient } from "./integrations/linear/client.js"; const linear = createLinearClient(); @@ -140,9 +148,12 @@ await slack.sendMessage({ blocks: [ { type: "section", - text: { type: "mrkdwn", text: "*Status:* Success\n*Duration:* 2.5 minutes" } - } - ] + text: { + type: "mrkdwn", + text: "*Status:* Success\n*Duration:* 2.5 minutes", + }, + }, + ], }); ``` @@ -180,20 +191,20 @@ const linear = createLinearClient(); // 1. GitHub Issue erstellt const githubIssue = await github.createIssue("owner/repo", { title: "Critical Bug", - body: "Production error" + body: "Production error", }); // 2. Slack Notification await slack.sendMessage({ channel: "#alerts", - text: `🚨 Critical issue: ${githubIssue.issue?.htmlUrl}` + text: `🚨 Critical issue: ${githubIssue.issue?.htmlUrl}`, }); // 3. Linear Issue für Tracking await linear.createIssue({ title: `[GitHub] Critical Bug`, description: `Source: ${githubIssue.issue?.htmlUrl}`, - priority: 1 + priority: 1, }); ``` @@ -206,7 +217,7 @@ const stopScore = 75; const linearIssue = await linear.createIssue({ title: `🚨 STOP Required: ${taskName}`, description: `STOP Score: ${stopScore}/100`, - priority: 1 + priority: 1, }); // Slack Alert @@ -218,17 +229,17 @@ await slack.sendMessage({ type: "section", text: { type: "mrkdwn", - text: `*Linear Issue:* ${linearIssue.issue?.url}\n*Action:* Manual review required` - } - } - ] + text: `*Linear Issue:* ${linearIssue.issue?.url}\n*Action:* Manual review required`, + }, + }, + ], }); // GitHub Issue als Backup await github.createIssue("team/repo", { title: `STOP: ${taskName}`, body: `STOP Score: ${stopScore}\nLinear: ${linearIssue.issue?.url}`, - labels: ["stop-score", "urgent"] + labels: ["stop-score", "urgent"], }); ``` @@ -288,27 +299,27 @@ console.log("Linear:", linearStatus.connected ? "✅" : "❌"); ### GitHub -| Error | Lösung | -|-------|--------| -| `invalid_auth` | Token neu generieren | -| `Not Found` | Repo-Name prüfen oder Permissions fehlen | -| `rate limit` | Warte 1 Stunde oder nutze authenticated token | +| Error | Lösung | +| -------------- | --------------------------------------------- | +| `invalid_auth` | Token neu generieren | +| `Not Found` | Repo-Name prüfen oder Permissions fehlen | +| `rate limit` | Warte 1 Stunde oder nutze authenticated token | ### Slack -| Error | Lösung | -|-------|--------| -| `not_in_channel` | `/invite @Bot` im Channel | +| Error | Lösung | +| ------------------- | ----------------------------- | +| `not_in_channel` | `/invite @Bot` im Channel | | `channel_not_found` | Channel-Name prüfen (mit `#`) | -| `invalid_auth` | Token neu generieren | +| `invalid_auth` | Token neu generieren | ### Linear -| Error | Lösung | -|-------|--------| -| `Invalid API key` | Key neu generieren | -| `No teams found` | Mindestens ein Team erstellen | -| `Unauthorized` | API key Permissions prüfen | +| Error | Lösung | +| ----------------- | ----------------------------- | +| `Invalid API key` | Key neu generieren | +| `No teams found` | Mindestens ein Team erstellen | +| `Unauthorized` | API key Permissions prüfen | --- diff --git a/MUJO_INTERACTIVE_BOT_SETUP.md b/MUJO_INTERACTIVE_BOT_SETUP.md index a66ab99..e782df3 100644 --- a/MUJO_INTERACTIVE_BOT_SETUP.md +++ b/MUJO_INTERACTIVE_BOT_SETUP.md @@ -13,36 +13,45 @@ Mujo ist jetzt ein vollständig interaktiver Bot mit Supervisor-Gehirn! Mujo nutzt das **Supervisor-System** als Gehirn: ✅ **System Status abfragen** + ``` @mujo system status mujo health ``` + → Zeigt: Alerts, Metrics, Task Stats ✅ **STOP Score Info** + ``` @mujo stop score mujo was ist ein stop score? ``` + → Erklärt das STOP Score System ✅ **STOP Score berechnen** + ``` mujo berechne stop score mujo calculate stop score ``` + → Beispiel-Berechnung mit Reasons ✅ **Alerts anzeigen** + ``` @mujo alerts mujo warnungen ``` + → Aktive System-Alerts ### 💬 Interaktive Responses ✅ **Wer bist du?** + ``` User: "mujo koji si ti K... obdje?" @@ -53,6 +62,7 @@ na vrijeme bacim vic. Govorim Deutsch, English i Bosanski!" ``` ✅ **Witze** + ``` User: "mujo erzähl einen witz" @@ -61,6 +71,7 @@ Production testet in Mujo." ``` ✅ **Hilfe** + ``` User: "@mujo help" @@ -73,6 +84,7 @@ Mujo: "🆘 Mujo's Commands: ``` ✅ **3 Sprachen** + - Erkennt automatisch: Deutsch, English, Bosnisch - Antwortet in erkannter Sprache @@ -99,6 +111,7 @@ Mujo: "🆘 Mujo's Commands: **Bot Token Scopes:** Stelle sicher diese Scopes sind aktiviert: + - ✅ `chat:write` - Send messages - ✅ `channels:read` - View channels - ✅ `groups:read` - View private channels @@ -130,11 +143,13 @@ SLACK_ARNEL_USER_ID=U01234567 # Arnel's User ID (für Paris-Easter-Egg) ``` **Bot User ID finden:** + 1. Slack App → **OAuth & Permissions** 2. Scroll zu **Bot User OAuth Token** 3. Copy User ID (z.B. U01234567) **Signing Secret finden:** + 1. Slack App → **Basic Information** 2. Scroll zu **App Credentials** 3. Copy **Signing Secret** @@ -148,6 +163,7 @@ npm run dev ``` Output: + ``` 🚀 Starting Code Cloud Agents... ✅ Database initialized @@ -175,11 +191,13 @@ ngrok http 3000 ``` Output: + ``` Forwarding https://abc123.ngrok.io -> http://localhost:3000 ``` **Trage in Slack ein:** + ``` Request URL: https://abc123.ngrok.io/api/slack/events ``` @@ -189,6 +207,7 @@ Request URL: https://abc123.ngrok.io/api/slack/events ### 5. Test in Slack **Mujo im Channel einladen:** + ``` /invite @Mujo ``` @@ -224,43 +243,44 @@ mujo sistem status ### 🧠 Supervisor Commands (mit Supervisor-Wissen) -| Command | Beschreibung | Beispiel | -|---------|--------------|----------| -| `system status` | System Health, Metrics, Alerts | `mujo system status` | -| `health` | System Health Check | `mujo health` | -| `stop score` | STOP Score System Info | `mujo stop score` | -| `berechne stop score` | STOP Score Beispiel | `mujo berechne stop score` | -| `alerts` | Aktive System Alerts | `mujo alerts` | +| Command | Beschreibung | Beispiel | +| --------------------- | ------------------------------ | -------------------------- | +| `system status` | System Health, Metrics, Alerts | `mujo system status` | +| `health` | System Health Check | `mujo health` | +| `stop score` | STOP Score System Info | `mujo stop score` | +| `berechne stop score` | STOP Score Beispiel | `mujo berechne stop score` | +| `alerts` | Aktive System Alerts | `mujo alerts` | ### 💬 Personality Commands -| Command | Beschreibung | Beispiel | -|---------|--------------|----------| -| `wer bist du?` | Mujo vorstellen | `mujo wer bist du?` | -| `help` | Command Liste | `mujo help` | -| `status` | Bot Status | `mujo status` | -| `joke` | Witz erzählen | `mujo joke` | -| `sprache [de\|en\|bs]` | Sprache wechseln | `mujo sprache en` | +| Command | Beschreibung | Beispiel | +| ---------------------- | ---------------- | ------------------- | +| `wer bist du?` | Mujo vorstellen | `mujo wer bist du?` | +| `help` | Command Liste | `mujo help` | +| `status` | Bot Status | `mujo status` | +| `joke` | Witz erzählen | `mujo joke` | +| `sprache [de\|en\|bs]` | Sprache wechseln | `mujo sprache en` | ### 👋 Greetings & Thanks -| Input | Response | -|-------|----------| +| Input | Response | +| ------------ | ----------------------------- | | `hallo mujo` | Greeting in detected language | -| `danke mujo` | Thanks response | -| `@mujo ...` | Responds to any mention | +| `danke mujo` | Thanks response | +| `@mujo ...` | Responds to any mention | ### 🎁 Easter Eggs -| Trigger | Action | -|---------|--------| -| `ja moram na put u Paris` | 🎯 Mujo sendet automatisch DM an Arnel: "Hocemo na kafu nas dvoje dok Denis bude na putu?" + ✈️ "Bon voyage!" im Channel | -| `moram u Paris` | Same as above | -| `going to Paris` | Same as above (English) | -| `nach Paris` | Same as above (German) | -| `mujo sta je tvoja najveca zelja` | 🎯 Multi-Level Easter Egg (3 Levels: Simple → Top 5 → Secret) | +| Trigger | Action | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `ja moram na put u Paris` | 🎯 Mujo sendet automatisch DM an Arnel: "Hocemo na kafu nas dvoje dok Denis bude na putu?" + ✈️ "Bon voyage!" im Channel | +| `moram u Paris` | Same as above | +| `going to Paris` | Same as above (English) | +| `nach Paris` | Same as above (German) | +| `mujo sta je tvoja najveca zelja` | 🎯 Multi-Level Easter Egg (3 Levels: Simple → Top 5 → Secret) | **Paris Easter Egg Details:** + - Wird ausgelöst **ohne** Mujo zu erwähnen - Sendet **automatisch** eine private DM an Arnel - Gibt subtile "Bon voyage!" Bestätigung im Channel @@ -311,16 +331,19 @@ USER SEES RESPONSE ### Files Created: **1. `src/api/slack-events.ts`** + - Webhook handler - Message & mention processing - Supervisor command handling - Signature verification **2. Updated `src/index.ts`** + - Added `/api/slack/events` route - Integrated event handler **3. Already exists:** + - `src/integrations/slack/bot-responses.ts` - Interactive responses - `src/integrations/slack/humor.ts` - Jokes & personality - `src/supervisor/notifications.ts` - Supervisor integration @@ -418,20 +441,25 @@ export function verifySlackSignature(req: Request): boolean { return crypto.timingSafeEqual( Buffer.from(mySignature), - Buffer.from(slackSignature) + Buffer.from(slackSignature), ); } ``` **Aktivieren:** + ```typescript // Optional: Add middleware to verify -app.post("/api/slack/events", (req, res, next) => { - if (!verifySlackSignature(req)) { - return res.status(401).send("Invalid signature"); - } - next(); -}, handleSlackEvents); +app.post( + "/api/slack/events", + (req, res, next) => { + if (!verifySlackSignature(req)) { + return res.status(401).send("Invalid signature"); + } + next(); + }, + handleSlackEvents, +); ``` ### Bot User ID Check @@ -451,19 +479,23 @@ if (event.user === botUserId) { ### Manual Testing **1. Start Server:** + ```bash npm run dev ``` **2. Start ngrok:** + ```bash ngrok http 3000 ``` **3. Update Slack App:** + - Request URL: `https://xyz.ngrok.io/api/slack/events` **4. Test in Slack:** + ``` @mujo wer bist du? ``` @@ -487,6 +519,7 @@ npx tsx test-mujo-humor.js **Problem:** Messages erreichen Mujo nicht **Check:** + 1. ✅ Event Subscriptions aktiviert? 2. ✅ Request URL korrekt? 3. ✅ ngrok läuft? (für lokales Testing) @@ -494,6 +527,7 @@ npx tsx test-mujo-humor.js 5. ✅ `SLACK_ENABLED=true` in .env? **Debug:** + ```bash # Check server logs npm run dev @@ -509,6 +543,7 @@ npm run dev **Problem:** Slack sagt "Challenge failed" **Lösung:** + ```typescript // In slack-events.ts - dies ist schon implementiert: if (event.type === "url_verification") { @@ -517,6 +552,7 @@ if (event.type === "url_verification") { ``` **Check:** + - Server muss laufen - URL muss erreichbar sein - JSON response muss exact sein @@ -528,12 +564,14 @@ if (event.type === "url_verification") { **Problem:** Infinite loop **Lösung:** + ```bash # In .env: SLACK_BOT_USER_ID=U01234567 # Mujo's User ID eintragen ``` Code check (schon implementiert): + ```typescript if (event.user === botUserId) { return; // Don't respond to self @@ -547,12 +585,14 @@ if (event.user === botUserId) { **Problem:** 401 Error **Check:** + ```bash # .env: SLACK_SIGNING_SECRET=abc123... # Muss korrekt sein ``` **Debug:** + ```typescript // Temporarily disable verification: // Comment out verification middleware @@ -567,6 +607,7 @@ SLACK_SIGNING_SECRET=abc123... # Muss korrekt sein Du brauchst eine öffentliche URL (nicht ngrok): Options: + - **Heroku** - `heroku.com` - **Railway** - `railway.app` - **Fly.io** - `fly.io` @@ -604,11 +645,13 @@ NODE_ENV=production ### 3. Update Slack App **Request URL:** + ``` https://your-production-domain.com/api/slack/events ``` **Save & Test:** + 1. Slack validates URL 2. Send test message 3. Mujo responds! @@ -618,6 +661,7 @@ https://your-production-domain.com/api/slack/events ### 4. Monitoring **Check logs:** + ```bash # Server logs tail -f logs/app.log @@ -627,6 +671,7 @@ tail -f logs/error.log ``` **Health Check:** + ```bash curl https://your-domain.com/health ``` @@ -674,16 +719,16 @@ await slack.sendMessage({ blocks: [ { type: "header", - text: { type: "plain_text", text: "📊 System Status" } + text: { type: "plain_text", text: "📊 System Status" }, }, { type: "section", fields: [ { type: "mrkdwn", text: `*Tasks:* ${metrics.totalTasks}` }, - { type: "mrkdwn", text: `*STOP Score:* ${metrics.avgStopScore}` } - ] - } - ] + { type: "mrkdwn", text: `*STOP Score:* ${metrics.avgStopScore}` }, + ], + }, + ], }); ``` @@ -700,6 +745,7 @@ Erstelle Slack Slash Commands: ``` **Setup:** + 1. Slack App → **Slash Commands** 2. **Create New Command** 3. Request URL: `https://your-domain.com/api/slack/commands` @@ -717,7 +763,7 @@ await slack.sendMessage({ blocks: [ { type: "section", - text: { type: "mrkdwn", text: "Task needs approval" } + text: { type: "mrkdwn", text: "Task needs approval" }, }, { type: "actions", @@ -725,17 +771,17 @@ await slack.sendMessage({ { type: "button", text: { type: "plain_text", text: "Approve ✅" }, - action_id: "approve_task" + action_id: "approve_task", }, { type: "button", text: { type: "plain_text", text: "Reject ❌" }, action_id: "reject_task", - style: "danger" - } - ] - } - ] + style: "danger", + }, + ], + }, + ], }); ``` diff --git a/README.md b/README.md index 2fd6be8..75074f6 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Ein intelligentes Supervisor-System, das AI-Agenten überwacht, Risiken bewertet ## ✨ Features ### Core Features + - **🎯 STOP-Score System**: Automatische Risikobewertung (0-100) basierend auf verschiedenen Faktoren - **🛡️ Enforcement Gate**: HARD STOP bei kritischen Entscheidungen (Score ≥ 70) - **👨‍💼 Engineering Lead Supervisor**: Meta-Supervisor für Planung, Delegation und Verifikation @@ -36,6 +37,7 @@ Ein intelligentes Supervisor-System, das AI-Agenten überwacht, Risiken bewertet - **🛡️ Rate Limiting**: Brute-force protection auf Login und sensitive Endpoints ### Integrations + - **Demo Invite System**: User-Onboarding mit Invite-Codes und Usage-Limits - **SQLite Database**: Leichtgewichtige, lokale Datenpersistenz - **Queue System**: Redis (production) oder In-Memory (development) @@ -136,6 +138,7 @@ Server läuft auf: **http://localhost:3000** ### Tech Stack **Backend:** + - Node.js v20+ - TypeScript - Express.js @@ -143,6 +146,7 @@ Server läuft auf: **http://localhost:3000** - Redis (optional) **Frontend:** + - React 18 - TypeScript - Vite @@ -370,16 +374,19 @@ npm test -- --coverage ### JWT Authentication System **Access Tokens:** + - Validity: 15 minutes - Purpose: API authentication - Automatic refresh via refresh token **Refresh Tokens:** + - Validity: 7 days - Token rotation on refresh (security best practice) - Stored in-memory blacklist on logout **Security Features:** + - Separate secrets for access/refresh tokens - Unique JWT IDs (jti) for refresh tokens - Issuer and Audience validation @@ -389,11 +396,13 @@ npm test -- --coverage ### User Management **Role-Based Access Control (RBAC):** + - **Admin**: Full system access, user management - **User**: Own profile access, password change - **Demo**: Read-only access **Features:** + - bcrypt password hashing (10 salt rounds) - Email uniqueness enforcement - Case-insensitive email lookup @@ -404,12 +413,14 @@ npm test -- --coverage ### Email Verification **Token System:** + - Secure 32-byte hex tokens - 24-hour expiry - One-time use (marked as used after verification) - Automatic old token invalidation **Flow:** + 1. User registers → verification token generated 2. Email sent with verification link (DEV: token returned in API) 3. User clicks link → token verified → email marked as verified @@ -417,12 +428,14 @@ npm test -- --coverage ### Password Reset **Token System:** + - Secure 32-byte hex tokens - **1-hour expiry** (more secure than email verification) - One-time use - Automatic old token invalidation **Security Features:** + - No user enumeration (always returns success) - Inactive users cannot reset password - Token marked as used after successful reset @@ -431,17 +444,20 @@ npm test -- --coverage ### Rate Limiting **IP-Based Protection:** + - **Login**: 5 attempts per 15 minutes - **Password Reset**: 3 attempts per hour - **Email Verification**: 3 attempts per hour **Implementation:** + - In-memory store (Redis-ready for production) - Automatic cleanup of expired entries - Real-time IP tracking - Returns retry-after time on rate limit **Status Codes:** + - `200`: Success - `401`: Unauthorized (invalid credentials/token) - `403`: Forbidden (inactive account, no permission) @@ -462,6 +478,7 @@ Siehe auch: **[Security Guide](docs/SECURITY.md)** (coming soon) ## 📦 Dependencies ### Production + - `express`: Web Framework - `better-sqlite3`: SQLite Database - `bcrypt`: Password hashing @@ -471,6 +488,7 @@ Siehe auch: **[Security Guide](docs/SECURITY.md)** (coming soon) - `@radix-ui/*`: UI Components ### Development + - `typescript`: Type Safety - `vite`: Build Tool - `tsx`: TypeScript Runtime @@ -511,6 +529,7 @@ Contributions sind willkommen! Bitte lies zuerst den **[Contributing Guide](docs ## 🎯 Roadmap ### ✅ Completed + - [x] **Authentication & User Management** (Agent 2 - Security Expert) - JWT-based authentication mit token rotation - User Management mit RBAC (Admin/User/Demo) @@ -520,10 +539,12 @@ Contributions sind willkommen! Bitte lies zuerst den **[Contributing Guide](docs - 19 comprehensive tests (all passing) ### 🔄 In Progress + - [ ] OpenAPI/Swagger Documentation - [ ] Postman Collection ### 📋 Planned + - [ ] WebSocket Real-time Updates - [ ] Integration APIs (GitHub, Slack, Linear) - [ ] Multi-Provider AI Support diff --git a/SETUP_ISSUES.md b/SETUP_ISSUES.md index 4323b1e..6e69bd7 100644 --- a/SETUP_ISSUES.md +++ b/SETUP_ISSUES.md @@ -21,16 +21,19 @@ ### 1. Backend startet nicht - Missing Dependency: better-sqlite3 **Fehler:** + ``` Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'better-sqlite3' imported from /Users/dsselmanovic/activi-dev-repos/Optimizecodecloudagents/src/db/database.ts ``` **Root Cause:** + - `better-sqlite3` wird in `src/db/database.ts:5` importiert - Package ist NICHT in `package.json` dependencies/devDependencies **Lösung:** + ```bash npm install better-sqlite3 npm install --save-dev @types/better-sqlite3 @@ -43,6 +46,7 @@ npm install --save-dev @types/better-sqlite3 ### 2. TypeScript Build fehlschlägt - Missing Type Definitions **Fehler:** + ``` error TS7016: Could not find a declaration file for module 'express' error TS2307: Cannot find module '@radix-ui/react-accordion@1.2.3' @@ -51,17 +55,20 @@ error TS2307: Cannot find module 'class-variance-authority@0.7.1' ``` **Root Cause:** + - `@types/express` fehlt in devDependencies - TypeScript strict mode erfordert explizite Types - Viele UI-Komponenten-Libraries haben keine Type-Definitionen **Lösung:** + ```bash npm install --save-dev @types/express npm install --save-dev @types/better-sqlite3 ``` Optional (für alle anderen Fehler): + ```bash # tsconfig.json anpassen { @@ -79,6 +86,7 @@ Optional (für alle anderen Fehler): ### 3. Tests schlagen fehl - TypeScript nicht kompiliert **Fehler:** + ``` Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/dsselmanovic/activi-dev-repos/Optimizecodecloudagents/src/audit/claimVerifier.js' @@ -86,11 +94,13 @@ imported from tests/enforcementGate.test.ts ``` **Root Cause:** + - Tests importieren `.js` Dateien (kompilierte TypeScript-Ausgabe) - TypeScript Build ist fehlgeschlagen → keine `.js` Dateien vorhanden - Tests können nicht laufen ohne kompilierte Dateien **Lösung:** + 1. Behebe zuerst Problem #2 (TypeScript Build) 2. Dann: `npm run backend:build` 3. Dann: `npm test` @@ -104,6 +114,7 @@ imported from tests/enforcementGate.test.ts ### 4. Security Vulnerabilities **Output von npm install:** + ``` 2 moderate severity vulnerabilities @@ -112,6 +123,7 @@ To address all issues (including breaking changes), run: ``` **Lösung:** + ```bash npm audit npm audit fix --force # (Achtung: Breaking changes möglich!) @@ -124,12 +136,14 @@ npm audit fix --force # (Achtung: Breaking changes möglich!) ### 5. Deprecated Packages **Warnungen:** + - `inflight@1.0.6` - "leaks memory" - `rimraf@3.0.2` - "no longer supported" - `glob@7.2.3, glob@8.1.0` - "no longer supported" - `npmlog@6.0.2` - "no longer supported" **Lösung:** + ```bash # Dependencies updaten npm update @@ -143,11 +157,13 @@ npm update ### 6. Missing API Keys in .env **Status:** + - `.env` hat Default-Werte aus `.env.example` - API Keys für Anthropic/OpenAI/etc. sind leer - Backend läuft wahrscheinlich, aber AI-Funktionen gehen nicht **Benötigte Keys (optional für AI-Features):** + ```bash ANTHROPIC_API_KEY=sk-ant-... OPENAI_API_KEY=sk-... @@ -164,12 +180,14 @@ User muss Keys manuell in `.env` eintragen ## 📋 Nächste Schritte ### Sofort nötig (BLOCKER): + 1. `npm install better-sqlite3 @types/better-sqlite3` 2. `npm install --save-dev @types/express` 3. Backend-Build fixen oder `tsconfig.json` anpassen 4. Tests erneut ausführen ### Optional: + - Security Audit durchführen (`npm audit fix`) - Dependencies updaten (`npm update`) - API Keys eintragen (falls AI-Features getestet werden sollen) @@ -178,20 +196,21 @@ User muss Keys manuell in `.env` eintragen ## 📊 Test-Ergebnisse -| Test | Status | Details | -|------|--------|---------| -| Dependencies Install | ✅ PASS | 647 packages installiert | -| Frontend Build | ✅ PASS | Vite Build erfolgreich | -| Frontend Start | ✅ PASS | Läuft auf localhost:3000 | -| Backend Build | ❌ FAIL | TypeScript Fehler (100+) | -| Backend Start | ❌ FAIL | better-sqlite3 fehlt | -| Tests | ❌ FAIL | Benötigt kompilierte JS-Dateien | +| Test | Status | Details | +| -------------------- | ------- | ------------------------------- | +| Dependencies Install | ✅ PASS | 647 packages installiert | +| Frontend Build | ✅ PASS | Vite Build erfolgreich | +| Frontend Start | ✅ PASS | Läuft auf localhost:3000 | +| Backend Build | ❌ FAIL | TypeScript Fehler (100+) | +| Backend Start | ❌ FAIL | better-sqlite3 fehlt | +| Tests | ❌ FAIL | Benötigt kompilierte JS-Dateien | --- ## 🔗 Logs Alle Logs sind im Projekt-Root: + - `test-results.log` - npm test Output - `backend-test.log` - npm run backend:dev Output - `frontend-test.log` - npm run dev Output diff --git a/TASKS.md b/TASKS.md index 79910bf..88897e4 100644 --- a/TASKS.md +++ b/TASKS.md @@ -7,12 +7,14 @@ ## 🔴 High Priority ### 1. Test Assertion Fix + **Location:** `tests/stopScorer.test.ts` **Status:** ⏳ Pending **Estimated Time:** 15 minutes **Description:** Fix failing test assertion in STOP scorer tests (1/15 tests failing) ### 2. Chat Provider Implementation + **Location:** `src/chat/manager.ts:219` **Status:** ⏳ Pending **Estimated Time:** 2-3 hours @@ -20,7 +22,9 @@ **Dependencies:** API keys configured, provider clients working ### 3. Admin Authentication Checks + **Locations:** + - `src/api/billing.ts:114` (GET /api/billing/limits) - `src/api/billing.ts:154` (PUT /api/billing/limits/:userId) - `src/api/billing.ts:180` (GET /api/billing/costs) @@ -35,10 +39,12 @@ ## 🟡 Medium Priority ### 4. Chat Frontend Implementation + **Location:** `src/components/` (to be created) **Status:** ⏳ Pending **Estimated Time:** 3-4 hours **Description:** Create React components for chat interface + - `ChatInterface.tsx` - Main chat container - `ChatList.tsx` - List of chats - `MessageList.tsx` - Message display @@ -48,10 +54,12 @@ **Reference:** See `docs/FRONTEND_IMPLEMENTATION.md` for component specs ### 5. Integration Stubs Implementation + **Location:** `src/integrations/index.ts:8` **Status:** ⏳ Pending **Estimated Time:** 4-6 hours **Description:** Implement actual clients for stubbed integrations: + - WhatsApp Business API (`whatsapp/client.ts:43`) - 2h - Voice AI integration - 2-3h - iCloud integration - 1-2h @@ -59,6 +67,7 @@ **Note:** WhatsApp and Voice require external API accounts ### 6. Redis/BullMQ Queue Adapter + **Location:** `src/queue/queue.ts:113` **Status:** ⏳ Pending **Estimated Time:** 3-4 hours @@ -71,12 +80,14 @@ ## 🟢 Low Priority ### 7. Project Planner with Cost Forecast + **Location:** To be created **Status:** ⏳ Pending **Estimated Time:** 6-8 hours **Description:** UI for project planning with cost estimation per phase ### 8. Design System Implementation + **Location:** To be created **Status:** ⏳ Pending **Estimated Time:** 2-3 hours @@ -84,10 +95,12 @@ **Reference:** See `docs/DESIGN_SPECIFICATION.md` ### 9. Accessibility Improvements + **Location:** Frontend components **Status:** ⏳ Pending **Estimated Time:** 1-2 hours **Description:** + - Add ARIA labels - Implement keyboard navigation - Add screen reader support @@ -96,11 +109,11 @@ ## 📊 Summary -| Priority | Tasks | Estimated Time | Status | -|----------|-------|----------------|--------| -| 🔴 High | 3 | 4-7 hours | 0% complete | -| 🟡 Medium | 3 | 10-14 hours | 0% complete | -| 🟢 Low | 3 | 9-13 hours | 0% complete | +| Priority | Tasks | Estimated Time | Status | +| --------- | ----- | --------------- | --------------- | +| 🔴 High | 3 | 4-7 hours | 0% complete | +| 🟡 Medium | 3 | 10-14 hours | 0% complete | +| 🟢 Low | 3 | 9-13 hours | 0% complete | | **Total** | **9** | **23-34 hours** | **0% complete** | --- @@ -108,16 +121,19 @@ ## 🚀 Quick Start Recommendations ### For Immediate Impact (Today): + 1. ✅ Fix test assertion (15 min) 2. ✅ Add admin auth to billing endpoints (1-2h) - **Security critical!** 3. ✅ Implement chat provider calls (2-3h) - **Feature enablement** ### For This Week: + 4. Chat frontend components (3-4h) 5. WhatsApp integration (2h) 6. Redis queue adapter (3-4h) ### For Next Week: + 7. Design system (2-3h) 8. Project planner UI (6-8h) 9. Accessibility (1-2h) diff --git a/agents/agent_training_prompts.md b/agents/agent_training_prompts.md index 31038b8..e7cdefd 100644 --- a/agents/agent_training_prompts.md +++ b/agents/agent_training_prompts.md @@ -1,4 +1,3 @@ - ## Training Module: Fehlererkennung & STOP **Fall 1: Preisfrage** diff --git a/agents/supervisor_dashboard_mock.json b/agents/supervisor_dashboard_mock.json index bb5f51d..86eb3c4 100644 --- a/agents/supervisor_dashboard_mock.json +++ b/agents/supervisor_dashboard_mock.json @@ -18,4 +18,4 @@ "action_required": true, "supervisor_recommendation": "Pause AGENT_011 and rebrief immediately" } -} \ No newline at end of file +} diff --git a/docs/AGENT_SETUP.md b/docs/AGENT_SETUP.md index 1ae9678..75dd466 100644 --- a/docs/AGENT_SETUP.md +++ b/docs/AGENT_SETUP.md @@ -6,42 +6,46 @@ ## 📋 Agent-Übersicht -| Agent | Ersetzt | Trigger | Priorität | -|-------|---------|---------|-----------| -| **Lead Import Agent** | Manuelles Eintragen | Webhook/API | 🔴 Hoch | -| **Lead Qualifier Agent** | Sales-Qualifizierung | Nach Import | 🔴 Hoch | -| **Voice AI Agent** | Telefon-Sales | Score > 50 | 🔴 Hoch | -| **Email Agent** | Follow-up Mails | Nach Call/Event | 🟡 Mittel | -| **Report Agent** | Tägliche Reports | Scheduled | 🟢 Niedrig | -| **Support Agent** | Kundenanfragen | Ticket/Chat | 🟡 Mittel | +| Agent | Ersetzt | Trigger | Priorität | +| ------------------------ | -------------------- | --------------- | ---------- | +| **Lead Import Agent** | Manuelles Eintragen | Webhook/API | 🔴 Hoch | +| **Lead Qualifier Agent** | Sales-Qualifizierung | Nach Import | 🔴 Hoch | +| **Voice AI Agent** | Telefon-Sales | Score > 50 | 🔴 Hoch | +| **Email Agent** | Follow-up Mails | Nach Call/Event | 🟡 Mittel | +| **Report Agent** | Tägliche Reports | Scheduled | 🟢 Niedrig | +| **Support Agent** | Kundenanfragen | Ticket/Chat | 🟡 Mittel | --- ## 🤖 Agent 1: Lead Import Agent ### Funktion + Automatisch Leads aus verschiedenen Quellen importieren ### Trigger + - Webhook von Website-Formular - API Call von Ads (Facebook, Google) - CSV Import - E-Mail Parser ### Einstellungen + ```yaml name: lead-import-agent -model: claude-3-haiku # Schnell + günstig +model: claude-3-haiku # Schnell + günstig tools: - Read - Write - Bash(curl:*) - Database -temperature: 0.1 # Deterministisch +temperature: 0.1 # Deterministisch max_tokens: 1000 ``` ### Prompt + ``` Du bist ein Lead Import Agent. @@ -71,26 +75,30 @@ OUTPUT: ## 🤖 Agent 2: Lead Qualifier Agent ### Funktion + Automatisch Lead Score berechnen und priorisieren ### Trigger + - Nach Lead Import - Täglich für alle Leads ohne Score - Nach Interaction Update ### Einstellungen + ```yaml name: lead-qualifier-agent -model: claude-3-sonnet # Bessere Analyse +model: claude-3-sonnet # Bessere Analyse tools: - Read - Database - - WebSearch # Firmen-Research + - WebSearch # Firmen-Research temperature: 0.3 max_tokens: 2000 ``` ### Scoring Kriterien + ``` SCORE BERECHNUNG (0-100): @@ -111,6 +119,7 @@ PRIORITÄT: ``` ### Prompt + ``` Du bist ein Lead Qualifier Agent. @@ -142,21 +151,24 @@ OUTPUT: ## 🤖 Agent 3: Voice AI Agent ### Funktion + Automatische Verkaufsanrufe durchführen ### Trigger + - Lead Score > 50 - Manueller Trigger - Scheduled Follow-up ### Provider-Einstellungen + ```yaml name: voice-ai-agent -provider: vapi # oder retell, bland +provider: vapi # oder retell, bland voice_config: language: de-DE - voice: "ElevenLabs - Stefan" # Männlich, professionell + voice: "ElevenLabs - Stefan" # Männlich, professionell speed: 1.0 stt_config: @@ -175,6 +187,7 @@ telephony: ``` ### Call Script + ``` ERÖFFNUNG: "Guten Tag, [Name]. Hier ist [Agent] von [Firma]. @@ -203,25 +216,29 @@ REGELN: ## 🤖 Agent 4: Email Agent ### Funktion + Automatische Follow-up E-Mails senden ### Trigger + - Nach Voice Call (immer) - Nach X Tagen ohne Reaktion - Nach Interaction Event ### Einstellungen + ```yaml name: email-agent model: claude-3-haiku tools: - Read - - SendGrid # oder SMTP + - SendGrid # oder SMTP - Database temperature: 0.5 ``` ### Email Templates + ``` NACH CALL - INTERESSE: Subject: Zusammenfassung unseres Gesprächs | [Firma] @@ -275,25 +292,29 @@ Beste Grüße, ## 🤖 Agent 5: Report Agent ### Funktion + Tägliche/Wöchentliche Reports erstellen ### Trigger + - Täglich 18:00 - Wöchentlich Montag 09:00 - Auf Anfrage ### Einstellungen + ```yaml name: report-agent model: claude-3-sonnet tools: - Read - Database - - Charts # Matplotlib/Plotly + - Charts # Matplotlib/Plotly temperature: 0.1 ``` ### Report Struktur + ``` DAILY REPORT: @@ -329,14 +350,17 @@ TOP LEADS: ## 🤖 Agent 6: Support Agent ### Funktion + Kundenanfragen beantworten ### Trigger + - Neues Support-Ticket - Chat-Nachricht - Email an Support ### Einstellungen + ```yaml name: support-agent model: claude-3-sonnet @@ -349,6 +373,7 @@ max_tokens: 1500 ``` ### Prompt + ``` Du bist ein Support Agent für [Firma]. diff --git a/docs/API.md b/docs/API.md index 76728cb..27b3002 100644 --- a/docs/API.md +++ b/docs/API.md @@ -43,6 +43,7 @@ Content-Type: application/json ``` **Response:** + ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", @@ -68,15 +69,18 @@ curl -H "Authorization: Bearer YOUR_TOKEN" \ ## 📋 API Endpoints Overview ### Health & Info + - `GET /health` - System health check - `GET /api` - API information ### Authentication + - `POST /api/auth/login` - User login - `POST /api/auth/logout` - User logout - `POST /api/auth/refresh` - Refresh JWT token ### Tasks + - `GET /api/tasks` - List all tasks - `POST /api/tasks` - Create new task - `GET /api/tasks/:id` - Get task details @@ -84,15 +88,18 @@ curl -H "Authorization: Bearer YOUR_TOKEN" \ - `DELETE /api/tasks/:id` - Delete task ### Audit + - `GET /api/audit` - Get audit logs - `POST /api/audit/verify/:id` - Verify task claims ### Enforcement + - `GET /api/enforcement/blocked` - Get blocked tasks - `POST /api/enforcement/approve` - Approve blocked task - `POST /api/enforcement/reject` - Reject blocked task ### Memory System (21 Endpoints) + - `GET /api/memory/conversations` - List conversations - `POST /api/memory/conversations` - Create conversation - `GET /api/memory/conversations/:id` - Get conversation @@ -102,25 +109,30 @@ curl -H "Authorization: Bearer YOUR_TOKEN" \ - _...and 15 more_ ### GitHub Integration + - `GET /api/github/repos` - List repositories - `GET /api/github/issues/:owner/:repo` - Get issues - `POST /api/github/issues/:owner/:repo` - Create issue - `GET /api/github/prs/:owner/:repo` - Get pull requests ### Linear Integration + - `GET /api/linear/issues` - List issues - `POST /api/linear/issues` - Create issue - `GET /api/linear/projects` - List projects ### Settings + - `GET /api/settings` - Get settings - `PUT /api/settings` - Update settings ### Webhooks + - `POST /api/webhooks/github` - GitHub webhook handler - `POST /api/webhooks/linear` - Linear webhook handler ### Slack + - `POST /api/slack/events` - Slack events handler --- @@ -134,6 +146,7 @@ curl http://178.156.178.70:3000/health ``` **Response:** + ```json { "status": "healthy", @@ -161,6 +174,7 @@ curl -X POST http://178.156.178.70:3000/api/tasks \ ``` **Response:** + ```json { "id": "task-abc123", @@ -200,12 +214,12 @@ curl -X POST http://178.156.178.70:3000/api/memory/search \ Tasks are automatically evaluated for risk using STOP scores (0-100): -| Score | Risk Level | Action | -|-------|------------|--------| -| 0-19 | LOW | ✅ Proceed automatically | -| 20-44 | MEDIUM | ⚠️ Review required | -| 45-69 | HIGH | ⚠️ Approval needed | -| 70-100 | CRITICAL | 🛑 **STOP REQUIRED** | +| Score | Risk Level | Action | +| ------ | ---------- | ------------------------ | +| 0-19 | LOW | ✅ Proceed automatically | +| 20-44 | MEDIUM | ⚠️ Review required | +| 45-69 | HIGH | ⚠️ Approval needed | +| 70-100 | CRITICAL | 🛑 **STOP REQUIRED** | When a task is blocked (STOP score ≥ 45), it requires human approval: @@ -225,11 +239,11 @@ POST /api/enforcement/approve Real-time updates available via WebSocket: ```javascript -const ws = new WebSocket('ws://178.156.178.70:3000'); +const ws = new WebSocket("ws://178.156.178.70:3000"); -ws.on('message', (data) => { +ws.on("message", (data) => { const event = JSON.parse(data); - console.log('Event:', event); + console.log("Event:", event); }); // Events: @@ -245,6 +259,7 @@ ws.on('message', (data) => { The Memory System provides semantic search and conversation management across 21 endpoints. ### Key Features: + - **Conversations**: Multi-turn chat history - **Messages**: Individual messages with embeddings - **Semantic Search**: Vector similarity search @@ -319,14 +334,14 @@ All errors follow this format: ### Common Error Codes: -| Code | Status | Description | -|------|--------|-------------| -| `UNAUTHORIZED` | 401 | Missing or invalid token | -| `FORBIDDEN` | 403 | Insufficient permissions | -| `NOT_FOUND` | 404 | Resource not found | -| `VALIDATION_ERROR` | 400 | Invalid request data | -| `STOP_REQUIRED` | 403 | Task blocked by enforcement gate | -| `INTERNAL_ERROR` | 500 | Server error | +| Code | Status | Description | +| ------------------ | ------ | -------------------------------- | +| `UNAUTHORIZED` | 401 | Missing or invalid token | +| `FORBIDDEN` | 403 | Insufficient permissions | +| `NOT_FOUND` | 404 | Resource not found | +| `VALIDATION_ERROR` | 400 | Invalid request data | +| `STOP_REQUIRED` | 403 | Task blocked by enforcement gate | +| `INTERNAL_ERROR` | 500 | Server error | --- @@ -336,6 +351,7 @@ All errors follow this format: - **Authenticated:** 1000 requests per minute per user Exceeded limits return: + ```json { "error": "Rate limit exceeded", @@ -348,6 +364,7 @@ Exceeded limits return: ## 🧪 Testing ### Using curl: + ```bash # Set token export TOKEN="your-jwt-token" @@ -358,6 +375,7 @@ curl -H "Authorization: Bearer $TOKEN" \ ``` ### Using Postman: + Import the Postman collection: `postman/collection.json` --- diff --git a/docs/API_CURL_EXAMPLES.md b/docs/API_CURL_EXAMPLES.md index 93b5566..d36d46a 100644 --- a/docs/API_CURL_EXAMPLES.md +++ b/docs/API_CURL_EXAMPLES.md @@ -3,6 +3,7 @@ ## Authentication ### Login (get JWT token) + ```bash # Success case curl -X POST http://localhost:4000/api/auth/login \ @@ -36,6 +37,7 @@ curl -X POST http://localhost:4000/api/auth/login \ ## Agents Status (NEW) ### GET /api/agents/status + Get status overview of all agents (for MCP tools / dashboard). ```bash @@ -46,6 +48,7 @@ curl -X GET http://localhost:4000/api/agents/status \ ``` **Response (200 OK):** + ```json { "success": true, @@ -75,6 +78,7 @@ curl -X GET http://localhost:4000/api/agents/status \ ``` **Error (401 Unauthorized):** + ```json { "error": "No authorization header" @@ -82,6 +86,7 @@ curl -X GET http://localhost:4000/api/agents/status \ ``` **Error (403 Forbidden):** + ```json { "error": "Admin access required" @@ -93,6 +98,7 @@ curl -X GET http://localhost:4000/api/agents/status \ ## Tasks ### GET /api/tasks - List all tasks + ```bash TOKEN="your-jwt-token" @@ -108,6 +114,7 @@ curl -X GET "http://localhost:4000/api/tasks?state=running" \ ``` **Response (200 OK):** + ```json { "success": true, @@ -126,14 +133,16 @@ curl -X GET "http://localhost:4000/api/tasks?state=running" \ ``` ### State mapping: + | API State | Database Status | -|-----------|-----------------| +| --------- | --------------- | | running | in_progress | | failed | stopped | | done | completed | | pending | pending | ### POST /api/tasks - Create task + ```bash TOKEN="your-jwt-token" @@ -148,6 +157,7 @@ curl -X POST http://localhost:4000/api/tasks \ ``` **Response (201 Created):** + ```json { "id": "uuid-123", @@ -158,6 +168,7 @@ curl -X POST http://localhost:4000/api/tasks \ ``` **Response (202 Accepted - BLOCKED by Enforcement Gate):** + ```json { "id": "uuid-123", @@ -216,6 +227,7 @@ curl -s -X GET $BASE_URL/health | jq '.' ## Error Response Shapes ### 400 Bad Request + ```json { "error": "Invalid request", @@ -230,12 +242,15 @@ curl -s -X GET $BASE_URL/health | jq '.' ``` ### 401 Unauthorized + ```json { "error": "No authorization header" } ``` + or + ```json { "error": "Invalid or expired token" @@ -243,6 +258,7 @@ or ``` ### 403 Forbidden + ```json { "error": "Admin access required" @@ -250,6 +266,7 @@ or ``` ### 404 Not Found + ```json { "error": "Task not found" @@ -257,6 +274,7 @@ or ``` ### 429 Too Many Requests + ```json { "error": "Too many requests. Please try again later." @@ -264,6 +282,7 @@ or ``` ### 500 Internal Server Error + ```json { "error": "Internal server error" diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 988274f..daae554 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -78,6 +78,7 @@ Code Cloud Agents ist ein **Supervised AI System**, das AI-Agenten überwacht un **Technologie:** React 18 + TypeScript + Vite **Verantwortlichkeiten:** + - User Interface für Agent-Monitoring - Task-Management Dashboard - Audit-Log Visualisierung @@ -85,6 +86,7 @@ Code Cloud Agents ist ein **Supervised AI System**, das AI-Agenten überwacht un - Admin Controls (Demo System, Enforcement Approvals) **Komponenten:** + - `App.tsx`: Main Application Component - `AgentCard.tsx`: Agent Status Display - `TaskCard.tsx`: Task Details Display @@ -98,6 +100,7 @@ Code Cloud Agents ist ein **Supervised AI System**, das AI-Agenten überwacht un **Technologie:** Node.js v20+ + Express + TypeScript **Verantwortlichkeiten:** + - REST API Endpoints - Business Logic Execution - Database Operations @@ -107,6 +110,7 @@ Code Cloud Agents ist ein **Supervised AI System**, das AI-Agenten überwacht un - Enforcement Gate Logic **Module:** + - `src/index.ts`: Server Entry Point - `src/api/*`: REST API Routes - `src/db/database.ts`: Database Interface @@ -121,12 +125,14 @@ Code Cloud Agents ist ein **Supervised AI System**, das AI-Agenten überwacht un **Technologie:** SQLite (better-sqlite3) **Verantwortlichkeiten:** + - Persistent Data Storage - ACID Transactions - Indexing & Query Optimization - Audit Trail Storage **Schema:** + - `tasks`: Task management - `audit_entries`: Audit log - `demo_invites`: Invite codes @@ -139,6 +145,7 @@ Code Cloud Agents ist ein **Supervised AI System**, das AI-Agenten überwacht un **Technologie:** Redis (production) / In-Memory (development) **Verantwortlichkeiten:** + - Task Queue Management - Background Job Processing - Retry Logic @@ -151,6 +158,7 @@ Code Cloud Agents ist ein **Supervised AI System**, das AI-Agenten überwacht un **Technologie:** Custom TypeScript Module **Verantwortlichkeiten:** + - STOP-Score Calculation - Risk Assessment - Task Blocking @@ -225,33 +233,35 @@ Code Cloud Agents ist ein **Supervised AI System**, das AI-Agenten überwacht un ### Datentypen #### Task + ```typescript interface Task { - id: string; // UUID - title: string; // Max 200 chars - description?: string; // Optional + id: string; // UUID + title: string; // Max 200 chars + description?: string; // Optional priority: "low" | "medium" | "high"; status: "pending" | "in_progress" | "completed" | "stopped"; - assignee: string; // Agent name - created_at: string; // ISO 8601 - updated_at?: string; // ISO 8601 - stop_score?: number; // 0-100 + assignee: string; // Agent name + created_at: string; // ISO 8601 + updated_at?: string; // ISO 8601 + stop_score?: number; // 0-100 } ``` #### Audit Entry + ```typescript interface AuditEntry { - id: string; // UUID - task_id?: string; // FK to tasks + id: string; // UUID + task_id?: string; // FK to tasks decision: "APPROVED" | "STOP_REQUIRED"; final_status: "COMPLETE" | "COMPLETE_WITH_GAPS" | "STOP_REQUIRED"; risk_level: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL"; - stop_score: number; // 0-100 - verified_artefacts: string; // JSON array + stop_score: number; // 0-100 + verified_artefacts: string; // JSON array missing_invalid_parts: string; // JSON array required_next_action: string; - created_at: string; // ISO 8601 + created_at: string; // ISO 8601 } ``` @@ -264,6 +274,7 @@ interface AuditEntry { **Base URL:** `http://localhost:3000` **Principles:** + - RESTful Design - JSON Request/Response - HTTP Status Codes @@ -275,12 +286,14 @@ interface AuditEntry { ### API Endpoints #### Health & Info + ``` GET /api → API Info GET /health → Health Check ``` #### Tasks + ``` POST /api/tasks → Create Task GET /api/tasks → List All Tasks @@ -289,12 +302,14 @@ PUT /api/tasks/:id → Update Task ``` #### Audit + ``` GET /api/audit → List Audit Entries GET /api/audit/:id → Get Audit Entry ``` #### Enforcement + ``` GET /api/enforcement/blocked → List Blocked Tasks POST /api/enforcement/approve → Approve Blocked Task @@ -302,6 +317,7 @@ POST /api/enforcement/reject → Reject Blocked Task ``` #### Demo System + ``` POST /api/demo/invites → Create Invite (Admin) POST /api/demo/redeem → Redeem Invite Code @@ -314,6 +330,7 @@ GET /api/demo/users/:id → User Usage Stats ### Request/Response Format **Success Response:** + ```json { "success": true, @@ -325,6 +342,7 @@ GET /api/demo/users/:id → User Usage Stats ``` **Error Response:** + ```json { "success": false, @@ -340,15 +358,15 @@ GET /api/demo/users/:id → User Usage Stats ### HTTP Status Codes -| Code | Meaning | Usage | -|------|---------|-------| -| 200 | OK | Successful GET/PUT | -| 201 | Created | Successful POST | -| 400 | Bad Request | Invalid input | -| 401 | Unauthorized | Authentication required | -| 403 | Forbidden | No permission | -| 404 | Not Found | Resource not found | -| 500 | Internal Error | Server error | +| Code | Meaning | Usage | +| ---- | -------------- | ----------------------- | +| 200 | OK | Successful GET/PUT | +| 201 | Created | Successful POST | +| 400 | Bad Request | Invalid input | +| 401 | Unauthorized | Authentication required | +| 403 | Forbidden | No permission | +| 404 | Not Found | Resource not found | +| 500 | Internal Error | Server error | --- @@ -385,12 +403,14 @@ App.tsx **Strategy:** React Context + Hooks **Global State:** + - `AgentContext`: Agent status, capabilities - `TaskContext`: Task list, active task - `AuditContext`: Audit entries - `EnforcementContext`: Blocked tasks, approvals **Component State:** + - Local UI state (modals, forms, etc.) - Form inputs - Loading states @@ -435,12 +455,12 @@ Der **STOP-Score** ist eine Risikobewertung (0-100), die bestimmt, ob eine Agent ```typescript const WEIGHTS = { - PRICE_CLAIM: 25, // Preisbehauptung - LEGAL_CLAIM: 30, // Rechtsbehauptung - UNPROVEN_CLAIM: 20, // Unbewiesene Behauptung - MISSING_EVIDENCE: 15, // Fehlende Evidenz - IMPLEMENTATION_GAP: 10, // Implementierungslücke - NO_VERIFICATION: 25, // Keine Verifikation + PRICE_CLAIM: 25, // Preisbehauptung + LEGAL_CLAIM: 30, // Rechtsbehauptung + UNPROVEN_CLAIM: 20, // Unbewiesene Behauptung + MISSING_EVIDENCE: 15, // Fehlende Evidenz + IMPLEMENTATION_GAP: 10, // Implementierungslücke + NO_VERIFICATION: 25, // Keine Verifikation }; function computeStopScore(reasons: StopReason[]): ScoreResult { @@ -463,25 +483,25 @@ function computeStopScore(reasons: StopReason[]): ScoreResult { ### Risk Levels -| Score | Level | Action | -|-------|-------|--------| -| 0-19 | LOW | ✅ Proceed | -| 20-44 | MEDIUM | ⚠️ Review Recommended | -| 45-69 | HIGH | ⚠️ Approval Recommended | -| 70-100 | CRITICAL | 🛑 **HARD STOP** | +| Score | Level | Action | +| ------ | -------- | ----------------------- | +| 0-19 | LOW | ✅ Proceed | +| 20-44 | MEDIUM | ⚠️ Review Recommended | +| 45-69 | HIGH | ⚠️ Approval Recommended | +| 70-100 | CRITICAL | 🛑 **HARD STOP** | --- ### STOP Reasons -| Reason | Score | Description | -|--------|-------|-------------| -| `PRICE_CLAIM` | 25 | Unverified price statement | -| `LEGAL_CLAIM` | 30 | Unverified legal statement | -| `UNPROVEN_CLAIM` | 20 | Claim without evidence | -| `MISSING_EVIDENCE` | 15 | Expected artefacts missing | -| `IMPLEMENTATION_GAP` | 10 | Code incomplete | -| `NO_VERIFICATION` | 25 | No verification performed | +| Reason | Score | Description | +| -------------------- | ----- | -------------------------- | +| `PRICE_CLAIM` | 25 | Unverified price statement | +| `LEGAL_CLAIM` | 30 | Unverified legal statement | +| `UNPROVEN_CLAIM` | 20 | Claim without evidence | +| `MISSING_EVIDENCE` | 15 | Expected artefacts missing | +| `IMPLEMENTATION_GAP` | 10 | Code incomplete | +| `NO_VERIFICATION` | 25 | No verification performed | --- @@ -492,6 +512,7 @@ function computeStopScore(reasons: StopReason[]): ScoreResult { **Regel:** Wenn `STOP_SCORE ≥ 70`, wird die Task **SOFORT BLOCKIERT**. **Ablauf:** + 1. Agent sendet Task-Result 2. System berechnet STOP-Score 3. Wenn Score ≥ 70: @@ -533,6 +554,7 @@ interface GateDecision { **Endpoint:** `POST /api/enforcement/approve` **Request:** + ```json { "taskId": "abc123", @@ -542,6 +564,7 @@ interface GateDecision { ``` **Response:** + ```json { "success": true, @@ -629,6 +652,7 @@ interface GateDecision { ### Input Validation **Zod Schemas:** + ```typescript const TaskSchema = z.object({ title: z.string().min(1).max(200), @@ -644,6 +668,7 @@ const TaskSchema = z.object({ **Status:** 🚧 Coming Soon (Agent 2 Task) **Planned:** + - JWT-based authentication - Role-based access control (RBAC) - Admin vs User permissions @@ -656,6 +681,7 @@ const TaskSchema = z.object({ **Status:** 🚧 Coming Soon (Agent 2 Task) **Planned:** + - Express Rate Limiter - IP-based limiting - Endpoint-specific limits @@ -666,6 +692,7 @@ const TaskSchema = z.object({ ### Audit Trail **All Actions Logged:** + - Task creation - Task updates - STOP decisions @@ -710,6 +737,7 @@ const TaskSchema = z.object({ ### Infrastructure **Server:** + - **IP:** 178.156.178.70 - **OS:** Ubuntu 24.04 - **CPU:** 8 cores (Dedicated) @@ -717,6 +745,7 @@ const TaskSchema = z.object({ - **Disk:** 226 GB **Stack:** + - **Process Manager:** PM2 - **Reverse Proxy:** Nginx - **Database:** SQLite @@ -745,12 +774,14 @@ Details: **[Deployment Guide](./DEPLOYMENT.md)** ### Metrics **Target Metrics:** + - API Response Time: < 100ms (p95) - Database Query Time: < 10ms (p95) - Frontend Load Time: < 2s - Task Processing Time: < 5s **Monitoring:** + - PM2 Monit - Nginx Access Logs - SQLite Query Logs diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 60f81b0..e9015f9 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -92,10 +92,12 @@ git merge upstream/main ### Branch Strategy **Main Branches:** + - `main`: Production-ready Code - `develop`: Development Branch (optional) **Feature Branches:** + ```bash # Agent-spezifische Branches agent-a2- @@ -109,6 +111,7 @@ docs/ ``` **Beispiele:** + ```bash git checkout -b feature/websocket-realtime git checkout -b fix/database-lock @@ -152,6 +155,7 @@ git push origin feature/my-feature ### TypeScript **Strict Mode:** + ```json { "compilerOptions": { @@ -164,6 +168,7 @@ git push origin feature/my-feature ``` **Keine `any` Types:** + ```typescript // ❌ Schlecht function processData(data: any) { @@ -180,29 +185,31 @@ function processData(data: { value: string }) { ### Naming Conventions -| Element | Convention | Beispiel | -|---------|------------|----------| -| Variables | camelCase | `userName`, `isActive` | -| Functions | camelCase | `getUserData()`, `calculateScore()` | -| Classes | PascalCase | `TaskManager`, `EnforcementGate` | -| Components | PascalCase | `AgentCard`, `TaskList` | -| Constants | SCREAMING_SNAKE_CASE | `MAX_RETRIES`, `API_URL` | -| Files | kebab-case | `task-manager.ts`, `user-api.ts` | -| Interfaces | PascalCase | `Task`, `AuditEntry` | -| Types | PascalCase | `GateStatus`, `StopReason` | +| Element | Convention | Beispiel | +| ---------- | -------------------- | ----------------------------------- | +| Variables | camelCase | `userName`, `isActive` | +| Functions | camelCase | `getUserData()`, `calculateScore()` | +| Classes | PascalCase | `TaskManager`, `EnforcementGate` | +| Components | PascalCase | `AgentCard`, `TaskList` | +| Constants | SCREAMING_SNAKE_CASE | `MAX_RETRIES`, `API_URL` | +| Files | kebab-case | `task-manager.ts`, `user-api.ts` | +| Interfaces | PascalCase | `Task`, `AuditEntry` | +| Types | PascalCase | `GateStatus`, `StopReason` | --- ### JSDoc Comments **Pflicht für:** + - Alle exported Functions - Alle exported Classes - Alle public Methods - Komplexe Logic **Format:** -```typescript + +````typescript /** * Creates a new task and assigns it to an agent * @@ -221,17 +228,18 @@ function processData(data: { value: string }) { */ async function createTask( taskData: Omit, - agentId: string + agentId: string, ): Promise { // Implementation } -``` +```` --- ### Code Style **Früh returnen:** + ```typescript // ✅ Gut - Early Return function validateTask(task: Task): boolean { @@ -256,6 +264,7 @@ function validateTask(task: Task): boolean { ``` **Destructuring:** + ```typescript // ✅ Gut const { title, priority, assignee } = task; @@ -267,6 +276,7 @@ const assignee = task.assignee; ``` **Async/Await:** + ```typescript // ✅ Gut async function fetchData() { @@ -283,9 +293,9 @@ async function fetchData() { // ❌ Schlecht - Promises ohne async/await function fetchData() { return fetch("/api/tasks") - .then(response => response.json()) - .then(data => data) - .catch(error => { + .then((response) => response.json()) + .then((data) => data) + .catch((error) => { console.error("Fetch failed:", error); throw error; }); @@ -297,6 +307,7 @@ function fetchData() { ### File Organization **Import Order:** + ```typescript // 1. External Libraries import express from "express"; @@ -318,6 +329,7 @@ const PORT = 3000; ### Error Handling **Always use try/catch:** + ```typescript // ✅ Gut async function createTask(taskData: TaskData): Promise { @@ -338,6 +350,7 @@ async function createTask(taskData: TaskData): Promise { ``` **Custom Errors:** + ```typescript class ValidationError extends Error { constructor(message: string) { @@ -360,6 +373,7 @@ function validateInput(data: unknown) { ### Test Requirements **Alle PRs müssen:** + - [ ] Bestehende Tests bestehen - [ ] Neue Tests für neue Features - [ ] Code Coverage ≥ 80% @@ -369,6 +383,7 @@ function validateInput(data: unknown) { ### Writing Tests **Test Structure:** + ```typescript import { describe, it, beforeEach, afterEach } from "node:test"; import assert from "node:assert"; @@ -400,14 +415,14 @@ describe("TaskManager", () => { it("should throw error with invalid data", async () => { const taskData = { - title: "", // Invalid: empty title + title: "", // Invalid: empty title priority: "high" as const, assignee: "agent-1", }; await assert.rejects( async () => await createTask(db, taskData), - /Title is required/ + /Title is required/, ); }); }); @@ -446,6 +461,7 @@ npm test -- --coverage ``` **Types:** + - `feat`: New feature - `fix`: Bug fix - `docs`: Documentation @@ -455,6 +471,7 @@ npm test -- --coverage - `chore`: Maintenance **Scopes:** + - `api`: API endpoints - `db`: Database - `ui`: Frontend @@ -506,6 +523,7 @@ Co-Authored-By: Claude Sonnet 4.5 " ### PR Guidelines **Vor dem PR:** + - [ ] Code follows coding standards - [ ] All tests pass - [ ] Build successful @@ -519,28 +537,34 @@ Co-Authored-By: Claude Sonnet 4.5 " ```markdown ## Description + Brief description of changes ## Type of Change + - [ ] Bug fix - [ ] New feature - [ ] Breaking change - [ ] Documentation update ## Changes Made + - Change 1 - Change 2 - Change 3 ## Testing + - [ ] Tests added/updated - [ ] All tests passing - [ ] Manual testing completed ## Screenshots (if applicable) + [Add screenshots here] ## Related Issues + Closes #123 ``` @@ -553,6 +577,7 @@ Closes #123 ``` **Examples:** + ``` feat(api): add WebSocket real-time updates fix(db): resolve database locking issue @@ -566,6 +591,7 @@ docs(readme): update installation instructions ### For Contributors **Nach PR-Erstellung:** + 1. Wait for CI/CD checks to pass 2. Wait for review comments 3. Address feedback promptly @@ -573,6 +599,7 @@ docs(readme): update installation instructions 5. Be patient and respectful **Wenn Änderungen gefordert:** + ```bash # Make changes git add . @@ -585,6 +612,7 @@ git push origin feature/my-feature ### For Reviewers **Review Checkliste:** + - [ ] Code follows coding standards - [ ] Tests are adequate - [ ] No security issues @@ -593,6 +621,7 @@ git push origin feature/my-feature - [ ] Breaking changes noted **Review Comments:** + - Be constructive and respectful - Explain why changes are needed - Suggest alternatives @@ -603,6 +632,7 @@ git push origin feature/my-feature ## 🎯 PR Merge Criteria **Erforderlich:** + - [ ] Mindestens 1 Approval - [ ] Alle CI Checks grün - [ ] Keine merge conflicts @@ -610,6 +640,7 @@ git push origin feature/my-feature - [ ] Commit-Messages sauber **Merge Strategie:** + - **Squash Merge**: Default für Feature Branches - **Merge Commit**: Für größere Features - **Rebase**: Für kleine Fixes @@ -627,6 +658,7 @@ git push origin feature/my-feature - **PATCH**: Bug fixes **Examples:** + - `0.1.0` → `0.2.0`: New feature added - `0.2.0` → `0.2.1`: Bug fix - `0.2.1` → `1.0.0`: Breaking change @@ -648,6 +680,7 @@ git push origin feature/my-feature ## 📞 Questions? **Hilfe gesucht?** + - 📖 Lese die [Developer Guide](./DEVELOPER_GUIDE.md) - 🏗️ Verstehe die [Architecture](./ARCHITECTURE.md) - 💬 Frage im GitHub Discussions @@ -660,6 +693,7 @@ git push origin feature/my-feature Danke für deinen Beitrag zu Code Cloud Agents! Jede Contribution, egal wie klein, wird geschätzt: + - Code - Dokumentation - Bug Reports diff --git a/docs/CRM_WORKFLOW.md b/docs/CRM_WORKFLOW.md index 4158915..8a2de38 100644 --- a/docs/CRM_WORKFLOW.md +++ b/docs/CRM_WORKFLOW.md @@ -6,6 +6,7 @@ **Repo:** https://github.com/dsactivi-2/old_crm_updated ### Features + - Customer Management (CRUD) - Interaction Logging (Calls, Emails, Meetings, Notes) - Voice AI Sales Agents (Vapi, Retell, Bland) @@ -27,13 +28,14 @@ ``` ### Manuelle Schritte -| Schritt | Wer | Dauer | Häufigkeit | -|---------|-----|-------|------------| -| Lead eintragen | Mitarbeiter | 5 min | 50x/Tag | -| Lead qualifizieren | Sales | 15 min | 50x/Tag | -| Anruf tätigen | Sales/Voice AI | 10 min | 30x/Tag | -| Follow-up planen | Sales | 5 min | 30x/Tag | -| Report erstellen | Manager | 30 min | 1x/Tag | + +| Schritt | Wer | Dauer | Häufigkeit | +| ------------------ | -------------- | ------ | ---------- | +| Lead eintragen | Mitarbeiter | 5 min | 50x/Tag | +| Lead qualifizieren | Sales | 15 min | 50x/Tag | +| Anruf tätigen | Sales/Voice AI | 10 min | 30x/Tag | +| Follow-up planen | Sales | 5 min | 30x/Tag | +| Report erstellen | Manager | 30 min | 1x/Tag | **Gesamt:** ~20 Stunden/Tag manuelle Arbeit @@ -53,21 +55,22 @@ ### Agent-Zuweisungen -| Task | Agent | Trigger | Automatisierung | -|------|-------|---------|-----------------| -| Lead Import | `/import-lead` | Webhook | 100% | -| Lead Qualifizierung | `/qualify-lead` | Nach Import | 90% | -| Erster Anruf | Voice AI Agent | Lead Score > 50 | 100% | -| Follow-up E-Mail | `/send-email` | Nach Anruf | 100% | -| Report | `/generate-report` | Täglich 18:00 | 100% | -| Code Review | `/review` | Bei PR | 100% | -| Security Check | `/security` | Vor Deploy | 100% | +| Task | Agent | Trigger | Automatisierung | +| ------------------- | ------------------ | --------------- | --------------- | +| Lead Import | `/import-lead` | Webhook | 100% | +| Lead Qualifizierung | `/qualify-lead` | Nach Import | 90% | +| Erster Anruf | Voice AI Agent | Lead Score > 50 | 100% | +| Follow-up E-Mail | `/send-email` | Nach Anruf | 100% | +| Report | `/generate-report` | Täglich 18:00 | 100% | +| Code Review | `/review` | Bei PR | 100% | +| Security Check | `/security` | Vor Deploy | 100% | --- ## 🔗 Agent-Kette (Befehlskette) ### Lead Processing Chain + ``` [Webhook: Neuer Lead] ↓ @@ -98,6 +101,7 @@ ``` ### Development Chain + ``` [PR Created] ↓ @@ -117,8 +121,10 @@ ## 📋 Neue Commands für CRM ### 1. `/import-lead` + ```markdown Importiere Lead aus: $ARGUMENTS (JSON/CSV/Form) + - Validiere Daten - Prüfe Duplikate - Berechne initialen Score @@ -126,8 +132,10 @@ Importiere Lead aus: $ARGUMENTS (JSON/CSV/Form) ``` ### 2. `/qualify-lead` + ```markdown Qualifiziere Lead: $ARGUMENTS (Customer ID) + - Analysiere vorhandene Daten - Berechne Lead Score (0-100) - Setze Priorität (Hot/Warm/Cold) @@ -135,8 +143,10 @@ Qualifiziere Lead: $ARGUMENTS (Customer ID) ``` ### 3. `/send-email` + ```markdown Sende E-Mail an: $ARGUMENTS (Customer ID + Template) + - Lade Customer Daten - Personalisiere Template - Sende via SMTP/SendGrid @@ -144,8 +154,10 @@ Sende E-Mail an: $ARGUMENTS (Customer ID + Template) ``` ### 4. `/generate-report` + ```markdown Erstelle Report: $ARGUMENTS (daily/weekly/monthly) + - Aggregiere Stats - Berechne KPIs - Erstelle Charts @@ -153,8 +165,10 @@ Erstelle Report: $ARGUMENTS (daily/weekly/monthly) ``` ### 5. `/voice-call` + ```markdown Starte Voice AI Call: $ARGUMENTS (Customer ID) + - Lade Customer Profil - Wähle passenden Voice Agent - Starte Anruf via Vapi/Retell/Bland @@ -165,13 +179,13 @@ Starte Voice AI Call: $ARGUMENTS (Customer ID) ## 📈 ROI Schätzung -| Metrik | Vorher (Manuell) | Nachher (Automatisiert) | Ersparnis | -|--------|------------------|-------------------------|-----------| -| Lead-Eingabe | 5 min/Lead | 0 min | 100% | -| Qualifizierung | 15 min/Lead | 1 min | 93% | -| Anrufe/Tag | 30 | 100+ | 233%+ | -| Follow-ups | Oft vergessen | 100% | ∞ | -| Reports | 30 min/Tag | 0 min | 100% | +| Metrik | Vorher (Manuell) | Nachher (Automatisiert) | Ersparnis | +| -------------- | ---------------- | ----------------------- | --------- | +| Lead-Eingabe | 5 min/Lead | 0 min | 100% | +| Qualifizierung | 15 min/Lead | 1 min | 93% | +| Anrufe/Tag | 30 | 100+ | 233%+ | +| Follow-ups | Oft vergessen | 100% | ∞ | +| Reports | 30 min/Tag | 0 min | 100% | **Zeitersparnis:** ~18 Stunden/Tag **Mehr Leads bearbeitet:** 3x mehr @@ -182,22 +196,26 @@ Starte Voice AI Call: $ARGUMENTS (Customer ID) ## 🚀 Implementierungs-Plan ### Phase 1: Basis (Woche 1) + - [ ] CRM deployen - [ ] Webhook für Lead Import einrichten - [ ] `/import-lead` Command implementieren - [ ] `/qualify-lead` Command implementieren ### Phase 2: Voice AI (Woche 2) + - [ ] Voice AI Provider konfigurieren (Vapi/Retell) - [ ] `/voice-call` Command implementieren - [ ] Auto-Call bei hohem Lead Score ### Phase 3: Automatisierung (Woche 3) + - [ ] E-Mail Templates erstellen - [ ] `/send-email` Command implementieren - [ ] Follow-up Automation ### Phase 4: Reporting (Woche 4) + - [ ] Dashboard KPIs definieren - [ ] `/generate-report` Command implementieren - [ ] Scheduled Reports diff --git a/docs/DESIGN_SPECIFICATION.md b/docs/DESIGN_SPECIFICATION.md index a76e412..a3abde1 100644 --- a/docs/DESIGN_SPECIFICATION.md +++ b/docs/DESIGN_SPECIFICATION.md @@ -7,18 +7,20 @@ ## 1. PROJEKT-ÜBERBLICK ### 1.1 Produktvision + **Step2Job Cloud Agents** ist ein Supervisor-Dashboard zur Überwachung und Qualitätssicherung von KI-Agenten. Das System analysiert Gespräche in Echtzeit und warnt Supervisoren bei kritischen Vorfällen. ### 1.2 Zielgruppen -| Persona | Rolle | Bedürfnisse | -|---------|-------|-------------| +| Persona | Rolle | Bedürfnisse | +| -------------------- | ---------------------------- | ----------------------------------------------------- | | **Supervisor Sarah** | Team-Leiterin (5-10 Agenten) | Schneller Überblick, sofortige Alerts, Batch-Aktionen | -| **Manager Michael** | Abteilungsleiter | Reports, Trends, KPIs, Export-Funktionen | -| **Admin Anna** | System-Administratorin | Konfiguration, Benutzerverwaltung, Logs | -| **Agent Alex** | KI-Agent-Operator | Eigene Performance sehen, Feedback verstehen | +| **Manager Michael** | Abteilungsleiter | Reports, Trends, KPIs, Export-Funktionen | +| **Admin Anna** | System-Administratorin | Konfiguration, Benutzerverwaltung, Logs | +| **Agent Alex** | KI-Agent-Operator | Eigene Performance sehen, Feedback verstehen | ### 1.3 Kernfunktionen + 1. **Live-Dashboard** - Echtzeit-Überwachung aller Agenten 2. **Alert-Center** - Kritische Vorfälle sofort erkennen 3. **Agent-Details** - Einzelne Agenten analysieren @@ -32,56 +34,59 @@ ### 2.1 Farbpalette #### Primärfarben + ```css :root { /* Brand Colors */ - --primary-50: #EEF2FF; - --primary-100: #E0E7FF; - --primary-200: #C7D2FE; - --primary-300: #A5B4FC; - --primary-400: #818CF8; - --primary-500: #6366F1; /* Hauptfarbe */ - --primary-600: #4F46E5; - --primary-700: #4338CA; - --primary-800: #3730A3; - --primary-900: #312E81; + --primary-50: #eef2ff; + --primary-100: #e0e7ff; + --primary-200: #c7d2fe; + --primary-300: #a5b4fc; + --primary-400: #818cf8; + --primary-500: #6366f1; /* Hauptfarbe */ + --primary-600: #4f46e5; + --primary-700: #4338ca; + --primary-800: #3730a3; + --primary-900: #312e81; } ``` #### Risk-Level Farben (KRITISCH - konsistent verwenden!) + ```css :root { /* Risk Colors - Semantisch */ - --risk-low: #10B981; /* Grün - Erfolg */ - --risk-low-bg: #D1FAE5; - --risk-low-border: #6EE7B7; + --risk-low: #10b981; /* Grün - Erfolg */ + --risk-low-bg: #d1fae5; + --risk-low-border: #6ee7b7; - --risk-medium: #F59E0B; /* Gelb/Orange - Warnung */ - --risk-medium-bg: #FEF3C7; - --risk-medium-border: #FCD34D; + --risk-medium: #f59e0b; /* Gelb/Orange - Warnung */ + --risk-medium-bg: #fef3c7; + --risk-medium-border: #fcd34d; - --risk-high: #F97316; /* Orange - Gefahr */ - --risk-high-bg: #FFEDD5; - --risk-high-border: #FDBA74; + --risk-high: #f97316; /* Orange - Gefahr */ + --risk-high-bg: #ffedd5; + --risk-high-border: #fdba74; - --risk-critical: #EF4444; /* Rot - Kritisch */ - --risk-critical-bg: #FEE2E2; - --risk-critical-border: #FCA5A5; + --risk-critical: #ef4444; /* Rot - Kritisch */ + --risk-critical-bg: #fee2e2; + --risk-critical-border: #fca5a5; } ``` #### Neutrale Farben + ```css :root { - --gray-50: #F9FAFB; - --gray-100: #F3F4F6; - --gray-200: #E5E7EB; - --gray-300: #D1D5DB; - --gray-400: #9CA3AF; - --gray-500: #6B7280; - --gray-600: #4B5563; + --gray-50: #f9fafb; + --gray-100: #f3f4f6; + --gray-200: #e5e7eb; + --gray-300: #d1d5db; + --gray-400: #9ca3af; + --gray-500: #6b7280; + --gray-600: #4b5563; --gray-700: #374151; - --gray-800: #1F2937; + --gray-800: #1f2937; --gray-900: #111827; } ``` @@ -91,18 +96,19 @@ ```css :root { /* Font Familie */ - --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - --font-mono: 'JetBrains Mono', 'Fira Code', monospace; + --font-sans: + "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-mono: "JetBrains Mono", "Fira Code", monospace; /* Font Sizes */ - --text-xs: 0.75rem; /* 12px */ - --text-sm: 0.875rem; /* 14px */ - --text-base: 1rem; /* 16px */ - --text-lg: 1.125rem; /* 18px */ - --text-xl: 1.25rem; /* 20px */ - --text-2xl: 1.5rem; /* 24px */ - --text-3xl: 1.875rem; /* 30px */ - --text-4xl: 2.25rem; /* 36px */ + --text-xs: 0.75rem; /* 12px */ + --text-sm: 0.875rem; /* 14px */ + --text-base: 1rem; /* 16px */ + --text-lg: 1.125rem; /* 18px */ + --text-xl: 1.25rem; /* 20px */ + --text-2xl: 1.5rem; /* 24px */ + --text-3xl: 1.875rem; /* 30px */ + --text-4xl: 2.25rem; /* 36px */ /* Font Weights */ --font-normal: 400; @@ -119,38 +125,38 @@ #### Typografie-Hierarchie -| Element | Größe | Gewicht | Verwendung | -|---------|-------|---------|------------| -| H1 | 36px | Bold | Seitentitel | -| H2 | 24px | Semibold | Sektionsüberschriften | -| H3 | 20px | Semibold | Card-Titel | -| H4 | 18px | Medium | Subsektionen | -| Body | 16px | Normal | Fließtext | -| Small | 14px | Normal | Sekundärtext | -| Caption | 12px | Normal | Labels, Timestamps | +| Element | Größe | Gewicht | Verwendung | +| ------- | ----- | -------- | --------------------- | +| H1 | 36px | Bold | Seitentitel | +| H2 | 24px | Semibold | Sektionsüberschriften | +| H3 | 20px | Semibold | Card-Titel | +| H4 | 18px | Medium | Subsektionen | +| Body | 16px | Normal | Fließtext | +| Small | 14px | Normal | Sekundärtext | +| Caption | 12px | Normal | Labels, Timestamps | ### 2.3 Abstände & Grid ```css :root { /* Spacing Scale (8px Basis) */ - --space-1: 0.25rem; /* 4px */ - --space-2: 0.5rem; /* 8px */ - --space-3: 0.75rem; /* 12px */ - --space-4: 1rem; /* 16px */ - --space-5: 1.25rem; /* 20px */ - --space-6: 1.5rem; /* 24px */ - --space-8: 2rem; /* 32px */ - --space-10: 2.5rem; /* 40px */ - --space-12: 3rem; /* 48px */ - --space-16: 4rem; /* 64px */ + --space-1: 0.25rem; /* 4px */ + --space-2: 0.5rem; /* 8px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-8: 2rem; /* 32px */ + --space-10: 2.5rem; /* 40px */ + --space-12: 3rem; /* 48px */ + --space-16: 4rem; /* 64px */ /* Border Radius */ - --radius-sm: 0.25rem; /* 4px */ - --radius-md: 0.375rem; /* 6px */ - --radius-lg: 0.5rem; /* 8px */ - --radius-xl: 0.75rem; /* 12px */ - --radius-2xl: 1rem; /* 16px */ + --radius-sm: 0.25rem; /* 4px */ + --radius-md: 0.375rem; /* 6px */ + --radius-lg: 0.5rem; /* 8px */ + --radius-xl: 0.75rem; /* 12px */ + --radius-2xl: 1rem; /* 16px */ --radius-full: 9999px; /* Shadows */ @@ -197,6 +203,7 @@ Sidebar-Breite: 256px (collapsed: 64px) ``` **Spezifikation:** + - Padding: 4px 12px - Border-Radius: 9999px (Pill) - Font: 12px, Semibold, Uppercase @@ -225,6 +232,7 @@ Sidebar-Breite: 256px (collapsed: 64px) ``` **Spezifikation:** + - Breite: 100% (Grid-responsive) - Min-Breite: 320px - Padding: 24px @@ -257,6 +265,7 @@ Sidebar-Breite: 256px (collapsed: 64px) ``` **Spezifikation:** + - Border-Left: 4px solid (Risk-Farbe) - Background: Risk-Farbe-bg (10% Opacity) - Padding: 16px 20px @@ -276,6 +285,7 @@ Sidebar-Breite: 256px (collapsed: 64px) ``` **Spezifikation:** + - Min-Breite: 160px - Padding: 20px - Trend-Indikator: Grün (positiv), Rot (negativ) @@ -292,6 +302,7 @@ Success: [████████████████] Gefüllt, Green- ``` **Größen:** + - Small: 32px Höhe, 12px Font, 12px Padding - Medium: 40px Höhe, 14px Font, 16px Padding - Large: 48px Höhe, 16px Font, 20px Padding @@ -327,6 +338,7 @@ States: ``` **Spezifikation:** + - Header: gray-50 bg, font-semibold - Rows: Alternating gray-50/white (optional) - Hover: primary-50 bg @@ -652,10 +664,10 @@ Progress Bar (Batch-Operations): ```css /* Mobile First Approach */ ---breakpoint-sm: 640px; /* Small tablets */ ---breakpoint-md: 768px; /* Tablets */ ---breakpoint-lg: 1024px; /* Small laptops */ ---breakpoint-xl: 1280px; /* Desktops */ +--breakpoint-sm: 640px; /* Small tablets */ +--breakpoint-md: 768px; /* Tablets */ +--breakpoint-lg: 1024px; /* Small laptops */ +--breakpoint-xl: 1280px; /* Desktops */ --breakpoint-2xl: 1536px; /* Large screens */ ``` @@ -691,6 +703,7 @@ Progress Bar (Batch-Operations): ``` **Mobile-Anpassungen:** + - Bottom-Navigation statt Sidebar - Vollbreite Cards (stacked) - Swipe-Gesten für Tabs @@ -722,10 +735,12 @@ Progress Bar (Batch-Operations): ## 7. ACCESSIBILITY (A11Y) ### 7.1 Farb-Kontrast + - Alle Text/Hintergrund-Kombinationen: min. 4.5:1 (WCAG AA) - Risk-Level nicht nur durch Farbe, auch durch Icons/Text ### 7.2 Keyboard Navigation + ``` Tab-Reihenfolge: 1. Skip to main content Link @@ -744,9 +759,14 @@ Shortcuts: ``` ### 7.3 Screen Reader Support + ```html - + CRITICAL @@ -764,6 +784,7 @@ Shortcuts: ``` ### 7.4 Focus Indicators + ```css :focus-visible { outline: 2px solid var(--primary-500); @@ -806,8 +827,9 @@ Shortcuts: /* Cards, Panels */ .card { - transition: box-shadow var(--duration-normal) var(--ease-out), - transform var(--duration-normal) var(--ease-out); + transition: + box-shadow var(--duration-normal) var(--ease-out), + transform var(--duration-normal) var(--ease-out); } /* Modals, Dropdowns */ @@ -821,8 +843,13 @@ Shortcuts: ```css /* Critical Alert Pulse */ @keyframes pulse-critical { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } } .alert-critical { @@ -847,8 +874,12 @@ Shortcuts: /* Skeleton Loading Shimmer */ @keyframes shimmer { - 0% { background-position: -200% 0; } - 100% { background-position: 200% 0; } + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } } .skeleton { @@ -866,13 +897,13 @@ Shortcuts: ```css [data-theme="dark"] { - --bg-primary: #0F172A; - --bg-secondary: #1E293B; + --bg-primary: #0f172a; + --bg-secondary: #1e293b; --bg-tertiary: #334155; - --text-primary: #F8FAFC; - --text-secondary: #CBD5E1; - --text-muted: #64748B; + --text-primary: #f8fafc; + --text-secondary: #cbd5e1; + --text-muted: #64748b; --border-color: #334155; @@ -900,32 +931,33 @@ Shortcuts: ### 10.1 Empfohlene Icons (Lucide/Heroicons) -| Funktion | Icon | Code | -|----------|------|------| -| Dashboard | LayoutDashboard | `` | -| Agents | Bot | `` | -| Alerts | Bell | `` | -| Reports | BarChart3 | `` | -| Settings | Settings | `` | -| Risk LOW | CheckCircle | `` | -| Risk MEDIUM | AlertTriangle | `` | -| Risk HIGH | AlertOctagon | `` | -| Risk CRITICAL | XCircle | `` | -| Refresh | RefreshCw | `` | -| Export | Download | `` | -| Filter | Filter | `` | -| Search | Search | `` | -| User | User | `` | -| Menu | Menu | `` | -| Close | X | `` | -| Expand | ChevronDown | `` | -| External | ExternalLink | `` | +| Funktion | Icon | Code | +| ------------- | --------------- | --------------------- | +| Dashboard | LayoutDashboard | `` | +| Agents | Bot | `` | +| Alerts | Bell | `` | +| Reports | BarChart3 | `` | +| Settings | Settings | `` | +| Risk LOW | CheckCircle | `` | +| Risk MEDIUM | AlertTriangle | `` | +| Risk HIGH | AlertOctagon | `` | +| Risk CRITICAL | XCircle | `` | +| Refresh | RefreshCw | `` | +| Export | Download | `` | +| Filter | Filter | `` | +| Search | Search | `` | +| User | User | `` | +| Menu | Menu | `` | +| Close | X | `` | +| Expand | ChevronDown | `` | +| External | ExternalLink | `` | --- ## 11. IMPLEMENTIERUNGS-CHECKLISTE ### Phase 1: Foundation + - [ ] Design System in Figma/Sketch erstellen - [ ] Farbpalette definieren und testen - [ ] Typografie-Scale implementieren @@ -933,6 +965,7 @@ Shortcuts: - [ ] Icon-Set auswählen und integrieren ### Phase 2: Komponenten + - [ ] Button-Varianten - [ ] Input-Felder - [ ] Risk-Level Badges @@ -946,6 +979,7 @@ Shortcuts: - [ ] Empty States ### Phase 3: Layouts + - [ ] Haupt-Layout mit Sidebar - [ ] Dashboard-Seite - [ ] Agent-Liste @@ -955,6 +989,7 @@ Shortcuts: - [ ] Settings-Seite ### Phase 4: Interaktionen + - [ ] Hover/Focus States - [ ] Transitions/Animations - [ ] Keyboard Navigation @@ -962,6 +997,7 @@ Shortcuts: - [ ] Real-time Updates (WebSocket) ### Phase 5: Polish + - [ ] Dark Mode - [ ] Responsive Testing - [ ] Accessibility Audit @@ -993,6 +1029,7 @@ Shortcuts: ### 12.2 Handoff-Format Für Developer-Handoff empfohlen: + - **Figma** mit Dev Mode aktiviert - **Design Tokens** als JSON für CSS-Variables - **Storybook** für Komponenten-Dokumentation diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md index 28f6c3b..5b5f5be 100644 --- a/docs/DEVELOPER_GUIDE.md +++ b/docs/DEVELOPER_GUIDE.md @@ -23,15 +23,18 @@ Umfassender Leitfaden für Entwickler, die an Code Cloud Agents arbeiten. ### 1. System-Voraussetzungen **Erforderlich:** + - Node.js ≥20.0.0 - npm ≥10.0.0 - Git **Optional:** + - Redis (für Production Queue) - PM2 (für Production Deployment) **Installation prüfen:** + ```bash node --version # v20.19.6 oder höher npm --version # 10.8.2 oder höher @@ -65,6 +68,7 @@ npm install --production ``` **Wichtige Dependencies:** + - `express`: Backend Web Framework - `better-sqlite3`: SQLite Database - `tsx`: TypeScript Runtime @@ -81,6 +85,7 @@ cp .env.example .env ``` **Minimale .env Konfiguration:** + ```bash # Server PORT=3000 @@ -98,6 +103,7 @@ MAX_PARALLEL_AGENTS=4 ``` **Production .env:** + ```bash PORT=3000 NODE_ENV=production @@ -123,6 +129,7 @@ Die SQLite-Datenbank wird automatisch beim ersten Start erstellt. ### 6. Projekt starten **Development Mode:** + ```bash # Backend (Terminal 1) npm run backend:dev @@ -132,6 +139,7 @@ npm run dev ``` **Production Mode:** + ```bash # Build npm run build @@ -240,6 +248,7 @@ npm run queue:status # Queue status ### Coding Standards **TypeScript Strict Mode:** + ```json { "compilerOptions": { @@ -251,6 +260,7 @@ npm run queue:status # Queue status ``` **Naming Conventions:** + ```typescript // Variables & Functions: camelCase const userName = "John"; @@ -266,6 +276,7 @@ const API_BASE_URL = "http://localhost:3000"; ``` **JSDoc Comments:** + ```typescript /** * Creates a new task and assigns it to an agent @@ -280,6 +291,7 @@ async function createTask(taskData: TaskData, agentId: string): Promise { ``` **Error Handling:** + ```typescript // Always use try/catch for async operations try { @@ -300,6 +312,7 @@ try { **Test Framework:** Node.js native test runner (Node v20+) **Test Structure:** + ``` tests/ ├── api/ @@ -335,6 +348,7 @@ npm test -- --coverage ### Writing Tests **Example Test:** + ```typescript import { describe, it } from "node:test"; import assert from "node:assert"; @@ -385,6 +399,7 @@ it("should create task with valid data", async () => { ### Backend Debugging **Console Logs:** + ```typescript console.log("✅ Success:", data); console.error("❌ Error:", error); @@ -393,6 +408,7 @@ console.info("ℹ️ Info:", info); ``` **VS Code Debugger:** + ```json // .vscode/launch.json { @@ -411,6 +427,7 @@ console.info("ℹ️ Info:", info); ``` **Node.js Inspector:** + ```bash node --inspect --import tsx/esm src/index.ts # Open chrome://inspect in Chrome @@ -421,11 +438,13 @@ node --inspect --import tsx/esm src/index.ts ### Frontend Debugging **React DevTools:** + 1. Install React DevTools Extension (Chrome/Firefox) 2. Open DevTools → React Tab 3. Inspect Component Tree, Props, State **Console Logs in Components:** + ```typescript function AgentCard({ agent }: { agent: Agent }) { console.log("AgentCard rendering:", agent); @@ -444,6 +463,7 @@ function AgentCard({ agent }: { agent: Agent }) { ### Database Debugging **SQLite CLI:** + ```bash # Connect to database sqlite3 data/app.sqlite @@ -463,6 +483,7 @@ SELECT * FROM audit_log LIMIT 10; ``` **Database Health Check:** + ```bash npm run db:health ``` @@ -474,6 +495,7 @@ npm run db:health ### Schema **Tables:** + 1. **tasks**: Task management 2. **audit_log**: Action audit trail 3. **enforcement_log**: STOP decisions @@ -481,6 +503,7 @@ npm run db:health 5. **demo_users**: Demo users **Migrations:** + ```bash npm run db:migrate ``` @@ -490,24 +513,30 @@ npm run db:migrate ### Database Operations **Insert:** + ```typescript -const stmt = db.prepare("INSERT INTO tasks (id, title, status) VALUES (?, ?, ?)"); +const stmt = db.prepare( + "INSERT INTO tasks (id, title, status) VALUES (?, ?, ?)", +); stmt.run(id, title, status); ``` **Select:** + ```typescript const stmt = db.prepare("SELECT * FROM tasks WHERE status = ?"); const tasks = stmt.all("pending"); ``` **Update:** + ```typescript const stmt = db.prepare("UPDATE tasks SET status = ? WHERE id = ?"); stmt.run("completed", taskId); ``` **Delete:** + ```typescript const stmt = db.prepare("DELETE FROM tasks WHERE id = ?"); stmt.run(taskId); @@ -520,6 +549,7 @@ stmt.run(taskId); ### Creating New Endpoints **1. Create Router File:** + ```typescript // src/api/myFeature.ts import { Router } from "express"; @@ -536,6 +566,7 @@ export function createMyFeatureRouter(): Router { ``` **2. Mount in index.ts:** + ```typescript // src/index.ts import { createMyFeatureRouter } from "./api/myFeature.js"; @@ -544,6 +575,7 @@ app.use("/api/myFeature", createMyFeatureRouter()); ``` **3. Test the endpoint:** + ```bash curl http://localhost:3000/api/myFeature ``` @@ -597,6 +629,7 @@ router.post("/tasks", async (req, res) => { ### Component Development **Component Structure:** + ```typescript // src/components/MyComponent.tsx import { useState } from "react"; @@ -629,21 +662,21 @@ export function MyComponent({ title, onAction }: MyComponentProps) { ### Styling **Tailwind CSS:** + ```tsx
-

- Title -

+

Title

``` **Radix UI Components:** + ```tsx import { Button } from "@/components/ui/button"; +; ``` --- @@ -669,6 +702,7 @@ git checkout -b docs/api-documentation ### Commit Messages **Format:** + ``` (): @@ -679,6 +713,7 @@ Co-Authored-By: Claude Sonnet 4.5 ``` **Types:** + - `feat`: New feature - `fix`: Bug fix - `docs`: Documentation @@ -687,6 +722,7 @@ Co-Authored-By: Claude Sonnet 4.5 - `chore`: Maintenance **Examples:** + ```bash git commit -m "feat(auth): Add admin middleware @@ -716,11 +752,13 @@ Co-Authored-By: Claude Sonnet 4.5 " ### Port bereits in Verwendung **Problem:** + ``` Error: EADDRINUSE: Port 3000 already in use ``` **Lösung:** + ```bash # Prozess finden lsof -i :3000 @@ -737,11 +775,13 @@ killall node ### Database locked **Problem:** + ``` Error: database is locked ``` **Lösung:** + ```bash # Prozesse prüfen lsof data/app.sqlite @@ -759,11 +799,13 @@ npm run backend:dev # Auto-recreates ### TypeScript Build Errors **Problem:** + ``` error TS2307: Cannot find module ``` **Lösung:** + ```bash # node_modules neu installieren rm -rf node_modules package-lock.json @@ -779,12 +821,14 @@ npm run backend:build ### Tests failing **Problem:** + ``` npm test # Tests fail with timeout ``` **Lösung:** + ```bash # Server stoppen (Tests brauchen Port 3000) lsof -i :3000 diff --git a/docs/FRONTEND_IMPLEMENTATION.md b/docs/FRONTEND_IMPLEMENTATION.md index 935bd8d..bf99945 100644 --- a/docs/FRONTEND_IMPLEMENTATION.md +++ b/docs/FRONTEND_IMPLEMENTATION.md @@ -3,6 +3,7 @@ ## Empfohlener Tech-Stack ### Framework & Build + ```json { "framework": "Next.js 14+ (App Router)", @@ -105,12 +106,12 @@ npx shadcn-ui@latest add alert toast sonner skeleton ```typescript // lib/types.ts -export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'; +export type RiskLevel = "LOW" | "MEDIUM" | "HIGH" | "CRITICAL"; export interface Agent { id: string; name: string; - status: 'online' | 'offline' | 'paused' | 'review'; + status: "online" | "offline" | "paused" | "review"; supervisor?: string; createdAt: string; } @@ -201,26 +202,31 @@ export interface SupervisorDashboard { ```tsx // components/dashboard/risk-badge.tsx -import { cva, type VariantProps } from 'class-variance-authority'; -import { CheckCircle, AlertTriangle, AlertOctagon, XCircle } from 'lucide-react'; -import { cn } from '@/lib/utils'; -import type { RiskLevel } from '@/lib/types'; +import { cva, type VariantProps } from "class-variance-authority"; +import { + CheckCircle, + AlertTriangle, + AlertOctagon, + XCircle, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import type { RiskLevel } from "@/lib/types"; const badgeVariants = cva( - 'inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold uppercase', + "inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold uppercase", { variants: { risk: { - LOW: 'bg-green-100 text-green-800 border border-green-200', - MEDIUM: 'bg-yellow-100 text-yellow-800 border border-yellow-200', - HIGH: 'bg-orange-100 text-orange-800 border border-orange-200', - CRITICAL: 'bg-red-100 text-red-800 border border-red-200 animate-pulse', + LOW: "bg-green-100 text-green-800 border border-green-200", + MEDIUM: "bg-yellow-100 text-yellow-800 border border-yellow-200", + HIGH: "bg-orange-100 text-orange-800 border border-orange-200", + CRITICAL: "bg-red-100 text-red-800 border border-red-200 animate-pulse", }, }, defaultVariants: { - risk: 'LOW', + risk: "LOW", }, - } + }, ); const icons: Record> = { @@ -236,7 +242,11 @@ interface RiskBadgeProps extends VariantProps { className?: string; } -export function RiskBadge({ level, showIcon = true, className }: RiskBadgeProps) { +export function RiskBadge({ + level, + showIcon = true, + className, +}: RiskBadgeProps) { const Icon = icons[level]; return ( @@ -256,9 +266,9 @@ export function RiskBadge({ level, showIcon = true, className }: RiskBadgeProps) ```tsx // components/dashboard/stat-card.tsx -import { Card, CardContent } from '@/components/ui/card'; -import { TrendingUp, TrendingDown, Minus } from 'lucide-react'; -import { cn } from '@/lib/utils'; +import { Card, CardContent } from "@/components/ui/card"; +import { TrendingUp, TrendingDown, Minus } from "lucide-react"; +import { cn } from "@/lib/utils"; interface StatCardProps { title: string; @@ -284,12 +294,12 @@ export function StatCard({ }; const getTrendColor = () => { - if (!trend) return 'text-gray-500'; - return trend > 0 ? 'text-green-600' : 'text-red-600'; + if (!trend) return "text-gray-500"; + return trend > 0 ? "text-green-600" : "text-red-600"; }; return ( - +

{title}

@@ -298,7 +308,9 @@ export function StatCard({

{value}

{trend !== undefined && ( -
+
{getTrendIcon()} {Math.abs(trend)}%
@@ -317,12 +329,17 @@ export function StatCard({ ```tsx // components/dashboard/agent-card.tsx -import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Avatar, AvatarFallback } from '@/components/ui/avatar'; -import { RiskBadge } from './risk-badge'; -import { Bot, Pause, Eye } from 'lucide-react'; -import type { Agent, AgentStatistics } from '@/lib/types'; +import { + Card, + CardContent, + CardFooter, + CardHeader, +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Avatar, AvatarFallback } from "@/components/ui/avatar"; +import { RiskBadge } from "./risk-badge"; +import { Bot, Pause, Eye } from "lucide-react"; +import type { Agent, AgentStatistics } from "@/lib/types"; interface AgentCardProps { agent: Agent; @@ -331,23 +348,36 @@ interface AgentCardProps { onPause?: () => void; } -export function AgentCard({ agent, stats, onViewDetails, onPause }: AgentCardProps) { +export function AgentCard({ + agent, + stats, + onViewDetails, + onPause, +}: AgentCardProps) { const getStatusColor = () => { switch (agent.status) { - case 'online': return 'bg-green-500'; - case 'offline': return 'bg-gray-400'; - case 'paused': return 'bg-yellow-500'; - case 'review': return 'bg-red-500'; - default: return 'bg-gray-400'; + case "online": + return "bg-green-500"; + case "offline": + return "bg-gray-400"; + case "paused": + return "bg-yellow-500"; + case "review": + return "bg-red-500"; + default: + return "bg-gray-400"; } }; const riskLevel = stats?.average_risk - ? stats.average_risk <= 0 ? 'LOW' - : stats.average_risk <= 1 ? 'MEDIUM' - : stats.average_risk <= 2 ? 'HIGH' - : 'CRITICAL' - : 'LOW'; + ? stats.average_risk <= 0 + ? "LOW" + : stats.average_risk <= 1 + ? "MEDIUM" + : stats.average_risk <= 2 + ? "HIGH" + : "CRITICAL" + : "LOW"; return ( @@ -366,7 +396,9 @@ export function AgentCard({ agent, stats, onViewDetails, onPause }: AgentCardPro
- {agent.status} + + {agent.status} +
@@ -388,39 +420,53 @@ export function AgentCard({ agent, stats, onViewDetails, onPause }: AgentCardPro

Avg. Risk

-

{stats.stop_rate}

+

+ {stats.stop_rate} +

Stop Rate

-

Risk Distribution

+

+ Risk Distribution +

- {(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] as const).map((level) => { - const count = stats.risk_distribution[level] || 0; - const total = Object.values(stats.risk_distribution).reduce((a, b) => a + b, 0); - const percentage = total > 0 ? (count / total) * 100 : 0; - - return ( -
-
{level}
-
-
+ {(["LOW", "MEDIUM", "HIGH", "CRITICAL"] as const).map( + (level) => { + const count = stats.risk_distribution[level] || 0; + const total = Object.values(stats.risk_distribution).reduce( + (a, b) => a + b, + 0, + ); + const percentage = total > 0 ? (count / total) * 100 : 0; + + return ( +
+
+ {level} +
+
+
+
+
+ {Math.round(percentage)}% +
-
- {Math.round(percentage)}% -
-
- ); - })} + ); + }, + )}
@@ -436,10 +482,10 @@ export function AgentCard({ agent, stats, onViewDetails, onPause }: AgentCardPro variant="outline" size="sm" onClick={onPause} - disabled={agent.status === 'paused'} + disabled={agent.status === "paused"} > - {agent.status === 'paused' ? 'Paused' : 'Pause'} + {agent.status === "paused" ? "Paused" : "Pause"} @@ -451,14 +497,14 @@ export function AgentCard({ agent, stats, onViewDetails, onPause }: AgentCardPro ```tsx // components/dashboard/alert-item.tsx -import { Card, CardContent } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { RiskBadge } from './risk-badge'; -import { Eye, Pause, Check, Clock } from 'lucide-react'; -import { formatDistanceToNow } from 'date-fns'; -import { de } from 'date-fns/locale'; -import type { Alert } from '@/lib/types'; -import { cn } from '@/lib/utils'; +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { RiskBadge } from "./risk-badge"; +import { Eye, Pause, Check, Clock } from "lucide-react"; +import { formatDistanceToNow } from "date-fns"; +import { de } from "date-fns/locale"; +import type { Alert } from "@/lib/types"; +import { cn } from "@/lib/utils"; interface AlertItemProps { alert: Alert; @@ -467,28 +513,33 @@ interface AlertItemProps { onDismiss?: () => void; } -export function AlertItem({ alert, onView, onPauseAgent, onDismiss }: AlertItemProps) { +export function AlertItem({ + alert, + onView, + onPauseAgent, + onDismiss, +}: AlertItemProps) { const borderColor = { - LOW: 'border-l-green-500', - MEDIUM: 'border-l-yellow-500', - HIGH: 'border-l-orange-500', - CRITICAL: 'border-l-red-500', + LOW: "border-l-green-500", + MEDIUM: "border-l-yellow-500", + HIGH: "border-l-orange-500", + CRITICAL: "border-l-red-500", }[alert.risk_level]; const bgColor = { - LOW: 'bg-green-50/50', - MEDIUM: 'bg-yellow-50/50', - HIGH: 'bg-orange-50/50', - CRITICAL: 'bg-red-50/50', + LOW: "bg-green-50/50", + MEDIUM: "bg-yellow-50/50", + HIGH: "bg-orange-50/50", + CRITICAL: "bg-red-50/50", }[alert.risk_level]; return ( @@ -504,10 +555,18 @@ export function AlertItem({ alert, onView, onPauseAgent, onDismiss }: AlertItemP
-

+

{alert.agent_id} {alert.contact_name && ( - - {alert.contact_name} + + {" "} + - {alert.contact_name} + )}

@@ -515,7 +574,10 @@ export function AlertItem({ alert, onView, onPauseAgent, onDismiss }: AlertItemP {alert.violations.length > 0 && (
    {alert.violations.map((violation, i) => ( -
  • +
  • • {violation}
  • @@ -534,7 +596,7 @@ export function AlertItem({ alert, onView, onPauseAgent, onDismiss }: AlertItemP View Full - {alert.risk_level === 'CRITICAL' && ( + {alert.risk_level === "CRITICAL" && (
@@ -715,11 +780,11 @@ export function Sidebar() { key={item.name} href={item.href} className={cn( - 'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors', + "flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors", isActive - ? 'bg-primary-50 text-primary-600' - : 'text-gray-600 hover:bg-gray-100 hover:text-gray-900', - collapsed && 'justify-center px-2' + ? "bg-primary-50 text-primary-600" + : "text-gray-600 hover:bg-gray-100 hover:text-gray-900", + collapsed && "justify-center px-2", )} > @@ -737,19 +802,19 @@ export function Sidebar() { ```tsx // components/layout/header.tsx -'use client'; +"use client"; -import { Bell, User, Search } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; +import { Bell, User, Search } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { Avatar, AvatarFallback } from '@/components/ui/avatar'; -import { useAlertStore } from '@/stores/alert-store'; +} from "@/components/ui/dropdown-menu"; +import { Avatar, AvatarFallback } from "@/components/ui/avatar"; +import { useAlertStore } from "@/stores/alert-store"; export function Header() { const unreadCount = useAlertStore((state) => state.unreadCount); @@ -775,7 +840,7 @@ export function Header() { {unreadCount > 0 && ( - {unreadCount > 9 ? '9+' : unreadCount} + {unreadCount > 9 ? "9+" : unreadCount} )} @@ -812,8 +877,8 @@ export function Header() { ```typescript // stores/alert-store.ts -import { create } from 'zustand'; -import type { Alert } from '@/lib/types'; +import { create } from "zustand"; +import type { Alert } from "@/lib/types"; interface AlertState { alerts: Alert[]; @@ -836,9 +901,7 @@ export const useAlertStore = create((set) => ({ markAsRead: (id) => set((state) => ({ - alerts: state.alerts.map((a) => - a.id === id ? { ...a, read: true } : a - ), + alerts: state.alerts.map((a) => (a.id === id ? { ...a, read: true } : a)), unreadCount: Math.max(0, state.unreadCount - 1), })), @@ -862,21 +925,21 @@ export const useAlertStore = create((set) => ({ ```typescript // hooks/use-agents.ts -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { api } from '@/lib/api'; -import type { Agent, AgentStatistics } from '@/lib/types'; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { api } from "@/lib/api"; +import type { Agent, AgentStatistics } from "@/lib/types"; export function useAgents() { return useQuery({ - queryKey: ['agents'], - queryFn: () => api.get('/api/agents'), + queryKey: ["agents"], + queryFn: () => api.get("/api/agents"), refetchInterval: 30000, // Refresh every 30s }); } export function useAgentStats(agentId: string) { return useQuery({ - queryKey: ['agent-stats', agentId], + queryKey: ["agent-stats", agentId], queryFn: () => api.get(`/api/agents/${agentId}/stats`), enabled: !!agentId, }); @@ -886,10 +949,9 @@ export function usePauseAgent() { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (agentId: string) => - api.post(`/api/agents/${agentId}/pause`), + mutationFn: (agentId: string) => api.post(`/api/agents/${agentId}/pause`), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['agents'] }); + queryClient.invalidateQueries({ queryKey: ["agents"] }); }, }); } @@ -899,12 +961,12 @@ export function usePauseAgent() { ```typescript // hooks/use-websocket.ts -import { useEffect, useRef, useCallback } from 'react'; -import { useAlertStore } from '@/stores/alert-store'; -import type { Alert } from '@/lib/types'; +import { useEffect, useRef, useCallback } from "react"; +import { useAlertStore } from "@/stores/alert-store"; +import type { Alert } from "@/lib/types"; interface WebSocketMessage { - type: 'alert.new' | 'agent.status' | 'score.update'; + type: "alert.new" | "agent.status" | "score.update"; payload: any; } @@ -916,31 +978,31 @@ export function useWebSocket(url: string) { const ws = new WebSocket(url); ws.onopen = () => { - console.log('WebSocket connected'); + console.log("WebSocket connected"); }; ws.onmessage = (event) => { const message: WebSocketMessage = JSON.parse(event.data); switch (message.type) { - case 'alert.new': + case "alert.new": addAlert(message.payload as Alert); // Show toast notification - if (message.payload.risk_level === 'CRITICAL') { + if (message.payload.risk_level === "CRITICAL") { // Trigger toast } break; - case 'agent.status': + case "agent.status": // Update agent status in query cache break; - case 'score.update': + case "score.update": // Update dashboard stats break; } }; ws.onclose = () => { - console.log('WebSocket disconnected, reconnecting...'); + console.log("WebSocket disconnected, reconnecting..."); setTimeout(connect, 3000); }; @@ -964,7 +1026,7 @@ export function useWebSocket(url: string) { ```typescript // lib/api.ts -const BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'; +const BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; class ApiClient { private baseUrl: string; @@ -975,14 +1037,14 @@ class ApiClient { private async request( endpoint: string, - options: RequestInit = {} + options: RequestInit = {}, ): Promise { const url = `${this.baseUrl}${endpoint}`; const response = await fetch(url, { ...options, headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", ...options.headers, }, }); @@ -1000,20 +1062,20 @@ class ApiClient { post(endpoint: string, data?: any): Promise { return this.request(endpoint, { - method: 'POST', + method: "POST", body: data ? JSON.stringify(data) : undefined, }); } put(endpoint: string, data: any): Promise { return this.request(endpoint, { - method: 'PUT', + method: "PUT", body: JSON.stringify(data), }); } delete(endpoint: string): Promise { - return this.request(endpoint, { method: 'DELETE' }); + return this.request(endpoint, { method: "DELETE" }); } } @@ -1026,43 +1088,43 @@ export const api = new ApiClient(BASE_URL); ```typescript // tailwind.config.ts -import type { Config } from 'tailwindcss'; +import type { Config } from "tailwindcss"; const config: Config = { - darkMode: ['class'], + darkMode: ["class"], content: [ - './pages/**/*.{ts,tsx}', - './components/**/*.{ts,tsx}', - './app/**/*.{ts,tsx}', + "./pages/**/*.{ts,tsx}", + "./components/**/*.{ts,tsx}", + "./app/**/*.{ts,tsx}", ], theme: { extend: { colors: { primary: { - 50: '#EEF2FF', - 100: '#E0E7FF', - 200: '#C7D2FE', - 300: '#A5B4FC', - 400: '#818CF8', - 500: '#6366F1', - 600: '#4F46E5', - 700: '#4338CA', - 800: '#3730A3', - 900: '#312E81', + 50: "#EEF2FF", + 100: "#E0E7FF", + 200: "#C7D2FE", + 300: "#A5B4FC", + 400: "#818CF8", + 500: "#6366F1", + 600: "#4F46E5", + 700: "#4338CA", + 800: "#3730A3", + 900: "#312E81", }, risk: { - low: '#10B981', - medium: '#F59E0B', - high: '#F97316', - critical: '#EF4444', + low: "#10B981", + medium: "#F59E0B", + high: "#F97316", + critical: "#EF4444", }, }, animation: { - 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite', + "pulse-slow": "pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite", }, }, }, - plugins: [require('tailwindcss-animate')], + plugins: [require("tailwindcss-animate")], }; export default config; diff --git a/docs/IMPLEMENTATION_GUIDE.md b/docs/IMPLEMENTATION_GUIDE.md index c5169d6..e009fa6 100644 --- a/docs/IMPLEMENTATION_GUIDE.md +++ b/docs/IMPLEMENTATION_GUIDE.md @@ -20,6 +20,7 @@ Dieser Guide beschreibt die nächsten Schritte nach Abschluss von Agent 3: ## 1. 🚀 HETZNER SERVER DEPLOYMENT ### Voraussetzungen + - SSH-Zugriff zu `178.156.178.70` - Git Repository Access - PM2 installiert auf Server @@ -108,6 +109,7 @@ curl http://localhost:3000/api/linear/status ### Webhook Configuration #### GitHub Webhooks + 1. Go to your GitHub repository → Settings → Webhooks 2. Add webhook: - URL: `https://your-domain.com/api/webhooks/github` @@ -116,6 +118,7 @@ curl http://localhost:3000/api/linear/status - Events: `push`, `pull_request`, `issues` #### Linear Webhooks + 1. Go to Linear → Settings → API → Webhooks 2. Add webhook: - URL: `https://your-domain.com/api/webhooks/linear` @@ -131,6 +134,7 @@ curl http://localhost:3000/api/linear/status Der Code ist bereits vollständig für OpenAI vorbereitet: **Verwendete Stellen:** + 1. `src/chat/manager.ts` - Chat-Funktionalität mit OpenAI 2. `src/memory/embeddings.ts` - Semantic Search mit text-embedding-3-small @@ -154,12 +158,14 @@ Der Code ist bereits vollständig für OpenAI vorbereitet: ### Environment Variable Setup **Auf dem Server:** + ```bash # Add to .env file OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx ``` **Restart Server:** + ```bash pm2 restart code-cloud-agents ``` @@ -167,6 +173,7 @@ pm2 restart code-cloud-agents ### Funktions-Check **Test Chat mit OpenAI:** + ```bash curl -X POST http://localhost:3000/api/chat/send \ -H "Content-Type: application/json" \ @@ -180,6 +187,7 @@ curl -X POST http://localhost:3000/api/chat/send \ ``` **Test Semantic Search:** + ```bash # 1. Create chat and add message curl -X POST http://localhost:3000/api/memory/chats \ @@ -208,21 +216,25 @@ curl -X POST http://localhost:3000/api/memory/semantic/search \ **OpenAI Pricing (as of 2025):** **text-embedding-3-small:** + - Cost: $0.02 per 1M tokens - Usage: ~150 tokens per message - 10,000 messages = ~$0.03 **GPT-4:** + - Input: $30 per 1M tokens - Output: $60 per 1M tokens - Average conversation: ~$0.05-0.15 **GPT-3.5-turbo (cheaper alternative):** + - Input: $0.50 per 1M tokens - Output: $1.50 per 1M tokens - Average conversation: ~$0.001-0.005 **Monitoring:** + ```bash # Check usage in OpenAI Dashboard https://platform.openai.com/usage @@ -270,21 +282,25 @@ Database (SQLite/PostgreSQL) ### Technology Stack Recommendations **Frontend Framework:** + - **Next.js 14+** (recommended) - App Router, Server Components - **React 18+** - Hooks, Suspense - **TypeScript** - Type safety **UI Library:** + - **Tailwind CSS** - Utility-first styling - **shadcn/ui** - High-quality components - **Radix UI** - Accessible primitives **State Management:** + - **React Query (TanStack Query)** - Server state - **Zustand** - Client state - **WebSocket Context** - Real-time updates **API Client:** + - **Axios** or **Fetch API** - **OpenAPI Generator** - Generate types from Swagger @@ -307,19 +323,20 @@ npm install lucide-react class-variance-authority clsx tailwind-merge #### Step 2: API Client Setup **`src/lib/api.ts`:** + ```typescript -import axios from 'axios'; +import axios from "axios"; export const api = axios.create({ - baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000', + baseURL: process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, }); // Add auth token if available api.interceptors.request.use((config) => { - const token = localStorage.getItem('auth_token'); + const token = localStorage.getItem("auth_token"); if (token) { config.headers.Authorization = `Bearer ${token}`; } @@ -332,16 +349,17 @@ api.interceptors.response.use( (error) => { if (error.response?.status === 401) { // Redirect to login - window.location.href = '/login'; + window.location.href = "/login"; } return Promise.reject(error); - } + }, ); ``` #### Step 3: Memory System Integration **Chat Component:** + ```typescript // app/chat/[chatId]/page.tsx 'use client'; @@ -436,9 +454,10 @@ export default function ChatPage({ params }: { params: { chatId: string } }) { #### Step 4: WebSocket Integration **WebSocket Hook:** + ```typescript // hooks/useWebSocket.ts -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from "react"; export function useWebSocket(url: string, token: string) { const [isConnected, setIsConnected] = useState(false); @@ -450,7 +469,7 @@ export function useWebSocket(url: string, token: string) { ws.current = new WebSocket(`${url}?token=${token}`); ws.current.onopen = () => { - console.log('WebSocket connected'); + console.log("WebSocket connected"); setIsConnected(true); }; @@ -460,12 +479,12 @@ export function useWebSocket(url: string, token: string) { }; ws.current.onclose = () => { - console.log('WebSocket disconnected'); + console.log("WebSocket disconnected"); setIsConnected(false); }; ws.current.onerror = (error) => { - console.error('WebSocket error:', error); + console.error("WebSocket error:", error); }; // Cleanup @@ -485,6 +504,7 @@ export function useWebSocket(url: string, token: string) { ``` **Usage:** + ```typescript // app/dashboard/page.tsx 'use client'; @@ -519,6 +539,7 @@ export default function Dashboard() { #### Step 5: Settings Management UI **Settings Page:** + ```typescript // app/settings/page.tsx 'use client'; @@ -623,6 +644,7 @@ frontend/ ### Testing Strategy **Tools:** + 1. **k6** - Load testing 2. **Artillery** - Load testing & monitoring 3. **Apache Bench (ab)** - Quick benchmarks @@ -631,6 +653,7 @@ frontend/ ### k6 Load Testing **Installation:** + ```bash brew install k6 # macOS # or @@ -640,32 +663,33 @@ choco install k6 # Windows ``` **Test Script (`load-test.js`):** + ```javascript -import http from 'k6/http'; -import { check, sleep } from 'k6'; +import http from "k6/http"; +import { check, sleep } from "k6"; export const options = { stages: [ - { duration: '30s', target: 10 }, // Warm up - { duration: '1m', target: 50 }, // Ramp up to 50 users - { duration: '3m', target: 50 }, // Stay at 50 users - { duration: '30s', target: 100 }, // Spike to 100 users - { duration: '1m', target: 100 }, // Stay at 100 users - { duration: '30s', target: 0 }, // Ramp down + { duration: "30s", target: 10 }, // Warm up + { duration: "1m", target: 50 }, // Ramp up to 50 users + { duration: "3m", target: 50 }, // Stay at 50 users + { duration: "30s", target: 100 }, // Spike to 100 users + { duration: "1m", target: 100 }, // Stay at 100 users + { duration: "30s", target: 0 }, // Ramp down ], thresholds: { - http_req_duration: ['p(95)<500'], // 95% of requests must complete below 500ms - http_req_failed: ['rate<0.01'], // Less than 1% errors + http_req_duration: ["p(95)<500"], // 95% of requests must complete below 500ms + http_req_failed: ["rate<0.01"], // Less than 1% errors }, }; -const BASE_URL = 'http://localhost:3000'; +const BASE_URL = "http://localhost:3000"; export default function () { // Test health endpoint let res = http.get(`${BASE_URL}/health`); check(res, { - 'health status is 200': (r) => r.status === 200, + "health status is 200": (r) => r.status === 200, }); sleep(1); @@ -673,7 +697,7 @@ export default function () { // Test memory list res = http.get(`${BASE_URL}/api/memory/chats/test-user`); check(res, { - 'memory list status is 200': (r) => r.status === 200, + "memory list status is 200": (r) => r.status === 200, }); sleep(1); @@ -682,15 +706,15 @@ export default function () { res = http.post( `${BASE_URL}/api/memory/chats`, JSON.stringify({ - userId: 'test-user', - title: 'Load Test Chat', + userId: "test-user", + title: "Load Test Chat", }), { - headers: { 'Content-Type': 'application/json' }, - } + headers: { "Content-Type": "application/json" }, + }, ); check(res, { - 'create chat status is 201': (r) => r.status === 201, + "create chat status is 201": (r) => r.status === 201, }); sleep(2); @@ -698,6 +722,7 @@ export default function () { ``` **Run Test:** + ```bash k6 run load-test.js ``` @@ -705,6 +730,7 @@ k6 run load-test.js ### Database Performance Testing **Query Performance:** + ```sql -- Enable query timing PRAGMA timer=on; @@ -728,61 +754,73 @@ LIMIT 10; ``` **Benchmark Script (`benchmark-db.js`):** + ```javascript -const Database = require('better-sqlite3'); -const db = new Database('./data/app.sqlite'); +const Database = require("better-sqlite3"); +const db = new Database("./data/app.sqlite"); -console.time('1000 inserts'); -const stmt = db.prepare('INSERT INTO chat_messages (id, chat_id, role, content, timestamp) VALUES (?, ?, ?, ?, ?)'); +console.time("1000 inserts"); +const stmt = db.prepare( + "INSERT INTO chat_messages (id, chat_id, role, content, timestamp) VALUES (?, ?, ?, ?, ?)", +); for (let i = 0; i < 1000; i++) { - stmt.run(`msg-${i}`, 'test-chat', 'user', `Message ${i}`, new Date().toISOString()); + stmt.run( + `msg-${i}`, + "test-chat", + "user", + `Message ${i}`, + new Date().toISOString(), + ); } -console.timeEnd('1000 inserts'); +console.timeEnd("1000 inserts"); -console.time('1000 reads'); -const selectStmt = db.prepare('SELECT * FROM chat_messages WHERE chat_id = ?'); +console.time("1000 reads"); +const selectStmt = db.prepare("SELECT * FROM chat_messages WHERE chat_id = ?"); for (let i = 0; i < 1000; i++) { - selectStmt.get('test-chat'); + selectStmt.get("test-chat"); } -console.timeEnd('1000 reads'); +console.timeEnd("1000 reads"); ``` ### WebSocket Load Testing **WebSocket Test (`ws-load-test.js`):** + ```javascript -import ws from 'k6/ws'; -import { check } from 'k6'; +import ws from "k6/ws"; +import { check } from "k6"; export const options = { stages: [ - { duration: '30s', target: 50 }, - { duration: '1m', target: 100 }, - { duration: '30s', target: 0 }, + { duration: "30s", target: 50 }, + { duration: "1m", target: 100 }, + { duration: "30s", target: 0 }, ], }; export default function () { - const url = 'ws://localhost:3000/ws?token=test-token'; - const params = { tags: { name: 'WebSocket' } }; + const url = "ws://localhost:3000/ws?token=test-token"; + const params = { tags: { name: "WebSocket" } }; const response = ws.connect(url, params, function (socket) { - socket.on('open', () => { - console.log('Connected'); + socket.on("open", () => { + console.log("Connected"); // Send test message - socket.send(JSON.stringify({ - type: 'chat_message', - payload: { message: 'Hello' } - })); + socket.send( + JSON.stringify({ + type: "chat_message", + payload: { message: "Hello" }, + }), + ); }); - socket.on('message', (data) => { - console.log('Message received:', data); + socket.on("message", (data) => { + console.log("Message received:", data); }); - socket.on('close', () => { - console.log('Disconnected'); + socket.on("close", () => { + console.log("Disconnected"); }); socket.setTimeout(() => { @@ -790,24 +828,27 @@ export default function () { }, 10000); }); - check(response, { 'status is 101': (r) => r && r.status === 101 }); + check(response, { "status is 101": (r) => r && r.status === 101 }); } ``` ### Performance Targets **API Endpoints:** + - **Response Time:** < 100ms (p95) - **Throughput:** 1000 requests/second - **Error Rate:** < 0.1% - **Concurrent Users:** 500+ **Database:** + - **Read:** < 10ms per query - **Write:** < 50ms per query - **Index Usage:** 100% for WHERE clauses **WebSocket:** + - **Connection Time:** < 500ms - **Message Latency:** < 50ms - **Concurrent Connections:** 1000+ @@ -819,11 +860,13 @@ export default function () { ### Monitoring Stack **Option 1: Self-Hosted (Recommended for Start)** + - **Prometheus** - Metrics collection - **Grafana** - Visualization - **Loki** - Log aggregation **Option 2: Cloud (Scalable)** + - **Datadog** - All-in-one - **New Relic** - APM - **Sentry** - Error tracking @@ -831,6 +874,7 @@ export default function () { ### Prometheus Setup **Install Prometheus:** + ```bash # macOS brew install prometheus @@ -842,22 +886,24 @@ cd prometheus-* ``` **Configure (`prometheus.yml`):** + ```yaml global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - - job_name: 'code-cloud-agents' + - job_name: "code-cloud-agents" static_configs: - - targets: ['localhost:3000'] - metrics_path: '/metrics' + - targets: ["localhost:3000"] + metrics_path: "/metrics" ``` **Add Metrics to Express App:** + ```typescript // src/monitoring/metrics.ts -import promClient from 'prom-client'; +import promClient from "prom-client"; // Create registry export const register = new promClient.Registry(); @@ -867,42 +913,43 @@ promClient.collectDefaultMetrics({ register }); // Custom metrics export const httpRequestDuration = new promClient.Histogram({ - name: 'http_request_duration_seconds', - help: 'Duration of HTTP requests in seconds', - labelNames: ['method', 'route', 'status'], + name: "http_request_duration_seconds", + help: "Duration of HTTP requests in seconds", + labelNames: ["method", "route", "status"], buckets: [0.1, 0.5, 1, 2, 5], registers: [register], }); export const chatMessagesTotal = new promClient.Counter({ - name: 'chat_messages_total', - help: 'Total number of chat messages', - labelNames: ['user_id', 'agent_name'], + name: "chat_messages_total", + help: "Total number of chat messages", + labelNames: ["user_id", "agent_name"], registers: [register], }); export const embeddingsGenerated = new promClient.Counter({ - name: 'embeddings_generated_total', - help: 'Total number of embeddings generated', + name: "embeddings_generated_total", + help: "Total number of embeddings generated", registers: [register], }); export const openaiApiCalls = new promClient.Counter({ - name: 'openai_api_calls_total', - help: 'Total OpenAI API calls', - labelNames: ['model', 'status'], + name: "openai_api_calls_total", + help: "Total OpenAI API calls", + labelNames: ["model", "status"], registers: [register], }); ``` **Add to Express:** + ```typescript // src/index.ts -import { register, httpRequestDuration } from './monitoring/metrics.js'; +import { register, httpRequestDuration } from "./monitoring/metrics.js"; // Metrics endpoint -app.get('/metrics', async (req, res) => { - res.setHeader('Content-Type', register.contentType); +app.get("/metrics", async (req, res) => { + res.setHeader("Content-Type", register.contentType); const metrics = await register.metrics(); res.send(metrics); }); @@ -910,10 +957,14 @@ app.get('/metrics', async (req, res) => { // Metrics middleware app.use((req, res, next) => { const start = Date.now(); - res.on('finish', () => { + res.on("finish", () => { const duration = (Date.now() - start) / 1000; httpRequestDuration - .labels(req.method, req.route?.path || req.path, res.statusCode.toString()) + .labels( + req.method, + req.route?.path || req.path, + res.statusCode.toString(), + ) .observe(duration); }); next(); @@ -923,6 +974,7 @@ app.use((req, res, next) => { ### Grafana Dashboard **Install Grafana:** + ```bash # macOS brew install grafana @@ -934,11 +986,13 @@ brew services start grafana **Access:** http://localhost:3000 (default: admin/admin) **Add Prometheus Data Source:** + 1. Configuration → Data Sources → Add Prometheus 2. URL: http://localhost:9090 3. Save & Test **Create Dashboard:** + ```json { "dashboard": { @@ -984,15 +1038,17 @@ brew services start grafana ### Error Tracking (Sentry) **Install Sentry:** + ```bash npm install @sentry/node @sentry/profiling-node ``` **Setup:** + ```typescript // src/monitoring/sentry.ts -import * as Sentry from '@sentry/node'; -import { nodeProfilingIntegration } from '@sentry/profiling-node'; +import * as Sentry from "@sentry/node"; +import { nodeProfilingIntegration } from "@sentry/profiling-node"; export function initSentry(app: Express) { Sentry.init({ @@ -1019,9 +1075,14 @@ export function sentryErrorHandler(app: Express) { ### Analytics Events **Track Key Events:** + ```typescript // src/monitoring/analytics.ts -import { chatMessagesTotal, embeddingsGenerated, openaiApiCalls } from './metrics.js'; +import { + chatMessagesTotal, + embeddingsGenerated, + openaiApiCalls, +} from "./metrics.js"; export function trackChatMessage(userId: string, agentName: string) { chatMessagesTotal.labels(userId, agentName).inc(); @@ -1031,29 +1092,33 @@ export function trackEmbeddingGeneration() { embeddingsGenerated.inc(); } -export function trackOpenAICall(model: string, status: 'success' | 'error') { +export function trackOpenAICall(model: string, status: "success" | "error") { openaiApiCalls.labels(model, status).inc(); } ``` **Use in Code:** + ```typescript // src/memory/embeddings.ts -import { trackEmbeddingGeneration, trackOpenAICall } from '../monitoring/analytics.js'; +import { + trackEmbeddingGeneration, + trackOpenAICall, +} from "../monitoring/analytics.js"; async function generateEmbedding(text: string): Promise { try { const response = await this.openai.embeddings.create({ - model: 'text-embedding-3-small', + model: "text-embedding-3-small", input: text, }); - trackOpenAICall('text-embedding-3-small', 'success'); + trackOpenAICall("text-embedding-3-small", "success"); trackEmbeddingGeneration(); return response.data[0].embedding; } catch (error) { - trackOpenAICall('text-embedding-3-small', 'error'); + trackOpenAICall("text-embedding-3-small", "error"); throw error; } } @@ -1062,41 +1127,49 @@ async function generateEmbedding(text: string): Promise { ### Logging Strategy **Structured Logging with Winston:** + ```bash npm install winston ``` ```typescript // src/monitoring/logger.ts -import winston from 'winston'; +import winston from "winston"; export const logger = winston.createLogger({ - level: process.env.LOG_LEVEL || 'info', + level: process.env.LOG_LEVEL || "info", format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), - winston.format.json() + winston.format.json(), ), transports: [ - new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), - new winston.transports.File({ filename: 'logs/combined.log' }), + new winston.transports.File({ filename: "logs/error.log", level: "error" }), + new winston.transports.File({ filename: "logs/combined.log" }), ], }); -if (process.env.NODE_ENV !== 'production') { - logger.add(new winston.transports.Console({ - format: winston.format.simple(), - })); +if (process.env.NODE_ENV !== "production") { + logger.add( + new winston.transports.Console({ + format: winston.format.simple(), + }), + ); } ``` **Usage:** + ```typescript -import { logger } from './monitoring/logger.js'; +import { logger } from "./monitoring/logger.js"; -logger.info('Chat message received', { userId, chatId, messageLength: content.length }); -logger.error('OpenAI API error', { error: error.message, model, userId }); -logger.warn('Rate limit approaching', { userId, currentRate, limit }); +logger.info("Chat message received", { + userId, + chatId, + messageLength: content.length, +}); +logger.error("OpenAI API error", { error: error.message, model, userId }); +logger.warn("Rate limit approaching", { userId, currentRate, limit }); ``` --- @@ -1104,6 +1177,7 @@ logger.warn('Rate limit approaching', { userId, currentRate, limit }); ## 📝 ZUSAMMENFASSUNG ### Completed ✅ + 1. ✅ Agent 3 - Alle 7 Tasks vollständig implementiert 2. ✅ Deployment Report erstellt 3. ✅ Implementation Guide erstellt @@ -1111,20 +1185,14 @@ logger.warn('Rate limit approaching', { userId, currentRate, limit }); ### Next Steps 🟢 **Immediate:** + 1. Hetzner Server Deployment (manual SSH) 2. Environment Variables Setup 3. Webhook Configuration -**Short-term:** -4. OpenAI API Key Setup (already prepared) -5. Frontend Development (Next.js + React Query) -6. Performance Testing (k6 + Artillery) -7. Monitoring Setup (Prometheus + Grafana) +**Short-term:** 4. OpenAI API Key Setup (already prepared) 5. Frontend Development (Next.js + React Query) 6. Performance Testing (k6 + Artillery) 7. Monitoring Setup (Prometheus + Grafana) -**Medium-term:** -8. Error Tracking (Sentry) -9. Analytics Dashboard -10. Load Testing & Optimization +**Medium-term:** 8. Error Tracking (Sentry) 9. Analytics Dashboard 10. Load Testing & Optimization --- diff --git a/docs/MEMORY.md b/docs/MEMORY.md index 56b8765..c2814e5 100644 --- a/docs/MEMORY.md +++ b/docs/MEMORY.md @@ -37,6 +37,7 @@ Das Memory System bietet vollständiges Conversation Memory Management mit drei ## Database Schema ### Chats Table + ```sql CREATE TABLE chats ( id TEXT PRIMARY KEY, @@ -53,6 +54,7 @@ CREATE INDEX idx_chats_user_id ON chats(user_id); ``` ### Chat Messages Table + ```sql CREATE TABLE chat_messages ( id TEXT PRIMARY KEY, @@ -71,6 +73,7 @@ CREATE INDEX idx_messages_chat_id ON chat_messages(chat_id); ``` ### Message Embeddings Table + ```sql CREATE TABLE message_embeddings ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -92,6 +95,7 @@ CREATE INDEX idx_embeddings_created_at ON message_embeddings(created_at); Verwaltet Chats und Messages mit vollständigem CRUD-Support. **Key Features:** + - Chat Lifecycle Management (create, read, update, delete) - Message Storage mit Token Tracking - Context Retrieval (last N messages) @@ -99,26 +103,27 @@ Verwaltet Chats und Messages mit vollständigem CRUD-Support. - User Statistics **Example Usage:** + ```typescript -import { MemoryManager } from './memory/manager.js'; +import { MemoryManager } from "./memory/manager.js"; const memory = new MemoryManager(db); // Create chat const chat = memory.createChat({ - userId: 'user-123', - title: 'My Conversation', - agentName: 'Claude', - initialMessage: 'Hello!' + userId: "user-123", + title: "My Conversation", + agentName: "Claude", + initialMessage: "Hello!", }); // Add messages memory.addMessage({ chatId: chat.id, - role: 'assistant', - content: 'Hi! How can I help you?', + role: "assistant", + content: "Hi! How can I help you?", tokensInput: 10, - tokensOutput: 15 + tokensOutput: 15, }); // Get recent context @@ -130,6 +135,7 @@ const context = memory.getRecentMessages(chat.id, 10); Full-text und keyword-basierte Suche mit Snippet-Generierung. **Key Features:** + - Message Search mit SQL LIKE - Chat Search nach Titel - Similar Messages (keyword overlap) @@ -137,20 +143,21 @@ Full-text und keyword-basierte Suche mit Snippet-Generierung. - Trending Topics (häufigste Keywords) **Example Usage:** + ```typescript -import { MemorySearch } from './memory/search.js'; +import { MemorySearch } from "./memory/search.js"; const search = new MemorySearch(db); // Full-text search const results = search.searchMessages({ - userId: 'user-123', - query: 'machine learning', - limit: 20 + userId: "user-123", + query: "machine learning", + limit: 20, }); // Get trending topics -const trending = search.getTrendingTopics('user-123', 10, 7); +const trending = search.getTrendingTopics("user-123", 10, 7); ``` ### 3. EmbeddingsManager @@ -158,6 +165,7 @@ const trending = search.getTrendingTopics('user-123', 10, 7); Semantic Search mit OpenAI Embeddings und Cosine Similarity. **Key Features:** + - Auto-Embedding bei Message Creation (optional) - Batch Embedding Generation für existing chats - Semantic Search mit Cosine Similarity @@ -165,14 +173,16 @@ Semantic Search mit OpenAI Embeddings und Cosine Similarity. - Model: `text-embedding-3-small` (1536 dimensions) **Setup:** + ```bash # Required environment variable export OPENAI_API_KEY="sk-..." ``` **Example Usage:** + ```typescript -import { EmbeddingsManager } from './memory/embeddings.js'; +import { EmbeddingsManager } from "./memory/embeddings.js"; const embeddings = new EmbeddingsManager(db); @@ -183,9 +193,9 @@ if (embeddings.isEnabled()) { // Semantic search const results = await embeddings.semanticSearch( - 'How do I deploy to production?', - 'user-123', - 10 + "How do I deploy to production?", + "user-123", + 10, ); // Find similar messages @@ -198,13 +208,16 @@ if (embeddings.isEnabled()) { ### Chat Management #### `GET /api/memory/chats/:userId` + List all chats for a user. **Query Parameters:** + - `limit` (default: 50) - Max chats to return - `offset` (default: 0) - Pagination offset **Response:** + ```json { "chats": [ @@ -223,9 +236,11 @@ List all chats for a user. ``` #### `POST /api/memory/chats` + Create a new chat. **Body:** + ```json { "userId": "user-123", @@ -236,12 +251,15 @@ Create a new chat. ``` #### `GET /api/memory/chats/:chatId/details` + Get chat details. #### `PATCH /api/memory/chats/:chatId` + Update chat metadata. **Body:** + ```json { "title": "Updated Title", @@ -250,18 +268,22 @@ Update chat metadata. ``` #### `DELETE /api/memory/chats/:chatId` + Delete a chat and all its messages. ### Message Management #### `GET /api/memory/chats/:chatId/messages` + Get messages from a chat. **Query Parameters:** + - `limit` (default: 100) - `offset` (default: 0) **Response:** + ```json { "messages": [ @@ -281,9 +303,11 @@ Get messages from a chat. ``` #### `POST /api/memory/chats/:chatId/messages` + Add a message to a chat. **Body:** + ```json { "role": "user", @@ -298,23 +322,29 @@ Add a message to a chat. If `OPENAI_API_KEY` is set, embeddings are automatically generated for new messages. #### `GET /api/memory/chats/:chatId/recent` + Get recent messages (for context). **Query Parameters:** + - `count` (default: 10) #### `DELETE /api/memory/chats/:chatId/messages/old` + Clear old messages, keeping only the last N. **Query Parameters:** + - `keepLast` (default: 100) ### Search #### `POST /api/memory/search` + Full-text search across messages. **Body:** + ```json { "userId": "user-123", @@ -325,6 +355,7 @@ Full-text search across messages. ``` **Response:** + ```json { "results": [ @@ -343,19 +374,24 @@ Full-text search across messages. ``` #### `GET /api/memory/search/chats/:userId` + Search chats by title. **Query Parameters:** + - `query` - Search term - `limit` (default: 20) #### `GET /api/memory/messages/:messageId/context` + Get context around a message. **Query Parameters:** + - `contextSize` (default: 5) - Messages before/after **Response:** + ```json { "before": [...], @@ -365,19 +401,24 @@ Get context around a message. ``` #### `GET /api/memory/messages/:messageId/similar` + Find similar messages (keyword-based). **Query Parameters:** + - `limit` (default: 10) #### `GET /api/memory/trending/:userId` + Get trending topics. **Query Parameters:** + - `limit` (default: 10) - `days` (default: 7) **Response:** + ```json { "topics": [ @@ -390,11 +431,13 @@ Get trending topics. ### Semantic Search (Embeddings) #### `POST /api/memory/semantic/search` + Semantic search using embeddings. **Requirements:** `OPENAI_API_KEY` must be set **Body:** + ```json { "query": "How do I deploy?", @@ -404,6 +447,7 @@ Semantic search using embeddings. ``` **Response:** + ```json { "results": [ @@ -421,9 +465,11 @@ Semantic search using embeddings. ``` #### `POST /api/memory/chats/:chatId/embeddings/generate` + Generate embeddings for all messages in a chat. **Response:** + ```json { "generated": 42, @@ -432,9 +478,11 @@ Generate embeddings for all messages in a chat. ``` #### `GET /api/memory/embeddings/stats` + Get embedding statistics. **Response:** + ```json { "totalEmbeddings": 1234, @@ -448,9 +496,11 @@ Get embedding statistics. ### Export & Stats #### `GET /api/memory/chats/:chatId/export` + Export complete chat with all messages. **Response:** + ```json { "chat": {...}, @@ -459,9 +509,11 @@ Export complete chat with all messages. ``` #### `GET /api/memory/stats/:userId` + Get user statistics. **Response:** + ```json { "totalChats": 25, @@ -473,21 +525,27 @@ Get user statistics. ## Performance Considerations ### Indexing + Alle wichtigen Spalten sind indexiert: + - `chats.user_id` - für User-Queries - `chat_messages.chat_id` - für Message-Retrieval - `message_embeddings.message_id` - für Embedding-Lookup ### Pagination + Alle List-Endpoints unterstützen `limit` und `offset` für Pagination. ### Token Tracking + Token-Counts werden optional gespeichert für: + - Cost Analysis - Usage Monitoring - Quota Enforcement ### Embedding Generation + - Embeddings werden asynchron generiert - Batch-Processing für existing chats - Cache in Database (kein Re-Generate) @@ -503,6 +561,7 @@ Alle Endpoints verwenden standard HTTP Status Codes: - `500 Internal Server Error` - Server-Fehler **Error Response Format:** + ```json { "error": "Error message" @@ -512,16 +571,19 @@ Alle Endpoints verwenden standard HTTP Status Codes: ## Security Considerations ### Input Validation + - Alle Inputs werden mit Zod validiert - SQL Injection Prevention durch Prepared Statements - XSS Prevention durch Content Sanitization ### Data Privacy + - User Isolation (alle Queries filtern nach `userId`) - CASCADE DELETE (Messages werden mit Chat gelöscht) - Embeddings werden mit Messages gelöscht (ON DELETE CASCADE) ### API Key Security + - `OPENAI_API_KEY` nur server-side - Niemals im Frontend exponiert - Environment Variable statt Hardcoding @@ -529,12 +591,14 @@ Alle Endpoints verwenden standard HTTP Status Codes: ## Cost Management ### OpenAI Embedding Costs + - Model: `text-embedding-3-small` - Cost: ~$0.02 per 1M tokens - Durchschnitt: ~150 tokens pro Message - 1000 Messages ≈ $0.003 ### Database Storage + - Messages: ~1KB pro Message - Embeddings: ~6KB pro Message (1536 floats als JSON) - 10,000 Messages ≈ 70MB @@ -583,8 +647,9 @@ curl -X POST http://localhost:3000/api/memory/semantic/search \ ## Integration Examples ### React Hook + ```typescript -import { useState, useEffect } from 'react'; +import { useState, useEffect } from "react"; function useChat(chatId: string) { const [messages, setMessages] = useState([]); @@ -592,8 +657,8 @@ function useChat(chatId: string) { useEffect(() => { fetch(`/api/memory/chats/${chatId}/messages`) - .then(res => res.json()) - .then(data => { + .then((res) => res.json()) + .then((data) => { setMessages(data.messages); setLoading(false); }); @@ -601,9 +666,9 @@ function useChat(chatId: string) { const addMessage = async (content: string, role: string) => { const res = await fetch(`/api/memory/chats/${chatId}/messages`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ content, role }) + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ content, role }), }); const newMessage = await res.json(); setMessages([...messages, newMessage]); @@ -614,24 +679,25 @@ function useChat(chatId: string) { ``` ### Node.js Client + ```typescript class MemoryClient { constructor(private baseUrl: string) {} async createChat(userId: string, title: string) { const res = await fetch(`${this.baseUrl}/api/memory/chats`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ userId, title }) + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ userId, title }), }); return res.json(); } async semanticSearch(userId: string, query: string) { const res = await fetch(`${this.baseUrl}/api/memory/semantic/search`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ userId, query, limit: 10 }) + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ userId, query, limit: 10 }), }); return res.json(); } @@ -654,6 +720,7 @@ Mögliche zukünftige Features: ## Support Bei Fragen oder Problemen: + - Dokumentation: `/docs/MEMORY.md` - API Reference: Siehe REST API Section - Examples: Siehe Integration Examples Section diff --git a/docs/POSTMAN.md b/docs/POSTMAN.md index c7db98f..3c5804a 100644 --- a/docs/POSTMAN.md +++ b/docs/POSTMAN.md @@ -18,10 +18,12 @@ ### 2. Import Environment **Production:** + 1. Click **Import** → Select `postman/environment.json` 2. Set environment to "Code Cloud Agents - Production" **Local Development:** + 1. Click **Import** → Select `postman/local-environment.json` 2. Set environment to "Code Cloud Agents - Local" @@ -30,11 +32,13 @@ The collection uses **Bearer Token** authentication. **Option A: Manual Setup** + 1. Go to **Authentication** folder 2. Run `Login` request with your credentials 3. Token will be automatically saved to environment variable `jwt_token` **Option B: Set Token Manually** + 1. Click **Environments** (eye icon) 2. Edit `jwt_token` variable 3. Paste your JWT token @@ -50,6 +54,7 @@ POST {{base_url}}/api/auth/login ``` **Body:** + ```json { "email": "admin@example.com", @@ -58,6 +63,7 @@ POST {{base_url}}/api/auth/login ``` **Response:** + ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", @@ -70,17 +76,19 @@ POST {{base_url}}/api/auth/login ``` The **Test** script automatically saves the token: + ```javascript if (pm.response.code === 200) { - const jsonData = pm.response.json(); - pm.environment.set('jwt_token', jsonData.token); - pm.collectionVariables.set('jwt_token', jsonData.token); + const jsonData = pm.response.json(); + pm.environment.set("jwt_token", jsonData.token); + pm.collectionVariables.set("jwt_token", jsonData.token); } ``` ### Step 2: Use Token All subsequent requests will automatically include: + ``` Authorization: Bearer {{jwt_token}} ``` @@ -90,27 +98,33 @@ Authorization: Bearer {{jwt_token}} ## 📋 Collection Structure ### 1. Health & Info (2 requests) + - `GET /health` - System health check - `GET /api` - API information ### 2. Authentication (1 request) + - `POST /api/auth/login` - User login (auto-saves token) ### 3. Tasks (3 requests) + - `GET /api/tasks` - List all tasks - `POST /api/tasks` - Create new task - `GET /api/tasks/:id` - Get task by ID ### 4. Audit (2 requests) + - `GET /api/audit` - Get audit logs - `POST /api/audit/verify/:id` - Verify task claims ### 5. Enforcement (3 requests) + - `GET /api/enforcement/blocked` - Get blocked tasks - `POST /api/enforcement/approve` - Approve task - `POST /api/enforcement/reject` - Reject task ### 6. Memory System (6 requests) + - `GET /api/memory/conversations` - List conversations - `POST /api/memory/conversations` - Create conversation - `GET /api/memory/conversations/:id` - Get conversation @@ -119,14 +133,17 @@ Authorization: Bearer {{jwt_token}} - `GET /api/memory/stats` - Statistics ### 7. GitHub (2 requests) + - `GET /api/github/repos` - List repositories - `GET /api/github/issues/:owner/:repo` - Get issues ### 8. Linear (2 requests) + - `GET /api/linear/issues` - List issues - `GET /api/linear/projects` - List projects ### 9. Settings (2 requests) + - `GET /api/settings` - Get settings - `PUT /api/settings` - Update settings @@ -136,11 +153,11 @@ Authorization: Bearer {{jwt_token}} ## 🔧 Environment Variables -| Variable | Description | Example | -|----------|-------------|---------| -| `base_url` | API base URL | `http://178.156.178.70:3000` | -| `jwt_token` | JWT authentication token | Auto-set after login | -| `user_email` | User email for testing | `admin@example.com` | +| Variable | Description | Example | +| ------------ | ------------------------ | ---------------------------- | +| `base_url` | API base URL | `http://178.156.178.70:3000` | +| `jwt_token` | JWT authentication token | Auto-set after login | +| `user_email` | User email for testing | `admin@example.com` | ### Switching Environments @@ -193,22 +210,24 @@ Click the environment dropdown → Select desired environment ### Auto-set Task ID Add this to a request's **Pre-request Script**: + ```javascript // Use last created task ID -const taskId = pm.collectionVariables.get('last_task_id'); +const taskId = pm.collectionVariables.get("last_task_id"); if (taskId) { - pm.variables.set('id', taskId); + pm.variables.set("id", taskId); } ``` ### Save Response Data Add this to a request's **Tests**: + ```javascript // Save task ID from response if (pm.response.code === 201) { - const jsonData = pm.response.json(); - pm.collectionVariables.set('last_task_id', jsonData.id); + const jsonData = pm.response.json(); + pm.collectionVariables.set("last_task_id", jsonData.id); } ``` @@ -219,25 +238,27 @@ if (pm.response.code === 201) { ### 1. Add Tests Example test script: + ```javascript pm.test("Status code is 200", function () { - pm.response.to.have.status(200); + pm.response.to.have.status(200); }); pm.test("Response has required fields", function () { - const jsonData = pm.response.json(); - pm.expect(jsonData).to.have.property('id'); - pm.expect(jsonData).to.have.property('status'); + const jsonData = pm.response.json(); + pm.expect(jsonData).to.have.property("id"); + pm.expect(jsonData).to.have.property("status"); }); pm.test("Response time < 500ms", function () { - pm.expect(pm.response.responseTime).to.be.below(500); + pm.expect(pm.response.responseTime).to.be.below(500); }); ``` ### 2. Use Variables Replace hardcoded values: + ```json { "email": "{{user_email}}", @@ -260,12 +281,14 @@ Replace hardcoded values: ### "401 Unauthorized" **Solution:** Token expired or missing + 1. Re-run `Authentication/Login` 2. Check `jwt_token` in environment variables ### "404 Not Found" **Solution:** Check URL and endpoint + - Verify `base_url` is correct - Check if server is running - Test with `Health Check` request @@ -273,6 +296,7 @@ Replace hardcoded values: ### "500 Internal Server Error" **Solution:** Server issue + 1. Check server logs 2. Verify request body format 3. Contact admin @@ -280,6 +304,7 @@ Replace hardcoded values: ### Variables Not Working **Solution:** + 1. Check environment is selected (dropdown) 2. Verify variable syntax: `{{variable_name}}` 3. Check variable scope (environment vs collection) @@ -289,6 +314,7 @@ Replace hardcoded values: ## 📊 Response Examples ### Task Creation Success + ```json { "id": "task-abc123", @@ -300,6 +326,7 @@ Replace hardcoded values: ``` ### Task Blocked (High STOP Score) + ```json { "blocked": true, @@ -312,6 +339,7 @@ Replace hardcoded values: ``` ### Memory Search Results + ```json [ { diff --git a/docs/POSTMAN_GUIDE.md b/docs/POSTMAN_GUIDE.md index b89a045..4463a4c 100644 --- a/docs/POSTMAN_GUIDE.md +++ b/docs/POSTMAN_GUIDE.md @@ -23,10 +23,12 @@ Komplette Anleitung für die Verwendung der Postman Collection zur Interaktion m ### 1. Postman installieren **Desktop App (empfohlen):** + - Download: https://www.postman.com/downloads/ - macOS, Windows, Linux verfügbar **Web Version:** + - URL: https://web.postman.com/ - Erfordert Login @@ -119,12 +121,14 @@ Code Cloud Agents API ### Example 1: Complete Task Workflow **Schritt 1: Health Check** + ``` GET /health → Verify system is running ``` **Schritt 2: Create Task** + ``` POST /api/tasks Body: @@ -137,12 +141,14 @@ Body: ``` **Schritt 3: Get Task Details** + ``` GET /api/tasks/{{taskId}} → Uses saved taskId from previous request ``` **Schritt 4: Submit Work** + ``` POST /api/tasks/{{taskId}} Body: @@ -155,12 +161,14 @@ Body: ``` **Schritt 5: Check Blocked Tasks (if blocked)** + ``` GET /api/enforcement/blocked → Shows all blocked tasks ``` **Schritt 6: Approve Task (if blocked)** + ``` POST /api/enforcement/approve Body: @@ -177,6 +185,7 @@ Body: ### Example 2: Demo User Creation **Schritt 1: Create Invite (Admin)** + ``` POST /api/demo/invites Body: @@ -190,6 +199,7 @@ Body: ``` **Schritt 2: Redeem Invite** + ``` POST /api/demo/redeem Body: @@ -203,6 +213,7 @@ Body: ``` **Schritt 3: Check User Usage** + ``` GET /api/demo/users/{{demoUserId}} → Shows usage stats (tasks, messages, credits) @@ -213,12 +224,14 @@ GET /api/demo/users/{{demoUserId}} ### Example 3: AI Chat Workflow **Schritt 1: Get Available Agents** + ``` GET /api/chat/agents → List of AI agents (emir, mujo, etc.) ``` **Schritt 2: Send Chat Message** + ``` POST /api/chat/send Body: @@ -240,10 +253,12 @@ Body: Jeder Request hat eingebaute Tests: **Global Tests (auf alle Requests):** + - ✅ Response time < 5000ms - ✅ Content-Type is application/json **Request-spezifische Tests:** + - ✅ Status Code Checks - ✅ Response Structure Validation - ✅ Data Type Checks @@ -251,17 +266,20 @@ Jeder Request hat eingebaute Tests: ### Tests ausführen: **Einzelner Request:** + 1. Request auswählen 2. "Send" klicken 3. "Test Results" Tab anzeigen **Ganzer Ordner:** + 1. Ordner auswählen (z.B. "Tasks") 2. "..." klicken → "Run folder" 3. "Run" klicken 4. Test Results anzeigen **Gesamte Collection:** + 1. Collection auswählen 2. "..." klicken → "Run collection" 3. Environment wählen @@ -291,24 +309,26 @@ Jeder Request hat eingebaute Tests: ### Environment Variables -| Variable | Type | Beschreibung | -|----------|------|--------------| -| `baseUrl` | Static | API Base URL | -| `taskId` | Dynamic | Zuletzt erstellte Task ID | -| `auditId` | Dynamic | Zuletzt erstellte Audit Entry ID | -| `inviteCode` | Dynamic | Zuletzt erstellter Invite Code | -| `demoUserId` | Dynamic | Zuletzt erstellte Demo User ID | -| `userId` | Static | Standard User ID | -| `adminEmail` | Static | Admin Email | +| Variable | Type | Beschreibung | +| ------------ | ------- | -------------------------------- | +| `baseUrl` | Static | API Base URL | +| `taskId` | Dynamic | Zuletzt erstellte Task ID | +| `auditId` | Dynamic | Zuletzt erstellte Audit Entry ID | +| `inviteCode` | Dynamic | Zuletzt erstellter Invite Code | +| `demoUserId` | Dynamic | Zuletzt erstellte Demo User ID | +| `userId` | Static | Standard User ID | +| `adminEmail` | Static | Admin Email | ### Variables verwenden: **In URL:** + ``` {{baseUrl}}/api/tasks/{{taskId}} ``` **In Request Body:** + ```json { "userId": "{{userId}}", @@ -317,6 +337,7 @@ Jeder Request hat eingebaute Tests: ``` **In Headers:** + ``` X-User-ID: {{userId}} ``` @@ -324,14 +345,16 @@ X-User-ID: {{userId}} ### Variables setzen: **Manuell:** + 1. Environment auswählen (rechts oben) 2. "..." klicken → "Edit" 3. Variable ändern 4. "Save" klicken **Automatisch via Tests:** + ```javascript -pm.environment.set('taskId', jsonData.id); +pm.environment.set("taskId", jsonData.id); ``` --- @@ -343,13 +366,14 @@ pm.environment.set('taskId', jsonData.id); Läuft vor **jedem** Request: ```javascript -console.log('=== Request Details ==='); -console.log('Method:', pm.request.method); -console.log('URL:', pm.request.url.toString()); -console.log('Timestamp:', new Date().toISOString()); +console.log("=== Request Details ==="); +console.log("Method:", pm.request.method); +console.log("URL:", pm.request.url.toString()); +console.log("Timestamp:", new Date().toISOString()); ``` **Nützlich für:** + - Logging - Token Refresh (zukünftig) - Request Timing @@ -359,11 +383,13 @@ console.log('Timestamp:', new Date().toISOString()); ### Request-spezifische Pre-request Scripts **Beispiel: Dynamic Timestamp** + ```javascript -pm.environment.set('timestamp', new Date().toISOString()); +pm.environment.set("timestamp", new Date().toISOString()); ``` Dann in Body verwenden: + ```json { "createdAt": "{{timestamp}}" @@ -379,6 +405,7 @@ Dann in Body verwenden: **Ursache:** Server läuft nicht **Lösung:** + ```bash # Check if server is running curl http://localhost:3000/health @@ -394,6 +421,7 @@ npm run backend:dev **Ursache:** Endpoint existiert nicht oder falsche URL **Lösung:** + 1. Check baseUrl in Environment 2. Check Endpoint URL 3. Verify server routes: http://localhost:3000/api @@ -405,6 +433,7 @@ npm run backend:dev **Ursache:** Variable nicht gesetzt **Lösung:** + 1. Check Environment ist aktiv (rechts oben) 2. Run "Create Task" Request zuerst (setzt taskId) 3. Oder setze Variable manuell @@ -416,6 +445,7 @@ npm run backend:dev **Ursache:** Server returned error (4xx/5xx) **Lösung:** + 1. Check Response Body für Error-Details 2. Check Request Body format 3. Check Server Logs: @@ -430,6 +460,7 @@ npm run backend:dev **Ursache:** Demo Redeem Endpoint ist rate-limited **Lösung:** + - Warte 15 Minuten - Oder use different IP/User @@ -448,16 +479,19 @@ npm run backend:dev ### 2. Request Order **Empfohlene Reihenfolge:** + 1. Health Check (verify server) 2. Get API Info (verify version) 3. Functionality Tests (Tasks, Chat, etc.) **Task Workflow:** + ``` Create Task → Get Task → Submit Work → Check Blocked → Approve/Reject ``` **Demo Workflow:** + ``` Create Invite → Redeem Invite → Check User Stats ``` @@ -467,11 +501,13 @@ Create Invite → Redeem Invite → Check User Stats ### 3. Testing Strategy **Development:** + - Run einzelne Requests - Verify Response manually - Iterate quickly **Pre-Deployment:** + - Run gesamte Collection - Verify all Tests pass - Check Performance (response times) @@ -491,6 +527,7 @@ Create Invite → Redeem Invite → Check User Stats ### Collection Runner **Automatisierte Test-Suites:** + 1. "Runner" klicken (links unten) 2. Collection wählen 3. Environment wählen @@ -498,6 +535,7 @@ Create Invite → Redeem Invite → Check User Stats 5. "Run Code Cloud Agents API" **Export Results:** + - JSON Export - HTML Export (via Newman CLI) @@ -527,6 +565,7 @@ newman run postman/Cloud-Agents.postman_collection.json \ ### CI/CD Integration **GitHub Actions Example:** + ```yaml - name: Run Postman Tests run: | @@ -541,12 +580,14 @@ newman run postman/Cloud-Agents.postman_collection.json \ ## 📚 Weitere Ressourcen ### Dokumentation: + - **[README.md](../README.md)**: Projekt-Overview - **[DEVELOPER_GUIDE.md](./DEVELOPER_GUIDE.md)**: Entwickler-Setup - **[ARCHITECTURE.md](./ARCHITECTURE.md)**: System-Design - **[OpenAPI Spec](../swagger.yaml)**: API Specification ### Postman Learning: + - https://learning.postman.com/ - https://www.postman.com/postman/workspace/postman-answers @@ -555,6 +596,7 @@ newman run postman/Cloud-Agents.postman_collection.json \ ## 🆘 Support **Bei Problemen:** + 1. Check Troubleshooting Section oben 2. Check Server Logs: `pm2 logs cloud-agents-backend` 3. Check API Docs: http://localhost:3000/api/docs diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md index 92fd855..c6dce65 100644 --- a/docs/SETTINGS.md +++ b/docs/SETTINGS.md @@ -70,6 +70,7 @@ CREATE TABLE settings_history ( ### Default Settings **User Settings:** + ```typescript { theme: "auto", // "light" | "dark" | "auto" @@ -88,6 +89,7 @@ CREATE TABLE settings_history ( ``` **System Settings:** + ```typescript { appName: "Code Cloud Agents", @@ -111,6 +113,7 @@ Returns user settings with automatic defaults if not exists. **Endpoint:** `GET /api/settings/user/:userId` **Response:** + ```json { "success": true, @@ -118,7 +121,11 @@ Returns user settings with automatic defaults if not exists. "theme": "auto", "language": "en", "notifications": { "email": true, "push": true, "inApp": true }, - "preferences": { "showAgentStatus": true, "showSystemNotifications": true, "autoSaveInterval": 30 } + "preferences": { + "showAgentStatus": true, + "showSystemNotifications": true, + "autoSaveInterval": 30 + } }, "metadata": { "createdAt": "2025-12-26T10:00:00.000Z", @@ -134,6 +141,7 @@ Updates user settings with deep merge. **Endpoint:** `PUT /api/settings/user/:userId` **Request Body:** + ```json { "theme": "dark", @@ -144,6 +152,7 @@ Updates user settings with deep merge. ``` **Response:** + ```json { "success": true, @@ -151,12 +160,17 @@ Updates user settings with deep merge. "theme": "dark", "language": "en", "notifications": { "email": true, "push": false, "inApp": true }, - "preferences": { "showAgentStatus": true, "showSystemNotifications": true, "autoSaveInterval": 30 } + "preferences": { + "showAgentStatus": true, + "showSystemNotifications": true, + "autoSaveInterval": 30 + } } } ``` **Validation Rules:** + - `theme`: Must be "light", "dark", or "auto" - `language`: Must be "en", "de", or "bs" - `notifications`: email, push, inApp must be booleans @@ -169,6 +183,7 @@ Resets user to defaults by deleting their settings. **Endpoint:** `DELETE /api/settings/user/:userId` **Response:** + ```json { "success": true, @@ -187,6 +202,7 @@ Preferences are a subset of user settings for quick access. **Endpoint:** `GET /api/settings/preferences/:userId` **Response:** + ```json { "success": true, @@ -205,6 +221,7 @@ Partial update of preferences only. **Endpoint:** `PATCH /api/settings/preferences/:userId` **Request Body:** + ```json { "autoSaveInterval": 60 @@ -212,6 +229,7 @@ Partial update of preferences only. ``` **Response:** + ```json { "success": true, @@ -234,6 +252,7 @@ Partial update of preferences only. **Endpoint:** `GET /api/settings/system` **Response:** + ```json { "success": true, @@ -256,6 +275,7 @@ Partial update of preferences only. **Example:** `GET /api/settings/system/maxConcurrentAgents` **Response:** + ```json { "success": true, @@ -269,6 +289,7 @@ Partial update of preferences only. ``` If setting doesn't exist, returns default: + ```json { "success": true, @@ -285,6 +306,7 @@ Partial update of system settings. **Endpoint:** `PUT /api/settings/system` **Request Body:** + ```json { "maintenanceMode": true, @@ -294,6 +316,7 @@ Partial update of system settings. ``` **Response:** + ```json { "success": true, @@ -306,6 +329,7 @@ Partial update of system settings. ``` **Validation Rules:** + - `appName`: 1-100 characters - `maxConcurrentAgents`: 1-10 - `defaultAgentTimeout`: 60-3600 seconds @@ -322,6 +346,7 @@ Every settings change is logged for audit purposes. **Endpoint:** `GET /api/settings/history/user/:userId?limit=50` **Response:** + ```json { "success": true, @@ -347,6 +372,7 @@ Every settings change is logged for audit purposes. **Example:** `GET /api/settings/history/system/maintenanceMode?limit=10` **Response:** + ```json { "success": true, @@ -371,33 +397,33 @@ Every settings change is logged for audit purposes. ### User Settings -| Method | Endpoint | Description | Auth | -|--------|----------|-------------|------| -| GET | `/api/settings/user/:userId` | Get user settings | User | -| PUT | `/api/settings/user/:userId` | Update user settings | User | +| Method | Endpoint | Description | Auth | +| ------ | ---------------------------- | -------------------- | ---- | +| GET | `/api/settings/user/:userId` | Get user settings | User | +| PUT | `/api/settings/user/:userId` | Update user settings | User | | DELETE | `/api/settings/user/:userId` | Delete user settings | User | ### Preferences -| Method | Endpoint | Description | Auth | -|--------|----------|-------------|------| -| GET | `/api/settings/preferences/:userId` | Get preferences | User | -| PATCH | `/api/settings/preferences/:userId` | Update preferences | User | +| Method | Endpoint | Description | Auth | +| ------ | ----------------------------------- | ------------------ | ---- | +| GET | `/api/settings/preferences/:userId` | Get preferences | User | +| PATCH | `/api/settings/preferences/:userId` | Update preferences | User | ### System Settings -| Method | Endpoint | Description | Auth | -|--------|----------|-------------|------| -| GET | `/api/settings/system` | Get all system settings | Admin | -| GET | `/api/settings/system/:key` | Get specific setting | Admin | -| PUT | `/api/settings/system` | Update system settings | Admin | +| Method | Endpoint | Description | Auth | +| ------ | --------------------------- | ----------------------- | ----- | +| GET | `/api/settings/system` | Get all system settings | Admin | +| GET | `/api/settings/system/:key` | Get specific setting | Admin | +| PUT | `/api/settings/system` | Update system settings | Admin | ### History -| Method | Endpoint | Description | Auth | -|--------|----------|-------------|------| -| GET | `/api/settings/history/user/:userId` | Get user history | User | -| GET | `/api/settings/history/system/:key` | Get system history | Admin | +| Method | Endpoint | Description | Auth | +| ------ | ------------------------------------ | ------------------ | ----- | +| GET | `/api/settings/history/user/:userId` | Get user history | User | +| GET | `/api/settings/history/system/:key` | Get system history | Admin | --- @@ -414,11 +440,14 @@ const getUserSettings = async (userId: string) => { }; // Update theme to dark mode -const updateTheme = async (userId: string, theme: "light" | "dark" | "auto") => { +const updateTheme = async ( + userId: string, + theme: "light" | "dark" | "auto", +) => { const response = await fetch(`/api/settings/user/${userId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ theme }) + body: JSON.stringify({ theme }), }); return response.json(); }; @@ -428,7 +457,7 @@ const updateAutoSave = async (userId: string, interval: number) => { const response = await fetch(`/api/settings/preferences/${userId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ autoSaveInterval: interval }) + body: JSON.stringify({ autoSaveInterval: interval }), }); return response.json(); }; @@ -447,8 +476,8 @@ const enableMaintenance = async (adminEmail: string) => { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ maintenanceMode: true, - _updatedBy: adminEmail - }) + _updatedBy: adminEmail, + }), }); return response.json(); }; @@ -583,8 +612,12 @@ const requireAdmin = (req, res, next) => { }; // Apply to routes -router.get("/user/:userId", requireAuth, (req, res) => { /* ... */ }); -router.get("/system", requireAdmin, (req, res) => { /* ... */ }); +router.get("/user/:userId", requireAuth, (req, res) => { + /* ... */ +}); +router.get("/system", requireAdmin, (req, res) => { + /* ... */ +}); ``` ### Best Practices @@ -655,6 +688,7 @@ If user settings don't exist, they are automatically created with defaults on fi ### History Logging All changes are logged with: + - Old value (null for creation) - New value - Changed by (user ID or admin email) @@ -672,6 +706,7 @@ All endpoints return consistent error format: ``` HTTP status codes: + - 200: Success - 201: Created - 400: Bad request (validation error) diff --git a/docs/WEBSOCKET.md b/docs/WEBSOCKET.md index ebf0bc3..20880b9 100644 --- a/docs/WEBSOCKET.md +++ b/docs/WEBSOCKET.md @@ -7,14 +7,17 @@ The Code Cloud Agents system includes a WebSocket server for real-time bidirecti ## Connection ### URL + ``` ws://localhost:3000/ws?token=YOUR_AUTH_TOKEN ``` ### Authentication + Include your authentication token as a query parameter: + ```javascript -const ws = new WebSocket('ws://localhost:3000/ws?token=abc123'); +const ws = new WebSocket("ws://localhost:3000/ws?token=abc123"); ``` **Note:** In production, use proper JWT tokens. The current implementation uses simplified token-based auth for development. @@ -27,9 +30,9 @@ All messages are JSON objects with the following structure: ```typescript interface WSMessage { - type: string; // Message type - data?: any; // Message payload - timestamp?: string; // ISO 8601 timestamp (added by server) + type: string; // Message type + data?: any; // Message payload + timestamp?: string; // ISO 8601 timestamp (added by server) } ``` @@ -63,6 +66,7 @@ Sent immediately after connection to confirm authentication status. Keep-alive mechanism to maintain connection. **Client sends:** + ```json { "type": "ping" @@ -70,6 +74,7 @@ Keep-alive mechanism to maintain connection. ``` **Server responds:** + ```json { "type": "pong", @@ -78,6 +83,7 @@ Keep-alive mechanism to maintain connection. ``` **Auto-Heartbeat:** + - Server sends `ping` every 30 seconds - Client should respond with `pong` or send own `ping` - Clients that don't respond within 60 seconds are disconnected @@ -104,6 +110,7 @@ Real-time updates about agent state and progress. ``` **Agent States:** + - `idle` - Agent is ready for work - `working` - Agent is processing a task - `stopped` - Agent has stopped (e.g., STOP decision) @@ -150,6 +157,7 @@ System notifications (info, success, warning, error). ``` **Notification Levels:** + - `info` - Informational message - `success` - Success message - `warning` - Warning message @@ -164,6 +172,7 @@ System notifications (info, success, warning, error). Track user online/offline status. **Client sends:** + ```json { "type": "user_presence", @@ -172,6 +181,7 @@ Track user online/offline status. ``` **Server broadcasts:** + ```json { "type": "user_presence", @@ -184,6 +194,7 @@ Track user online/offline status. ``` **Presence Status:** + - `online` - User is active - `away` - User is idle - `busy` - User is busy (do not disturb) @@ -226,7 +237,7 @@ class CloudAgentsWebSocket { this.ws = new WebSocket(`ws://localhost:3000/ws?token=${this.token}`); this.ws.onopen = () => { - console.log('✅ WebSocket connected'); + console.log("✅ WebSocket connected"); this.startHeartbeat(); }; @@ -236,27 +247,27 @@ class CloudAgentsWebSocket { }; this.ws.onclose = () => { - console.log('🔌 WebSocket disconnected'); + console.log("🔌 WebSocket disconnected"); this.reconnect(); }; this.ws.onerror = (error) => { - console.error('WebSocket error:', error); + console.error("WebSocket error:", error); }; } handleMessage(message) { switch (message.type) { - case 'agent_status': + case "agent_status": this.onAgentStatus(message.data); break; - case 'chat_message': + case "chat_message": this.onChatMessage(message.data); break; - case 'notification': + case "notification": this.onNotification(message.data); break; - case 'user_presence': + case "user_presence": this.onUserPresence(message.data); break; } @@ -264,14 +275,14 @@ class CloudAgentsWebSocket { startHeartbeat() { this.heartbeatInterval = setInterval(() => { - this.send({ type: 'ping' }); + this.send({ type: "ping" }); }, 30000); } reconnect() { clearInterval(this.heartbeatInterval); setTimeout(() => { - console.log('🔄 Reconnecting...'); + console.log("🔄 Reconnecting..."); this.connect(); }, this.reconnectDelay); } @@ -284,16 +295,24 @@ class CloudAgentsWebSocket { setPresence(status) { this.send({ - type: 'user_presence', - data: status + type: "user_presence", + data: status, }); } // Override these in your app - onAgentStatus(data) { console.log('Agent status:', data); } - onChatMessage(data) { console.log('Chat message:', data); } - onNotification(data) { console.log('Notification:', data); } - onUserPresence(data) { console.log('User presence:', data); } + onAgentStatus(data) { + console.log("Agent status:", data); + } + onChatMessage(data) { + console.log("Chat message:", data); + } + onNotification(data) { + console.log("Notification:", data); + } + onUserPresence(data) { + console.log("User presence:", data); + } close() { clearInterval(this.heartbeatInterval); @@ -302,11 +321,11 @@ class CloudAgentsWebSocket { } // Usage -const client = new CloudAgentsWebSocket('your-token-here'); +const client = new CloudAgentsWebSocket("your-token-here"); // Custom handlers client.onAgentStatus = (data) => { - document.getElementById('agent-status').innerText = data.state; + document.getElementById("agent-status").innerText = data.state; }; client.onNotification = (data) => { @@ -405,27 +424,29 @@ function Dashboard() { ### Node.js Client ```javascript -const WebSocket = require('ws'); +const WebSocket = require("ws"); -const ws = new WebSocket('ws://localhost:3000/ws?token=test-token'); +const ws = new WebSocket("ws://localhost:3000/ws?token=test-token"); -ws.on('open', () => { - console.log('✅ Connected'); +ws.on("open", () => { + console.log("✅ Connected"); // Send presence - ws.send(JSON.stringify({ - type: 'user_presence', - data: 'online' - })); + ws.send( + JSON.stringify({ + type: "user_presence", + data: "online", + }), + ); }); -ws.on('message', (data) => { +ws.on("message", (data) => { const message = JSON.parse(data); - console.log('📨', message); + console.log("📨", message); }); -ws.on('close', () => { - console.log('🔌 Disconnected'); +ws.on("close", () => { + console.log("🔌 Disconnected"); }); ``` @@ -441,26 +462,26 @@ const wsManager = (global as any).wsManager; // Broadcast agent status to all authenticated clients wsManager.broadcastAgentStatus({ - agentName: 'CLOUD_ASSISTANT', - state: 'working', - currentTask: 'Analyzing code', - progress: 50 + agentName: "CLOUD_ASSISTANT", + state: "working", + currentTask: "Analyzing code", + progress: 50, }); // Broadcast chat message wsManager.broadcastChatMessage({ - conversationId: 'conv-123', - messageId: 'msg-456', - content: 'Analysis complete!', - sender: 'CLOUD_ASSISTANT', - timestamp: new Date().toISOString() + conversationId: "conv-123", + messageId: "msg-456", + content: "Analysis complete!", + sender: "CLOUD_ASSISTANT", + timestamp: new Date().toISOString(), }); // Send notification to all users -wsManager.sendNotification('success', 'All tests passed!'); +wsManager.sendNotification("success", "All tests passed!"); // Send notification to specific user -wsManager.sendNotification('error', 'Task failed', 'user-123'); +wsManager.sendNotification("error", "Task failed", "user-123"); ``` --- @@ -468,13 +489,16 @@ wsManager.sendNotification('error', 'Task failed', 'user-123'); ## Security ### Authentication + - Tokens should be JWT in production - Current implementation uses simplified token auth for development - Token is validated on connection - Unauthenticated clients can connect but won't receive sensitive updates ### Best Practices + 1. **Always use WSS (WebSocket Secure) in production** + ``` wss://your-domain.com/ws?token=... ``` @@ -499,11 +523,13 @@ wsManager.sendNotification('error', 'Task failed', 'user-123'); ### Connection Issues **Problem:** "Connection failed" + ``` Solution: Check that the server is running and WebSocket port is open ``` **Problem:** "Disconnects frequently" + ``` Solution: Ensure ping/pong heartbeat is working. Check network stability. ``` @@ -511,6 +537,7 @@ Solution: Ensure ping/pong heartbeat is working. Check network stability. ### Authentication Issues **Problem:** "Auth failed" + ``` Solution: Check token format and validity. Verify token is passed in URL query. ``` @@ -518,6 +545,7 @@ Solution: Check token format and validity. Verify token is passed in URL query. ### Message Delivery **Problem:** "Not receiving messages" + ``` Solution: Check that client is authenticated. Verify message filters. ``` @@ -533,7 +561,9 @@ Solution: Check that client is authenticated. Verify message filters. const totalClients = wsManager.getClientsCount(); const authenticatedClients = wsManager.getAuthenticatedClientsCount(); -console.log(`Connected: ${totalClients}, Authenticated: ${authenticatedClients}`); +console.log( + `Connected: ${totalClients}, Authenticated: ${authenticatedClients}`, +); ``` --- @@ -543,11 +573,13 @@ console.log(`Connected: ${totalClients}, Authenticated: ${authenticatedClients}` ### Manual Testing with wscat Install wscat: + ```bash npm install -g wscat ``` Connect and test: + ```bash # Connect wscat -c "ws://localhost:3000/ws?token=test-123" diff --git a/docs/api-client-setup.md b/docs/api-client-setup.md index d0d382d..6500aae 100644 --- a/docs/api-client-setup.md +++ b/docs/api-client-setup.md @@ -41,10 +41,10 @@ npm run generate:api ### 3. Frontend nutzt generated types ```tsx -import api from '@/lib/api-client'; +import api from "@/lib/api-client"; // Type-safe API call -const { data, error } = await api.GET('/api/tasks'); +const { data, error } = await api.GET("/api/tasks"); ``` --- @@ -54,13 +54,13 @@ const { data, error } = await api.GET('/api/tasks'); ### GET Request (List) ```tsx -import api from '@/lib/api-client'; +import api from "@/lib/api-client"; async function loadTasks() { - const { data, error } = await api.GET('/api/tasks'); + const { data, error } = await api.GET("/api/tasks"); if (error) { - console.error('Failed to load tasks'); + console.error("Failed to load tasks"); return; } @@ -71,19 +71,19 @@ async function loadTasks() { ### POST Request (Create) ```tsx -import api from '@/lib/api-client'; +import api from "@/lib/api-client"; async function createTask(title: string) { - const { data, error } = await api.POST('/api/tasks', { + const { data, error } = await api.POST("/api/tasks", { body: { title, - description: 'New task', - status: 'pending', + description: "New task", + status: "pending", }, }); if (error) { - throw new Error('Failed to create task'); + throw new Error("Failed to create task"); } return data; // Type: Task @@ -93,17 +93,17 @@ async function createTask(title: string) { ### DELETE Request ```tsx -import api from '@/lib/api-client'; +import api from "@/lib/api-client"; async function deleteTask(id: string) { - const { error } = await api.DELETE('/api/tasks/{id}', { + const { error } = await api.DELETE("/api/tasks/{id}", { params: { path: { id }, }, }); if (error) { - throw new Error('Failed to delete task'); + throw new Error("Failed to delete task"); } } ``` @@ -111,18 +111,18 @@ async function deleteTask(id: string) { ### With React State ```tsx -import { useEffect, useState } from 'react'; -import api from '@/lib/api-client'; -import type { components } from '@/generated/api-types'; +import { useEffect, useState } from "react"; +import api from "@/lib/api-client"; +import type { components } from "@/generated/api-types"; -type Task = components['schemas']['Task']; +type Task = components["schemas"]["Task"]; function TasksList() { const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { - api.GET('/api/tasks').then(({ data }) => { + api.GET("/api/tasks").then(({ data }) => { setTasks(data || []); setLoading(false); }); @@ -148,15 +148,15 @@ API client automatically adds `Authorization` header from localStorage: ```tsx // Login -const { data } = await api.POST('/api/auth/login', { +const { data } = await api.POST("/api/auth/login", { body: { email, password }, }); -localStorage.setItem('token', data.token); +localStorage.setItem("token", data.token); // Now all requests include: Authorization: Bearer // Logout -localStorage.removeItem('token'); +localStorage.removeItem("token"); ``` --- @@ -178,7 +178,7 @@ Edit `src/lib/api-client.ts`: ```tsx const customMiddleware: Middleware = { async onRequest({ request }) { - request.headers.set('X-Custom-Header', 'value'); + request.headers.set("X-Custom-Header", "value"); return request; }, }; diff --git a/docs/architecture/decision-flow.md b/docs/architecture/decision-flow.md index 9c4c0f6..64c5dd6 100644 --- a/docs/architecture/decision-flow.md +++ b/docs/architecture/decision-flow.md @@ -58,6 +58,7 @@ ## Decision Examples ### Example 1: Clean Submission + ``` Input: - Description: "Added user login endpoint" @@ -75,6 +76,7 @@ Final Status: COMPLETE ``` ### Example 2: Missing Tests + ``` Input: - Description: "Added payment processing" @@ -93,6 +95,7 @@ Required Action: Add missing: Tests, Deploy config ``` ### Example 3: Pricing Claim + ``` Input: - Description: "Set price to $99/month for premium tier" @@ -111,6 +114,7 @@ Required Action: STOP: Address PRICING_WITHOUT_FACT before proceeding ``` ### Example 4: Multiple Issues + ``` Input: - Description: "Deployed the API with legal disclaimer" diff --git a/docs/architecture/system-overview.md b/docs/architecture/system-overview.md index ef02084..abbee56 100644 --- a/docs/architecture/system-overview.md +++ b/docs/architecture/system-overview.md @@ -46,16 +46,19 @@ ## Key Principles ### 1. Evidence-Based Verification + - No claim accepted without proof - Every artefact must be verifiable - File paths, test outputs, configs required ### 2. STOP is Success + - When truth or safety at risk, STOP is correct - STOP decisions are never overridden by lower layers - Escalation path: Assistant → Lead → Meta → Human ### 3. Cross-Layer Consistency + - Frontend ↔ Backend alignment required - Backend ↔ Database schema match - Claims ↔ Artefacts correspondence @@ -63,18 +66,21 @@ ## Component Details ### Meta Supervisor + - Routes tasks to systems - Monitors health metrics - Aggregates costs and outcomes - Does NOT make business decisions ### Engineering Lead Supervisor + - Plans implementation approach - Delegates to Cloud Assistant - Verifies completed work - Makes APPROVE/STOP decisions ### Cloud Assistant + - Executes code changes - Collects evidence - Reports status honestly @@ -84,13 +90,13 @@ All integrations are **STUBS** until explicitly approved: -| Integration | Status | Notes | -|-------------|--------|-------| -| WhatsApp | STUB | Requires approval | -| Voice | STUB | Requires approval | -| Google | STUB | OAuth not configured | -| iCloud | STUB | Requires approval | -| Pinecone | STUB | Vector DB not connected | +| Integration | Status | Notes | +| ----------- | ------ | ----------------------- | +| WhatsApp | STUB | Requires approval | +| Voice | STUB | Requires approval | +| Google | STUB | OAuth not configured | +| iCloud | STUB | Requires approval | +| Pinecone | STUB | Vector DB not connected | ## Database Schema @@ -125,15 +131,15 @@ CREATE TABLE audit_entries ( ## API Endpoints -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/` | GET | Service info | -| `/health` | GET | Health check | -| `/health/ready` | GET | Readiness probe | -| `/health/live` | GET | Liveness probe | -| `/api/tasks` | GET | List tasks | -| `/api/tasks` | POST | Create task | -| `/api/tasks/:id` | GET | Get task | -| `/api/audit` | GET | List audit entries | -| `/api/audit/:id` | GET | Get audit entry | -| `/api/audit/stats/stop-scores` | GET | Stop score statistics | +| Endpoint | Method | Description | +| ------------------------------ | ------ | --------------------- | +| `/` | GET | Service info | +| `/health` | GET | Health check | +| `/health/ready` | GET | Readiness probe | +| `/health/live` | GET | Liveness probe | +| `/api/tasks` | GET | List tasks | +| `/api/tasks` | POST | Create task | +| `/api/tasks/:id` | GET | Get task | +| `/api/audit` | GET | List audit entries | +| `/api/audit/:id` | GET | Get audit entry | +| `/api/audit/stats/stop-scores` | GET | Stop score statistics | diff --git a/docs/otop-standard.md b/docs/otop-standard.md index 1aed462..490cea9 100644 --- a/docs/otop-standard.md +++ b/docs/otop-standard.md @@ -1,39 +1,46 @@ # OTOP Standard (Repo-weit) ## Ziel -1) **Backend-Funktionen** sind eindeutig & stabil referenzierbar (OpenAPI `operationId`). -2) **UI-Elemente** sind eindeutig & stabil referenzierbar (`data-otop-id` / `data-testid`). -3) **Verbindung** Frontend↔Backend ist robust (keine hardcoded URLs). + +1. **Backend-Funktionen** sind eindeutig & stabil referenzierbar (OpenAPI `operationId`). +2. **UI-Elemente** sind eindeutig & stabil referenzierbar (`data-otop-id` / `data-testid`). +3. **Verbindung** Frontend↔Backend ist robust (keine hardcoded URLs). --- ## 1) Backend Standard (OpenAPI = Function Registry) ### Pflicht + - Jede REST-Operation MUSS besitzen: - `operationId` (eindeutig innerhalb der Spec) - `tags` (mind. 1 Tag; dient als Gruppierung im Tool) ### operationId Konvention (deterministisch) + **Format:** `{tagSlug}_{method}_{pathSlug}` Beispiele: + - Tag: `Tasks`, Methode: `POST`, Pfad: `/tasks` → `tasks_post_tasks` - Tag: `Candidates`, Methode: `GET`, Pfad: `/candidates/{id}` → `candidates_get_candidates_id` **Regeln:** + - `tagSlug` = lower + `_` statt Leerzeichen - `pathSlug` = path ohne führenden `/`, `/`→`_`, `{id}`→`id` - Keine Sonderzeichen, nur `[a-z0-9_]` ### Tags Konvention + - Tags sollten “Menü-Struktur” im OTOP Tool abbilden: - `Auth`, `Agents`, `Tasks`, `CRM`, `Telephony`, `Admin`, `Utils` - Optional: “Substruktur” im Tag-Name: `CRM/Candidates`, `CRM/Jobs` ### Health/Ready (empfohlen) + - `/health` (liveness) - `/ready` (readiness; z.B. DB/Queue ready) @@ -42,19 +49,23 @@ Beispiele: ## 2) Frontend Standard (UI IDs = Link Targets) ### Web (React/Vite/Next) + Jede interaktive Komponente MUSS haben: + - `data-testid` - `data-otop-id` **ID Schema:** `{domain}.{entity}.{screen}.{component}.{action}` Beispiele: + - `crm.candidate.list.search.input` - `crm.candidate.list.create.button` - `crm.candidate.form.save.button` - `agents.task.detail.disable.toggle` **Beispiel:** + ```tsx
@@ -148,17 +148,17 @@ async function sendEmail() { try { const info = await transporter.sendMail({ from: '"Cloud Agents" ', - to: 'd.selmanovic@step2job.com', - subject: '🚀 Cloud Agents - Production Access & Test Guide', + to: "d.selmanovic@step2job.com", + subject: "🚀 Cloud Agents - Production Access & Test Guide", html: emailHTML, - text: 'Cloud Agents Production Access - Siehe HTML Version für vollständige Details.' + text: "Cloud Agents Production Access - Siehe HTML Version für vollständige Details.", }); - console.log('✅ E-Mail gesendet!'); - console.log('📧 Message ID:', info.messageId); - console.log('🔗 Preview URL:', nodemailer.getTestMessageUrl(info)); + console.log("✅ E-Mail gesendet!"); + console.log("📧 Message ID:", info.messageId); + console.log("🔗 Preview URL:", nodemailer.getTestMessageUrl(info)); } catch (error) { - console.error('❌ Fehler beim Senden:', error); + console.error("❌ Fehler beim Senden:", error); } } diff --git a/src/App.tsx b/src/App.tsx index 6631c16..3ebd283 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,26 @@ -'use client'; - -import { useState, useEffect } from 'react'; -import { LoginPage } from './components/LoginPage'; -import { ChatInterface } from './components/ChatInterface'; -import { TaskBoard } from './components/TaskBoard'; -import { ImageWithFallback } from './components/figma/ImageWithFallback'; -import { AgentCard } from './components/AgentCard'; -import { CreateAgentDialog } from './components/CreateAgentDialog'; -import { StatsCard } from './components/StatsCard'; -import { ActivityLog } from './components/ActivityLog'; -import { SettingsPanel } from './components/SettingsPanel'; -import { BrainMemoryPage } from './components/BrainMemoryPage'; -import { Button } from './components/ui/button'; -import { Input } from './components/ui/input'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from './components/ui/tabs'; +"use client"; + +import { useState, useEffect } from "react"; +import { LoginPage } from "./components/LoginPage"; +import { ChatInterface } from "./components/ChatInterface"; +import { TaskBoard } from "./components/TaskBoard"; +import { ImageWithFallback } from "./components/figma/ImageWithFallback"; +import { AgentCard } from "./components/AgentCard"; +import { CreateAgentDialog } from "./components/CreateAgentDialog"; +import { StatsCard } from "./components/StatsCard"; +import { ActivityLog } from "./components/ActivityLog"; +import { SettingsPanel } from "./components/SettingsPanel"; +import { BrainMemoryPage } from "./components/BrainMemoryPage"; +import { Button } from "./components/ui/button"; +import { Input } from "./components/ui/input"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from './components/ui/dropdown-menu'; -import { Sheet, SheetContent, SheetTrigger } from './components/ui/sheet'; +} from "./components/ui/dropdown-menu"; +import { Sheet, SheetContent, SheetTrigger } from "./components/ui/sheet"; import { Plus, Search, @@ -35,39 +35,39 @@ import { MessageSquare, CheckSquare, Brain, -} from 'lucide-react'; -import { toast } from 'sonner'; +} from "lucide-react"; +import { toast } from "sonner"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from './components/ui/dialog'; -import { Label } from './components/ui/label'; -import { Switch } from './components/ui/switch'; +} from "./components/ui/dialog"; +import { Label } from "./components/ui/label"; +import { Switch } from "./components/ui/switch"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from './components/ui/select'; -import { StatusDashboard } from './components/StatusDashboard'; +} from "./components/ui/select"; +import { StatusDashboard } from "./components/StatusDashboard"; // Reserved for future use export interface DailyTask { id: string; date: string; task: string; - status: 'completed' | 'failed' | 'in_progress'; + status: "completed" | "failed" | "in_progress"; } interface Agent { id: string; name: string; description: string; - status: 'active' | 'paused' | 'stopped'; + status: "active" | "paused" | "stopped"; language: string; lastRun: string; executionCount: number; @@ -78,153 +78,163 @@ interface LogEntry { timestamp: string; agent: string; message: string; - level: 'info' | 'warning' | 'error' | 'success'; + level: "info" | "warning" | "error" | "success"; } export default function App() { const [isAuthenticated, setIsAuthenticated] = useState(false); - const [_currentUser, setCurrentUser] = useState<{ id: string; email: string; name: string; role: string } | null>(null); + const [_currentUser, setCurrentUser] = useState<{ + id: string; + email: string; + name: string; + role: string; + } | null>(null); // All useState hooks MUST be before any conditional returns const [agents, setAgents] = useState([ { - id: '1', - name: 'Data Processor', - description: 'Processes and transforms incoming data streams', - status: 'active', - language: 'Python', - lastRun: '2 min ago', + id: "1", + name: "Data Processor", + description: "Processes and transforms incoming data streams", + status: "active", + language: "Python", + lastRun: "2 min ago", executionCount: 1247, }, { - id: '2', - name: 'API Monitor', - description: 'Monitors API endpoints for health and performance', - status: 'active', - language: 'TypeScript', - lastRun: '5 min ago', + id: "2", + name: "API Monitor", + description: "Monitors API endpoints for health and performance", + status: "active", + language: "TypeScript", + lastRun: "5 min ago", executionCount: 892, }, { - id: '3', - name: 'Backup Agent', - description: 'Automated backup and recovery system', - status: 'paused', - language: 'Go', - lastRun: '1 hour ago', + id: "3", + name: "Backup Agent", + description: "Automated backup and recovery system", + status: "paused", + language: "Go", + lastRun: "1 hour ago", executionCount: 456, }, { - id: '4', - name: 'Log Analyzer', - description: 'Analyzes logs for anomalies and patterns', - status: 'stopped', - language: 'Rust', - lastRun: '3 hours ago', + id: "4", + name: "Log Analyzer", + description: "Analyzes logs for anomalies and patterns", + status: "stopped", + language: "Rust", + lastRun: "3 hours ago", executionCount: 234, }, ]); const [logs, setLogs] = useState([ { - id: '1', - timestamp: '14:23:45', - agent: 'Data Processor', - message: 'Successfully processed batch of 1,000 records', - level: 'success', + id: "1", + timestamp: "14:23:45", + agent: "Data Processor", + message: "Successfully processed batch of 1,000 records", + level: "success", }, { - id: '2', - timestamp: '14:20:12', - agent: 'API Monitor', - message: 'All endpoints responding normally', - level: 'info', + id: "2", + timestamp: "14:20:12", + agent: "API Monitor", + message: "All endpoints responding normally", + level: "info", }, { - id: '3', - timestamp: '14:18:30', - agent: 'Data Processor', - message: 'High memory usage detected (85%)', - level: 'warning', + id: "3", + timestamp: "14:18:30", + agent: "Data Processor", + message: "High memory usage detected (85%)", + level: "warning", }, { - id: '4', - timestamp: '14:15:22', - agent: 'Backup Agent', - message: 'Scheduled backup completed successfully', - level: 'success', + id: "4", + timestamp: "14:15:22", + agent: "Backup Agent", + message: "Scheduled backup completed successfully", + level: "success", }, { - id: '5', - timestamp: '14:12:01', - agent: 'Log Analyzer', - message: 'Connection timeout to database', - level: 'error', + id: "5", + timestamp: "14:12:01", + agent: "Log Analyzer", + message: "Connection timeout to database", + level: "error", }, { - id: '6', - timestamp: '14:10:45', - agent: 'API Monitor', - message: 'Started health check cycle', - level: 'info', + id: "6", + timestamp: "14:10:45", + agent: "API Monitor", + message: "Started health check cycle", + level: "info", }, ]); const [dialogOpen, setDialogOpen] = useState(false); - const [searchQuery, setSearchQuery] = useState(''); - const [currentTab, setCurrentTab] = useState('dashboard'); + const [searchQuery, setSearchQuery] = useState(""); + const [currentTab, setCurrentTab] = useState("dashboard"); const [configureAgentId, setConfigureAgentId] = useState(null); const [agentSettings, setAgentSettings] = useState({ autoRestart: true, - logLevel: 'info', - maxRetries: '3', - timeout: '30', + logLevel: "info", + maxRetries: "3", + timeout: "30", }); // Check authentication on mount useEffect(() => { - const token = localStorage.getItem('token'); - const user = localStorage.getItem('user'); + const token = localStorage.getItem("token"); + const user = localStorage.getItem("user"); if (token && user) { try { setCurrentUser(JSON.parse(user)); setIsAuthenticated(true); } catch { - localStorage.removeItem('token'); - localStorage.removeItem('user'); + localStorage.removeItem("token"); + localStorage.removeItem("user"); } } }, []); - const handleLoginSuccess = (user: { id: string; email: string; name: string; role: string }) => { + const handleLoginSuccess = (user: { + id: string; + email: string; + name: string; + role: string; + }) => { setCurrentUser(user); setIsAuthenticated(true); }; const handleLogout = () => { - localStorage.removeItem('token'); - localStorage.removeItem('user'); + localStorage.removeItem("token"); + localStorage.removeItem("user"); setCurrentUser(null); setIsAuthenticated(false); - toast.success('Logged out successfully'); + toast.success("Logged out successfully"); }; const handleStartAgent = (id: string) => { setAgents( agents.map((agent) => - agent.id === id ? { ...agent, status: 'active' as const } : agent - ) + agent.id === id ? { ...agent, status: "active" as const } : agent, + ), ); const agent = agents.find((a) => a.id === id); toast.success(`${agent?.name} has been started`); - + const newLog: LogEntry = { id: Date.now().toString(), - timestamp: new Date().toLocaleTimeString('en-US', { hour12: false }), - agent: agent?.name || '', - message: 'Agent started successfully', - level: 'success', + timestamp: new Date().toLocaleTimeString("en-US", { hour12: false }), + agent: agent?.name || "", + message: "Agent started successfully", + level: "success", }; setLogs([newLog, ...logs]); }; @@ -232,18 +242,18 @@ export default function App() { const handlePauseAgent = (id: string) => { setAgents( agents.map((agent) => - agent.id === id ? { ...agent, status: 'paused' as const } : agent - ) + agent.id === id ? { ...agent, status: "paused" as const } : agent, + ), ); const agent = agents.find((a) => a.id === id); toast.info(`${agent?.name} has been paused`); - + const newLog: LogEntry = { id: Date.now().toString(), - timestamp: new Date().toLocaleTimeString('en-US', { hour12: false }), - agent: agent?.name || '', - message: 'Agent paused by user', - level: 'info', + timestamp: new Date().toLocaleTimeString("en-US", { hour12: false }), + agent: agent?.name || "", + message: "Agent paused by user", + level: "info", }; setLogs([newLog, ...logs]); }; @@ -263,13 +273,13 @@ export default function App() { const agent = agents.find((a) => a.id === id); setAgents(agents.filter((a) => a.id !== id)); toast.success(`${agent?.name} has been deleted`); - + const newLog: LogEntry = { id: Date.now().toString(), - timestamp: new Date().toLocaleTimeString('en-US', { hour12: false }), - agent: agent?.name || '', - message: 'Agent deleted by user', - level: 'warning', + timestamp: new Date().toLocaleTimeString("en-US", { hour12: false }), + agent: agent?.name || "", + message: "Agent deleted by user", + level: "warning", }; setLogs([newLog, ...logs]); }; @@ -284,20 +294,20 @@ export default function App() { id: Date.now().toString(), name: newAgent.name, description: newAgent.description, - status: 'stopped', + status: "stopped", language: newAgent.language, - lastRun: 'Never', + lastRun: "Never", executionCount: 0, }; setAgents([...agents, agent]); toast.success(`Agent "${newAgent.name}" created successfully`); - + const newLog: LogEntry = { id: Date.now().toString(), - timestamp: new Date().toLocaleTimeString('en-US', { hour12: false }), + timestamp: new Date().toLocaleTimeString("en-US", { hour12: false }), agent: newAgent.name, - message: 'New agent created and deployed', - level: 'success', + message: "New agent created and deployed", + level: "success", }; setLogs([newLog, ...logs]); }; @@ -305,11 +315,14 @@ export default function App() { const filteredAgents = agents.filter( (agent) => agent.name.toLowerCase().includes(searchQuery.toLowerCase()) || - agent.description.toLowerCase().includes(searchQuery.toLowerCase()) + agent.description.toLowerCase().includes(searchQuery.toLowerCase()), ); - const activeAgents = agents.filter((a) => a.status === 'active').length; - const totalExecutions = agents.reduce((acc, agent) => acc + agent.executionCount, 0); + const activeAgents = agents.filter((a) => a.status === "active").length; + const totalExecutions = agents.reduce( + (acc, agent) => acc + agent.executionCount, + 0, + ); // Show login page if not authenticated if (!isAuthenticated) { @@ -332,33 +345,33 @@ export default function App() { - +
@@ -395,7 +408,7 @@ export default function App() { Profile setCurrentTab('settings')} + onClick={() => setCurrentTab("settings")} data-testid="agents.navigation.settingsMenu.menuitem" data-otop-id="agents.navigation.settingsMenu.menuitem" > @@ -418,7 +431,11 @@ export default function App() { {/* Main Content */}
- + } /> } /> } />
@@ -536,12 +553,18 @@ export default function App() { {/* Task Management Section - Coming Soon */}
-

Top Priority Tasks

-

Task management coming soon...

+

+ Top Priority Tasks +

+

+ Task management coming soon... +

Repository Tasks

-

Repository integration coming soon...

+

+ Repository integration coming soon... +

@@ -601,8 +624,8 @@ export default function App() {

No agents found

{searchQuery - ? 'Try adjusting your search' - : 'Create your first agent to get started'} + ? "Try adjusting your search" + : "Create your first agent to get started"}

{!searchQuery && (
@@ -690,7 +719,10 @@ export default function App() { type="number" value={agentSettings.timeout} onChange={(e) => - setAgentSettings({ ...agentSettings, timeout: e.target.value }) + setAgentSettings({ + ...agentSettings, + timeout: e.target.value, + }) } /> diff --git a/src/Attributions.md b/src/Attributions.md index f444479..0514359 100644 --- a/src/Attributions.md +++ b/src/Attributions.md @@ -1,3 +1,3 @@ Diese Figma Make-Datei enthält Komponenten von [shadcn/ui](https://ui.shadcn.com/), verwendet unter [MIT-Lizenz](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md). -Diese Figma Make-Datei enthält Fotos von [Unsplash](https://unsplash.com), die unter [Lizenz](https://unsplash.com/license) verwendet werden. \ No newline at end of file +Diese Figma Make-Datei enthält Fotos von [Unsplash](https://unsplash.com), die unter [Lizenz](https://unsplash.com/license) verwendet werden. diff --git a/src/agents/agent-worker.ts b/src/agents/agent-worker.ts index 9768b5f..62e677f 100644 --- a/src/agents/agent-worker.ts +++ b/src/agents/agent-worker.ts @@ -4,12 +4,12 @@ * Automatic fallback: Claude → OpenAI when rate limited */ -import { EventEmitter } from 'events'; -import { taskQueue, type AgentTask, type CodeFile } from './task-queue.ts'; -import Anthropic from '@anthropic-ai/sdk'; -import OpenAI from 'openai'; -import * as fs from 'fs'; -import * as path from 'path'; +import { EventEmitter } from "events"; +import { taskQueue, type AgentTask, type CodeFile } from "./task-queue.ts"; +import Anthropic from "@anthropic-ai/sdk"; +import OpenAI from "openai"; +import * as fs from "fs"; +import * as path from "path"; interface AgentConfig { name: string; @@ -22,10 +22,10 @@ interface AgentConfig { const AGENT_CONFIGS: Record = { emir: { - name: 'Emir (Supervisor)', - role: 'Lead Supervisor', - model: 'claude-sonnet-4-20250514', - openaiModel: 'gpt-4o', + name: "Emir (Supervisor)", + role: "Lead Supervisor", + model: "claude-sonnet-4-20250514", + openaiModel: "gpt-4o", systemPrompt: `Du bist Emir, der Lead Supervisor des Cloud Agents Teams. Deine Aufgaben: @@ -41,13 +41,13 @@ Du hast folgende Agenten zur Verfügung: - Docs: Schreibt Dokumentation Antworte strukturiert mit klaren Aktionspunkten.`, - capabilities: ['delegate', 'plan', 'review', 'communicate'] + capabilities: ["delegate", "plan", "review", "communicate"], }, coder: { - name: 'Coder', - role: 'Developer', - model: 'claude-sonnet-4-20250514', - openaiModel: 'gpt-4o', + name: "Coder", + role: "Developer", + model: "claude-sonnet-4-20250514", + openaiModel: "gpt-4o", systemPrompt: `Du bist der Coder Agent, ein erfahrener Full-Stack Entwickler. Deine Aufgaben: @@ -69,13 +69,13 @@ export function formatDate(date: Date): string { \`\`\` Gib immer den vollständigen Dateipfad an.`, - capabilities: ['code', 'debug', 'implement', 'refactor'] + capabilities: ["code", "debug", "implement", "refactor"], }, tester: { - name: 'Tester', - role: 'QA Engineer', - model: 'claude-sonnet-4-20250514', - openaiModel: 'gpt-4o', + name: "Tester", + role: "QA Engineer", + model: "claude-sonnet-4-20250514", + openaiModel: "gpt-4o", systemPrompt: `Du bist der Tester Agent, spezialisiert auf Qualitätssicherung. Deine Aufgaben: @@ -85,13 +85,13 @@ Deine Aufgaben: - Dokumentiere Testergebnisse Nutze die gleiche Code-Formatierung wie der Coder Agent.`, - capabilities: ['test', 'qa', 'coverage', 'debug'] + capabilities: ["test", "qa", "coverage", "debug"], }, security: { - name: 'Security', - role: 'Security Analyst', - model: 'claude-sonnet-4-20250514', - openaiModel: 'gpt-4o', + name: "Security", + role: "Security Analyst", + model: "claude-sonnet-4-20250514", + openaiModel: "gpt-4o", systemPrompt: `Du bist der Security Agent, spezialisiert auf Sicherheit. Deine Aufgaben: @@ -101,13 +101,13 @@ Deine Aufgaben: - Dokumentiere Sicherheitsrisiken Kategorisiere Findings: CRITICAL, HIGH, MEDIUM, LOW`, - capabilities: ['security', 'audit', 'scan', 'report'] + capabilities: ["security", "audit", "scan", "report"], }, docs: { - name: 'Docs', - role: 'Technical Writer', - model: 'claude-sonnet-4-20250514', - openaiModel: 'gpt-4o', + name: "Docs", + role: "Technical Writer", + model: "claude-sonnet-4-20250514", + openaiModel: "gpt-4o", systemPrompt: `Du bist der Docs Agent, spezialisiert auf Dokumentation. Deine Aufgaben: @@ -117,8 +117,8 @@ Deine Aufgaben: - Schreibe Benutzeranleitungen Formatiere Dokumentation als Markdown.`, - capabilities: ['documentation', 'readme', 'api-docs', 'comments'] - } + capabilities: ["documentation", "readme", "api-docs", "comments"], + }, }; class AgentWorker extends EventEmitter { @@ -126,8 +126,8 @@ class AgentWorker extends EventEmitter { private currentTask: AgentTask | null = null; private anthropicClient: Anthropic | null = null; private openaiClient: OpenAI | null = null; - private projectRoot = '/root/cloud-agents'; - private currentProvider: 'anthropic' | 'openai' = 'anthropic'; + private projectRoot = "/root/cloud-agents"; + private currentProvider: "anthropic" | "openai" = "anthropic"; private clientsInitialized = false; constructor() { @@ -146,18 +146,18 @@ class AgentWorker extends EventEmitter { const anthropicKey = process.env.ANTHROPIC_API_KEY; if (anthropicKey) { this.anthropicClient = new Anthropic({ apiKey: anthropicKey }); - console.log('[AgentWorker] Anthropic client initialized'); + console.log("[AgentWorker] Anthropic client initialized"); } else { - console.warn('[AgentWorker] ANTHROPIC_API_KEY not set'); + console.warn("[AgentWorker] ANTHROPIC_API_KEY not set"); } // Initialize OpenAI client const openaiKey = process.env.OPENAI_API_KEY; if (openaiKey) { this.openaiClient = new OpenAI({ apiKey: openaiKey }); - console.log('[AgentWorker] OpenAI client initialized (fallback)'); + console.log("[AgentWorker] OpenAI client initialized (fallback)"); } else { - console.warn('[AgentWorker] OPENAI_API_KEY not set'); + console.warn("[AgentWorker] OPENAI_API_KEY not set"); } this.clientsInitialized = true; @@ -173,8 +173,8 @@ class AgentWorker extends EventEmitter { this.initClients(); this.isRunning = true; - console.log('[AgentWorker] Started'); - this.emit('worker:started'); + console.log("[AgentWorker] Started"); + this.emit("worker:started"); this.processLoop(); } @@ -183,8 +183,8 @@ class AgentWorker extends EventEmitter { */ stop() { this.isRunning = false; - console.log('[AgentWorker] Stopped'); - this.emit('worker:stopped'); + console.log("[AgentWorker] Stopped"); + this.emit("worker:stopped"); } /** @@ -201,8 +201,8 @@ class AgentWorker extends EventEmitter { id, name: config.name, role: config.role, - status: this.currentTask?.assignedAgent === id ? 'working' : 'idle' - })) + status: this.currentTask?.assignedAgent === id ? "working" : "idle", + })), }; } @@ -223,7 +223,7 @@ class AgentWorker extends EventEmitter { await this.sleep(2000); } } catch (error) { - console.error('[AgentWorker] Loop error:', error); + console.error("[AgentWorker] Loop error:", error); await this.sleep(5000); } } @@ -242,11 +242,11 @@ class AgentWorker extends EventEmitter { // Assign task taskQueue.assignTask(task.id, agentId); - this.emit('task:assigned', { task, agent: agentConfig.name }); + this.emit("task:assigned", { task, agent: agentConfig.name }); // Start task taskQueue.startTask(task.id); - this.emit('task:started', { task, agent: agentConfig.name }); + this.emit("task:started", { task, agent: agentConfig.name }); // Call AI with automatic fallback const result = await this.callAgentWithFallback(agentConfig, task); @@ -256,17 +256,16 @@ class AgentWorker extends EventEmitter { for (const file of codeFiles) { await this.writeCodeFile(file); taskQueue.addCodeFile(task.id, file); - this.emit('task:code', { task, file }); + this.emit("task:code", { task, file }); } // Complete task taskQueue.completeTask(task.id, result); - this.emit('task:completed', { task, result, codeFiles }); - + this.emit("task:completed", { task, result, codeFiles }); } catch (error) { - const errorMsg = error instanceof Error ? error.message : 'Unknown error'; + const errorMsg = error instanceof Error ? error.message : "Unknown error"; taskQueue.failTask(task.id, errorMsg); - this.emit('task:failed', { task, error: errorMsg }); + this.emit("task:failed", { task, error: errorMsg }); } finally { this.currentTask = null; } @@ -277,25 +276,28 @@ class AgentWorker extends EventEmitter { */ private selectAgent(task: AgentTask): string { switch (task.type) { - case 'code': - case 'refactor': - return 'coder'; - case 'test': - return 'tester'; - case 'review': - return 'security'; - case 'docs': - return 'docs'; + case "code": + case "refactor": + return "coder"; + case "test": + return "tester"; + case "review": + return "security"; + case "docs": + return "docs"; default: // Default to Emir for general tasks - return 'emir'; + return "emir"; } } /** * Call agent with automatic fallback from Claude to OpenAI */ - private async callAgentWithFallback(config: AgentConfig, task: AgentTask): Promise { + private async callAgentWithFallback( + config: AgentConfig, + task: AgentTask, + ): Promise { // Ensure clients are initialized this.initClients(); @@ -303,18 +305,26 @@ class AgentWorker extends EventEmitter { if (this.anthropicClient) { try { console.log(`[AgentWorker] Trying Anthropic for ${config.name}...`); - this.currentProvider = 'anthropic'; + this.currentProvider = "anthropic"; return await this.callAnthropic(config, task); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); console.warn(`[AgentWorker] Anthropic failed: ${errorMsg}`); // Check if it's a rate limit or quota error - if (errorMsg.includes('rate_limit') || errorMsg.includes('429') || errorMsg.includes('quota')) { - console.log('[AgentWorker] Anthropic rate limited, switching to OpenAI fallback...'); + if ( + errorMsg.includes("rate_limit") || + errorMsg.includes("429") || + errorMsg.includes("quota") + ) { + console.log( + "[AgentWorker] Anthropic rate limited, switching to OpenAI fallback...", + ); } else { // For other errors, still try OpenAI but log the error - console.log('[AgentWorker] Anthropic error, trying OpenAI as fallback...'); + console.log( + "[AgentWorker] Anthropic error, trying OpenAI as fallback...", + ); } } } @@ -323,7 +333,7 @@ class AgentWorker extends EventEmitter { if (this.openaiClient) { try { console.log(`[AgentWorker] Using OpenAI for ${config.name}...`); - this.currentProvider = 'openai'; + this.currentProvider = "openai"; return await this.callOpenAI(config, task); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); @@ -332,22 +342,37 @@ class AgentWorker extends EventEmitter { } } - throw new Error('No AI provider available. Configure ANTHROPIC_API_KEY or OPENAI_API_KEY'); + throw new Error( + "No AI provider available. Configure ANTHROPIC_API_KEY or OPENAI_API_KEY", + ); } /** * Call Anthropic Claude API */ - private async callAnthropic(config: AgentConfig, task: AgentTask): Promise { + private async callAnthropic( + config: AgentConfig, + task: AgentTask, + ): Promise { if (!this.anthropicClient) { - throw new Error('Anthropic client not initialized'); + throw new Error("Anthropic client not initialized"); } - console.log(`[AgentWorker] Calling Claude ${config.model} for task: ${task.title}`); + console.log( + `[AgentWorker] Calling Claude ${config.model} for task: ${task.title}`, + ); // Update progress - taskQueue.updateProgress(task.id, 10, `${config.name} analysiert Aufgabe (Claude)...`); - this.emit('progress', { task, progress: 10, message: `${config.name} analysiert Aufgabe (Claude)...` }); + taskQueue.updateProgress( + task.id, + 10, + `${config.name} analysiert Aufgabe (Claude)...`, + ); + this.emit("progress", { + task, + progress: 10, + message: `${config.name} analysiert Aufgabe (Claude)...`, + }); const response = await this.anthropicClient.messages.create({ model: config.model, @@ -355,7 +380,7 @@ class AgentWorker extends EventEmitter { system: config.systemPrompt, messages: [ { - role: 'user', + role: "user", content: `Aufgabe: ${task.title} Beschreibung: @@ -364,19 +389,27 @@ ${task.description} Bitte bearbeite diese Aufgabe vollständig. Wenn Code geschrieben werden soll, formatiere ihn als: \`\`\`language:pfad/zur/datei.ext // Code -\`\`\`` - } - ] +\`\`\``, + }, + ], }); // Update progress - taskQueue.updateProgress(task.id, 80, `${config.name} hat Lösung erstellt (Claude)`); - this.emit('progress', { task, progress: 80, message: `${config.name} hat Lösung erstellt (Claude)` }); + taskQueue.updateProgress( + task.id, + 80, + `${config.name} hat Lösung erstellt (Claude)`, + ); + this.emit("progress", { + task, + progress: 80, + message: `${config.name} hat Lösung erstellt (Claude)`, + }); // Extract text from response - let result = ''; + let result = ""; for (const block of response.content) { - if (block.type === 'text') { + if (block.type === "text") { result += block.text; } } @@ -387,27 +420,40 @@ Bitte bearbeite diese Aufgabe vollständig. Wenn Code geschrieben werden soll, f /** * Call OpenAI GPT API (fallback) */ - private async callOpenAI(config: AgentConfig, task: AgentTask): Promise { + private async callOpenAI( + config: AgentConfig, + task: AgentTask, + ): Promise { if (!this.openaiClient) { - throw new Error('OpenAI client not initialized'); + throw new Error("OpenAI client not initialized"); } - console.log(`[AgentWorker] Calling OpenAI ${config.openaiModel} for task: ${task.title}`); + console.log( + `[AgentWorker] Calling OpenAI ${config.openaiModel} for task: ${task.title}`, + ); // Update progress - taskQueue.updateProgress(task.id, 10, `${config.name} analysiert Aufgabe (OpenAI)...`); - this.emit('progress', { task, progress: 10, message: `${config.name} analysiert Aufgabe (OpenAI)...` }); + taskQueue.updateProgress( + task.id, + 10, + `${config.name} analysiert Aufgabe (OpenAI)...`, + ); + this.emit("progress", { + task, + progress: 10, + message: `${config.name} analysiert Aufgabe (OpenAI)...`, + }); const response = await this.openaiClient.chat.completions.create({ model: config.openaiModel, max_tokens: 4096, messages: [ { - role: 'system', - content: config.systemPrompt + role: "system", + content: config.systemPrompt, }, { - role: 'user', + role: "user", content: `Aufgabe: ${task.title} Beschreibung: @@ -416,16 +462,24 @@ ${task.description} Bitte bearbeite diese Aufgabe vollständig. Wenn Code geschrieben werden soll, formatiere ihn als: \`\`\`language:pfad/zur/datei.ext // Code -\`\`\`` - } - ] +\`\`\``, + }, + ], }); // Update progress - taskQueue.updateProgress(task.id, 80, `${config.name} hat Lösung erstellt (OpenAI)`); - this.emit('progress', { task, progress: 80, message: `${config.name} hat Lösung erstellt (OpenAI)` }); + taskQueue.updateProgress( + task.id, + 80, + `${config.name} hat Lösung erstellt (OpenAI)`, + ); + this.emit("progress", { + task, + progress: 80, + message: `${config.name} hat Lösung erstellt (OpenAI)`, + }); - return response.choices[0]?.message?.content || ''; + return response.choices[0]?.message?.content || ""; } /** @@ -447,7 +501,7 @@ Bitte bearbeite diese Aufgabe vollständig. Wenn Code geschrieben werden soll, f path: filePath, content: code, language, - action: 'create' + action: "create", }); } @@ -467,12 +521,12 @@ Bitte bearbeite diese Aufgabe vollständig. Wenn Code geschrieben werden soll, f } // Write file - fs.writeFileSync(fullPath, file.content, 'utf-8'); + fs.writeFileSync(fullPath, file.content, "utf-8"); console.log(`[AgentWorker] Wrote file: ${fullPath}`); } private sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } } diff --git a/src/agents/live-updates.ts b/src/agents/live-updates.ts index 01ef0b9..233abfb 100644 --- a/src/agents/live-updates.ts +++ b/src/agents/live-updates.ts @@ -3,9 +3,9 @@ * Broadcasts task and agent events to all connected clients */ -import type { WebSocketServer, WebSocket } from 'ws'; -import { taskQueue } from './task-queue.ts'; -import { agentWorker } from './agent-worker.ts'; +import type { WebSocketServer, WebSocket } from "ws"; +import { taskQueue } from "./task-queue.ts"; +import { agentWorker } from "./agent-worker.ts"; interface LiveMessage { type: string; @@ -24,7 +24,8 @@ function broadcast(message: LiveMessage) { const data = JSON.stringify(message); wss.clients.forEach((client: WebSocket) => { - if (client.readyState === 1) { // WebSocket.OPEN + if (client.readyState === 1) { + // WebSocket.OPEN client.send(data); } }); @@ -35,118 +36,129 @@ function broadcast(message: LiveMessage) { */ export function initializeLiveUpdates(wsServer: WebSocketServer) { wss = wsServer; - console.log('[LiveUpdates] Initializing...'); + console.log("[LiveUpdates] Initializing..."); // Subscribe to task queue events - taskQueue.on('task:created', (task) => { + taskQueue.on("task:created", (task) => { broadcast({ - type: 'task:created', + type: "task:created", data: task, - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }); }); - taskQueue.on('task:assigned', ({ task, agent }) => { + taskQueue.on("task:assigned", ({ task, agent }) => { broadcast({ - type: 'task:assigned', + type: "task:assigned", data: { taskId: task.id, title: task.title, agent }, - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }); }); - taskQueue.on('task:started', (task) => { + taskQueue.on("task:started", (task) => { broadcast({ - type: 'task:started', + type: "task:started", data: { taskId: task.id, title: task.title, agent: task.assignedAgent }, - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }); }); - taskQueue.on('task:progress', ({ task, progress, message }) => { + taskQueue.on("task:progress", ({ task, progress, message }) => { broadcast({ - type: 'task:progress', + type: "task:progress", data: { taskId: task.id, title: task.title, progress, message }, - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }); }); - taskQueue.on('task:completed', (task) => { + taskQueue.on("task:completed", (task) => { broadcast({ - type: 'task:completed', - data: { taskId: task.id, title: task.title, result: task.result?.substring(0, 200) }, - timestamp: new Date().toISOString() + type: "task:completed", + data: { + taskId: task.id, + title: task.title, + result: task.result?.substring(0, 200), + }, + timestamp: new Date().toISOString(), }); }); - taskQueue.on('task:failed', ({ task, error }) => { + taskQueue.on("task:failed", ({ task, error }) => { broadcast({ - type: 'task:failed', + type: "task:failed", data: { taskId: task.id, title: task.title, error }, - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }); }); - taskQueue.on('task:code', ({ task, file }) => { + taskQueue.on("task:code", ({ task, file }) => { broadcast({ - type: 'task:code', - data: { taskId: task.id, file: { path: file.path, language: file.language } }, - timestamp: new Date().toISOString() + type: "task:code", + data: { + taskId: task.id, + file: { path: file.path, language: file.language }, + }, + timestamp: new Date().toISOString(), }); }); - taskQueue.on('subtask:completed', ({ task, subtask }) => { + taskQueue.on("subtask:completed", ({ task, subtask }) => { broadcast({ - type: 'subtask:completed', - data: { taskId: task.id, subtaskId: subtask.id, subtaskTitle: subtask.title }, - timestamp: new Date().toISOString() + type: "subtask:completed", + data: { + taskId: task.id, + subtaskId: subtask.id, + subtaskTitle: subtask.title, + }, + timestamp: new Date().toISOString(), }); }); // Subscribe to agent worker events - agentWorker.on('worker:started', () => { + agentWorker.on("worker:started", () => { broadcast({ - type: 'worker:started', - data: { status: 'running' }, - timestamp: new Date().toISOString() + type: "worker:started", + data: { status: "running" }, + timestamp: new Date().toISOString(), }); }); - agentWorker.on('worker:stopped', () => { + agentWorker.on("worker:stopped", () => { broadcast({ - type: 'worker:stopped', - data: { status: 'stopped' }, - timestamp: new Date().toISOString() + type: "worker:stopped", + data: { status: "stopped" }, + timestamp: new Date().toISOString(), }); }); - agentWorker.on('progress', ({ task, progress, message }) => { + agentWorker.on("progress", ({ task, progress, message }) => { broadcast({ - type: 'agent:progress', + type: "agent:progress", data: { taskId: task.id, progress, message }, - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }); }); // Handle new WebSocket connections - wss.on('connection', (ws: WebSocket) => { - console.log('[LiveUpdates] Client connected'); + wss.on("connection", (ws: WebSocket) => { + console.log("[LiveUpdates] Client connected"); // Send current status on connect const status = { - type: 'status', + type: "status", data: { worker: agentWorker.getStatus(), tasks: taskQueue.getStats(), - allTasks: taskQueue.getAllTasks().slice(0, 10) + allTasks: taskQueue.getAllTasks().slice(0, 10), }, - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }; ws.send(JSON.stringify(status)); - ws.on('close', () => { - console.log('[LiveUpdates] Client disconnected'); + ws.on("close", () => { + console.log("[LiveUpdates] Client disconnected"); }); }); - console.log('[LiveUpdates] Initialized successfully'); + console.log("[LiveUpdates] Initialized successfully"); } diff --git a/src/agents/task-queue.ts b/src/agents/task-queue.ts index 460b57f..2105cbe 100644 --- a/src/agents/task-queue.ts +++ b/src/agents/task-queue.ts @@ -3,17 +3,17 @@ * Supports real-time updates via EventEmitter */ -import { EventEmitter } from 'events'; -import { randomUUID } from 'crypto'; +import { EventEmitter } from "events"; +import { randomUUID } from "crypto"; export interface AgentTask { id: string; title: string; description: string; - type: 'code' | 'review' | 'test' | 'docs' | 'refactor' | 'general'; - status: 'pending' | 'assigned' | 'in_progress' | 'completed' | 'failed'; + type: "code" | "review" | "test" | "docs" | "refactor" | "general"; + status: "pending" | "assigned" | "in_progress" | "completed" | "failed"; assignedAgent: string | null; - priority: 'low' | 'medium' | 'high' | 'urgent'; + priority: "low" | "medium" | "high" | "urgent"; progress: number; // 0-100 subtasks: SubTask[]; result: string | null; @@ -29,7 +29,7 @@ export interface AgentTask { export interface SubTask { id: string; title: string; - status: 'pending' | 'in_progress' | 'completed' | 'failed'; + status: "pending" | "in_progress" | "completed" | "failed"; result: string | null; } @@ -37,12 +37,12 @@ export interface CodeFile { path: string; content: string; language: string; - action: 'create' | 'modify' | 'delete'; + action: "create" | "modify" | "delete"; } class TaskQueue extends EventEmitter { private tasks: Map = new Map(); - + constructor() { super(); } @@ -53,8 +53,8 @@ class TaskQueue extends EventEmitter { createTask(params: { title: string; description: string; - type?: AgentTask['type']; - priority?: AgentTask['priority']; + type?: AgentTask["type"]; + priority?: AgentTask["priority"]; userId: string; chatId?: string; subtasks?: string[]; @@ -64,16 +64,16 @@ class TaskQueue extends EventEmitter { id: randomUUID(), title: params.title, description: params.description, - type: params.type || 'general', - status: 'pending', + type: params.type || "general", + status: "pending", assignedAgent: null, - priority: params.priority || 'medium', + priority: params.priority || "medium", progress: 0, - subtasks: (params.subtasks || []).map(title => ({ + subtasks: (params.subtasks || []).map((title) => ({ id: randomUUID(), title, - status: 'pending' as const, - result: null + status: "pending" as const, + result: null, })), result: null, error: null, @@ -82,11 +82,11 @@ class TaskQueue extends EventEmitter { completedAt: null, userId: params.userId, chatId: params.chatId || null, - codeFiles: [] + codeFiles: [], }; this.tasks.set(task.id, task); - this.emit('task:created', task); + this.emit("task:created", task); console.log(`[TaskQueue] Created task: ${task.id} - ${task.title}`); return task; } @@ -94,7 +94,11 @@ class TaskQueue extends EventEmitter { /** * Create task from chat message */ - createTaskFromChat(message: string, userId: string, chatId: string): AgentTask | null { + createTaskFromChat( + message: string, + userId: string, + chatId: string, + ): AgentTask | null { // Parse message to determine if it's a command // Command patterns const codePatterns = [ @@ -106,60 +110,61 @@ class TaskQueue extends EventEmitter { /write.*function/i, /implement/i, /add.*feature/i, - /füge?.*hinzu/i + /füge?.*hinzu/i, ]; - + const reviewPatterns = [ /review/i, /prüf(e|en)?/i, /check/i, - /analysier(e|en)?/i + /analysier(e|en)?/i, ]; - + const testPatterns = [ /test(e|en|s)?/i, /schreib(e|en)?.*test/i, - /write.*test/i + /write.*test/i, ]; - + const refactorPatterns = [ /refactor/i, /optimier(e|en)?/i, /verbessere?/i, /improve/i, - /cleanup/i + /cleanup/i, ]; - - let taskType: AgentTask['type'] = 'general'; - let priority: AgentTask['priority'] = 'medium'; - - if (codePatterns.some(p => p.test(message))) { - taskType = 'code'; - } else if (reviewPatterns.some(p => p.test(message))) { - taskType = 'review'; - } else if (testPatterns.some(p => p.test(message))) { - taskType = 'test'; - } else if (refactorPatterns.some(p => p.test(message))) { - taskType = 'refactor'; + + let taskType: AgentTask["type"] = "general"; + let priority: AgentTask["priority"] = "medium"; + + if (codePatterns.some((p) => p.test(message))) { + taskType = "code"; + } else if (reviewPatterns.some((p) => p.test(message))) { + taskType = "review"; + } else if (testPatterns.some((p) => p.test(message))) { + taskType = "test"; + } else if (refactorPatterns.some((p) => p.test(message))) { + taskType = "refactor"; } - + // Priority detection if (/dringend|urgent|asap|sofort|wichtig/i.test(message)) { - priority = 'urgent'; + priority = "urgent"; } else if (/wichtig|important|high/i.test(message)) { - priority = 'high'; + priority = "high"; } - + // Generate title from message - const title = message.length > 60 ? message.substring(0, 60) + '...' : message; - + const title = + message.length > 60 ? message.substring(0, 60) + "..." : message; + return this.createTask({ title, description: message, type: taskType, priority, userId, - chatId + chatId, }); } @@ -174,8 +179,10 @@ class TaskQueue extends EventEmitter { * Get all tasks */ getAllTasks(): AgentTask[] { - return Array.from(this.tasks.values()) - .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + return Array.from(this.tasks.values()).sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); } /** @@ -183,7 +190,7 @@ class TaskQueue extends EventEmitter { */ getPendingTasks(): AgentTask[] { return this.getAllTasks() - .filter(t => t.status === 'pending') + .filter((t) => t.status === "pending") .sort((a, b) => { const priorityOrder = { urgent: 0, high: 1, medium: 2, low: 3 }; return priorityOrder[a.priority] - priorityOrder[b.priority]; @@ -195,13 +202,13 @@ class TaskQueue extends EventEmitter { */ assignTask(taskId: string, agentName: string): boolean { const task = this.tasks.get(taskId); - if (!task || task.status !== 'pending') return false; + if (!task || task.status !== "pending") return false; task.assignedAgent = agentName; - task.status = 'assigned'; + task.status = "assigned"; task.updatedAt = new Date().toISOString(); - - this.emit('task:assigned', { task, agent: agentName }); + + this.emit("task:assigned", { task, agent: agentName }); console.log(`[TaskQueue] Assigned task ${taskId} to ${agentName}`); return true; } @@ -211,12 +218,12 @@ class TaskQueue extends EventEmitter { */ startTask(taskId: string): boolean { const task = this.tasks.get(taskId); - if (!task || task.status !== 'assigned') return false; + if (!task || task.status !== "assigned") return false; - task.status = 'in_progress'; + task.status = "in_progress"; task.updatedAt = new Date().toISOString(); - - this.emit('task:started', task); + + this.emit("task:started", task); console.log(`[TaskQueue] Started task ${taskId}`); return true; } @@ -230,8 +237,8 @@ class TaskQueue extends EventEmitter { task.progress = Math.min(100, Math.max(0, progress)); task.updatedAt = new Date().toISOString(); - - this.emit('task:progress', { task, progress, message }); + + this.emit("task:progress", { task, progress, message }); console.log(`[TaskQueue] Task ${taskId} progress: ${progress}%`); return true; } @@ -243,18 +250,20 @@ class TaskQueue extends EventEmitter { const task = this.tasks.get(taskId); if (!task) return false; - const subtask = task.subtasks.find(s => s.id === subtaskId); + const subtask = task.subtasks.find((s) => s.id === subtaskId); if (!subtask) return false; - subtask.status = 'completed'; + subtask.status = "completed"; subtask.result = result; - + // Update overall progress based on subtasks - const completedCount = task.subtasks.filter(s => s.status === 'completed').length; + const completedCount = task.subtasks.filter( + (s) => s.status === "completed", + ).length; task.progress = Math.round((completedCount / task.subtasks.length) * 100); task.updatedAt = new Date().toISOString(); - - this.emit('subtask:completed', { task, subtask }); + + this.emit("subtask:completed", { task, subtask }); return true; } @@ -267,8 +276,8 @@ class TaskQueue extends EventEmitter { task.codeFiles.push(file); task.updatedAt = new Date().toISOString(); - - this.emit('task:code', { task, file }); + + this.emit("task:code", { task, file }); return true; } @@ -279,20 +288,20 @@ class TaskQueue extends EventEmitter { const task = this.tasks.get(taskId); if (!task) return false; - task.status = 'completed'; + task.status = "completed"; task.progress = 100; task.result = result; task.completedAt = new Date().toISOString(); task.updatedAt = new Date().toISOString(); - + // Mark all subtasks as completed - task.subtasks.forEach(s => { - if (s.status !== 'completed') { - s.status = 'completed'; + task.subtasks.forEach((s) => { + if (s.status !== "completed") { + s.status = "completed"; } }); - - this.emit('task:completed', task); + + this.emit("task:completed", task); console.log(`[TaskQueue] Completed task ${taskId}`); return true; } @@ -304,11 +313,11 @@ class TaskQueue extends EventEmitter { const task = this.tasks.get(taskId); if (!task) return false; - task.status = 'failed'; + task.status = "failed"; task.error = error; task.updatedAt = new Date().toISOString(); - - this.emit('task:failed', { task, error }); + + this.emit("task:failed", { task, error }); console.log(`[TaskQueue] Failed task ${taskId}: ${error}`); return true; } @@ -320,11 +329,11 @@ class TaskQueue extends EventEmitter { const tasks = this.getAllTasks(); return { total: tasks.length, - pending: tasks.filter(t => t.status === 'pending').length, - assigned: tasks.filter(t => t.status === 'assigned').length, - inProgress: tasks.filter(t => t.status === 'in_progress').length, - completed: tasks.filter(t => t.status === 'completed').length, - failed: tasks.filter(t => t.status === 'failed').length + pending: tasks.filter((t) => t.status === "pending").length, + assigned: tasks.filter((t) => t.status === "assigned").length, + inProgress: tasks.filter((t) => t.status === "in_progress").length, + completed: tasks.filter((t) => t.status === "completed").length, + failed: tasks.filter((t) => t.status === "failed").length, }; } } diff --git a/src/api/agent-control.ts b/src/api/agent-control.ts index d883923..42c892d 100644 --- a/src/api/agent-control.ts +++ b/src/api/agent-control.ts @@ -14,7 +14,12 @@ import { z } from "zod"; import { requireAdmin } from "../auth/middleware.js"; // Agent types and statuses -export type AgentStatus = "online" | "offline" | "starting" | "stopping" | "error"; +export type AgentStatus = + | "online" + | "offline" + | "starting" + | "stopping" + | "error"; export interface Agent { id: number; @@ -33,99 +38,31 @@ export interface Agent { // In-memory agent store (in production, this would be in a database) const agents: Map = new Map([ - [0, { - id: 0, - name: "Agent 0", - role: "Lead Developer & Orchestrator", - status: "online", - currentTask: "Koordiniert Team, macht Code Reviews", - completedTasks: 13, - uptime: 86400, // 1 day - lastActivity: new Date().toISOString(), - metadata: { - version: "1.0.0", - capabilities: ["coordination", "code-review", "deployment", "architecture"], - }, - }], - [1, { - id: 1, - name: "Agent 1", - role: "Frontend Developer", - status: "offline", - currentTask: null, - completedTasks: 0, - uptime: 0, - lastActivity: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago - metadata: { - version: "1.0.0", - capabilities: ["react", "typescript", "ui-design", "testing"], - }, - }], - [2, { - id: 2, - name: "Agent 2", - role: "Security & Backend Infrastructure", - status: "offline", - currentTask: null, - completedTasks: 0, - uptime: 0, - lastActivity: new Date(Date.now() - 7200000).toISOString(), // 2 hours ago - metadata: { - version: "1.0.0", - capabilities: ["jwt", "authentication", "security", "infrastructure"], - }, - }], - [3, { - id: 3, - name: "Agent 3", - role: "Integrations & APIs", - status: "online", - currentTask: "Implementiert Agent Control API", - completedTasks: 2, - uptime: 3600, // 1 hour - lastActivity: new Date().toISOString(), - metadata: { - version: "1.0.0", - capabilities: ["github", "linear", "slack", "webhooks", "rest-api"], - }, - }], - [4, { - id: 4, - name: "Agent 4", - role: "Documentation & DevOps", - status: "offline", - currentTask: null, - completedTasks: 0, - uptime: 0, - lastActivity: new Date(Date.now() - 86400000).toISOString(), // 1 day ago - metadata: { - version: "1.0.0", - capabilities: ["swagger", "postman", "devops", "ci-cd"], - }, - }], -]); - -// Agent logs storage (in-memory for now) -const agentLogs: Map = new Map(); - -// Helper function to get default agents (for testing) -function getDefaultAgents(): Map { - return new Map([ - [0, { + [ + 0, + { id: 0, name: "Agent 0", role: "Lead Developer & Orchestrator", status: "online", currentTask: "Koordiniert Team, macht Code Reviews", completedTasks: 13, - uptime: 86400, + uptime: 86400, // 1 day lastActivity: new Date().toISOString(), metadata: { version: "1.0.0", - capabilities: ["coordination", "code-review", "deployment", "architecture"], + capabilities: [ + "coordination", + "code-review", + "deployment", + "architecture", + ], }, - }], - [1, { + }, + ], + [ + 1, + { id: 1, name: "Agent 1", role: "Frontend Developer", @@ -133,13 +70,16 @@ function getDefaultAgents(): Map { currentTask: null, completedTasks: 0, uptime: 0, - lastActivity: new Date(Date.now() - 3600000).toISOString(), + lastActivity: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago metadata: { version: "1.0.0", capabilities: ["react", "typescript", "ui-design", "testing"], }, - }], - [2, { + }, + ], + [ + 2, + { id: 2, name: "Agent 2", role: "Security & Backend Infrastructure", @@ -147,27 +87,33 @@ function getDefaultAgents(): Map { currentTask: null, completedTasks: 0, uptime: 0, - lastActivity: new Date(Date.now() - 7200000).toISOString(), + lastActivity: new Date(Date.now() - 7200000).toISOString(), // 2 hours ago metadata: { version: "1.0.0", capabilities: ["jwt", "authentication", "security", "infrastructure"], }, - }], - [3, { + }, + ], + [ + 3, + { id: 3, name: "Agent 3", role: "Integrations & APIs", status: "online", currentTask: "Implementiert Agent Control API", completedTasks: 2, - uptime: 3600, + uptime: 3600, // 1 hour lastActivity: new Date().toISOString(), metadata: { version: "1.0.0", capabilities: ["github", "linear", "slack", "webhooks", "rest-api"], }, - }], - [4, { + }, + ], + [ + 4, + { id: 4, name: "Agent 4", role: "Documentation & DevOps", @@ -175,12 +121,111 @@ function getDefaultAgents(): Map { currentTask: null, completedTasks: 0, uptime: 0, - lastActivity: new Date(Date.now() - 86400000).toISOString(), + lastActivity: new Date(Date.now() - 86400000).toISOString(), // 1 day ago metadata: { version: "1.0.0", capabilities: ["swagger", "postman", "devops", "ci-cd"], }, - }], + }, + ], +]); + +// Agent logs storage (in-memory for now) +const agentLogs: Map = new Map(); + +// Helper function to get default agents (for testing) +function getDefaultAgents(): Map { + return new Map([ + [ + 0, + { + id: 0, + name: "Agent 0", + role: "Lead Developer & Orchestrator", + status: "online", + currentTask: "Koordiniert Team, macht Code Reviews", + completedTasks: 13, + uptime: 86400, + lastActivity: new Date().toISOString(), + metadata: { + version: "1.0.0", + capabilities: [ + "coordination", + "code-review", + "deployment", + "architecture", + ], + }, + }, + ], + [ + 1, + { + id: 1, + name: "Agent 1", + role: "Frontend Developer", + status: "offline", + currentTask: null, + completedTasks: 0, + uptime: 0, + lastActivity: new Date(Date.now() - 3600000).toISOString(), + metadata: { + version: "1.0.0", + capabilities: ["react", "typescript", "ui-design", "testing"], + }, + }, + ], + [ + 2, + { + id: 2, + name: "Agent 2", + role: "Security & Backend Infrastructure", + status: "offline", + currentTask: null, + completedTasks: 0, + uptime: 0, + lastActivity: new Date(Date.now() - 7200000).toISOString(), + metadata: { + version: "1.0.0", + capabilities: ["jwt", "authentication", "security", "infrastructure"], + }, + }, + ], + [ + 3, + { + id: 3, + name: "Agent 3", + role: "Integrations & APIs", + status: "online", + currentTask: "Implementiert Agent Control API", + completedTasks: 2, + uptime: 3600, + lastActivity: new Date().toISOString(), + metadata: { + version: "1.0.0", + capabilities: ["github", "linear", "slack", "webhooks", "rest-api"], + }, + }, + ], + [ + 4, + { + id: 4, + name: "Agent 4", + role: "Documentation & DevOps", + status: "offline", + currentTask: null, + completedTasks: 0, + uptime: 0, + lastActivity: new Date(Date.now() - 86400000).toISOString(), + metadata: { + version: "1.0.0", + capabilities: ["swagger", "postman", "devops", "ci-cd"], + }, + }, + ], ]); } @@ -196,9 +241,7 @@ export function resetAgents(): void { // Initialize logs for all agents agents.forEach((_, id) => { - agentLogs.set(id, [ - `[${new Date().toISOString()}] Agent ${id} initialized`, - ]); + agentLogs.set(id, [`[${new Date().toISOString()}] Agent ${id} initialized`]); }); // Request validation schemas @@ -471,7 +514,9 @@ export function createAgentControlRouter(): Router { updatedAgent.lastActivity = new Date().toISOString(); const logs = agentLogs.get(agentId) || []; - logs.push(`[${new Date().toISOString()}] Agent restarted successfully`); + logs.push( + `[${new Date().toISOString()}] Agent restarted successfully`, + ); agentLogs.set(agentId, logs); } }, 2000); @@ -601,7 +646,7 @@ export function createAgentControlRouter(): Router { // Log action const logs = agentLogs.get(agentId) || []; logs.push( - `[${new Date().toISOString()}] Task assigned: ${parsed.data.task} (priority: ${parsed.data.priority || "medium"})` + `[${new Date().toISOString()}] Task assigned: ${parsed.data.task} (priority: ${parsed.data.priority || "medium"})`, ); agentLogs.set(agentId, logs); diff --git a/src/api/agent-tasks.ts b/src/api/agent-tasks.ts index 823bb5e..afecce9 100644 --- a/src/api/agent-tasks.ts +++ b/src/api/agent-tasks.ts @@ -2,10 +2,10 @@ * Agent Tasks API - REST endpoints for task management */ -import { Router } from 'express'; -import { taskQueue } from '../agents/task-queue.ts'; -import { agentWorker } from '../agents/agent-worker.ts'; -import { requireAuth } from '../auth/middleware.js'; +import { Router } from "express"; +import { taskQueue } from "../agents/task-queue.ts"; +import { agentWorker } from "../agents/agent-worker.ts"; +import { requireAuth } from "../auth/middleware.js"; export function createAgentTasksRouter(): Router { const router = Router(); @@ -16,16 +16,16 @@ export function createAgentTasksRouter(): Router { /** * GET /api/agent-tasks - Get all tasks */ - router.get('/', (_req, res) => { + router.get("/", (_req, res) => { try { const tasks = taskQueue.getAllTasks(); res.json({ tasks, count: tasks.length, - stats: taskQueue.getStats() + stats: taskQueue.getStats(), }); } catch { - res.status(500).json({ error: 'Failed to get tasks' }); + res.status(500).json({ error: "Failed to get tasks" }); } }); @@ -33,64 +33,66 @@ export function createAgentTasksRouter(): Router { * GET /api/agent-tasks/status/all - Get worker and task status * MUST be before /:id route! */ - router.get('/status/all', (_req, res) => { + router.get("/status/all", (_req, res) => { try { res.json({ worker: agentWorker.getStatus(), stats: taskQueue.getStats(), - recentTasks: taskQueue.getAllTasks().slice(0, 5) + recentTasks: taskQueue.getAllTasks().slice(0, 5), }); } catch { - res.status(500).json({ error: 'Failed to get status' }); + res.status(500).json({ error: "Failed to get status" }); } }); /** * POST /api/agent-tasks/worker/start - Start agent worker */ - router.post('/worker/start', (_req, res) => { + router.post("/worker/start", (_req, res) => { try { agentWorker.start(); - res.json({ success: true, message: 'Worker started' }); + res.json({ success: true, message: "Worker started" }); } catch { - res.status(500).json({ error: 'Failed to start worker' }); + res.status(500).json({ error: "Failed to start worker" }); } }); /** * POST /api/agent-tasks/worker/stop - Stop agent worker */ - router.post('/worker/stop', (_req, res) => { + router.post("/worker/stop", (_req, res) => { try { agentWorker.stop(); - res.json({ success: true, message: 'Worker stopped' }); + res.json({ success: true, message: "Worker stopped" }); } catch { - res.status(500).json({ error: 'Failed to stop worker' }); + res.status(500).json({ error: "Failed to stop worker" }); } }); /** * POST /api/agent-tasks - Create new task */ - router.post('/', (req, res) => { + router.post("/", (req, res) => { try { const { title, description, type, priority, userId } = req.body; if (!title || !description) { - return res.status(400).json({ error: 'Title and description required' }); + return res + .status(400) + .json({ error: "Title and description required" }); } const task = taskQueue.createTask({ title, description, - type: type || 'general', - priority: priority || 'medium', - userId: userId || req.headers['x-user-id'] as string || 'system' + type: type || "general", + priority: priority || "medium", + userId: userId || (req.headers["x-user-id"] as string) || "system", }); res.status(201).json(task); } catch { - res.status(500).json({ error: 'Failed to create task' }); + res.status(500).json({ error: "Failed to create task" }); } }); @@ -98,15 +100,15 @@ export function createAgentTasksRouter(): Router { * GET /api/agent-tasks/:id - Get single task * MUST be after specific routes like /status/all! */ - router.get('/:id', (req, res) => { + router.get("/:id", (req, res) => { try { const task = taskQueue.getTask(req.params.id); if (!task) { - return res.status(404).json({ error: 'Task not found' }); + return res.status(404).json({ error: "Task not found" }); } res.json(task); } catch { - res.status(500).json({ error: 'Failed to get task' }); + res.status(500).json({ error: "Failed to get task" }); } }); diff --git a/src/api/auth.ts b/src/api/auth.ts index 7def083..4c97124 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -24,73 +24,78 @@ export function createAuthRouter(): Router { * Login with email and password * Rate limited: 5 attempts per 15 minutes */ - router.post("/login", loginRateLimiter, async (req: Request, res: Response) => { - try { - const { email, password } = req.body; - - // Validation - if (!email || !password) { - return res.status(400).json({ - error: "Email and password required", + router.post( + "/login", + loginRateLimiter, + async (req: Request, res: Response) => { + try { + const { email, password } = req.body; + + // Validation + if (!email || !password) { + return res.status(400).json({ + error: "Email and password required", + }); + } + + // Verify user credentials against database + const rawDb = db.getRawDb(); + const user = await verifyUserPassword(rawDb, email, password); + + if (!user) { + return res.status(401).json({ + error: "Invalid credentials", + }); + } + + // Check if user is active + if (!user.isActive) { + return res.status(403).json({ + error: "Account deactivated", + message: + "Your account has been deactivated. Please contact support.", + }); + } + + // Generate tokens + const tokens = generateTokenPair({ + userId: user.id, + role: user.role, + email: user.email, }); - } - - // Verify user credentials against database - const rawDb = db.getRawDb(); - const user = await verifyUserPassword(rawDb, email, password); - if (!user) { - return res.status(401).json({ - error: "Invalid credentials", + // Log login event + db.audit.log({ + kind: "user_login", + message: `User ${user.email} logged in`, + userId: user.id, + severity: "info", + meta: { email: user.email, role: user.role }, }); - } - // Check if user is active - if (!user.isActive) { - return res.status(403).json({ - error: "Account deactivated", - message: "Your account has been deactivated. Please contact support.", + // Return tokens + res.json({ + success: true, + user: { + id: user.id, + email: user.email, + role: user.role, + displayName: user.displayName, + }, + tokens: { + accessToken: tokens.accessToken, + refreshToken: tokens.refreshToken, + expiresIn: tokens.expiresIn, + }, + }); + } catch (error) { + console.error("Login error:", error); + res.status(500).json({ + error: "Internal server error", }); } - - // Generate tokens - const tokens = generateTokenPair({ - userId: user.id, - role: user.role, - email: user.email, - }); - - // Log login event - db.audit.log({ - kind: "user_login", - message: `User ${user.email} logged in`, - userId: user.id, - severity: "info", - meta: { email: user.email, role: user.role }, - }); - - // Return tokens - res.json({ - success: true, - user: { - id: user.id, - email: user.email, - role: user.role, - displayName: user.displayName, - }, - tokens: { - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - expiresIn: tokens.expiresIn, - }, - }); - } catch (error) { - console.error("Login error:", error); - res.status(500).json({ - error: "Internal server error", - }); - } - }); + }, + ); /** * POST /api/auth/logout diff --git a/src/api/billing.ts b/src/api/billing.ts index 62131e9..4ab46a1 100644 --- a/src/api/billing.ts +++ b/src/api/billing.ts @@ -9,7 +9,11 @@ import { costTracker } from "../billing/costTracker.ts"; import { selectModel, compareCosts } from "../billing/modelSelector.ts"; import { MODEL_PRICING, formatCost } from "../billing/pricing.ts"; import type { UserLimit } from "../billing/types.ts"; -import { requireAdmin, requireAuth, type AuthenticatedRequest } from "../auth/middleware.ts"; +import { + requireAdmin, + requireAuth, + type AuthenticatedRequest, +} from "../auth/middleware.ts"; export function createBillingRouter(): Router { const router = Router(); @@ -18,260 +22,316 @@ export function createBillingRouter(): Router { * GET /api/billing/summary - Get cost summary for time period * Requires authentication */ - router.get("/summary", requireAuth, (req: AuthenticatedRequest, res: Response) => { - try { - const startDate = (req.query.start as string) || new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(); - const endDate = (req.query.end as string) || new Date().toISOString(); - - const summary = costTracker.getSummary(startDate, endDate); - - res.json(summary); - } catch (error) { - res.status(500).json({ - error: "Failed to get cost summary", - details: error instanceof Error ? error.message : String(error), - }); - } - }); + router.get( + "/summary", + requireAuth, + (req: AuthenticatedRequest, res: Response) => { + try { + const startDate = + (req.query.start as string) || + new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(); + const endDate = (req.query.end as string) || new Date().toISOString(); + + const summary = costTracker.getSummary(startDate, endDate); + + res.json(summary); + } catch (error) { + res.status(500).json({ + error: "Failed to get cost summary", + details: error instanceof Error ? error.message : String(error), + }); + } + }, + ); /** * GET /api/billing/usage/:userId - Get user usage for current month * Requires authentication */ - router.get("/usage/:userId", requireAuth, (req: AuthenticatedRequest, res: Response) => { - try { - const userId = req.params.userId; - const month = (req.query.month as string) || undefined; - - const usage = costTracker.getUserUsage(userId, month); - - if (!usage) { - return res.status(404).json({ error: "No usage found for user" }); + router.get( + "/usage/:userId", + requireAuth, + (req: AuthenticatedRequest, res: Response) => { + try { + const userId = req.params.userId; + const month = (req.query.month as string) || undefined; + + const usage = costTracker.getUserUsage(userId, month); + + if (!usage) { + return res.status(404).json({ error: "No usage found for user" }); + } + + res.json(usage); + } catch (error) { + res.status(500).json({ + error: "Failed to get user usage", + details: error instanceof Error ? error.message : String(error), + }); } - - res.json(usage); - } catch (error) { - res.status(500).json({ - error: "Failed to get user usage", - details: error instanceof Error ? error.message : String(error), - }); - } - }); + }, + ); /** * GET /api/billing/costs - Get all cost entries * Requires admin privileges */ - router.get("/costs", requireAdmin, (req: AuthenticatedRequest, res: Response) => { - try { - const limit = parseInt(req.query.limit as string) || 1000; - const costs = costTracker.getAllCosts(limit); - - res.json({ - total: costs.length, - costs, - }); - } catch (error) { - res.status(500).json({ - error: "Failed to get costs", - details: error instanceof Error ? error.message : String(error), - }); - } - }); + router.get( + "/costs", + requireAdmin, + (req: AuthenticatedRequest, res: Response) => { + try { + const limit = parseInt(req.query.limit as string) || 1000; + const costs = costTracker.getAllCosts(limit); + + res.json({ + total: costs.length, + costs, + }); + } catch (error) { + res.status(500).json({ + error: "Failed to get costs", + details: error instanceof Error ? error.message : String(error), + }); + } + }, + ); /** * POST /api/billing/log - Log a cost entry * Requires authentication */ - router.post("/log", requireAuth, (req: AuthenticatedRequest, res: Response) => { - try { - const entry = req.body; - - // Validate required fields - if (!entry.userId || !entry.model || !entry.provider) { - return res.status(400).json({ - error: "Missing required fields: userId, model, provider", - }); - } - - if (entry.inputTokens === undefined || entry.outputTokens === undefined) { - return res.status(400).json({ - error: "Missing token counts: inputTokens, outputTokens", + router.post( + "/log", + requireAuth, + (req: AuthenticatedRequest, res: Response) => { + try { + const entry = req.body; + + // Validate required fields + if (!entry.userId || !entry.model || !entry.provider) { + return res.status(400).json({ + error: "Missing required fields: userId, model, provider", + }); + } + + if ( + entry.inputTokens === undefined || + entry.outputTokens === undefined + ) { + return res.status(400).json({ + error: "Missing token counts: inputTokens, outputTokens", + }); + } + + const costEntry = costTracker.log(entry); + + res.status(201).json(costEntry); + } catch (error) { + res.status(500).json({ + error: "Failed to log cost", + details: error instanceof Error ? error.message : String(error), }); } - - const costEntry = costTracker.log(entry); - - res.status(201).json(costEntry); - } catch (error) { - res.status(500).json({ - error: "Failed to log cost", - details: error instanceof Error ? error.message : String(error), - }); - } - }); + }, + ); /** * GET /api/billing/limits - Get all user limits (Admin only) * Requires admin privileges */ - router.get("/limits", requireAdmin, (_req: AuthenticatedRequest, res: Response) => { - try { - const limits = costTracker.getAllLimits(); - - res.json({ - total: limits.length, - limits, - }); - } catch (error) { - res.status(500).json({ - error: "Failed to get limits", - details: error instanceof Error ? error.message : String(error), - }); - } - }); + router.get( + "/limits", + requireAdmin, + (_req: AuthenticatedRequest, res: Response) => { + try { + const limits = costTracker.getAllLimits(); + + res.json({ + total: limits.length, + limits, + }); + } catch (error) { + res.status(500).json({ + error: "Failed to get limits", + details: error instanceof Error ? error.message : String(error), + }); + } + }, + ); /** * GET /api/billing/limits/:userId - Get user limit * Requires authentication */ - router.get("/limits/:userId", requireAuth, (req: AuthenticatedRequest, res: Response) => { - try { - const limit = costTracker.getUserLimit(req.params.userId); - - if (!limit) { - return res.status(404).json({ error: "No limit set for user" }); + router.get( + "/limits/:userId", + requireAuth, + (req: AuthenticatedRequest, res: Response) => { + try { + const limit = costTracker.getUserLimit(req.params.userId); + + if (!limit) { + return res.status(404).json({ error: "No limit set for user" }); + } + + res.json(limit); + } catch (error) { + res.status(500).json({ + error: "Failed to get user limit", + details: error instanceof Error ? error.message : String(error), + }); } - - res.json(limit); - } catch (error) { - res.status(500).json({ - error: "Failed to get user limit", - details: error instanceof Error ? error.message : String(error), - }); - } - }); + }, + ); /** * POST /api/billing/limits - Set user limit (Admin only) * Requires admin privileges */ - router.post("/limits", requireAdmin, (req: AuthenticatedRequest, res: Response) => { - try { - const limitData: Omit = req.body; - - // Validate required fields - if (!limitData.userId || !limitData.monthlyLimitUSD || !limitData.currency) { - return res.status(400).json({ - error: "Missing required fields: userId, monthlyLimitUSD, currency", + router.post( + "/limits", + requireAdmin, + (req: AuthenticatedRequest, res: Response) => { + try { + const limitData: Omit = req.body; + + // Validate required fields + if ( + !limitData.userId || + !limitData.monthlyLimitUSD || + !limitData.currency + ) { + return res.status(400).json({ + error: "Missing required fields: userId, monthlyLimitUSD, currency", + }); + } + + const limit = costTracker.setUserLimit(limitData); + + res.status(201).json(limit); + } catch (error) { + res.status(500).json({ + error: "Failed to set user limit", + details: error instanceof Error ? error.message : String(error), }); } - - const limit = costTracker.setUserLimit(limitData); - - res.status(201).json(limit); - } catch (error) { - res.status(500).json({ - error: "Failed to set user limit", - details: error instanceof Error ? error.message : String(error), - }); - } - }); + }, + ); /** * DELETE /api/billing/limits/:userId - Remove user limit (Admin only) * Requires admin privileges */ - router.delete("/limits/:userId", requireAdmin, (req: AuthenticatedRequest, res: Response) => { - try { - const deleted = costTracker.removeUserLimit(req.params.userId); - - if (!deleted) { - return res.status(404).json({ error: "No limit found for user" }); + router.delete( + "/limits/:userId", + requireAdmin, + (req: AuthenticatedRequest, res: Response) => { + try { + const deleted = costTracker.removeUserLimit(req.params.userId); + + if (!deleted) { + return res.status(404).json({ error: "No limit found for user" }); + } + + res.status(204).send(); + } catch (error) { + res.status(500).json({ + error: "Failed to remove user limit", + details: error instanceof Error ? error.message : String(error), + }); } - - res.status(204).send(); - } catch (error) { - res.status(500).json({ - error: "Failed to remove user limit", - details: error instanceof Error ? error.message : String(error), - }); - } - }); + }, + ); /** * GET /api/billing/limits/:userId/status - Check limit status * Requires authentication */ - router.get("/limits/:userId/status", requireAuth, (req: AuthenticatedRequest, res: Response) => { - try { - const status = costTracker.checkLimit(req.params.userId); - - if (!status) { - return res.status(404).json({ error: "No limit set for user" }); + router.get( + "/limits/:userId/status", + requireAuth, + (req: AuthenticatedRequest, res: Response) => { + try { + const status = costTracker.checkLimit(req.params.userId); + + if (!status) { + return res.status(404).json({ error: "No limit set for user" }); + } + + res.json(status); + } catch (error) { + res.status(500).json({ + error: "Failed to check limit status", + details: error instanceof Error ? error.message : String(error), + }); } - - res.json(status); - } catch (error) { - res.status(500).json({ - error: "Failed to check limit status", - details: error instanceof Error ? error.message : String(error), - }); - } - }); + }, + ); /** * POST /api/billing/model-select - Get model recommendation * Requires authentication */ - router.post("/model-select", requireAuth, (req: AuthenticatedRequest, res: Response) => { - try { - const { taskDescription, preferLocal, maxCostUSD, forceModel } = req.body; + router.post( + "/model-select", + requireAuth, + (req: AuthenticatedRequest, res: Response) => { + try { + const { taskDescription, preferLocal, maxCostUSD, forceModel } = + req.body; + + if (!taskDescription) { + return res.status(400).json({ error: "Missing taskDescription" }); + } + + const recommendation = selectModel(taskDescription, { + preferLocal, + maxCostUSD, + forceModel, + }); - if (!taskDescription) { - return res.status(400).json({ error: "Missing taskDescription" }); + res.json(recommendation); + } catch (error) { + res.status(500).json({ + error: "Failed to select model", + details: error instanceof Error ? error.message : String(error), + }); } - - const recommendation = selectModel(taskDescription, { - preferLocal, - maxCostUSD, - forceModel, - }); - - res.json(recommendation); - } catch (error) { - res.status(500).json({ - error: "Failed to select model", - details: error instanceof Error ? error.message : String(error), - }); - } - }); + }, + ); /** * POST /api/billing/compare - Compare costs for different models * Requires authentication */ - router.post("/compare", requireAuth, (req: AuthenticatedRequest, res: Response) => { - try { - const { taskDescription, models } = req.body; - - if (!taskDescription || !models) { - return res.status(400).json({ error: "Missing taskDescription or models" }); + router.post( + "/compare", + requireAuth, + (req: AuthenticatedRequest, res: Response) => { + try { + const { taskDescription, models } = req.body; + + if (!taskDescription || !models) { + return res + .status(400) + .json({ error: "Missing taskDescription or models" }); + } + + const comparison = compareCosts(taskDescription, models); + + res.json({ + taskDescription, + recommendations: comparison, + }); + } catch (error) { + res.status(500).json({ + error: "Failed to compare costs", + details: error instanceof Error ? error.message : String(error), + }); } - - const comparison = compareCosts(taskDescription, models); - - res.json({ - taskDescription, - recommendations: comparison, - }); - } catch (error) { - res.status(500).json({ - error: "Failed to compare costs", - details: error instanceof Error ? error.message : String(error), - }); - } - }); + }, + ); /** * GET /api/billing/pricing - Get all model pricing diff --git a/src/api/brain.ts b/src/api/brain.ts index 27e8843..8ed2425 100644 --- a/src/api/brain.ts +++ b/src/api/brain.ts @@ -106,7 +106,11 @@ export function createBrainRouter(db: Database): Router { message: `Document "${doc.title}" ingested`, userId, severity: "info", - meta: { docId: doc.id, sourceType: doc.sourceType, contentLength: parsed.data.content.length }, + meta: { + docId: doc.id, + sourceType: doc.sourceType, + contentLength: parsed.data.content.length, + }, }); res.status(201).json({ @@ -152,7 +156,7 @@ export function createBrainRouter(db: Database): Router { url: parsed.data.url, metadata: parsed.data.metadata, }, - parsed.data.content + parsed.data.content, ); // Generate embeddings in background @@ -280,7 +284,7 @@ export function createBrainRouter(db: Database): Router { // Log brain search event (POST) db.audit.log({ kind: "brain_search", - message: `Search: "${query.substring(0, 50)}${query.length > 50 ? '...' : ''}"`, + message: `Search: "${query.substring(0, 50)}${query.length > 50 ? "..." : ""}"`, userId, severity: "info", meta: { query, mode, resultCount: results.length }, @@ -355,7 +359,7 @@ export function createBrainRouter(db: Database): Router { // Log brain search event (GET) db.audit.log({ kind: "brain_search", - message: `Search: "${query.substring(0, 50)}${query.length > 50 ? '...' : ''}"`, + message: `Search: "${query.substring(0, 50)}${query.length > 50 ? "..." : ""}"`, userId, severity: "info", meta: { query, mode, resultCount: results.length }, @@ -397,7 +401,12 @@ export function createBrainRouter(db: Database): Router { const sourceType = req.query.sourceType as string; const status = req.query.status as string; - const docs = brainManager.listDocs(userId, { limit, offset, sourceType, status }); + const docs = brainManager.listDocs(userId, { + limit, + offset, + sourceType, + status, + }); res.json({ success: true, diff --git a/src/api/chat.ts b/src/api/chat.ts index 4ff40d4..8df8000 100644 --- a/src/api/chat.ts +++ b/src/api/chat.ts @@ -40,7 +40,7 @@ export function createChatRouter(chatManager: ChatManager): Router { const task = taskQueue.createTaskFromChat( request.message, request.userId, - response.chatId + response.chatId, ); if (task) { console.log("[Chat] Created task from message:", task.id); @@ -57,19 +57,25 @@ export function createChatRouter(chatManager: ChatManager): Router { * Get chat history * Requires authentication */ - router.get("/:chatId/messages", requireAuth, (req: Request, res: Response) => { - try { - const { chatId } = req.params; - const limit = parseInt(req.query.limit as string) || 50; - const offset = parseInt(req.query.offset as string) || 0; + router.get( + "/:chatId/messages", + requireAuth, + (req: Request, res: Response) => { + try { + const { chatId } = req.params; + const limit = parseInt(req.query.limit as string) || 50; + const offset = parseInt(req.query.offset as string) || 0; - const history = chatManager.getChatHistory(chatId, limit, offset); - res.json(history); - } catch (error: any) { - console.error("Get chat history error:", error); - res.status(500).json({ error: error.message || "Internal server error" }); - } - }); + const history = chatManager.getChatHistory(chatId, limit, offset); + res.json(history); + } catch (error: any) { + console.error("Get chat history error:", error); + res + .status(500) + .json({ error: error.message || "Internal server error" }); + } + }, + ); /** * GET /api/chat/list/:userId @@ -138,31 +144,40 @@ export function createChatRouter(chatManager: ChatManager): Router { { name: "emir", displayName: "Emir (Supervisor)", - description: "Lead supervisor, coordinates all agents and makes final decisions", + description: + "Lead supervisor, coordinates all agents and makes final decisions", capabilities: ["planning", "review", "coordination", "decision-making"], }, { name: "planner", displayName: "Planner", - description: "Creates detailed project plans with requirements and architecture", + description: + "Creates detailed project plans with requirements and architecture", capabilities: ["planning", "architecture", "requirements"], }, { name: "berater", displayName: "Berater (Consultant)", - description: "Project intake specialist, asks precise questions about requirements", - capabilities: ["consultation", "requirements-gathering", "risk-analysis"], + description: + "Project intake specialist, asks precise questions about requirements", + capabilities: [ + "consultation", + "requirements-gathering", + "risk-analysis", + ], }, { name: "designer", displayName: "Designer", - description: "UI/UX designer, creates design concepts and ensures accessibility", + description: + "UI/UX designer, creates design concepts and ensures accessibility", capabilities: ["ui-design", "ux-design", "accessibility"], }, { name: "coder", displayName: "Coder", - description: "Implements features according to plan and design specifications", + description: + "Implements features according to plan and design specifications", capabilities: ["coding", "implementation", "debugging"], }, { @@ -175,12 +190,17 @@ export function createChatRouter(chatManager: ChatManager): Router { name: "security", displayName: "Security", description: "Security review specialist, checks for vulnerabilities", - capabilities: ["security-review", "vulnerability-scanning", "secure-coding"], + capabilities: [ + "security-review", + "vulnerability-scanning", + "secure-coding", + ], }, { name: "docs", displayName: "Docs", - description: "Technical writer, creates README, guides, and API documentation", + description: + "Technical writer, creates README, guides, and API documentation", capabilities: ["documentation", "technical-writing", "api-docs"], }, ]; @@ -188,7 +208,6 @@ export function createChatRouter(chatManager: ChatManager): Router { res.json({ agents }); }); - // =============================== // Chat Threads API (for Dashboard) // =============================== @@ -199,7 +218,10 @@ export function createChatRouter(chatManager: ChatManager): Router { */ router.get("/threads", requireAuth, (req: Request, res: Response) => { try { - const userId = (req as any).user?.id || (req as any).user?.userId || (req as any).userId; + const userId = + (req as any).user?.id || + (req as any).user?.userId || + (req as any).userId; if (!userId) { return res.status(401).json({ error: "Authentication required" }); } @@ -207,8 +229,9 @@ export function createChatRouter(chatManager: ChatManager): Router { const limit = parseInt(req.query.limit as string) || 50; const page = parseInt(req.query.page as string) || 1; - const result = chatManager.listChats(userId, page, limit); const chats = result.chats || []; - + const result = chatManager.listChats(userId, page, limit); + const chats = result.chats || []; + const threads = chats.map((chat: any) => ({ id: chat.id, title: chat.title, @@ -233,7 +256,10 @@ export function createChatRouter(chatManager: ChatManager): Router { */ router.post("/threads", requireAuth, (req: Request, res: Response) => { try { - const userId = (req as any).user?.id || (req as any).user?.userId || (req as any).userId; + const userId = + (req as any).user?.id || + (req as any).user?.userId || + (req as any).userId; if (!userId) { return res.status(401).json({ error: "Authentication required" }); } @@ -251,7 +277,7 @@ export function createChatRouter(chatManager: ChatManager): Router { agentId: chat.agentName || agentId, userId: chat.userId || userId, createdAt: chat.createdAt || new Date().toISOString(), - } + }, }); } catch (error: any) { console.error("Create thread error:", error); @@ -263,32 +289,42 @@ export function createChatRouter(chatManager: ChatManager): Router { * GET /api/chat/threads/:id/messages * Get messages for a specific thread */ - router.get("/threads/:id/messages", requireAuth, (req: Request, res: Response) => { - try { - const userId = (req as any).user?.id || (req as any).user?.userId || (req as any).userId; - if (!userId) { - return res.status(401).json({ error: "Authentication required" }); - } + router.get( + "/threads/:id/messages", + requireAuth, + (req: Request, res: Response) => { + try { + const userId = + (req as any).user?.id || + (req as any).user?.userId || + (req as any).userId; + if (!userId) { + return res.status(401).json({ error: "Authentication required" }); + } - const threadId = req.params.id; - const limit = parseInt(req.query.limit as string) || 100; - const offset = parseInt(req.query.offset as string) || 0; - - const history = chatManager.getChatHistory(threadId, limit, offset); - - const messages = (history.messages || []).map((msg: any) => ({ - id: msg.id, - role: msg.role, - content: msg.content, - timestamp: msg.timestamp || msg.created_at || new Date().toISOString(), - })); + const threadId = req.params.id; + const limit = parseInt(req.query.limit as string) || 100; + const offset = parseInt(req.query.offset as string) || 0; - res.json({ success: true, messages }); - } catch (error: any) { - console.error("Get thread messages error:", error); - res.status(500).json({ error: error.message || "Internal server error" }); - } - }); + const history = chatManager.getChatHistory(threadId, limit, offset); + + const messages = (history.messages || []).map((msg: any) => ({ + id: msg.id, + role: msg.role, + content: msg.content, + timestamp: + msg.timestamp || msg.created_at || new Date().toISOString(), + })); + + res.json({ success: true, messages }); + } catch (error: any) { + console.error("Get thread messages error:", error); + res + .status(500) + .json({ error: error.message || "Internal server error" }); + } + }, + ); return router; } diff --git a/src/api/demo.ts b/src/api/demo.ts index e14e734..a9439f9 100644 --- a/src/api/demo.ts +++ b/src/api/demo.ts @@ -7,15 +7,23 @@ import { Router, type Request, type Response } from "express"; import bcrypt from "bcrypt"; import { DemoInviteManager } from "../demo/inviteManager.ts"; -import type { CreateInviteRequest, RedeemInviteRequest } from "../demo/types.ts"; +import type { + CreateInviteRequest, + RedeemInviteRequest, +} from "../demo/types.ts"; import type { Database } from "../db/database.ts"; -import { requireAdmin, requireCronAuth, type AuthenticatedRequest, createRateLimiter } from "../auth/middleware.ts"; +import { + requireAdmin, + requireCronAuth, + type AuthenticatedRequest, + createRateLimiter, +} from "../auth/middleware.ts"; export function createDemoRouter(db: Database): Router { const router = Router(); const demoManager = new DemoInviteManager(db); const redeemLimiter = createRateLimiter(5, 60000); - + // Initialize demo tables demoManager.initTables(); @@ -23,53 +31,69 @@ export function createDemoRouter(db: Database): Router { * POST /api/demo/invites * Erstellt einen neuen Demo-Invite (Admin only) */ - router.post("/invites", requireAdmin, (req: AuthenticatedRequest, res: Response) => { - try { - const adminUserId = req.userId!; + router.post( + "/invites", + requireAdmin, + (req: AuthenticatedRequest, res: Response) => { + try { + const adminUserId = req.userId!; - const request: CreateInviteRequest = req.body; + const request: CreateInviteRequest = req.body; - // Validate - if (!request.creditLimitUSD || request.creditLimitUSD <= 0) { - return res.status(400).json({ error: "creditLimitUSD must be positive" }); - } - if (!request.maxMessages || request.maxMessages <= 0) { - return res.status(400).json({ error: "maxMessages must be positive" }); - } - if (!request.maxDays || request.maxDays <= 0) { - return res.status(400).json({ error: "maxDays must be positive" }); - } + // Validate + if (!request.creditLimitUSD || request.creditLimitUSD <= 0) { + return res + .status(400) + .json({ error: "creditLimitUSD must be positive" }); + } + if (!request.maxMessages || request.maxMessages <= 0) { + return res + .status(400) + .json({ error: "maxMessages must be positive" }); + } + if (!request.maxDays || request.maxDays <= 0) { + return res.status(400).json({ error: "maxDays must be positive" }); + } - const invite = demoManager.createInvite(adminUserId, request); + const invite = demoManager.createInvite(adminUserId, request); - res.status(201).json({ - success: true, - invite, - inviteUrl: `${req.protocol}://${req.get("host")}/demo/register?code=${invite.code}`, - }); - } catch (error: any) { - console.error("Create demo invite error:", error); - res.status(500).json({ error: error.message || "Failed to create invite" }); - } - }); + res.status(201).json({ + success: true, + invite, + inviteUrl: `${req.protocol}://${req.get("host")}/demo/register?code=${invite.code}`, + }); + } catch (error: any) { + console.error("Create demo invite error:", error); + res + .status(500) + .json({ error: error.message || "Failed to create invite" }); + } + }, + ); /** * GET /api/demo/invites * Listet alle Demo-Invites (Admin only) */ - router.get("/invites", requireAdmin, (_req: AuthenticatedRequest, res: Response) => { - try { - const invites = demoManager.listInvites(); + router.get( + "/invites", + requireAdmin, + (_req: AuthenticatedRequest, res: Response) => { + try { + const invites = demoManager.listInvites(); - res.json({ - invites, - total: invites.length, - }); - } catch (error: any) { - console.error("List invites error:", error); - res.status(500).json({ error: error.message || "Failed to list invites" }); - } - }); + res.json({ + invites, + total: invites.length, + }); + } catch (error: any) { + console.error("List invites error:", error); + res + .status(500) + .json({ error: error.message || "Failed to list invites" }); + } + }, + ); /** * GET /api/demo/invites/:code @@ -86,7 +110,8 @@ export function createDemoRouter(db: Database): Router { // Nur public info zurückgeben const isActive = invite.status === "active"; - const notExpired = !invite.expiresAt || new Date(invite.expiresAt) > new Date(); + const notExpired = + !invite.expiresAt || new Date(invite.expiresAt) > new Date(); res.json({ code: invite.code, creditLimitUSD: invite.creditLimitUSD, @@ -118,7 +143,9 @@ export function createDemoRouter(db: Database): Router { return res.status(400).json({ error: "email is required" }); } if (!request.password || request.password.length < 8) { - return res.status(400).json({ error: "password must be at least 8 characters" }); + return res + .status(400) + .json({ error: "password must be at least 8 characters" }); } // Hash password with bcrypt (salt rounds: 10) @@ -140,7 +167,9 @@ export function createDemoRouter(db: Database): Router { }); } catch (error: any) { console.error("Redeem invite error:", error); - res.status(400).json({ error: error.message || "Failed to redeem invite" }); + res + .status(400) + .json({ error: error.message || "Failed to redeem invite" }); } }); @@ -171,7 +200,9 @@ export function createDemoRouter(db: Database): Router { }); } catch (error: any) { console.error("Get demo user error:", error); - res.status(500).json({ error: error.message || "Failed to get demo user" }); + res + .status(500) + .json({ error: error.message || "Failed to get demo user" }); } }); @@ -179,41 +210,53 @@ export function createDemoRouter(db: Database): Router { * DELETE /api/demo/invites/:inviteId * Deaktiviert einen Invite (Admin only) */ - router.delete("/invites/:inviteId", requireAdmin, (req: Request, res: Response) => { - try { - const { inviteId } = req.params; + router.delete( + "/invites/:inviteId", + requireAdmin, + (req: Request, res: Response) => { + try { + const { inviteId } = req.params; - demoManager.deactivateInvite(inviteId); + demoManager.deactivateInvite(inviteId); - res.json({ - success: true, - message: "Invite deactivated", - }); - } catch (error: any) { - console.error("Deactivate invite error:", error); - res.status(500).json({ error: error.message || "Failed to deactivate invite" }); - } - }); + res.json({ + success: true, + message: "Invite deactivated", + }); + } catch (error: any) { + console.error("Deactivate invite error:", error); + res + .status(500) + .json({ error: error.message || "Failed to deactivate invite" }); + } + }, + ); /** * DELETE /api/demo/users/:userId * Deaktiviert einen Demo-User (Admin only) */ - router.delete("/users/:userId", requireAdmin, (req: Request, res: Response) => { - try { - const { userId } = req.params; + router.delete( + "/users/:userId", + requireAdmin, + (req: Request, res: Response) => { + try { + const { userId } = req.params; - demoManager.deactivateDemoUser(userId); + demoManager.deactivateDemoUser(userId); - res.json({ - success: true, - message: "Demo user deactivated", - }); - } catch (error: any) { - console.error("Deactivate demo user error:", error); - res.status(500).json({ error: error.message || "Failed to deactivate demo user" }); - } - }); + res.json({ + success: true, + message: "Demo user deactivated", + }); + } catch (error: any) { + console.error("Deactivate demo user error:", error); + res + .status(500) + .json({ error: error.message || "Failed to deactivate demo user" }); + } + }, + ); /** * GET /api/demo/stats @@ -234,20 +277,26 @@ export function createDemoRouter(db: Database): Router { * POST /api/demo/cron/expire * Deaktiviert abgelaufene Demo-User (Cron-Job) */ - router.post("/cron/expire", requireCronAuth, (_req: Request, res: Response) => { - try { - const expiredCount = demoManager.expireOldDemoUsers(); + router.post( + "/cron/expire", + requireCronAuth, + (_req: Request, res: Response) => { + try { + const expiredCount = demoManager.expireOldDemoUsers(); - res.json({ - success: true, - expiredCount, - message: `${expiredCount} demo users expired`, - }); - } catch (error: any) { - console.error("Expire demo users error:", error); - res.status(500).json({ error: error.message || "Failed to expire users" }); - } - }); + res.json({ + success: true, + expiredCount, + message: `${expiredCount} demo users expired`, + }); + } catch (error: any) { + console.error("Expire demo users error:", error); + res + .status(500) + .json({ error: error.message || "Failed to expire users" }); + } + }, + ); return router; } diff --git a/src/api/email-verification.ts b/src/api/email-verification.ts index e5c4c4e..9f51c65 100644 --- a/src/api/email-verification.ts +++ b/src/api/email-verification.ts @@ -24,49 +24,54 @@ export function createEmailVerificationRouter(db: Database): Router { * Send verification email (authenticated users only) * Rate limited: 3 attempts per hour */ - router.post("/send", emailVerificationRateLimiter, requireAuth, async (req: AuthenticatedRequest, res: Response) => { - try { - const userId = req.userId!; - const user = getUserById(rawDb, userId); - - if (!user) { - return res.status(404).json({ - error: "User not found", - }); - } - - if (user.emailVerified) { - return res.status(400).json({ - error: "Email already verified", + router.post( + "/send", + emailVerificationRateLimiter, + requireAuth, + async (req: AuthenticatedRequest, res: Response) => { + try { + const userId = req.userId!; + const user = getUserById(rawDb, userId); + + if (!user) { + return res.status(404).json({ + error: "User not found", + }); + } + + if (user.emailVerified) { + return res.status(400).json({ + error: "Email already verified", + }); + } + + // Generate new verification token + const token = generateVerificationToken(rawDb, userId, user.email); + + // Send verification email + const emailResult = await sendVerificationEmail(user.email, token); + + if (!emailResult.success) { + return res.status(500).json({ + error: "Failed to send verification email", + details: emailResult.error, + }); + } + + res.json({ + success: true, + message: "Verification email sent successfully", + // Include preview URL for development (Ethereal) + ...(emailResult.previewUrl && { previewUrl: emailResult.previewUrl }), }); - } - - // Generate new verification token - const token = generateVerificationToken(rawDb, userId, user.email); - - // Send verification email - const emailResult = await sendVerificationEmail(user.email, token); - - if (!emailResult.success) { - return res.status(500).json({ + } catch (error) { + console.error("[Email Verification] Send error:", error); + res.status(500).json({ error: "Failed to send verification email", - details: emailResult.error, }); } - - res.json({ - success: true, - message: "Verification email sent successfully", - // Include preview URL for development (Ethereal) - ...(emailResult.previewUrl && { previewUrl: emailResult.previewUrl }), - }); - } catch (error) { - console.error("[Email Verification] Send error:", error); - res.status(500).json({ - error: "Failed to send verification email", - }); - } - }); + }, + ); /** * POST /api/email-verification/verify @@ -110,33 +115,37 @@ export function createEmailVerificationRouter(db: Database): Router { * GET /api/email-verification/status * Check verification status */ - router.get("/status", requireAuth, async (req: AuthenticatedRequest, res: Response) => { - try { - const userId = req.userId!; - const user = getUserById(rawDb, userId); - - if (!user) { - return res.status(404).json({ - error: "User not found", + router.get( + "/status", + requireAuth, + async (req: AuthenticatedRequest, res: Response) => { + try { + const userId = req.userId!; + const user = getUserById(rawDb, userId); + + if (!user) { + return res.status(404).json({ + error: "User not found", + }); + } + + const pendingToken = getVerificationToken(rawDb, userId); + + res.json({ + success: true, + emailVerified: user.emailVerified, + email: user.email, + hasPendingToken: !!pendingToken, + tokenExpiresAt: pendingToken?.expiresAt, + }); + } catch (error) { + console.error("[Email Verification] Status error:", error); + res.status(500).json({ + error: "Failed to get verification status", }); } - - const pendingToken = getVerificationToken(rawDb, userId); - - res.json({ - success: true, - emailVerified: user.emailVerified, - email: user.email, - hasPendingToken: !!pendingToken, - tokenExpiresAt: pendingToken?.expiresAt, - }); - } catch (error) { - console.error("[Email Verification] Status error:", error); - res.status(500).json({ - error: "Failed to get verification status", - }); - } - }); + }, + ); return router; } diff --git a/src/api/enforcement.ts b/src/api/enforcement.ts index ac959e7..d0ca991 100644 --- a/src/api/enforcement.ts +++ b/src/api/enforcement.ts @@ -8,13 +8,18 @@ import { Router } from "express"; import { z } from "zod"; import type { EnforcementGate } from "../audit/enforcementGate.js"; -import { verifyClaims, generateVerificationReport } from "../audit/claimVerifier.js"; +import { + verifyClaims, + generateVerificationReport, +} from "../audit/claimVerifier.js"; import { requireAdmin } from "../auth/middleware.ts"; const ApproveSchema = z.object({ approver: z.string().min(1, "Approver identity required"), reason: z.string().min(10, "Reason must be at least 10 characters"), - acknowledged_risks: z.array(z.string()).min(1, "Must acknowledge at least one risk"), + acknowledged_risks: z + .array(z.string()) + .min(1, "Must acknowledge at least one risk"), }); const RejectSchema = z.object({ @@ -40,9 +45,10 @@ export function createEnforcementRouter(gate: EnforcementGate): Router { res.json({ blocked_count: pending.length, tasks: pending, - message: pending.length > 0 - ? "⚠️ Tasks blocked. Human approval required." - : "✅ No blocked tasks.", + message: + pending.length > 0 + ? "⚠️ Tasks blocked. Human approval required." + : "✅ No blocked tasks.", }); }); @@ -96,13 +102,14 @@ export function createEnforcementRouter(gate: EnforcementGate): Router { req.params.taskId, parsed.data.approver, parsed.data.reason, - parsed.data.acknowledged_risks + parsed.data.acknowledged_risks, ); res.json({ status: "APPROVED", decision, - warning: "⚠️ Task approved despite STOP condition. This has been logged.", + warning: + "⚠️ Task approved despite STOP condition. This has been logged.", }); } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; diff --git a/src/api/github.ts b/src/api/github.ts index e13f2bb..dfef438 100644 --- a/src/api/github.ts +++ b/src/api/github.ts @@ -61,7 +61,8 @@ export function createGitHubRouter(): Router { if (!githubClient.isEnabled()) { return res.status(200).json({ connected: false, - error: "GitHub integration is disabled. Set GITHUB_ENABLED=true in .env", + error: + "GitHub integration is disabled. Set GITHUB_ENABLED=true in .env", }); } diff --git a/src/api/linear.ts b/src/api/linear.ts index 27b929d..de4975d 100644 --- a/src/api/linear.ts +++ b/src/api/linear.ts @@ -73,7 +73,8 @@ export function createLinearRouter(): Router { if (!linearClient.isEnabled()) { return res.status(200).json({ connected: false, - error: "Linear integration is disabled. Set LINEAR_ENABLED=true in .env", + error: + "Linear integration is disabled. Set LINEAR_ENABLED=true in .env", }); } @@ -181,7 +182,7 @@ export function createLinearRouter(): Router { : null, createdAt: issue.createdAt, updatedAt: issue.updatedAt, - })) + })), ); res.json({ @@ -304,7 +305,7 @@ export function createLinearRouter(): Router { createdAt: project.createdAt, updatedAt: project.updatedAt, }; - }) + }), ); res.json({ diff --git a/src/api/memory.ts b/src/api/memory.ts index ad09bd4..7c08d91 100644 --- a/src/api/memory.ts +++ b/src/api/memory.ts @@ -261,9 +261,11 @@ export function createMemoryRouter(db: Database): Router { // Generate embedding in background (non-blocking) if (embeddingsManager.isEnabled()) { - embeddingsManager.storeEmbedding(message.id, message.content).catch((error) => { - console.error("Failed to generate embedding:", error); - }); + embeddingsManager + .storeEmbedding(message.id, message.content) + .catch((error) => { + console.error("Failed to generate embedding:", error); + }); } res.status(201).json({ @@ -491,7 +493,11 @@ export function createMemoryRouter(db: Database): Router { }); } - const results = await embeddingsManager.semanticSearch(query, userId, limit || 10); + const results = await embeddingsManager.semanticSearch( + query, + userId, + limit || 10, + ); res.json({ success: true, diff --git a/src/api/modules.ts b/src/api/modules.ts index 9c92936..ecd5c1e 100644 --- a/src/api/modules.ts +++ b/src/api/modules.ts @@ -5,7 +5,13 @@ */ import { Router, type Request, type Response } from "express"; -import { MODULES, getModule, getModulesByCategory, getModulesByStatus, getCategories } from "../modules/moduleRegistry.ts"; +import { + MODULES, + getModule, + getModulesByCategory, + getModulesByStatus, + getCategories, +} from "../modules/moduleRegistry.ts"; import { getStatusBadge } from "../modules/moduleStatus.ts"; import type { ModuleStatusReport } from "../modules/moduleStatus.ts"; import { requireAuth } from "../auth/middleware.js"; diff --git a/src/api/ops.ts b/src/api/ops.ts index ac02537..31a46a7 100644 --- a/src/api/ops.ts +++ b/src/api/ops.ts @@ -14,7 +14,13 @@ import { requireAuth, requireAdmin } from "../auth/middleware.js"; */ export interface OpsEvent { id: string; - type: "task_created" | "task_started" | "task_completed" | "task_stopped" | "audit_decision" | "agent_activity"; + type: + | "task_created" + | "task_started" + | "task_completed" + | "task_stopped" + | "audit_decision" + | "agent_activity"; timestamp: string; agentId?: string; taskId?: string; @@ -66,7 +72,9 @@ export function createOpsRouter(db: Database): Router { const eventType = req.query.type as string | undefined; // Default: last 24 hours - const defaultSince = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); + const defaultSince = new Date( + Date.now() - 24 * 60 * 60 * 1000, + ).toISOString(); const since = (req.query.since as string) || defaultSince; const events: OpsEvent[] = []; @@ -97,7 +105,10 @@ export function createOpsRouter(db: Database): Router { // Task completed/stopped event (if updated) if (task.updated_at && task.updated_at >= since) { - if (task.status === "completed" && (!eventType || eventType === "task_completed")) { + if ( + task.status === "completed" && + (!eventType || eventType === "task_completed") + ) { events.push({ id: `task-completed-${task.id}`, type: "task_completed", @@ -108,7 +119,10 @@ export function createOpsRouter(db: Database): Router { status: task.status, stopScore: task.stop_score, }); - } else if (task.status === "stopped" && (!eventType || eventType === "task_stopped")) { + } else if ( + task.status === "stopped" && + (!eventType || eventType === "task_stopped") + ) { events.push({ id: `task-stopped-${task.id}`, type: "task_stopped", @@ -152,7 +166,10 @@ export function createOpsRouter(db: Database): Router { } // Sort by timestamp (newest first) - events.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + events.sort( + (a, b) => + new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(), + ); // Apply limit const limitedEvents = events.slice(0, limit); @@ -292,13 +309,17 @@ export function createOpsRouter(db: Database): Router { const stopScoreStats = db.getStopScoreStats(); // Agent activity (group by assignee) - const agentActivity: Record = {}; + const agentActivity: Record< + string, + { total: number; completed: number; stopped: number } + > = {}; for (const task of allTasks) { if (!agentActivity[task.assignee]) { agentActivity[task.assignee] = { total: 0, completed: 0, stopped: 0 }; } agentActivity[task.assignee].total++; - if (task.status === "completed") agentActivity[task.assignee].completed++; + if (task.status === "completed") + agentActivity[task.assignee].completed++; if (task.status === "stopped") agentActivity[task.assignee].stopped++; } diff --git a/src/api/password-reset.ts b/src/api/password-reset.ts index 66851b0..3ed51c3 100644 --- a/src/api/password-reset.ts +++ b/src/api/password-reset.ts @@ -45,60 +45,70 @@ export function createPasswordResetRouter(db: Database): Router { * Request password reset (generates token) * Rate limited: 3 attempts per hour */ - router.post("/request", passwordResetRateLimiter, async (req: Request, res: Response) => { - try { - const { email } = req.body as PasswordResetRequestBody; - - if (!email) { - return res.status(400).json({ - error: "Email required", - }); - } - - // Find user by email - const user = getUserByEmail(rawDb, email); - - // Security: Always return success even if user doesn't exist - // This prevents user enumeration attacks - if (!user) { - return res.json({ + router.post( + "/request", + passwordResetRateLimiter, + async (req: Request, res: Response) => { + try { + const { email } = req.body as PasswordResetRequestBody; + + if (!email) { + return res.status(400).json({ + error: "Email required", + }); + } + + // Find user by email + const user = getUserByEmail(rawDb, email); + + // Security: Always return success even if user doesn't exist + // This prevents user enumeration attacks + if (!user) { + return res.json({ + success: true, + message: + "If an account with that email exists, a password reset link has been sent.", + }); + } + + // Check if user is active + if (!user.isActive) { + return res.json({ + success: true, + message: + "If an account with that email exists, a password reset link has been sent.", + }); + } + + // Generate reset token + const token = generatePasswordResetToken(rawDb, user.id, user.email); + + // Send password reset email + const emailResult = await sendPasswordResetEmail(user.email, token); + + if (!emailResult.success) { + // Don't reveal email send failure to prevent user enumeration + console.error( + "[Password Reset] Email send failed:", + emailResult.error, + ); + } + + res.json({ success: true, - message: "If an account with that email exists, a password reset link has been sent.", + message: + "If an account with that email exists, a password reset link has been sent.", + // Include preview URL for development (Ethereal) + ...(emailResult.previewUrl && { previewUrl: emailResult.previewUrl }), }); - } - - // Check if user is active - if (!user.isActive) { - return res.json({ - success: true, - message: "If an account with that email exists, a password reset link has been sent.", + } catch (error) { + console.error("[Password Reset] Request error:", error); + res.status(500).json({ + error: "Failed to process password reset request", }); } - - // Generate reset token - const token = generatePasswordResetToken(rawDb, user.id, user.email); - - // Send password reset email - const emailResult = await sendPasswordResetEmail(user.email, token); - - if (!emailResult.success) { - // Don't reveal email send failure to prevent user enumeration - console.error("[Password Reset] Email send failed:", emailResult.error); - } - - res.json({ - success: true, - message: "If an account with that email exists, a password reset link has been sent.", - // Include preview URL for development (Ethereal) - ...(emailResult.previewUrl && { previewUrl: emailResult.previewUrl }), - }); - } catch (error) { - console.error("[Password Reset] Request error:", error); - res.status(500).json({ - error: "Failed to process password reset request", - }); - } - }); + }, + ); /** * POST /api/password-reset/verify diff --git a/src/api/settings.ts b/src/api/settings.ts index e40810c..2d42549 100644 --- a/src/api/settings.ts +++ b/src/api/settings.ts @@ -12,7 +12,11 @@ import { Router } from "express"; import { z } from "zod"; import type { Database } from "../db/database.js"; import { SettingsDB } from "../db/settings.js"; -import { requireAdmin, requireAuth, type AuthenticatedRequest } from "../auth/middleware.js"; +import { + requireAdmin, + requireAuth, + type AuthenticatedRequest, +} from "../auth/middleware.js"; // Default user settings const DEFAULT_USER_SETTINGS = { @@ -105,7 +109,10 @@ export function createSettingsRouter(db: Database): Router { // Create default settings if not exists if (!userSettings) { - userSettings = settingsDB.createUserSettings(userId, DEFAULT_USER_SETTINGS); + userSettings = settingsDB.createUserSettings( + userId, + DEFAULT_USER_SETTINGS, + ); } res.json({ @@ -153,7 +160,9 @@ export function createSettingsRouter(db: Database): Router { // Merge with existing or default settings let existingSettings = settingsDB.getUserSettings(userId); - let currentSettings = existingSettings ? JSON.parse(existingSettings.settings) : DEFAULT_USER_SETTINGS; + let currentSettings = existingSettings + ? JSON.parse(existingSettings.settings) + : DEFAULT_USER_SETTINGS; // Deep merge const newSettings = { @@ -196,132 +205,152 @@ export function createSettingsRouter(db: Database): Router { * Delete user settings (reset to defaults) * Phase-1 Hardening: Requires authentication + owner check */ - router.delete("/user/:userId", requireAuth, (req: AuthenticatedRequest, res) => { - // Owner check: Users can only delete their own settings - const authReq = req as AuthenticatedRequest; - if (!authReq.isAdmin && authReq.userId !== req.params.userId) { - return res.status(403).json({ - success: false, - error: "Forbidden: You can only delete your own settings", - }); - } - try { - const { userId } = req.params; + router.delete( + "/user/:userId", + requireAuth, + (req: AuthenticatedRequest, res) => { + // Owner check: Users can only delete their own settings + const authReq = req as AuthenticatedRequest; + if (!authReq.isAdmin && authReq.userId !== req.params.userId) { + return res.status(403).json({ + success: false, + error: "Forbidden: You can only delete your own settings", + }); + } + try { + const { userId } = req.params; - const success = settingsDB.deleteUserSettings(userId); + const success = settingsDB.deleteUserSettings(userId); - res.json({ - success, - message: success ? "Settings deleted successfully" : "Settings not found", - }); - } catch (error) { - console.error("Failed to delete user settings:", error); - res.status(500).json({ - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }); - } - }); + res.json({ + success, + message: success + ? "Settings deleted successfully" + : "Settings not found", + }); + } catch (error) { + console.error("Failed to delete user settings:", error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }); + } + }, + ); /** * GET /api/settings/preferences/:userId * Get user preferences (subset of settings) * Phase-1 Hardening: Requires authentication + owner check */ - router.get("/preferences/:userId", requireAuth, (req: AuthenticatedRequest, res) => { - // Owner check - const authReq = req as AuthenticatedRequest; - if (!authReq.isAdmin && authReq.userId !== req.params.userId) { - return res.status(403).json({ - success: false, - error: "Forbidden: You can only access your own preferences", - }); - } - try { - const { userId } = req.params; + router.get( + "/preferences/:userId", + requireAuth, + (req: AuthenticatedRequest, res) => { + // Owner check + const authReq = req as AuthenticatedRequest; + if (!authReq.isAdmin && authReq.userId !== req.params.userId) { + return res.status(403).json({ + success: false, + error: "Forbidden: You can only access your own preferences", + }); + } + try { + const { userId } = req.params; - let userSettings = settingsDB.getUserSettings(userId); + let userSettings = settingsDB.getUserSettings(userId); - if (!userSettings) { - userSettings = settingsDB.createUserSettings(userId, DEFAULT_USER_SETTINGS); - } + if (!userSettings) { + userSettings = settingsDB.createUserSettings( + userId, + DEFAULT_USER_SETTINGS, + ); + } - const settings = JSON.parse(userSettings.settings); - const preferences = settings.preferences || DEFAULT_USER_SETTINGS.preferences; + const settings = JSON.parse(userSettings.settings); + const preferences = + settings.preferences || DEFAULT_USER_SETTINGS.preferences; - res.json({ - success: true, - preferences, - }); - } catch (error) { - console.error("Failed to get preferences:", error); - res.status(500).json({ - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }); - } - }); + res.json({ + success: true, + preferences, + }); + } catch (error) { + console.error("Failed to get preferences:", error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }); + } + }, + ); /** * PATCH /api/settings/preferences/:userId * Update user preferences (partial update) * Phase-1 Hardening: Requires authentication + owner check */ - router.patch("/preferences/:userId", requireAuth, (req: AuthenticatedRequest, res) => { - // Owner check - const authReq = req as AuthenticatedRequest; - if (!authReq.isAdmin && authReq.userId !== req.params.userId) { - return res.status(403).json({ - success: false, - error: "Forbidden: You can only update your own preferences", - }); - } - try { - const { userId } = req.params; - - const parsed = PreferencesSchema.safeParse(req.body); - if (!parsed.success) { - return res.status(400).json({ + router.patch( + "/preferences/:userId", + requireAuth, + (req: AuthenticatedRequest, res) => { + // Owner check + const authReq = req as AuthenticatedRequest; + if (!authReq.isAdmin && authReq.userId !== req.params.userId) { + return res.status(403).json({ success: false, - error: "Invalid preferences format", - details: parsed.error.issues, + error: "Forbidden: You can only update your own preferences", }); } + try { + const { userId } = req.params; + + const parsed = PreferencesSchema.safeParse(req.body); + if (!parsed.success) { + return res.status(400).json({ + success: false, + error: "Invalid preferences format", + details: parsed.error.issues, + }); + } - // Get current settings - let userSettings = settingsDB.getUserSettings(userId); - let currentSettings = userSettings ? JSON.parse(userSettings.settings) : DEFAULT_USER_SETTINGS; - - // Update preferences - const newSettings = { - ...currentSettings, - preferences: { - ...currentSettings.preferences, - ...parsed.data, - }, - }; + // Get current settings + let userSettings = settingsDB.getUserSettings(userId); + let currentSettings = userSettings + ? JSON.parse(userSettings.settings) + : DEFAULT_USER_SETTINGS; + + // Update preferences + const newSettings = { + ...currentSettings, + preferences: { + ...currentSettings.preferences, + ...parsed.data, + }, + }; + + // Update or create + let success: boolean; + if (userSettings) { + success = settingsDB.updateUserSettings(userId, newSettings, userId); + } else { + settingsDB.createUserSettings(userId, newSettings); + success = true; + } - // Update or create - let success: boolean; - if (userSettings) { - success = settingsDB.updateUserSettings(userId, newSettings, userId); - } else { - settingsDB.createUserSettings(userId, newSettings); - success = true; + res.json({ + success, + preferences: newSettings.preferences, + }); + } catch (error) { + console.error("Failed to update preferences:", error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }); } - - res.json({ - success, - preferences: newSettings.preferences, - }); - } catch (error) { - console.error("Failed to update preferences:", error); - res.status(500).json({ - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }); - } - }); + }, + ); /** * GET /api/settings/system @@ -362,7 +391,8 @@ export function createSettingsRouter(db: Database): Router { if (!setting) { // Return default if exists - const defaultValue = DEFAULT_SYSTEM_SETTINGS[key as keyof typeof DEFAULT_SYSTEM_SETTINGS]; + const defaultValue = + DEFAULT_SYSTEM_SETTINGS[key as keyof typeof DEFAULT_SYSTEM_SETTINGS]; if (defaultValue !== undefined) { return res.json({ success: true, diff --git a/src/api/slack-events.ts b/src/api/slack-events.ts index 17a1993..f63ec1d 100644 --- a/src/api/slack-events.ts +++ b/src/api/slack-events.ts @@ -5,7 +5,11 @@ import type { Request, Response } from "express"; import { createSlackClient } from "../integrations/slack/client.js"; -import { getSmartResponse, isMentioningMujo, cleanMessage } from "../integrations/slack/bot-responses.js"; +import { + getSmartResponse, + isMentioningMujo, + cleanMessage, +} from "../integrations/slack/bot-responses.js"; import { createMetaSupervisor } from "../meta/metaSupervisor.js"; import { computeStopScore } from "../audit/stopScorer.js"; import { type Language } from "../integrations/slack/humor.js"; @@ -149,7 +153,7 @@ async function handleMention(event: any) { async function handleSupervisorCommands( message: string, channel: string, - language: Language + language: Language, ): Promise { const messageLower = message.toLowerCase(); @@ -294,6 +298,6 @@ export function verifySlackSignature(req: Request): boolean { return crypto.timingSafeEqual( Buffer.from(mySignature, "utf8"), - Buffer.from(slackSignature, "utf8") + Buffer.from(slackSignature, "utf8"), ); } diff --git a/src/api/tasks.ts b/src/api/tasks.ts index 0c9bb3d..a664139 100644 --- a/src/api/tasks.ts +++ b/src/api/tasks.ts @@ -26,7 +26,11 @@ const SubmitWorkSchema = z.object({ claims: z.array(z.string()).optional().default([]), }); -export function createTaskRouter(db: Database, queue: QueueAdapter, gate: EnforcementGate): Router { +export function createTaskRouter( + db: Database, + queue: QueueAdapter, + gate: EnforcementGate, +): Router { const router = Router(); // Phase-1 Hardening: All task routes require authentication @@ -55,11 +59,18 @@ export function createTaskRouter(db: Database, queue: QueueAdapter, gate: Enforc // ENFORCEMENT GATE: Evaluate task content before queuing const content = `${parsed.data.title} ${parsed.data.description ?? ""}`; - const gateDecision = gate.evaluate(task.id, content, parsed.data.artefacts); + const gateDecision = gate.evaluate( + task.id, + content, + parsed.data.artefacts, + ); if (gateDecision.status === "BLOCKED") { // Task is BLOCKED - do NOT queue, require human approval - db.updateTask(task.id, { status: "stopped", stop_score: gateDecision.stopScore }); + db.updateTask(task.id, { + status: "stopped", + stop_score: gateDecision.stopScore, + }); // Log blocked task event db.audit.log({ @@ -68,7 +79,10 @@ export function createTaskRouter(db: Database, queue: QueueAdapter, gate: Enforc taskId: task.id, userId: (req as any).userId, severity: "warn", - meta: { stopScore: gateDecision.stopScore, reasons: gateDecision.reasons }, + meta: { + stopScore: gateDecision.stopScore, + reasons: gateDecision.reasons, + }, }); return res.status(202).json({ @@ -76,7 +90,9 @@ export function createTaskRouter(db: Database, queue: QueueAdapter, gate: Enforc status: "BLOCKED", stop_score: gateDecision.stopScore, reasons: gateDecision.reasons, - message: "⚠️ STOP_REQUIRED: Task blocked. Human approval needed at /api/enforcement/approve/" + task.id, + message: + "⚠️ STOP_REQUIRED: Task blocked. Human approval needed at /api/enforcement/approve/" + + task.id, approval_url: `/api/enforcement/approve/${task.id}`, }); } @@ -125,11 +141,18 @@ export function createTaskRouter(db: Database, queue: QueueAdapter, gate: Enforc } // ENFORCEMENT GATE: Evaluate submitted work - const gateDecision = gate.evaluate(task.id, parsed.data.content, parsed.data.artefacts); + const gateDecision = gate.evaluate( + task.id, + parsed.data.content, + parsed.data.artefacts, + ); if (gateDecision.status === "BLOCKED") { // Work is BLOCKED - cannot complete task - db.updateTask(task.id, { status: "stopped", stop_score: gateDecision.stopScore }); + db.updateTask(task.id, { + status: "stopped", + stop_score: gateDecision.stopScore, + }); // Log blocked submission event db.audit.log({ @@ -138,7 +161,10 @@ export function createTaskRouter(db: Database, queue: QueueAdapter, gate: Enforc taskId: task.id, userId: (req as any).userId, severity: "warn", - meta: { stopScore: gateDecision.stopScore, reasons: gateDecision.reasons }, + meta: { + stopScore: gateDecision.stopScore, + reasons: gateDecision.reasons, + }, }); return res.status(202).json({ @@ -146,13 +172,17 @@ export function createTaskRouter(db: Database, queue: QueueAdapter, gate: Enforc status: "BLOCKED", stop_score: gateDecision.stopScore, reasons: gateDecision.reasons, - message: "⚠️ STOP_REQUIRED: Submission blocked. Human approval needed.", + message: + "⚠️ STOP_REQUIRED: Submission blocked. Human approval needed.", approval_url: `/api/enforcement/approve/${task.id}`, }); } // Work passed gate - mark as completed - db.updateTask(task.id, { status: "completed", stop_score: gateDecision.stopScore }); + db.updateTask(task.id, { + status: "completed", + stop_score: gateDecision.stopScore, + }); // Log task completed event db.audit.log({ @@ -182,7 +212,9 @@ export function createTaskRouter(db: Database, queue: QueueAdapter, gate: Enforc router.get("/", (req, res) => { try { const state = req.query.state as string | undefined; - const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 100; + const limit = req.query.limit + ? parseInt(req.query.limit as string, 10) + : 100; const tasks = db.listTasks({ state, limit }); @@ -222,7 +254,9 @@ export function createTaskRouter(db: Database, queue: QueueAdapter, gate: Enforc enforcement: { blocked: isBlocked, pending_approval: pending ?? null, - approval_url: isBlocked ? `/api/enforcement/approve/${task.id}` : null, + approval_url: isBlocked + ? `/api/enforcement/approve/${task.id}` + : null, }, }); } catch (error) { diff --git a/src/api/users.ts b/src/api/users.ts index 95d9095..1cbb378 100644 --- a/src/api/users.ts +++ b/src/api/users.ts @@ -5,7 +5,11 @@ import { Router, type Request, type Response } from "express"; import type { Database } from "../db/database.js"; -import { requireAdmin, requireAuth, type AuthenticatedRequest } from "../auth/middleware.js"; +import { + requireAdmin, + requireAuth, + type AuthenticatedRequest, +} from "../auth/middleware.js"; import { createUser, getUserById, @@ -31,9 +35,16 @@ export function createUsersRouter(db: Database): Router { router.get("/", requireAdmin, async (req: Request, res: Response) => { try { const role = req.query.role as "admin" | "user" | "demo" | undefined; - const isActive = req.query.isActive === "true" ? true : req.query.isActive === "false" ? false : undefined; + const isActive = + req.query.isActive === "true" + ? true + : req.query.isActive === "false" + ? false + : undefined; const limit = req.query.limit ? parseInt(req.query.limit as string) : 100; - const offset = req.query.offset ? parseInt(req.query.offset as string) : 0; + const offset = req.query.offset + ? parseInt(req.query.offset as string) + : 0; const users = listUsers(rawDb, { role, isActive, limit, offset }); @@ -54,89 +65,101 @@ export function createUsersRouter(db: Database): Router { * GET /api/users/stats * Get user statistics (Admin only) */ - router.get("/stats", requireAdmin, async (_req: AuthenticatedRequest, res: Response) => { - try { - const stats = getUserStats(rawDb); - - res.json({ - success: true, - stats, - }); - } catch (error) { - console.error("[Users API] Stats error:", error); - res.status(500).json({ - error: "Failed to get user stats", - }); - } - }); + router.get( + "/stats", + requireAdmin, + async (_req: AuthenticatedRequest, res: Response) => { + try { + const stats = getUserStats(rawDb); + + res.json({ + success: true, + stats, + }); + } catch (error) { + console.error("[Users API] Stats error:", error); + res.status(500).json({ + error: "Failed to get user stats", + }); + } + }, + ); /** * GET /api/users/me * Get current user's profile */ - router.get("/me", requireAuth, async (req: AuthenticatedRequest, res: Response) => { - try { - const user = getUserById(rawDb, req.userId!); + router.get( + "/me", + requireAuth, + async (req: AuthenticatedRequest, res: Response) => { + try { + const user = getUserById(rawDb, req.userId!); + + if (!user) { + return res.status(404).json({ + error: "User not found", + }); + } - if (!user) { - return res.status(404).json({ - error: "User not found", + // Exclude password hash + const { passwordHash, ...userWithoutPassword } = user; + + res.json({ + success: true, + user: userWithoutPassword, + }); + } catch (error) { + console.error("[Users API] Get me error:", error); + res.status(500).json({ + error: "Failed to get user", }); } - - // Exclude password hash - const { passwordHash, ...userWithoutPassword } = user; - - res.json({ - success: true, - user: userWithoutPassword, - }); - } catch (error) { - console.error("[Users API] Get me error:", error); - res.status(500).json({ - error: "Failed to get user", - }); - } - }); + }, + ); /** * GET /api/users/:id * Get user by ID (Admin only, or own profile) */ - router.get("/:id", requireAuth, async (req: AuthenticatedRequest, res: Response) => { - try { - const { id } = req.params; + router.get( + "/:id", + requireAuth, + async (req: AuthenticatedRequest, res: Response) => { + try { + const { id } = req.params; + + // Users can only view their own profile, admins can view any + if (!req.isAdmin && id !== req.userId) { + return res.status(403).json({ + error: "Forbidden", + message: "You can only view your own profile", + }); + } - // Users can only view their own profile, admins can view any - if (!req.isAdmin && id !== req.userId) { - return res.status(403).json({ - error: "Forbidden", - message: "You can only view your own profile", - }); - } + const user = getUserById(rawDb, id); - const user = getUserById(rawDb, id); + if (!user) { + return res.status(404).json({ + error: "User not found", + }); + } - if (!user) { - return res.status(404).json({ - error: "User not found", + // Exclude password hash + const { passwordHash, ...userWithoutPassword } = user; + + res.json({ + success: true, + user: userWithoutPassword, + }); + } catch (error) { + console.error("[Users API] Get user error:", error); + res.status(500).json({ + error: "Failed to get user", }); } - - // Exclude password hash - const { passwordHash, ...userWithoutPassword } = user; - - res.json({ - success: true, - user: userWithoutPassword, - }); - } catch (error) { - console.error("[Users API] Get user error:", error); - res.status(500).json({ - error: "Failed to get user", - }); - } - }); + }, + ); /** * POST /api/users @@ -196,136 +219,147 @@ export function createUsersRouter(db: Database): Router { * PATCH /api/users/:id * Update user (Admin only, or own profile with limited fields) */ - router.patch("/:id", requireAuth, async (req: AuthenticatedRequest, res: Response) => { - try { - const { id } = req.params; - const { email, role, displayName, isActive } = req.body; - - // Check permissions - const isOwnProfile = id === req.userId; - if (!req.isAdmin && !isOwnProfile) { - return res.status(403).json({ - error: "Forbidden", - message: "You can only update your own profile", - }); - } + router.patch( + "/:id", + requireAuth, + async (req: AuthenticatedRequest, res: Response) => { + try { + const { id } = req.params; + const { email, role, displayName, isActive } = req.body; + + // Check permissions + const isOwnProfile = id === req.userId; + if (!req.isAdmin && !isOwnProfile) { + return res.status(403).json({ + error: "Forbidden", + message: "You can only update your own profile", + }); + } - // Non-admins can't change role or isActive - if (!req.isAdmin && (role !== undefined || isActive !== undefined)) { - return res.status(403).json({ - error: "Forbidden", - message: "You cannot change role or active status", - }); - } + // Non-admins can't change role or isActive + if (!req.isAdmin && (role !== undefined || isActive !== undefined)) { + return res.status(403).json({ + error: "Forbidden", + message: "You cannot change role or active status", + }); + } - // Check if user exists - const user = getUserById(rawDb, id); - if (!user) { - return res.status(404).json({ - error: "User not found", - }); - } + // Check if user exists + const user = getUserById(rawDb, id); + if (!user) { + return res.status(404).json({ + error: "User not found", + }); + } - // Update user - const updates: UserUpdateData = {}; - if (email !== undefined) updates.email = email; - if (role !== undefined) updates.role = role; - if (displayName !== undefined) updates.displayName = displayName; - if (isActive !== undefined) updates.isActive = isActive; + // Update user + const updates: UserUpdateData = {}; + if (email !== undefined) updates.email = email; + if (role !== undefined) updates.role = role; + if (displayName !== undefined) updates.displayName = displayName; + if (isActive !== undefined) updates.isActive = isActive; - const updatedUser = updateUser(rawDb, id, updates); + const updatedUser = updateUser(rawDb, id, updates); - if (!updatedUser) { - return res.status(404).json({ - error: "User not found", - }); - } + if (!updatedUser) { + return res.status(404).json({ + error: "User not found", + }); + } - // Exclude password hash - const { passwordHash, ...userWithoutPassword } = updatedUser; + // Exclude password hash + const { passwordHash, ...userWithoutPassword } = updatedUser; - res.json({ - success: true, - user: userWithoutPassword, - }); - } catch (error) { - console.error("[Users API] Update error:", error); - res.status(500).json({ - error: "Failed to update user", - }); - } - }); + res.json({ + success: true, + user: userWithoutPassword, + }); + } catch (error) { + console.error("[Users API] Update error:", error); + res.status(500).json({ + error: "Failed to update user", + }); + } + }, + ); /** * POST /api/users/:id/password * Change user password (Admin only, or own password) */ - router.post("/:id/password", requireAuth, async (req: AuthenticatedRequest, res: Response) => { - try { - const { id } = req.params; - const { currentPassword, newPassword } = req.body; - - // Check permissions - const isOwnProfile = id === req.userId; - if (!req.isAdmin && !isOwnProfile) { - return res.status(403).json({ - error: "Forbidden", - message: "You can only change your own password", - }); - } - - // Validation - if (!newPassword || newPassword.length < 8) { - return res.status(400).json({ - error: "New password must be at least 8 characters", - }); - } - - // Check if user exists - const user = getUserById(rawDb, id); - if (!user) { - return res.status(404).json({ - error: "User not found", - }); - } + router.post( + "/:id/password", + requireAuth, + async (req: AuthenticatedRequest, res: Response) => { + try { + const { id } = req.params; + const { currentPassword, newPassword } = req.body; + + // Check permissions + const isOwnProfile = id === req.userId; + if (!req.isAdmin && !isOwnProfile) { + return res.status(403).json({ + error: "Forbidden", + message: "You can only change your own password", + }); + } - // Non-admins must provide current password - if (!req.isAdmin) { - if (!currentPassword) { + // Validation + if (!newPassword || newPassword.length < 8) { return res.status(400).json({ - error: "Current password required", + error: "New password must be at least 8 characters", }); } - // Verify current password - const isValid = await bcrypt.compare(currentPassword, user.passwordHash); - if (!isValid) { - return res.status(401).json({ - error: "Current password incorrect", + // Check if user exists + const user = getUserById(rawDb, id); + if (!user) { + return res.status(404).json({ + error: "User not found", }); } - } - // Change password - const success = await changeUserPassword(rawDb, id, newPassword); + // Non-admins must provide current password + if (!req.isAdmin) { + if (!currentPassword) { + return res.status(400).json({ + error: "Current password required", + }); + } + + // Verify current password + const isValid = await bcrypt.compare( + currentPassword, + user.passwordHash, + ); + if (!isValid) { + return res.status(401).json({ + error: "Current password incorrect", + }); + } + } - if (!success) { - return res.status(500).json({ + // Change password + const success = await changeUserPassword(rawDb, id, newPassword); + + if (!success) { + return res.status(500).json({ + error: "Failed to change password", + }); + } + + res.json({ + success: true, + message: "Password changed successfully", + }); + } catch (error) { + console.error("[Users API] Change password error:", error); + res.status(500).json({ error: "Failed to change password", }); } - - res.json({ - success: true, - message: "Password changed successfully", - }); - } catch (error) { - console.error("[Users API] Change password error:", error); - res.status(500).json({ - error: "Failed to change password", - }); - } - }); + }, + ); /** * DELETE /api/users/:id diff --git a/src/api/webhooks.ts b/src/api/webhooks.ts index c45b375..5cfd976 100644 --- a/src/api/webhooks.ts +++ b/src/api/webhooks.ts @@ -68,7 +68,9 @@ export function createWebhookRouter(): Router { } if (!registration.events || registration.events.length === 0) { - return res.status(400).json({ error: "Missing required field: events" }); + return res + .status(400) + .json({ error: "Missing required field: events" }); } // Generate secret if not provided @@ -182,7 +184,9 @@ export function createWebhookRouter(): Router { const { webhookId } = req.body; if (!webhookId) { - return res.status(400).json({ error: "Missing required field: webhookId" }); + return res + .status(400) + .json({ error: "Missing required field: webhookId" }); } const webhook = webhookManager.get(webhookId); diff --git a/src/audit/claimVerifier.ts b/src/audit/claimVerifier.ts index 2a9726a..0aa2383 100644 --- a/src/audit/claimVerifier.ts +++ b/src/audit/claimVerifier.ts @@ -29,9 +29,15 @@ export interface VerificationResult { * Claim patterns we look for */ const CLAIM_PATTERNS = [ - { pattern: /scraped\s+(\d+)\s*(urls?|pages?|sites?|portals?)/i, type: "scrape" }, + { + pattern: /scraped\s+(\d+)\s*(urls?|pages?|sites?|portals?)/i, + type: "scrape", + }, { pattern: /created\s+(\d+)\s*(files?|components?|modules?)/i, type: "file" }, - { pattern: /implemented\s+(\d+)\s*(features?|endpoints?|apis?)/i, type: "feature" }, + { + pattern: /implemented\s+(\d+)\s*(features?|endpoints?|apis?)/i, + type: "feature", + }, { pattern: /fixed\s+(\d+)\s*(bugs?|issues?|errors?)/i, type: "fix" }, { pattern: /added\s+(\d+)\s*(tests?|specs?)/i, type: "test" }, { pattern: /deployed\s+(\d+)\s*(services?|instances?)/i, type: "deploy" }, @@ -43,7 +49,7 @@ const CLAIM_PATTERNS = [ export function verifyClaims( content: string, artefacts: string[], - projectRoot?: string + projectRoot?: string, ): VerificationResult { const verifications: ClaimVerification[] = []; @@ -55,7 +61,11 @@ export function verifyClaims( const claim = match[0]; // Count relevant artefacts - const relevantArtefacts = filterArtefactsByType(artefacts, type, projectRoot); + const relevantArtefacts = filterArtefactsByType( + artefacts, + type, + projectRoot, + ); const actualCount = relevantArtefacts.length; const verification: ClaimVerification = { @@ -89,7 +99,7 @@ export function verifyClaims( function filterArtefactsByType( artefacts: string[], type: string, - projectRoot?: string + projectRoot?: string, ): string[] { const relevant: string[] = []; @@ -131,14 +141,20 @@ function filterArtefactsByType( case "test": // Test files - if (/\.(test|spec)\.(ts|js|tsx|jsx)$/i.test(artefact) || /tests?\//i.test(artefact)) { + if ( + /\.(test|spec)\.(ts|js|tsx|jsx)$/i.test(artefact) || + /tests?\//i.test(artefact) + ) { relevant.push(artefact); } break; case "deploy": // Deploy/config files - if (/\.(yaml|yml|json|toml|dockerfile)$/i.test(artefact) || /docker|k8s|deploy/i.test(artefact)) { + if ( + /\.(yaml|yml|json|toml|dockerfile)$/i.test(artefact) || + /docker|k8s|deploy/i.test(artefact) + ) { relevant.push(artefact); } break; @@ -163,7 +179,9 @@ export function generateVerificationReport(result: VerificationResult): string { lines.push(`- Total Claims: ${result.totalClaims}`); lines.push(`- Verified: ${result.verifiedClaims}`); lines.push(`- Failed: ${result.failedClaims.length}`); - lines.push(`- Status: ${result.allVerified ? "✅ ALL VERIFIED" : "❌ VERIFICATION FAILED"}`); + lines.push( + `- Status: ${result.allVerified ? "✅ ALL VERIFIED" : "❌ VERIFICATION FAILED"}`, + ); lines.push(""); if (result.failedClaims.length > 0) { diff --git a/src/audit/enforcementGate.ts b/src/audit/enforcementGate.ts index 1b682d7..cbc4c6f 100644 --- a/src/audit/enforcementGate.ts +++ b/src/audit/enforcementGate.ts @@ -6,7 +6,11 @@ */ import type { Database } from "../db/database.js"; -import { computeStopScore, analyzeContent, type StopReason } from "../audit/stopScorer.js"; +import { + computeStopScore, + analyzeContent, + type StopReason, +} from "../audit/stopScorer.js"; export type GateStatus = "OPEN" | "BLOCKED" | "AWAITING_APPROVAL"; @@ -139,7 +143,7 @@ export class EnforcementGate { taskId: string, approver: string, reason: string, - acknowledgedRisks: string[] + acknowledgedRisks: string[], ): GateDecision { const pending = this.blockedTasks.get(taskId); diff --git a/src/auth/jwt.ts b/src/auth/jwt.ts index e4964b1..48ccb87 100644 --- a/src/auth/jwt.ts +++ b/src/auth/jwt.ts @@ -74,15 +74,11 @@ export function generateRefreshToken(payload: TokenPayload): string { // Add random jti (JWT ID) for token rotation const jti = randomBytes(16).toString("hex"); - return jwt.sign( - { ...payload, jti }, - secret, - { - expiresIn: REFRESH_TOKEN_EXPIRY, - issuer: "code-cloud-agents", - audience: "cloud-agents-api", - } - ); + return jwt.sign({ ...payload, jti }, secret, { + expiresIn: REFRESH_TOKEN_EXPIRY, + issuer: "code-cloud-agents", + audience: "cloud-agents-api", + }); } /** diff --git a/src/auth/middleware.ts b/src/auth/middleware.ts index da2b409..dbc3ae2 100644 --- a/src/auth/middleware.ts +++ b/src/auth/middleware.ts @@ -35,7 +35,11 @@ function extractJwtPayload(req: Request): TokenPayload | null { */ function isSystemId(userId: string | undefined): boolean { if (!userId) return false; - return userId.startsWith("system-") || userId === "health-check" || userId === "diagnostics"; + return ( + userId.startsWith("system-") || + userId === "health-check" || + userId === "diagnostics" + ); } /** @@ -46,12 +50,19 @@ function isSystemId(userId: string | undefined): boolean { * Usage: * router.post("/admin-only", requireAdmin, handler); */ -export function requireAdmin(req: AuthenticatedRequest, res: Response, next: NextFunction): void { +export function requireAdmin( + req: AuthenticatedRequest, + res: Response, + next: NextFunction, +): void { const payload = extractJwtPayload(req); // Check if JWT is valid if (!payload) { - log.warn("Auth failed: invalid or missing JWT", { endpoint: req.path, method: req.method }); + log.warn("Auth failed: invalid or missing JWT", { + endpoint: req.path, + method: req.method, + }); res.status(401).json({ error: "Authentication required", message: "Valid JWT token required in Authorization header", @@ -69,7 +80,11 @@ export function requireAdmin(req: AuthenticatedRequest, res: Response, next: Nex // Check if user has admin role if (role !== "admin") { - log.warn("Auth failed: insufficient privileges", { userId, role, endpoint: req.path }); + log.warn("Auth failed: insufficient privileges", { + userId, + role, + endpoint: req.path, + }); res.status(403).json({ error: "Admin access required", message: "This endpoint requires admin privileges", @@ -93,7 +108,11 @@ export function requireAdmin(req: AuthenticatedRequest, res: Response, next: Nex * Extracts userId and role from JWT token * Returns 401 if not authenticated */ -export function requireAuth(req: AuthenticatedRequest, res: Response, next: NextFunction): void { +export function requireAuth( + req: AuthenticatedRequest, + res: Response, + next: NextFunction, +): void { const payload = extractJwtPayload(req); if (!payload) { @@ -116,7 +135,11 @@ export function requireAuth(req: AuthenticatedRequest, res: Response, next: Next * Optional authentication (JWT-based) * Adds user info if valid JWT available, but doesn't block */ -export function optionalAuth(req: AuthenticatedRequest, _res: Response, next: NextFunction): void { +export function optionalAuth( + req: AuthenticatedRequest, + _res: Response, + next: NextFunction, +): void { const payload = extractJwtPayload(req); if (payload) { @@ -132,9 +155,14 @@ export function optionalAuth(req: AuthenticatedRequest, _res: Response, next: Ne * Cron job authentication * Validates cron API key */ -export function requireCronAuth(req: Request, res: Response, next: NextFunction): void { +export function requireCronAuth( + req: Request, + res: Response, + next: NextFunction, +): void { const apiKey = req.headers["x-api-key"] as string; - const expectedKey = process.env.CRON_API_KEY || "dev-cron-key-change-in-production"; + const expectedKey = + process.env.CRON_API_KEY || "dev-cron-key-change-in-production"; if (!apiKey) { res.status(401).json({ @@ -147,7 +175,10 @@ export function requireCronAuth(req: Request, res: Response, next: NextFunction) if (apiKey !== expectedKey) { console.error(`[SECURITY] Invalid cron API key attempt from ${req.ip}`); - log.error("Security: Invalid cron API key", { ip: req.ip, endpoint: req.path }); + log.error("Security: Invalid cron API key", { + ip: req.ip, + endpoint: req.path, + }); res.status(403).json({ error: "Invalid API key", code: "FORBIDDEN", @@ -171,10 +202,15 @@ export function createRateLimiter(maxRequests: number, windowMs: number) { const userRequests = requests.get(identifier) || []; // Remove old requests outside window - const validRequests = userRequests.filter(time => now - time < windowMs); + const validRequests = userRequests.filter((time) => now - time < windowMs); if (validRequests.length >= maxRequests) { - log.warn("Rate limit exceeded", { ip: identifier, requests: validRequests.length, maxRequests, endpoint: req.path }); + log.warn("Rate limit exceeded", { + ip: identifier, + requests: validRequests.length, + maxRequests, + endpoint: req.path, + }); res.status(429).json({ error: "Rate limit exceeded", message: `Max ${maxRequests} requests per ${windowMs / 1000}s`, @@ -196,10 +232,18 @@ export function createRateLimiter(maxRequests: number, windowMs: number) { */ export function createDatabaseAuth(_db: Database) { return { - requireAdmin: (req: AuthenticatedRequest, res: Response, next: NextFunction) => { + requireAdmin: ( + req: AuthenticatedRequest, + res: Response, + next: NextFunction, + ) => { requireAdmin(req, res, next); }, - requireAuth: (req: AuthenticatedRequest, res: Response, next: NextFunction) => { + requireAuth: ( + req: AuthenticatedRequest, + res: Response, + next: NextFunction, + ) => { requireAuth(req, res, next); }, }; diff --git a/src/auth/rate-limiter.ts b/src/auth/rate-limiter.ts index c377065..67fd555 100644 --- a/src/auth/rate-limiter.ts +++ b/src/auth/rate-limiter.ts @@ -76,7 +76,10 @@ function initRedis(): void { }); redisClient.on("error", (err: Error) => { - console.warn("⚠️ Redis connection error, falling back to in-memory rate limiting:", err.message); + console.warn( + "⚠️ Redis connection error, falling back to in-memory rate limiting:", + err.message, + ); redisAvailable = false; }); @@ -91,7 +94,9 @@ function initRedis(): void { redisAvailable = false; }); } catch (err) { - console.log("ℹ️ Redis initialization failed, using in-memory rate limiting"); + console.log( + "ℹ️ Redis initialization failed, using in-memory rate limiting", + ); redisAvailable = false; } } @@ -121,7 +126,8 @@ setInterval(() => { * Uses IP address as identifier */ function getRateLimitKey(req: Request, prefix: string): string { - const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress || "unknown"; + const ip = + req.headers["x-forwarded-for"] || req.socket.remoteAddress || "unknown"; const ipStr = Array.isArray(ip) ? ip[0] : ip; return `ratelimit:${prefix}:${ipStr}`; } @@ -132,7 +138,7 @@ function getRateLimitKey(req: Request, prefix: string): string { async function checkRateLimitRedis( key: string, maxRequests: number, - windowMs: number + windowMs: number, ): Promise<{ allowed: boolean; count: number; resetAt: number }> { if (!redisClient || !redisAvailable) { throw new Error("Redis not available"); @@ -168,7 +174,7 @@ async function checkRateLimitRedis( function checkRateLimitMemory( key: string, maxRequests: number, - windowMs: number + windowMs: number, ): { allowed: boolean; count: number; resetAt: number } { const now = Date.now(); let entry = rateLimitStore.get(key); @@ -200,7 +206,11 @@ function checkRateLimitMemory( export function createRateLimiter(config: RateLimitConfig) { const { maxRequests, windowMs, message, keyPrefix = "default" } = config; - return async (req: Request, res: Response, next: NextFunction): Promise => { + return async ( + req: Request, + res: Response, + next: NextFunction, + ): Promise => { const key = getRateLimitKey(req, keyPrefix); try { @@ -216,7 +226,10 @@ export function createRateLimiter(config: RateLimitConfig) { // Set rate limit headers res.setHeader("X-RateLimit-Limit", maxRequests); - res.setHeader("X-RateLimit-Remaining", Math.max(0, maxRequests - result.count)); + res.setHeader( + "X-RateLimit-Remaining", + Math.max(0, maxRequests - result.count), + ); res.setHeader("X-RateLimit-Reset", Math.ceil(result.resetAt / 1000)); if (!result.allowed) { @@ -301,7 +314,10 @@ export const apiRateLimiter = createRateLimiter({ * Manually reset rate limit for a request * Useful for testing or manual intervention */ -export async function resetRateLimit(req: Request, prefix: string): Promise { +export async function resetRateLimit( + req: Request, + prefix: string, +): Promise { const key = getRateLimitKey(req, prefix); if (redisAvailable && redisClient) { @@ -322,8 +338,13 @@ export async function getRateLimitStatus( req: Request, prefix: string, maxRequests: number, - windowMs: number -): Promise<{ count: number; limit: number; resetAt: number; remaining: number } | null> { + windowMs: number, +): Promise<{ + count: number; + limit: number; + resetAt: number; + remaining: number; +} | null> { const key = getRateLimitKey(req, prefix); if (redisAvailable && redisClient) { diff --git a/src/billing/costTracker.ts b/src/billing/costTracker.ts index 067faa8..bc008ab 100644 --- a/src/billing/costTracker.ts +++ b/src/billing/costTracker.ts @@ -3,7 +3,13 @@ */ import { randomUUID } from "crypto"; -import type { CostEntry, UserLimit, UserUsage, LimitStatus, CostSummary } from "./types.ts"; +import type { + CostEntry, + UserLimit, + UserUsage, + LimitStatus, + CostSummary, +} from "./types.ts"; import { calculateCost, formatCost } from "./pricing.ts"; class CostTracker { @@ -14,11 +20,16 @@ class CostTracker { /** * Log a cost entry */ - log(entry: Omit): CostEntry { + log( + entry: Omit< + CostEntry, + "id" | "timestamp" | "totalTokens" | "costUSD" | "costEUR" + >, + ): CostEntry { const { costUSD, costEUR } = calculateCost( entry.model, entry.inputTokens, - entry.outputTokens + entry.outputTokens, ); const costEntry: CostEntry = { @@ -33,7 +44,7 @@ class CostTracker { this.costs.set(costEntry.id, costEntry); console.log( - `💰 Cost logged: ${entry.userId} | ${entry.model} | ${formatCost(costUSD, "USD")} / ${formatCost(costEUR, "EUR")}` + `💰 Cost logged: ${entry.userId} | ${entry.model} | ${formatCost(costUSD, "USD")} / ${formatCost(costEUR, "EUR")}`, ); // Update usage cache @@ -60,7 +71,7 @@ class CostTracker { this.userLimits.set(limit.userId, userLimit); console.log( - `✅ User limit set: ${limit.userId} | ${formatCost(limit.monthlyLimitUSD, "USD")} / ${formatCost(limit.monthlyLimitEUR, "EUR")}` + `✅ User limit set: ${limit.userId} | ${formatCost(limit.monthlyLimitUSD, "USD")} / ${formatCost(limit.monthlyLimitEUR, "EUR")}`, ); return userLimit; @@ -184,8 +195,10 @@ class CostTracker { const usage = this.getUserUsage(userId); if (!usage) return null; - const percentageUsedUSD = (usage.totalCostUSD / limit.monthlyLimitUSD) * 100; - const percentageUsedEUR = (usage.totalCostEUR / limit.monthlyLimitEUR) * 100; + const percentageUsedUSD = + (usage.totalCostUSD / limit.monthlyLimitUSD) * 100; + const percentageUsedEUR = + (usage.totalCostEUR / limit.monthlyLimitEUR) * 100; const remainingUSD = limit.monthlyLimitUSD - usage.totalCostUSD; const remainingEUR = limit.monthlyLimitEUR - usage.totalCostEUR; @@ -213,11 +226,11 @@ class CostTracker { if (isOverLimit) { console.warn( - `⚠️ User ${userId} is OVER LIMIT: ${formatCost(usage.totalCostUSD, "USD")} / ${formatCost(limit.monthlyLimitUSD, "USD")}` + `⚠️ User ${userId} is OVER LIMIT: ${formatCost(usage.totalCostUSD, "USD")} / ${formatCost(limit.monthlyLimitUSD, "USD")}`, ); } else if (shouldNotify) { console.warn( - `⚠️ User ${userId} reached ${percentageUsedUSD.toFixed(1)}% of limit` + `⚠️ User ${userId} reached ${percentageUsedUSD.toFixed(1)}% of limit`, ); } @@ -229,7 +242,7 @@ class CostTracker { */ getSummary(startDate: string, endDate: string): CostSummary { const relevantCosts = Array.from(this.costs.values()).filter( - (cost) => cost.timestamp >= startDate && cost.timestamp <= endDate + (cost) => cost.timestamp >= startDate && cost.timestamp <= endDate, ); const summary: CostSummary = { @@ -294,7 +307,10 @@ class CostTracker { */ getAllCosts(limit = 1000): CostEntry[] { return Array.from(this.costs.values()) - .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) + .sort( + (a, b) => + new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(), + ) .slice(0, limit); } diff --git a/src/billing/index.ts b/src/billing/index.ts index 60bddb8..00afb9a 100644 --- a/src/billing/index.ts +++ b/src/billing/index.ts @@ -39,8 +39,17 @@ */ export { costTracker } from "./costTracker.ts"; -export { selectModel, analyzeTaskComplexity, compareCosts } from "./modelSelector.ts"; -export { MODEL_PRICING, getModelPricing, calculateCost, formatCost } from "./pricing.ts"; +export { + selectModel, + analyzeTaskComplexity, + compareCosts, +} from "./modelSelector.ts"; +export { + MODEL_PRICING, + getModelPricing, + calculateCost, + formatCost, +} from "./pricing.ts"; export type { ModelPricing, CostEntry, diff --git a/src/billing/modelSelector.ts b/src/billing/modelSelector.ts index 31841ef..72e6a5f 100644 --- a/src/billing/modelSelector.ts +++ b/src/billing/modelSelector.ts @@ -75,7 +75,7 @@ export function selectModel( preferLocal?: boolean; maxCostUSD?: number; forceModel?: string; - } = {} + } = {}, ): ModelRecommendation { const complexity = analyzeTaskComplexity(taskDescription); @@ -121,8 +121,10 @@ export function selectModel( model: "claude-haiku-3-5", provider: "anthropic", reasoning: "Budget constrained - using Haiku for medium task", - estimatedCostUSD: estimateCost("claude-haiku-3-5", 2000, 1000).costUSD, - estimatedCostEUR: estimateCost("claude-haiku-3-5", 2000, 1000).costEUR, + estimatedCostUSD: estimateCost("claude-haiku-3-5", 2000, 1000) + .costUSD, + estimatedCostEUR: estimateCost("claude-haiku-3-5", 2000, 1000) + .costEUR, }; } @@ -142,8 +144,10 @@ export function selectModel( model: "claude-sonnet-4-5", provider: "anthropic", reasoning: "High complexity but budget constrained - using Sonnet", - estimatedCostUSD: estimateCost("claude-sonnet-4-5", 5000, 2000).costUSD, - estimatedCostEUR: estimateCost("claude-sonnet-4-5", 5000, 2000).costEUR, + estimatedCostUSD: estimateCost("claude-sonnet-4-5", 5000, 2000) + .costUSD, + estimatedCostEUR: estimateCost("claude-sonnet-4-5", 5000, 2000) + .costEUR, }; } @@ -160,7 +164,9 @@ export function selectModel( /** * Get provider for a specific model */ -function getProviderForModel(model: string): "anthropic" | "openai" | "xai" | "ollama" { +function getProviderForModel( + model: string, +): "anthropic" | "openai" | "xai" | "ollama" { if (model.startsWith("claude")) return "anthropic"; if (model.startsWith("gpt")) return "openai"; if (model.startsWith("grok")) return "xai"; @@ -173,7 +179,7 @@ function getProviderForModel(model: string): "anthropic" | "openai" | "xai" | "o function estimateCost( model: string, inputTokens: number, - outputTokens: number + outputTokens: number, ): { costUSD: number; costEUR: number } { const pricing: Record = { "claude-opus-4-5": { input: 15.0, output: 75.0 }, @@ -188,7 +194,8 @@ function estimateCost( const price = pricing[model] || { input: 3.0, output: 15.0 }; const costUSD = - (inputTokens / 1_000_000) * price.input + (outputTokens / 1_000_000) * price.output; + (inputTokens / 1_000_000) * price.input + + (outputTokens / 1_000_000) * price.output; const costEUR = costUSD * 0.92; return { @@ -202,7 +209,7 @@ function estimateCost( */ export function compareCosts( taskDescription: string, - models: string[] + models: string[], ): ModelRecommendation[] { return models.map((model) => { const complexity = analyzeTaskComplexity(taskDescription); diff --git a/src/billing/pricing.ts b/src/billing/pricing.ts index b8fa409..30ababa 100644 --- a/src/billing/pricing.ts +++ b/src/billing/pricing.ts @@ -105,8 +105,7 @@ export const USD_TO_EUR_RATE = 0.92; // Approximate rate export function getModelPricing(model: string): ModelPricing | undefined { return MODEL_PRICING.find( (p) => - p.model === model || - model.toLowerCase().includes(p.model.toLowerCase()) + p.model === model || model.toLowerCase().includes(p.model.toLowerCase()), ); } @@ -116,7 +115,7 @@ export function getModelPricing(model: string): ModelPricing | undefined { export function calculateCost( model: string, inputTokens: number, - outputTokens: number + outputTokens: number, ): { costUSD: number; costEUR: number } { const pricing = getModelPricing(model); diff --git a/src/brain/index.ts b/src/brain/index.ts index 063675c..269e8df 100644 --- a/src/brain/index.ts +++ b/src/brain/index.ts @@ -16,4 +16,9 @@ export { BrainSearch } from "./search.js"; export type { BrainSearchResult, KeywordSearchResult } from "./search.js"; // Re-export types from DB schema -export type { BrainDoc, BrainChunk, BrainEmbedding, BrainChatLink } from "../db/brain.js"; +export type { + BrainDoc, + BrainChunk, + BrainEmbedding, + BrainChatLink, +} from "../db/brain.js"; diff --git a/src/brain/manager.ts b/src/brain/manager.ts index 2ce8140..0bb34bb 100644 --- a/src/brain/manager.ts +++ b/src/brain/manager.ts @@ -74,7 +74,7 @@ export class BrainManager { "processing", 0, now, - now + now, ); // Chunk and store @@ -118,7 +118,7 @@ export class BrainManager { "processing", 0, now, - now + now, ); // Chunk and store @@ -166,7 +166,7 @@ export class BrainManager { "processing", 0, now, - now + now, ); // Chunk and store @@ -201,7 +201,12 @@ export class BrainManager { */ listDocs( userId: string, - options: { limit?: number; offset?: number; sourceType?: string; status?: string } = {} + options: { + limit?: number; + offset?: number; + sourceType?: string; + status?: string; + } = {}, ): BrainDoc[] { const rawDb = this.db.getRawDb(); const { limit = 50, offset = 0, sourceType, status } = options; @@ -387,7 +392,9 @@ export class BrainManager { } { const rawDb = this.db.getRawDb(); - const docsStmt = rawDb.prepare("SELECT COUNT(*) as count FROM brain_docs WHERE user_id = ?"); + const docsStmt = rawDb.prepare( + "SELECT COUNT(*) as count FROM brain_docs WHERE user_id = ?", + ); const docsRow = docsStmt.get(userId) as any; const chunksStmt = rawDb.prepare(` @@ -413,7 +420,9 @@ export class BrainManager { return { totalDocs: docsRow.count, totalChunks: chunksRow.count, - bySourceType: Object.fromEntries(typeRows.map((r) => [r.source_type, r.count])), + bySourceType: Object.fromEntries( + typeRows.map((r) => [r.source_type, r.count]), + ), byStatus: Object.fromEntries(statusRows.map((r) => [r.status, r.count])), }; } @@ -423,7 +432,9 @@ export class BrainManager { /** * Split text into overlapping chunks */ - private chunkText(text: string): { content: string; start: number; end: number }[] { + private chunkText( + text: string, + ): { content: string; start: number; end: number }[] { const chunks: { content: string; start: number; end: number }[] = []; if (text.length <= CHUNK_SIZE) { @@ -462,7 +473,10 @@ export class BrainManager { /** * Store chunks in database */ - private storeChunks(docId: string, chunks: { content: string; start: number; end: number }[]): void { + private storeChunks( + docId: string, + chunks: { content: string; start: number; end: number }[], + ): void { const rawDb = this.db.getRawDb(); const now = new Date().toISOString(); @@ -475,7 +489,16 @@ export class BrainManager { for (let i = 0; i < chunkList.length; i++) { const chunk = chunkList[i]; const tokenCount = Math.ceil(chunk.content.length / 4); // rough estimate - stmt.run(randomUUID(), docId, i, chunk.content, tokenCount, chunk.start, chunk.end, now); + stmt.run( + randomUUID(), + docId, + i, + chunk.content, + tokenCount, + chunk.start, + chunk.end, + now, + ); } }); diff --git a/src/brain/search.ts b/src/brain/search.ts index 9acd452..a579a17 100644 --- a/src/brain/search.ts +++ b/src/brain/search.ts @@ -41,7 +41,9 @@ export class BrainSearch { this.openai = new OpenAI({ apiKey }); this.enabled = true; } else { - console.warn("⚠️ OPENAI_API_KEY not set - brain semantic search disabled"); + console.warn( + "⚠️ OPENAI_API_KEY not set - brain semantic search disabled", + ); } } @@ -77,7 +79,11 @@ export class BrainSearch { /** * Store embedding for a chunk */ - async storeChunkEmbedding(chunkId: string, docId: string, text: string): Promise { + async storeChunkEmbedding( + chunkId: string, + docId: string, + text: string, + ): Promise { if (!this.enabled) { return; } @@ -92,7 +98,14 @@ export class BrainSearch { VALUES (?, ?, ?, ?, ?, ?) `); - stmt.run(chunkId, docId, JSON.stringify(embedding), this.embeddingModel, embedding.length, now); + stmt.run( + chunkId, + docId, + JSON.stringify(embedding), + this.embeddingModel, + embedding.length, + now, + ); } catch (error) { console.error("Failed to store chunk embedding:", error); } @@ -125,7 +138,10 @@ export class BrainSearch { // Rate limiting await new Promise((resolve) => setTimeout(resolve, 100)); } catch (error) { - console.error(`Failed to generate embedding for chunk ${row.id}:`, error); + console.error( + `Failed to generate embedding for chunk ${row.id}:`, + error, + ); } } @@ -135,7 +151,11 @@ export class BrainSearch { /** * Semantic search in knowledge base */ - async semanticSearch(query: string, userId: string, limit: number = 10): Promise { + async semanticSearch( + query: string, + userId: string, + limit: number = 10, + ): Promise { if (!this.enabled) { return []; } @@ -186,18 +206,27 @@ export class BrainSearch { /** * Keyword search in knowledge base */ - keywordSearch(query: string, userId: string, limit: number = 20): KeywordSearchResult[] { + keywordSearch( + query: string, + userId: string, + limit: number = 20, + ): KeywordSearchResult[] { const rawDb = this.db.getRawDb(); // Split query into words for matching - const words = query.toLowerCase().split(/\s+/).filter((w) => w.length > 2); + const words = query + .toLowerCase() + .split(/\s+/) + .filter((w) => w.length > 2); if (words.length === 0) { return []; } // Build LIKE conditions for each word - const likeConditions = words.map(() => "(LOWER(d.content) LIKE ? OR LOWER(d.title) LIKE ?)").join(" AND "); + const likeConditions = words + .map(() => "(LOWER(d.content) LIKE ? OR LOWER(d.title) LIKE ?)") + .join(" AND "); const params: any[] = [userId]; words.forEach((word) => { @@ -227,8 +256,10 @@ export class BrainSearch { const titleLower = (row.title || "").toLowerCase(); words.forEach((word) => { - const contentMatches = (contentLower.match(new RegExp(word, "g")) || []).length; - const titleMatches = (titleLower.match(new RegExp(word, "g")) || []).length; + const contentMatches = (contentLower.match(new RegExp(word, "g")) || []) + .length; + const titleMatches = (titleLower.match(new RegExp(word, "g")) || []) + .length; matchCount += contentMatches + titleMatches; }); @@ -252,7 +283,7 @@ export class BrainSearch { async hybridSearch( query: string, userId: string, - options: { semanticWeight?: number; limit?: number } = {} + options: { semanticWeight?: number; limit?: number } = {}, ): Promise { const { semanticWeight = 0.7, limit = 10 } = options; @@ -263,7 +294,10 @@ export class BrainSearch { const keywordResults = this.keywordSearch(query, userId, limit * 2); // Combine and re-rank - const scoreMap = new Map(); + const scoreMap = new Map< + string, + { result: BrainSearchResult; score: number } + >(); // Add semantic scores semanticResults.forEach((result, index) => { @@ -273,10 +307,13 @@ export class BrainSearch { // Add keyword scores keywordResults.forEach((kwResult, index) => { - const keywordScore = (1 - index / keywordResults.length) * (1 - semanticWeight); + const keywordScore = + (1 - index / keywordResults.length) * (1 - semanticWeight); // Find matching chunks from this doc - const docChunks = semanticResults.filter((r) => r.docId === kwResult.docId); + const docChunks = semanticResults.filter( + (r) => r.docId === kwResult.docId, + ); if (docChunks.length > 0) { // Boost existing chunks @@ -301,7 +338,11 @@ export class BrainSearch { /** * Find similar documents */ - async findSimilar(docId: string, userId: string, limit: number = 5): Promise { + async findSimilar( + docId: string, + userId: string, + limit: number = 5, + ): Promise { if (!this.enabled) { return []; } @@ -410,7 +451,8 @@ export class BrainSearch { return { totalEmbeddings, totalDocs, - embeddingCoverage: totalChunks > 0 ? Math.round((totalEmbeddings / totalChunks) * 100) : 0, + embeddingCoverage: + totalChunks > 0 ? Math.round((totalEmbeddings / totalChunks) * 100) : 0, }; } @@ -447,7 +489,11 @@ export class BrainSearch { /** * Generate snippet around a keyword */ - private generateSnippet(text: string, keyword: string, maxLength: number): string { + private generateSnippet( + text: string, + keyword: string, + maxLength: number, + ): string { const lowerText = text.toLowerCase(); const lowerKeyword = keyword.toLowerCase(); const index = lowerText.indexOf(lowerKeyword); diff --git a/src/chat/manager.ts b/src/chat/manager.ts index d7f1872..38c7059 100644 --- a/src/chat/manager.ts +++ b/src/chat/manager.ts @@ -4,12 +4,7 @@ */ import { ChatStorage } from "./storage.ts"; -import type { - ChatRequest, - ChatResponse, - ChatMessage, - Chat, -} from "./types.ts"; +import type { ChatRequest, ChatResponse, ChatMessage, Chat } from "./types.ts"; import { costTracker } from "../billing/costTracker.ts"; import { selectModel } from "../billing/modelSelector.ts"; import { agentTools, executeTool } from "./tools.ts"; @@ -69,7 +64,9 @@ export class ChatManager { }); // Track chat metrics - metrics.increment("chat.messages.received", 1, { agentName: request.agentName }); + metrics.increment("chat.messages.received", 1, { + agentName: request.agentName, + }); metrics.set("chat.users.active", request.userId); // Get or create chat @@ -83,12 +80,12 @@ export class ChatManager { } else { // Create new chat with auto-generated title const title = this.generateTitle(request.message); - chat = this.storage.createChat( - request.userId, - title, - request.agentName - ); - log.info("New chat created", { chatId: chat.id, userId: request.userId, agentName: request.agentName }); + chat = this.storage.createChat(request.userId, title, request.agentName); + log.info("New chat created", { + chatId: chat.id, + userId: request.userId, + agentName: request.agentName, + }); } // Store user message @@ -100,14 +97,15 @@ export class ChatManager { // Get chat history for context (if requested) const history: ChatMessage[] = request.includeHistory - ? this.storage.getRecentMessages( - chat.id, - request.maxHistory || 10 - ) + ? this.storage.getRecentMessages(chat.id, request.maxHistory || 10) : []; // Build prompt with history - const prompt = this.buildPrompt(request.message, history, request.agentName); + const prompt = this.buildPrompt( + request.message, + history, + request.agentName, + ); // Select optimal model const modelRecommendation = selectModel(request.message, { @@ -122,7 +120,7 @@ export class ChatManager { providerToUse, modelToUse, prompt, - request.agentName + request.agentName, ); // Store assistant response @@ -156,11 +154,20 @@ export class ChatManager { }); // Track AI usage metrics - metrics.increment("ai.requests.total", 1, { provider: providerToUse, model: modelToUse }); - metrics.distribution("ai.tokens.input", aiResponse.tokens.input, { provider: providerToUse }); - metrics.distribution("ai.tokens.output", aiResponse.tokens.output, { provider: providerToUse }); + metrics.increment("ai.requests.total", 1, { + provider: providerToUse, + model: modelToUse, + }); + metrics.distribution("ai.tokens.input", aiResponse.tokens.input, { + provider: providerToUse, + }); + metrics.distribution("ai.tokens.output", aiResponse.tokens.output, { + provider: providerToUse, + }); if (aiResponse.cost?.usd) { - metrics.distribution("ai.cost.usd", aiResponse.cost.usd * 100, { provider: providerToUse }); // in cents + metrics.distribution("ai.cost.usd", aiResponse.cost.usd * 100, { + provider: providerToUse, + }); // in cents } } @@ -245,9 +252,7 @@ export class ChatManager { private generateTitle(message: string): string { // Take first 50 chars or first sentence const shortened = message.substring(0, 50); - return shortened.length < message.length - ? shortened + "..." - : shortened; + return shortened.length < message.length ? shortened + "..." : shortened; } /** @@ -256,7 +261,7 @@ export class ChatManager { private buildPrompt( message: string, history: ChatMessage[], - agentName: string + agentName: string, ): string { let prompt = ""; @@ -292,7 +297,7 @@ export class ChatManager { const preferred = preferredProvider.toLowerCase(); if (allProviders.includes(preferred)) { - return [preferred, ...allProviders.filter(p => p !== preferred)]; + return [preferred, ...allProviders.filter((p) => p !== preferred)]; } return allProviders; } @@ -306,7 +311,7 @@ export class ChatManager { provider: string, model: string, prompt: string, - agentName: string + agentName: string, ): Promise<{ content: string; tokens?: { input: number; output: number; total: number }; @@ -315,75 +320,76 @@ export class ChatManager { console.log(`[Chat] Calling ${provider}/${model} for agent ${agentName}`); // Wrap AI call with Sentry tracking - return trackAICall( - { provider, model, agentName, prompt }, - async () => { - const providers = this.getProviderOrder(provider); - let lastError: Error | null = null; - - for (const p of providers) { - try { - console.log(`[Chat] Trying provider: ${p}`); - - switch (p.toLowerCase()) { - case "anthropic": - if (!process.env.ANTHROPIC_API_KEY) { - console.log("[Chat] Anthropic: No API key, skipping"); - continue; - } - return await this.callAnthropic(model, prompt, agentName); - - case "openai": - if (!process.env.OPENAI_API_KEY) { - console.log("[Chat] OpenAI: No API key, skipping"); - continue; - } - // Use gpt-4o as fallback model - return await this.callOpenAI("gpt-4o", prompt, agentName); - - case "gemini": - if (!process.env.GEMINI_API_KEY) { - console.log("[Chat] Gemini: No API key, skipping"); - continue; - } - return await this.callGemini(model, prompt, agentName); - - default: + return trackAICall({ provider, model, agentName, prompt }, async () => { + const providers = this.getProviderOrder(provider); + let lastError: Error | null = null; + + for (const p of providers) { + try { + console.log(`[Chat] Trying provider: ${p}`); + + switch (p.toLowerCase()) { + case "anthropic": + if (!process.env.ANTHROPIC_API_KEY) { + console.log("[Chat] Anthropic: No API key, skipping"); + continue; + } + return await this.callAnthropic(model, prompt, agentName); + + case "openai": + if (!process.env.OPENAI_API_KEY) { + console.log("[Chat] OpenAI: No API key, skipping"); + continue; + } + // Use gpt-4o as fallback model + return await this.callOpenAI("gpt-4o", prompt, agentName); + + case "gemini": + if (!process.env.GEMINI_API_KEY) { + console.log("[Chat] Gemini: No API key, skipping"); continue; - } - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - console.warn(`[Chat] ${p} failed: ${errorMessage}`); - - // Check if it's a rate limit error - try next provider - if (errorMessage.includes("429") || errorMessage.includes("rate_limit")) { - console.log(`[Chat] ${p} rate limited, trying next provider...`); - log.warn("AI provider rate limited, trying fallback", { - provider: p, - error: errorMessage, - }); - } else { - log.error("AI provider call failed", { - provider: p, - model, - agentName, - error: errorMessage, - }); - } - - lastError = error instanceof Error ? error : new Error(errorMessage); - continue; // Try next provider + } + return await this.callGemini(model, prompt, agentName); + + default: + continue; + } + } catch (error: unknown) { + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + console.warn(`[Chat] ${p} failed: ${errorMessage}`); + + // Check if it's a rate limit error - try next provider + if ( + errorMessage.includes("429") || + errorMessage.includes("rate_limit") + ) { + console.log(`[Chat] ${p} rate limited, trying next provider...`); + log.warn("AI provider rate limited, trying fallback", { + provider: p, + error: errorMessage, + }); + } else { + log.error("AI provider call failed", { + provider: p, + model, + agentName, + error: errorMessage, + }); } - } - // All providers failed - log.error("All AI providers failed", { - attemptedProviders: providers.join(", "), - lastError: lastError?.message, - }); - throw lastError || new Error("All AI providers failed"); + lastError = error instanceof Error ? error : new Error(errorMessage); + continue; // Try next provider + } } - ); + + // All providers failed + log.error("All AI providers failed", { + attemptedProviders: providers.join(", "), + lastError: lastError?.message, + }); + throw lastError || new Error("All AI providers failed"); + }); } /** @@ -392,7 +398,7 @@ export class ChatManager { private async callAnthropic( model: string, prompt: string, - _agentName: string + _agentName: string, ): Promise<{ content: string; tokens: { input: number; output: number; total: number }; @@ -444,7 +450,7 @@ export class ChatManager { }); const result = await executeTool( block.name, - block.input as Record + block.input as Record, ); log.debug("Tool execution completed", { tool: block.name, @@ -512,7 +518,7 @@ export class ChatManager { private async callOpenAI( model: string, prompt: string, - agentName: string + agentName: string, ): Promise<{ content: string; tokens: { input: number; output: number; total: number }; @@ -569,7 +575,7 @@ export class ChatManager { private async callGemini( model: string, prompt: string, - agentName: string + agentName: string, ): Promise<{ content: string; tokens: { input: number; output: number; total: number }; diff --git a/src/chat/storage.ts b/src/chat/storage.ts index eb47960..603e15c 100644 --- a/src/chat/storage.ts +++ b/src/chat/storage.ts @@ -39,7 +39,7 @@ export class ChatStorage { this.db .prepare( `INSERT INTO chats (id, user_id, title, agent_name, created_at, updated_at, message_count) - VALUES (?, ?, ?, ?, ?, ?, 0)` + VALUES (?, ?, ?, ?, ?, ?, 0)`, ) .run(id, userId, title, agentName, now, now); @@ -59,7 +59,7 @@ export class ChatStorage { const row = this.db .prepare( `SELECT id, user_id, title, agent_name, created_at, updated_at, message_count, last_message - FROM chats WHERE id = ?` + FROM chats WHERE id = ?`, ) .get(chatId) as ChatRow | undefined; @@ -85,7 +85,7 @@ export class ChatStorage { FROM chats WHERE user_id = ? ORDER BY updated_at DESC - LIMIT ? OFFSET ?` + LIMIT ? OFFSET ?`, ) .all(userId, limit, offset); @@ -114,15 +114,13 @@ export class ChatStorage { const id = randomUUID(); const timestamp = new Date().toISOString(); - const metadata = message.metadata - ? JSON.stringify(message.metadata) - : null; + const metadata = message.metadata ? JSON.stringify(message.metadata) : null; this.db .prepare( `INSERT INTO chat_messages (id, chat_id, role, content, agent_name, timestamp, input_tokens, output_tokens, total_tokens, metadata) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ) .run( id, @@ -134,7 +132,7 @@ export class ChatStorage { message.tokens?.input || null, message.tokens?.output || null, message.tokens?.total || null, - metadata + metadata, ); // Update chat metadata @@ -144,13 +142,9 @@ export class ChatStorage { SET updated_at = ?, message_count = message_count + 1, last_message = ? - WHERE id = ?` + WHERE id = ?`, ) - .run( - timestamp, - message.content.substring(0, 100), - message.chatId - ); + .run(timestamp, message.content.substring(0, 100), message.chatId); return { id, @@ -160,11 +154,7 @@ export class ChatStorage { } // Get messages for chat - getMessages( - chatId: string, - limit = 50, - offset = 0 - ): ChatMessage[] { + getMessages(chatId: string, limit = 50, offset = 0): ChatMessage[] { const rows = this.db .prepare( `SELECT id, chat_id, role, content, agent_name, timestamp, @@ -172,7 +162,7 @@ export class ChatStorage { FROM chat_messages WHERE chat_id = ? ORDER BY timestamp ASC - LIMIT ? OFFSET ?` + LIMIT ? OFFSET ?`, ) .all(chatId, limit, offset); @@ -203,7 +193,7 @@ export class ChatStorage { FROM chat_messages WHERE chat_id = ? ORDER BY timestamp DESC - LIMIT ?` + LIMIT ?`, ) .all(chatId, limit); diff --git a/src/chat/tools.ts b/src/chat/tools.ts index 191fc61..881ed27 100644 --- a/src/chat/tools.ts +++ b/src/chat/tools.ts @@ -86,7 +86,7 @@ export const agentTools: Anthropic.Tool[] = [ */ export async function executeTool( toolName: string, - toolInput: Record + toolInput: Record, ): Promise { try { switch (toolName) { @@ -95,12 +95,12 @@ export async function executeTool( case "list_files": return listFiles( toolInput.directory as string, - toolInput.recursive as boolean + toolInput.recursive as boolean, ); case "search_code": return searchCode( toolInput.query as string, - toolInput.file_pattern as string | undefined + toolInput.file_pattern as string | undefined, ); case "get_project_structure": return getProjectStructure(); @@ -164,7 +164,7 @@ function listFiles(directory: string, recursive = false): string { // Skip node_modules and hidden directories const filtered = entries.filter( - (e) => !e.name.startsWith(".") && e.name !== "node_modules" + (e) => !e.name.startsWith(".") && e.name !== "node_modules", ); for (const entry of filtered) { @@ -186,8 +186,10 @@ function listFiles(directory: string, recursive = false): string { return "Directory is empty or contains only hidden files/node_modules"; } - return results.slice(0, 100).join("\n") + - (results.length > 100 ? `\n... and ${results.length - 100} more files` : ""); + return ( + results.slice(0, 100).join("\n") + + (results.length > 100 ? `\n... and ${results.length - 100} more files` : "") + ); } /** @@ -222,8 +224,19 @@ function searchCode(query: string, filePattern?: string): string { // Only search text files const ext = path.extname(entry.name).toLowerCase(); const textExts = [ - ".ts", ".tsx", ".js", ".jsx", ".json", ".md", ".css", - ".html", ".yaml", ".yml", ".toml", ".env", ".sh", + ".ts", + ".tsx", + ".js", + ".jsx", + ".json", + ".md", + ".css", + ".html", + ".yaml", + ".yml", + ".toml", + ".env", + ".sh", ]; if (!textExts.includes(ext) && ext !== "") { continue; @@ -260,9 +273,7 @@ function searchCode(query: string, filePattern?: string): string { return `No matches found for: ${query}`; } - return results - .map((r) => `${r.file}:${r.line}: ${r.content}`) - .join("\n"); + return results.map((r) => `${r.file}:${r.line}: ${r.content}`).join("\n"); } /** @@ -277,7 +288,11 @@ function getProjectStructure(): string { const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8")); structure.push(`📦 Project: ${pkg.name || "unknown"}`); structure.push(`📝 Description: ${pkg.description || "N/A"}`); - structure.push(`🔧 Main dependencies: ${Object.keys(pkg.dependencies || {}).slice(0, 10).join(", ")}`); + structure.push( + `🔧 Main dependencies: ${Object.keys(pkg.dependencies || {}) + .slice(0, 10) + .join(", ")}`, + ); structure.push(""); } @@ -285,7 +300,7 @@ function getProjectStructure(): string { structure.push("📁 Project Structure:"); const entries = fs.readdirSync(PROJECT_ROOT, { withFileTypes: true }); const filtered = entries.filter( - (e) => !e.name.startsWith(".") && e.name !== "node_modules" + (e) => !e.name.startsWith(".") && e.name !== "node_modules", ); for (const entry of filtered) { @@ -299,7 +314,9 @@ function getProjectStructure(): string { return stat.isDirectory() && !c.startsWith("."); }); if (childDirs.length > 0) { - structure.push(` └── ${childDirs.slice(0, 5).join(", ")}${childDirs.length > 5 ? "..." : ""}`); + structure.push( + ` └── ${childDirs.slice(0, 5).join(", ")}${childDirs.length > 5 ? "..." : ""}`, + ); } } catch { // Skip if can't read diff --git a/src/components/ActivityLog.tsx b/src/components/ActivityLog.tsx index 32c4156..e6a7c74 100644 --- a/src/components/ActivityLog.tsx +++ b/src/components/ActivityLog.tsx @@ -1,13 +1,13 @@ -import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; -import { ScrollArea } from './ui/scroll-area'; -import { Badge } from './ui/badge'; +import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; +import { ScrollArea } from "./ui/scroll-area"; +import { Badge } from "./ui/badge"; interface LogEntry { id: string; timestamp: string; agent: string; message: string; - level: 'info' | 'warning' | 'error' | 'success'; + level: "info" | "warning" | "error" | "success"; } interface ActivityLogProps { @@ -16,10 +16,10 @@ interface ActivityLogProps { export function ActivityLog({ logs }: ActivityLogProps) { const levelColors = { - info: 'bg-blue-500', - warning: 'bg-yellow-500', - error: 'bg-red-500', - success: 'bg-green-500', + info: "bg-blue-500", + warning: "bg-yellow-500", + error: "bg-red-500", + success: "bg-green-500", }; return ( @@ -35,10 +35,14 @@ export function ActivityLog({ logs }: ActivityLogProps) { key={log.id} className="flex items-start gap-3 p-3 rounded-lg bg-muted/50 hover:bg-muted transition-colors" > -
+
- {log.timestamp} + + {log.timestamp} + {log.agent} diff --git a/src/components/AgentCard.tsx b/src/components/AgentCard.tsx index 4b64ced..076dd57 100644 --- a/src/components/AgentCard.tsx +++ b/src/components/AgentCard.tsx @@ -1,13 +1,19 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card'; -import { Badge } from './ui/badge'; -import { Button } from './ui/button'; -import { Play, Pause, Settings, Trash2, Activity } from 'lucide-react'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "./ui/card"; +import { Badge } from "./ui/badge"; +import { Button } from "./ui/button"; +import { Play, Pause, Settings, Trash2, Activity } from "lucide-react"; interface AgentCardProps { id: string; name: string; description: string; - status: 'active' | 'paused' | 'stopped'; + status: "active" | "paused" | "stopped"; language: string; lastRun: string; executionCount: number; @@ -31,9 +37,9 @@ export function AgentCard({ onDelete, }: AgentCardProps) { const statusColors = { - active: 'bg-green-500', - paused: 'bg-yellow-500', - stopped: 'bg-gray-500', + active: "bg-green-500", + paused: "bg-yellow-500", + stopped: "bg-gray-500", }; return ( @@ -67,18 +73,32 @@ export function AgentCard({ {lastRun}
- {status === 'active' ? ( - ) : ( - )} -
- +
@@ -60,7 +102,9 @@ export function SettingsPanel() { Cloud Configuration - Manage your cloud provider settings + + Manage your cloud provider settings +
@@ -78,13 +122,25 @@ export function SettingsPanel() {
- +
- +
- +
@@ -93,7 +149,9 @@ export function SettingsPanel() { Security Settings - Configure security and access controls + + Configure security and access controls +
@@ -123,7 +181,10 @@ export function SettingsPanel() { Track all actions in your workspace

- +
@@ -133,7 +194,9 @@ export function SettingsPanel() { Notification Preferences - Choose what updates you want to receive + + Choose what updates you want to receive +
@@ -143,7 +206,10 @@ export function SettingsPanel() { Get notified when agents start or stop

- +
@@ -153,7 +219,10 @@ export function SettingsPanel() { Receive alerts when agents encounter errors

- +
@@ -168,7 +237,12 @@ export function SettingsPanel() {
- +
diff --git a/src/components/StatsCard.tsx b/src/components/StatsCard.tsx index 604330c..11c55f0 100644 --- a/src/components/StatsCard.tsx +++ b/src/components/StatsCard.tsx @@ -1,6 +1,12 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card'; -import { Badge } from './ui/badge'; -import { TrendingUp, TrendingDown } from 'lucide-react'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "./ui/card"; +import { Badge } from "./ui/badge"; +import { TrendingUp, TrendingDown } from "lucide-react"; interface StatsCardProps { title: string; @@ -8,12 +14,18 @@ interface StatsCardProps { description: string; trend?: { value: number; - direction: 'up' | 'down'; + direction: "up" | "down"; }; icon: React.ReactNode; } -export function StatsCard({ title, value, description, trend, icon }: StatsCardProps) { +export function StatsCard({ + title, + value, + description, + trend, + icon, +}: StatsCardProps) { return ( @@ -25,8 +37,11 @@ export function StatsCard({ title, value, description, trend, icon }: StatsCardP {description} {trend && ( - - {trend.direction === 'up' ? ( + + {trend.direction === "up" ? ( ) : ( diff --git a/src/components/StatusDashboard.tsx b/src/components/StatusDashboard.tsx index df62c52..428a733 100644 --- a/src/components/StatusDashboard.tsx +++ b/src/components/StatusDashboard.tsx @@ -1,7 +1,7 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState } from "react"; interface Stats { - serverStatus: 'online' | 'offline'; + serverStatus: "online" | "offline"; userCount: number; agentCount: number; chatCount: number; @@ -12,7 +12,7 @@ interface Stats { */ function getStoredUser(): { id: string; role: string } | null { try { - const userStr = localStorage.getItem('user'); + const userStr = localStorage.getItem("user"); if (!userStr) return null; return JSON.parse(userStr); } catch { @@ -27,14 +27,14 @@ function getAuthHeaders(): Record { const user = getStoredUser(); if (!user) return {}; return { - 'x-user-id': user.id, - 'x-user-role': user.role, + "x-user-id": user.id, + "x-user-role": user.role, }; } export function StatusDashboard() { const [stats, setStats] = useState({ - serverStatus: 'offline', + serverStatus: "offline", userCount: 0, agentCount: 5, // Fixed: 5 agents available chatCount: 0, @@ -46,26 +46,26 @@ export function StatusDashboard() { const headers = getAuthHeaders(); // 1. Health Check - fetch('/api/health') - .then(res => res.json()) + fetch("/api/health") + .then((res) => res.json()) .then(() => { if (!cancelled) { - setStats(prev => ({ ...prev, serverStatus: 'online' })); + setStats((prev) => ({ ...prev, serverStatus: "online" })); } }) .catch(() => { if (!cancelled) { - setStats(prev => ({ ...prev, serverStatus: 'offline' })); + setStats((prev) => ({ ...prev, serverStatus: "offline" })); } }); // 2. User Count (from /api/users/stats - requires admin) - if (headers['x-user-role'] === 'admin') { - fetch('/api/users/stats', { headers }) - .then(res => res.json()) - .then(data => { + if (headers["x-user-role"] === "admin") { + fetch("/api/users/stats", { headers }) + .then((res) => res.json()) + .then((data) => { if (!cancelled && data.stats) { - setStats(prev => ({ ...prev, userCount: data.stats.total || 0 })); + setStats((prev) => ({ ...prev, userCount: data.stats.total || 0 })); } }) .catch(() => { @@ -75,12 +75,12 @@ export function StatusDashboard() { // 3. Memory Stats const user = getStoredUser(); - const userId = user?.id || 'test-user'; + const userId = user?.id || "test-user"; fetch(`/api/memory/stats/${userId}`, { headers }) - .then(res => res.json()) - .then(data => { + .then((res) => res.json()) + .then((data) => { if (!cancelled) { - setStats(prev => ({ ...prev, chatCount: data.totalChats || 0 })); + setStats((prev) => ({ ...prev, chatCount: data.totalChats || 0 })); setLoading(false); } }) @@ -113,7 +113,9 @@ export function StatusDashboard() { {/* Server Status Card */}

Server Status

-

+

{stats.serverStatus}

diff --git a/src/components/TaskBoard.tsx b/src/components/TaskBoard.tsx index c78db93..f5ead78 100644 --- a/src/components/TaskBoard.tsx +++ b/src/components/TaskBoard.tsx @@ -1,9 +1,9 @@ -import { useState, useEffect } from 'react'; -import { Button } from './ui/button'; -import { Input } from './ui/input'; -import { Textarea } from './ui/textarea'; -import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'; +import { useState, useEffect } from "react"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; +import { Textarea } from "./ui/textarea"; +import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"; import { Dialog, DialogContent, @@ -11,24 +11,24 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from './ui/dialog'; -import { Label } from './ui/label'; +} from "./ui/dialog"; +import { Label } from "./ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from './ui/select'; -import { toast } from 'sonner'; -import { Plus, CheckCircle2, Circle, XCircle, Clock } from 'lucide-react'; +} from "./ui/select"; +import { toast } from "sonner"; +import { Plus, CheckCircle2, Circle, XCircle, Clock } from "lucide-react"; interface Task { id: string; title: string; description: string; - status: 'future' | 'active' | 'finished' | 'cancelled'; - priority: 'low' | 'medium' | 'high'; + status: "future" | "active" | "finished" | "cancelled"; + priority: "low" | "medium" | "high"; agent?: string; repository?: string; model?: string; @@ -56,14 +56,14 @@ export function TaskBoard() { const [isLoading, setIsLoading] = useState(true); const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [newTask, setNewTask] = useState({ - title: '', - description: '', - priority: 'medium' as Task['priority'], - agent: 'emir', - repository: '', - model: 'claude-3.5-sonnet', + title: "", + description: "", + priority: "medium" as Task["priority"], + agent: "emir", + repository: "", + model: "claude-3.5-sonnet", tags: [] as string[], - dueDate: '', + dueDate: "", artefacts: [] as string[], }); @@ -74,35 +74,47 @@ export function TaskBoard() { const fetchAgents = async () => { try { - const response = await fetch('/api/chat/agents'); - if (!response.ok) throw new Error('Failed to fetch agents'); + const response = await fetch("/api/chat/agents"); + if (!response.ok) throw new Error("Failed to fetch agents"); const data = await response.json(); setAgents(data.agents || []); } catch (error) { - toast.error('Failed to load agents'); + toast.error("Failed to load agents"); // Fallback agents setAgents([ - { name: 'emir', displayName: 'Emir (Supervisor)', description: 'Lead supervisor' }, - { name: 'planner', displayName: 'Planner', description: 'Task planning' }, - { name: 'coder', displayName: 'Coder', description: 'Code implementation' }, + { + name: "emir", + displayName: "Emir (Supervisor)", + description: "Lead supervisor", + }, + { + name: "planner", + displayName: "Planner", + description: "Task planning", + }, + { + name: "coder", + displayName: "Coder", + description: "Code implementation", + }, ]); } }; const fetchTasks = async () => { try { - const response = await fetch('/api/tasks/', { + const response = await fetch("/api/tasks/", { headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}`, + Authorization: `Bearer ${localStorage.getItem("token")}`, }, }); - if (!response.ok) throw new Error('Failed to fetch tasks'); + if (!response.ok) throw new Error("Failed to fetch tasks"); const data = await response.json(); setTasks(data.tasks || []); } catch (error) { - toast.error('Failed to load tasks'); + toast.error("Failed to load tasks"); } finally { setIsLoading(false); } @@ -110,94 +122,98 @@ export function TaskBoard() { const createTask = async () => { if (!newTask.title.trim()) { - toast.error('Please enter a task title'); + toast.error("Please enter a task title"); return; } try { - const response = await fetch('/api/tasks/', { - method: 'POST', + const response = await fetch("/api/tasks/", { + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}`, + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, }, body: JSON.stringify({ ...newTask, - status: 'future', + status: "future", }), }); - if (!response.ok) throw new Error('Failed to create task'); + if (!response.ok) throw new Error("Failed to create task"); const data = await response.json(); setTasks((prev) => [...prev, data.task]); setNewTask({ - title: '', - description: '', - priority: 'medium', - agent: 'emir', - repository: '', - model: 'claude-3.5-sonnet', + title: "", + description: "", + priority: "medium", + agent: "emir", + repository: "", + model: "claude-3.5-sonnet", tags: [], - dueDate: '', + dueDate: "", artefacts: [], }); setIsCreateDialogOpen(false); - toast.success('Task created successfully'); + toast.success("Task created successfully"); } catch (error) { - toast.error(error instanceof Error ? error.message : 'Failed to create task'); + toast.error( + error instanceof Error ? error.message : "Failed to create task", + ); } }; - const updateTaskStatus = async (taskId: string, status: Task['status']) => { + const updateTaskStatus = async (taskId: string, status: Task["status"]) => { try { const response = await fetch(`/api/tasks/${taskId}/submit`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}`, + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, }, body: JSON.stringify({ status }), }); - if (!response.ok) throw new Error('Failed to update task'); + if (!response.ok) throw new Error("Failed to update task"); setTasks((prev) => prev.map((task) => - task.id === taskId ? { ...task, status, updatedAt: new Date().toISOString() } : task - ) + task.id === taskId + ? { ...task, status, updatedAt: new Date().toISOString() } + : task, + ), ); - toast.success('Task updated'); + toast.success("Task updated"); } catch (error) { - toast.error('Failed to update task'); + toast.error("Failed to update task"); } }; - const getTasksByStatus = (status: Task['status']) => { + const getTasksByStatus = (status: Task["status"]) => { return tasks.filter((task) => task.status === status); }; - const getStatusIcon = (status: Task['status']) => { + const getStatusIcon = (status: Task["status"]) => { switch (status) { - case 'future': + case "future": return ; - case 'active': + case "active": return ; - case 'finished': + case "finished": return ; - case 'cancelled': + case "cancelled": return ; } }; - const getPriorityColor = (priority: Task['priority']) => { + const getPriorityColor = (priority: Task["priority"]) => { switch (priority) { - case 'high': - return 'border-l-red-500'; - case 'medium': - return 'border-l-yellow-500'; - case 'low': - return 'border-l-green-500'; + case "high": + return "border-l-red-500"; + case "medium": + return "border-l-yellow-500"; + case "low": + return "border-l-green-500"; } }; @@ -216,7 +232,10 @@ export function TaskBoard() {
Task Management - + )} - {status === 'active' && ( + {status === "active" && ( <> - {error &&
{error}
} + {error &&
{error}
} ); } @@ -131,10 +131,10 @@ export function CreateTaskForm() { export async function checkBackendHealth() { // Type-safe API call with operationId: health_get_health - const { data, error } = await api.GET('/health'); + const { data, error } = await api.GET("/health"); if (error) { - return { healthy: false, error: 'Backend unreachable' }; + return { healthy: false, error: "Backend unreachable" }; } return { healthy: true, data }; @@ -146,16 +146,16 @@ export async function checkBackendHealth() { export async function approveTask(taskId: string, reason: string) { // Type-safe API call with operationId: enforcement_post_api_enforcement_approve - const { data, error } = await api.POST('/api/enforcement/approve', { + const { data, error } = await api.POST("/api/enforcement/approve", { body: { taskId, - approvedBy: 'current-user', + approvedBy: "current-user", reason, }, }); if (error) { - throw new Error('Failed to approve task'); + throw new Error("Failed to approve task"); } return data; diff --git a/src/lib/events.ts b/src/lib/events.ts index 3a91631..2ec47c6 100644 --- a/src/lib/events.ts +++ b/src/lib/events.ts @@ -3,7 +3,11 @@ * Single source of truth for all system events */ -import type { AuditService, AuditEventKind, AuditSeverity } from "../db/audit-events.js"; +import type { + AuditService, + AuditEventKind, + AuditSeverity, +} from "../db/audit-events.js"; let auditService: AuditService | null = null; @@ -29,13 +33,17 @@ export function recordEvent( userId?: string; severity?: AuditSeverity; meta?: Record; - } + }, ): void { const severity = options?.severity ?? "info"; - const prefix = severity === "error" ? "❌" : severity === "warn" ? "⚠️" : "📝"; + const prefix = + severity === "error" ? "❌" : severity === "warn" ? "⚠️" : "📝"; // Always log to console for visibility - console.log(`${prefix} [${kind}] ${message}`, options?.meta ? JSON.stringify(options.meta) : ""); + console.log( + `${prefix} [${kind}] ${message}`, + options?.meta ? JSON.stringify(options.meta) : "", + ); // Persist to database if available if (auditService) { @@ -69,7 +77,11 @@ export const events = { recordEvent("task_finished", `Task completed`, { taskId, agentId }), taskFailed: (taskId: string, error: string, agentId?: string) => - recordEvent("task_failed", `Task failed: ${error}`, { taskId, agentId, severity: "error" }), + recordEvent("task_failed", `Task failed: ${error}`, { + taskId, + agentId, + severity: "error", + }), // Chat chatSent: (userId: string, agentId: string, messagePreview: string) => @@ -99,6 +111,10 @@ export const events = { recordEvent("deploy", message, { meta }), // API calls (for monitoring) - apiCall: (endpoint: string, method: string, userId?: string, meta?: Record) => - recordEvent("api_call", `${method} ${endpoint}`, { userId, meta }), + apiCall: ( + endpoint: string, + method: string, + userId?: string, + meta?: Record, + ) => recordEvent("api_call", `${method} ${endpoint}`, { userId, meta }), }; diff --git a/src/main.tsx b/src/main.tsx index 6011475..4de0efd 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -21,7 +21,7 @@ Sentry.init({ createRoot(document.getElementById("root")!).render( }> - + , ); // Error fallback component @@ -30,7 +30,9 @@ function ErrorFallback() {

Etwas ist schiefgelaufen

-

Ein unerwarteter Fehler ist aufgetreten.

+

+ Ein unerwarteter Fehler ist aufgetreten. +