Ë
    ãß²j^X  ã                   ó¢  — 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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ZddlmZ ddlmZ  ej.                  e«      Zej4                  Zej8                  ZdZdZee e gdf   Z!ee"e#e e f      gdf   Z$eegdf   Z%ee"e    gdf   Z&eg e"e#e e f      f   Z'd	Z(d
Z)dZ*dZ+dZ,dZ-de.fd„Z/de fd„Z0de de1fd„Z2d%dedz  de.fd„Z3d&de de"e#e e f      fd„Z4de de fd„Z5de6e    fd„Z7de"e    de fd„Z8de"e    de fd„Z9de"e#e e f      de"e    fd „Z:d!e de fd"„Z; G d#„ d$«      Z<y# e$ r dZY �Œw xY w)'a  Shared file sync manager for remote execution backends.

Tracks local file changes via mtime+size, detects deletions, and syncs to
remote environments transactionally.  Used by SSH, Modal, and Daytona.
Docker and Singularity use bind mounts (live host FS view) and don't need this.
é    N)ÚPath)ÚCallable)Úget_hermes_home)Ú_file_mtime_keyg      @ÚHERMES_FORCE_FILE_SYNCé   )é   é   é   l        Úsync_back_max_byteszhermes-sync-back-i  Úreturnc                  ó  — ddl m}   | «       xs i j                  d«      xs i j                  t        «      }|�	 t	        |«      S t        S # t
        t        f$ r# t        j                  dt        |«       Y t        S w xY w)z³Extraction cap; config.yaml ``terminal.sync_back_max_bytes`` overrides it for trees that
    legitimately exceed 2 GiB (a skipped extraction silently discards the whole download).r   )Úload_configÚterminalz.sync_back: ignoring non-integer terminal.%s=%r)
Úhermes_cli.configr   ÚgetÚ_SYNC_BACK_MAX_BYTES_KEYÚintÚ	TypeErrorÚ
ValueErrorÚloggerÚwarningÚ_SYNC_BACK_MAX_BYTES)r   Úraws     úA/home/ubuntu/.hermes/hermes-agent/tools/environments/file_sync.pyÚ_sync_back_max_bytesr   @   s|   € õ .á‹MÒ˜R×$Ñ$ ZÓ0Ò6°B×
;Ñ
;Ô<TÓ
U€CØ
€ð	lÜ�s“8ˆOô  Ðøô œ:Ð&ò 	lÜ�N‰NÐKÔMeÐgjÕkÜÐð	lús   »
A Á*A>Á=A>c                  ó<   — t         › t        j                  «       › d�S )zúTemp prefix embedding the owning PID so the stale sweep can tell a hard-killed
    process's leftovers from another live gateway's in-flight transfer without guessing
    from mtime (a staging dir's mtime does not move while content streams into it).Ú-)Ú_SYNC_BACK_TEMP_PREFIXÚosÚgetpid© ó    r   Ú_sync_back_temp_prefixr$   N   s   € ô %Ð%¤b§i¡i£k ]°!Ð4Ð4r#   Únamec                 óª   — | t        t        «      d j                  dd«      d   }|j                  «       syt	        j
                  t        |«      «      S )z�Whether the process that created a sync-back temp entry may still be running.
    Names without a PID count as alive: the age cutoff applies.Nr   é   r   T)Úlenr   ÚsplitÚisdigitÚpsutilÚ
pid_existsr   )r%   Úpid_parts     r   Ú_temp_entry_owner_aliver.   U   sM   € ð ”CÔ.Ó/Ð0Ð1×7Ñ7¸¸QÓ?ÀÑB€HØ×ÑÔØÜ×ÑœS ›]Ó+Ð+r#   Útemp_dirc                 ó”  — | xs t        t        j                  «       «      }t        j                  «       t        z
  }d}	 t        |j                  t        › d�«      «      }|D ]˜  }	 |j                  «       rŒt        |j                  «      r|j                  «       j                  |k\  rŒH|j!                  «       rt#        j$                  |«       n|j'                  «        |dz  }t        j                  d|«       Œš |S # t        $ r t        j                  d|«       Y yw xY w# t        $ r t        j                  d|«       Y Œãw xY w)ao  Remove sync-back tars and staging dirs left behind by a hard-killed process.

    Only entries carrying this module's prefix are touched: at once when their owner PID
    is dead, otherwise only past ``_SYNC_BACK_STALE_SECONDS``. Returns the number of
    entries removed; a permission error or a race with another sync-back must not prevent
    the current one.
    r   Ú*z0sync_back: could not scan temporary directory %sr'   z+sync_back: removed stale temporary entry %sz4sync_back: could not remove stale temporary entry %s)r   ÚtempfileÚ
gettempdirÚtimeÚ_SYNC_BACK_STALE_SECONDSÚlistÚglobr   ÚOSErrorr   ÚdebugÚ
is_symlinkr.   r%   ÚlstatÚst_mtimeÚis_dirÚshutilÚrmtreeÚunlink)r/   Ú	directoryÚcutoffÚremovedÚ
candidatesÚ	candidates         r   Ú_cleanup_stale_sync_back_temprF   ^   s'  € ð Ò7œD¤×!4Ñ!4Ó!6Ó7€IÜ�Y‰Y‹[Ô3Ñ3€FØ€GðÜ˜)Ÿ.™.Ô,BÐ+CÀ1Ð)EÓFÓGˆ
ð  ò \ˆ	ð	\Ø×#Ñ#Ô%ØÜ& y§~¡~Ô6¸9¿?¹?Ó;L×;UÑ;UÐY_Ò;_ØØ×ÑÔ!Ü—‘˜iÕ(à× Ñ Ô"Ø�q‰LˆGÜ�L‰LÐFÈ	ÕRð\ð €Nøô# ò Ü�‰ÐGÈÔSÙðûô ò 	\Ü�L‰LÐOÐQZÖ[ð	\ús1   Á !D  Á'D%Á82D%Â+AD%Ä D"Ä!D"Ä%EÅEÚcontainer_basec           	      óä   — ddl m}m}m}  |«       D �cg c]  }|d   |d   j	                  d| d«      f‘Œ }}|g  || ¬«      ¢ || ¬«      ¢­D �cg c]  }|d   |d   f‘Œ c}z  }|S c c}w c c}w )z²Enumerate all (host_path, remote_path) pairs to sync to a remote. Credential paths are
    remapped from the hardcoded /root/.hermes to *container_base* (remote home may differ).r   )Úget_credential_file_mountsÚiter_cache_filesÚiter_skills_filesÚ	host_pathÚcontainer_pathú/root/.hermesr'   )rG   )Útools.credential_filesrI   rJ   rK   Úreplace)rG   rI   rJ   rK   ÚentryÚfiless         r   Úiter_sync_filesrS      s³   € ÷ gÑfñ 0Ó1ö3àð 
ˆ{Ñ	˜UÐ#3Ñ4×<Ñ<¸_ÈnÐ^_Ó`Òað3€Eð 3ð 
ðHÑ(¸ÔGð HÙ'°~ÔFñHöIàð 
ˆ{Ñ	˜UÐ#3Ñ4Ò5òIñ I€Eð €Lùò3ùòIs   ”"A(ÁA-rL   c                 óÆ   — 	 t        t        | «      j                  «       j                  «       «      S # t        $ r% t        t        | «      j                  «       «      cY S w xY w)zVCanonical string form of a host path (``resolve()`` falling back to ``expanduser()``).)Ústrr   Ú
expanduserÚresolver8   )rL   s    r   Ú_resolve_host_path_strrX   �   sO   € ð1Ü”4˜	“?×-Ñ-Ó/×7Ñ7Ó9Ó:Ð:øÜò 1Ü”4˜	“?×-Ñ-Ó/Ó0Ò0ð1ús   ‚/2 ²+A ÁA c                  óÔ   — 	 ddl m}   | «       }|D �ch c]1  }t	        |t
        «      r|j                  d«      rt        |d   «      ’Œ3 c}S # t        $ r t        «       cY S w xY wc c}w )zBReturn credential files that are upload-only for remote sandboxes.r   )rI   rL   )rO   rI   Ú	ExceptionÚsetÚ
isinstanceÚdictr   rX   )rI   ÚmountsrQ   s      r   Ú_credential_host_pathsr_   —   sm   € ðÝEÙ+Ó-ˆð
 öSàœz¨%´Ô6¸5¿9¹9À[Ô;Qô 	˜u [Ñ1Õ2òSð Søô ò Ü‹uŠðüòSs   ‚A “6A%ÁA"Á!A"Úremote_pathsc                 ó8   — ddj                  d„ | D «       «      z   S )z<Build a shell ``rm -f`` command for a batch of remote paths.zrm -f Ú c              3   óF   K  — | ]  }t        j                  |«      –— Œ y ­w©N©ÚshlexÚquote)Ú.0Úps     r   ú	<genexpr>z$quoted_rm_command.<locals>.<genexpr>¥   s   è ø€ ÒD°!œuŸ{™{¨1Ÿ~ÑDùó   ‚!©Újoin)r`   s    r   Úquoted_rm_commandrn   £   s   € à�c—h‘hÑD°|ÔDÓDÑDÐDr#   Údirsc                 ó8   — ddj                  d„ | D «       «      z   S )z>Build a shell ``mkdir -p`` command for a batch of directories.z	mkdir -p rb   c              3   óF   K  — | ]  }t        j                  |«      –— Œ y ­wrd   re   )rh   Úds     r   rj   z'quoted_mkdir_command.<locals>.<genexpr>ª   s   è ø€ Ò!?°Q¤%§+¡+¨a§.Ñ!?ùrk   rl   )ro   s    r   Úquoted_mkdir_commandrs   ¨   s   € à˜Ÿ™Ñ!?¸$Ô!?Ó?Ñ?Ð?r#   rR   c           	      óp   — t        | D ��ch c]  \  }}t        j                  |«      ’Œ c}}«      S c c}}w )zCExtract sorted unique parent directories from (host, remote) pairs.)ÚsortedÚ	posixpathÚdirname)rR   Ú_Úremotes      r   Úunique_parent_dirsrz   ­   s*   € ä¸e×D±°°F”9×$Ñ$ VÕ,ÓDÓEÐEùÓDs   ‹2
Úpathc                 óø   ‡— t        j                  «       }t        | d«      5 Št        ˆfd„d«      D ]  }|j	                  |«       Œ 	 ddd«       |j                  «       S # 1 sw Y   |j                  «       S xY w)z$Return hex SHA-256 digest of a file.Úrbc                  ó&   •— ‰ j                  d«      S )Ni   )Úread)Úfs   €r   ú<lambda>z_sha256_file.<locals>.<lambda>¶   s   ø€  !§&¡&¨£-€ r#   r#   N)ÚhashlibÚsha256ÚopenÚiterÚupdateÚ	hexdigest)r{   ÚhÚchunkr€   s      @r   Ú_sha256_filerŠ   ²   si   ø€ ä�‰Ó€AÜ	ˆd�DÓ	ð ˜QÜÓ/°Ó5ò 	ˆEØ�H‰H�U�Oñ	÷ð �;‰;‹=Ð÷ð �;‰;‹=Ðús   ¢&A!Á!A9c                   ó  — e Zd ZdZeddfdedededede	dz  de
dz  fd	„Zd
dœdeddfd„Zd
dœdeddfd„Zdeeeef      deeeeef      eeeeef   f   ee   f   fd„Zdeeeef      dee   ddfd„Zd&dedz  ddfd„Zd&dedz  ddfd„Zdeddfd„Zdeddfd„Zd'd„Zdededeeeef      dee   def
d „Zd&dedeeeef      dz  dedz  fd!„Zd&dd"œdedeeeef      dz  dee   dz  dedz  fd#„Ze d$edee   defd%„«       Z!y)(ÚFileSyncManagera  Tracks local file changes and syncs to a remote environment. Backends supply transport
    callbacks (upload, delete) and a file-source callable; the manager handles mtime-based
    change detection, deletion tracking, rate limiting, and transactional state.NÚget_files_fnÚ	upload_fnÚ	delete_fnÚsync_intervalÚbulk_upload_fnÚbulk_download_fnc                 óÒ   — || _         || _        || _        || _        || _        t        j                  «       | _        i | _        i | _	        t        «       | _        d| _        || _        y )Ng        )Ú_get_files_fnÚ
_upload_fnÚ_bulk_upload_fnÚ_bulk_download_fnÚ
_delete_fnÚ	threadingÚLockÚ_transaction_lockÚ_synced_filesÚ_pushed_hashesr[   Ú_upload_only_host_pathsÚ_last_sync_timeÚ_sync_interval)Úselfr�   rŽ   r�   r�   r‘   r’   s          r   Ú__init__zFileSyncManager.__init__À   sc   € ð *ˆÔØ#ˆŒØ-ˆÔØ!1ˆÔØ#ˆŒÜ!*§¡Ó!1ˆÔØ;=ˆÔØ.0ˆÔÜ14³ˆÔ$Ø&)ˆÔØ+ˆÕr#   F©Úforcer¤   r   c                ój   — | j                   5  | j                  |¬«       ddd«       y# 1 sw Y   yxY w)a1  Run a sync cycle: upload changed files, delete removed files. Rate-limited to once
        per ``sync_interval`` unless *force* or ``HERMES_FORCE_FILE_SYNC=1``. Transactional:
        state is committed only if ALL operations succeed; on failure it rolls back so the
        next cycle retries everything.r£   N)r›   Ú_sync_transaction)r¡   r¤   s     r   ÚsynczFileSyncManager.syncÔ   s3   € ð
 ×#Ñ#ñ 	0Ø×"Ñ"¨Ð"Ô/÷	0÷ 	0ñ 	0úó   �)©2c                óT  — |sHt         j                  j                  t        «      s%t	        «       | j
                  z
  | j                  k  ry| j                  «       }| j                  j                  t        «       «       | j                  |«      \  }}}|s|st	        «       | _        yt        | j                  «      }t        | j                  «      }	 t        j                   d¬«      5 }g }	i }
t#        |«      D ]h  \  }\  }}t         j$                  j'                  |t)        |«      «      }t+        j,                  ||«       t/        |«      |
|<   |	j1                  ||f«       Œj | j3                  |	|«       ddd«       | j                  j                  
«       |D ]0  }|j5                  |d«       | j                  j5                  |d«       Œ2 || _        t	        «       | _        y# 1 sw Y   ŒpxY w# t6        $ r.}|| _        || _        t8        j;                  d|«       Y d}~yd}~ww xY w)z:Execute one sync cycle while holding the per-manager lock.Nzhermes-sync-push-©Úprefixz-file_sync: sync failed, rolled back state: %s)r    Úenvironr   Ú_FORCE_SYNC_ENVÚ
_monotonicrŸ   r    r”   rž   r†   r_   Ú
_plan_syncr]   rœ   r�   r2   ÚTemporaryDirectoryÚ	enumerater{   rm   rU   r>   Úcopy2rŠ   ÚappendÚ_pushÚpoprZ   r   r   )r¡   r¤   Úcurrent_filesÚ	to_uploadÚ	new_filesÚ	to_deleteÚ
prev_filesÚprev_hashesÚstagingÚstaged_filesÚpushed_hashesÚindexrL   Úremote_pathÚstaged_pathri   Úexcs                    r   r¦   z!FileSyncManager._sync_transactionÜ   sæ  € ñ Ü—J‘J—N‘N¤?Ô3Ü“˜t×3Ñ3Ñ3°d×6IÑ6IÒIØà×*Ñ*Ó,ˆØ×$Ñ$×+Ñ+Ô,BÓ,DÔEØ*.¯/©/¸-Ó*HÑ'ˆ	�9˜iá¡Ü#-£<ˆDÔ Øä˜$×,Ñ,Ó-ˆ
Ü˜4×.Ñ.Ó/ˆð	Qô ×,Ñ,Ð4GÔHð 4ÈGØ!�Ø "�Ü7@ÀÓ7Kò DÑ3�EÑ3˜I {Ü"$§'¡'§,¡,¨w¼¸E»
Ó"C�KÜ—L‘L ¨KÔ8Ü1=¸kÓ1J�M +Ñ.Ø ×'Ñ'¨°kÐ(BÕCð	Dð
 —
‘
˜<¨Ô3÷4ð ×Ñ×&Ñ& }Ô5Øò 1�Ø—‘˜a Ô&Ø×#Ñ#×'Ñ'¨¨4Õ0ð1ð "+ˆDÔÜ#-£<ˆDÕ ÷4ð 4ûô  ò 	QØ!+ˆDÔØ"-ˆDÔô �N‰NÐJÈC×PÑPûð	Qús2   ÃG0 Ã(BG$Å5A.G0 Ç$G-Ç)G0 Ç0	H'Ç9$H"È"H'r¶   c                 óP  — g }t        | j                  «      }|D ]I  \  }}t        |«      }|�| j                  j                  |«      |k(  rŒ2|j	                  ||f«       |||<   ŒK |D ��ch c]  \  }}|’Œ	 }	}}| j                  D �
cg c]	  }
|
|	vsŒ|
‘Œ }}
|||fS c c}}w c c}
w )zZDiff *current_files* against synced state -> ``(to_upload, new_synced_state, to_delete)``.)r]   rœ   r   r   r³   )r¡   r¶   r·   r¸   rL   rÀ   Úfile_keyrx   ry   Úcurrent_remote_pathsri   r¹   s               r   r¯   zFileSyncManager._plan_sync  sÇ   € ð ,.ˆ	Ü˜×+Ñ+Ó,ˆ	Ø&3ò 	.Ñ"ˆI�{Ü& yÓ1ˆHØÐ 4×#5Ñ#5×#9Ñ#9¸+Ó#FÈ(Ò#RØØ×Ñ˜i¨Ð5Ô6Ø%-ˆI�kÒ"ð	.ð 9F×F©9¨1¨f¢ÐFÐÑFØ $× 2Ñ 2ÖT˜1°aÐ?SÒ6S’QÐTˆ	ÐTØ˜) YÐ.Ð.ùó  GùÚTs   Á+BÂ	B#ÂB#r·   r¹   c                 óÀ  — |rt         j                  dt        |«      «       |rt         j                  dt        |«      «       |r=| j                  �1| j                  |«       t         j                  dt        |«      «       n3|D ].  \  }}| j	                  ||«       t         j                  d||«       Œ0 |r(| j                  |«       t         j                  d|«       yy)zCRun the transport calls for one cycle (bulk upload when available).zfile_sync: uploading %d file(s)z+file_sync: deleting %d stale remote file(s)Nz#file_sync: bulk-uploaded %d file(s)zfile_sync: uploaded %s -> %szfile_sync: deleted %s)r   r9   r(   r–   r•   r˜   )r¡   r·   r¹   rL   rÀ   s        r   r´   zFileSyncManager._push  s·   € áÜ�L‰LÐ:¼CÀ	»NÔKÙÜ�L‰LÐFÌÈIËÔWÙ˜×-Ñ-Ð9Ø× Ñ  Ô+Ü�L‰LÐ>ÄÀIÃÕOà*3ò UÑ&�	˜;Ø—‘ 	¨;Ô7Ü—‘Ð;¸YÈÕTðUñ Ø�O‰O˜IÔ&Ü�L‰LÐ0°)Õ<ð r#   Úhermes_homec                 ój   — | j                   5  | j                  |¬«       ddd«       y# 1 sw Y   yxY w)a   Pull remote changes back to the host: download the remote ``.hermes/`` as a tar and
        apply only files whose SHA-256 differs from what was pushed. SIGINT is deferred until
        complete; concurrent gateway sandboxes are serialized via a file lock.)rÇ   N)r›   Ú_sync_back_transaction)r¡   rÇ   s     r   Ú	sync_backzFileSyncManager.sync_back*  s7   € ð ×#Ñ#ñ 	AØ×'Ñ'°KÐ'Ô@÷	A÷ 	Añ 	Aúr¨   c                 ó  — | j                   €y| j                  s"| j                  st        j	                  d«       y|xs
 t        «       dz  }|j                  j                  dd¬«       d}t        t        «      D ]  }	 | j                  |«        y t        j                  dt        |«       y# t        $ rG}|}|t        dz
  k  r/t        |   }t        j                  d|dz   ||«       t        |«       Y d}~Œd}~ww xY w)	zLExecute sync-back (with retries) against a stable snapshot of manager state.Nu+   sync_back: no prior push state â€” skippingz
.sync.lockT)ÚparentsÚexist_okr'   z2sync_back: attempt %d failed (%s), retrying in %dsz%sync_back: all %d attempts failed: %s)r—   r�   rœ   r   r9   r   ÚparentÚmkdirÚrangeÚ_SYNC_BACK_MAX_RETRIESÚ_sync_back_oncerZ   Ú_SYNC_BACK_BACKOFFr   Ú_sleep)r¡   rÇ   Ú	lock_pathÚlast_excÚattemptrÂ   Údelays          r   rÉ   z&FileSyncManager._sync_back_transaction1  s÷   € à×!Ñ!Ð)Øð ×"Ò"¨4×+=Ò+=Ü�L‰LÐFÔGØà Ò5¤OÓ$5¸ÑEˆ	Ø×Ñ×Ñ t°dÐÔ;à%)ˆÜÔ3Ó4ò 		"ˆGð"Ø×$Ñ$ YÔ/Ùð		"ô 	�‰Ð>Ô@VÐX`Õaøô ò "Ø�ØÔ3°aÑ7Ò7Ü.¨wÑ7�EÜ—N‘NÐ#WÐY`ÐcdÑYdÐfiÐkpÔqÜ˜5”Mÿøð"ús   Á?B/Â/	C?Â8=C:Ã:C?rÕ   c                 óh  ‡— t        j                  «       t        j                  «       u }g Šd}|rLt        j                  t        j
                  «      }ˆfd„}t        j                  t        j
                  |«       	 | j                  |«       |rN|�Kt        j                  t        j
                  |«       ‰r$t        j                  t        j
                  «       yyyy# |rN|�Kt        j                  t        j
                  |«       ‰r$t        j                  t        j
                  «       w w w w xY w)z>Single sync-back attempt with SIGINT protection and file lock.Nc                 óV   •— ‰j                  | |f«       t        j                  d«       y )Nz/sync_back: SIGINT deferred until sync completes)r³   r   r9   )ÚsignumÚframeÚdeferred_sigints     €r   Ú_defer_sigintz6FileSyncManager._sync_back_once.<locals>._defer_sigintX  s"   ø€ Ø×&Ñ&¨° Ô7Ü—‘ÐNÕOr#   )r™   Úcurrent_threadÚmain_threadÚsignalÚ	getsignalÚSIGINTÚ_sync_back_lockedÚraise_signal)r¡   rÕ   Úon_main_threadÚoriginal_handlerrÞ   rÝ   s        @r   rÒ   zFileSyncManager._sync_back_onceM  sð   ø€ ô #×1Ñ1Ó3´y×7LÑ7LÓ7NÐNˆà(*ˆØÐÙÜ%×/Ñ/´·±Ó>ÐôPô �M‰Mœ&Ÿ-™-¨Ô7ð		7Ø×"Ñ" 9Ô-áÐ"2Ð">Ü—‘œfŸm™mÐ-=Ô>Ù"ô ×'Ñ'¬¯©Õ6ð	 #ð #?ˆ~ø‰~Ð"2Ð">Ü—‘œfŸm™mÐ-=Ô>Ù"ô ×'Ñ'¬¯©Õ6ð	 #ð #?ˆ~ús   Á=C ÃAD1c                 óì  — t         €| j                  «        yt        |dd¬«      }	 t        j                  |t         j                  «       | j                  «        	 t        j                  |t         j
                  «       |j                  «        y# t        t        f$ r Y Œ"w xY w# 	 t        j                  |t         j
                  «       n# t        t        f$ r Y nw xY w|j                  «        w xY w)z;Sync-back under file lock (serializes concurrent gateways).NÚwzutf-8)Úencoding)	ÚfcntlÚ_sync_back_implr„   ÚflockÚLOCK_EXÚLOCK_UNr8   ÚIOErrorÚclose)r¡   rÕ   Úlock_fds      r   rä   z!FileSyncManager._sync_back_lockedh  sº   € äˆ=à× Ñ Ô"ØÜ�y #°Ô8ˆð	Ü�K‰K˜¤§¡Ô/Ø× Ñ Ô"ðÜ—‘˜G¤U§]¡]Ô3ð �M‰M�Oøô œWÐ%ò ÙðûðÜ—‘˜G¤U§]¡]Õ3øÜœWÐ%ò Ùðúà�M‰M�OúsA   §4B& Á$B ÂB#Â"B#Â&C3Â($CÃC3ÃCÃC3ÃCÃC3c           
      ó„  — | j                   €t        d«      ‚	 t        | j                  «       «      }t        «        t        j                  t        «       d¬«      \  }}t        j                  |«       	 | j                  t        |«      «       	 t        j                  j                  |«      }t        «       }||kD  r4t         j#                  d||t$        «       	 	 t        j&                  |«       yt        j(                  t        «       ¬«      5 }t+        j,                  |«      5 }|j/                  |d¬	«       ddd«       | j0                  t3        «       z  }d}	t        j4                  |«      D ]~  \  }
}}|D ]s  }t        j                  j7                  |
|«      }d
t        t        j                  j9                  ||«      «      j;                  «       z   }|	| j=                  ||||«      z  }	Œu Œ€ |	rt         j?                  d|	«       nt         jA                  d«       ddd«       	 t        j&                  |«       y# t        $ r g }Y �Œw xY w# t        $ r d}Y �Œ¯w xY w# t        $ r Y yw xY w# 1 sw Y   �Œ9xY w# 1 sw Y   Œ`xY w# t        $ r Y yw xY w# 	 t        j&                  |«       w # t        $ r Y w w xY wxY w)z1Download, diff, and apply remote changes to host.Nz/_sync_back_impl called without bulk_download_fnz.tar)r«   Úsuffixr   u]   sync_back: remote tar is %d bytes (cap %d, override with terminal.%s) â€” skipping extractionrª   Údata)Úfilterú/z%sync_back: applied %d changed file(s)z%sync_back: no remote changes detected)!r—   ÚRuntimeErrorr6   r”   rZ   rF   r2   Úmkstempr$   r    rñ   r   r{   Úgetsizer8   r   r   r   r   r@   r°   Útarfiler„   Ú
extractallrž   r_   Úwalkrm   ÚrelpathÚas_posixÚ_apply_staged_fileÚinfor9   )r¡   Úfile_mappingÚfdÚtar_pathÚtar_sizeÚ	max_bytesr¼   ÚtarÚupload_onlyÚappliedÚdirpathÚ	_dirnamesÚ	filenamesÚfnameÚstaged_filerÀ   s                   r   rì   zFileSyncManager._sync_back_imply  s  € à×!Ñ!Ð)ÜÐPÓQÐQð	Ü × 2Ñ 2Ó 4Ó5ˆLô 	&Ô'ô  ×'Ñ'Ô/EÓ/GÐPVÔW‰ˆˆHÜ
�‰�Œð$	Ø×"Ñ"¤4¨£>Ô2ðÜŸ7™7Ÿ?™?¨8Ó4�ô -Ó.ˆIØ˜)Ò#Ü—‘ØsØ˜iÔ)AôCð ð(Ü—	‘	˜(Õ#ô' ×,Ñ,Ô4JÓ4LÔMð JÐQXÜ—\‘\ (Ó+ð ;¨sØ—N‘N 7°6�NÔ:÷;ð #×:Ñ:Ô=SÓ=UÑU�Ø�Ü57·W±W¸WÓ5Eò pÑ1�G˜Y¨	Ø!*ò p˜Ü&(§g¡g§l¡l°7¸EÓ&B˜à&)¬D´·±·±ÀÈgÓ1VÓ,W×,`Ñ,`Ó,bÑ&b˜Ø 4×#:Ñ#:¸;ÈÐUaÐcnÓ#oÑo™ñ	pðpñ Ü—K‘KÐ GÈÕQä—L‘LÐ!HÔI÷!Jð$Ü—	‘	˜(Õ#øô[ ò 	Ø‹Lð	ûô" ò Ø“ðûô: ò Ùðú÷';ñ ;ú÷Jð Jûô( ò ÙðûðÜ—	‘	˜(Õ#øÜò ÙðýsÀ   ™H= Á4J ÂI Â.+J ÃI! Ã1J ÄI=Ä%I0Ä9C%I=ÈJ È'J	 È=IÉIÉIÉJ ÉIÉJ É!	I-É,I-É0I:	É5I=É=JÊJ Ê		JÊJÊJ?ÊJ0Ê/J?Ê0	J<Ê9J?Ê;J<Ê<J?r  rÀ   r  Úupload_only_host_pathsc                 óN  — | j                   j                  |«      }|�t        |«      |k(  ry| j                  ||«      }|€-| j	                  |||¬«      }|€t
        j                  d|«       y| j                  ||«      rt
        j                  d|«       y|�Ct        j                  j                  |«      r$t        |«      |k7  rt
        j                  d|«       t        j                  t        j                  j                  |«      d¬«       t        j                  ||«       y)	a  Copy one extracted remote file onto the host if it changed since push. Returns 1 if
        applied, 0 if skipped (unchanged, unmapped, or an upload-only credential). A host file
        modified since push is overwritten with the remote version (last-write-wins) with a warning.r   ©r  z(sync_back: skipping %s (no host mapping)z2sync_back: skipping upload-only credential file %suw   sync_back: conflict on %s â€” host modified since push, remote also changed. Applying remote version (last-write-wins).T)rÍ   r'   )r�   r   rŠ   Ú_resolve_host_pathÚ_infer_host_pathr   r9   Ú_is_upload_only_host_pathr    r{   Úexistsr   Úmakedirsrw   r>   r²   )r¡   r  rÀ   r  r  Úpushed_hashrL   s          r   r   z"FileSyncManager._apply_staged_file²  s   € ð ×)Ñ)×-Ñ-¨kÓ:ˆØÐ"¤|°KÓ'@ÀKÒ'OØà×+Ñ+¨K¸ÓFˆ	ØÐØ×-Ñ-¨k¸<Ð`vÐ-ÓwˆIØÐ Ü—‘ÐGÈÔUØà×)Ñ)¨)Ð5KÔLÜ�L‰LÐMÈ{Ô[ØàÐ"¤r§w¡w§~¡~°iÔ'@Ä\ÐR[ÓE\Ð`kÒEkÜ�N‰Nð^àôô
 	�‰”B—G‘G—O‘O IÓ.¸Õ>Ü�‰�[ )Ô,Ør#   c                 ó6   ‡— t        ˆfd„|xs g D «       d«      S )zAFind the host path for a known remote path from the file mapping.c              3   ó4   •K  — | ]  \  }}|‰k(  sŒ|–— Œ y ­wrd   r"   )rh   Úhostry   rÀ   s      €r   rj   z5FileSyncManager._resolve_host_path.<locals>.<genexpr>Ó  s   øè ø€ ÒZ™l˜d FÀFÈkÓDY”TÑZùs   ƒ‘N)Únext)r¡   rÀ   r  s    ` r   r  z"FileSyncManager._resolve_host_pathÑ  s   ø€ äÓZ¨lÒ.@¸bÔZÐ\`ÓaÐar#   r  c                ó  — |xs
 t        «       }|xs g D ]s  \  }}| j                  ||«      rŒt        j                  |«      }|j	                  |dz   «      sŒCt        t        |«      j                  |t        |«      dz   d z  «      c S  y)z÷Infer a host path for a new remote file by matching path prefixes: an existing
        remote->host pair whose parent directory prefixes *remote_path* gets the same
        substitution (``/root/.hermes/skills/b.md`` -> ``~/.hermes/skills/b.md``).r÷   r'   N)	r[   r  rv   rw   Ú
startswithrU   r   rÎ   r(   )r¡   rÀ   r  r  r  ry   Ú
remote_dirs          r   r  z FileSyncManager._infer_host_pathÕ  s’   € ð
 "8Ò!@¼3»5ÐØ(Ò.¨Bò 	R‰LˆD�&Ø×-Ñ-¨dÐ4JÔKØÜ"×*Ñ*¨6Ó2ˆJØ×%Ñ% j°3Ñ&6Õ7Üœ4 ›:×,Ñ,¨{¼3¸z»?ÈQÑ;NÐ;OÐ/PÑPÓQÒQð	Rð r#   rL   c                 ó   — t        | «      |v S rd   )rX   )rL   r  s     r   r  z)FileSyncManager._is_upload_only_host_pathã  s   € ä% iÓ0Ð4JÐJÐJr#   rd   )r   N)"Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ú_SYNC_INTERVAL_SECONDSÚ
GetFilesFnÚUploadFnÚDeleteFnÚfloatÚBulkUploadFnÚBulkDownloadFnr¢   Úboolr§   r¦   r6   ÚtuplerU   r]   r   r¯   r´   r   rÊ   rÉ   rÒ   rä   rì   r[   r   r  r  Ústaticmethodr  r"   r#   r   rŒ   rŒ   »   se  „ ñTð  6Ø.2Ø26ñ,à ð,ð ð,ð ð	,ð
 ð,ð % tÑ+ð,ð )¨4Ñ/ó,ð( %*ò 0˜Tð 0¨dó 0ð 27ò *Q¨$ð *Q¸4ó *QðX/Ø! %¨¨S¨¡/Ñ2ð/à	ˆt�E˜#˜s˜(‘OÑ$ d¨3°°e¸S°jÑ0AÐ+AÑ&BÀDÈÁIÐMÑ	Nó/ð =˜t E¨#¨s¨(¡OÑ4ð =ÀÀcÁð =Ètó =ñ$A T¨D¡[ð A¸Dó Añb°$¸±+ð bÈó bð87¨ð 7°$ó 7ð6¨4ð °Dó ó"7ðrØðØ-0ðØ@DÀUÈ3ÐPSÈ8Á_Ñ@UðØorÐsvÑowðà	óñ>b¨cð bÀÀeÈCÐQTÈHÁoÑAVÐY]ÑA]ð bÐilÐosÑisó bðØCGò¨Cð ¸tÀEÈ#ÈsÈ(ÁOÑ?TÐW[Ñ?[ð Ø14°S±¸D±ðØLOÐRVÉJóð ðK¨Sð KÈ#ÈcÉ(ð KÐW[ò Kó ñKr#   rŒ   rd   )rN   )=r#  r‚   Úloggingr    rv   rf   r>   rá   rû   r2   r™   r4   rë   ÚImportErrorÚpathlibr   Útypingr   r+   Úhermes_constantsr   Útools.environments.baser   Ú	getLoggerr   r   ÚsleeprÔ   Ú	monotonicr®   r$  r­   rU   r&  r6   r,  r)  r*  r'  r%  rÑ   rÓ   r   r   r   r5   r   r   r$   r+  r.   rF   rS   rX   r[   r_   rn   rs   rz   rŠ   rŒ   r"   r#   r   ú<module>r7     s-  ðñó Û Û 	Û Û Û Û Û Û Û Û ðÛõ Ý ã å ,Ý 3à	ˆ×	Ñ	˜8Ó	$€ð
 
�‰€Ø�^‰^€
àÐ Ø*€ð �S˜#�J Ð$Ñ%€Ø˜˜e C¨ H™oÑ.Ð/°Ð5Ñ6€Ø˜4˜& $˜,Ñ'€Ø�T˜#‘Y�K Ð%Ñ&€Ø�b˜$˜u S¨# X™Ñ/Ð/Ñ0€
àÐ ØÐ Ø-Ð Ø0Ð Ø,Ð ð #Ð ð ˜có  ð5 ó 5ð, #ð ,¨$ó ,ñ¨D°4©Kð À3ó ñB Cð ¸dÀ5ÈÈcÈÁ?Ñ>Só ð 1 cð 1¨có 1ð	S  C¡ó 	SðE D¨¡Ið E°#ó Eð
@˜t C™yð @¨Só @ð
F˜d 5¨¨c¨¡?Ñ3ð F¸¸S¹	ó Fð
�sð ˜só ÷jKò jKøðK ò ØƒEðús   °E ÅEÅE