feat(brain+ui+app): Triggers — passive Aufweck-Quellen fuer ARIA
ARIA hatte bisher nur ein "User fragt → Brain antwortet"-Modell. Neu:
Trigger laufen passiv im Hintergrund (kein LLM-Call) und wecken ARIA
nur dann auf wenn ein Event tatsaechlich passiert.
Drei Typen, zwei aktuell implementiert:
timer — einmalig zu festem ISO-Timestamp ("erinner mich in 10min")
watcher — Polling alle N Sek einer Condition, feuert bei True mit Throttle
(z.B. "disk_free_gb < 5", max 1x/h)
cron — Platzhalter fuer spaeter
aria-brain/triggers.py
CRUD auf /data/triggers/<name>.json + /data/triggers/logs/<name>.jsonl.
create_timer, create_watcher, mark_fired, list_logs, etc.
aria-brain/watcher.py
Built-in Condition-Variablen: disk_free_gb, disk_free_pct, uptime_sec,
hour_of_day, day_of_week, rvs_connected, memory_count.
Sicherer Condition-Parser via ast — Whitelist auf Vergleich + BoolOp +
Name + Const. Kein eval, kein exec, keine Builtins.
aria-brain/background.py
Async Loop laeuft alle 30s, sammelt einmalig Variables, geht durch
Trigger-Liste, _should_fire-Check (Timer: fires_at vergangen / Watcher:
check_interval + throttle respektiert + condition true). Fire ruft
agent.chat(prompt, source="trigger") — ARIA bekommt das wie eine
Push-Nachricht und antwortet via Bridge → RVS → App.
aria-brain/main.py
/triggers/list, /{name}, /{name}/logs, /timer, /watcher, PATCH, DELETE,
/triggers/conditions (Variablen + aktuelle Werte). Lifespan-Handler
startet den Background-Loop beim Container-Start, stoppt beim Shutdown.
aria-brain/agent.py
Meta-Tools fuer ARIA: trigger_timer, trigger_watcher, trigger_cancel,
trigger_list. ARIA legt Trigger via Tool-Call selbst an wenn Stefan das
wuenscht. Side-Channel-Event 'trigger_created' wird in chat-Response
mitgeschickt damit App + Diagnostic eine Bubble zeigen.
aria-brain/prompts.py
Neue System-Prompt-Section: Liste aktiver Triggers + verfuegbare
Condition-Variablen mit aktuellen Werten + Operatoren-Erklaerung.
ARIA weiss damit immer was es schon gibt und welche Vars sie nutzen kann.
bridge/aria_bridge.py + rvs/server.js
trigger_created als neuer RVS-Message-Type, Bridge forwarded das aus
data.events analog zu skill_created.
diagnostic/index.html
Neuer Top-Tab "Trigger". Liste mit Type-Badges (⏱ TIMER / 👁 WATCHER),
Status, Fire-Count, last_fired. Aktivieren/Deaktivieren + Löschen pro
Trigger. "+ Neu"-Modal mit Type-Dropdown, Timer-Minuten oder
Watcher-Condition + Vars-Anzeige + Throttle. Info-Modal-Eintrag mit
Erklaerung. Live-Bubble im Chat wenn ARIA selbst einen anlegt.
android/src/screens/ChatScreen.tsx
trigger_created RVS-Handler → eigene Bubble (gelber Border, "⏰ ARIA
hat einen Trigger angelegt", Type/Detail/Message/Zeit). ChatMessage
bekam triggerCreated-Feld. Lokal-only-Schutz beim Server-Sync analog
zu skill_created.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,14 @@ interface ChatMessage {
|
||||
active: boolean;
|
||||
setupError?: string;
|
||||
};
|
||||
/** Trigger-Created-Bubble: ARIA hat einen neuen Trigger angelegt */
|
||||
triggerCreated?: {
|
||||
name: string;
|
||||
type: 'timer' | 'watcher' | string;
|
||||
message: string;
|
||||
fires_at?: string;
|
||||
condition?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// --- Konstanten ---
|
||||
@@ -442,6 +450,7 @@ const ChatScreen: React.FC = () => {
|
||||
// gesetzt UND text leer/Placeholder)
|
||||
const localOnly = prev.filter(m =>
|
||||
m.skillCreated ||
|
||||
m.triggerCreated ||
|
||||
(m.audioRequestId && (!m.text || m.text === '🎙 Aufnahme...' || m.text === 'Aufnahme...'))
|
||||
);
|
||||
// Server-Stand + lokal-only (chronologisch sortiert)
|
||||
@@ -476,6 +485,26 @@ const ChatScreen: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// trigger_created: ARIA hat einen Trigger angelegt → eigene Bubble
|
||||
if (message.type === 'trigger_created') {
|
||||
const p = (message.payload || {}) as any;
|
||||
const triggerMsg: ChatMessage = {
|
||||
id: nextId(),
|
||||
sender: 'aria',
|
||||
text: '',
|
||||
timestamp: Date.now(),
|
||||
triggerCreated: {
|
||||
name: String(p.name || '(unbenannt)'),
|
||||
type: String(p.type || 'timer'),
|
||||
message: String(p.message || ''),
|
||||
fires_at: p.fires_at ? String(p.fires_at) : undefined,
|
||||
condition: p.condition ? String(p.condition) : undefined,
|
||||
},
|
||||
};
|
||||
setMessages(prev => capMessages([...prev, triggerMsg]));
|
||||
return;
|
||||
}
|
||||
|
||||
// file_deleted: Datei wurde geloescht (vom Diagnostic User) → Bubble updaten
|
||||
if (message.type === 'file_deleted') {
|
||||
const p = (message.payload?.path as string) || '';
|
||||
@@ -1207,6 +1236,28 @@ const ChatScreen: React.FC = () => {
|
||||
? { borderWidth: 2, borderColor: '#FFD60A' }
|
||||
: null;
|
||||
|
||||
// Spezial-Bubble: ARIA hat einen Trigger angelegt
|
||||
if (item.triggerCreated) {
|
||||
const t = item.triggerCreated;
|
||||
const detailLine = t.type === 'timer'
|
||||
? `feuert: ${t.fires_at || '?'}`
|
||||
: `wenn: ${t.condition || '?'}`;
|
||||
return (
|
||||
<View style={[styles.messageBubble, styles.ariaBubble, {borderLeftWidth: 3, borderLeftColor: '#FFD60A'}, searchHighlightStyle]}>
|
||||
<Text style={{color: '#FFD60A', fontWeight: 'bold', fontSize: 14}}>
|
||||
{'⏰ ARIA hat einen Trigger angelegt'}
|
||||
</Text>
|
||||
<Text style={{color: '#E0E0F0', marginTop: 4, fontSize: 14}}>
|
||||
<Text style={{fontWeight: 'bold'}}>{t.name}</Text>
|
||||
<Text style={{color: '#8888AA', fontSize: 12}}>{` (${t.type})`}</Text>
|
||||
</Text>
|
||||
<Text style={{color: '#8888AA', fontSize: 12, marginTop: 2, fontFamily: 'monospace'}}>{detailLine}</Text>
|
||||
<Text style={{color: '#888', fontSize: 12, marginTop: 2}}>{`"${t.message}"`}</Text>
|
||||
<Text style={{color: '#555570', fontSize: 10, marginTop: 6}}>ARIA-Trigger · {time}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Spezial-Bubble: ARIA hat einen Skill erstellt
|
||||
if (item.skillCreated) {
|
||||
const s = item.skillCreated;
|
||||
|
||||
Reference in New Issue
Block a user