
    i2              	       t   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  ej        d          Z	g dZ
g dZda ej                    Zdefd	Z e            Zdefd
ZdefdZdefdZdefdZdefdZddZdedefdZdefdZdefdZdefdZ	 	 ddedededefdZ	 	 ddedededefdZ dedefdZ!dS )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}$)memoriessessionsskillsskinslogsplans	workspacecron)zconfig.yaml.envzSOUL.mddefaultreturnc                     t          j        dd                                          } | r!t          |                                           S t          j        dd                                          }|r?t          |                                          }|j        j        dk    r|j        j        S |S t          j                    dz  S )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      )/home/ubuntu/hermes-webui/api/profiles.py_resolve_base_hermes_homer      s    * I0"55;;==M 0M""--///)M2..4466K ((**8=J&&8?"9;;""    c                      t           dz  } |                                 r<	 |                                                                 }|r|S n# t          $ r Y nw xY wdS )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   sx    "%55G~~ 	$$&&,,..D  	 	 	D	9s   )A 
AAc                      t           S )z)Return the currently active profile name.)_active_profile r    r   get_active_profile_namer,   T   s    r    c                      t           dk    rt          S t          dz  t           z  } |                                 r| S t          S )z=Return the HERMES_HOME path for the currently active profile.r   r   )r*   r#   is_dir)profile_dirs    r   get_active_hermes_homer0   Y   sB    )####&3oEK r    r   c                 :   t          |           t          j        d<   	 ddlm} | |_        | dz  |_        n# t          t          f$ r Y nw xY w	 ddl	m
} | |_        | dz  |_        |j        dz  |_        |j        dz  |_        dS # t          t          f$ r Y dS w xY w)zCSet HERMES_HOME env var and monkey-patch cached module-level paths.r   r   Nr   r   z	jobs.jsonoutput)strr   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_homerB   c   s     #D		BJ}''''''(   f}{20(   s!   6 A
	A
5B BBc                    | dz  }|                                 sdS 	 |                                                                D ]}|                                }|r|                    d          s~d|v rz|                    dd          \  }}|                                }|                                                    d                              d          }|r|r|t          j        |<   dS # t          $ r Y dS w xY w)z:Load .env from the profile dir into os.environ (additive).r   N#=   "')	r$   r%   
splitlinesr   
startswithsplitr   r4   r&   )r   env_pathlinekvs        r   _reload_dotenvrP   z   s   f}H?? 
&&((3355 	& 	&D::<<D &DOOC00 &SD[[zz#q))1GGIIGGIIOOC((..s33 & &$%BJqM	& 	&    s   CC1 1
C?>C?c                  z    t                      at                      } t          |            t	          |            dS )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*   r0   rB   rP   )r   s    r   init_profile_staterR      s<     011O!##DT4r    r   c                 .   ddl m}m}m} |5  t	          |          dk    rt          d          	 ddd           n# 1 swxY w Y   | dk    rt          }n4t          dz  | z  }|                                st          d|  d          t          5  | a
t          |           t          |           ddd           n# 1 swxY w Y   	 t          d	z  }|                    | dk    r| nd
           n# t          $ r Y nw xY w |             ddlm} ddl m}  |            }|                    di           }	d}
t'          |	t(                    r|	}
n*t'          |	t*                    r|	                    d          }
t-                      | |
 |            dS )a  Switch 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.
    r   )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)r   activedefault_modeldefault_workspace)
api.configrT   rU   rV   lenRuntimeErrorr#   r.   
ValueError_profile_lockr*   rB   rP   
write_textr&   api.workspacerY   rZ   get
isinstancer3   dictlist_profiles_api)r   rT   rU   rV   r   r'   rY   rZ   cfg	model_cfgr]   s              r   switch_profilerl      s    @????????? 
  w<<!3                  y##j047{{}} 	B@@@@AAA	  t              &)9949#4#444"====    MOOO 100000%%%%%%
*,,C$$IM)S!! 1!	It	$	$ 1!i00 &''&//11	  s3   #=AA!B>>CC
'C2 2
C?>C?c                  >   	 ddl m}   |             }n# t          $ r t                      gcY S w xY wt          }g }|D ]_}|                    |j        t          |j                  |j	        |j        |k    |j
        |j        |j        |j        |j        d	           `|S )z>List all profiles with metadata, serialized for JSON response.r   )list_profiles	r   path
is_default	is_activegateway_runningr[   providerhas_envskill_count)hermes_cli.profilesrn   r8   _default_profile_dictr*   appendr   r3   rp   rq   rs   r[   rt   ru   rv   )rn   infosr\   resultr   s        r   ri   ri      s    )555555 ) ) )%''(((() FF  FKK,6) 0W
y=

 

 
	 
	 
	 
	 Ms    //c            
      r    dt          t                    dddddt          dz                                  dd	S )z8Fallback profile dict when hermes_cli is not importable.r   TFNr   r   ro   )r3   r#   r$   r+   r    r   rx   rx      sH     ()) (6199;;
 
 
r    c                     | dk    rt          d          t                              |           st          d| d          dS )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)rb   _PROFILE_ID_RE	fullmatch)r   s    r   _validate_profile_namer     sd    yabbb##D)) 
2D 2 2 2
 
 	

 
r    F
clone_fromclone_configc                    t           dz  | z  }|                                rt          d|  d          |                    dd           t          D ]}||z                      dd           |rl|rj|dk    rt           }nt           dz  |z  }|                                r;t          D ]3}||z  }|                                rt          j        |||z             4|S )zKCreate a profile directory without hermes_cli (Docker/standalone fallback).r   rW   z' already exists.TF)parentsexist_okr   )	r#   r$   FileExistsErrormkdir_PROFILE_DIRSr.   _CLONE_CONFIG_FILESshutilcopy2)r   r   r   r/   subdir
source_dirfilenamesrcs           r   _create_profile_fallbackr     s    '3d:K CA$AAABBB dU333 B B	v	$$TD$AAAA  	>
 	>""-JJ-
:ZGJ 	>/ > > 8+::<< >LkH&<===r    c           
         t          |            ||dk    rt          |           	 ddlm}  || ||dd           n!# t          $ r t	          | ||           Y nw xY wt
          dz  | z  }t                      D ]}|d	         | k    r|c S | t          |          dt          | k    ddd|d
z  	                                dd	S )z8Create a new profile. Returns the new profile info dict.Nr   r   )create_profileFT)r   r   	clone_allno_aliasr   r   r   ro   )
r   rw   r   r8   r   r#   ri   r3   r*   r$   )r   r   r   r   profile_pathr   s         r   create_profile_apir   )  s=    4    *	"9"9z***
A666666!%	
 	
 	
 	
 	
  A A A z<@@@@@A (*4t;L    V9HHH  L!!$,  6)1133
 
 
s   ? AAc                    | dk    rt          d          t          | k    r2	 t          d           n!# t          $ r t          d|  d          w xY w	 ddlm}  || d           nf# t          $ rY dd	l}t          d
z  | z  }|	                                r |j
        t          |                     nt          d|  d          Y nw xY wd| dS )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.r   )delete_profileT)yesNr   rW   rX   )okr   )rb   r*   rl   ra   rw   r   r8   r   r#   r.   rmtreer3   )r   r   r   r/   s       r   delete_profile_apir   S  sM   y=>>> $	9%%%% 	 	 	3 3 3 3  	
B666666t&&&&& B B B*Z7$> 	BFM#k**++++@@@@AAA ,+B %%%s   2 AA( (A C
C)r   N)NF)"__doc__jsonr   rer   	threadingpathlibr   compiler~   r   r   r*   Lockrc   r   r#   r3   r(   r,   r0   rB   rP   rR   rh   rl   listri   rx   r   boolr   r   r   r+   r    r   <module>r      s  	 	  				 				            9::   988  	  "#4 "# "# "# "#H 1022 
3 
 
 
 
    
         4    .    $
 
 
 
= = = = = =@4    4t    	
 	
 	
 	
 	
 ;?38 3 C ,0=A   6 59,1' 'S 'c '%)'6:' ' ' 'T&S &T & & & & & &r    