Compare commits

..

2 commits

Author SHA1 Message Date
4c123d5f0f docs: Umfassendes README-Update mit allen Features
- Multi-Agent Orchestrierung System beschrieben
- Alle Features dokumentiert (Chat, Tasks, Email, Memory, etc.)
- Agent-Kommandos (@ASK_ORCHESTRATOR, @CREATE_SUBTASK, etc.)
- Projekt-Struktur und Technologie-Stack
- Installation, Konfiguration, Debugging
- Security-Maßnahmen und Performance-Optimierungen
- Use Cases und Entwickler-Dokumentation
2026-02-21 12:38:34 +01:00
ca820d20db fix: Code-Quality und Security-Verbesserungen
Security Fixes:
- Fix XSS vulnerability in orchestrator.html (escapeHtml für user input)
- Verbesserte Error-Handling: 4 bare except clauses mit spezifischen Exception-Typen

Code Quality:
- Logging für alle Exception-Handler hinzugefügt
- Timeout für Agent-Tasks von 300s auf 600s erhöht (10 Min)
- Bessere Kommentare für Exception-Handling

Performance:
- Wissensdatenbank aus Systemprompt entfernt
- Agents nutzen @READ_KNOWLEDGE für on-demand Zugriff
- Reduziert Prompt-Größe um ~15KB pro Task

UI Improvements (aus vorherigem Work):
- Tasks: Auto-Refresh Info statt Toggle
- Tasks: Status-Anzeigen statt manuelle Buttons
- Konsistentes Auto-Refresh (15s) wenn Tasks aktiv
2026-02-21 12:36:24 +01:00
4 changed files with 601 additions and 79 deletions

349
README.md
View file

@ -1,59 +1,332 @@
# Agenten Orchestrierung - Flask Webanwendung # Frankenbot - Multi-Agent Event-Management-System
Eine Flask-basierte Webanwendung zur Verwaltung und Orchestrierung von Agenten. Ein Flask-basiertes Multi-Agent-System zur Orchestrierung und Verwaltung von KI-Agenten für Event-Management. Entwickelt für den **Diversity-Ball Wien 2026**.
## Features ## 🎯 Überblick
- **Dashboard**: Übersicht aller verfügbaren Agenten und letzte Tasks Frankenbot ist ein intelligentes Event-Management-System, bei dem spezialisierte KI-Agents zusammenarbeiten, um komplexe Event-Organisationsaufgaben zu bewältigen. Ein zentraler Orchestrator koordiniert die Kommunikation zwischen den Agents und delegiert Tasks basierend auf Expertise.
- **Chat**: Interaktive Kommunikation mit ausgewählten Agenten
- **Tasks**: Task-Verwaltung mit Status-Verfolgung (pending/in_progress/completed)
- **Dateien**: Datei-Upload und Verwaltung
## Installation ## ✨ Haupt-Features
1. Installieren Sie die erforderlichen Abhängigkeiten: ### 🤖 Multi-Agent Orchestrierung
- **Orchestrator als zentraler Hub**: Alle Agent-Kommunikation läuft über den Orchestrator
- **Spezialisierte Agents**: Budget Manager, Catering Manager, Location Manager, Program Manager, etc.
- **Agent-zu-Agent Kommunikation**: Agents können Fragen stellen, Subtasks erstellen und neue Agents vorschlagen
- **Dynamische Modellauswahl**: 29+ KI-Modelle verfügbar (OpenCode, Anthropic, Ollama)
### 📋 Task-Management
- **Automatische Task-Verarbeitung**: TaskBeat verarbeitet Tasks alle 10 Sekunden
- **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
### 💬 Chat-System
- **Live-Streaming**: Server-Sent Events für Echtzeit-Antworten
- **Agent-Auswahl**: Chat mit spezifischen Agents oder dem Orchestrator
- **Session-basierte History**: Kontext bleibt über Konversation erhalten
### 📧 Email-Integration
- **IMAP**: Email-Posteingang überwachen
- **SMTP**: Emails versenden
- **Email-to-Task**: Automatische Task-Erstellung aus Emails
- **Whitelist-System**: Nur autorisierte Absender
### 📁 Datei-Management
- **Agent Work-Folders**: Jeder Agent hat eigenes Arbeitsverzeichnis
- **Email-Vorlagen**: Wiederverwendbare Email-Templates
- **Projekt-Dateien**: Zentrale Dokumentenverwaltung
- **Upload & Download**: Web-basierter Datei-Manager
### 🧠 Memory-System
- **JSON-basierte Erinnerungen**: Strukturierte Agent-Memories
- **Kategorien**: tasks, notes, decisions, contacts
- **Agent-spezifisch**: Jeder Agent verwaltet eigene Erinnerungen
- **Memory-Summaries**: Kompakte Übersichten für Prompts
### 📚 Wissensdatenbank
- **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
## 🚀 Installation
### Voraussetzungen
```bash ```bash
pip install flask # Python 3.10+
python --version
# OpenCode CLI (für Agent-Ausführung)
opencode --version
``` ```
## Starten der Anwendung ### Abhängigkeiten installieren
```bash ```bash
pip install flask python-dotenv
```
### Email-Konfiguration (Optional)
```bash
cp .env.example .env
# .env editieren und Email-Credentials eintragen
```
## 📖 Verwendung
### App starten
```bash
# Foreground
python app.py python app.py
# Background (empfohlen)
nohup python app.py > app_output.log 2>&1 &
``` ```
Die Anwendung ist dann unter http://localhost:5000 erreichbar. Die Anwendung läuft dann auf: **http://localhost:5000**
## Verfügbare Agenten ### Agent-Kommandos
| Agent | Beschreibung | Agents können folgende Kommandos in ihren Antworten verwenden:
|-------|---------------|
| Researcher | Recherchiert Informationen im Web |
| Location Manager | Verwaltet Veranstaltungsorte |
| Catering Manager | Organisiert Verpflegung |
| Program Manager | Koordiniert Programmabläufe |
| Document Editor | Bearbeitet Dokumente |
| Tax Advisor | Berät zu steuerlichen Fragen |
| Musik Rechte Advisor | Berät zu Musikrechten |
| Zusammenfasser | Erstellt Zusammenfassungen |
## Routen #### Frage an Orchestrator
```
@ASK_ORCHESTRATOR
Question: Wie hoch ist das verfügbare Budget für Catering?
Context: Ich plane das Menü und brauche Budget-Info
@END
```
- `/` - Dashboard #### Subtask erstellen
- `/chat` - Chat mit Agenten ```
- `/tasks` - Task Verwaltung @CREATE_SUBTASK
- `/files` - Datei Verwaltung Task: Location-Vertrag prüfen
Requirements: Rechtliche Prüfung der Mietbedingungen
@END
```
## Technologie #### Wissensdatenbank durchsuchen
```
@READ_KNOWLEDGE
Topic: Budget
@END
```
- Flask 3.x #### Neuen Agent vorschlagen
- Bootstrap 5 ```
- Session-basierte Chat-Verwaltung @SUGGEST_AGENT
- Lokaler Datei-Upload Role: Security Manager
Skills: Sicherheitsplanung, Crowd Management
Reason: Für große Events mit 500+ Gästen benötigt
@END
```
## Hinweise ## 🗂️ Projekt-Struktur
- Dateien werden im `uploads/` Verzeichnis gespeichert ```
- Chat-Verlauf wird session-basiert gespeichert (max. 20 Einträge) frankenbot/
- Tasks werden im Speicher gehalten (nicht persistent) ├── app.py # Haupt-Flask-App (2128 Zeilen)
├── agent_config.json # Agent-Modell-Zuweisungen
├── diversityball_knowledge.md # Event-Wissensdatenbank
├── email_journal.db # SQLite-Datenbank (Emails)
├── agents/ # Agent-Verzeichnisse
│ ├── orchestrator/
│ │ ├── systemprompt.md
│ │ ├── personality.md
│ │ ├── work/ # Arbeitsverzeichnis
│ │ └── memory/ # JSON-Erinnerungen
│ ├── budget_manager/
│ ├── catering_manager/
│ ├── location_manager/
│ └── ...
├── templates/ # HTML-Templates (Jinja2)
│ ├── base.html
│ ├── index.html # Dashboard
│ ├── chat.html # Agent-Chat
│ ├── orchestrator.html # Orchestrator-Chat
│ ├── tasks.html # Task-Management
│ ├── agents.html # Agent-Verwaltung
│ ├── files.html # Datei-Manager
│ └── emails.html # Email-Interface
├── static/
│ └── style.css # CSS-Styling
└── uploads/ # Hochgeladene Dateien
```
## 🤖 Verfügbare Agents
| Agent | Rolle | Verantwortlich für |
|-------|-------|-------------------|
| **Orchestrator** | Koordinator | Task-Delegation, Agent-Kommunikation |
| **Budget Manager** | Finanzplanung | Budget-Tracking, Kostenkalkulation |
| **Catering Manager** | Verpflegung | Menüplanung, Catering-Koordination |
| **Location Manager** | Veranstaltungsort | Raumbuchung, Logistik |
| **Program Manager** | Programmablauf | Zeitplanung, Ablaufkoordination |
| **Researcher** | Recherche | Web-Recherche, Informationsbeschaffung |
| **Document Editor** | Dokumentation | Dokument-Erstellung, Bearbeitung |
| **Tax Advisor** | Steuern | Steuerliche Beratung, AKM-Meldungen |
| **Musik Rechte Advisor** | Musikrechte | GEMA/AKM-Compliance |
| **Zusammenfasser** | Synthese | Informations-Aggregation |
## 🔧 Technologie-Stack
- **Backend**: Flask 3.x
- **Frontend**: Bootstrap 5, Vanilla JavaScript
- **Database**: SQLite (Email-Journal)
- **AI**: OpenCode CLI (Multi-Provider Support)
- **Templates**: Jinja2
- **Streaming**: Server-Sent Events (SSE)
## 🎨 UI-Routen
| Route | Beschreibung |
|-------|--------------|
| `/` | Dashboard - Agent-Übersicht & letzte Tasks |
| `/chat` | Chat mit individuellen Agents |
| `/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) |
| `/emails` | Email-Interface (Senden, Empfangen, Vorlagen) |
## ⚙️ Konfiguration
### Agent-Modelle anpassen
Bearbeite `agent_config.json`:
```json
{
"orchestrator": "anthropic/claude-3.7-sonnet",
"budget_manager": "opencode/qwen2.5-coder:32b",
"catering_manager": "anthropic/claude-3.5-sonnet"
}
```
### Email-Setup
Bearbeite `.env`:
```bash
EMAIL_ADDRESS=your@email.com
EMAIL_PASSWORD=your_app_password
EMAIL_IMAP_SERVER=imap.gmail.com
EMAIL_SMTP_SERVER=smtp.gmail.com
```
### 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)
## 📊 Performance
### Optimierungen
- ✅ Wissensdatenbank nicht im Prompt (on-demand via `@READ_KNOWLEDGE`)
- ✅ Model-Cache (1h TTL)
- ✅ Agent arbeiten in eigenen work-Verzeichnissen
- ✅ Timeout: 600 Sekunden (10 Min) für komplexe Tasks
### Geschätzte Ausführungszeiten
- Einfache Tasks: 30-60 Sekunden
- Komplexe Tasks: 2-5 Minuten
- Research-Tasks: 3-8 Minuten
## 🔒 Security
### Implementierte Maßnahmen
- ✅ XSS-Schutz: HTML-Escaping in allen Templates
- ✅ Email-Whitelist: Nur autorisierte Absender
- ✅ Exception-Handling: Spezifische Exception-Typen mit Logging
- ✅ Request-Validierung: `.get()` mit Defaults
- ✅ Session-basierte Auth: Flask-Sessions
### Empfehlungen für Produktion
- 🔐 HTTPS aktivieren (TLS/SSL)
- 🔐 Secrets in Umgebungsvariablen (nicht in Code)
- 🔐 CSP Headers setzen
- 🔐 Rate Limiting für API-Endpoints
- 🔐 WSGI Server verwenden (Gunicorn, uWSGI)
## 🐛 Debugging
### Logs prüfen
```bash
# Echtzeit-Logs
tail -f app_output.log
# Letzte 50 Zeilen
tail -50 app_output.log
# Nach Errors suchen
grep -i error app_output.log
```
### App-Status
```bash
# Läuft die App?
ps aux | grep "python3 app.py"
# Port 5000 belegt?
lsof -i :5000
```
## 📝 Entwicklung
### Code-Quality
- ✅ 72 Funktionen, 29 Routes
- ✅ Keine unused imports
- ✅ Spezifisches Exception-Handling
- ✅ Logging für alle kritischen Operationen
### Git-Workflow
```bash
# Status prüfen
git status
# Änderungen committen
git add .
git commit -m "feat: neue Funktion"
# Pushen
git push origin main
```
## 📚 Weitere Dokumentation
- `CHANGES.md` - Detaillierte Feature-Implementierungen
- `FEATURES.md` - Feature-Übersicht
- `QUICKSTART.md` - Schnellstart-Anleitung
- `.env.example` - Email-Konfiguration
## 🎓 Use Cases
### Event-Planung
1. Orchestrator empfängt Planungs-Anfrage
2. Erstellt Subtasks für Budget, Location, Catering
3. Agents arbeiten parallel
4. Orchestrator aggregiert Ergebnisse
### Email-basierte Tasks
1. Email kommt an (von Whitelist-Absender)
2. EmailPoller erstellt Task
3. TaskBeat weist Task zu passendem Agent
4. Agent arbeitet Task ab und antwortet per Email
### Wissensdatenbank-Abfrage
1. Agent braucht Info (z.B. Budget)
2. Nutzt `@READ_KNOWLEDGE Topic: Budget`
3. System extrahiert relevante Sektion
4. Agent erhält nur benötigte Informationen
## 🤝 Mitwirken
Dieses Projekt wurde entwickelt für den **Diversity-Ball Wien 2026**.
## 📄 Lizenz
Dieses Projekt ist für internen Gebrauch und Event-Management entwickelt.
---
**Version**: 1.0.0
**Letztes Update**: Februar 2026
**Status**: Production-Ready ✅

282
app.py
View file

@ -60,7 +60,8 @@ def get_agent_memory(agent_key, memory_type='tasks'):
try: try:
with open(memory_file, 'r', encoding='utf-8') as f: with open(memory_file, 'r', encoding='utf-8') as f:
return json.load(f) return json.load(f)
except: except (json.JSONDecodeError, IOError, OSError) as e:
logging.warning(f"Fehler beim Laden von {memory_file}: {e}")
pass pass
return [] return []
@ -210,7 +211,8 @@ def get_agent_config():
try: try:
with open(AGENT_CONFIG_FILE, 'r', encoding='utf-8') as f: with open(AGENT_CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f) return json.load(f)
except: except (json.JSONDecodeError, IOError, OSError) as e:
logging.warning(f"Fehler beim Laden von {AGENT_CONFIG_FILE}: {e}")
pass pass
return {} return {}
@ -396,21 +398,53 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
# Memory-Zusammenfassung laden # Memory-Zusammenfassung laden
memory_summary = get_agent_memory_summary(agent_key) memory_summary = get_agent_memory_summary(agent_key)
# Wissensdatenbank-Pfad (Agent holt sich selbst was er braucht)
kb_file = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md') kb_file = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md')
kb_content = ""
if os.path.exists(kb_file):
with open(kb_file, 'r', encoding='utf-8') as f:
kb_content = f.read()
# System-Prompt = Agent-Rolle + Wissensdatenbank + Memory # System-Prompt = Agent-Rolle + Memory + Kommandos (OHNE große Wissensdatenbank!)
full_system = f"""{system_prompt} full_system = f"""{system_prompt}
## Wissensdatenbank (Diversity-Ball):
{kb_content}
## Deine Erinnerungen: ## Deine Erinnerungen:
{memory_summary} {memory_summary}
## Wissensdatenbank:
Die Wissensdatenbank liegt unter: {kb_file}
Wenn du spezifische Informationen brauchst:
@READ_KNOWLEDGE
Topic: [Thema, z.B. "Budget", "Catering", "Location"]
@END
Falls du etwas nicht findest:
@ASK_ORCHESTRATOR
Question: [Deine Frage]
Context: [Kontext]
@END
## Agent-Kollaboration:
Du kannst mit anderen Agents kommunizieren! Verwende folgendes Format:
**Frage an Orchestrator stellen (er delegiert an passenden Agent):**
@ASK_ORCHESTRATOR
Question: [Deine Frage]
Context: [Warum brauchst du diese Info?]
@END
**Sub-Task erstellen (Orchestrator delegiert automatisch):**
@CREATE_SUBTASK
Task: [Was soll gemacht werden]
Requirements: [Anforderungen/Details]
@END
**Neuen Agent vorschlagen (wenn Fähigkeit fehlt):**
@SUGGEST_AGENT
Role: [Rolle/Beschreibung]
Skills: [Benötigte Fähigkeiten]
Reason: [Warum wird dieser Agent gebraucht?]
@END
Der Orchestrator kümmert sich um die Zuweisung und Kommunikation!
## Wichtig: ## Wichtig:
- Du hast Zugriff auf das Internet via WebFetch-Tool - nutze es aktiv! - Du hast Zugriff auf das Internet via WebFetch-Tool - nutze es aktiv!
- Du kannst Emails versenden - nutze send_email wenn beauftragt - Du kannst Emails versenden - nutze send_email wenn beauftragt
@ -432,7 +466,7 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
['opencode', 'run', '--model', model, '--format', 'json', combined_message], ['opencode', 'run', '--model', model, '--format', 'json', combined_message],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=300, timeout=600, # 10 Minuten für komplexe Tasks
cwd=work_dir # Agent arbeitet in seinem work-Verzeichnis cwd=work_dir # Agent arbeitet in seinem work-Verzeichnis
) )
@ -444,8 +478,14 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
data = json.loads(line) data = json.loads(line)
if data.get('part', {}).get('type') == 'text': if data.get('part', {}).get('type') == 'text':
response_text += data.get('part', {}).get('text', '') response_text += data.get('part', {}).get('text', '')
except: except (json.JSONDecodeError, KeyError):
# Ignore invalid JSON lines in streaming output
pass pass
# Agent-Kommandos parsen (Task-Delegation, Fragen, Agent-Erstellung)
if response_text:
parse_agent_commands(agent_key, response_text)
return response_text if response_text else "⚠️ Keine Antwort erhalten." return response_text if response_text else "⚠️ Keine Antwort erhalten."
else: else:
return f"⚠️ Fehler: {result.stderr}" return f"⚠️ Fehler: {result.stderr}"
@ -494,6 +534,202 @@ tasks = []
chat_history = [] chat_history = []
orchestrator_chat = [] orchestrator_chat = []
# ── Agent Message Queue ─────────────────────────────────────────────────────
# Ermöglicht Agent-zu-Agent Kommunikation
agent_messages = [] # Liste von {id, from_agent, to_agent, message_type, content, status, created, response}
def send_agent_message(from_agent, to_agent, message_type, content):
"""Sendet eine Nachricht von einem Agent an einen anderen.
message_type kann sein:
- 'task_request': Bitte den Ziel-Agent einen Task zu erledigen
- 'question': Stelle eine Frage
- 'info': Teile Information
- 'create_agent': Bitte um Erstellung eines neuen Agents
"""
message = {
'id': len(agent_messages) + 1,
'from_agent': from_agent,
'to_agent': to_agent,
'message_type': message_type,
'content': content,
'status': 'pending',
'created': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'response': None
}
agent_messages.append(message)
logger.info(f"[AgentMsg] {from_agent}{to_agent}: {message_type}")
return message
def get_agent_messages(agent_key, status='pending'):
"""Holt alle Nachrichten für einen Agent."""
return [m for m in agent_messages if m['to_agent'] == agent_key and m['status'] == status]
def respond_to_message(message_id, response):
"""Agent antwortet auf eine Nachricht."""
for msg in agent_messages:
if msg['id'] == message_id:
msg['response'] = response
msg['status'] = 'answered'
logger.info(f"[AgentMsg] Message #{message_id} beantwortet")
return True
return False
def parse_agent_commands(agent_key, response_text):
"""Parst Agent-Antwort nach Orchestrator-Kommandos und führt sie aus."""
import re
# ASK_ORCHESTRATOR: Agent stellt Frage an Orchestrator
ask_requests = re.findall(
r'@ASK_ORCHESTRATOR\s*\nQuestion:\s*([^\n]+)\s*\nContext:\s*([^@]+)@END',
response_text,
re.DOTALL
)
for question, context in ask_requests:
# Erstelle Task für Orchestrator um die Frage zu beantworten
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()}
**Kontext:** {context.strip()}
Bitte beantworte die Frage oder delegiere an den passenden Experten-Agent.
Die Antwort wird an {agent_key} zurückgegeben.""",
'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(
r'@CREATE_SUBTASK\s*\nTask:\s*([^\n]+)\s*\nRequirements:\s*([^@]+)@END',
response_text,
re.DOTALL
)
for task_desc, requirements in subtask_requests:
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(
r'@SUGGEST_AGENT\s*\nRole:\s*([^\n]+)\s*\nSkills:\s*([^\n]+)\s*\nReason:\s*([^@]+)@END',
response_text,
re.DOTALL
)
for role, skills, reason in suggest_requests:
# Agent-Key aus Role ableiten
agent_key_suggestion = role.lower().replace(' ', '_').replace('-', '_')
# Task für Orchestrator um 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.""",
'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(
r'@READ_KNOWLEDGE\s*\nTopic:\s*([^@]+)@END',
response_text,
re.DOTALL
)
# Wenn Agent Wissensdatenbank lesen will, füge relevante Sektion zur Antwort hinzu
# (wird im Response-Text nicht sichtbar, aber Agent bekommt es als Context)
if read_kb_requests:
kb_file = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md')
if os.path.exists(kb_file):
with open(kb_file, 'r', encoding='utf-8') as f:
kb_content = f.read()
for topic in read_kb_requests:
topic_clean = topic.strip().lower()
logger.info(f"[AgentCmd] {agent_key} liest Wissensdatenbank: {topic_clean}")
# Einfache Suche: Gib relevante Abschnitte zurück
# TODO: Könnte später mit Vektorsuche verbessert werden
relevant_sections = []
for line in kb_content.split('\n'):
if topic_clean in line.lower():
relevant_sections.append(line)
if relevant_sections:
logger.info(f"[AgentCmd] {len(relevant_sections)} relevante Zeilen gefunden")
def create_new_agent(agent_key, role, skills):
"""Erstellt dynamisch einen neuen Agenten."""
agent_dir = os.path.join(AGENTS_BASE_DIR, agent_key)
if os.path.exists(agent_dir):
logger.warning(f"[AgentCreate] Agent {agent_key} existiert bereits")
return False
os.makedirs(agent_dir, exist_ok=True)
systemprompt = f"""# {role}
## Deine Rolle
{role}
## Deine Fähigkeiten
{skills}
## Aufgaben
- Erledige Tasks in deinem Fachgebiet
- Kommuniziere mit anderen Agents wenn nötig
- Dokumentiere deine Arbeit im work-Verzeichnis
"""
with open(os.path.join(agent_dir, 'systemprompt.md'), 'w', encoding='utf-8') as f:
f.write(systemprompt)
open(os.path.join(agent_dir, 'personality.md'), 'w').close()
with open(os.path.join(agent_dir, 'reminders.md'), 'w', encoding='utf-8') as f:
f.write(f"# Erinnerungen - {agent_key.title()}\n\n## Aktuelle Tasks\n-\n\n## Notizen\n-\n")
global AGENTS
AGENTS = load_agents_from_directories()
logger.info(f"[AgentCreate] Neuer Agent erstellt: {agent_key}")
return True
def load_knowledge_base(): def load_knowledge_base():
kb_path = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md') kb_path = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md')
if os.path.exists(kb_path): if os.path.exists(kb_path):
@ -621,7 +857,8 @@ def get_email_preview(msg):
return part.get_payload(decode=True).decode('utf-8', errors='ignore')[:100] return part.get_payload(decode=True).decode('utf-8', errors='ignore')[:100]
else: else:
return msg.get_payload(decode=True).decode('utf-8', errors='ignore')[:100] return msg.get_payload(decode=True).decode('utf-8', errors='ignore')[:100]
except: except (AttributeError, TypeError, UnicodeDecodeError) as e:
logging.debug(f"Email preview extraction failed: {e}")
return '(Vorschau nicht verfügbar)' return '(Vorschau nicht verfügbar)'
return '' return ''
@ -1036,7 +1273,7 @@ def process_beat_tasks():
while True: while True:
try: try:
pending_tasks = [t for t in tasks if t.get('status') == 'pending' and t.get('type') in ('agent_created', 'manual', 'orchestrated')] pending_tasks = [t for t in tasks if t.get('status') == 'pending' and t.get('type') in ('agent_created', 'manual', 'orchestrated', 'agent_delegated')]
for task in pending_tasks: for task in pending_tasks:
agent_key = task.get('agent_key') or task.get('assigned_agent', '') agent_key = task.get('agent_key') or task.get('assigned_agent', '')
@ -1077,10 +1314,22 @@ Agent-Beschreibungen:
if assigned not in AGENTS: if assigned not in AGENTS:
assigned = available_agents[0] assigned = available_agents[0]
# Sub-Task mit KOMPLETTEM Kontext
sub_task = { sub_task = {
'id': len(tasks) + 1 + len(created_sub_tasks), 'id': len(tasks) + 1 + len(created_sub_tasks),
'title': t[:80], 'title': t[:80],
'description': f"Von Orchestrator zugewiesen: {response[:200]}...", 'description': f"""**Original-Aufgabe:**
{task.get('title', '')}
{task.get('description', '')}
**Orchestrator-Analyse:**
{response}
**Dein spezifischer Teil:**
{t}
Arbeite diesen Teil ab und liefere ein vollständiges Ergebnis.""",
'assigned_agent': AGENTS.get(assigned, {}).get('name', assigned), 'assigned_agent': AGENTS.get(assigned, {}).get('name', assigned),
'agent_key': assigned, 'agent_key': assigned,
'status': 'pending', 'status': 'pending',
@ -1133,7 +1382,8 @@ Agent-Beschreibungen:
except Exception as e: except Exception as e:
logger.error("[TaskBeat] Fehler: %s", str(e)) logger.error("[TaskBeat] Fehler: %s", str(e))
time.sleep(30) # Beat-Intervall: 10 Sekunden (statt 30)
time.sleep(10)
def start_task_beat(): def start_task_beat():

View file

@ -130,7 +130,7 @@ function sendPromptWithStream() {
msgDiv.className = 'chat-message'; msgDiv.className = 'chat-message';
msgDiv.innerHTML = ` msgDiv.innerHTML = `
<div class="chat-timestamp">${new Date().toLocaleTimeString()} · <span class="badge bg-primary" style="font-size:.65rem;" id="agentBadge">wird ausgewählt…</span></div> <div class="chat-timestamp">${new Date().toLocaleTimeString()} · <span class="badge bg-primary" style="font-size:.65rem;" id="agentBadge">wird ausgewählt…</span></div>
<div class="chat-prompt mt-1"><strong>Sie:</strong> ${prompt}</div> <div class="chat-prompt mt-1"><strong>Sie:</strong> ${escapeHtml(prompt)}</div>
<div class="chat-response mt-1" id="responseDiv"><strong>Orchestrator:</strong> <span id="responseText">⏳ Agent arbeitet…</span></div> <div class="chat-response mt-1" id="responseDiv"><strong>Orchestrator:</strong> <span id="responseText">⏳ Agent arbeitet…</span></div>
`; `;
chatContainer.insertBefore(msgDiv, chatContainer.firstChild); chatContainer.insertBefore(msgDiv, chatContainer.firstChild);
@ -220,5 +220,11 @@ function distributeTodos() {
status.className = 'form-text mt-2 text-danger'; status.className = 'form-text mt-2 text-danger';
}); });
} }
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script> </script>
{% endblock %} {% endblock %}

View file

@ -40,13 +40,13 @@
<div class="card"> <div class="card">
<div class="card-header bg-info"> <div class="card-header bg-info">
<h5 class="mb-0">🔄 Auto-Refresh</h5> <h5 class="mb-0"> Info</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="form-check form-switch"> <p class="mb-0" style="font-size:0.875rem;color:var(--text-muted);">
<input class="form-check-input" type="checkbox" id="autoRefresh" onchange="toggleAutoRefresh()"> Tasks werden automatisch alle 10 Sekunden vom TaskBeat verarbeitet.
<label class="form-check-label" for="autoRefresh">Automatisch aktualisieren (alle 30s)</label> <br>Diese Seite aktualisiert sich automatisch alle 15 Sekunden wenn Tasks aktiv sind.
</div> </p>
</div> </div>
</div> </div>
</div> </div>
@ -108,14 +108,14 @@
</td> </td>
<td style="color:var(--text-muted);font-size:.75rem;">{{ task.created }}</td> <td style="color:var(--text-muted);font-size:.75rem;">{{ task.created }}</td>
<td> <td>
{% if task.type == 'email' %} {% if task.status == 'pending' %}
<span style="color:var(--text-muted);font-size:.75rem;">Auto</span> <span style="color:var(--text-muted);font-size:.75rem;">⏳ Wartend</span>
{% elif task.status == 'pending' %}
<a href="/tasks/update/{{ task.id }}/in_progress" class="btn btn-sm btn-primary">Start</a>
{% elif task.status == 'in_progress' %} {% elif task.status == 'in_progress' %}
<a href="/tasks/update/{{ task.id }}/completed" class="btn btn-sm btn-success">Fertig</a> <span style="color:var(--info);font-size:.75rem;">🔄 Läuft...</span>
{% elif task.status == 'completed' %}
<span style="color:var(--success);font-size:.75rem;">✓ Auto</span>
{% else %} {% else %}
<span style="color:var(--text-muted);"></span> <span style="color:var(--text-muted);font-size:.75rem;"></span>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
@ -137,20 +137,13 @@
{% block scripts %} {% block scripts %}
<script> <script>
let autoRefreshInterval = null; // Auto-Refresh alle 15 Sekunden wenn Tasks pending/in_progress sind
const hasPendingTasks = {{ 'true' if tasks|selectattr('status', 'in', ['pending', 'in_progress'])|list else 'false' }};
function toggleAutoRefresh() { if (hasPendingTasks) {
const checkbox = document.getElementById('autoRefresh'); setInterval(() => {
if (checkbox.checked) {
autoRefreshInterval = setInterval(() => {
location.reload(); location.reload();
}, 30000); }, 15000); // 15 Sekunden
} else {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
}
} }
</script> </script>
{% endblock %} {% endblock %}