43697f8340
Feedback: the push broadcast her raw internal thought ("Eelis Parssinen's
victory is a reminder...") — read like a journal entry, not her texting him.
Now the flow matches the intent: she thinks/journals, then *decides* "I should
tell Brian about this." think() asks for an optional `reach_out` — a real text
message addressed TO him in her own voice, written only when she chooses to. The
ping sends that message (title "Lyra", like a text from her), never the internal
thought. No reach_out = nothing sent (most thoughts stay hers).
- Pinging decoupled from the salience score: her decision (a reach_out) drives it,
not a threshold. PING_SALIENCE is now an optional floor (default 0.0).
- Defensive: reject the placeholder echo ("reach_out"), too-short junk, or the
thought pasted back as the message.
- notify.push: title now optional (omitted -> cleaner text-style notification).
Verified live: 3 passes kept private; a decided reach-out lands as a personal
text. Suite 67 green, ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
47 lines
1.8 KiB
Python
47 lines
1.8 KiB
Python
"""Outbound push so Lyra can reach Brian when he's not in the app (ntfy).
|
|
|
|
This is the literal version of what she asked for — thinking "unprompted, without
|
|
you" only matters if she can also *reach* you. When a thought tugs hard enough,
|
|
the thought loop calls `push()` here and it lands on your phone with a tap-through
|
|
to the Thoughts feed. One-way: you reply in the app, which feeds the loop.
|
|
|
|
Transport only. Whether/when to ping (salience bar, cooldown, quiet hours) is the
|
|
thought loop's call — see `thoughts.maybe_ping`.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import httpx
|
|
|
|
from lyra import config, logbus
|
|
|
|
|
|
def push(title: str, message: str, click: str | None = None,
|
|
tags: str | None = None, priority: str | None = None) -> bool:
|
|
"""Publish a notification to the configured ntfy topic. Returns True on success.
|
|
Never raises — a down ntfy must not break the thought loop.
|
|
|
|
Uses ntfy's JSON publishing (POST to the base URL) rather than headers, so
|
|
UTF-8 titles/messages (em-dashes, smart quotes, her actual words) go through —
|
|
HTTP headers are latin-1 only and choke on them."""
|
|
cfg = config.load()
|
|
if not cfg.ntfy_url:
|
|
return False
|
|
payload: dict = {"topic": cfg.ntfy_topic, "message": message}
|
|
if title:
|
|
payload["title"] = title
|
|
if click:
|
|
payload["click"] = click
|
|
if tags:
|
|
payload["tags"] = [t.strip() for t in tags.split(",") if t.strip()]
|
|
if priority:
|
|
payload["priority"] = priority
|
|
try:
|
|
r = httpx.post(cfg.ntfy_url, json=payload, timeout=8.0)
|
|
ok = r.status_code < 400
|
|
if not ok:
|
|
logbus.log("error", "ntfy push failed", status=r.status_code)
|
|
return ok
|
|
except Exception as exc:
|
|
logbus.log("error", "ntfy push error", error=str(exc)[:160])
|
|
return False
|