Bridge-ul Baileys scrie erori tranzitorii, non-fatale (timeout pe query-uri
interne de init) la nivel pino 50 chiar și conectat — verificarea "ultima
linie de log conține 'error'" le raporta ca eșec. /status e ce consideră
bridge-ul însuși despre starea lui.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SiJGsZVSEGjRHZEJiXaxCC
Loggerul `echo-core.invoke` avea handler propriu pe echo-core.log ȘI
propagare la root handler pe același fișier — fiecare linie scrisă de
două ori. La fel `echo-core.security`, scris și în security.log și în
echo-core.log. Împreună, 1.2GB de log.
- handler propriu pentru invoke: eliminat (propagarea la root ajunge)
- security_logger.propagate = False
- FileHandler → RotatingFileHandler (50MB × 5 backups)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SiJGsZVSEGjRHZEJiXaxCC
Un al doilea mesaj trimis cât Claude încă lucra aștepta până se termina
turul 1 — corecția „stai, nu în master" ajungea după ce greșeala era gata.
Verificat în producție înainte de commit: mesajul 2 stătea 25s blocat în
lock, apoi pornea ca tur separat.
Acum canalele de chat pot ține un proces `claude` viu per canal, cu stdin
deschis, și al doilea mesaj intră în ACELAȘI tur.
- `src/claude_runner.py` — ClaudeProcess (steering, respawn cu --resume,
drenare stderr, respawn la comutarea OpenRouter) + RunnerRegistry
(max_live, reaper pe inactivitate, stop_all la shutdown)
- `src/stream_json.py` — parser stream-json partajat cu `_run_claude`;
pur, nu aruncă niciodată pe is_error (PlanningSession retrimite pe
error_max_turns și depinde de asta)
- `src/sentinels.py` — un singur loc pentru __AUDIO__/__STEERED__, în loc
de 4 verificări copiate; repară și bug-ul preexistent prin care
WhatsApp posta literal `__AUDIO__:/cale`
- dispecer în `send_message`: lock.acquire(blocking=False) — eșecul de a
lua lock-ul ESTE „rulează un tur", ceea ce elimină flagul inflight din
decizie și cursa TOCTOU odată cu el
- `/stop` oprește turul, nu sesiunea — active.json rămâne valid
- rate limit prin proces persistent vine ca result.is_error, nu ca exit
code; convertit înapoi în același RuntimeError, altfel fallback-ul
local nu s-ar mai declanșa niciodată, în tăcere
Steering-ul nu face niciodată cross-adapter (un mesaj text nu intră
într-un tur voice: împart același channel_id). Mesajele steered dintr-un
tur care pică sunt re-livrate, nu pierdute.
Testat live cu CLI-ul real: corecție la secunda 10 dintr-un tur de 24s,
un singur result, num_turns=2. Notă: mesajele steered sunt împachetate în
[EXTERNAL CONTENT], deci o corecție formulată ca override agresiv poate
fi refuzată ca prompt injection — pentru oprire folosește /stop.
Suită: 1199 passed, 12 failed (toate pre-existente pe HEAD curat).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SiJGsZVSEGjRHZEJiXaxCC
Accesul la /echo trece deja prin `tailscale serve` (tailnet only), deci
formularul de token era o a doua autentificare peste una existentă.
`DASHBOARD_AUTH` nesetat => `_check_dashboard_cookie` trece mereu: paginile
.html nu mai redirectează la /echo/login, POST-urile /api/* nu mai dau 401,
iar /echo/login sare direct la destinație. Codul de login rămâne intact —
`DASHBOARD_AUTH=on` în dashboard/.env îl reactivează neschimbat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188YmDTUAzVvZDVh8JL8xSa
Dashboard-ul face `git add -A` + commit + push fără intervenție umană
(dashboard/handlers/git.py:110). Regulile pe nume exact acopereau doar fișierele
observate, nu clasele lor: `bridge/whatsapp/auth/` era ignorat, dar
`auth.bak-*/` nu — așa a ajuns creds.json cu chei de sesiune WhatsApp pe Gitea.
Audit: din 21 de nume plauzibile testate (.env.local, credentials.json, token.json,
id_rsa, auth-old/, server.pem, cookies.txt, dump.sql, config.json.bak, nohup.out),
toate 21 treceau nestingherite. Acum toate sunt prinse.
- bridge/whatsapp/auth*/ acoperă orice variantă de director de stare Baileys
- .env* (cu excepție pentru .env.example), *.pem, *.key, id_rsa*, id_ed25519*
- *token*.json, *credential*.json, client_secret*.json, creds.json, cookies*.txt
- backup-uri/dump-uri: *.bak, *.backup, *.orig, *.dump, *.sql, *.tar.gz, *.zip,
*.bundle — cu !memory/kb/**/*.bak pentru notițele legitime din KB
- nohup.out, core.[0-9]*
Verificat în ambele direcții: `git ls-files | git check-ignore --stdin` nu
întoarce nimic (niciun fișier tracked nu devine ignorat), iar cele 21 de nume
ipotetice sunt acum toate ignorate.
Restul auditului e curat: zero secrete în fișierele tracked, config.json fără
credențiale (keyring folosit corect), credentials/, dashboard/.env,
memory/echo.sqlite și bridge/whatsapp/auth/ deja acoperite.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188YmDTUAzVvZDVh8JL8xSa
Cele două linkuri Facebook din 31 aug erau video-uri DIFERITE, nu unul singur —
de aceea retry-urile au produs duplicate în loc să acopere ambele.
- adaugă „How to Do ACTIVE RECALL Effectively" (Simple, actually, 9:14) și
„How to Study & Learn Using Active Recall" (Huberman × Cal Newport, 5:39),
transcrise corect în engleză, cu TL;DR completat
- șterge 807k-views-15k-reactions.md și 810k-views-15k-reactions.md: ambele erau
același link, transcrise cu `ro` forțat pe audio englezesc (text halucinat)
- adaugă nota YouTube din 29 aug rămasă necomisă
- reindexare via tools/update_notes_index.py
cron/: bookkeeping de last_run/next_run scris de scheduler.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188YmDTUAzVvZDVh8JL8xSa
Două linkuri Facebook trimise pe Discord n-au produs niciun răspuns: scriptul
rula în interiorul turnului de chat, iar yt-dlp + ffmpeg + whisper depășeau
constant DEFAULT_TIMEOUT (300s, src/claude_session.py:37). Procesul era omorât
la mijlocul transcrierii, de cinci ori pe patru încercări.
Efecte în cascadă, toate reparate aici:
- retry-uri oarbe: linkul 1 reprocesat de două ori (notițe duplicate), linkul 2
niciodată atins
- notițe cu TL;DR gol — arată „gata" în index dar nu sunt
- orfani de 33MB în /tmp (trap EXIT nu rulează la SIGKILL)
- userul nu primea nici rezultat, nici eroare acționabilă
Schimbări:
- --bg (setsid nohup) + --notify <channel_id>; turnul confirmă pornirea, nu
terminarea. send_text() nou în tools/discord_send_file.py, pentru că un job
detașat supraviețuiește turnului și nu mai poate raporta prin on_text.
- faster-whisper int8 în locul openai-whisper: 554s audio în 113s (~5x realtime)
vs. peste 300s. Era deja în requirements.txt; openai-whisper nici măcar nu era
declarat. cpu_threads pe core-uri fizice, nu logice.
- limba implicită „auto": rularea forțată cu `ro` pe audio englezesc producea
transcriere halucinată (română inventată amestecată cu CJK)
- .venv/bin/python3 explicit; whisper nu există în python-ul de sistem, scriptul
mergea doar când era chemat cu venv-ul activat
- metadata yt-dlp pe disc, parsată o dată: descrierile Facebook conțin caractere
de control care corup variabila de shell, iar `|| echo Unknown` masca eșecul
- titlul sare segmentele de statistici: Facebook emite „830K views · 15K
reactions | Titlul real | Pagina", iar split('|')[0] producea notițe numite
2026-08-31_807k-views-15k-reactions.md
- curăță workdir-urile orfane mai vechi de 60 min la pornire
.gitignore: bridge/whatsapp/auth.bak-*/ — backup-urile Baileys conțin creds.json
(chei de sesiune vii), la fel ca auth/ care era deja ignorat.
Verificat end-to-end pe ambele linkuri: titlu corect, limbă en auto-detectată,
notiță + reindex + link, trap curăță /tmp, zero orfani.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188YmDTUAzVvZDVh8JL8xSa
VM 109 a ajuns pe pvemini pe 27 aug fara ca root-ul de acolo sa aiba cheia
SSH autorizata pe Windows guest, ceea ce a stricat testul DR saptamanal
(eroare de auth SSH mascata drept "NFS/restore failed"). Migrat inapoi pe
pveelite (unde cheia originala functioneaza) si documentat gap-ul structural
din procedura de failover/migrare pentru viitor.
- dashboard/handlers/files.py: handle_files_download() servește mp3/wav/zip
din WORKSPACE_DIR cu Content-Disposition attachment, resolve dedicat
(nu _resolve_sandboxed, care nu ajunge niciodată la WORKSPACE_DIR)
- dashboard/api.py: rutează /api/files/download
- tools/epub_to_audio.py: tool nou de conversie EPUB → audio
- cron/jobs.json, memory/kb/index.json: stare auto-generată (job runs,
regenerare index KB)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Chunk-urile mari (tabele dense cu IP-uri/cod) treceau de split-ul pe
paragrafe dar depaseau limita reala de tokeni a all-minilm, nu doar
un prag fix de caractere — 18/20 chunk-uri din infrastructure.md
esuau silentios la fiecare heartbeat. embed_chunk() acum injumatateste
textul recursiv la eroare 500 si retrimite pana reuseste.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Detectia de rate limit functiona, dar `_local_fallback_reply` putea
intoarce None din patru locuri fara nicio linie de log — userul primea
`Claude CLI error (exit 1): You've hit your session limit` pe Discord in
loc de raspuns, si nu se putea afla ulterior din log care branch a picat.
- daca runda de unelte iese goala, turnul se reface fara unelte in loc
sa fie abandonat
- fiecare return None ramas e logat
- daca nici modelul local nu raspunde, mesajul e „Claude e la limita…"
plus ora de reset, nu eroarea bruta a CLI-ului
- cron: job-urile heartbeat* tac la limita (last_status: rate_limited);
celelalte trimit o singura linie scurta, ca sa se vada rularea sarita
- is_rate_limit_error / rate_limit_detail mutate in claude_session.py,
folosite si de scheduler (care nu poate importa router-ul)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzZAMzbyQbzFdsrVoXijBg
Fallback-ul local (Qwen3.5-2B, LXC 104) raspundea ca nu are unelte sau
internet. Acum conversează, are unelte read-only si istoric per canal.
Unelte (allowlist strict read-only, src/local_fallback_tools.py):
- cauta_web — DuckDuckGo Lite, fara API key
- masini — status Proxmox/LXC prin SSH paralel (src/net_status.py)
- vremea, sold, facturi, trezorerie, doctor, logs, email, kb,
cauta_memorie, citeste_pagina
Protocol: function calling OpenAI nativ. Protocolul text anterior
("TOOL: nume arg") pierdea argumentul in 100% din cazuri.
Uneltele cu output deja formatat se intorc verbatim — modelul de 2B
transforma un `doctor` cu 5 linii OK in "Sistemul este in 5/5 state.".
Doua treceri: prima decide uneltele (cu tools, temp 0), a doua reface
turnul fara ele — prezenta definitiilor strica raspunsurile simple
("cat fac 128/4?" -> "Nu stiu ce inseamna 128/4"). Few-shot doar pe
cereri creative: pe turnuri factuale strica aritmetica (17*23 -> 471).
Ocoliri deterministe pentru ce modelul greseste reproductibil: vremea si
preturile live (raspundea din memorie), plus sarcini "instructiune: text"
(payload-ul dicta unealta — "scrie mai politicos: da-mi raportul acum"
chema `sold`). llama.cpp nu e determinist nici la temperatura 0 si
ignora tool_choice, deci promptul singur nu ajunge.
Masurat: 36/36 pe set combinat (17 alegere unealta + 19 comprehensiune),
de la 14/19 pe comprehensiune inainte de regulile negative din prompt.
Comenzi: /f (inlocuieste /testfallback, ramas alias), /f reset, /masini.
Prefixul "Claude e la limita" apare doar pe calea de rate limit.
roa2web: /otp <cod> pentru 2FA din chat — mesajul de eroare trimitea la
verify_2fa(code, email), imposibil de rulat din Discord.
Corectat memory/kb/tools/infrastructure.md: LXC 101 si 110 sunt pe pve1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B88u3KkzvUNnzU56y7GBn2
Markerul opac [tts-lang:en] depindea de regula din VOICE_MODE.md, aflată
la ~30k caractere distanță în system prompt — modelul o rata pe ~1 din 5
turnuri. Răspunsul în română era apoi respins integral de pocket-tts
(English-only) și turnul rămânea fără audio.
- src/router.py: markerul poartă instrucțiunea inline (un singur token,
acoperit în continuare de strip-ul anti-jailbreak); 5/5 EN la repro
- src/voice/tts_stream.py: dacă modelul tot scapă română pe o voce
pockettts, blocul cade pe Supertonic (M2, ro) în loc de tăcere;
coada TTS transportă (clause, voice, lang) per clauză
- tools/tts.py: looks_romanian public (folosit de tts_stream)
- personality/VOICE_MODE.md: regula gated pe prefixul [tts-lang:en
- tests: 4 teste noi fallback + expectații marker actualizate
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Două cauze pentru audio mut pe pocket-tts după fix-ul [tts-lang:en]:
1. normalize_for_tts expanda numerele/orele în cuvinte românești chiar și
în text englezesc ("It's 9:46 PM" → "nouă și patruzeci și șase de
minute") → diacritice → pocket-tts respingea clauzele. push_text rezolvă
acum lang din engine-ul vocii curente (engine_for_voice) și sare
expansiunile RO când lang != 'ro'. [scris de Echo în sesiunea Discord]
2. sanitize_for_supertonic (cap 400 chars — limită ONNX per request) rula
pe textul întreg în normalize, înainte de segmentarea în clauze —
răspunsurile lungi erau retezate mid-word și trunchierea la 200 cuvinte
cu sufixul "Restul l-am scris în chat" nu se mai aplica. Separat
map_tts_punctuation (fără cap, folosit de normalize) de
sanitize_for_supertonic (cap, aplicat în _synthesize_supertonic).
Include vocea clonată "Marius 4" (tts_voices.json + default în config).
Fixează test_truncate_exactly_200_words_unchanged și
test_truncate_over_200_words_appends_suffix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Instrucțiunea „engleză pentru audio" nu ajungea la model: VOICE_MODE.md nu
avea nicio regulă de limbă, iar condiția din TOOLS.md („când generez pentru
o voce pockettts") era neevaluabilă — modelul nu știa ce voce e activă.
Rezultat: mirroring pe română → filtrul de diacritice bloca sinteza → tăcere.
- tools/tts.py: helper public engine_for_voice() (catalog → tts.default_engine)
- src/router.py: injectează [tts-lang:en] în prefixul voice când vocea activă
e pockettts (config citit fresh — vede swap-ul live /voice setvoice);
anti-jailbreak strip extins la [tts-lang:...]
- personality/VOICE_MODE.md: regulă de limbă gated pe marker
- tests/test_router.py: 5 teste noi pentru marker + strip
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kyutai pocket-tts nu suportă română (vezi eval anterior). Codul rula
oricum sinteza pe orice text primit; acum _synthesize_pockettts
respinge diacriticele RO înainte de request, fără fallback silențios
pe altă voce.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Finalizare manuală după ce ralph.sh a epuizat bugetul de 15 iterații
la 10/12 povești (self-improve TTS pocket-tts + Supertonic).
- tests/test_tts.py: rutare engine din catalog, fallback tehnic vs
eroare de conținut, regression retry lang=na
- tests/test_config.py: test dedicat persistență tts.default_engine
- personality/TOOLS.md: documentează /voice engine, setvoice,
addvoice, doctor și /audio
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- synthesize() rezolvă engine din tts_voices.json (fallback la tts.default_engine din config)
- _synthesize_pockettts nou: POST multipart la :7789/tts cu voice_wav/voice_url, ignoră lang
- fallback tehnic automat pe _synthesize_supertonic(M2, ro) via _PocketTTSUnavailable; erori de
conținut (voce lipsă, text gol) nu declanșează fallback; rezultatul include engine_used
- gates rulate: tests PASS (1043 passed, 22 eșecuri preexistente neschimbate), /workflow:simplify
(4 agenți) cu 1 fix aplicat, /review (backend, manual) PASS
- tools/pocket_tts_add_voice.py fusese deja creat și commitat cu US-003
(e3b5cdf); acest commit doar marchează US-004 complet în prd.json după
verificare
- verificat: script rulează cu .venv-pockettts/bin/python fără .venv
principal; _next_free_index întoarce 4 pentru Marius/Paula existenți
(1-3 ocupate), 1 pentru nume noi
- verificat integritatea celor 6 blob-uri models/voices/*.safetensors deja
exportate (12 tensori fiecare, safetensors.torch.load_file OK)
- gates: /review (backend) PASS, pytest 1043 passed (22 eșecuri
preexistente neschimbate)
- REQUIRED_SECRETS include acum hf_token alături de discord_token
- teste actualizate pentru noul set de secrete obligatorii (+ test_partial_required)
- gates rulate: tests PASS (22 eșecuri preexistente neschimbate), /review (backend) PASS
Pre-existing work committed before starting Ralph self-improvement run on
ralph/echo-improve branch, so that branch's diff stays isolated to the
pocket-tts integration.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Jobul intern rula pe zile greșite (vineri/sâmbătă/marți în loc de
joi/vineri/luni) din cauza convenției diferite de day-of-week dintre
APScheduler și crontab standard. Scriptul standalone via OS crontab
e mecanismul corect și funcțional — rămâne singura sursă.
chore: sync stare working-tree — cron, KB notes, anaf-monitor snapshots
Pre-existing uncommitted changes swept in with the STT work:
anaf-monitor snapshots/versions, cron job + newsletter state, 9 youtube KB
notes, tools/ocr_bon.py, and tools/tts.py.
Note: the tts.py change breaks 2 truncation tests in test_voice_normalize.py
(sanitize word-count) — flagged for a separate follow-up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gemma 4 cloud audio was infeasible (31b-cloud has no audio; E4B broken
upstream, no deploy host), so improve faster-whisper instead.
- Pin temperature=0.0 to disable the fallback ladder that re-decoded unclear
audio up to 6x (source of the 16-24s latency outliers); reject hallucinated
segments via avg_logprob/compression_ratio in the new pure _filter_segments.
- Adopt mikr/whisper-small-ro-cv11 (CT2 int8) via configurable voice.stt_model:
spike showed WER 24%->10%, numbers fixed at source, +0.33s p50 (in budget).
- Add tools/voice_stt_mine.py (log mining) + tools/voice_stt_spike.py (model
eval with diacritic scoring) + tests for the gate and miner.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- dashboard/handlers/youtube.py: după descărcare transcriere, cheamă
`claude -p` cu un prompt structurat care generează TL;DR + puncte cheie
+ citate + idei acționabile + secțiuni tematice în proze. Fallback la
transcriptul brut dacă Claude eșuează.
- nota Grantham: format complet — TL;DR, puncte cheie, citate,
idei acționabile, secțiuni tematice în proze curgătoare.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Expose a navigation layer to the agent and harden RAG, after analyzing the
OKF note and testing on the real KB.
- memory_search.search(): dedupe best-chunk-per-file (a relevant note can no
longer be buried by another file's chunks) + keyword fallback tagged
degraded:True when Ollama is unreachable (no more hard crash).
- update_notes_index.py: emit per-folder index.md + root router; prune empty
folders; fix latent subcategory->project bug.
- Exclude generated index.md from RAG rglob (reindex/incremental) + indexer
scans + heartbeat freshness check (prevents self-pollution / reindex thrash).
- CLAUDE.md: reframe memory as hybrid (navigation first, RAG for fuzzy recall).
- Delete stale orphan kb/youtube/index.json; correct the OKF source note.
- Tests: dedup, keyword fallback, index.md exclusion. Plan + review in docs/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- tools/youtube_subs.py: get_subtitles() returneaza acum (title, desc, transcript).
Functii noi is_description_about_video() si extract_relevant_description()
detecteaza daca descrierea contine capitole/timestamps (nu doar promotie autori)
si curata trailing-urile promotionale inainte sa includa descrierea in output.
- dashboard/handlers/youtube.py: aceleasi functii adaugate; nota KB generata
include acum un bloc "Descriere / Index" daca descrierea e relevanta pentru video.
- memory/kb/youtube: nota Jeremy Grantham (AI bubble, investitii, toxicitate)
cu descrierea ca index de capitole.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>