
    i$+                     $   d Z ddlZddlZddlZddlmZ ddlmZm	Z
mZmZmZmZ defdZdefdZdefdZdefd	Zd
edefdZdefdZdefdZd
eddfdZdefdZdeddfdZdededefdZddedefdZdededefdZddZ dedefdZ!dS )a{  
Hermes Web UI -- Workspace and file system helpers.

Workspace lists and last-used workspace are stored per-profile so each
profile has its own workspace configuration.  State files live at
``{profile_home}/webui_state/workspaces.json`` and
``{profile_home}/webui_state/last_workspace.txt``.  The global STATE_DIR
paths are used as fallback when no profile module is available.
    N)Path)WORKSPACES_FILELAST_WORKSPACE_FILEDEFAULT_WORKSPACEMAX_FILE_BYTES
IMAGE_EXTSMD_EXTSreturnc                      	 ddl m} m}  |             }|r,|dk    r& |            dz  }|                    dd           |S n# t          $ r Y nw xY wt
          j        S )zReturn the webui_state directory for the active profile.

    For the default profile, returns the global STATE_DIR (respects
    HERMES_WEBUI_STATE_DIR env var for test isolation).
    For named profiles, returns {profile_home}/webui_state/.
    r   )get_active_profile_nameget_active_hermes_homedefaultwebui_stateTparentsexist_ok)api.profilesr   r   mkdirImportError_GLOBAL_WS_FILEparent)r   r   nameds       */home/ubuntu/hermes-webui/api/workspace.py_profile_state_dirr      s    PPPPPPPP&&(( 	DI%%&&((=8AGGD4G000H   !!s   ?A 
AAc                  $    t                      dz  S )z7Return the workspaces.json path for the active profile.zworkspaces.jsonr        r   _workspaces_filer    ,   s    "333r   c                  $    t                      dz  S )z:Return the last_workspace.txt path for the active profile.zlast_workspace.txtr   r   r   r   _last_workspace_filer"   1   s    "666r   c                     	 ddl m}   |             }dD ]~}|                    |          }|ret          t	          |                                                                                    }|                                rt	          |          c S |                    di           }t          |t                    r|                    dd          }|rtt	          |          dvrct          t	          |                                                                                    }|                                rt	          |          S n# t          t          f$ r Y nw xY wt	          t                    S )ug  Read the profile's default workspace from its config.yaml.

    Checks keys in priority order:
      1. 'workspace'         — explicit webui workspace key
      2. 'default_workspace' — alternate explicit key
      3. 'terminal.cwd'      — hermes-agent terminal working dir (most common)

    Falls back to the boot-time DEFAULT_WORKSPACE constant.
    r   )
get_config)	workspacedefault_workspaceterminalcwd ).r)   )
api.configr$   getr   str
expanduserresolveis_dir
isinstancedictr   	Exception_BOOT_DEFAULT_WORKSPACE)r$   cfgkeywspterminal_cfgr(   s          r   _profile_default_workspacer:   6   s^   ))))))jll5 	" 	"CB "RMM,,..668888:: "q66MMMwwz2..lD)) 	"""5"--C "s3xxy00SNN--//779988:: "q66M#   &'''s   BE B7E EE
workspacesc                 .   t          j                    dz  dz                                  }g }| D ]}|                    dd          }|                    dd          }|r!t          |                                          nt          d          }d|v sd|v ri|                                s~	 |                    |           # t          $ r Y nw xY w|                                d	k    rd
}|                    t          |          |d           |S )a  Sanitize a workspace list:
    - Remove entries whose paths no longer exist on disk.
    - Remove entries that look like test artifacts (webui-mvp-test, test-workspace).
    - Remove entries whose paths live inside another profile's directory
      (e.g. ~/.hermes/profiles/X/... should not appear on a different profile).
    - Rename any entry whose name is literally 'default' to 'Home' (avoids
      confusion with the 'default' profile name).
    Returns the cleaned list (may be empty).
    z.hermesprofilespathr)   r   /ztest-workspacezwebui-mvp-testr   Homer>   r   )
r   homer/   r,   r0   relative_to
ValueErrorlowerappendr-   )r;   hermes_profilesresultwr>   r   r8   s          r   _clean_workspace_listrJ   Y   s.    y{{Y.;DDFFOF 6 6uuVR  uuVR  $(7DJJ   d3iit##'74'?'?xxzz 		MM/*** 	 	 	D	 ::<<9$$Ds1vvt445555Ms   /C
CCc                  X   t          j                    sg S 	 t          j        t          j        d                    } t          |           }t          |          t          |           k    r+t          j        t          j        |dd          d           |S # t          $ r g cY S w xY w)a7  Read the legacy global workspaces.json, clean it, and return the result.

    This is the migration path for users upgrading from a pre-profile version:
    their global file may contain cross-profile entries, test artifacts, and
    stale paths accumulated over time.  We clean it in-place and rewrite it.
    utf-8encodingF   ensure_asciiindent)
r   existsjsonloads	read_textrJ   len
write_textdumpsr3   )rawcleaneds     r   _migrate_global_workspacesr\   |   s     !## 	
j2GDDDEE',,w<<3s88##&
7qAAAG       			s   BB B)(B)c                  R   t                      } |                                 r	 t          j        |                     d                    }t          |          }t          |          t          |          k    r>	 |                     t          j        |dd          d           n# t          $ r Y nw xY w|pt                      ddgS # t          $ r Y nw xY w	 dd	lm}  |            d
v }n# t          $ r d}Y nw xY w|rt                      }|r|S t                      ddgS )NrL   rM   FrO   rP   r@   rA   r   )r   )r   NT)r    rS   rT   rU   rV   rJ   rW   rX   rY   r3   r:   r   r   r   r\   )ws_filerZ   r[   r   
is_defaultmigrateds         r   load_workspacesra      s     G~~ 	*W...@@AAC+C00G7||s3xx''&&
7qIIIT[ '     !   DV(B(D(DfUUVV 	 	 	D	
888888,,..2CC

   


 -// 	O/116BBCCsH   AC <,B) (C )
B63C 5B66C 
CCC1 1D ?D c                     t                      }|j                            dd           |                    t	          j        | dd          d           d S )NTr   FrO   rP   rL   rM   )r    r   r   rX   rT   rY   )r;   r^   s     r   save_workspacesrc      sY      GN555tz*5KKKV]^^^^^r   c                     t                      } |                                 r_	 |                     d                                          }|r#t	          |                                          r|S n# t          $ r Y nw xY wt          j                    r^	 t          j        d                                          }|r#t	          |                                          r|S n# t          $ r Y nw xY wt                      S )NrL   rM   )	r"   rS   rV   stripr   r0   r3   _GLOBAL_LW_FILEr:   )lw_filer8   s     r   get_last_workspacerh      s   "$$G~~ 	!!7!3399;;A T!WW^^%%  	 	 	D	  	)7;;;AACCA T!WW^^%%  	 	 	D	%'''s%   AA2 2
A?>A?AC# #
C0/C0r>   c                     	 t                      }|j                            dd           |                    t	          |           d           d S # t
          $ r Y d S w xY w)NTr   rL   rM   )r"   r   r   rX   r-   r3   )r>   rg   s     r   set_last_workspacerj      st    &((TD9993t99w77777   s   AA 
A A root	requestedc                     | |z                                   }|                    |                                             |S )zQResolve a relative path inside a workspace root, raising ValueError on traversal.)r/   rC   )rk   rl   resolveds      r   safe_resolve_wsro      s9    y ))++H(((Or   r*   r%   relc           	         t          | |          }|                                st          d|           g }t          |                                d           D ]}|                    |j        t          |                    |                     |                                rdnd|	                                r|
                                j        nd d           t          |          dk    r n|S )NzNot a directory: c                 \    |                                  | j                                        fS )N)is_filer   rE   )r8   s    r   <lambda>zlist_dir.<locals>.<lambda>   s    		QV\\^^7T r   )r6   dirfile)r   r>   typesize   )ro   r0   FileNotFoundErrorsortediterdirrF   r   r-   rC   rs   statst_sizerW   )r%   rp   targetentriesitems        r   list_dirr      s    Y,,F==?? ; 9C 9 9:::Gv~~''-T-TUUU  I((3344![[]]6EE+/<<>>CDIIKK''t	
 
 	 	 	 w<<3E Nr   c                 T   t          | |          }|                                st          d|           |                                j        }|t
          k    rt          d| dt
           d          |                    dd          }||||                    d          d	z   d
S )NzNot a file: zFile too large (z bytes, max )rL   replace)rN   errors
   )r>   contentrx   lines)	ro   rs   rz   r}   r~   r   rD   rV   count)r%   rp   r   rx   r   s        r   read_file_contentr      s    Y,,F>> 6 4s 4 4555;;== DnODOOnOOOPPP	BBGGTGMMRVDWDWZ[D[\\\r      c                     	 t          j        dg| z   t          |          dd|          }|j        dk    r|j                                        ndS # t           j        t          t          f$ r Y dS w xY w)z8Run a git command and return stdout, or None on failure.gitT)r(   capture_outputtexttimeoutr   N)	
subprocessrunr-   
returncodestdoutre   TimeoutExpiredrz   OSError)argsr(   r   rs       r   _run_gitr      s    NGdNCw
 
 
 $%<1#4#4qx~~$>%'8'B   tts   AA A21A2c                 6   | dz                                   sdS t          g d|           }|dS t          ddg|           pd}d |                                D             }t          d |D                       }t          d	 |D                       }t	          |          }t          g d
|           }t          g d|           }|||||r#|                                rt          |          nd|r#|                                rt          |          ndddS )zEReturn git info for a workspace directory, or None if not a git repo.z.gitN)z	rev-parsez--abbrev-refHEADstatusz--porcelainr)   c                     g | ]}||S r   r   .0ls     r   
<listcomp>z*git_info_for_workspace.<locals>.<listcomp>  s    555115Q555r   c              3   h   K   | ]-}t          |          d k    |d         dv s
|d         dv )dV  .dS )rO   r   MARr   N)rW   r   s     r   	<genexpr>z)git_info_for_workspace.<locals>.<genexpr>  sE      XXs1vv{{!1QV1XXr   c              3   D   K   | ]}|                     d           dV  dS )z??r   N)
startswithr   s     r   r   z)git_info_for_workspace.<locals>.<genexpr>  s3      ;;!T(:(:;A;;;;;;r   )rev-list--countz
@{u}..HEAD)r   r   z
HEAD..@{u}r   T)branchdirtymodified	untrackedaheadbehindis_git)rS   r   
splitlinessumrW   isdigitint)	r%   r   
status_outr   r   r   r   r   r   s	            r   git_info_for_workspacer     sE   &&(( t;;;YGGF~t8]3Y??E2J55
--//555EXXeXXXXXH;;u;;;;;IJJE:::IFFE;;;YGGF$??Ua!'CFNN,<,<C#f+++!  r   )r*   )r   )"__doc__rT   osr   pathlibr   r+   r   r   r   rf   r   r4   r   r   r	   r   r    r"   r-   r:   listrJ   r\   ra   rc   rh   rj   ro   r   r2   r   r   r   r   r   r   <module>r      s     				                         "D " " " "&4$ 4 4 4 4
7d 7 7 7 7
(C ( ( ( (F d  t        FD    ,D D D D DB_ _ _ _ _ _(C ( ( ( ((S T    $ 3 4      3    "] ]C ]D ] ] ] ]	 	 	 	d t      r   