fix FCM push notifications
This commit is contained in:
1 parent
560d9c19b3
commit
d801a18db5
4 files changed
+177
-6
No files matched your search
@@ -1360,6 +1360,16 @@ class IrisAdapter(BasePlatformAdapter):
|
||||
# M5: per-chat push throttle (epoch seconds of the last successful
|
||||
# push). The coalesced frame still reaches the app via sync.
|
||||
self._last_push_at: dict[str, float] = {}
|
||||
# M5: turn-aware push. chat_ids whose agent turn is in flight, per
|
||||
# the typing indicator (hermes turns typing on at turn start and off
|
||||
# in the handler's finally at turn end). While a turn is active and
|
||||
# the device is offline, pushable message/media frames are held back
|
||||
# in _pending_push instead of pushing per intermediate status
|
||||
# message; the latest one is pushed when the turn ends, so an
|
||||
# offline user gets ONE push with the final answer. High-priority
|
||||
# notifications (approval/clarify/cron) always push immediately.
|
||||
self._typing_turns: set[str] = set()
|
||||
self._pending_push: dict[str, tuple[protocol.Frame, int]] = {}
|
||||
# Live todo lists (lane key -> items): the agent's planning state,
|
||||
# re-served as a snapshot when a device opens its event stream.
|
||||
# In-memory only — a gateway restart drops it (the agent re-emits on
|
||||
@@ -1948,8 +1958,27 @@ class IrisAdapter(BasePlatformAdapter):
|
||||
frame.type,
|
||||
cursor,
|
||||
)
|
||||
# M5: wake the offline device(s) via the push backend.
|
||||
await self._maybe_push(chat_id, frame, cursor)
|
||||
# M5: wake the offline device(s) via the push backend -- unless
|
||||
# this is a normal-priority message/media frame while the
|
||||
# agent's turn is still in flight: hold it back and push the
|
||||
# latest one when the turn ends (stop_typing), so an offline
|
||||
# user gets one push with the final answer instead of one per
|
||||
# intermediate status message. High-priority notifications
|
||||
# (approval/clarify/cron) still push immediately.
|
||||
if chat_id in self._typing_turns and frame.type in (
|
||||
protocol.TYPE_MESSAGE,
|
||||
protocol.TYPE_MESSAGE_STOP,
|
||||
protocol.TYPE_MEDIA_OFFER,
|
||||
):
|
||||
self._pending_push[chat_id] = (frame, cursor)
|
||||
logger.info(
|
||||
"iris: push deferred for %s (turn active; %s frame cursor=%s)",
|
||||
chat_id,
|
||||
frame.type,
|
||||
cursor,
|
||||
)
|
||||
else:
|
||||
await self._maybe_push(chat_id, frame, cursor)
|
||||
await self._maybe_notify_outbox_prune(chat_id)
|
||||
elif (
|
||||
frame.type == protocol.TYPE_NOTIFICATION
|
||||
@@ -2099,6 +2128,9 @@ class IrisAdapter(BasePlatformAdapter):
|
||||
tid = metadata.get("thread_id")
|
||||
if isinstance(tid, str) and tid:
|
||||
thread_id = tid
|
||||
# M5: typing on marks the chat's agent turn as in flight (hermes
|
||||
# turns typing on at turn start, before the first output).
|
||||
self._typing_turns.add(chat_id)
|
||||
frame = protocol.typing(chat_id, True, thread_id=thread_id)
|
||||
await self._http_server.fanout(frame, cursor=None)
|
||||
|
||||
@@ -2106,6 +2138,32 @@ class IrisAdapter(BasePlatformAdapter):
|
||||
"""Clear the typing indicator (``typing`` frame, on=false)."""
|
||||
frame = protocol.typing(chat_id, False)
|
||||
await self._http_server.fanout(frame, cursor=None)
|
||||
# M5: turn ended (hermes fires stop_typing in the handler's finally,
|
||||
# after the final send). Flush the held-back push -- the final
|
||||
# answer -- but only while the device is still offline; a live
|
||||
# device already got the frames via its event stream / sync.
|
||||
# Idempotent: hermes may call stop_typing more than once per turn.
|
||||
self._typing_turns.discard(chat_id)
|
||||
pending = self._pending_push.pop(chat_id, None)
|
||||
if pending is None:
|
||||
return
|
||||
if self._http_server.has_devices():
|
||||
logger.info("iris: deferred push dropped for %s (device back online)", chat_id)
|
||||
return
|
||||
held_frame, held_cursor = pending
|
||||
await self._maybe_push(chat_id, held_frame, held_cursor)
|
||||
|
||||
def on_device_online(self) -> None:
|
||||
"""A device opened its event stream (SSE/long-poll): it will sync
|
||||
the outbox, so drop any held-back pushes -- flushing them later
|
||||
would duplicate what the app already shows. Called from the HTTP
|
||||
server's handler thread; dict.clear() is atomic under the GIL."""
|
||||
if self._pending_push:
|
||||
logger.info(
|
||||
"iris: device online; dropping %d deferred push(es)",
|
||||
len(self._pending_push),
|
||||
)
|
||||
self._pending_push.clear()
|
||||
|
||||
# ── M4: outbound media (agent -> app) ─────────────────────────────────
|
||||
#
|
||||
|
||||
Reference in new issue
Block a user