diff --git a/README.md b/README.md index 9b1716a..eb61414 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,7 @@ Frankenbot ist ein intelligentes Event-Management-System, bei dem spezialisierte - **Dynamische Modellauswahl**: 29+ KI-Modelle verfügbar (OpenCode, Anthropic, Ollama) ### 📋 Task-Management -- **Persistente Speicherung**: SQLite-Datenbank für Tasks - **Automatische Task-Verarbeitung**: TaskBeat verarbeitet Tasks alle 10 Sekunden -- **Auto-Cleanup**: Löscht completed Tasks automatisch nach 7 Tagen - **Auto-Refresh UI**: Tasks-Seite aktualisiert sich automatisch alle 15 Sekunden - **Task-Delegation**: Orchestrator analysiert Tasks und weist sie passenden Agents zu - **Status-Tracking**: pending → in_progress → completed @@ -31,9 +29,7 @@ Frankenbot ist ein intelligentes Event-Management-System, bei dem spezialisierte - **IMAP**: Email-Posteingang überwachen - **SMTP**: Emails versenden - **Email-to-Task**: Automatische Task-Erstellung aus Emails -- **Whitelist-System**: Nur autorisierte Absender (diversityball.at Domain) -- **Intelligente Vorstellung**: Neue Absender werden automatisch gebeten sich vorzustellen -- **Team-Member Lernsystem**: Orchestrator baut automatisch Wissensdatenbank auf +- **Whitelist-System**: Nur autorisierte Absender ### 📁 Datei-Management - **Agent Work-Folders**: Jeder Agent hat eigenes Arbeitsverzeichnis @@ -51,21 +47,6 @@ Frankenbot ist ein intelligentes Event-Management-System, bei dem spezialisierte - **On-Demand Zugriff**: Agents nutzen `@READ_KNOWLEDGE` statt volle KB im Prompt - **Performance-Optimierung**: Reduziert Prompt-Größe um ~15KB - **Event-spezifisch**: Diversity-Ball Wien 2026 Informationen -- **Self-Learning**: System lernt automatisch über Team-Members und Verantwortlichkeiten - -### 👥 Team-Management -- **Team-Members Datenbank**: Reale Mitarbeiter mit Rollen und Verantwortlichkeiten -- **Automatisches Lernen**: Neue Absender werden erkannt und vorgestellt -- **Orchestrator Beat**: Prüft alle 30 Minuten Tasks und eskaliert bei Bedarf -- **Direkter Kontakt**: Orchestrator kann Team-Members per Email/Telegram kontaktieren -- **Intelligente Koordination**: Weiß wer wofür verantwortlich ist - -### 💬 Telegram-Integration -- **Bidirektionale Kommunikation**: Nachrichten senden und empfangen -- **QR-Code Setup**: Einfache Bot-Verbindung über Settings-Seite -- **Task-Erstellung**: Telegram-Nachrichten werden automatisch zu Tasks -- **Antworten**: Agent-Responses werden zurück zu Telegram gesendet -- **User-Whitelist**: Nur autorisierte Telegram-User ## 🚀 Installation @@ -138,42 +119,14 @@ Reason: Für große Events mit 500+ Gästen benötigt @END ``` -#### Team-Member kontaktieren -``` -@SEND_EMAIL -To: georg.tschare@signtime.media -Subject: Budget-Freigabe benötigt -Body: Hallo Georg, für das Catering benötigen wir eine Budget-Freigabe... -@END -``` - -#### Team-Member aktualisieren -``` -@UPDATE_TEAM_MEMBER -Identifier: p.dyderski@live.at -TelegramID: 123456789 -Responsibilities: System-Administration, 3D-Visualisierung, AI-Agenten, R&D -@END -``` - -#### Neues Team-Mitglied hinzufügen -``` -@ADD_TEAM_MEMBER -Name: Max Mustermann -Role: Marketing Coordinator -Responsibilities: Social Media, Content Creation -Email: max@diversityball.at -@END -``` - ## 🗂️ Projekt-Struktur ``` frankenbot/ -├── app.py # Haupt-Flask-App (2900+ Zeilen) +├── app.py # Haupt-Flask-App (2128 Zeilen) ├── agent_config.json # Agent-Modell-Zuweisungen ├── diversityball_knowledge.md # Event-Wissensdatenbank -├── email_journal.db # SQLite-Datenbank (Emails, Tasks, Team-Members) +├── email_journal.db # SQLite-Datenbank (Emails) │ ├── agents/ # Agent-Verzeichnisse │ ├── orchestrator/ @@ -221,11 +174,10 @@ frankenbot/ - **Backend**: Flask 3.x - **Frontend**: Bootstrap 5, Vanilla JavaScript -- **Database**: SQLite (Email-Journal, Tasks, Team-Members) -- **AI**: OpenCode CLI (Multi-Provider Support: Anthropic, Ollama, etc.) +- **Database**: SQLite (Email-Journal) +- **AI**: OpenCode CLI (Multi-Provider Support) - **Templates**: Jinja2 - **Streaming**: Server-Sent Events (SSE) -- **Bot**: python-telegram-bot, QR-Code Generation ## 🎨 UI-Routen @@ -236,9 +188,8 @@ frankenbot/ | `/orchestrator` | Orchestrator-Chat mit Task-Verteilung | | `/tasks` | Task-Management mit Auto-Refresh | | `/agents` | Agent-Verwaltung (Erstellen, Bearbeiten, Modell-Auswahl) | -| `/files` | Datei-Manager (Upload, Download, Agent Work-Folders, View) | +| `/files` | Datei-Manager (Upload, Download, Agent Work-Folders) | | `/emails` | Email-Interface (Senden, Empfangen, Vorlagen) | -| `/settings` | System-Einstellungen, Telegram QR-Code, Poller-Konfiguration | ## ⚙️ Konfiguration @@ -261,25 +212,10 @@ EMAIL_IMAP_SERVER=imap.gmail.com EMAIL_SMTP_SERVER=smtp.gmail.com ``` -### Telegram-Konfiguration -Bearbeite `.env`: -```bash -TELEGRAM_BOT_TOKEN=your-bot-token-here -TELEGRAM_ALLOWED_USERS=123456789,987654321 -TELEGRAM_BOT_USERNAME=your_bot_name_bot -``` - -Setup-Anleitung: -1. Erstelle Bot via [@BotFather](https://t.me/BotFather) -2. Hole deine User-ID via [@userinfobot](https://t.me/userinfobot) -3. QR-Code scannen auf `/settings` Seite - ### Background-Threads - **EmailPoller**: Prüft alle 2 Minuten auf neue Emails - **TaskBeat**: Verarbeitet Tasks alle 10 Sekunden - **TaskWorker**: Führt Agent-Tasks aus (Timeout: 10 Min) -- **OrchestratorBeat**: Prüft alle 30 Minuten auf blockierte Tasks ⭐ NEU -- **TelegramBot**: Polling für Telegram-Nachrichten (wenn konfiguriert) ## 📊 Performance @@ -288,9 +224,6 @@ Setup-Anleitung: - ✅ Model-Cache (1h TTL) - ✅ Agent arbeiten in eigenen work-Verzeichnissen - ✅ Timeout: 600 Sekunden (10 Min) für komplexe Tasks -- ✅ Task-Cleanup: Auto-Löschung nach 7 Tagen -- ✅ Persistente Tasks in SQLite-Datenbank -- ✅ Self-Learning Team-Management ### Geschätzte Ausführungszeiten - Einfache Tasks: 30-60 Sekunden @@ -300,12 +233,10 @@ Setup-Anleitung: ## 🔒 Security ### Implementierte Maßnahmen -- ✅ XSS-Schutz: HTML-Escaping in allen Templates (orchestrator.html, chat.html) -- ✅ Email-Whitelist: Nur autorisierte Absender (diversityball.at Domain) -- ✅ Telegram-Whitelist: Nur autorisierte User-IDs +- ✅ XSS-Schutz: HTML-Escaping in allen Templates +- ✅ Email-Whitelist: Nur autorisierte Absender - ✅ Exception-Handling: Spezifische Exception-Typen mit Logging - ✅ Request-Validierung: `.get()` mit Defaults -- ✅ Path Traversal Protection: Agent-Files nur aus eigenem work-Verzeichnis - ✅ Session-basierte Auth: Flask-Sessions ### Empfehlungen für Produktion @@ -380,23 +311,6 @@ git push origin main 3. TaskBeat weist Task zu passendem Agent 4. Agent arbeitet Task ab und antwortet per Email -### Neuer Team-Member (Self-Learning) -1. Email von unbekanntem @diversityball.at Absender -2. System erkennt: "Nicht in Team-DB!" -3. Orchestrator übernimmt automatisch -4. Begrüßt und bittet um Vorstellung (Name, Rolle, Verantwortlichkeiten) -5. Beantwortet auch die eigentliche Anfrage -6. Nutzt @ADD_TEAM_MEMBER zum Speichern -7. Zukünftig: Bessere Koordination durch bekannte Verantwortlichkeiten - -### Orchestrator Beat Monitoring -1. Alle 30 Minuten: System-Check -2. Findet Tasks ohne Fortschritt (>2h pending) -3. Findet blockierte Tasks (>4h in_progress) -4. Fragt Orchestrator was zu tun ist -5. Orchestrator entscheidet ob Eskalation nötig -6. Bei Bedarf: @SEND_EMAIL an verantwortliches Team-Mitglied - ### Wissensdatenbank-Abfrage 1. Agent braucht Info (z.B. Budget) 2. Nutzt `@READ_KNOWLEDGE Topic: Budget` @@ -413,7 +327,6 @@ Dieses Projekt ist für internen Gebrauch und Event-Management entwickelt. --- -**Version**: 2.0.0 -**Letztes Update**: 21. Februar 2026 -**Status**: Production-Ready ✅ -**Neue Features**: Self-Learning Team-Management, Orchestrator Beat, Task-Persistierung, Telegram-Integration +**Version**: 1.0.0 +**Letztes Update**: Februar 2026 +**Status**: Production-Ready ✅ diff --git a/app.py b/app.py index 46a128d..8368537 100644 --- a/app.py +++ b/app.py @@ -884,9 +884,10 @@ def parse_agent_commands(agent_key, response_text): ) for question, context in ask_requests: # Erstelle Task für Orchestrator um die Frage zu beantworten - task_id = create_task( - title=f"Frage von {agent_key}: {question.strip()[:80]}", - description=f"""Ein Agent braucht Hilfe! + new_task = { + 'id': len(tasks) + 1, + 'title': f"Frage von {agent_key}: {question.strip()[:80]}", + 'description': f"""Ein Agent braucht Hilfe! **Von:** {agent_key} **Frage:** {question.strip()} @@ -894,13 +895,16 @@ def parse_agent_commands(agent_key, response_text): Bitte beantworte die Frage oder delegiere an den passenden Experten-Agent. Die Antwort wird an {agent_key} zurückgegeben.""", - agent_key='orchestrator', - task_type='agent_question', - created_by=agent_key, - from_agent=agent_key, - return_to=agent_key - ) - logger.info(f"[AgentCmd] {agent_key} fragt Orchestrator (Task #{task_id}): {question.strip()[:50]}") + 'assigned_agent': 'Orchestrator', + 'agent_key': 'orchestrator', + 'status': 'pending', + 'created': datetime.now().strftime('%Y-%m-%d %H:%M'), + 'type': 'agent_question', + 'from_agent': agent_key, + 'return_to': agent_key + } + tasks.append(new_task) + logger.info(f"[AgentCmd] {agent_key} fragt Orchestrator: {question.strip()[:50]}") # CREATE_SUBTASK: Agent möchte Subtask erstellen subtask_requests = re.findall( @@ -909,15 +913,19 @@ Die Antwort wird an {agent_key} zurückgegeben.""", re.DOTALL ) for task_desc, requirements in subtask_requests: - task_id = create_task( - title=task_desc.strip()[:100], - description=f"Von {agent_key} angefordert:\n{requirements.strip()}", - agent_key='orchestrator', - task_type='agent_subtask', - created_by=agent_key, - from_agent=agent_key - ) - logger.info(f"[AgentCmd] {agent_key} erstellt Subtask #{task_id} via Orchestrator") + new_task = { + 'id': len(tasks) + 1, + 'title': task_desc.strip()[:100], + 'description': f"Von {agent_key} angefordert:\n{requirements.strip()}", + 'assigned_agent': 'Orchestrator', + 'agent_key': 'orchestrator', + 'status': 'pending', + 'created': datetime.now().strftime('%Y-%m-%d %H:%M'), + 'type': 'agent_subtask', + 'from_agent': agent_key + } + tasks.append(new_task) + logger.info(f"[AgentCmd] {agent_key} erstellt Subtask via Orchestrator") # SUGGEST_AGENT: Agent schlägt neuen Agent vor suggest_requests = re.findall( @@ -930,24 +938,28 @@ Die Antwort wird an {agent_key} zurückgegeben.""", agent_key_suggestion = role.lower().replace(' ', '_').replace('-', '_') # Task für Orchestrator um Agent zu erstellen - task_id = create_task( - title=f"Agent-Vorschlag: {role.strip()}", - description=f"""Agent {agent_key} schlägt vor, einen neuen Agent zu erstellen: + new_task = { + 'id': len(tasks) + 1, + 'title': f"Agent-Vorschlag: {role.strip()}", + 'description': f"""Agent {agent_key} schlägt vor, einen neuen Agent zu erstellen: **Rolle:** {role.strip()} **Fähigkeiten:** {skills.strip()} **Begründung:** {reason.strip()} Bitte entscheide ob dieser Agent erstellt werden soll.""", - agent_key='orchestrator', - task_type='agent_suggestion', - created_by=agent_key, - from_agent=agent_key, - suggested_agent=agent_key_suggestion, - suggested_role=role.strip(), - suggested_skills=skills.strip() - ) - logger.info(f"[AgentCmd] {agent_key} schlägt neuen Agent vor (Task #{task_id}): {role.strip()}") + 'assigned_agent': 'Orchestrator', + 'agent_key': 'orchestrator', + 'status': 'pending', + 'created': datetime.now().strftime('%Y-%m-%d %H:%M'), + 'type': 'agent_suggestion', + 'from_agent': agent_key, + 'suggested_agent': agent_key_suggestion, + 'suggested_role': role.strip(), + 'suggested_skills': skills.strip() + } + tasks.append(new_task) + logger.info(f"[AgentCmd] {agent_key} schlägt neuen Agent vor: {role.strip()}") # READ_KNOWLEDGE: Agent möchte Wissensdatenbank durchsuchen read_kb_requests = re.findall( @@ -977,124 +989,6 @@ Bitte entscheide ob dieser Agent erstellt werden soll.""", if relevant_sections: logger.info(f"[AgentCmd] {len(relevant_sections)} relevante Zeilen gefunden") - - # SEND_EMAIL: Orchestrator sendet Email an Team-Member - send_email_requests = re.findall( - r'@SEND_EMAIL\s*\nTo:\s*([^\n]+)\s*\nSubject:\s*([^\n]+)\s*\nBody:\s*([^@]+)@END', - response_text, - re.DOTALL - ) - for to_addr, subject, body in send_email_requests: - to_clean = to_addr.strip() - subject_clean = subject.strip() - body_clean = body.strip() - - # Versuche Email zu senden - success, message = send_email(to_clean, subject_clean, body_clean) - if success: - logger.info(f"[AgentCmd] Email gesendet an {to_clean}: {subject_clean}") - else: - logger.error(f"[AgentCmd] Email-Fehler: {message}") - - # SEND_TELEGRAM: Orchestrator sendet Telegram-Nachricht - send_telegram_requests = re.findall( - r'@SEND_TELEGRAM\s*\nTo:\s*([^\n]+)\s*\nMessage:\s*([^@]+)@END', - response_text, - re.DOTALL - ) - for recipient, message in send_telegram_requests: - recipient_clean = recipient.strip() - message_clean = message.strip() - - # Telegram-Integration (wenn aktiviert) - if TELEGRAM_CONFIG.get('bot_token') and TELEGRAM_CONFIG.get('telegram_bot'): - try: - # Finde Chat-ID für Recipient (basierend auf Team-Member) - con = sqlite3.connect(EMAIL_JOURNAL_DB) - result = con.execute( - "SELECT telegram_chat_id FROM team_members WHERE name = ? OR email = ?", - (recipient_clean, recipient_clean) - ).fetchone() - con.close() - - if result and result[0]: - chat_id = result[0] - import asyncio - asyncio.run(TELEGRAM_CONFIG['telegram_bot'].bot.send_message( - chat_id=chat_id, - text=message_clean - )) - logger.info(f"[AgentCmd] Telegram gesendet an {recipient_clean}") - else: - logger.warning(f"[AgentCmd] Keine Telegram Chat-ID für {recipient_clean}") - except Exception as e: - logger.error(f"[AgentCmd] Telegram-Fehler: {str(e)}") - else: - logger.warning("[AgentCmd] Telegram nicht konfiguriert") - - # ADD_TEAM_MEMBER: Füge neues Team-Mitglied hinzu - add_member_requests = re.findall( - r'@ADD_TEAM_MEMBER\s*\nName:\s*([^\n]+)\s*\nEmail:\s*([^\n]+)\s*\nRole:\s*([^\n]+)\s*\nResponsibilities:\s*([^@]+)@END', - response_text, - re.DOTALL - ) - for name, email, role, resp in add_member_requests: - name_clean = name.strip() - email_clean = email.strip() - role_clean = role.strip() - resp_clean = resp.strip() - - success = add_team_member(name_clean, role_clean, email_clean, resp_clean) - if success: - logger.info(f"[AgentCmd] Team-Member hinzugefügt: {name_clean} ({role_clean})") - else: - logger.warning(f"[AgentCmd] Team-Member konnte nicht hinzugefügt werden: {name_clean}") - - # UPDATE_TEAM_MEMBER: Aktualisiere Team-Mitglied - update_member_requests = re.findall( - r'@UPDATE_TEAM_MEMBER\s*\nEmail:\s*([^\n]+)\s*\nField:\s*([^\n]+)\s*\nValue:\s*([^@]+)@END', - response_text, - re.DOTALL - ) - for email, field, value in update_member_requests: - email_clean = email.strip() - field_clean = field.strip().lower() - value_clean = value.strip() - - # Hole aktuelles Team-Member - con = sqlite3.connect(EMAIL_JOURNAL_DB) - member = con.execute( - "SELECT name, role, email, responsibilities FROM team_members WHERE LOWER(email) = ?", - (email_clean.lower(),) - ).fetchone() - - if member: - # Update je nach Field - updates = { - 'name': member[0], - 'role': member[1], - 'email': member[2], - 'responsibilities': member[3] - } - - if field_clean in updates: - updates[field_clean] = value_clean - success = update_team_member( - email_clean, - updates['name'], - updates['role'], - updates['responsibilities'] - ) - if success: - logger.info(f"[AgentCmd] Team-Member aktualisiert: {email_clean} - {field_clean}") - else: - logger.error(f"[AgentCmd] Update fehlgeschlagen für {email_clean}") - else: - logger.warning(f"[AgentCmd] Unbekanntes Field: {field_clean}") - else: - logger.warning(f"[AgentCmd] Team-Member nicht gefunden: {email_clean}") - - con.close() def create_new_agent(agent_key, role, skills): """Erstellt dynamisch einen neuen Agenten.""" @@ -1448,15 +1342,24 @@ async def telegram_message_handler(update: Update, context: ContextTypes.DEFAULT return # Task erstellen - task_id = create_task( - title=f"Telegram: {message_text[:50]}{'...' if len(message_text) > 50 else ''}", - description=message_text, - agent_key='orchestrator', - task_type='telegram', - created_by=f'telegram_user_{user_id}', - telegram_chat_id=update.effective_chat.id, - telegram_user=username - ) + task_id = len(tasks) + 1 + while any(t['id'] == task_id for t in tasks): + task_id += 1 + + new_task = { + 'id': task_id, + 'title': f"Telegram: {message_text[:50]}{'...' if len(message_text) > 50 else ''}", + 'description': message_text, + 'assigned_agent': 'Orchestrator', + 'agent_key': 'orchestrator', + 'status': 'pending', + 'created': datetime.now().strftime('%Y-%m-%d %H:%M'), + 'type': 'telegram', + 'created_by': f'telegram_user_{user_id}', + 'telegram_chat_id': update.effective_chat.id, + 'telegram_user': username + } + tasks.append(new_task) await update.message.reply_text( f"✅ Task #{task_id} erstellt!\n\n" @@ -1697,26 +1600,9 @@ Diese Email kommt von **{known_member[0]}** ({known_member[1]}) # Journal: Status 'queued' – \Seen wird NICHT gesetzt (erst nach Verarbeitung) journal_insert(message_id, email_id.decode(), sender, subject, 'queued', agent_key) - # Task erstellen in DB - task_id = create_task( - title=f"📧 Email: {subject[:50]}", - description=body[:500], - agent_key=agent_key, - task_type='email', - created_by=sender_email, - reply_to=sender_email, - reply_subject=f"Re: {subject}" if not subject.startswith('Re:') else subject, - original_sender=sender, - original_subject=subject, - original_body=body, - message_id=message_id, - imap_uid=email_id.decode(), - extra_context=extra_context - ) - - # Für task_queue brauchen wir Task als Dict (Legacy-Kompatibilität) + # Task erstellen und in beide Listen eintragen task = { - 'id': task_id, + 'id': len(tasks) + 1, 'title': f"📧 Email: {subject[:50]}", 'description': body[:500], 'assigned_agent': AGENTS.get(agent_key, {}).get('name', agent_key), @@ -1729,11 +1615,11 @@ Diese Email kommt von **{known_member[0]}** ({known_member[1]}) 'original_sender': sender, 'original_subject': subject, 'original_body': body, - 'message_id': message_id, + 'message_id': message_id, # für Journal-Update durch TaskWorker 'imap_uid': email_id.decode(), - 'extra_context': extra_context, + 'extra_context': extra_context, # Für unbekannte Absender } - + tasks.append(task) with task_queue_lock: task_queue.append(task) @@ -1741,7 +1627,7 @@ Diese Email kommt von **{known_member[0]}** ({known_member[1]}) log_entry['status'] = 'queued' add_to_email_log(log_entry) - logger.info("[EmailPoller] Task #%d für Email von %s in Queue eingereiht.", task_id, sender_email) + logger.info("[EmailPoller] Task #%d für Email von %s in Queue eingereiht.", task['id'], sender_email) except Exception as e: logger.error("[EmailPoller] Fehler bei Email-ID %s: %s", email_id, str(e)) @@ -1931,9 +1817,10 @@ Agent-Beschreibungen: assigned = available_agents[0] # Sub-Task mit KOMPLETTEM Kontext - sub_task_id = create_task( - title=t[:80], - description=f"""**Original-Aufgabe:** + sub_task = { + 'id': len(tasks) + 1 + len(created_sub_tasks), + 'title': t[:80], + 'description': f"""**Original-Aufgabe:** {task.get('title', '')} {task.get('description', '')} @@ -1945,13 +1832,16 @@ Agent-Beschreibungen: {t} Arbeite diesen Teil ab und liefere ein vollständiges Ergebnis.""", - agent_key=assigned, - task_type='orchestrated', - created_by='orchestrator', - parent_task_id=task['id'] - ) - created_sub_tasks.append(sub_task_id) - logger.info("[TaskBeat] Sub-Task #%d zugewiesen an %s", sub_task_id, assigned) + 'assigned_agent': AGENTS.get(assigned, {}).get('name', assigned), + 'agent_key': assigned, + 'status': 'pending', + 'created': datetime.now().strftime('%Y-%m-%d %H:%M'), + 'type': 'orchestrated', + 'parent_task': task['id'] + } + tasks.append(sub_task) + created_sub_tasks.append(sub_task['id']) + logger.info("[TaskBeat] Sub-Task #%d zugewiesen an %s", sub_task['id'], assigned) task['status'] = 'completed' task['sub_task_ids'] = created_sub_tasks @@ -2092,12 +1982,12 @@ Nutze @READ_KNOWLEDGE um Kontext zu holen falls nötig. logger.info("[OrchestratorBeat] Frage Orchestrator zu %d Problemen", len(old_pending) + len(stuck_tasks)) response = execute_agent_task('orchestrator', summary) - # Response loggen und Kommandos parsen + # Response loggen if response: logger.info("[OrchestratorBeat] Orchestrator-Antwort: %s", response[:200]) - # Parse Orchestrator-Kommandos (Email, Telegram, etc.) - parse_agent_commands('orchestrator', response) + # TODO: Parse @SEND_EMAIL und @SEND_TELEGRAM Kommandos + # und sende tatsächlich Benachrichtigungen except Exception as e: logger.error("[OrchestratorBeat] Fehler: %s", str(e)) @@ -2118,9 +2008,7 @@ start_orchestrator_beat() @app.route('/') def index(): - # Hole die 5 neuesten Tasks aus DB - all_tasks = get_tasks() - recent_tasks = all_tasks[:5] if all_tasks else [] + recent_tasks = tasks[-5:] if tasks else [] return render_template('index.html', agents=AGENTS, recent_tasks=recent_tasks) @app.route('/chat', methods=['GET', 'POST']) @@ -2201,30 +2089,28 @@ def task_list(): assigned_agent = request.form.get('assigned_agent', '') if title: - task_id = create_task( - title=title, - description=description, - agent_key=assigned_agent if assigned_agent else None, - task_type='manual', - created_by='user' - ) - flash(f'Task #{task_id} erstellt!', 'success') + task = { + 'id': len(tasks) + 1, + 'title': title, + 'description': description, + 'assigned_agent': AGENTS.get(assigned_agent, {}).get('name', assigned_agent) if assigned_agent else 'Nicht zugewiesen', + 'status': 'pending', + 'created': datetime.now().strftime('%Y-%m-%d %H:%M'), + 'type': 'manual', + } + tasks.append(task) + flash('Task erstellt!', 'success') - # Alle Tasks aus Datenbank holen – Neueste zuerst - all_tasks = get_tasks() + # Alle Tasks anzeigen – Email-Tasks sind mit type='email' markiert + all_tasks = list(reversed(tasks)) # Neueste zuerst return render_template('tasks.html', agents=AGENTS, tasks=all_tasks) @app.route('/tasks/update//') def update_task(task_id, status): - # Update in Datenbank - update_task_db(task_id, status=status) - - # Auch in-memory array aktualisieren (Legacy-Kompatibilität für task_queue) for task in tasks: if task['id'] == task_id: task['status'] = status break - return redirect(url_for('task_list')) @app.route('/api/agent-stream', methods=['POST']) @@ -2812,35 +2698,26 @@ def api_tasks(): if not title: return jsonify({'error': 'Kein Titel übergeben'}), 400 - task_id = create_task( - title=title, - description=description, - agent_key=agent_key or assigned_agent or 'orchestrator', - task_type='agent_created', - created_by=agent_key or 'api' - ) - - # Task aus DB holen für Response - con = sqlite3.connect(EMAIL_JOURNAL_DB) - task_row = con.execute("SELECT * FROM tasks WHERE id = ?", (task_id,)).fetchone() - con.close() + task_id = len(tasks) + 1 + while any(t['id'] == task_id for t in tasks): + task_id += 1 new_task = { 'id': task_id, 'title': title, 'description': description, - 'assigned_agent': AGENTS.get(agent_key or assigned_agent, {}).get('name', agent_key or assigned_agent) if (agent_key or assigned_agent) else 'Nicht zugewiesen', - 'agent_key': agent_key or assigned_agent or 'orchestrator', + 'assigned_agent': AGENTS.get(assigned_agent, {}).get('name', assigned_agent) if assigned_agent else 'Nicht zugewiesen', + 'agent_key': agent_key or assigned_agent, 'status': 'pending', 'created': datetime.now().strftime('%Y-%m-%d %H:%M'), 'type': 'agent_created', - 'created_by': agent_key or 'api' + 'created_by': agent_key } + tasks.append(new_task) return jsonify({'success': True, 'task': new_task}) - # GET: Alle Tasks aus DB - task_list = get_tasks() + task_list = list(reversed(tasks)) return jsonify({'tasks': task_list}) @@ -2850,17 +2727,12 @@ def update_task_api(task_id): data = request.get_json() new_status = data.get('status') - # Update in DB - update_task_db(task_id, status=new_status) - - # Auch in-memory aktualisieren (Legacy) for task in tasks: if task['id'] == task_id: task['status'] = new_status return jsonify({'success': True, 'task': task}) - # Falls nur in DB vorhanden - return jsonify({'success': True, 'task_id': task_id, 'status': new_status}) + return jsonify({'error': 'Task nicht gefunden'}), 404 @app.route('/api/models', methods=['GET']) @@ -2945,35 +2817,43 @@ def distribute_tasks(): tasks_text = '\n'.join([f"- {t}" for t in tasks_list]) agents_text = ', '.join(selected_agents) if selected_agents else 'alle verfügbaren Agenten' - planning_task_id = create_task( - title=f"Planungsphase: {tasks_list[0][:50]}{'...' if len(tasks_list[0]) > 50 else ''}", - description=f"Tasks:\n{tasks_text}\n\nVerfügbare Agenten: {agents_text}\n\nDer Orchestrator soll diese Tasks analysieren und den richtigen Agenten zuweisen.", - agent_key='orchestrator', - task_type='orchestrated', - created_by='orchestrator', - sub_tasks=tasks_list, - available_agents=selected_agents - ) + planning_task = { + 'id': len(tasks) + 1, + 'title': f"Planungsphase: {tasks_list[0][:50]}{'...' if len(tasks_list[0]) > 50 else ''}", + 'description': f"Tasks:\n{tasks_text}\n\nVerfügbare Agenten: {agents_text}\n\nDer Orchestrator soll diese Tasks analysieren und den richtigen Agenten zuweisen.", + 'assigned_agent': 'Orchestrator', + 'agent_key': 'orchestrator', + 'status': 'pending', + 'created': datetime.now().strftime('%Y-%m-%d %H:%M'), + 'type': 'orchestrated', + 'sub_tasks': tasks_list, + 'available_agents': selected_agents + } + tasks.append(planning_task) return jsonify({ 'success': True, 'message': f'Planungs-Task erstellt. Der Orchestrator wird die richtigen Agenten zuweisen.', - 'tasks': [planning_task_id] + 'tasks': [planning_task['id']] }) for i, task_text in enumerate(tasks_list): agent_key = selected_agents[i % len(selected_agents)] + agent_info = AGENTS.get(agent_key, {}) + agent_name = agent_info.get('name', agent_key) - task_id = create_task( - title=task_text[:80], - description='Automatisch erstellt durch Orchestrator', - agent_key=agent_key, - task_type='orchestrated', - created_by='orchestrator' - ) - # Setze Status sofort auf in_progress - update_task_db(task_id, status='in_progress') - created_tasks.append(task_id) + new_task = { + 'id': len(tasks) + 1 + i, + 'title': task_text[:80], + 'description': 'Automatisch erstellt durch Orchestrator', + 'assigned_agent': agent_name, + 'status': 'in_progress', + 'created': datetime.now().strftime('%Y-%m-%d %H:%M'), + 'type': 'orchestrated', + 'agent_key': agent_key + } + tasks.append(new_task) + created_tasks.append(new_task['id']) executor = concurrent.futures.ThreadPoolExecutor(max_workers=len(selected_agents)) for i, task_text in enumerate(tasks_list):