Ë
    âß²j,  ã                  ón  — U d Z ddlmZ ddlZddlZddlZddlZddlmZm	Z	m
Z
mZ ddlmZ ddlmZ  ej"                  e«      Zd%ddœd&d	„Zd
„ Zdaded<   d'd„Zd(d)d„Zd„ Zd(ddœ	 	 	 d*d„Zd+d„Zd,d„Zd-d„Zd„ d„ fd„ efd„ d„ fd„ ej@                  fdœZ!ded<   d.d „Z"ddd!œ	 	 	 	 	 d/d"„Z#d0d#„Z$d1d$„Z%y)2zÉCross-adapter helpers shared by gateway/platforms/* and plugins/platforms/*.

Kept dependency-light (stdlib + ``agent.secret_scope``) so every adapter can
import it at module top level without cycles.
é    )ÚannotationsN)ÚAnyÚCallableÚIterableÚOptional)ÚUnscopedSecretError)Ú
get_secretF)Úexternal_fallbackc               ó¾   — 	 t        | d«      }|€%|r#t	        «       €t        «       j                  | «      }|�|S |S # t        $ r t        j                  | «      }Y ŒMw xY w)aô  Scope-aware credential read with the default-profile startup fallback.

    An installed profile secret scope is authoritative: a scoped miss returns
    ``default`` (never borrow another profile's value from ``os.environ``).
    The DEFAULT profile constructs and sends *unscoped* under multiplexing,
    where a bare ``get_secret`` raises ``UnscopedSecretError``; there
    ``os.environ`` is that profile's own value, so fall back to it.

    ``external_fallback`` adds one rung for startup gates that run before any
    scope exists: an unscoped miss consults a one-shot build of the profile's
    own secret scope, so externally managed credentials (Bitwarden etc., only
    ``BWS_ACCESS_TOKEN`` in ``.env``) are visible to ``check_requirements``.
    N)Ú_scoped_get_secretÚ_UnscopedSecretErrorÚosÚgetenvÚ_current_scopeÚ_unscoped_profile_secretsÚget)ÚnameÚdefaultr
   Úvals       ú>/home/ubuntu/.hermes/hermes-agent/gateway/platforms/_shared.pyÚget_scoped_secretr      se   € ðÜ   tÓ,ˆð €{Ñ(¬^Ó-=Ð-EÜ'Ó)×-Ñ-¨dÓ3ˆØ�/ˆ3Ð. wÐ.øô	  ò Ü�i‰i˜‹oŠðús   ‚; »AÁAc                 ó   — ddl m}   | «       S )Nr   ©Úcurrent_secret_scope)Úagent.secret_scoper   r   s    r   r   r   -   s   € Ý7ÙÓ!Ð!ó    úOptional[dict]Ú_UNSCOPED_PROFILE_SECRETSc                 óÆ   — t         €)	 ddlm}  ddlm} t         |  |«       «      «      a t         S t         S # t        $ r! t        j                  dd¬«       i a Y t         S w xY w)z\Process-cached profile secret mapping (external resolvers are slow); failures degrade to {}.r   )Úbuild_profile_secret_scope)Úget_hermes_homezyCould not build the profile secret scope; externally managed credentials will not be visible to the startup gate (#95216)T)Úexc_info)	r   r   r    Úhermes_constantsr!   ÚdictÚ	ExceptionÚloggerÚwarning)r    r!   s     r   r   r   5   sp   € ô !Ð(ð	+ÝEÝ8Ü(,Ñ-GÉÓHYÓ-ZÓ([Ð%ô %Ð$Ô$Ð$øô ò 	+Ü�N‰Nð7ØAEð ô Gð )+Ñ%Ü$Ð$ð	+ús   ˆ"6 ¶"A ÁA Ú c                óR  — | s|S t        j                  t        «      5  ddlm}m}  |«       }|�> |«       r7|j                  | «      }|€|nt        |«      j                  «       cddd«       S ddd«       t        j                  | «      xs |j                  «       S # 1 sw Y   Œ0xY w)u¬  Allow/deny gate env read with per-profile isolation, always stripped.

    With a profile secret scope installed AND multiplexing active, a scoped miss returns ``default``
    instead of falling through to ``os.environ``, which may hold ANOTHER profile's first-writer
    bridged value (the YAMLâ†’env bridges are first-writer-wins; allowlist leak, #72348).
    Single-profile deployments behave exactly like ``os.getenv``.
    r   ©r   Úis_multiplex_activeN)Ú
contextlibÚsuppressr%   r   r   r+   r   ÚstrÚstripr   r   )r   r   r   r+   Úscoper   s         r   Úplatform_gate_envr1   E   s•   € ñ ØˆÜ	×	Ñ	œYÓ	'ñ @ßPá$Ó&ˆØÐÑ!4Ô!6Ø—)‘)˜D“/ˆCØ!˜k‘7¬s°3«x¯~©~Ó/?÷@÷ @ñ @ô �I‰I�d‹OÒ&˜w×-Ñ-Ó/Ð/÷@ð @ús   žABÂB&c                óÄ   — t        | t        «      r>| j                  «       dd dk(  r(	 t        j                  | «      }t        |t        «      r|S | S # t
        $ r | cY S w xY w)aB  Decode a JSON-encoded allowlist written by ``hermes config set``.

    String-typed defaults keep list literals verbatim on write (``allowed_chats`` is
    declared as ``""``), so the config can hold ``'["-100","-200"]'`` as a string.
    Malformed JSON passes through unchanged and keeps the legacy comma-split path.
    Né   ú[)Ú
isinstancer.   ÚlstripÚjsonÚloadsÚ
ValueErrorÚlist)ÚrawÚloadeds     r   Údecode_json_list_literalr=   Y   s`   € ô �#”sÔ §
¡
£¨R¨aÐ 0°CÒ 7ð	Ü—Z‘Z “_ˆFô �fœdÔ#ØˆMØ€Jøô	 ò 	ØŠJð	ús   ¨A ÁAÁAT)Úblank_is_unsetc               óÒ   — |r)t        |d«      }|�t        |«      j                  «       r|S | xs i j                  |«      }|�"|r"t	        |t        «      r|j                  «       s|S |S )u  The ONE per-profile setting reader: explicit env ``env`` â†’ the profile's YAML
    ``config.extra[key]`` â†’ ``default``.

    The env rung is the owning profile's, read through ``get_scoped_secret``: a secondary
    multiplex profile sees its own ``.env`` and a miss falls to ITS YAML, never to the launch
    process's ``os.environ`` (which holds the default profile's bridged values); single-profile
    and default-profile installs read ``os.environ`` there, keeping the documented env-over-YAML
    contract (an explicit ``DISCORD_ALLOW_MENTION_EVERYONE=false`` beats ``everyone: true``,
    #108440; ``TELEGRAM_REACTIONS=true`` beats the stock ``reactions: false``, #109032). A blank
    env value is unset. An explicit ``False``/``0`` in YAML is a real value (``require_mention:
    false`` must not fall to ``default``). A blank YAML string is unset by default; readers whose
    YAML key means "clear it" (``allowed_channels: "" `` = no whitelist) pass
    ``blank_is_unset=False`` so only a missing/``None`` key falls through.
    N)r   r.   r/   r   r5   )ÚextraÚkeyÚenvr   r>   Ú	env_valueÚvalues          r   Úextra_or_secretrE   j   sd   € ñ  Ü% c¨4Ó0ˆ	ØÐ ¤S¨£^×%9Ñ%9Ô%;ØÐØŠ[�b×Ñ˜cÓ"€EØ€}™¬J°u¼cÔ,BÈ5Ï;É;Ì=ØˆØ€Lr   c                 óh   — 	 ddl m} m} t         |«       xr	  | «       du«      S # t        $ r Y yw xY w)ao  True when running inside a multiplexed secondary profile's scope.

    Secondary-profile adapters are constructed/connected inside
    ``_profile_runtime_scope`` (secret scope installed + multiplex active).
    The DEFAULT profile under multiplexing runs unscoped and keeps the legacy
    ``os.environ`` precedence, so YAML->env bridges must skip only when True.
    r   r*   NF)r   r   r+   Úboolr%   r*   s     r   Úprofile_scopedrH   „   s9   € ðßPÜÑ'Ó)ÒPÑ.BÓ.DÈDÐ.PÓQÐQøÜò Ùðús   ‚"% ¥	1°1c                 ó(   ‡— t        «       Šdˆfd„} | S )ur  ``set_env(name, value)`` for ``apply_yaml_config_fn`` hooks: writes ``os.environ[name]`` only
    when the var is unset (explicit env wins over YAML) and NEVER while a multiplexed secondary
    profile's scope is active â€” the gateway loads every secondary's config inside
    ``_profile_runtime_scope``, so a write there would pin that profile's policy process-wide and the
    default profile's adapters would read it as their own (first-writer-wins poisoning, #80099).
    Hooks seed the same values into the returned ``extra`` so each profile's adapter reads its own.
    Lists are comma-joined; ``None`` is skipped.
    c                óÂ   •— |�‰st        j                  | «      ry t        |t        «      rdj	                  d„ |D «       «      n
t        |«      t         j                  | <   y )Nú,c              3  ó2   K  — | ]  }t        |«      –— Œ y ­w©N)r.   )Ú.0Úvs     r   ú	<genexpr>z3yaml_env_setter.<locals>.set_env.<locals>.<genexpr>¥   s   è ø€ Ò#:¨q¤C¨§FÑ#:ùs   ‚)r   r   r5   r:   Újoinr.   Úenviron)r   rD   Úskips     €r   Úset_envz yaml_env_setter.<locals>.set_env¢   sG   ø€ Øˆ=™D¤B§I¡I¨d¤OØÜ>HÈÔPTÔ>U˜3Ÿ8™8Ñ#:°EÔ#:Ô:Ô[^Ð_dÓ[eŒ�
‰
�4Òr   )r   r.   rD   r   ÚreturnÚNone)rH   )rT   rS   s    @r   Úyaml_env_setterrW   —   s   ø€ ô Ó€Dõfð
 €Nr   c                ó0   — ddl m}  |t        | «      «      S )zÎStandalone-sender failure envelope with vendor exception text redacted (the same helper
    ``send_message`` uses), so a token or signed URL in an httpx/aiohttp error never reaches the
    model transcript.r   )Ú_error)Útools.send_message_sendersrY   r.   )ÚmessagerY   s     r   Ú
send_errorr\   ª   s   € õ 2Ù”#�g“,ÓÐr   c                ó
   — || v S rM   © ©ÚcfgrA   s     r   ú<lambda>ra   ¶   s
   € ˜s c˜z€ r   c                ó4   — t        | «      j                  «       S rM   )r.   Úlower©rO   s    r   ra   ra   ¶   s   € ´S¸³V·\±\³^€ r   c                ó(   — | j                  |«      dvS )N)Nr(   ©r   r_   s     r   ra   ra   ·   s   € ˜SŸW™W S›\°Ð;€ r   c                ó(   — | j                  |«      d uS rM   rf   r_   s     r   ra   ra   ¸   s   € ˜SŸW™W S›\°Ð5€ r   c                ó   — | S rM   r^   rd   s    r   ra   ra   ¸   s   € À€ r   c                ó
   — || v S rM   r^   r_   s     r   ra   ra   ¹   s
   € ˜c S˜j€ r   )rc   r.   Úcsvr7   zCdict[str, tuple[Callable[[dict, str], bool], Callable[[Any], Any]]]Ú_YAML_KINDSc                óœ   — t        «       }i }|D ]6  \  }}}t        |   \  }} || |«      sŒ| |   ||<    || || |   «      «       Œ8 |xs dS )a#  Table-driven ``apply_yaml_config_fn`` body: for each ``(yaml_key, ENV_VAR, kind)`` row seed the
    original YAML value into the returned ``extra`` and bridge it to env through ``yaml_env_setter``
    (env wins; skipped under a secondary profile's scope). ``None`` when nothing matched.
    N)rW   rk   )	r`   ÚspecrT   ÚseededrA   rB   ÚkindÚappliesÚencodes	            r   Úapply_yaml_bridgerr   ½   sj   € ô
 Ó€GØ€FØò +‰ˆˆS�$Ü% dÑ+‰ˆ�Ù�3˜ÕØ˜c™(ˆF�3‰KÙ�C™  C¡Ó)Õ*ð	+ð
 Š>�TÐr   )Úhome_envÚhome_defaultc               óv  — i }| D ]a  \  }}}t        t        |d«      xs d«      j                  «       }|sŒ1t        j                  t
        «      5  |r ||«      n|||<   ddd«       Œc |rBt        t        |d«      xs d«      j                  «       xs |}|r|t        |› d�d«      dœ|d<   |S # 1 sw Y   Œ³xY w)u2  Table-driven ``env_enablement_fn`` body: for each ``(ENV_VAR, extra_key, conv)`` row read the
    profile-scoped value, skip it when blank or when ``conv`` raises ``ValueError``, else seed
    ``conv(value)`` (``None`` conv keeps the stripped string).

    ``home_env`` seeds ``home_channel`` as ``{"chat_id", "name"}`` from ``<home_env>`` (else
    ``home_default``) with ``<home_env>_NAME`` naming it â€” the core hook lifts that dict into a
    ``HomeChannel``. The name falls back to the literal ``"Home"``, the same rule the built-in
    platforms use.
    r(   NÚ_NAMEÚHome)Úchat_idr   Úhome_channel)r.   r   r/   r,   r-   r9   )	rm   rs   rt   ÚseedrB   rA   Úconvr;   Úhomes	            r   Úseed_extra_from_envr}   Ð   sÑ   € ð €DØò 3‰ˆˆS�$ÜÔ# C¨Ó,Ò2°Ó3×9Ñ9Ó;ˆÙØÜ× Ñ ¤Ó,ñ 	3Ù%)™˜Sœ	¨sˆD�‰I÷	3ð 	3ð	3ñ ÜÔ$ X¨rÓ2Ò8°bÓ9×?Ñ?ÓAÒQÀ\ˆÙØ/3Ô=NÐRZÐQ[Ð[`ÐOaÐciÓ=jÑ#kˆD�Ñ Ø€K÷	3ð 	3ús   ÁB/Â/B8	c                 ó   ‡ — dˆ fd„}|S )a  ``is_connected`` for platforms whose only configuration is env: True when every ``names`` var is
    non-blank. Resolves ``hermes_cli.gateway.get_env_value`` at call time (scope-aware, .env-backed) so
    setup-status tests that patch it see the same value.c                ó:   •‡— dd l mŠ t        ˆfd„‰D «       «      S )Nr   c              3  ód   •K  — | ]'  }‰j                  |«      xs d j                  «       –— Œ) y­w)r(   N)Úget_env_valuer/   )rN   r   Úgateway_mods     €r   rP   z9env_is_connected.<locals>.is_connected.<locals>.<genexpr>ð   s*   øè ø€ ÒUÀt�K×-Ñ-¨dÓ3Ò9°r×@Ñ@×BÑUùs   ƒ-0)Úhermes_cli.gatewayÚgatewayÚall)Úconfigr‚   Únamess    @€r   Úis_connectedz&env_is_connected.<locals>.is_connectedî   s   ù€ Ý0ÜÓUÈuÔUÓUÐUr   )r†   r   rU   rG   r^   )r‡   rˆ   s   ` r   Úenv_is_connectedr‰   é   s   ø€ õ
Vð Ðr   c                óH   — 	 t        | «      S # t        t        f$ r |cY S w xY w)z/``int(value)`` or ``default`` when unparseable.)ÚintÚ	TypeErrorr9   )rD   r   s     r   Úcoerce_portr�   õ   s*   € ðÜ�5‹zÐøÜ”zÐ"ò ØŠðús   ‚
 �! !rM   )r   r.   r   r   r
   rG   rU   r   )rU   r$   )r(   )r   r.   r   r.   rU   r.   )r@   r   rA   r.   rB   r.   r   r   r>   rG   rU   r   )rU   rG   )rU   zCallable[[str, Any], None])r[   r   rU   r$   )r`   r$   rm   zIterable[tuple[str, str, str]]rU   zdict | None)rm   z6Iterable[tuple[str, str, Callable[[str], Any] | None]]rs   z
str | Nonert   r.   rU   r$   )r‡   r.   rU   zCallable[[Any], bool])rD   r   r   r‹   rU   r‹   )&Ú__doc__Ú
__future__r   r,   r7   Úloggingr   Útypingr   r   r   r   r   r   r   r	   r   Ú	getLoggerÚ__name__r&   r   r   r   Ú__annotations__r   r1   r=   rE   rH   rW   r\   r.   Údumpsrk   rr   r}   r‰   r�   r^   r   r   ú<module>r–      s   ðòõ #ã Û Û Û 	ß 4Ó 4õ KÝ ?à	ˆ×	Ñ	˜8Ó	$€ð/ÐSXõ /ò."ð
 -1Ð ˜>Ó 0ó%ô 0ò(ð"Ø.2ñØ'+ðØ7:óó4ó&ó& ñ *Ñ+CÐDÙ;¸SÐAÙ5±{ÐCÙ(¨$¯*©*Ð5ñ	T€ÐPó óð( 04ÈñØ",ðØCFðØPTóó2	ôr   