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
41 lines
1.3 KiB
Python
41 lines
1.3 KiB
Python
"""Single place to recognise router sentinels.
|
|
|
|
``route_message()`` normally returns text to post back to the user. Two
|
|
special values are protocol instead of a reply, and every adapter must
|
|
catch them before sending anything:
|
|
|
|
- ``__AUDIO__:<path>`` — a TTS result; the payload is a WAV path to attach.
|
|
- ``__STEERED__`` — the message was injected into an in-flight turn; the
|
|
adapter should react (not reply) and post nothing.
|
|
|
|
Zero dependencies on adapters or router — importable standalone.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
AUDIO_PREFIX = "__AUDIO__:"
|
|
STEERED = "__STEERED__"
|
|
|
|
|
|
def audio_path(response: str | None) -> str | None:
|
|
"""Return the WAV path if `response` is an audio sentinel, else None."""
|
|
if response and response.startswith(AUDIO_PREFIX):
|
|
return response[len(AUDIO_PREFIX):]
|
|
return None
|
|
|
|
|
|
def is_steered(response: str | None) -> bool:
|
|
"""True if `response` is the steering-acknowledgement sentinel."""
|
|
return response == STEERED
|
|
|
|
|
|
if __name__ == "__main__":
|
|
assert audio_path("__AUDIO__:/tmp/x.wav") == "/tmp/x.wav"
|
|
assert audio_path("hello") is None
|
|
assert audio_path("") is None
|
|
assert audio_path(None) is None
|
|
assert is_steered("__STEERED__") is True
|
|
assert is_steered("__STEERED__ ") is False
|
|
assert is_steered("") is False
|
|
assert is_steered(None) is False
|
|
print("ok")
|