
    Jj                       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ZddlZddl	Z	ddl
Z
ddlmZ ddlmZ ddlmZmZmZ  ej        e          Zi ddd	d
ddddddddddddddddddddddd d!d"d#d$d%d&d'i d(d)d*d+d,d-d.d/d0d1d2d3d4d5d6d7d8d9d:d;d<d=d>d?d@dAdBdCdDdEdFdGZdHedI<    ej        dJ          Z G dK dLe          Z edMN           G dO dP                      ZdQZdRZddUZddWZ dd[Z!dd]Z"dd^Z#dd`Z$ddbZ%dddZ&ddeZ'ddfZ(ddgZ)ddhZ*ddiZ+djdkddpZ,ddqZ-ddrZ.dMdsdduZ/ddvZ0ddwZ1ddyZ2dzdsdd|Z3dzdsddZ4dS )u  
Lazy dependency installer for opt-in Hermes Agent backends.

Many Hermes features (Mistral TTS, ElevenLabs TTS, Honcho memory, Bedrock,
Slack, Matrix, etc.) require Python packages that not every user needs. The
historical approach was to bundle them all under ``pyproject.toml`` extras
(``hermes-agent[all]``) and install them eagerly at setup time. That has
two problems:

1. **Fragility.** When one extra's transitive dependency becomes
   unavailable on PyPI (quarantined for malware, yanked, broken upload),
   the *entire* ``[all]`` resolve fails and fresh installs silently fall
   back to a stripped tier — losing 10+ unrelated extras at once.

2. **Bloat.** A user who only ever talks to one provider pulls hundreds
   of packages they will never import.

The lazy-install pattern fixes both. Backends call :func:`ensure` at the
top of their first-import path. If the deps are missing, ``ensure`` checks
the ``security.allow_lazy_installs`` config flag (default true) and runs
a venv-scoped pip install. If the user has explicitly disabled lazy
installs, ``ensure`` raises :class:`FeatureUnavailable` with a clear
remediation hint pointing at ``hermes tools`` or the manual pip command.

Security model:

* **Venv-scoped by default.** Installs target ``sys.executable`` in the
  active venv. We never touch the system Python.
* **Durable-target mode (immutable images).** When the deployment seals the
  agent's own venv (the Docker image sets ``HERMES_DISABLE_LAZY_INSTALLS=1``
  and makes ``/opt/hermes`` read-only), setting
  ``HERMES_LAZY_INSTALL_TARGET`` redirects lazy installs to a writable
  directory on the durable data volume (e.g. ``/opt/data/lazy-packages``).
  That directory is **appended to the end of ``sys.path``** — never
  prepended, never exported via ``PYTHONPATH`` — so the agent's own
  site-packages wins every name collision. A package installed this way can
  only ADD new importable modules; it can never shadow, downgrade, or break
  a module the core already ships. The worst a bad/incompatible backend
  package can do is fail to import and report itself unavailable — the agent
  core stays healthy. This is the structural guarantee that a lazily
  installed package cannot brick Hermes, which is what made it safe to seal
  the venv in the first place. Compiled-wheel safety across image rebuilds
  is handled by an ABI/Python-version stamp on the target subdir (see
  :func:`_ensure_target_ready`).
* **PyPI by package name only.** Specs may be ``"package>=1.0,<2"`` etc.
  We do NOT support ``--index-url`` overrides, ``git+https://``, file:
  paths, or any other input that could be hijacked by a malicious config.
* **Allowlist.** Only specs that appear in :data:`LAZY_DEPS` can be
  installed via this path. A typo in feature name doesn't get the user
  install-anything semantics.
* **Opt-out.** Setting ``security.allow_lazy_installs: false`` in
  ``config.yaml`` disables runtime installs in BOTH modes. Users in
  restricted networks or strict security postures can pin themselves to
  whatever was installed at setup time.
* **Offline detection.** If the install fails (offline, mirror down,
  PyPI 404 / quarantine), we surface the failure as
  :class:`FeatureUnavailable` with the actual pip stderr — no silent
  retries, no caching of bad state.

Adding a new backend:

1. Add an entry to :data:`LAZY_DEPS` with the package specs.
2. At the top of the backend module's import path, call
   ``ensure("feature.name")`` inside a try/except that converts
   :class:`FeatureUnavailable` to a useful runtime error.
    )annotationsN)	dataclass)Path)AnyCallableOptionalzprovider.anthropic)zanthropic==0.87.0zprovider.bedrock)zboto3==1.42.89zprovider.vertex)zgoogle-auth==2.55.1zprovider.azure_identity)zazure-identity==1.25.3z
search.exa)zexa-py==2.10.2zsearch.firecrawl)zfirecrawl-py==4.17.0zsearch.parallel)zparallel-web==0.4.2ztts.mistral)zmistralai==2.4.8ztts.edge)zedge-tts==7.2.7ztts.elevenlabs)zelevenlabs==1.59.0zstt.mistralzstt.faster_whisper)zfaster-whisper==1.2.1zsounddevice==0.5.5znumpy==2.4.3z	image.fal)zfal-client==0.13.1zmemory.honcho)zhoncho-ai==2.0.1zmemory.hindsight)zhindsight-client==0.6.1zmemory.supermemory)zsupermemory==3.50.0zmemory.mem0)zmem0ai==2.0.10zplatform.telegram)z#python-telegram-bot[webhooks]==22.6zplatform.discord)zdiscord.py[voice]==2.7.1zbrotlicffi==1.2.0.1aiohttp==3.14.1zplatform.slack)zslack-bolt==1.27.0zslack-sdk==3.40.1r	   platform.matrix)zmautrix[encryption]==0.21.0zaiosqlite==0.22.1zasyncpg==0.31.0zaiohttp-socks==0.11.0r	   zplatform.dingtalk)zdingtalk-stream==0.24.3zalibabacloud-dingtalk==2.2.42qrcode==7.4.2zplatform.feishu)zlark-oapi==1.5.3r   zplatform.wecom_callback)zdefusedxml==0.7.1zplatform.teams)zmicrosoft-teams-apps==2.0.13.4r	   zterminal.modal)zmodal==1.3.4zterminal.daytona)zdaytona==0.155.0zskill.google_workspace)z!google-api-python-client==2.194.0zgoogle-auth-oauthlib==1.3.1zgoogle-auth-httplib2==0.3.1zskill.youtube)zyoutube-transcript-api==1.2.4ztool.acp)zagent-client-protocol==0.9.0ztool.dashboard)zfastapi==0.133.1zuvicorn[standard]==0.41.0starlette==1.0.1zpython-multipart==0.0.27ztool.vision)zPillow==12.2.0ztool.computer_use)zmcp==1.26.0r   zdict[str, tuple[str, ...]]	LAZY_DEPSz]^[A-Za-z0-9_][A-Za-z0-9_.\-]*(?:\[[A-Za-z0-9_,\-]+\])?(?:[<>=!~]=?[A-Za-z0-9_.\-+,*<>=!~]+)?$c                  ,     e Zd ZdZd
 fdZdd	Z xZS )FeatureUnavailablezA lazily-installable feature is missing and cannot be made available.

    Either the deps were never installed and the user has disabled lazy
    installs, or the install attempt failed.
    featurestrmissingtuple[str, ...]reasonc                    || _         || _        || _        t                                          |                                            d S N)r   r   r   super__init___format)selfr   r   r   	__class__s       5/home/rurouni/.hermes/hermes-agent/tools/lazy_deps.pyr   zFeatureUnavailable.__init__  s>    (((((    returnc           	     |    d                     d | j        D                       }d| j        d| j         d| d| d	S )N c              3  4   K   | ]}t          |          V  d S r   repr.0ss     r   	<genexpr>z-FeatureUnavailable._format.<locals>.<genexpr>  s(      ;;T!WW;;;;;;r   zFeature z unavailable: z%. To enable manually: uv pip install z  (or: pip install z).)joinr   r   r   )r   	spec_lists     r   r   zFeatureUnavailable._format  si    HH;;dl;;;;;	.t| . .T[ . .2;. . ). . .	
r   )r   r   r   r   r   r   r   r   )__name__
__module____qualname____doc__r   r   __classcell__)r   s   @r   r   r      s[         ) ) ) ) ) )
 
 
 
 
 
 
 
r   r   T)frozenc                  .    e Zd ZU ded<   ded<   ded<   dS )_InstallResultboolsuccessr   stdoutstderrN)r+   r,   r-   __annotations__ r   r   r2   r2     s+         MMMKKKKKKKKr   r2   HERMES_LAZY_INSTALL_TARGETz.python-abir   r   c                     t           j        j         dt           j        j         } t	          j        d          pd}|  d| S )a  A stable token identifying the running interpreter's ABI.

    Combines the X.Y version with the EXT_SUFFIX (which encodes the ABI
    tag and platform, e.g. ``cpython-313-x86_64-linux-gnu``). Two
    interpreters that can share compiled wheels produce the same token.
    .
EXT_SUFFIX :)sysversion_infomajorminor	sysconfigget_config_var)verexts     r   _python_abi_tagrG   1  sH     #
>
>c&6&<
>
>C

"<
0
0
6BC>>C>>r   Optional[Path]c                     t           j                            t          d                                          } | sdS t          |           S )zReturn the durable install-target dir, or None for venv-scoped mode.

    Returns a path only when :data:`_LAZY_TARGET_ENV` is set to a non-empty
    value. The directory is created on demand by :func:`_ensure_target_ready`.
    r=   N)osenvironget_LAZY_TARGET_ENVstripr   )raws    r   _lazy_install_targetrP   =  s?     *..)2
.
.
4
4
6
6C t99r   targetr   Optional[str]c                   t                      }| t          z  }	 |                                 rd}	 |                    d                                          }n# t
          t          f$ r d}Y nw xY w|r||k    rt                              d| ||           | 	                                D ]f}|
                                r+|                                st          j        |d           A	 |                                 W# t
          $ r Y cw xY w|                     dd           |                    |d           n# t
          $ r}d|  d	| cY d
}~S d
}~ww xY wd
S )a  Create the target dir and validate its ABI stamp.

    If the stamp is missing it is written. If it is present but records a
    different interpreter ABI than the one now running (e.g. the container
    image was rebuilt onto a newer Python), the directory's contents are
    wiped and the stamp rewritten, so stale compiled wheels can't be
    imported against an incompatible interpreter.

    Returns ``None`` on success, or an error string if the directory can't
    be created / written (e.g. read-only mount, permission error).
    r=   utf-8encodingzYLazy install target %s was built for ABI %r but running ABI is %r; wiping stale packages.T)ignore_errors)parentsexist_okzlazy install target z is not writable: N)rG   _TARGET_STAMP_NAMEexists	read_textrN   OSErrorFileNotFoundErrorloggerinfoiterdiris_dir
is_symlinkshutilrmtreeunlinkmkdir
write_text)rQ   wantstamphavechildes         r   _ensure_target_readyrn   I  s    D''ED==?? 	!D88>>@@./    !8D$  
 $^^-- ! !E||~~ !e.>.>.@.@ !e4@@@@@!!LLNNNN& ! ! ! D!TD1110000 D D DCfCCCCCCCCCCD4se   E (A E A0-E /A00A=E .DE 
DE D1E 
EEEENonec                f   t          |           }t          t          j                  |vrt	          j        |           fdt          j        D             r*fdt          j        D             z   t          j        dd<   	 ddl}|                                 dS # t          $ r Y dS w xY w)u  Append the durable target to ``sys.path`` so its packages import.

    Appended to the END (never prepended) so the agent's own venv
    site-packages takes precedence on every name collision. Idempotent.
    Uses :func:`site.addsitedir` so ``.pth`` files (namespace packages,
    editable installs) inside the target are honoured, then enforces the
    append ordering — ``addsitedir`` would otherwise insert near the front.
    c                    g | ]}|v|	S r8   r8   )r%   pbefores     r   
<listcomp>z/_activate_target_on_syspath.<locals>.<listcomp>  s    :::!6//1///r   c                    g | ]}|v|	S r8   r8   )r%   rr   new_entriess     r   rt   z/_activate_target_on_syspath.<locals>.<listcomp>  s#    CCCQa{.B.Bq.B.B.Br   Nr   )	r   listr?   pathsite
addsitedir	importlibinvalidate_caches	Exception)rQ   
target_strr{   rs   rv   s      @@r   _activate_target_on_syspathr   s  s     VJ#(^^F
### ;:::ch:::K RCCCC#(CCCkQ##%%%%%   s   B" "
B0/B0c                     t                      } | dS 	 |                                 rt          |            dS dS # t          $ r'}t                              d| |           Y d}~dS d}~ww xY w)aS  Public: wire the durable lazy-install target onto ``sys.path``.

    Safe no-op when :data:`_LAZY_TARGET_ENV` is unset or the directory does
    not yet exist. Called once early in process startup (before backends
    import) so packages installed into the durable store on a previous run
    are importable on this run. Never raises.
    Nz-Failed to activate durable lazy target %s: %s)rP   r[   r   r}   r_   debug)rQ   rm   s     r   activate_durable_lazy_targetr     s     "##F~Q==?? 	0'/////	0 	0 Q Q QDfaPPPPPPPPPQs   #; 
A,A''A,r3   c                 0   	 ddl m}   |             }n# t          $ r d}Y nw xY w|<|                    d          pi }t	          |                    dd                    sdS t
          j                            d          d	k    rt                      duS dS )
uF  Return whether lazy installs are permitted in this environment.

    Resolution order:

    1. ``security.allow_lazy_installs: false`` in config.yaml is an absolute
       opt-out — it disables installs in BOTH venv-scoped and durable-target
       modes. This is the user-facing kill switch.
    2. ``HERMES_DISABLE_LAZY_INSTALLS=1`` seals the *agent venv* (set by the
       immutable Docker image). It blocks venv-scoped installs — UNLESS a
       durable install target is configured, in which case installs are
       redirected there (a path that structurally cannot break the sealed
       venv) and are therefore allowed.

    Defaults to True. If config is unreadable we fail open (allow), because
    refusing to install would lock people out of their own backends; the
    decision to block is an explicit user opt-in.
    r   )load_configNsecurityallow_lazy_installsTFHERMES_DISABLE_LAZY_INSTALLS1)hermes_cli.configr   r}   rL   r3   rJ   rK   rP   )r   cfgsecs      r   _allow_lazy_installsr     s    &111111kmm   
ggj!!'RCGG148899 	5
 
z~~455<<#%%T114s    ""r   c                8    t           j        dk    r	| dk    r	 dS dS )a
  Return why a lazy feature cannot work on this host, or ``None``.

    This is a platform capability gate, not a security policy gate. It keeps
    known-impossible installs out of both first-use lazy installation and the
    ``hermes update`` lazy-refresh pass.
    win32r
   zunsupported on Windows: Matrix E2EE depends on python-olm, which has no Windows wheel and requires make + libolm to build from sdist. Run Hermes under WSL to use Matrix on Windows.N)r?   platformr   s    r   _unsupported_feature_reasonr     s3     |w7.?#?#?I	
 	

 4r   specc                      rt                     dk    rdS t           fddD                       rdS                      d          sd v sd v rdS t          t                                                   S )zCReject pip specs that contain URLs, paths, or shell metacharacters.   Fc              3      K   | ]}|v V  	d S r   r8   )r%   chr   s     r   r'   z _spec_is_safe.<locals>.<genexpr>  s'      
R
R"2:
R
R
R
R
R
Rr   )	;|&`$
	\)-/r;   z://@)lenany
startswithr3   
_SAFE_SPECmatch)r   s   `r   _spec_is_safer     s     3t99s??u

R
R
R
R Q
R
R
RRR u'' 5D==C4KKu
  &&'''r   c                ^    t          j        d|           }|r|                    d          n| S )u   Extract the bare package name from a pip spec.

    ``"slack-bolt>=1.18.0,<2"`` → ``"slack-bolt"``
    ``"mautrix[encryption]>=0.20"`` → ``"mautrix"``
    z^([A-Za-z0-9_][A-Za-z0-9_.\-]*)   )rer   groupr   ms     r   _pkg_name_from_specr     s/     	3T::A$1771:::$r   c                l    t          j        d|           }|sdS | |                                d         S )u   Extract just the version-specifier portion of a pip spec.

    ``"honcho-ai==2.0.1"`` → ``"==2.0.1"``
    ``"mautrix[encryption]>=0.20,<1"`` → ``">=0.20,<1"``
    ``"package"`` → ``""`` (no version constraint)
    z6^[A-Za-z0-9_][A-Za-z0-9_.\-]*(?:\[[A-Za-z0-9_,\-]+\])?r=   N)r   r   endr   s     r   _specifier_from_specr     s9     	JDQQA r>r   c                b   t          |           }	 ddlm}m} n# t          $ r Y dS w xY w	  ||          }n# |$ r Y dS t
          $ r Y dS w xY wt          |           }|sdS 	 ddlm}m	} ddl
m}m}	 n# t          $ r Y dS w xY w	  |	|           ||          v S # ||t
          f$ r Y dS w xY w)a^  Is ``spec`` already satisfied in the current env?

    Checks both presence AND version. If the package is installed at a
    version outside the spec's range, returns False so the caller will
    upgrade/downgrade to the pinned version. This is what makes
    ``hermes update`` propagate pin bumps in :data:`LAZY_DEPS` to already-
    installed backends instead of silently leaving stale versions in place.

    If ``packaging`` is unavailable for any reason (it's a transitive of
    pip so this should never happen), we fall back to a presence-only check
    so we err on the side of "don't churn".
    r   PackageNotFoundErrorversionFT)InvalidSpecifierSpecifierSet)InvalidVersionVersion)r   importlib.metadatar   r   ImportErrorr}   r   packaging.specifiersr   r   packaging.versionr   r   )
r   pkgr   r   	installed	spec_tailr   r   r   r   s
             r   _is_satisfiedr     se    d
#
#CDDDDDDDDD   uuGCLL		   uu   uu %T**I tGGGGGGGG=========   ttwy!!\\)%<%<<<ni8   ttsE    
((8 A 	AA$A5 5
BBB B.-B.c                    t          |           }	 ddlm}m} n# t          $ r Y dS w xY w	  ||           dS # |$ r Y dS t
          $ r Y dS w xY w)zCheap presence-only check (package name installed at any version).

    Used by :func:`active_features` to detect backends the user has
    previously activated, regardless of whether the version pin moved.
    r   r   FT)r   r   r   r   r   r}   )r   r   r   r   s       r   _is_presentr   '  s     d
#
#CDDDDDDDDD   uut   uu   uus!    
((9 A	AAc                    	 ddl m}  n# t          $ r Y dS w xY w	 ddl}g }t	                      } |             D ]l}|j        r|j        d         nd}|j        }|r|s$|                                }||v r=|                    |           |	                    | d|            m|sdS |
                    dd          \  }}	t          j        |d	d
          5 }
|
                    d                    t          |                    dz              ddd           n# 1 swxY w Y   t!          |	          S # t"          $ r&}t$                              d|           Y d}~dS d}~ww xY w)u  Write a pip constraints file pinning every package already importable
    in the core environment to its installed version.

    Passed as ``--constraint`` for durable-target installs so the resolver
    pins shared transitive deps (httpx, pydantic, aiohttp, …) to the exact
    versions the core venv already ships, instead of pulling newer copies
    into the durable store. Two payoffs:

    * The durable store stays minimal — only genuinely-new packages land
      there; shared deps resolve to "already satisfied" against core.
    * A backend that *requires* a version conflicting with core fails loudly
      at install time (resolver conflict) rather than silently installing a
      shadowed copy that can never win on sys.path anyway.

    Returns the path to a temp constraints file, or None if enumeration
    failed (in which case the caller installs without constraints — still
    safe, just less tidy).
    r   )distributionsNNamez==zhermes-core-constraints-z.txt)prefixsuffixwrT   rU   r   z)Could not build core constraints file: %s)r   r   r   tempfilesetmetadatar   loweraddappendmkstemprJ   fdopenwriter(   sortedr   r}   r_   r   )r   r   linesseendistnamerE   keyfdrx   frm   s               r   _core_constraints_filer   ;  s   &4444444   ttuu!MOO 		+ 		+D,0MC4=((tD,C s **,,Cd{{HHSMMMLLD))C))**** 	4##+Ef#UUDYr3111 	5QGGDIIfUmm,,t3444	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5Dzz   @!DDDtttttsN   	 
BD: *1D: 9D D:  D$$D: 'D$(D: :
E*E%%E*i,  )timeoutspecsr   r   intc          	        | st          ddd          S t                      }d}|0t          |          }|rt          dd|          S t                      }g }|dt	          |          g}g }|dt	          |          g}	 t          t          j                  j        j        }ddl	m
}  |d	          }	t	          |          |	d
<   t          j        d          }
|
r	 t          j        |
ddg||| dd||	t          j                  }|j        dk    rX|t#          |           t          d|j        pd|j        pd          |&	 |                                 S # t*          $ r Y S w xY wS t,                              d|j                   n># t          j        t2          f$ r%}t,                              d|           Y d}~nd}~ww xY wt          j        ddg}	 t          j        |dgz   dddt          j                  }|j        dk    rt3          d          n# t          j        t2          f$ r 	 t          j        t          j        ddddgddddt          j                   nf# t          j        t          j        f$ rH}t          ddd|           cY d}~cY |&	 |                                 S # t*          $ r Y S w xY wS d}~ww xY wY nw xY w	 t          j        |dg||| z   dd|t          j                  }|j        dk    r|t#          |           t          |j        dk    |j        pd|j        pd          |&	 |                                 S # t*          $ r Y S w xY wS # t          j        $ rF}t          ddd|           cY d}~|&	 |                                 S # t*          $ r Y S w xY wS d}~wt6          $ rF}t          ddd|           cY d}~|&	 |                                 S # t*          $ r Y S w xY wS d}~ww xY w# |&	 |                                 w # t*          $ r Y w w xY ww xY w)u  Install ``specs`` using the uv → pip → ensurepip ladder.

    Two modes:

    * **Venv-scoped (default).** Installs into the active venv
      (``sys.executable``). Used on normal installs.
    * **Durable-target.** When :data:`_LAZY_TARGET_ENV` is set, installs into
      that directory via ``--target`` and constrains shared deps to the
      core venv's versions (see :func:`_core_constraints_file`). The target
      is append-only on ``sys.path`` so it can never shadow core. Used by
      the immutable Docker image to keep lazy installs off the sealed venv.

    Mirrors the strategy in ``hermes_cli.tools_config._pip_install`` but
    kept independent here so this module has no CLI dependency.
    Tr=   NFz--targetz--constraintr   )hermes_subprocess_env)inherit_credentialsVIRTUAL_ENVuvpipinstall)capture_outputtextr   envstdinzuv pip install failed: %szuv invocation failed: %sz-mz	--version   )r   r   r   r   zpip not in venv	ensurepipz	--upgradez--default-pipx   )r   r   r   checkr   z(pip not available and ensurepip failed: zpip install timed out: pip install failed: )r2   rP   rn   r   r   r   r?   
executableparenttools.environments.localr   rd   which
subprocessrunDEVNULL
returncoder   r5   r6   rf   r]   r_   r   TimeoutExpiredr^   CalledProcessErrorr}   )r   r   rQ   constraintserrtarget_argsconstraint_args	venv_rootr   uv_envuv_binrrm   pip_cmdprobes                  r   _venv_pip_installr  k  s      ,dB+++!##F"&K"6** 	2!%S111,..K!3v;;/!#O)3{+;+;<>((/6	BBBBBB&&5AAA #I} d## 	<<NUIVVoVPUV#'dG$,  
 <1$$)3F;;;)$BBOOP """$$$$    #O 8!(CCCC-/@A < < <7;;;;;;;;< >4/	VN;-'#$ (  E
 1$$'(9::: %)+<= 		V 		V 		VV^T;_U#'dCt$,    
 1:3LM V V V%eR&TQR&T&TV V V V V V V V V" """$$$$    #%V 		V	I9M{M_MuMM#$ (  A
 |q  V%7+F333!!,!"3QX^QX^QSTT """$$$$    # ( 	L 	L 	L!%-Jq-J-JKKKKKKK """$$$$    #  	I 	I 	I!%-GA-G-GHHHHHHH"""$$$$    #	I """$$$$    #sZ  AP A'F 	E
E+*E+/ F P G&GP GP AH  P  K73I+*K+KK	KKP #J88
KK	KKP KP A0M1 M  
M-,M-1P OPP N00
N=<N=PP!P"P )O>>
P
PPP QP0/Q0
P=:Q<P==Qc                R    | t           vrt          d|           t           |          S )z=Return the registered specs for a feature, or raise KeyError.zUnknown lazy feature: )r   KeyErrorr   s    r   feature_specsr    s/    i;;;<<<Wr   c                N    t          d t          |           D                       S )zCReturn the subset of specs for ``feature`` not currently installed.c              3  8   K   | ]}t          |          |V  d S r   )r   r$   s     r   r'   z"feature_missing.<locals>.<genexpr>  s/      KKq-:J:JKKKKKKKr   )tupler  r   s    r   feature_missingr    s'    KKM'22KKKKKKr   promptr  c               R   | t           vrt          | dd| d          t          |           }|sdS t          |           }|rt          | ||          |D ]%}t	          |          st          | |d|          &t                      st          | |d          d}dt          j        v r9	 d	d
lm	}  |            }|duot          |dd          }n# t          $ r d}Y nw xY w|r|st          j                                        rt          j                                        rd                    |          }	 t!          d| d| d                                                                          }	n# t&          t(          f$ r d}	Y nw xY w|	r|	dvrt          | |d          t*                              dd                    |          |            t/          |          }
|
j        sD|
j        p|
j        pd                                }|r
|dd         }t          | |d|pd           	 d	dlm} t9          |d          r|                                 n# t          $ r Y nw xY wt          |           }|rt          | |d          t*                              d|            dS )u  Make sure all packages for ``feature`` are importable.

    If they're missing, attempts to install them in the active venv. Raises
    :class:`FeatureUnavailable` if the user has disabled lazy installs or
    if the install attempt fails.

    ``prompt``: when True (default) and stdin is a TTY, asks the user to
    confirm before installing. Non-interactive callers (gateway, cron,
    batch) get prompt=False and skip the confirmation — config flag is
    the gate in that case.
    r8   zfeature z not in LAZY_DEPS allowlistNz refusing to install unsafe spec z;lazy installs disabled (security.allow_lazy_installs=false)Fz"prompt_toolkit.application.currentr   )get_app_or_none
is_runningz, z	
Feature z requires: z)
Install into the active venv now? [Y/n] n>   yyeszuser declined install at promptz!Lazy-installing %s for feature %rr    r=   i0r   zno error output_cache_clearzWinstall reported success but packages still not importable (may require Python restart)z$Lazy install complete for feature %r)r   r   r  r   r   r   r?   modules"prompt_toolkit.application.currentr  getattrr}   r   isattyr5   r(   inputrN   r   EOFErrorKeyboardInterruptr_   r`   r  r4   r6   r   r   hasattrr  )r   r  r   unsupportedr   
_pt_activer  _appr)   answerresultsnippet_mdstill_missings                 r   ensurer%    s    i RJGJJJ
 
 	
 g&&G -g66K @ ';???   T"" 	$;4;;  	  !! 
 WI
 
 	
 J+s{::	JJJJJJ"?$$DT)PgdL%.P.PJJ 	 	 	JJJ	  j SY%5%5%7%7 CJ<M<M<O<O IIg&&		<W < <9 < < <  eggeegg F +, 	 	 	FFF	 	fL00$"C   KK3SXXg5F5FPPPw''F> 

 =7FM7R>>@@ 	&effoG WA7#?.?AA
 
 	
((((((3'' 	    $G,,M 
 ]+
 
 	
 KK6@@@@@s6   ,%C C! C!::E5 5F
F/*I 
I'&I'c                8    | t           vrdS t          |            S )z8Return True if the feature's deps are already satisfied.F)r   r  r   s    r   is_availabler'  I  s#    iuw''''r   c                v    | t           vrdS t           |          }dd                    d |D                       z   S )zFReturn the ``pip install`` command a user could run manually, or None.Nzuv pip install r    c              3  4   K   | ]}t          |          V  d S r   r"   r$   s     r   r'   z*feature_install_command.<locals>.<genexpr>U  s(      '?'?AQ'?'?'?'?'?'?r   )r   r(   )r   r   s     r   feature_install_commandr*  P  sB    itgEsxx'?'?'?'?'?????r   	list[str]c                     g } t                                           D ]3\  }}t          d |D                       r|                     |           4| S )a  Return the list of features the user has ever lazy-installed.

    A feature counts as "active" if at least one of its declared packages
    is currently installed in the venv (presence check, ignoring version).
    Features the user has never enabled stay quiet.

    Used by ``hermes update`` to figure out which lazy backends need a
    refresh pass when pins move in :data:`LAZY_DEPS`.
    c              3  4   K   | ]}t          |          V  d S r   )r   r$   s     r   r'   z"active_features.<locals>.<genexpr>d  s(      --!{1~~------r   )r   itemsr   r   )activer   r   s      r   active_featuresr0  X  s]     F#//++ # #--u----- 	#MM'"""Mr   Fdict[str, str]c                   i }t                      D ]}t          |          }|sd||<   t          |          }|r	d| ||<   3	 t          ||            d||<   K# t          $ ra}dt          |          v s+dt          |          v s|j                            d          rd|j         ||<   nd|j         ||<   Y d	}~d	}~wt          $ r}d| ||<   Y d	}~d	}~ww xY w|S )
uj  Re-run ``ensure`` for every feature the user has previously activated.

    Returns a ``{feature: status}`` map where status is one of:
        ``"current"``  — pins already satisfied, no install run
        ``"refreshed"`` — pins were stale, reinstall succeeded
        ``"failed: <reason>"`` — install attempt failed; caller decides
                                  whether to surface it (we don't raise)
        ``"skipped: <reason>"`` — gated off (config flag, user decline)

    Intended for ``hermes update``. Never raises; lazy-install failures
    here must not block the rest of the update flow.
    currentz	skipped: r  	refreshedzlazy installs disableddeclinedzunsupported zfailed: N)	r0  r  r   r%  r   r   r   r   r}   )r  resultsr   r   r  rm   s         r   refresh_active_featuresr7  i  sV    !G"$$ . .!'** 	(GG1':: 	8;88GG	.76*****GG! 	9 	9 	9
 )CFF22Q''8&&~66 ( $:qx#9#9  #8ah#8#8  	. 	. 	.-!~~GG	.Ns%   A
C!&ACC!CC!importerCallable[[], dict[str, Any]]target_globalsdictc                   	 t          | |           n# t          t          f$ r Y dS w xY w	  |            }n# t          $ r Y dS w xY w|                    |           dS )a  Ensure a feature is installed, then rebind names into the caller's globals.

    Combines :func:`ensure` with a post-install import step that rebinds
    module-level names.  This eliminates the error-prone pattern of manually
    listing every global that needs updating after lazy-install.

    ``importer`` is a zero-arg callable that returns a dict of
    ``{name: value}`` for all symbols the caller needs rebound.  It is called
    only after :func:`ensure` succeeds (or if the packages are already
    installed).

    Returns True on success, False if deps couldn't be installed or imported.

    Example usage in a platform adapter::

        def check_slack_requirements() -> bool:
            if SLACK_AVAILABLE:
                return True
            def _import():
                from slack_bolt.async_app import AsyncApp
                from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler
                from slack_sdk.web.async_client import AsyncWebClient
                import aiohttp
                return {
                    "AsyncApp": AsyncApp,
                    "AsyncSocketModeHandler": AsyncSocketModeHandler,
                    "AsyncWebClient": AsyncWebClient,
                    "aiohttp": aiohttp,
                    "SLACK_AVAILABLE": True,
                }
            return ensure_and_bind("platform.slack", _import, globals(), prompt=False)
    r  FT)r%  r   r}   r   update)r   r8  r:  r  bindingss        r   ensure_and_bindr?    s    Nwv&&&&&	*   uu8::   uu (###4s    ))
8 
AAr*   )r   rH   )rQ   r   r   rR   )rQ   r   r   ro   )r   ro   )r   r3   )r   r   r   rR   )r   r   r   r3   )r   r   r   r   )r   r   r   r   r   r2   )r   r   r   r   )r   r   r  r3   r   ro   )r   r   r   r3   )r   r+  )r  r3   r   r1  )
r   r   r8  r9  r:  r;  r  r3   r   r3   )5r.   
__future__r   loggingrJ   r   rd   ry   r   r?   rC   dataclassesr   pathlibr   typingr   r   r   	getLoggerr+   r_   r   r7   compiler   RuntimeErrorr   r2   rM   rZ   rG   rP   rn   r   r   r   r   r   r   r   r   r   r   r  r  r  r%  r'  r*  r0  r7  r?  r8   r   r   <module>rH     s  A A AF # " " " " "  				 				       



     ! ! ! ! ! !       * * * * * * * * * *		8	$	$R) 0	R) +R) /R) :R)$ %%R)& 1'R)( /)R)> (?R)@ $AR)B -CR)H (IR)J  KR)X (YR)^ *_R)` 4aR)p 2qR)r &sR) R)x AyR)D  ER)V  WR)`  	aR)t  uR)~  R)L 5MR)V KWR)\ ']R)^ -_R)d  eR)n 7oR)v 1wR)z  {R)P &QR)\  ]R) R)	 R R R Rp RZ	 

 
 
 
 
 
 
 
, $       " 0  # 	 	 	 		 	 	 	' ' ' 'T   :Q Q Q Q$# # # #L    ( ( ( (% % % %   ) ) ) )X   (- - - -` AD b b b b b bT   L L L L
 ,0 eA eA eA eA eA eAP( ( ( (@ @ @ @   " /4 * * * * * *d 2 2 2 2 2 2 2 2r   