feat: memory_store + profiles_store + 10 new yaml entries (119 → 129)
Mirrors `long_term_memory` and `profiles` modules from rust fork.
memory_store.py (new, ~95 lines)
- JSON store at <here>/long_term_memory.json (atomic write).
- remember(key, value) / recall(key) / forget(key) / search(query, limit) /
all_facts() / build_llm_context(prompt, limit).
- Uses the same JSON shape as the rust side, so the file is
interoperable if you ever symlink the two installations to the same dir.
profiles_store.py (new, ~145 lines)
- Profiles at <here>/profiles/<name>.json. active_profile.txt persists.
- Seeds 5 defaults on first init: default ★, work 💼, game 🎮, sleep 🌙,
driving 🚗 — same as rust fork.
- active() / active_name() / set_active(name) / list_names() /
allows_command(cmd_id).
extensions.py (+6 handlers, +160 lines)
- do_memory_remember — derives key (first 6 words or split on '=')
- do_memory_recall — substring search or top-3 if no query
- do_memory_forget — exact then substring fallback
- do_memory_list — count + first 5 keys
- do_profile_set — target via action.target or voice ("работа" → work)
- do_profile_what — speaks current profile + description
main.py
- +6 dispatch entries for the new action types.
commands.yaml (+10 entries, 119 → 129)
- memory_remember, memory_recall, memory_forget, memory_list
- profile_work, profile_game, profile_sleep, profile_driving, profile_default
- profile_what
Tests: ast.parse passes for all .py files. yaml.safe_load gives 129 entries.
Python parity is now meaningful: memory + profiles are usable by voice
without any rust install. Still missing for full parity: scheduler (needs
background thread), macros (needs phrase-capture state), vision (needs
Groq vision model call), codebase_qa, github_pr.
This commit is contained in:
parent
f82427c7b6
commit
02c6f2f90d
5 changed files with 521 additions and 0 deletions
136
extensions.py
136
extensions.py
|
|
@ -20,6 +20,9 @@ import uuid as uuidlib
|
|||
import re
|
||||
import urllib.request
|
||||
|
||||
import memory_store
|
||||
import profiles_store
|
||||
|
||||
_speak_fn = print
|
||||
_set_clipboard_fn = None
|
||||
|
||||
|
|
@ -445,6 +448,139 @@ def do_self_ping(action, voice):
|
|||
_speak(random.choice(options))
|
||||
|
||||
|
||||
# ── memory ──────────────────────────────────────────────────────────────────
|
||||
|
||||
_MEMORY_REMEMBER_TRIGGERS = (
|
||||
'запомни обо мне что', 'запомни обо мне', 'запомни что',
|
||||
'помни что', 'запомни это', 'запомни про меня', 'запомни',
|
||||
)
|
||||
|
||||
|
||||
def do_memory_remember(action, voice):
|
||||
body = (voice or '').strip()
|
||||
low = body.lower()
|
||||
for trig in _MEMORY_REMEMBER_TRIGGERS:
|
||||
if low.startswith(trig):
|
||||
body = body[len(trig):].strip(' ,.:')
|
||||
break
|
||||
|
||||
if not body:
|
||||
_speak("Что именно запомнить?")
|
||||
return
|
||||
|
||||
if '=' in body:
|
||||
key, value = body.split('=', 1)
|
||||
key, value = key.strip(), value.strip()
|
||||
else:
|
||||
words = body.split()
|
||||
key = ' '.join(words[:6])
|
||||
value = body
|
||||
|
||||
if not key:
|
||||
_speak("Не понял ключ.")
|
||||
return
|
||||
|
||||
memory_store.remember(key, value)
|
||||
_speak("Запомнил.")
|
||||
|
||||
|
||||
def do_memory_recall(action, voice):
|
||||
body = (voice or '').lower()
|
||||
query = ''
|
||||
for trig in ('что ты помнишь о', 'что ты помнишь про', 'что ты знаешь обо мне',
|
||||
'что помнишь про', 'вспомни'):
|
||||
idx = body.find(trig)
|
||||
if idx >= 0:
|
||||
query = body[idx + len(trig):].strip(' ,.:')
|
||||
break
|
||||
|
||||
if not query:
|
||||
recs = memory_store.all_facts()
|
||||
if not recs:
|
||||
_speak("Ничего пока не помню.")
|
||||
return
|
||||
line = "Я помню: "
|
||||
for r in recs[:3]:
|
||||
line += f"{r['key']} — {r['value']}. "
|
||||
_speak(line)
|
||||
return
|
||||
|
||||
hits = memory_store.search(query, 3)
|
||||
if not hits:
|
||||
_speak(f"Ничего не нашёл про {query}.")
|
||||
return
|
||||
if len(hits) == 1:
|
||||
_speak(hits[0]['value'])
|
||||
else:
|
||||
_speak('. '.join(f"{h['key']}: {h['value']}" for h in hits))
|
||||
|
||||
|
||||
def do_memory_forget(action, voice):
|
||||
body = (voice or '').lower()
|
||||
for trig in ('забудь про', 'забудь что', 'забудь обо мне'):
|
||||
idx = body.find(trig)
|
||||
if idx >= 0:
|
||||
key = body[idx + len(trig):].strip(' ,.:')
|
||||
if memory_store.forget(key):
|
||||
_speak(f"Забыл про {key}.")
|
||||
return
|
||||
hits = memory_store.search(key, 1)
|
||||
if hits:
|
||||
memory_store.forget(hits[0]['key'])
|
||||
_speak(f"Забыл про {hits[0]['key']}.")
|
||||
return
|
||||
_speak(f"Я и не помнил про {key}.")
|
||||
return
|
||||
_speak("Что забыть?")
|
||||
|
||||
|
||||
def do_memory_list(action, voice):
|
||||
recs = memory_store.all_facts()
|
||||
if not recs:
|
||||
_speak("Я пока ничего о вас не запомнил.")
|
||||
return
|
||||
sample = min(len(recs), 5)
|
||||
line = f"Помню {len(recs)} фактов. Первые: "
|
||||
line += ", ".join(r['key'] for r in recs[:sample]) + "."
|
||||
_speak(line)
|
||||
|
||||
|
||||
# ── profile switch ──────────────────────────────────────────────────────────
|
||||
|
||||
def do_profile_set(action, voice):
|
||||
target = action.get('target') or _profile_target_from_voice(voice)
|
||||
if not target:
|
||||
_speak("Не понял какой режим.")
|
||||
return
|
||||
try:
|
||||
p = profiles_store.set_active(target)
|
||||
except ValueError:
|
||||
_speak(f"Профиль {target} не найден.")
|
||||
return
|
||||
greeting = p.get('greeting', '') or f"Режим {target}."
|
||||
_speak(greeting)
|
||||
|
||||
|
||||
def _profile_target_from_voice(voice):
|
||||
low = (voice or '').lower()
|
||||
if 'работ' in low: return 'work'
|
||||
if 'игр' in low: return 'game'
|
||||
if 'сон' in low or 'спать' in low or 'тиш' in low: return 'sleep'
|
||||
if 'руль' in low or 'вожд' in low or 'поехали' in low: return 'driving'
|
||||
if 'обычн' in low or 'умолчан' in low or 'сброс' in low or 'верн' in low: return 'default'
|
||||
return ''
|
||||
|
||||
|
||||
def do_profile_what(action, voice):
|
||||
p = profiles_store.active()
|
||||
name = p.get('name', 'default')
|
||||
desc = p.get('description', '')
|
||||
msg = f"Сейчас {name}."
|
||||
if desc:
|
||||
msg += " " + desc
|
||||
_speak(msg)
|
||||
|
||||
|
||||
# ── interesting fact (LLM-dependent) ───────────────────────────────────────
|
||||
|
||||
def do_interesting_fact(action, voice):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue