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
This commit is contained in:
pdyde 2026-02-21 12:36:24 +01:00
parent 93eb8c6d47
commit ca820d20db
3 changed files with 290 additions and 41 deletions

282
app.py
View file

@ -60,7 +60,8 @@ def get_agent_memory(agent_key, memory_type='tasks'):
try:
with open(memory_file, 'r', encoding='utf-8') as f:
return json.load(f)
except:
except (json.JSONDecodeError, IOError, OSError) as e:
logging.warning(f"Fehler beim Laden von {memory_file}: {e}")
pass
return []
@ -210,7 +211,8 @@ def get_agent_config():
try:
with open(AGENT_CONFIG_FILE, 'r', encoding='utf-8') as 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
return {}
@ -396,21 +398,53 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
# Memory-Zusammenfassung laden
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_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}
## Wissensdatenbank (Diversity-Ball):
{kb_content}
## Deine Erinnerungen:
{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:
- Du hast Zugriff auf das Internet via WebFetch-Tool - nutze es aktiv!
- 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],
capture_output=True,
text=True,
timeout=300,
timeout=600, # 10 Minuten für komplexe Tasks
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)
if data.get('part', {}).get('type') == 'text':
response_text += data.get('part', {}).get('text', '')
except:
except (json.JSONDecodeError, KeyError):
# Ignore invalid JSON lines in streaming output
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."
else:
return f"⚠️ Fehler: {result.stderr}"
@ -494,6 +534,202 @@ tasks = []
chat_history = []
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():
kb_path = os.path.join(os.path.dirname(__file__), 'diversityball_knowledge.md')
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]
else:
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 ''
@ -1036,7 +1273,7 @@ def process_beat_tasks():
while True:
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:
agent_key = task.get('agent_key') or task.get('assigned_agent', '')
@ -1077,10 +1314,22 @@ Agent-Beschreibungen:
if assigned not in AGENTS:
assigned = available_agents[0]
# Sub-Task mit KOMPLETTEM Kontext
sub_task = {
'id': len(tasks) + 1 + len(created_sub_tasks),
'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),
'agent_key': assigned,
'status': 'pending',
@ -1133,7 +1382,8 @@ Agent-Beschreibungen:
except Exception as e:
logger.error("[TaskBeat] Fehler: %s", str(e))
time.sleep(30)
# Beat-Intervall: 10 Sekunden (statt 30)
time.sleep(10)
def start_task_beat():