Add agent reminders, model selection, task distribution and delete functionality
This commit is contained in:
parent
56d9bc2c76
commit
84b2fe3dd7
16 changed files with 759 additions and 74 deletions
301
app.py
301
app.py
|
|
@ -18,6 +18,33 @@ from dotenv import load_dotenv
|
|||
|
||||
load_dotenv()
|
||||
|
||||
# ── Agent Konfiguration ───────────────────────────────────────────────────────
|
||||
AGENT_CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'agent_config.json')
|
||||
|
||||
def get_agent_config():
|
||||
"""Lädt die Agentenkonfiguration aus der JSON-Datei."""
|
||||
if os.path.exists(AGENT_CONFIG_FILE):
|
||||
try:
|
||||
with open(AGENT_CONFIG_FILE, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
pass
|
||||
return {}
|
||||
|
||||
def get_agent_model(agent_key):
|
||||
"""Gibt das konfigurierte Modell für einen Agenten zurück."""
|
||||
config = get_agent_config()
|
||||
return config.get(agent_key, {}).get('model', 'opencode/big-pickle')
|
||||
|
||||
def save_agent_config(agent_key, model):
|
||||
"""Speichert die Konfiguration für einen Agenten."""
|
||||
config = get_agent_config()
|
||||
if agent_key not in config:
|
||||
config[agent_key] = {}
|
||||
config[agent_key]['model'] = model
|
||||
with open(AGENT_CONFIG_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
# ── Email-Journal (SQLite) ──────────────────────────────────────────────────
|
||||
# Speichert jede gesehene Email mit Message-ID und Verarbeitungsstatus.
|
||||
# Verhindert, dass Emails verloren gehen wenn die App abstürzt.
|
||||
|
|
@ -185,10 +212,13 @@ def execute_agent_task(agent_key, user_prompt, extra_context=""):
|
|||
# System-Prompt + User-Prompt zusammen als eine Message
|
||||
# (--prompt flag gibt leere Antwort, daher alles in eine Message)
|
||||
combined_message = f"{full_system}\n\n---\n\n{user_prompt}"
|
||||
|
||||
|
||||
# Modell aus Konfiguration holen
|
||||
model = get_agent_model(agent_key)
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['opencode', 'run', '--format', 'json', combined_message],
|
||||
['opencode', 'run', '--model', model, '--format', 'json', combined_message],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300,
|
||||
|
|
@ -887,8 +917,9 @@ def agent_stream():
|
|||
yield f"data: {json.dumps({'type': 'processing', 'message': f'⏳ {agent_name} arbeitet...'})}\n\n"
|
||||
|
||||
try:
|
||||
model = get_agent_model(selected_agent)
|
||||
proc = subprocess.Popen(
|
||||
['opencode', 'run', '--format', 'json', full_prompt],
|
||||
['opencode', 'run', '--model', model, '--format', 'json', full_prompt],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
|
|
@ -958,35 +989,83 @@ def agents():
|
|||
|
||||
if os.path.exists(agents_dir):
|
||||
for agent_name in sorted(os.listdir(agents_dir)):
|
||||
prompt_file = os.path.join(agents_dir, agent_name, 'systemprompt.md')
|
||||
if os.path.isdir(os.path.join(agents_dir, agent_name)) and os.path.exists(prompt_file):
|
||||
with open(prompt_file, 'r', encoding='utf-8') as f:
|
||||
prompt_content = f.read()
|
||||
agent_path = os.path.join(agents_dir, agent_name)
|
||||
prompt_file = os.path.join(agent_path, 'systemprompt.md')
|
||||
reminders_file = os.path.join(agent_path, 'reminders.md')
|
||||
personality_file = os.path.join(agent_path, 'personality.md')
|
||||
|
||||
if os.path.isdir(agent_path):
|
||||
prompt_content = ''
|
||||
reminders_content = ''
|
||||
personality_content = ''
|
||||
|
||||
if os.path.exists(prompt_file):
|
||||
with open(prompt_file, 'r', encoding='utf-8') as f:
|
||||
prompt_content = f.read()
|
||||
|
||||
if os.path.exists(reminders_file):
|
||||
with open(reminders_file, 'r', encoding='utf-8') as f:
|
||||
reminders_content = f.read()
|
||||
else:
|
||||
reminders_content = '# Erinnerungen - ' + agent_name.title() + '\n\n## Aktuelle Tasks\n-\n\n## Notizen\n- \n\n## Letzte Aktionen\n- '
|
||||
|
||||
if os.path.exists(personality_file):
|
||||
with open(personality_file, 'r', encoding='utf-8') as f:
|
||||
personality_content = f.read()
|
||||
|
||||
agents_list.append({
|
||||
'name': agent_name,
|
||||
'prompt': prompt_content
|
||||
'prompt': prompt_content,
|
||||
'reminders': reminders_content,
|
||||
'personality': personality_content
|
||||
})
|
||||
|
||||
edit_agent = request.args.get('edit')
|
||||
|
||||
if not edit_agent and agents_list:
|
||||
return redirect(url_for('agents', edit=agents_list[0]['name']))
|
||||
|
||||
edit_prompt = ''
|
||||
edit_reminders = ''
|
||||
edit_personality = ''
|
||||
edit_model = 'opencode/big-pickle'
|
||||
if edit_agent:
|
||||
for agent in agents_list:
|
||||
if agent['name'] == edit_agent:
|
||||
edit_prompt = agent['prompt']
|
||||
edit_reminders = agent['reminders']
|
||||
edit_personality = agent['personality']
|
||||
break
|
||||
edit_model = get_agent_model(edit_agent)
|
||||
|
||||
if request.method == 'POST':
|
||||
agent_name = request.form.get('agent_name', '').strip()
|
||||
prompt_content = request.form.get('prompt_content', '')
|
||||
reminders_content = request.form.get('reminders_content', '')
|
||||
personality_content = request.form.get('personality_content', '')
|
||||
|
||||
if agent_name and prompt_content is not None:
|
||||
prompt_file = os.path.join(agents_dir, agent_name, 'systemprompt.md')
|
||||
with open(prompt_file, 'w', encoding='utf-8') as f:
|
||||
f.write(prompt_content)
|
||||
flash(f'System-Prompt für "{agent_name}" gespeichert!', 'success')
|
||||
return redirect(url_for('agents'))
|
||||
if agent_name:
|
||||
agent_path = os.path.join(agents_dir, agent_name)
|
||||
|
||||
if prompt_content is not None:
|
||||
prompt_file = os.path.join(agent_path, 'systemprompt.md')
|
||||
with open(prompt_file, 'w', encoding='utf-8') as f:
|
||||
f.write(prompt_content)
|
||||
|
||||
if reminders_content is not None:
|
||||
reminders_file = os.path.join(agent_path, 'reminders.md')
|
||||
with open(reminders_file, 'w', encoding='utf-8') as f:
|
||||
f.write(reminders_content)
|
||||
|
||||
if personality_content is not None:
|
||||
personality_file = os.path.join(agent_path, 'personality.md')
|
||||
with open(personality_file, 'w', encoding='utf-8') as f:
|
||||
f.write(personality_content)
|
||||
|
||||
flash(f'Daten für "{agent_name}" gespeichert!', 'success')
|
||||
return redirect(url_for('agents', edit=agent_name))
|
||||
|
||||
return render_template('agents.html', agents=AGENTS, agents_list=agents_list, edit_agent=edit_agent, edit_prompt=edit_prompt)
|
||||
return render_template('agents.html', agents=AGENTS, agents_list=agents_list, edit_agent=edit_agent, edit_prompt=edit_prompt, edit_reminders=edit_reminders, edit_personality=edit_personality, edit_model=edit_model)
|
||||
|
||||
|
||||
@app.route('/files', methods=['GET', 'POST'])
|
||||
|
|
@ -1196,5 +1275,197 @@ def journal_clear():
|
|||
return redirect(url_for('settings'))
|
||||
|
||||
|
||||
# ── Task API ────────────────────────────────────────────────────────────────
|
||||
@app.route('/api/tasks', methods=['GET', 'POST'])
|
||||
def api_tasks():
|
||||
"""API zum Erstellen und Abrufen von Tasks."""
|
||||
if request.method == 'POST':
|
||||
data = request.get_json()
|
||||
title = data.get('title', '').strip()
|
||||
description = data.get('description', '')
|
||||
assigned_agent = data.get('assigned_agent', '')
|
||||
agent_key = data.get('agent_key', '')
|
||||
|
||||
if not title:
|
||||
return jsonify({'error': 'Kein Titel übergeben'}), 400
|
||||
|
||||
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(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
|
||||
}
|
||||
tasks.append(new_task)
|
||||
|
||||
return jsonify({'success': True, 'task': new_task})
|
||||
|
||||
task_list = list(reversed(tasks))
|
||||
return jsonify({'tasks': task_list})
|
||||
|
||||
|
||||
@app.route('/api/tasks/<int:task_id>', methods=['PUT'])
|
||||
def update_task_api(task_id):
|
||||
"""API zum Aktualisieren eines Tasks."""
|
||||
data = request.get_json()
|
||||
new_status = data.get('status')
|
||||
|
||||
for task in tasks:
|
||||
if task['id'] == task_id:
|
||||
task['status'] = new_status
|
||||
return jsonify({'success': True, 'task': task})
|
||||
|
||||
return jsonify({'error': 'Task nicht gefunden'}), 404
|
||||
|
||||
|
||||
@app.route('/api/agent/<agent_name>/model', methods=['POST'])
|
||||
def set_agent_model(agent_name):
|
||||
"""Setzt das Modell für einen Agenten."""
|
||||
data = request.get_json()
|
||||
if data and 'model' in data:
|
||||
save_agent_config(agent_name, data['model'])
|
||||
return jsonify({'success': True, 'message': f'Modell für {agent_name} gesetzt auf {data["model"]}'})
|
||||
return jsonify({'error': 'Kein Modell übergeben'}), 400
|
||||
|
||||
|
||||
@app.route('/api/agent/<agent_name>/delete', methods=['DELETE'])
|
||||
def delete_agent(agent_name):
|
||||
"""Löscht einen Agenten (den gesamten Ordner)."""
|
||||
import shutil
|
||||
|
||||
agents_dir = os.path.join(os.path.dirname(__file__), 'agents')
|
||||
agent_path = os.path.join(agents_dir, agent_name)
|
||||
|
||||
if not os.path.isdir(agent_path):
|
||||
return jsonify({'error': 'Agent nicht gefunden'}), 404
|
||||
|
||||
try:
|
||||
shutil.rmtree(agent_path)
|
||||
|
||||
config = get_agent_config()
|
||||
if agent_name in config:
|
||||
del config[agent_name]
|
||||
with open(AGENT_CONFIG_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
return jsonify({'success': True, 'message': f'Agent "{agent_name}" wurde gelöscht.'})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/agent/<agent_name>/reminders', methods=['GET', 'POST'])
|
||||
def agent_reminders(agent_name):
|
||||
agents_dir = os.path.join(os.path.dirname(__file__), 'agents')
|
||||
agent_path = os.path.join(agents_dir, agent_name)
|
||||
reminders_file = os.path.join(agent_path, 'reminders.md')
|
||||
|
||||
if not os.path.isdir(agent_path):
|
||||
return jsonify({'error': 'Agent nicht gefunden'}), 404
|
||||
|
||||
if request.method == 'GET':
|
||||
if os.path.exists(reminders_file):
|
||||
with open(reminders_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
else:
|
||||
content = ''
|
||||
return jsonify({'reminders': content})
|
||||
|
||||
if request.method == 'POST':
|
||||
data = request.get_json()
|
||||
if data and 'reminders' in data:
|
||||
with open(reminders_file, 'w', encoding='utf-8') as f:
|
||||
f.write(data['reminders'])
|
||||
return jsonify({'success': True, 'message': 'Erinnerungen gespeichert'})
|
||||
return jsonify({'error': 'Keine Daten übergeben'}), 400
|
||||
|
||||
|
||||
@app.route('/api/orchestrator-distribute', methods=['POST'])
|
||||
def distribute_tasks():
|
||||
"""Verteilt Tasks parallel an mehrere Agenten."""
|
||||
data = request.get_json()
|
||||
tasks_list = data.get('tasks', [])
|
||||
selected_agents = data.get('agents', [])
|
||||
|
||||
if not tasks_list:
|
||||
return jsonify({'error': 'Keine Tasks übergeben'}), 400
|
||||
if not selected_agents:
|
||||
return jsonify({'error': 'Keine Agenten ausgewählt'}), 400
|
||||
|
||||
results = []
|
||||
|
||||
def run_task_for_agent(agent_key, task):
|
||||
response = execute_agent_task(agent_key, task)
|
||||
|
||||
reminders_file = os.path.join(os.path.dirname(__file__), 'agents', agent_key, 'reminders.md')
|
||||
if os.path.exists(reminders_file):
|
||||
try:
|
||||
with open(reminders_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||
new_entry = f"\n- {timestamp}: {task[:100]}... - erledigt"
|
||||
|
||||
if '## Letzte Aktionen' in content:
|
||||
content = content.replace('## Letzte Aktionen', f'## Letzte Aktionen{new_entry}')
|
||||
else:
|
||||
content += new_entry
|
||||
|
||||
with open(reminders_file, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
except:
|
||||
pass
|
||||
|
||||
return {
|
||||
'agent': agent_key,
|
||||
'task': task,
|
||||
'response': response[:200] + '...' if len(response) > 200 else response
|
||||
}
|
||||
|
||||
import concurrent.futures
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=len(selected_agents)) as executor:
|
||||
futures = []
|
||||
for i, task in enumerate(tasks_list):
|
||||
agent = selected_agents[i % len(selected_agents)]
|
||||
future = executor.submit(run_task_for_agent, agent, task)
|
||||
futures.append(future)
|
||||
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
try:
|
||||
results.append(future.result())
|
||||
except Exception as e:
|
||||
results.append({'error': str(e)})
|
||||
|
||||
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)
|
||||
|
||||
new_task = {
|
||||
'id': len(tasks) + 1 + i,
|
||||
'title': task_text[:80],
|
||||
'description': 'Automatisch erstellt durch Orchestrator',
|
||||
'assigned_agent': agent_name,
|
||||
'status': 'completed',
|
||||
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
||||
'type': 'orchestrated',
|
||||
'agent_key': agent_key
|
||||
}
|
||||
tasks.append(new_task)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Tasks verteilt',
|
||||
'results': results
|
||||
})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=False, host='0.0.0.0', port=5000, threaded=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue