Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bd7c747d9 | ||
|
|
d0694cb637 | ||
|
|
8f313eb68b | ||
|
|
954ad9ade8 | ||
|
|
75fa574a85 | ||
|
|
a3d7933949 |
@@ -79,8 +79,8 @@ android {
|
|||||||
applicationId "com.ariacockpit"
|
applicationId "com.ariacockpit"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 20407
|
versionCode 20408
|
||||||
versionName "0.2.4.7"
|
versionName "0.2.4.8"
|
||||||
// Fallback fuer Libraries mit Product Flavors
|
// Fallback fuer Libraries mit Product Flavors
|
||||||
missingDimensionStrategy 'react-native-camera', 'general'
|
missingDimensionStrategy 'react-native-camera', 'general'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aria-cockpit",
|
"name": "aria-cockpit",
|
||||||
"version": "0.2.4.7",
|
"version": "0.2.4.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
+53
-2
@@ -132,6 +132,34 @@ WEB_SEARCH_TOOL = {
|
|||||||
|
|
||||||
# Meta-Tool: ARIA kann selbst neue Skills bauen
|
# Meta-Tool: ARIA kann selbst neue Skills bauen
|
||||||
META_TOOLS = [
|
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",
|
"type": "function",
|
||||||
"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"|\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+auf\s+(?:zu\s*)?(?:zu)?(?:h[oö]r|lausch)\w*"
|
||||||
rf"|h[oö]r\s+nicht\s+mehr\s+zu"
|
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,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2132,6 +2165,7 @@ class Agent:
|
|||||||
# Konversation bleiben gesprochen.
|
# Konversation bleiben gesprochen.
|
||||||
self._claude_turn_speak = True
|
self._claude_turn_speak = True
|
||||||
self._claude_turn_converse = True # Default Gespraech; run_*-Skill setzt es
|
self._claude_turn_converse = True # Default Gespraech; run_*-Skill setzt es
|
||||||
|
self._ear_control = None # ear_control-Tool: 'off'|'on' oder None
|
||||||
try:
|
try:
|
||||||
for iteration in range(self.MAX_TOOL_ITERATIONS):
|
for iteration in range(self.MAX_TOOL_ITERATIONS):
|
||||||
result = self.proxy.chat_full(messages, tools=tools,
|
result = self.proxy.chat_full(messages, tools=tools,
|
||||||
@@ -2245,8 +2279,16 @@ class Agent:
|
|||||||
converse = False
|
converse = False
|
||||||
elif _wants_continue:
|
elif _wants_continue:
|
||||||
converse = True
|
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).
|
# 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 ───────────────────────────────────────
|
# ── Tool-Dispatcher ───────────────────────────────────────
|
||||||
|
|
||||||
@@ -2841,6 +2883,15 @@ class Agent:
|
|||||||
f"WICHTIG: Schreibe in deiner Antwort an Stefan den Pfad EXAKT als "
|
f"WICHTIG: Schreibe in deiner Antwort an Stefan den Pfad EXAKT als "
|
||||||
f"Marker: [FILE: {result['path']}] — dann zeigt die App das Bild inline."
|
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":
|
if name == "memory_search":
|
||||||
query = (arguments.get("query") or "").strip()
|
query = (arguments.get("query") or "").strip()
|
||||||
if not query:
|
if not query:
|
||||||
|
|||||||
@@ -199,6 +199,13 @@ def build_voice_flow_section() -> str:
|
|||||||
"- Ohne Marker = normales Gespraech: du wirst vorgelesen und ich lausche "
|
"- Ohne Marker = normales Gespraech: du wirst vorgelesen und ich lausche "
|
||||||
"danach kurz weiter (Stefan kann einfach antworten, ohne 'Computer').",
|
"danach kurz weiter (Stefan kann einfach antworten, ohne 'Computer').",
|
||||||
"- Nie widerspruechlich: `[[ENDE]]` schlaegt `[[WEITER]]`.",
|
"- 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.",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2299,9 +2299,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendDiagAttachments() {
|
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) {
|
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) {
|
if (diagPendingFiles.length > 0) {
|
||||||
addChat('sent', `${diagPendingFiles.length} Anhang/Anhaenge`, 'Datei');
|
addChat('sent', `${diagPendingFiles.length} Anhang/Anhaenge`, 'Datei');
|
||||||
|
|||||||
@@ -2568,7 +2568,7 @@ wss.on("connection", (ws) => {
|
|||||||
// Datei von Diagnostic an Bridge via RVS senden
|
// Datei von Diagnostic an Bridge via RVS senden
|
||||||
sendToRVS_raw({
|
sendToRVS_raw({
|
||||||
type: "file",
|
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(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
log("info", "server", `Datei gesendet: ${msg.name} (${msg.type})`);
|
log("info", "server", `Datei gesendet: ${msg.name} (${msg.type})`);
|
||||||
|
|||||||
@@ -126,6 +126,11 @@ SILERO_VAD_ENABLED = os.getenv("SILERO_VAD_ENABLED", "true").lower() in ("1", "t
|
|||||||
SILERO_VAD_THRESHOLD = float(os.getenv("SILERO_VAD_THRESHOLD", "0.5"))
|
SILERO_VAD_THRESHOLD = float(os.getenv("SILERO_VAD_THRESHOLD", "0.5"))
|
||||||
SILERO_MIN_SPEECH_MS = int(os.getenv("SILERO_MIN_SPEECH_MS", "150"))
|
SILERO_MIN_SPEECH_MS = int(os.getenv("SILERO_MIN_SPEECH_MS", "150"))
|
||||||
SILERO_PAD_MS = int(os.getenv("SILERO_PAD_MS", "200"))
|
SILERO_PAD_MS = int(os.getenv("SILERO_PAD_MS", "200"))
|
||||||
|
# Speech-Endpoint gegen laute Umgebungsmusik: der RMS-Stille-Endpoint feuert bei
|
||||||
|
# durchgehender Musik NIE (Energie bleibt oben). Deshalb waehrend lauter Phasen
|
||||||
|
# periodisch (alle X ms) mit Silero pruefen, ob im letzten endpoint_ms-Fenster
|
||||||
|
# ueberhaupt noch Sprache ist — wenn nicht (nur Musik/Stille), Turn beenden.
|
||||||
|
STREAM_SPEECH_ENDPOINT_CHECK_MS = int(os.getenv("STREAM_SPEECH_ENDPOINT_CHECK_MS", "700"))
|
||||||
|
|
||||||
_vad_state = {"model": None, "get_ts": None, "failed": False}
|
_vad_state = {"model": None, "get_ts": None, "failed": False}
|
||||||
|
|
||||||
@@ -281,6 +286,7 @@ class StreamSession:
|
|||||||
last_growth_at: float = 0.0
|
last_growth_at: float = 0.0
|
||||||
last_transcribe_at: float = 0.0
|
last_transcribe_at: float = 0.0
|
||||||
last_voice_at: float = 0.0
|
last_voice_at: float = 0.0
|
||||||
|
last_speech_check_at: float = 0.0 # Drossel fuer den Silero-Speech-Endpoint
|
||||||
noise_floor: float = 0.0
|
noise_floor: float = 0.0
|
||||||
closed: bool = False
|
closed: bool = False
|
||||||
endpoint_sent: bool = False
|
endpoint_sent: bool = False
|
||||||
@@ -506,6 +512,26 @@ class SessionManager:
|
|||||||
"audioRequestId": sess.audio_request_id,
|
"audioRequestId": sess.audio_request_id,
|
||||||
"text": "",
|
"text": "",
|
||||||
})
|
})
|
||||||
|
# Speech-Endpoint gegen laute Umgebungsmusik: es ist gerade laut (rms
|
||||||
|
# ueber Schwelle) — aber ist es Sprache oder Musik? Der RMS-Endpoint
|
||||||
|
# unten wuerde bei Musik NIE feuern (last_voice_at bleibt frisch).
|
||||||
|
# Deshalb gedrosselt mit Silero das letzte endpoint_ms-Fenster pruefen:
|
||||||
|
# KEINE Sprache drin (nur Musik) → Turn ist zu Ende. Real gesprochene
|
||||||
|
# Turns haben Sprache im Fenster → laufen weiter.
|
||||||
|
if (self._buffer_ms(sess) >= sess.endpoint_ms
|
||||||
|
and (now - sess.last_speech_check_at) * 1000.0 >= STREAM_SPEECH_ENDPOINT_CHECK_MS):
|
||||||
|
sess.last_speech_check_at = now
|
||||||
|
try:
|
||||||
|
tail_bytes = int(sess.endpoint_ms / 1000.0 * sess.sample_rate) * 2
|
||||||
|
tail = pcm_s16le_to_float32(bytes(sess.pcm_buffer[-tail_bytes:]))
|
||||||
|
segs = _speech_segments(tail)
|
||||||
|
if segs is not None and len(segs) == 0:
|
||||||
|
logger.info("Stream %s: Speech-Endpoint (keine Sprache im letzten %dms — Musik/Stille) → finalize",
|
||||||
|
sess.request_id[:8], sess.endpoint_ms)
|
||||||
|
await self._finalize(sess, "endpoint")
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Speech-Endpoint-Check fehlgeschlagen — ignoriert")
|
||||||
else:
|
else:
|
||||||
self._update_noise_floor(sess, rms)
|
self._update_noise_floor(sess, rms)
|
||||||
# No-Speech-Timeout: wurde die GANZE Zeit KEINE Stimme erkannt
|
# No-Speech-Timeout: wurde die GANZE Zeit KEINE Stimme erkannt
|
||||||
|
|||||||
Reference in New Issue
Block a user