
    Jj^                        U d 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mZ ddl	m
Z
 daeed<    e            Z ede          Zeeez           ed<   d	ee
z  dz  d
efdZded
dfdZd
edz  fdZd
e
fdZd
e
fdZd
e
fdZded
e
dz  fdZdJde
dz  d
e
fdZdJde
dz  d
e
fdZdJde
dz  d
e
fdZdeded
e
fdZdJde
dz  d
ee
         fdZded
ee         fdZ e ej!        "                    dd                     Z#da$ e
e%          &                                j'        d!z  d"z  d#z  Z(d	edz  d
efd$Z)dJde
dz  d
efd%Z*d
efd&Z+d
efd'Z,ded
edz  fd(Z-ded
edz  fd)Z.ded
edz  fd*Z/dJd+e0eef         dz  d
e0eef         fd,Z1d	edz  d
efd-Z2d	e
d
efd.Z3d
efd/Z4d	e
d
dfd0Z5d	edz  d
efd1Z6dJd+e0eef         dz  d
edz  fd2Z7d3edz  d4edz  d
efd5Z8dJd+e0eef         dz  d
ee         fd6Z9dJd+e0eef         dz  d
efd7Z:dJd+e0eef         dz  d
edz  fd8Z;d+e0eef         d
dfd9Z<d:Z=d
e0dz  fd;Z>d
efd<Z?da@edz  ed=<   d
efd>ZAdaBedz  ed?<   d
efd@ZCd
e
fdAZDd
e
fdBZEd
e
fdCZFdKdDed
dfdEZGdFZHdGZIdHZJeJ dIZKdS )Lu   Shared constants for Hermes Agent.

Import-safe module with no dependencies — can be imported from anywhere
without risk of circular imports.
    N)
ContextVarToken)PathF_profile_fallback_warned_HERMES_HOME_OVERRIDE)defaultpathreturnc                 f    | t           nt          |           }t                              |          S )zSet a context-local Hermes home override and return its reset token.

    This is for in-process, per-task scoping.  It deliberately does not mutate
    ``os.environ`` because that is shared by every thread in the process.
    )_UNSETstrr   set)r	   values     6/home/rurouni/.hermes/hermes-agent/hermes_constants.pyset_hermes_home_overrider      s+     %)L&&c$iiE $$U+++    tokenc                 :    t                               |            dS )z8Restore the previous context-local Hermes home override.N)r   reset)r   s    r   reset_hermes_home_overrider   !   s    &&&&&r   c                  l    t                                           } | t          u s| sdS t          |           S )z=Return the active context-local Hermes home override, if any.N)r   getr   r   )overrides    r   get_hermes_home_overrider   &   s4    $((**H6tx==r   c                     t           j        dk    rat          j                            dd                                          } | rt          |           nt          j                    dz  dz  }|dz  S t          j                    dz  S )z4Return the platform-native default Hermes home path.win32LOCALAPPDATA AppDataLocalhermesz.hermes)sysplatformosenvironr   stripr   home)local_appdatabases     r   !_get_platform_default_hermes_homer*   .   ss    
|w
~r::@@BB&3ZtM"""y9PSZ9Zh9;;""r   c                     t                      } | rt          |           S t          j                            dd                                          }|rt          |          S t          s	 t                      }|dz  }|                                r&|	                                                                nd}n# t          t          f$ r d}Y nw xY w|rf|dk    r`dad|d| d|d	}	 t          j                            |d
z              t          j                                         n# t           $ r Y nw xY wt                      S )u  Return the Hermes home directory (default: platform-native path).

    Reads HERMES_HOME env var, falls back to the platform-native default.
    This is the single source of truth — all other copies should import this.

    When ``HERMES_HOME`` is unset but an ``active_profile`` file indicates
    a non-default profile is active, logs a loud one-shot warning to
    ``errors.log`` so cross-profile data corruption is diagnosable instead
    of silent.  Behavior is unchanged otherwise — we still return
    the platform-native default — because raising here would brick 30+ module-level
    callers that import this at load time.  Subprocess spawners are
    expected to propagate ``HERMES_HOME`` explicitly (see the systemd
    template in ``hermes_cli/gateway.py`` and the kanban dispatcher in
    ``hermes_cli/kanban_db.py``).  See https://github.com/NousResearch/hermes-agent/issues/18594.
    HERMES_HOMEr   active_profiler   TzB[HERMES_HOME fallback] HERMES_HOME is unset but active profile is z. Falling back to u'   , which is the DEFAULT profile — not z. Any data this process writes will land in the wrong profile. The subprocess spawner should pass HERMES_HOME explicitly (see issue #18594).
)r   r   r$   r%   r   r&   r   r*   exists	read_textUnicodeDecodeErrorOSErrorr"   stderrwriteflush	Exception)r   valfallback_homeactive_pathactivemsgs         r   get_hermes_homer<   7   s     ())H H~~
*..
+
+
1
1
3
3C
 Cyy
 $ 	=??M'*::K8C8J8J8L8LT[**,,22444RTFF"G, 	 	 	FFF	 	f	))'+$'$' ':G' '28' ' ' 
  t,,,
  """"    -...s&   +AB; ;CC+A D, ,
D98D9c                  X   t                      } t          j                            dd          }|s| S t	          |          }	 |                                                    |                                            | S # t          $ r Y nw xY w|j        j	        dk    r|j        j        S |S )u  Return the root Hermes directory for profile-level operations.

    In standard deployments this is the platform-native Hermes home
    (``~/.hermes`` on POSIX, ``%LOCALAPPDATA%\hermes`` on native Windows).

    In Docker or custom deployments where ``HERMES_HOME`` points outside
    ``~/.hermes`` (e.g. ``/opt/data``), returns ``HERMES_HOME`` directly
    — that IS the root.

    In profile mode where ``HERMES_HOME`` is ``<root>/profiles/<name>``,
    returns ``<root>`` so that ``profile list`` can see all profiles.
    Works both for standard (``~/.hermes/profiles/coder``) and Docker
    (``/opt/data/profiles/coder``) layouts.

    Import-safe — no dependencies beyond stdlib.
    r,   r   profiles)
r*   r$   r%   r   r   resolverelative_to
ValueErrorparentname)native_homeenv_homeenv_paths      r   get_default_hermes_rootrG   q   s    " 455Kz~~mR00H H~~H&&{':':'<'<===    z))%% Os   :A> >
B
BrC   c                     g }dD ]=}t          j        |          }|r%|                    t          |          | z             >|D ]}|                                r|c S dS )zReturn an installed data-files directory if one exists.

    Used to discover bundled skills/optional-skills when Hermes is installed
    from a wheel that emitted them via setuptools data_files.
    )datapurelibplatlibN)	sysconfigget_pathappendr   r/   )rC   
candidatesschemeraw	candidates        r   _get_packaged_data_dirrS      s     J0 0 0 (( 	0d3ii$.///  	 		4r   r   c                     t          j        dd                                          }|rt          |          S t	          d          }||S | | S t                      dz  S )zReturn the optional-skills directory, honoring package-manager wrappers.

    Packaged installs may ship ``optional-skills`` outside the Python package
    tree and expose it via ``HERMES_OPTIONAL_SKILLS``.
    HERMES_OPTIONAL_SKILLSr   zoptional-skillsr$   getenvr&   r   rS   r<   r   r   packageds      r   get_optional_skills_dirrZ      sl     y1266<<>>H H~~%&788H000r   c                     t          j        dd                                          }|rt          |          S t	          d          }||S | | S t                      dz  S )ao  Return the optional-mcps directory, honoring package-manager wrappers.

    Mirrors :func:`get_optional_skills_dir` for the MCP catalog (Nous-approved
    Model Context Protocol servers shipped with the repo but disabled by
    default). Packaged installs may ship ``optional-mcps`` outside the Python
    package tree and expose it via ``HERMES_OPTIONAL_MCPS``.
    HERMES_OPTIONAL_MCPSr   zoptional-mcpsrV   rX   s      r   get_optional_mcps_dirr]      sj     y/44::<<H H~~%o66H..r   c                     t          j        dd                                          }|rt          |          S t	          d          }||S | | S t                      dz  S )av  Return the bundled skills directory for source and packaged installs.

    Resolution order:
        1. ``HERMES_BUNDLED_SKILLS`` env var (Nix wrapper / explicit override)
        2. Wheel-installed ``<sysconfig data>/skills`` (pip install path)
        3. Caller-supplied ``default`` (typically the source-checkout path)
        4. ``<HERMES_HOME>/skills`` last-resort
    HERMES_BUNDLED_SKILLSr   skillsrV   rX   s      r   get_bundled_skills_dirra      sj     y0"55;;==H H~~%h//Hx''r   new_subpathold_namec                 T    t                      }||z  }t          |          r|S || z  S )u  Resolve a Hermes subdirectory with backward compatibility.

    New installs get the consolidated layout (e.g. ``cache/images``).
    Existing installs that already have the old path (e.g. ``image_cache``)
    keep using it — no migration required.

    A bare empty ``<old_name>/`` directory does **not** count as "the
    legacy install is in use" — install scaffolds, manual ``mkdir`` work,
    and cleared-then-abandoned locations all create empty stubs that
    would otherwise silently shadow real data populated at
    ``<new_subpath>/``. See #27602 for the pairing-store regression where
    a dormant empty ``pairing/`` orphaned approved-user data in
    ``platforms/pairing/``.

    Args:
        new_subpath: Preferred path relative to HERMES_HOME (e.g. ``"cache/images"``).
        old_name: Legacy path relative to HERMES_HOME (e.g. ``"image_cache"``).

    Returns:
        Absolute ``Path`` — legacy location if it exists with content,
        otherwise the new location.
    )r<   _legacy_path_has_content)rb   rc   r'   old_paths       r   get_hermes_dirrg      s8    . DhH)) +r   r'   c                 v    | pt                      }|dz  g}|dz  dz  }t          j        dk    r||gz   S |g|z   S )aI  Return Hermes-managed Node.js directories in preferred lookup order.

    Windows installs from ``scripts/install.ps1`` unpack portable Node directly
    into ``%LOCALAPPDATA%\hermes\node``. POSIX installs use
    ``$HERMES_HOME/node/bin``. Include both shapes on every platform so mixed
    or migrated installs still work.
    nodebinr   )r<   r"   r#   )r'   rootdirsbin_dirs       r   iter_hermes_node_dirsrn      sV     $?$$D6M?DVme#G |wwi9tr   commandc                    t          |           j        }t          j        dk    sd|v r|gS |                                dk    rg dS |                                dk    rg dS |                                dk    rddgS | d	| d
|gS )Nr   .npm)znpm.cmdznpm.exerr   npx)znpx.cmdznpx.exers   ri   node.exez.cmdz.exe)r   rC   r"   r#   lower)ro   r)   s     r   _candidate_node_command_namesrv     s    ==D
|w#++vzz||u -,,,zz||u,,,,zz||vF##MMMd===$//r   HERMES_NODE_TARGET_MAJOR22scriptslibznode-bootstrap.shc                    | sdS t          |           }t          j        dk    r|                                sdS n@t          j                            |           rt	          j        | t          j                  sdS ddl	}	 ddl
m} |                    | dgddt                       |            	          }n# t          |j        t           f$ r Y dS w xY w|j        dk    S )
u  Return True only when *path* is a Node/npm/npx binary that actually runs.

    Hermes-managed Node trees live under ``$HERMES_HOME/node`` (or a profile's
    ``HERMES_HOME``). A partial upgrade or interrupted install can leave
    ``bin/npm`` behind while ``lib/cli.js`` is missing — the wrapper exists but
    immediately throws ``MODULE_NOT_FOUND``. ``find_hermes_node_executable``
    used to trust file presence alone, so ``hermes update`` would pick that
    broken npm and fail the Node refresh / web UI build.

    Probe with ``--version`` (same pattern as :func:`agent_browser_runnable`) so
    broken managed wrappers are detected before use.
    Fr   r   Nwindows_hide_flags	--versionT
   capture_outputtimeoutenvcreationflags)r   r"   r#   is_filer$   r	   r/   accessX_OK
subprocesshermes_cli._subprocess_compatr}   runwith_hermes_node_pathr2   TimeoutExpiredrA   
returncode)r	   rR   r   r}   results        r   node_tool_runnabler   '  s     uT

I
|w  "" 	5	W^^D!! 4)A)A uDDDDDD;%'',,..   
 
 Z.
;   uu!!s    6B7 7CCc                 6   t                      }dD ]$}|                    t          |                     %t          |           D ]S}|D ]N}||z  }|                                r3t
          j        dk    st          j        |t          j	                  r  dS OTdS )zEReturn True when any Hermes-managed node/npm/npx shim exists on disk.)ri   rr   rs   r   TF)
r   updaterv   rn   r   r"   r#   r$   r   r   )r'   namesro   	directoryrC   rR   s         r    hermes_managed_node_tree_presentr   N  s    EEE) = =27;;<<<<*400  	 	 	D!D(I  "" ''29Y+H+H'ttt	 5r   c                  J   ddl } ddl}ddl}ddl}t          j                            d          pt          j                            dd                                          }|dv rd}n|dk    rd}n	|d	v rd
}ndS t                      }dt           d}	 |j
                            |d          5 }|                                                    dd          }	ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY w|                     dt           d| d|	          }
|
sdS |
                    d          }| | }	 |j
                            |d          5 }|                                }ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY w	 |                                5 }t%          |          }||z  }|                    |           |dz  }|                                 |                    |          5 }|                    |           ddd           n# 1 swxY w Y   t/          |                    d          d          }||                                s	 ddd           dS |dz  }|                                rt7          j        |           t7          j        t=          |          t=          |                     ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY wt?          t=          |dz                      S )zHRedownload the portable Node zip into ``%HERMES_HOME%\node`` on Windows.r   NPROCESSOR_ARCHITEW6432PROCESSOR_ARCHITECTUREr   )amd64x86_64x64arm64)x86r   Fz https://nodejs.org/dist/latest-vz.x/<   )r   utf-8replace)errorsznode-vz\.\d+\.\d+-win-z\.zip,  extractznode-v*ri   rt   ) retempfileurllib.requestzipfiler$   r%   r   ru   r<   _HERMES_NODE_TARGET_MAJORrequesturlopenreaddecoder2   searchgroupTemporaryDirectoryr   write_bytesmkdirZipFile
extractallnextglobis_dirr/   shutilrmtreemover   r   )r   r   urllibr   arch	node_archr'   	index_urlresponse
index_htmlmatchzip_namedownload_url	zip_bytestmp_dirtmp_pathzip_pathextract_dirarchive	extractedtargets                        r   _heal_managed_node_windowsr   ]  s   IIIOOONNNJNN344d
G_ac8d8dkkmmD"""										uDQ3LQQQI^##Ir#:: 	Kh!//	/JJJ	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K   uu IIL+LLILLL E  u{{1~~H+++L^##L##>> 	(( I	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	(   uu((** 	5gG}}H(*H  +++"Y.K** 0g"";///0 0 0 0 0 0 0 0 0 0 0 0 0 0 0[--i88$??I 	(8(8(:(: 	5 	5 	5 	5 	5 	5 	5 	5 F]F}} &f%%%KIF444	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5    uu c&:"566777s   C3 1*C'C3 'C++C3 .C+/C3 3
D DF "F7F FF 
FF 
FF!K5 5AK)H/#K)/H3	3K)6H3	7=K)4K5 AK)K5 )K--K5 0K-1K5 5
LLc            	         t           rdS t                      sdS da t          j        dk    rt	                      S t
                                          sdS ddl} 	 |                     dddt
           d	gi t          j
        d
t          t                                iddd          }n# t          | j        f$ r Y dS w xY w|j        dk    S )a  Redownload Hermes-managed Node when the tree exists but is broken.

    Runs at most once per process. POSIX installs shell out to
    ``heal_managed_node`` in ``scripts/lib/node-bootstrap.sh``; Windows
    downloads the portable zip directly (same source as ``install.ps1``).
    FTr   r   Nbashz-czsource "z" && heal_managed_noder,   r   )r   r   r   check)_managed_node_heal_attemptedr   r"   r#   r   _NODE_BOOTSTRAP_SCRIPTr   r   r   r$   r%   r   r<   r2   SubprocessErrorr   )r   r   s     r   heal_hermes_managed_noder     s	    $ u+-- u#' 
|w)+++!))++ uI1III
 F2:E}c/2C2C.D.DEE   

 

 Z/0   uu!!s   AB* *B?>B?c                 T   t          |           }d}t                      D ]u}|D ]p}||z  }|                                rUt          j        dk    st          j        |t
          j                  r&t          |          }t          |          r|c c S d}qv|rt                      rt                      D ]s}|D ]n}||z  }|                                rSt          j        dk    st          j        |t
          j                  r$t          |          }t          |          r|c c S otdS )zGReturn a Hermes-managed Node/npm executable path, healing broken trees.Fr   TN)rv   rn   r   r"   r#   r$   r   r   r   r   r   )ro   r   broken_presentr   rC   rR   resolveds          r   find_hermes_node_executabler     sX   )'22EN*,, 	& 	&	 	& 	&D!D(I  "" &''29Y+H+H'y>>%h// $#OOOOO!%	&  	(244 	(.00 	( 	(I ( (%,	$$&& (LG++ryBG/L/L+"9~~H)(33 ('( 4r   c                 >   t           j        dk    rt          j        |           S t	          |           t          fdt          j        t          j        ddfD                       }|r%t                    
                                rndS t                    D ]~}t          j                            dd                              t          j                  D ]>}|st          |          |z  }|
                                rt	          |          c c S ?dS )aA  Return a Node/npm executable from PATH with Windows shim ordering.

    ``shutil.which("npm")`` can resolve an extensionless npm shim before the
    ``.cmd`` shim on Windows. Python's CreateProcess cannot execute that shim
    directly, so prefer the launchable variants explicitly for Hermes-owned
    subprocesses.
    r   c              3   $   K   | ]
}|o|v V  d S N ).0sepcommand_strs     r   	<genexpr>z/find_node_executable_on_path.<locals>.<genexpr>  s>        '*"{"     r   /\NPATHr   )r"   r#   r   whichr   anyr$   r   altsepr   r   rv   r%   r   splitpathsep)ro   has_path_separatorrC   r   rR   r   s        @r   find_node_executable_on_pathr     s<    |w|G$$$g,,K    /1vry#t.L      D";//7799C{{tC-k:: & &3399"*EE 	& 	&I Y$.I  "" &9~~%%%%%&		& 4r   c                 f    t          |           }|r|S t                      rdS t          |           S )aQ  Resolve a Node.js command, preferring healthy Hermes-managed installs.

    This is for Hermes-owned subprocesses that should not be broken by a bad,
    missing, or elevation-triggering system Node/npm on PATH. When a managed
    tree exists but cannot be healed, returns ``None`` instead of falling back
    to system npm on PATH.
    N)r   r   r   )ro   manageds     r   find_node_executabler     s?     *'22G ')) t'000r   r   c                    t          | t          j        n|           }|                    dd          }d |                    t          j                  D             }d t                      D             }t          |          D ]}||vr|                    d|           t          j        	                    |          |d<   |S )zDReturn *env* with Hermes-managed Node directories prepended to PATH.Nr   r   c                     g | ]}||S r   r   )r   ps     r   
<listcomp>z)with_hermes_node_path.<locals>.<listcomp>  s    8881a8Q888r   c                 T    g | ]%}|                                 t          |          &S r   )r   r   )r   r	   s     r   r   z)with_hermes_node_path.<locals>.<listcomp>  s+    NNNTNs4yyNNNr   r   )
dictr$   r%   r   r   r   rn   reversedinsertjoin)r   mergedexistingpartsr   entrys         r   r   r   	  s    "**55Fzz&"%%H88rz22888ENN%:%<%<NNNG'"" # #LLE"""Z__U++F6NMr   c                    | sdS d| v r/|                                  d                             d          rdS t          j                            |           rt          j        | t          j                  sdS ddl}	 ddlm	} |
                    | dgdd	t                       |            
          }n# t          |j        t          f$ r Y dS w xY w|j        dk    S )uk  Return True only when *path* is an agent-browser CLI that actually runs.

    A bare presence check (``shutil.which`` / ``Path.exists``) is not enough:
    agent-browser's npm ``postinstall`` re-points a *global* install symlink
    (e.g. ``/opt/homebrew/bin/agent-browser``) at our local
    ``node_modules/agent-browser/bin/...`` binary, which then disappears on the
    next ``hermes update`` — leaving a **dangling symlink** that ``which`` still
    reports but exec fails on with exit 127 (issue #48521). Callers that trust
    such a path silently break every browser tool.

    This validates the candidate by resolving it to a real, executable file and
    running ``--version`` with a short timeout. Returns True only on a clean
    (exit 0) run, so a dead/wrong-arch/hung binary is rejected and the caller
    can fall through to the next resolution candidate.

    Special cases:
      * ``None`` / empty → False.
      * The ``"npx agent-browser"`` fallback form (contains a space, not a real
        file) → True; npx resolves and validates the package at run time, so
        there is nothing to stat here.
    F r   rs   TNr|   r~   r   r   )r   endswithr$   r	   r/   r   r   r   r   r}   r   r   r2   r   rA   r   )r	   r   r}   r   s       r   agent_browser_runnabler     s   ,  u
d{{tzz||A//66{t 7>>$ ryrw'?'? uDDDDDD;%'',,..   
 
 Z.
;   uu!!s   =6B4 4CCc                    	 |                                  }n# t          $ r Y dS t          $ r Y dS w xY wt          j        |j                  rO	 |                                 }n# t          $ r Y dS t          $ r Y dS w xY wt          j        |j                  sdS nt          j        |j                  sdS 	 t          |                                            n# t          $ r Y dS t          $ r Y dS w xY wdS )u  Return ``True`` iff ``path`` exists and has content worth honouring.

    A populated *directory* (any entry inside) counts. A non-directory
    file at ``path`` also counts — the consumer presumably wrote it.
    An empty directory does **not** count, so a stale empty
    legacy stub falls through to the new layout. If the path cannot be
    inspected (``PermissionError`` on ``stat``/``iterdir``, or any other
    ``OSError`` short of "not found"), assume occupied so we don't
    accidentally orphan legacy data. Only a genuine
    ``FileNotFoundError`` counts as absent.

    Symlinks are resolved before judging content: a symlink pointing at a
    populated directory (or any existing non-directory target) counts, but
    a **dangling** symlink (broken target) does **not** — it must not be
    allowed to shadow populated new-layout data, matching the old
    ``exists()`` gate's behaviour for broken links.
    FT)
lstatFileNotFoundErrorr2   statS_ISLNKst_modeS_ISDIRr   iterdirStopIteration)r	   st	target_sts      r   re   re   F  sP   $ZZ\\   uu    tt |BJ 			II  	 	 	55 	 	 	44	|I-.. 	4	 \"*%% tT\\^^   uu   tt4s@    
1	11A# #
A=0	A=<A=8!C 
C4'	C43C4c                      t                      } 	 dt          |                     t          j                                        z   S # t
          $ r t          |           cY S w xY w)a  Return a user-friendly display string for the current HERMES_HOME.

    Uses ``~/`` shorthand for readability::

        default:  ``~/.hermes``
        profile:  ``~/.hermes/profiles/coder``
        custom:   ``/opt/hermes-custom``

    Use this in **user-facing** print/log messages instead of hardcoding
    ``~/.hermes``.  For code that needs a real ``Path``, use
    :func:`get_hermes_home` instead.
    z~/)r<   r   r@   r   r'   rA   )r'   s    r   display_hermes_homer  w  sh     Dc$**49;;778888   4yys   5A A"!A"c                     | j                                         }|t          d          k    st          |j                  dk     rdS 	 t          j        |d           dS # t          $ r Y dS w xY w)a  Chmod ``0o700`` on the parent directory of *path*, but only if safe.

    Refuses to chmod ``/`` or any top-level directory (resolved parent with
    fewer than 3 parts, i.e. ``/`` or any direct child like ``/usr``) to
    prevent catastrophic host bricking when ``HERMES_HOME`` or other path
    env vars resolve to an unexpected location.

    See https://github.com/NousResearch/hermes-agent/issues/25821.
    r      Ni  )rB   r?   r   lenr   r$   chmodr2   )r	   rB   s     r   secure_parent_dirr    s     [  ""Fcc&,//!33
   s   A 
A-,A-c                 H   | pd                                 }|sdS 	 t          j                            t          j                            t          j                            |                              S # t          $ r" t          j                            |          cY S w xY w)zDReturn a comparable absolute path string, or ``""`` for empty input.r   )r&   r$   r	   normcaseabspath
expanduserr6   )r	   rQ   s     r   _norm_home_pathr    s    :2



C r%w0B0B30G0G H HIII % % %w$$$$$%s   AA5 5)B! B!c                    t                      p*| pi                     d          pt          j        d          }|sdS t          j                            |d          }t          j                            |          r|S dS )zEReturn ``{HERMES_HOME}/home`` when the profile-home directory exists.r,   Nr'   )r   r   r$   rW   r	   r   isdir)r   hermes_homeprofile_homes      r   _profile_home_pathr    sw    *,,j0N0NjRTR[\iRjRjK t7<<V44L	w}}\"" 4r   rR   r  c                 d    t          | o!|ot          |           t          |          k              S r   )boolr  )rR   r  s     r   _is_profile_homer    s0    	jljy/I/I_]iMjMj/jkkkr   c                 f   | pi } g }t          |                     d          pt          j        dd                                                    }|r|                    |           t          |                     d          pt          j        dd                                                    }|r|                    |           	 ddl}|                    t          j                              j	                                        }|r|                    |           n# t          $ r Y nw xY wt          |                     d          pt          j        dd                                                    }|r|                    |           t          |                     d          pt          j        dd                                                    }t          |                     d          pt          j        dd                                                    }|rP|rN|                    |                    d	          r| | nt          j                            ||                     t          j                            d
          }	|	r|	d
k    r|                    |	           |S )z5Return likely OS-user home candidates in trust order.HERMES_REAL_HOMEr   HOMEr   NUSERPROFILE	HOMEDRIVEHOMEPATH)r   r   ~)r   r   r$   rW   r&   rN   pwdgetpwuidgetuidpw_dirr6   
startswithr	   r   r  )
r   rO   explicitr'   r"  pw_homeuserprofiledriver	   expandeds
             r   _iter_real_home_candidatesr,    s   
)CJ377-..S")<NPR2S2STTZZ\\H $(###swwv7")FB"7"788>>@@D  $


,,ry{{++288:: 	'g&&&   cggm,,L	-0L0LMMSSUUK '+&&&$$B	+r(B(BCCIIKKEswwz""?bi
B&?&?@@FFHHD k kdook.J.JiU*D***PRPWP\P\]bdhPiPijjjw!!#&&H $HOO(###s   AD! !
D.-D.c                     t          |           }t                      }t          |           D ]A}t          |          }|r||v r|                    |           t          ||          s|c S BdS )as  Return the OS user's real home directory, avoiding Hermes profile HOME.

    ``HERMES_HOME`` scopes Hermes state. ``HOME`` is reserved for the OS/user
    account and the many external CLIs that store credentials under ``~``.
    If a parent process is already running with ``HOME={HERMES_HOME}/home``,
    this helper repairs back to the account home when possible.
    z/tmp)r  r   r,  r  addr  )r   r  seenrR   keys        r   get_real_homer1    s     &c**LUUD/44  	i(( 	cTkk	<88 		6r   c                    | pi } t          |           }t          |                     d          pt          j        dd                                                                                    pd}|dv rd}|dv rd}|dk    r|S t          |           }t          |                     d          pt          j        dd                                                    }|dk    r$t          |          t          |          k    r|nd	S |rt                      r|S t          ||          r$t          |          t          |          k    r|nd	S d	S )
aJ  Return a subprocess ``HOME`` override, if one should be applied.

    Policy is controlled by ``terminal.home_mode`` (bridged to
    ``TERMINAL_HOME_MODE``):

    * ``auto`` (default): host installs keep the real user HOME; containers use
      ``{HERMES_HOME}/home`` for persistent state. If a host parent already has
      HOME pointed at the profile home, repair subprocesses back to real HOME.
    * ``real``: always prefer the real OS-user HOME.
    * ``profile``: use ``{HERMES_HOME}/home`` when it exists, preserving the
      older strict per-profile tool-config isolation.
    TERMINAL_HOME_MODEauto>   profile-homeisolatedr  profile>   	real-homehostuser	real_homerealr  r   N)r  r   r   r$   rW   r&   ru   r1  r  is_containerr  )r   r  moder;  current_homes        r   get_subprocess_homer@    s]    )C%c**Lsww+,,W	:NPV0W0WXX^^``ffhhrlrD;;;999yc""Iswwv?")FB*?*?@@FFHHLv~~+I66/,:W:WWWyy]aa  l33 b+I66/,:W:WWWyy]aa4r   c                 b    t          |           }|r|| d<   t          |           }|r|| d<   dS dS )z9Apply Hermes' subprocess HOME contract to *env* in-place.r  r  N)r1  r@  )r   r;  r'   s      r   apply_subprocess_home_envrB    sN    c""I ,"+s##D F r   )minimallowmediumhighxhighmaxc                     | du rddiS | | du rdS t          |           } |                                 sdS |                                                                 } | dv rddiS | t          v rd| dS dS )u=  Parse a reasoning effort level into a config dict.

    Valid levels: "none", "minimal", "low", "medium", "high", "xhigh", "max".
    Returns None when the input is empty or unrecognized (caller uses default).
    Returns {"enabled": False} for "none" (aliases: "false", "disabled", and
    YAML boolean False — users write ``reasoning_effort: false``/``off``/``no``
    in config.yaml and YAML hands us a bool, which must mean disabled, not
    "fall back to the default and keep thinking").
    Returns {"enabled": True, "effort": <level>} for valid effort levels.
    FenabledNT>   nonefalsedisabled)rJ  effort)r   r&   ru   VALID_REASONING_EFFORTS)rN  s    r   parse_reasoning_effortrP    s     5!!~4t[[F<<>> t\\^^!!##F...5!!(((62224r   c                  v    t          j        dd          } t          t          j        d          pd| v           S )u   Return True when running inside a Termux (Android) environment.

    Checks ``TERMUX_VERSION`` (set by Termux) or the Termux-specific
    ``PREFIX`` path.  Import-safe — no heavy deps.
    PREFIXr   TERMUX_VERSIONzcom.termux/files/usr)r$   rW   r  )prefixs    r   	is_termuxrU  7  s9     Yx$$F	*++O/E/OPPPr   _wsl_detectedc                      t           t           S 	 t          ddd          5 } d|                                                                 v a ddd           n# 1 swxY w Y   n# t          $ r da Y nw xY wt           S )u   Return True when running inside WSL (Windows Subsystem for Linux).

    Checks ``/proc/version`` for the ``microsoft`` marker that both WSL1
    and WSL2 inject.  Result is cached for the process lifetime.
    Import-safe — no heavy deps.
    Nz/proc/versionrr   encoding	microsoftF)rV  openr   ru   r6   )fs    r   is_wslr^  D  s      /3999 	<Q'16688>>+;+;;M	< 	< 	< 	< 	< 	< 	< 	< 	< 	< 	< 	< 	< 	< 	<   s4   A# )AA# AA# AA# #A21A2_container_detectedc                     t           t           S t          j                            d          rda dS t          j                            d          rda dS t          j                            d          rda dS d} 	 t          ddd	
          5 }|                                t          fd| D                       rda 	 ddd           dS 	 ddd           n# 1 swxY w Y   n# t          $ r Y nw xY w	 t          ddd	
          5 }|                                t          fddD                       rda 	 ddd           dS 	 ddd           n# 1 swxY w Y   n# t          $ r Y nw xY wda dS )u	  Return True when running inside a container.

    Recognizes Docker (``/.dockerenv``), Podman (``/run/.containerenv``),
    and — via ``/proc/1/cgroup`` — the docker/podman/lxc cgroup-v1 markers.

    cgroup v2 collapses ``/proc/1/cgroup`` to a single ``0::/`` line with no
    runtime marker, so containerd/CRI-O runtimes (the common case on
    Kubernetes/k3s) were previously missed. To cover those, also check:
      * ``KUBERNETES_SERVICE_HOST`` env var — set in every Kubernetes pod.
      * ``kubepods`` / ``containerd`` / ``crio`` markers in ``/proc/1/cgroup``.
      * the same markers in ``/proc/self/mountinfo`` (cgroup-v2 fallback).

    Result is cached for the process lifetime.  Import-safe — no heavy deps.

    See: NousResearch/hermes-agent#47111
    Nz/.dockerenvTz/run/.containerenvKUBERNETES_SERVICE_HOST)dockerpodmanz/lxc/kubepods
containerdcrioz/proc/1/cgrouprX  r   rY  c              3       K   | ]}|v V  	d S r   r   )r   markercgroups     r   r   zis_container.<locals>.<genexpr>{  s(      BB6V#BBBBBBr   z/proc/self/mountinfoc              3       K   | ]}|v V  	d S r   r   )r   rh  	mountinfos     r   r   zis_container.<locals>.<genexpr>  s(      XX66Y&XXXXXXr   )rd  re  rf  F)
r_  r$   r	   r/   r%   r   r\  r   r   r2   )_CGROUP_MARKERSr]  ri  rk  s     @@r   r=  r=  Y  sx   $ &""	w~~m$$ "t	w~~*++ "t	z~~/00 "tUO"C'::: 	aVVXXFBBBB/BBBBB &*#		 	 	 	 	 	 	 		 	 	 	 	 	 	 	 	 	 	 	 	 	 	
    
(#@@@ 	AIXXXX5WXXXXX &*#		 	 	 	 	 	 	 		 	 	 	 	 	 	 	 	 	 	 	 	 	 	
    5sx   =C( 3CC( C( C  C( #C $C( (
C54C59E$ 3E>E$ E$ EE$ E E$ $
E10E1c                  $    t                      dz  S )zReturn the path to ``config.yaml`` under HERMES_HOME.

    Replaces the ``get_hermes_home() / "config.yaml"`` pattern repeated
    in 7+ files (skill_utils.py, hermes_logging.py, hermes_time.py, etc.).
    zconfig.yamlr<   r   r   r   get_config_pathro    s     },,r   c                  $    t                      dz  S )z:Return the path to the skills directory under HERMES_HOME.r`   rn  r   r   r   get_skills_dirrq    s    x''r   c                  $    t                      dz  S )z7Return the path to the ``.env`` file under HERMES_HOME.z.envrn  r   r   r   get_env_pathrs    s    v%%r   forcec                     | sdS ddl t          j        dd          rdS j        dfd	}d|_        |_        dS )u  Monkey-patch ``socket.getaddrinfo`` to prefer IPv4 connections.

    On servers with broken or unreachable IPv6, Python tries AAAA records
    first and hangs for the full TCP timeout before falling back to IPv4.
    This affects httpx, requests, urllib, the OpenAI SDK — everything that
    uses ``socket.getaddrinfo``.

    When *force* is True, patches ``getaddrinfo`` so that calls with
    ``family=AF_UNSPEC`` (the default) resolve as ``AF_INET`` instead,
    skipping IPv6 entirely.  If no A record exists, falls back to the
    original unfiltered resolution so pure-IPv6 hosts still work.

    Safe to call multiple times — only patches once.
    Set ``network.force_ipv4: true`` in ``config.yaml`` to enable.
    Nr   _hermes_ipv4_patchedFc           	          |dk    r6	  | |j         |||          S # j        $ r  | |||||          cY S w xY w | |||||          S )Nr   )AF_INETgaierror)r9  portfamilytypeprotoflags_original_getaddrinfosockets         r   _ipv4_getaddrinfoz0apply_ipv4_preference.<locals>._ipv4_getaddrinfo  s    Q;;U,,$eU   ? U U U,,T4ueTTTTTU %$T4ueLLLs    ;;T)r   r   r   r   )r  getattrgetaddrinforv  )rt  r  r  r  s     @@r   apply_ipv4_preferencer    s       MMM v!#95AA ".	M 	M 	M 	M 	M 	M 	M .2**Fr   zpartial-stream-stublengthzhttps://openrouter.ai/api/v1z/modelsr   )F)L__doc__r$   r   r   r"   rL   contextvarsr   r   pathlibr   r   r  __annotations__objectr   r   r   r   r   r   r*   r<   rG   rS   rZ   r]   ra   rg   listrn   rv   intr%   r   r   r   __file__r?   rB   r   r   r   r   r   r   r   r   r   r   r   re   r  r  r  r  r  r,  r1  r@  rB  rO  rP  rU  rV  r^  r_  r=  ro  rq  rs  r  PARTIAL_STREAM_STUB_IDFINISH_REASON_LENGTHOPENROUTER_BASE_URLOPENROUTER_MODELS_URLr   r   r   <module>r     sr     
			   



     ) ) ) ) ) ) ) )       "' $ & & &	2<*V3 3 3 z#,/   
,3:#4 , , , , ,'e ' ' ' ' '
#*    #4 # # # #7/ 7/ 7/ 7/ 7/t% % % % %P     "1 1TD[ 1D 1 1 1 1"/ /4$; /$ / / / /&( (D4K (4 ( ( ( (( s t    < t tDz    &03 049 0 0 0 0  C
/I4 P PQQ $ h//1189DuLObb $"S4Z $"D $" $" $" $"N 4$; $    ;8D ;8 ;8 ;8 ;8|$"$ $" $" $" $"N t    6# #*    81# 1#* 1 1 1 1 
 
tCH~4 
S#X 
 
 
 
-"t -" -" -" -" -"`.4 .D . . . .bS    (D T    (%#* % % % % % DcNT1 S4Z    ld
 l#* l l l l l DcNT$9 T#Y    > tCH~,     (! !T#s(^d2 !cDj ! ! ! !H4S> d     O dTk    4Q4 Q Q Q Q "td{ ! ! !    $ $( TD[ ' ' '3d 3 3 3 3r- - - - -( ( ( ( (&d & & & &'+ '+ '+$ '+ '+ '+ '+Z /   5 .777   r   