Fix task ordering (FIFO) + Telegram standup reply matching

- get_tasks(): ORDER BY id ASC (oldest first) for TaskBeat FIFO processing
- UI calls use order='desc' so display stays newest-first
- trigger_daily_standup(): standup sub-tasks now stored as type='standup_reply'
  with telegram_chat_id set correctly so replies can be matched
- telegram_message_handler: checks for open standup_reply task from same
  chat_id before creating new task - if found, marks standup complete and
  creates orchestrator follow-up task with full context to process the reply
- tasks.html: badge for standup_reply type
This commit is contained in:
eric 2026-02-21 20:22:39 +00:00
parent 01858b5fc6
commit 8780c2b176
2 changed files with 89 additions and 22 deletions

109
app.py
View file

@ -712,16 +712,21 @@ chat_history = []
orchestrator_chat = [] orchestrator_chat = []
# ── Task Database Functions ───────────────────────────────────────────────── # ── Task Database Functions ─────────────────────────────────────────────────
def get_tasks(status=None, limit=None): def get_tasks(status=None, limit=None, order='asc'):
"""Lädt Tasks aus der Datenbank.""" """Lädt Tasks aus der Datenbank.
order='asc' älteste zuerst (Standard für TaskBeat: FIFO)
order='desc' neueste zuerst (für UI-Anzeige)
"""
con = sqlite3.connect(EMAIL_JOURNAL_DB) con = sqlite3.connect(EMAIL_JOURNAL_DB)
con.row_factory = sqlite3.Row con.row_factory = sqlite3.Row
direction = 'ASC' if order == 'asc' else 'DESC'
if status: if status:
query = "SELECT * FROM tasks WHERE status = ? ORDER BY id DESC" query = f"SELECT * FROM tasks WHERE status = ? ORDER BY id {direction}"
params = (status,) params = (status,)
else: else:
query = "SELECT * FROM tasks ORDER BY id DESC" query = f"SELECT * FROM tasks ORDER BY id {direction}"
params = () params = ()
if limit: if limit:
@ -1596,30 +1601,84 @@ async def telegram_message_handler(update: Update, context: ContextTypes.DEFAULT
user_id = update.effective_user.id user_id = update.effective_user.id
username = update.effective_user.username or update.effective_user.first_name username = update.effective_user.username or update.effective_user.first_name
message_text = update.message.text message_text = update.message.text
chat_id = update.effective_chat.id
# Whitelist-Check # Whitelist-Check
if TELEGRAM_CONFIG['allowed_users'] and user_id not in TELEGRAM_CONFIG['allowed_users']: if TELEGRAM_CONFIG['allowed_users'] and user_id not in TELEGRAM_CONFIG['allowed_users']:
await update.message.reply_text("⛔ Zugriff verweigert. Verwende /start für Details.") await update.message.reply_text("⛔ Zugriff verweigert. Verwende /start für Details.")
return return
# Task erstellen # ── Prüfe ob diese Nachricht eine Antwort auf einen offenen standup_reply-Task ist ──
try:
con = sqlite3.connect(EMAIL_JOURNAL_DB)
con.row_factory = sqlite3.Row
open_standup = con.execute(
"SELECT id, parent_task_id, title FROM tasks "
"WHERE type='standup_reply' AND status='pending' AND telegram_chat_id=? "
"ORDER BY id ASC LIMIT 1",
(chat_id,)
).fetchone()
con.close()
except Exception as e:
logging.error(f"[Telegram] DB-Fehler beim Standup-Check: {e}")
open_standup = None
if open_standup:
# Antwort auf offenen Standup-Task → mit Kontext an Orchestrator weiterleiten
standup_task_id = open_standup['id']
parent_id = open_standup['parent_task_id']
# Orchestrator-Task erstellen: verarbeite die Standup-Antwort
orchestrator_prompt = (
f"## Standup-Antwort von {username}\n\n"
f"**Antwort:** {message_text}\n\n"
f"**Aufgabe:**\n"
f"1. Verarbeite diese Standup-Antwort und extrahiere wichtige Informationen.\n"
f"2. Falls neue Informationen enthalten sind (Änderungen, Entscheidungen, Probleme): "
f"aktualisiere die Wissensdatenbank mit `<update_knowledge>` und informiere betroffene Agenten mit `<update_agent_reminder>`.\n"
f"3. Falls Handlungsbedarf besteht: erstelle entsprechende Tasks mit `<create_task>`.\n"
f"4. Antworte {username} kurz per Telegram (telegram_id: {chat_id}) und bestätige den Empfang."
)
followup_id = create_task(
title=f"Standup-Antwort verarbeiten: {username}",
description=orchestrator_prompt,
agent_key='orchestrator',
task_type='telegram',
created_by=f'telegram_user_{user_id}',
telegram_chat_id=chat_id,
telegram_user=username,
parent_task_id=parent_id,
)
# Standup-reply-Task als beantwortet markieren
update_task_db(standup_task_id, status='completed',
response=f"Antwort erhalten von {username}: {message_text[:200]}")
await update.message.reply_text(
f"✅ Danke {username}! Deine Standup-Antwort wurde aufgenommen.\n\n"
f"📝 Der Orchestrator verarbeitet dein Update (Task #{followup_id})."
)
logging.info(f"[Telegram] Standup-Antwort von {username} → Task #{standup_task_id} completed, Follow-up #{followup_id} erstellt")
return
# ── Normale Nachricht → neuer Orchestrator-Task ──────────────────────────
task_id = create_task( task_id = create_task(
title=f"Telegram: {message_text[:50]}{'...' if len(message_text) > 50 else ''}", title=f"Telegram: {message_text[:50]}{'...' if len(message_text) > 50 else ''}",
description=message_text, description=message_text,
agent_key='orchestrator', agent_key='orchestrator',
task_type='telegram', task_type='telegram',
created_by=f'telegram_user_{user_id}', created_by=f'telegram_user_{user_id}',
telegram_chat_id=update.effective_chat.id, telegram_chat_id=chat_id,
telegram_user=username telegram_user=username
) )
await update.message.reply_text( await update.message.reply_text(
f"✅ Task #{task_id} erstellt!\n\n" f"✅ Task #{task_id} erstellt!\n\n"
f"📝 {message_text[:100]}{'...' if len(message_text) > 100 else ''}\n\n" f"📝 {message_text[:100]}{'...' if len(message_text) > 100 else ''}\n\n"
f"⏳ Der Orchestrator wird deine Anfrage verarbeiten.\n" f"⏳ Der Orchestrator wird deine Anfrage verarbeiten.\n"
f"Ich benachrichtige dich, sobald die Antwort bereit ist." f"Ich benachrichtige dich, sobald die Antwort bereit ist."
) )
logging.info(f"[Telegram] Task #{task_id} created by {username} (ID: {user_id})") logging.info(f"[Telegram] Task #{task_id} created by {username} (ID: {user_id})")
@ -2399,23 +2458,29 @@ def trigger_daily_standup():
# Telegram bevorzugen, Fallback auf Email # Telegram bevorzugen, Fallback auf Email
if telegram_id: if telegram_id:
create_task( standup_sub_id = create_task(
title=f"Standup-Frage an {name} (Telegram)", title=f"Standup-Antwort ausstehend: {name}",
description=f"Telegram-ID: {telegram_id}\nNachricht: {msg}", description=(
f"Standup-Frage wurde per Telegram an {name} gesendet.\n"
f"Warte auf Antwort...\n\n"
f"Gesendete Frage:\n{msg}"
),
agent_key='orchestrator', agent_key='orchestrator',
task_type='action_telegram', task_type='standup_reply',
created_by='system', created_by='system',
parent_task_id=standup_task_id, parent_task_id=standup_task_id,
telegram_chat_id=int(telegram_id),
telegram_user=name,
) )
send_telegram_message(telegram_id, msg) send_telegram_message(telegram_id, msg)
logger.info("[DailyStandup] Telegram-Standup gesendet an %s (%s)", name, telegram_id) logger.info("[DailyStandup] Telegram-Standup gesendet an %s (%s), Task #%s wartet auf Antwort", name, telegram_id, standup_sub_id)
elif email: elif email:
subject = f"Daily Standup {today} — Was gibt's Neues?" subject = f"Daily Standup {today} — Was gibt's Neues?"
create_task( create_task(
title=f"Standup-Frage an {name} (Email)", title=f"Standup-Antwort ausstehend: {name} (Email)",
description=f"An: {email}\nBetreff: {subject}\n\n{msg}", description=f"An: {email}\nBetreff: {subject}\n\n{msg}",
agent_key='orchestrator', agent_key='orchestrator',
task_type='action_email', task_type='standup_reply',
created_by='system', created_by='system',
parent_task_id=standup_task_id, parent_task_id=standup_task_id,
) )
@ -2580,7 +2645,7 @@ def logout():
@login_required @login_required
def index(): def index():
# Hole die 5 neuesten Tasks aus DB # Hole die 5 neuesten Tasks aus DB
all_tasks = get_tasks() all_tasks = get_tasks(order='desc')
recent_tasks = all_tasks[:5] if all_tasks else [] recent_tasks = all_tasks[:5] if all_tasks else []
return render_template('index.html', agents=AGENTS, recent_tasks=recent_tasks) return render_template('index.html', agents=AGENTS, recent_tasks=recent_tasks)
@ -2713,8 +2778,8 @@ def task_list():
) )
flash(f'Task #{task_id} erstellt!', 'success') flash(f'Task #{task_id} erstellt!', 'success')
# Alle Tasks aus Datenbank holen Neueste zuerst # Alle Tasks aus Datenbank holen Neueste zuerst (für UI)
all_tasks = get_tasks() all_tasks = get_tasks(order='desc')
return render_template('tasks.html', agents=AGENTS, tasks=all_tasks) return render_template('tasks.html', agents=AGENTS, tasks=all_tasks)
@app.route('/tasks/<int:task_id>') @app.route('/tasks/<int:task_id>')
@ -3577,8 +3642,8 @@ def api_tasks():
return jsonify({'success': True, 'task': new_task}) return jsonify({'success': True, 'task': new_task})
# GET: Alle Tasks aus DB # GET: Alle Tasks aus DB (neueste zuerst für API)
task_list = get_tasks() task_list = get_tasks(order='desc')
return jsonify({'tasks': task_list}) return jsonify({'tasks': task_list})

View file

@ -52,6 +52,8 @@
<span class="badge bg-warning ms-1">❓ Frage</span> <span class="badge bg-warning ms-1">❓ Frage</span>
{% elif task.type == 'standup' %} {% elif task.type == 'standup' %}
<span class="badge ms-1" style="background-color:#0d9488;">☀ Standup</span> <span class="badge ms-1" style="background-color:#0d9488;">☀ Standup</span>
{% elif task.type == 'standup_reply' %}
<span class="badge ms-1" style="background-color:#0d9488;">💬 Standup-Antwort</span>
{% elif task.type == 'broadcast' %} {% elif task.type == 'broadcast' %}
<span class="badge ms-1" style="background-color:#b45309;">📡 Broadcast</span> <span class="badge ms-1" style="background-color:#b45309;">📡 Broadcast</span>
{% elif task.type == 'action_knowledge' %} {% elif task.type == 'action_knowledge' %}