Compare commits

..
4 Commits
Author SHA1 Message Date
duffyduckandClaude Opus 4.8 2bd7c747d9 fix(diagnostic): Bild-Upload landet im fokussierten Projekt statt Hauptchat
Datei-Uploads (Copy-Paste UND Datei-Picker) trugen im Diagnostic-Portal
den projectId des fokussierten Projekts nicht mit — anders als Text und
Zwischenrufe. Die Bridge las payload.projectId, bekam nichts und routete
die Datei-Bubble in den Hauptchat.

- index.html: sendDiagAttachments schickt projectId: focusedContextId
- server.js: send_file-Relay reicht projectId in die RVS-Payload weiter

Beide Upload-Wege laufen ueber handleDiagFileSelect → ein Fix deckt beide.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 08:38:58 +02:00
duffyduckandClaude Opus 4.8 d0694cb637 fix(voice): 'gute Nacht' loest nicht mehr faelschlich Ohr-Aus aus
Regression aus dem Schlaf-Idiom-Commit (954ad9a): 'gute nacht' im wake-off-Regex
matchte jeden Verabschiedungs-Gruss ('Ich wünsche dir eine gute Nacht') → Ohr
ging aus, Stefan hing in einer 'Ohr aus'-Schleife fest. 'gute nacht'/'schlaf
gut' sind Gruesse, kein Ohr-Aus-Befehl → raus aus dem Regex. Nur klare Imperative
an ARIA bleiben ('geh/leg dich schlafen', 'leg dich aufs Ohr/hin'); mehrdeutige
Faelle faengt ARIA per ear_control aus dem Kontext ab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 22:43:18 +02:00
duffyduckandClaude Opus 4.8 8f313eb68b feat(voice): ear_control-Tool — ARIA versteht Ohr-Aus/-An in jeder Formulierung
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 <noreply@anthropic.com>
2026-08-16 22:24:04 +02:00
duffyduckandClaude Opus 4.8 954ad9ade8 fix(voice): wake-off erkennt mehr Schlaf-Idiome
"leg dich schlafen" / "leg dich aufs Ohr" / "leg dich hin" / "gute Nacht" /
"schlaf gut" zusätzlich zu "geh schlafen". Sofort-Netz (instant, kein Claude-
Call). Der robustere Weg (ear_control-Tool, ARIA versteht jede Formulierung)
folgt separat, wenn Stefan will.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 22:19:29 +02:00
4 changed files with 64 additions and 5 deletions
+53 -2
View File
@@ -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": {
@@ -1455,7 +1483,12 @@ _WAKE_OFF_RE = re.compile(
rf"|\b(?:beende|beend\w*|deaktivier\w*|stopp?e?)\b[^.!?]{{0,20}}\b{_WAKE_NOUN}\b"
rf"|h[oö]r\s+auf\s+(?:zu\s*)?(?:zu)?(?:h[oö]r|lausch)\w*"
rf"|h[oö]r\s+nicht\s+mehr\s+zu"
rf"|geh\s+schlafen",
# Schlaf-Idiome NUR als klare Imperative an ARIA ('geh/leg dich schlafen',
# 'leg dich aufs Ohr/hin'). Mehrdeutige Gruesse ('gute Nacht', 'schlaf gut')
# bewusst NICHT — das sind Verabschiedungen, kein Ohr-Aus-Befehl; die faengt
# ARIA per ear_control aus dem Kontext ab, wenn's wirklich gemeint ist.
rf"|(?:geh|leg\s+dich)\s+schlafen"
rf"|leg\s+dich\s+(?:aufs?\s+ohr|hin)",
re.IGNORECASE,
)
@@ -2132,6 +2165,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,
@@ -2245,8 +2279,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 ───────────────────────────────────────
@@ -2841,6 +2883,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:
+7
View File
@@ -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.",
])
+3 -2
View File
@@ -2299,9 +2299,10 @@
}
function sendDiagAttachments() {
// Alle pending Dateien an RVS senden
// Alle pending Dateien an RVS senden — projectId des aktuellen Focus
// mitschicken, sonst landet die Datei-Bubble im Hauptchat statt im Projekt.
for (const f of diagPendingFiles) {
send({ action: 'send_file', name: f.name, type: f.type, size: f.size, base64: f.base64 });
send({ action: 'send_file', name: f.name, type: f.type, size: f.size, base64: f.base64, projectId: focusedContextId });
}
if (diagPendingFiles.length > 0) {
addChat('sent', `${diagPendingFiles.length} Anhang/Anhaenge`, 'Datei');
+1 -1
View File
@@ -2568,7 +2568,7 @@ wss.on("connection", (ws) => {
// Datei von Diagnostic an Bridge via RVS senden
sendToRVS_raw({
type: "file",
payload: { name: msg.name, type: msg.type, size: msg.size, base64: msg.base64 },
payload: { name: msg.name, type: msg.type, size: msg.size, base64: msg.base64, projectId: msg.projectId || "" },
timestamp: Date.now(),
});
log("info", "server", `Datei gesendet: ${msg.name} (${msg.type})`);