§
    øžyj  ã                  óx   — d Z ddlmZ ddlZddlmZmZmZ dZdZ	h d£Z
d#d$d„Ze	fd%d„Zd&d„Zd'd„Zd(d„Zd)d"„ZdS )*uH  Scale-to-zero idle detection + dormant-quiesce for the gateway (Phase 0).

This is the gateway-side BEHAVIOUR layer that consumes the relay scale-to-zero
PRIMITIVES (gateway-gateway Phase 5: the buffered-flip, the durable per-instance
buffer, the wakeUrl poke, the reconnect supervisor). It owns the *decision* to go
idle and drives the relay transport's ``go_dormant()`` (D12) â€” it does NOT itself
suspend the machine. On Fly, the now-traffic-idle machine is suspended by
``autostop:"suspend"`` and woken by autostart-on-wakeUrl (decisions.md Q3=Câ€²).

Design constraints (decisions.md):
  - Per-instance enable is gated SOLELY by the NAS "Labs" toggle, carried to the
    gateway as the ``HERMES_SCALE_TO_ZERO`` env stamp (D11/Q8=A). NOT a user
    config key; ``scale_to_zero.idle_timeout_minutes`` IS config.yaml (D2).
  - Arm only when messaging is relay-only or absent (D1/F6) AND a wakeUrl is
    registered (Â§3.4(1)) AND the flag is set.
  - Idle = no in-flight agent turn AND no inbound for N min AND no live
    background work (D2/D3/F7).
  - The quiesce uses ``go_dormant()`` (socket closed + supervisor preserved),
    NEVER the stop/restart drain or ``disconnect()`` (F12/F14). The process stays
    alive; Fly freezes+resumes it.
  - ``mark_resume_pending`` is deliberately NOT called here (D13 â€” suspend
    preserves RAM; revive only if we move to autostop:"stop" or see kills).

The pure helpers (``parse_idle_timeout_seconds``, ``scale_to_zero_enabled``,
``messaging_is_relay_only_or_absent``, ``is_idle``, ``should_arm``) take plain
inputs so they unit-test without a live gateway.
é    )ÚannotationsN)ÚAnyÚIterableÚOptionalÚHERMES_SCALE_TO_ZEROé   >   Ú1ÚonÚyesÚtrueÚenvironúOptional[dict]ÚreturnÚboolc                óÈ   — | �| nt           j        }t          |                     t          d¦  «        ¦  «                             ¦   «                              ¦   «         t          v S )zèWhether the per-instance Labs toggle is on (the HERMES_SCALE_TO_ZERO stamp).

    D11/Q8=A: this env flag is the SOLE per-instance enable signal reaching the
    gateway. Absent/blank/falsey -> disabled (fail-safe default off).
    NÚ )Úosr   ÚstrÚgetÚSCALE_TO_ZERO_ENVÚstripÚlowerÚ_TRUTHY)r   Úenvs     ú;/home/ragecks/.hermes/hermes-agent/gateway/scale_to_zero.pyÚscale_to_zero_enabledr   ,   sN   € ð Ð(ˆ'ˆ'­b¬j€CÝˆs�wŠwÕ(¨"Ñ-Ô-Ñ.Ô.×4Ò4Ñ6Ô6×<Ò<Ñ>Ô>Å'ÐIÐIó    Ú	cfg_valuer   Údefault_minutesÚintÚfloatc                ó¤   — 	 t          | ¦  «        }n&# t          t          f$ r t          |¦  «        }Y nw xY w|dk    rt          |¦  «        }|dz  S )u$  Coerce ``scale_to_zero.idle_timeout_minutes`` (config.yaml, D2) to seconds.

    Degrades to the default on any non-numeric / non-positive value (never raises,
    never returns <= 0 â€” a zero/negative timeout would make the gateway go dormant
    instantly, which is never the intent).
    r   g      N@)r!   Ú	TypeErrorÚ
ValueError)r   r   Úminutess      r   Úparse_idle_timeout_secondsr&   6   sg   € ð)Ý˜	Ñ"Ô"ˆˆøÝ•zÐ"ð )ð )ð )Ý˜Ñ(Ô(ˆˆˆð)øøøà�!‚|€|Ý˜Ñ(Ô(ˆØ�T‰>Ðs   ‚ ’ 5´5Ú	platformsúIterable[Any]c                ój   — d„ | D ¦   «         }|                      d¦  «         t          |¦  «        dk    S )u¿  True iff the only connected messaging platform is RELAY, or there is none
    (a Chronos-only / no-platform agent) â€” the F6/D1 structural precondition.

    A directly-connected platform (Discord/Telegram/Slack/...) holds a live
    socket and cannot scale to zero, so its presence disarms the feature. We
    compare by the platform's ``.value``/name to avoid importing the enum here
    (keeps this module import-light and unit-testable).
    c                ó,   — h | ]}t          |¦  «        ’ŒS © )Ú_platform_name)Ú.0Úps     r   ú	<setcomp>z4messaging_is_relay_only_or_absent.<locals>.<setcomp>Q   s    € Ð2Ð2Ð2 1�^˜AÑÔÐ2Ð2Ð2r   Úrelayr   )ÚdiscardÚlen)r'   Únamess     r   Ú!messaging_is_relay_only_or_absentr4   H   s9   € ð 3Ð2¨	Ð2Ñ2Ô2€EØ	‡M‚M�'ÑÔÐÝˆu‰:Œ:˜Š?Ðr   Úplatformr   c                óŠ   — t          | d| ¦  «        }t          |¦  «                             ¦   «                              ¦   «         S )NÚvalue)Úgetattrr   r   r   )r5   r7   s     r   r,   r,   V   s7   € Ý�H˜g xÑ0Ô0€EÝˆu‰:Œ:×ÒÑÔ×#Ò#Ñ%Ô%Ð%r   ÚenabledÚrelay_only_or_absentÚwake_urlúOptional[str]c                ó\   — t          | ¦  «        ot          |¦  «        ot          |¦  «        S )uz  Whether to start the idle watcher at all (D1/D11/Â§3.4(1)).

    ALL must hold: the Labs flag is on, messaging is relay-only/absent, and a
    wakeUrl is registered (a suspended instance with no reachable wake target is
    a black hole â€” Â§3.4(1)). Any unmet -> the watcher never starts (no idle
    timer, no dormancy), so a non-opted instance behaves exactly as today.
    )r   )r9   r:   r;   s      r   Ú
should_armr>   [   s)   € õ �‰=Œ=ÐJ�TÐ"6Ñ7Ô7ÐJ½DÀ¹N¼NÐJr   Úrunning_agent_countÚseconds_since_last_inboundÚidle_timeout_secondsÚhas_live_background_workc                ó&   — | dk    rdS |rdS ||k    S )uB  The idle predicate (D2/D3/F7). Pure â€” composes the three conjuncts.

    Idle iff: no in-flight agent turn, no inbound within the timeout window, and
    no live background work (backgrounded delegate_task / kanban / bg terminal).
    Any active work keeps the gateway awake â€” suspending mid-flight would lose it.
    r   Fr+   )r?   r@   rA   rB   s       r   Úis_idlerD   k   s-   € ð ˜QÒÐØˆuØð ØˆuØ%Ð)=Ò=Ð=r   )N)r   r   r   r   )r   r   r   r    r   r!   )r'   r(   r   r   )r5   r   r   r   )r9   r   r:   r   r;   r<   r   r   )
r?   r    r@   r!   rA   r!   rB   r   r   r   )Ú__doc__Ú
__future__r   r   Útypingr   r   r   r   ÚDEFAULT_IDLE_TIMEOUT_MINUTESr   r   r&   r4   r,   r>   rD   r+   r   r   ú<module>rI      s   ððð ð8 #Ð "Ð "Ð "Ð "Ð "à 	€	€	€	Ø *Ð *Ð *Ð *Ð *Ð *Ð *Ð *Ð *Ð *ð +Ð ð  !Ð à
$Ð
$Ð
$€ðJð Jð Jð Jð Jð ,Hðð ð ð ð ð$ð ð ð ð&ð &ð &ð &ð
Kð Kð Kð Kð >ð >ð >ð >ð >ð >r   