fix(trigger): Antwort landet im Projekt des Timers — und der Zustand luegt nicht mehr

Zwei Fehler, die zusammen wie "der Timer hat nicht gefeuert" aussahen.

1) Trigger hatten KEIN Projekt. Beim Feuern ging die Antwort ohne projectId
   durch _process_core_response — also immer in den Hauptchat, auch wenn der
   Timer mitten in einem Projekt-Thread gestellt wurde. Im Projekt blieb es
   still, und es sah aus wie "nicht gefeuert". Jetzt wird das Projekt des
   Requests mit angelegt, der getriggerte Turn laeuft darin, und die Antwort
   kommt dort zurueck.

2) trigger_list zeigte nur "aktiv" / "DEAKTIVIERT" und verschwieg fire_count
   und last_fired_at. Ein One-Shot-Timer wird NACH dem Feuern automatisch
   deaktiviert — "hat sauber gefeuert" und "war nie an" sahen damit identisch
   aus. ARIA hat genau das falsch gelesen und berichtet, sie habe vergessen den
   Timer zu aktivieren, obwohl er puenktlich gefeuert hatte. Die Ausgabe nennt
   jetzt ausdruecklich "SCHON GEFEUERT (Nx, zuletzt <Zeit>)" plus den Kontext,
   in dem die Antwort landet.

Nachgewiesen am Fall flugwetter-whv-freitag-0900: gefeuert 07:00:05 UTC
(= 09:00 lokal), Antwort 46s spaeter, fire_count=1 — aber active=false im
Manifest und die Antwort im Hauptchat statt im Projekt-Thread.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-03 10:17:35 +02:00
co-authored by Claude Opus 5
parent e628170760
commit 12b8362d26
4 changed files with 75 additions and 18 deletions
+15 -5
View File
@@ -4618,11 +4618,15 @@ class ARIABridge:
trigger_name = data.get("trigger_name", "")
ttype = data.get("type", "trigger")
events = data.get("events") or []
logger.info("[bridge ← brain] Trigger '%s' (%s) gefeuert, reply=%d chars, events=%d",
trigger_name, ttype, len(reply), len(events))
trigger_pid = str(data.get("projectId") or "")
logger.info("[bridge ← brain] Trigger '%s' (%s) gefeuert, reply=%d chars, "
"events=%d, project=%s",
trigger_name, ttype, len(reply), len(events),
trigger_pid or "(main)")
# Async-spawn — HTTP-Antwort nicht durch RVS-Broadcast blockieren
asyncio.create_task(
self._handle_trigger_fired(reply, trigger_name, ttype, events)
self._handle_trigger_fired(reply, trigger_name, ttype, events,
trigger_pid)
)
await _send_response(writer, 200, {"ok": True})
elif method == "POST" and path == "/internal/agent-activity":
@@ -5233,7 +5237,8 @@ class ARIABridge:
return {"ok": True, "role": role, "content_preview": content[:80]}
async def _handle_trigger_fired(self, reply: str, trigger_name: str,
ttype: str, events: list) -> None:
ttype: str, events: list,
project_id: str = "") -> None:
"""Spiegelt eine Brain-Trigger-Antwort wie eine normale ARIA-Antwort.
Side-Channel-Events zuerst (trigger_created, location_tracking, ...),
@@ -5285,7 +5290,12 @@ class ARIABridge:
try:
await self._process_core_response(
reply,
{"metadata": {"trigger_name": trigger_name, "trigger_type": ttype}},
{"metadata": {"trigger_name": trigger_name, "trigger_type": ttype},
# Ohne projectId landete jede Trigger-Antwort im Hauptchat —
# auch wenn der Timer in einem Projekt-Thread gestellt wurde.
# Stefan sah dann im Projekt nichts und hielt den Timer fuer
# nicht gefeuert.
"projectId": project_id or ""},
)
except Exception:
logger.exception("[trigger-fire] _process_core_response fehlgeschlagen")