+
    i2                     V   R t ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIHt ]P                  ! R4      t	. R!Ot
. R"OtRs]P                  ! 4       tR R lt]! 4       tR R ltR	 R
 ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltR#R R lltR#R R lltR R  ltR# )$a  
Hermes Web UI -- Profile state management.
Wraps hermes_cli.profiles to provide profile switching for the web UI.

The web UI maintains a process-level "active profile" that determines which
HERMES_HOME directory is used for config, skills, memory, cron, and API keys.
Profile switches update os.environ['HERMES_HOME'] and monkey-patch module-level
cached paths in hermes-agent modules (skills_tool, cron/jobs) that snapshot
HERMES_HOME at import time.
NPathz^[a-z0-9][a-z0-9_-]{0,63}$defaultc                $    V ^8  d   QhR\         /#    returnr   )formats   ")/home/ubuntu/hermes-webui/api/profiles.py__annotate__r      s     "# "#4 "#    c                    \         P                  ! RR4      P                  4       p V '       d   \        V 4      P	                  4       # \         P                  ! RR4      P                  4       pV'       dM   \        V4      P	                  4       pVP
                  P                  R8X  d   VP
                  P
                  # V# \        P                  ! 4       R,          # )u  Return the BASE ~/.hermes directory — the root that contains profiles/.

This is intentionally distinct from HERMES_HOME, which tracks the *active
profile's* home and changes on every profile switch.  The base dir must
always point to the top-level .hermes regardless of which profile is active.

Resolution order:
  1. HERMES_BASE_HOME env var (set explicitly, highest priority)
  2. HERMES_HOME env var — but only if it does NOT look like a profile subdir
     (i.e. its parent is not named 'profiles').  This handles test isolation
     where HERMES_HOME is set to an isolated test state dir.
  3. ~/.hermes (always-correct default)

The bug this prevents: if HERMES_HOME has already been mutated to
/home/user/.hermes/profiles/webui (by init_profile_state at startup),
reading it here would make _DEFAULT_HERMES_HOME point to that subdir,
causing switch_profile('webui') to look for
/home/user/.hermes/profiles/webui/profiles/webui — which doesn't exist.
HERMES_BASE_HOME HERMES_HOMEprofilesz.hermes)osgetenvstripr   
expanduserparentnamehome)base_overridehermes_homeps      r
   _resolve_base_hermes_homer      s    * II0"5;;=MM"--//))M2.446K((*88==J&88??"99;""r   c                $    V ^8  d   QhR\         /# r   str)r	   s   "r
   r   r   E   s     
 
3 
r   c                     \         R,          p V P                  4       '       d-    V P                  4       P                  4       pV'       d   V#  R# R#   \         d     R# i ; i)z=Read the sticky active profile from ~/.hermes/active_profile.active_profiler   )_DEFAULT_HERMES_HOMEexists	read_textr   	Exception)ap_filer   s     r
   _read_active_profile_filer'   E   s_    "%55G~~	$$&,,.D    		s   %A A A! A!c                $    V ^8  d   QhR\         /# r   r   )r	   s   "r
   r   r   T   s       r   c                     \         # )z)Return the currently active profile name.)_active_profile r   r
   get_active_profile_namer,   T   s    r   c                $    V ^8  d   QhR\         /# r   r   )r	   s   "r
   r   r   Y   s          r   c                     \         R8X  d   \        # \        R,          \         ,          p V P                  4       '       d   V # \        # )z=Return the HERMES_HOME path for the currently active profile.r   r   )r*   r"   is_dir)profile_dirs    r
   get_active_hermes_homer1   Y   s:    )###&3oEKr   c                $    V ^8  d   QhR\         /# r   r   r   )r	   s   "r
   r   r   c   s      4 r   c                h   \        V 4      \        P                  R&    ^ RIHp Wn        V R,          Vn         ^ RI	H
p Wn        V R,          Vn        VP                  R,          Vn        VP                  R,          Vn        R#   \        \        3 d     L`i ; i  \        \        3 d     R# i ; i)zCSet HERMES_HOME env var and monkey-patch cached module-level paths.r   Nskillscronz	jobs.jsonoutput)r   r   environtools.skills_toolskills_toolr   
SKILLS_DIRImportErrorAttributeError	cron.jobsjobs
HERMES_DIRCRON_DIR	JOBS_FILE
OUTPUT_DIR)r   _sk_cjs   &  r
   _set_hermes_homerF   c   s     #D	BJJ}'
f}{20 (  ( s#   B A
B BBB10B1c                $    V ^8  d   QhR\         /# r3   r   )r	   s   "r
   r   r   z   s       r   c                "   V R,          pVP                  4       '       g   R#  VP                  4       P                  4        F  pVP                  4       pV'       g   K  VP	                  R4      '       d   K6  RV9   g   K?  VP                  R^4      w  r4VP                  4       pVP                  4       P                  R4      P                  R4      pV'       g   K  V'       g   K  V\        P                  V&   K  	  R#   \         d     R# i ; i)z:Load .env from the profile dir into os.environ (additive)..envN#="')	r#   r$   
splitlinesr   
startswithsplitr   r8   r%   )r   env_pathlinekvs   &    r
   _reload_dotenvrU   z   s    f}H??
&&(335D::<DtDOOC00SD[zz#q)GGIGGIOOC(..s31$%BJJqM 6  s0   6C? C? 7C?  AC? C? &C? ?DDc                    V ^8  d   QhRR/# )r   r   Nr+   )r	   s   "r
   r   r      s     
 
D 
r   c                 Z    \        4       s\        4       p \        V 4       \	        V 4       R# )zInitialize profile state at server startup.

Reads ~/.hermes/active_profile, sets HERMES_HOME env var, patches
module-level cached paths.  Called once from config.py after imports.
N)r'   r*   r1   rF   rU   )r   s    r
   init_profile_staterX      s$     01O!#DT4r   c                0    V ^8  d   QhR\         R\        /# r   r   r   r   dict)r	   s   "r
   r   r      s     = = = =r   c           	     *   ^ RI HpHpHp T;_uu_ 4        \	        V4      ^ 8  d   \        R4      h RRR4       V R8X  d   \        pM9\        R,          V ,          pVP                  4       '       g   \        RV  R24      h\        ;_uu_ 4        V s
\        V4       \        V4       RRR4        \        R,          pTP                  V R8w  d   T MR	4       T! 4        ^ R
IHp ^ RI Hp V! 4       pVP%                  R/ 4      p	Rp
\'        V	\(        4      '       d   T	p
M'\'        V	\*        4      '       d   V	P%                  R4      p
R\-        4       RV RV
RV! 4       /#   + '       g   i     EL4; i  + '       g   i     L; i  \         d     Li ; i)zSwitch the active profile.

Validates the profile exists, updates process state, patches module caches,
reloads .env, and reloads config.yaml.

Returns: {'profiles': [...], 'active': name}
Raises ValueError if profile doesn't exist or agent is busy.
)STREAMSSTREAMS_LOCKreload_configzRCannot switch profiles while an agent is running. Cancel or wait for it to finish.Nr   r   	Profile '' does not exist.r!   r   )get_last_workspace)
get_configmodelactivedefault_modeldefault_workspace)
api.configr^   r_   r`   lenRuntimeErrorr"   r/   
ValueError_profile_lockr*   rF   rU   
write_textr%   api.workspacerc   rd   get
isinstancer   r\   list_profiles_api)r   r^   r_   r`   r   r&   rc   rd   cfg	model_cfgrg   s   &          r
   switch_profileru      sd    @? 
w<!3   
 y##j047{{}}y.?@AA	t 
&)9949#44"=
 O 1%
,C$IM)S!!!	It	$	$!i0 	%'$/1	 S 
 
  s)   EE11'F E.	1F	FFc                $    V ^8  d   QhR\         /# r   )list)r	   s   "r
   r   r      s      4 r   c                     ^ RI Hp  V ! 4       p\        p. pT F  pTP                  RTP                  R\        TP                  4      RTP                  RTP                  T8H  RTP                  RTP                  RTP                  R	TP                  R
TP                  /	4       K  	  T#   \         d    \        4       .u # i ; i)z>List all profiles with metadata, serialized for JSON response.)list_profilesr   path
is_default	is_activegateway_runningre   providerhas_envskill_count)hermes_cli.profilesry   r<   _default_profile_dictr*   appendr   r   rz   r{   r}   re   r~   r   r   )ry   infosrf   resultr   s        r
   rr   rr      s    )5
 FFAFFCK!,,6)q00QWW

qyy1==

 
	  M%  )%'(()s   B+ +CCc                $    V ^8  d   QhR\         /# r   )r\   )r	   s   "r
   r   r      s      t r   c                 v    RRR\        \        4      RRRRRRR	R
RR
R\        R,          P                  4       R^ /	# )z8Fallback profile dict when hermes_cli is not importable.r   r   rz   r{   Tr|   r}   Fre   Nr~   r   rI   r   )r   r"   r#   r+   r   r
   r   r      sP     		()dT5D(6199;q
 
r   c                $    V ^8  d   QhR\         /# )r   r   r   )r	   s   "r
   r   r     s     	
 	
 	
r   c                    V R8X  d   \        R4      h\        P                  V 4      '       g   \        RV : R24      hR# )zDValidate profile name format (matches hermes_cli.profiles upstream).r   zFCannot create a profile named 'default' -- it is the built-in profile.zInvalid profile name z%. Must match [a-z0-9][a-z0-9_-]{0,63}N)rl   _PROFILE_ID_RE	fullmatch)r   s   &r
   _validate_profile_namer     sJ    yabb##D))#D8 ,2 2
 	
 *r   c                H    V ^8  d   QhR\         R\         R\        R\        /# r   r   
clone_fromclone_configr   )r   boolr   )r	   s   "r
   r   r     s*      3 C ,0=Ar   c                   \         R,          V ,          pVP                  4       '       d   \        RV  R24      hVP                  RRR7       \         F  pW4,          P                  RRR7       K  	  V'       d   V'       d   VR8X  d   \         pM\         R,          V,          pVP                  4       '       dK   \         F@  pWV,          pVP                  4       '       g   K#  \        P                  ! WsV,          4       KB  	  V# )zKCreate a profile directory without hermes_cli (Docker/standalone fallback).r   ra   z' already exists.TF)parentsexist_okr   )	r"   r#   FileExistsErrormkdir_PROFILE_DIRSr/   _CLONE_CONFIG_FILESshutilcopy2)r   r   r   r0   subdir
source_dirfilenamesrcs   &&&     r
   _create_profile_fallbackr     s     '3d:K	$/@ABB dU3		$$TD$A   
"-J-
:ZGJ/ +::<<LLH&<= 0
 r   c                H    V ^8  d   QhR\         R\         R\        R\        /# r   )r   r   r\   )r	   s   "r
   r   r   )  s*     ' 'S 'c '%)'6:'r   c                   \        V 4       Ve   VR8w  d   \        V4        ^ RIHp V! V VVRRR7       \
        R,          V ,          p\        4        F  pVR,          V 8X  g   K  Vu # 	  RV R	\        V4      R
RR\        V 8H  RRRRRRRVR,          P                  4       R^ /	#   \         d    \	        YT4        Li ; i)z8Create a new profile. Returns the new profile info dict.Nr   )create_profileFT)r   r   	clone_allno_aliasr   r   rz   r{   r|   r}   re   r~   r   rI   r   )
r   r   r   r<   r   r"   rr   r   r*   r#   )r   r   r   r   profile_pathr   s   &&&   r
   create_profile_apir   )  s     4  *	"9z*
A6!%	
 (*4t;L V9H ! 	L!e_,5DL6)113q
 
  A <@As   B& &C ?C c                0    V ^8  d   QhR\         R\        /# rZ   r[   )r	   s   "r
   r   r   S  s     & &S &T &r   c                   V R8X  d   \        R4      h\        V 8X  d    \        R4        ^ RIHp V! V RR7       RRRT /#   \         d    \        RT  R24      hi ; i  \         d\    ^ RIp\        R	,          T ,          pTP                  4       '       d   TP                  ! \        T4      4        Lz\        R
T  R24      hi ; i)zCDelete a profile. Switches to default first if it's the active one.r   z"Cannot delete the default profile.zCannot delete active profile 'z=' while an agent is running. Cancel or wait for it to finish.)delete_profileT)yesNr   ra   rb   okr   )rl   r*   ru   rk   r   r   r<   r   r"   r/   rmtreer   )r   r   r   r0   s   &   r
   delete_profile_apir   S  s    y=>> $	9%
B6t& $%%%  	0 73 3 	  B*Z7$>MM#k*+y.?@AABs"   A A A8CC5C)memoriessessionsr5   skinslogsplans	workspacer6   )zconfig.yamlrI   zSOUL.md)NF)__doc__jsonr   rer   	threadingpathlibr   compiler   r   r   r*   Lockrm   r   r"   r'   r,   r1   rF   rU   rX   ru   rr   r   r   r   r   r   r+   r   r
   <module>r      s   	  	 	    9: 9   "#H 12 

 .$
=@4	
6'T&r   