feat(steering): mesaje mid-tur + /stop pe turul în zbor
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
This commit is contained in:
10
CLAUDE.md
10
CLAUDE.md
@@ -90,6 +90,13 @@ source .venv/bin/activate && pip install -r requirements.txt
|
||||
|
||||
**Sesiuni** (`src/claude_session.py`): o sesiune persistentă per canal, `claude --resume <session_id>`. Mesajele externe împachetate în markeri `[EXTERNAL CONTENT]`.
|
||||
|
||||
**Steering — turnuri persistente** (`src/claude_runner.py`): pe lângă calea one-shot de mai sus (`_run_claude` — un `claude -p` per tur, procesul iese la final), canalele de chat interactive (Discord/Telegram/WhatsApp) pot ține un proces `claude` **viu** per canal, cu stdin deschis, ca un al doilea mesaj trimis cât primul încă rulează să intre în ACELAȘI tur (`ClaudeProcess.steer()`) în loc să aștepte după el.
|
||||
- **Config** (`config.json → steering`): `{"enabled": false, "idle_minutes": 20, "max_live": 2}`. **Off implicit** — rollback e o linie (`enabled: false` + restart). Kill switch fără să atingi JSON versionat: variabila de mediu `ECHO_STEERING=off`.
|
||||
- `heartbeat.py`, `planning_session.py` și `scheduler.py` rămân **deliberat one-shot** — folosesc `_run_claude`/`_run_claude_extra` direct, nu importă router-ul: n-are cine corecta un job cron sau o conversație de planning la mijlocul turului, deci un proces viu acolo ar adăuga doar RAM (292-541 MB per proces) pentru o capabilitate nefolosită.
|
||||
- **`/stop`** oprește doar turul curent în zbor (`ClaudeProcess.stop()` / `stop_turn()` în `claude_session.py`), nu sesiunea — `sessions/active.json` rămâne valid, canalul răspunde normal la mesajul următor.
|
||||
- **Diagnostic:** `eco status` arată `steering: on/off · N procese vii` (flag citit din config la fiecare apel; numărătoarea e prin `pgrep -f "--input-format stream-json"`, nu prin registry-ul din proces — `eco` rulează separat de serviciu). `eco doctor` verifică suportul binarului pentru `--input-format stream-json` doar cât timp `steering.enabled` e pornit.
|
||||
- **Rețetă de reproducere manuală** (T15): cere-i lui Echo ceva cu un `sleep` de 30s+ în Bash pe canalul de test (ex. „rulează `sleep 40 && echo gata`, apoi zi-mi vremea"), apoi trimite al doilea mesaj pe același canal cât primul încă rulează — urmărește linia de log „steered N chars". **Contează:** primul spike de testare n-a dovedit nimic, pentru că Claude a mutat `sleep`-ul în `run_in_background`, iar turul s-a terminat în 7.8s înainte ca steering-ul să apuce să conteze — dacă turul se termină prea repede, cere explicit ca task-ul să blocheze în prim-plan, nu în fundal.
|
||||
|
||||
**State:** `sessions/active.json` — channel ID → `{session_id, model, message_count, ...}`
|
||||
|
||||
**Credențiale** (`src/credential_store.py`): keyring de sistem, serviciu `"echo-core"`. Niciodată secrete ca argumente CLI.
|
||||
@@ -255,6 +262,9 @@ Fișierele Ralph (planning_session, planning_orchestrator, ralph.sh, ralph_dag,
|
||||
| `src/main.py` | Entry point — adaptoare + scheduler + heartbeat |
|
||||
| `src/router.py` | Comenzi vs mesaje Claude |
|
||||
| `src/claude_session.py` | Wrapper Claude CLI cu `--resume` |
|
||||
| `src/claude_runner.py` | Procese Claude persistente per canal ("steering") — vezi § Arhitectură |
|
||||
| `src/stream_json.py` | Parser stream-json partajat între `claude_session.py` și `claude_runner.py` |
|
||||
| `src/sentinels.py` | Markeri de protocol partajați (ex. `__AUDIO__:`, `__STEERED__`) pe cele 4 căi (Discord/Telegram/WhatsApp/voice) |
|
||||
| `src/local_fallback_tools.py` | Registry allowlist de unelte doar-citire pentru modelul local (vezi § Fallback local) |
|
||||
| `src/fallback_history.py` | Istoric conversație per canal pentru fallback (6 schimburi, TTL 30 min) |
|
||||
| `src/net_status.py` | Status read-only mașini Proxmox/LXC prin SSH paralel |
|
||||
|
||||
Reference in New Issue
Block a user