From 8f313eb68b3876bad67199492cc9c8877b3efd9e Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 16 Aug 2026 22:24:04 +0200 Subject: [PATCH] =?UTF-8?q?feat(voice):=20ear=5Fcontrol-Tool=20=E2=80=94?= =?UTF-8?q?=20ARIA=20versteht=20Ohr-Aus/-An=20in=20jeder=20Formulierung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Statt jede Schlaf-/Aus-Formulierung per Regex aufzuzählen: ein Tool ear_control (action off|on), das ARIA aufruft, wenn Stefan sinngemäß "hör auf/wieder zu zuhören" sagt — egal wie ("leg dich schlafen", "ich geh ins Bett, du kannst aus", "mach Pause vom Zuhören"). Tools ruft ARIA zuverlässig (im Gegensatz zu den [[..]]-Markern, die sie oft ignoriert). Nutzt die komplette bestehende Plumbing: der Tool-Call setzt _ear_control → answered_by wird "wake-off"/"wake-on" → main.py setzt wake_off/wake_on → Bridge → App stoppt/startet Listener. Reiner Steuerbefehl (still, kein Weiterlauschen). Der deterministische Regex-Detektor bleibt als schnelles Netz für die Standard-Sätze (instant, kein Claude-Call). Prompt-Hinweis in der Voice-Flow- Sektion ergänzt. Brain-only — kein neues APK nötig. Co-Authored-By: Claude Opus 4.8 --- aria-brain/agent.py | 48 ++++++++++++++++++++++++++++++++++++++++++- aria-brain/prompts.py | 7 +++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/aria-brain/agent.py b/aria-brain/agent.py index a2d2a09..07f4607 100644 --- a/aria-brain/agent.py +++ b/aria-brain/agent.py @@ -132,6 +132,34 @@ WEB_SEARCH_TOOL = { # Meta-Tool: ARIA kann selbst neue Skills bauen META_TOOLS = [ + { + "type": "function", + "function": { + "name": "ear_control", + "description": ( + "Schaltet dein Ohr (den Wake-Word-Listener) an oder aus. Nutze das, " + "wenn Stefan will dass du aufhoerst zuzuhoeren — EGAL wie er es " + "formuliert: 'leg dich schlafen', 'geh schlafen', 'Ohr aus', 'gute " + "Nacht', 'mach mal Pause vom Zuhoeren', 'ich geh ins Bett, du kannst " + "aus' → action='off'. Wenn er dich wieder aktivieren will ('Ohr an', " + "'wach auf', 'hoer wieder zu') → action='on'. Reiner Steuerbefehl: " + "die App stoppt/startet den Listener, du bestaetigst nur kurz (wird " + "nicht vorgelesen). Nach 'off' geht Wieder-An per 'Ohr an' (Text/" + "Aufnahme-Knopf) oder App-Button." + ), + "parameters": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["off", "on"], + "description": "off = Ohr aus (nicht mehr zuhoeren), on = Ohr wieder an", + }, + }, + "required": ["action"], + }, + }, + }, { "type": "function", "function": { @@ -2135,6 +2163,7 @@ class Agent: # Konversation bleiben gesprochen. self._claude_turn_speak = True self._claude_turn_converse = True # Default Gespraech; run_*-Skill setzt es + self._ear_control = None # ear_control-Tool: 'off'|'on' oder None try: for iteration in range(self.MAX_TOOL_ITERATIONS): result = self.proxy.chat_full(messages, tools=tools, @@ -2248,8 +2277,16 @@ class Agent: converse = False elif _wants_continue: converse = True + # ear_control-Tool aufgerufen → Ohr schalten. answered_by=wake-off/wake-on + # nutzt die bestehende Plumbing (main.py → wake_off/wake_on → Bridge → App). + # Reiner Steuerbefehl: still + kein Weiterlauschen. + answered_by = "claude" + if self._ear_control == "off": + answered_by, speak, converse = "wake-off", False, False + elif self._ear_control == "on": + answered_by, speak, converse = "wake-on", False, False # awaiting_reply = ARIA stellt eine blockierende Rueckfrage (Queue pausiert). - return (final_reply, "claude", speak, converse, awaiting_reply) + return (final_reply, answered_by, speak, converse, awaiting_reply) # ── Tool-Dispatcher ─────────────────────────────────────── @@ -2844,6 +2881,15 @@ class Agent: f"WICHTIG: Schreibe in deiner Antwort an Stefan den Pfad EXAKT als " f"Marker: [FILE: {result['path']}] — dann zeigt die App das Bild inline." ) + if name == "ear_control": + action = (arguments.get("action") or "").strip().lower() + if action not in ("off", "on"): + return "FEHLER: action muss 'off' oder 'on' sein." + self._ear_control = action + logger.info("[ear_control] action=%s — App schaltet den Listener", action) + return ("Ohr wird ausgeschaltet — Wieder-An per 'Ohr an' (Text/" + "Aufnahme-Knopf) oder App-Button." if action == "off" + else "Ohr wird wieder eingeschaltet.") if name == "memory_search": query = (arguments.get("query") or "").strip() if not query: diff --git a/aria-brain/prompts.py b/aria-brain/prompts.py index 7bdbf09..0c73cc7 100644 --- a/aria-brain/prompts.py +++ b/aria-brain/prompts.py @@ -199,6 +199,13 @@ def build_voice_flow_section() -> str: "- Ohne Marker = normales Gespraech: du wirst vorgelesen und ich lausche " "danach kurz weiter (Stefan kann einfach antworten, ohne 'Computer').", "- Nie widerspruechlich: `[[ENDE]]` schlaegt `[[WEITER]]`.", + "", + "**Ohr aus/an:** Wenn Stefan will dass du aufhoerst zuzuhoeren — egal wie " + "formuliert ('leg dich schlafen', 'geh schlafen', 'Ohr aus', 'gute Nacht', " + "'mach Pause vom Zuhoeren') — ruf das Tool `ear_control(action='off')`. " + "Wieder aktivieren ('Ohr an', 'wach auf', 'hoer wieder zu') → " + "`ear_control(action='on')`. Die App stoppt/startet dann den Listener; du " + "bestaetigst nur kurz.", ])