diff --git a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/consumer.py b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/consumer.py index 861d696..cb054b7 100644 --- a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/consumer.py +++ b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/consumer.py @@ -187,16 +187,34 @@ def ask_llm(chunks: list[str], question: str, istoric: list[dict] | None = None) return resp.json()["choices"][0]["message"]["content"] -def send_reply(to: str, text: str) -> None: - requests.post(f"{bridge_url()}/send", json={"to": to, "text": REPLY_PREFIX + text}, timeout=15) +def _trimite(url: str, corp: dict, catre: str, timeout: int) -> str: + """Trimite prin punte. Intoarce "" daca a plecat, altfel motivul esecului. + + `requests` NU ridica exceptie la 4xx/5xx, iar puntea raspunde 503 cand nu e + conectata la WhatsApp si 500 cand `sendMessage` cade. Fara verificarea + codului, escaladarea se inregistra ca „notificata" si Maria promitea „te + contacteaza cineva" pentru un mesaj care nu plecase nicaieri — exact + promisiunea pe care ESCALATED_RECORDED exista ca sa n-o facem. + """ + try: + resp = requests.post(url, json=corp, timeout=timeout) + except Exception as exc: # noqa: BLE001 + print(f"[consumer] trimitere esuata catre {catre}: {exc}", file=sys.stderr) + return str(exc) + if resp.status_code >= 400: + motiv = f"HTTP {resp.status_code}: {resp.text[:150]}" + print(f"[consumer] trimitere esuata catre {catre}: {motiv}", file=sys.stderr) + return motiv + return "" -def send_image(to: str, path: str, caption: str) -> None: - requests.post( - f"{bridge_url()}/send-image", - json={"to": to, "path": path, "caption": caption}, - timeout=60, - ) +def send_reply(to: str, text: str) -> str: + return _trimite(f"{bridge_url()}/send", {"to": to, "text": REPLY_PREFIX + text}, to, 15) + + +def send_image(to: str, path: str, caption: str) -> str: + return _trimite(f"{bridge_url()}/send-image", + {"to": to, "path": path, "caption": caption}, to, 60) def react_seen(to: str, message_id: str, from_me: bool) -> None: @@ -269,7 +287,9 @@ def prepare_query(msg: dict) -> tuple[str | None, str | None, str | None]: file=sys.stderr, ) cautare = ocr.retrieval_query(text, ocr_text) - print(f"[consumer] caut dupa: {cautare!r}", file=sys.stderr) + # „din captura", nu „caut dupa": pe un fir, interogarea finala e alta (ancora + # plus asta) si se logheaza separat — doua linii „caut dupa" induceau in eroare. + print(f"[consumer] din captura, retin: {cautare!r}", file=sys.stderr) return build_image_question(text, ocr_text), cautare, None @@ -323,15 +343,14 @@ def escalate(msg: dict, question: str, search_result: dict, fir: dict | None = N f"{(msg.get('ocr_text') or msg.get('text') or '').strip()[:1200]}" + (f"\n\n--- discutia de pana acum ---\n{discutie}" if discutie else "") ) - try: - if media.get("path") and os.path.exists(media["path"]): - send_image(jid, media["path"], rezumat[:1000]) - else: - requests.post(f"{bridge_url()}/send", json={"to": jid, "text": rezumat}, timeout=20) - record["notified"] = True - except Exception as exc: # noqa: BLE001 - record["notify_error"] = str(exc) - print(f"[consumer] escaladare: notificarea a esuat: {exc}", file=sys.stderr) + if media.get("path") and os.path.exists(media["path"]): + eroare = send_image(jid, media["path"], rezumat[:1000]) + else: + eroare = _trimite(f"{bridge_url()}/send", {"to": jid, "text": rezumat}, jid, 20) + record["notified"] = not eroare + if eroare: + record["notify_error"] = eroare + print(f"[consumer] escaladare: notificarea a esuat: {eroare}", file=sys.stderr) else: print("[consumer] escaladare: SUPPORT_JID nesetat — doar in jurnal", file=sys.stderr) @@ -362,15 +381,9 @@ def _notifica_suport(text: str, imagine: str | None = None) -> bool: jid = config.get("SUPPORT_JID") or "" if not jid: return False - try: - if imagine and os.path.exists(imagine): - send_image(jid, imagine, text[:1000]) - else: - requests.post(f"{bridge_url()}/send", json={"to": jid, "text": text}, timeout=20) - return True - except Exception as exc: # noqa: BLE001 - print(f"[consumer] mesaj catre suport netrimis: {exc}", file=sys.stderr) - return False + if imagine and os.path.exists(imagine): + return not send_image(jid, imagine, text[:1000]) + return not _trimite(f"{bridge_url()}/send", {"to": jid, "text": text}, jid, 20) def completeaza(ref: str, msg: dict, text: str) -> str | None: diff --git a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/tests/test_escaladare.py b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/tests/test_escaladare.py index e662ca3..8764a7a 100644 --- a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/tests/test_escaladare.py +++ b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/tests/test_escaladare.py @@ -124,3 +124,23 @@ def test_notificarea_esuata_nu_pierde_jurnalul(rezultat_fara_acoperire, monkeypa inregistrare = json.loads(next((config.STATE_DIR / "escalations").glob("*.json")).read_text()) assert inregistrare["notified"] is False assert "puntea nu raspunde" in inregistrare["notify_error"] + + +def test_puntea_respinge_mesajul_nu_promitem_ca_a_plecat(rezultat_fara_acoperire, monkeypatch): + """503 „not connected to WhatsApp" nu ridica exceptie in requests — dar nu e trimitere. + + Fara verificarea codului, escaladarea se scria „notificata" si omul primea + „te contacteaza cineva" pentru un mesaj care nu plecase nicaieri. + """ + monkeypatch.setitem(config._env, "SUPPORT_JID", "40799999999@s.whatsapp.net") + + class Respins: + status_code = 503 + text = '{"ok":false,"error":"not connected to WhatsApp"}' + + monkeypatch.setattr(consumer.requests, "post", lambda *a, **kw: Respins()) + consumer.escalate(_mesaj(), "intrebare", rezultat_fara_acoperire) + + inregistrare = json.loads(next((config.STATE_DIR / "escalations").glob("*.json")).read_text()) + assert inregistrare["notified"] is False + assert "503" in inregistrare["notify_error"] diff --git a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/whatsapp/index.js b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/whatsapp/index.js index 6986e64..eaec630 100644 --- a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/whatsapp/index.js +++ b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/whatsapp/index.js @@ -285,7 +285,12 @@ async function startConnection() { fromMe: msg.key.fromMe || false, }); - console.log(`[whatsapp] Message from ${msg.pushName || 'unknown'} in ${msg.key.remoteJid}: ${media ? '[imagine] ' : ''}${text.substring(0, 80)}`); + // Taiat, dar marcat: 80 de caractere fix aratau ca un mesaj intreg, iar + // logul e primul loc in care te uiti cand un raspuns iese prost. Pe + // 2026-09-01 asta a dus la concluzia gresita ca gardul de „mesaj prea vag" + // nu functioneaza, cand de fapt mesajul era mai lung decat parea in log. + const scurt = text.length > 200 ? `${text.substring(0, 200)}… (+${text.length - 200})` : text; + console.log(`[whatsapp] Message from ${msg.pushName || 'unknown'} in ${msg.key.remoteJid}: ${media ? '[imagine] ' : ''}${scurt}`); } }); } @@ -372,6 +377,7 @@ app.post('/send', async (req, res) => { try { const result = await sock.sendMessage(to, { text }); + console.log(`[whatsapp] -> ${to}: ${text.substring(0, 120)}${text.length > 120 ? '…' : ''}`); res.json({ ok: true, id: result.key.id }); } catch (err) { console.error('[whatsapp] Send failed:', err.message); @@ -398,6 +404,7 @@ app.post('/send-image', async (req, res) => { try { const buffer = fs.readFileSync(resolved); const result = await sock.sendMessage(to, { image: buffer, caption: caption || undefined }); + console.log(`[whatsapp] -> ${to}: [imagine ${buffer.length} octeti] ${(caption || '').substring(0, 100)}`); res.json({ ok: true, id: result.key.id }); } catch (err) { console.error('[whatsapp] Send image failed:', err.message);