diff --git a/aria-brain/agent.py b/aria-brain/agent.py index c851190..e0e2fce 100644 --- a/aria-brain/agent.py +++ b/aria-brain/agent.py @@ -1226,6 +1226,11 @@ class Agent: cfg = router_mod.load_config() if not router_mod.should_try_local(user_message, cfg): return None + # Merker: hat dieser lokale Turn einen echten Skill (run_*) ausgefuehrt? + # Dann ist es ein Steuerbefehl (z.B. Spotify) → NICHT vorlesen (speak= + # False), wie beim Fast-Path. Info-Tools (web_search/memory_search/ + # trigger_timer) zaehlen NICHT — deren Antwort/Bestaetigung wird gesprochen. + self._local_turn_executed_skill = False local_only = bool(cfg.get("localOnly")) local_model = cfg.get("localLlmModel") or "qwen3-8b" # B0.5: llama-swap-Key tools = self._build_local_tools() # B1b: kuratierte Tools @@ -1273,6 +1278,10 @@ class Agent: logger.info("[router] lokal Tool-Call: %s(%s)", tname, ", ".join(targs.keys())) tresult = self._dispatch_tool(tname, targs) + # Echter Skill (run_*) ausgefuehrt = Steuerbefehl → nachher + # nicht vorlesen. Nur bei Erfolg (Fehler → eskaliert eh). + if tname.startswith("run_") and not self._local_tool_failed(tname, tresult): + self._local_turn_executed_skill = True if self._local_tool_failed(tname, tresult): had_error = True messages.append({"role": "tool", @@ -1383,7 +1392,11 @@ class Agent: # teure Claude-Aufbau + Tool-Loop wird uebersprungen. Sonst None → Claude. local_reply = self._try_local_fast_lane(user_message, active_project_id) if local_reply is not None: - return local_reply, "local", True # ARIA-Antwort → vorlesen ok + # Hat local einen echten Skill (run_*) ausgefuehrt → Steuerbefehl, + # NICHT vorlesen (kein TTS, App beendet direkt statt 30s-Gespraech) — + # genau wie Fast-Path. Sonst normale gesprochene Antwort. + speak = not getattr(self, "_local_turn_executed_skill", False) + return local_reply, "local", speak # 2. Hot Memory (alle pinned Punkte) hot = self.store.list_pinned()