Ë
    ãß²jÌ4  ã                   óä  — 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ZddlmZ ddl	m
Z
mZmZ ddlmZ ddlmZmZ ddlmZ  ej(                  d«      Zd	d
dœZeed<   dZd„ Zd„ Zddœdeee
f   dedededededee   dee   fd„Zddœde
dee   ddfd„Z ddd œde
dedee   d!eddf
d"„Z!deddfd#„Z"defd$„Z#d-d%efd&„Z$defd'„Z%d.dee   fd(„Z&dedefd)„Z'd*„ Z(d
dœdedefd+„Z)dee   ddfd,„Z*y)/a¤  Sandbox lifecycle for the terminal tool: idle reaping, teardown, manual/atexit
cleanup, and the lazy ensure_task_env bring-up. The env cache dicts and locks
stay in tools.terminal_tool (tests patch them there) and are read through it
at call time.

Split out of ``tools/terminal_tool.py``; every public/patched name is re-imported there,
so ``tools.terminal_tool.<name>`` keeps resolving (and monkeypatching) as before.
é    N)ÚPath)ÚAnyÚDictÚOptional)Ú_get_scratch_dir)Ú_container_config_from_configÚ_ssh_config_from_config)Ú_quietztools.terminal_toolg        F)Ú	timestampÚresultÚ_disk_usage_cacheg     Àr@c                  óT   — t        j                   t        t        «       dz  «      «      S )Nzhermes-*)ÚglobÚstrr   © ó    úB/home/ubuntu/.hermes/hermes-agent/tools/terminal_tool_lifecycle.pyÚ_scratch_pathsr   $   s   € Ü�9‰9”SÔ)Ó+¨jÑ8Ó9Ó:Ð:r   c                  óT  — ddl m}  t        j                  «       t        d   z
  t
        k  r	t        d   S 	 d}t        «       D ]j  }t        |«      j                  d«      D ]K  }|j                  «       sŒt        d|t        ¬«      5  ||j                  «       j                  z  }ddd«       ŒM Œl |d	z  }|| kD  }|rt        j                  d
|| «       t        j                  «       t        d<   |t        d<   |S # 1 sw Y   Œ¢xY w# t         $ r t        j#                  dd¬«       Y yw xY w)zNTrue when hermes scratch dirs exceed the warning threshold (cached, advisory).r   )ÚDISK_USAGE_WARNING_THRESHOLD_GBr   r   Ú*zCould not stat file %s©ÚexcNi   @z\Disk usage (%.1fGB) exceeds threshold (%.0fGB). Consider running cleanup_all_environments().zDisk usage warning check failedT©Úexc_infoF)Útools.terminal_toolr   ÚtimeÚ	monotonicr   Ú_DISK_USAGE_CACHE_TTLr   r   ÚrglobÚis_filer
   ÚOSErrorÚstatÚst_sizeÚloggerÚwarningÚ	ExceptionÚdebug)r   Útotal_bytesÚpathÚfÚtotal_gbÚexceededs         r   Ú_check_disk_usage_warningr.   (   s!  € åCÜ‡~�~ÓÔ+¨KÑ8Ñ8Ô;PÒPÜ  Ñ*Ð*ðØˆÜ"Ó$ò 	8ˆDÜ˜$“Z×%Ñ% cÓ*ò 8�Ø—9‘9•;ÜÐ 8¸!ÄÔIñ 8Ø# q§v¡v£x×'7Ñ'7Ñ7˜÷8ð 8ñ8ð	8ð
  )Ñ,ˆØÐ=Ñ=ˆÙÜ�N‰NÐyØ#Ð%DôFä)-¯©Ó)9Ô˜+Ñ&Ø&.Ô˜(Ñ#Øˆ÷8ð 8ûô ò ä�‰Ð6ÀˆÔFÙðús0   ¶<D Á3D ÂC8Â#AD Ã8DÃ=D Ä D'Ä&D')Úlocal_configÚconfigÚenv_typeÚimageÚcwdÚtimeoutÚtask_idÚhost_cwdr/   c                ó‚   — ddl m} ddlm}	  ||||||dk(  rt	        | «      nd |	|«      rt        | «      nd|||¬«	      S )z›``_create_environment`` with the ssh/container kwargs shaped from *config*
    (shared by the terminal tool and the lazy :func:`ensure_task_env` bring-up).r   )Ú_create_environment)Ú_is_container_backendÚsshN)	r1   r2   r3   r4   Ú
ssh_configÚcontainer_configr/   r5   r6   )Útools.terminal_tool_backendsr8   Útools.terminal_tool_configr9   r	   r   )
r0   r1   r2   r3   r4   r5   r6   r/   r8   r9   s
             r   Ú_create_configured_envr?   B   sM   € õ AÝ@ÙØ ¨C¸Ø6>À%Ò6GÔ*¨6Ô2ÈTá5JÈ8Ô5TÔ)¨&Ô1ÐZ^à!¨7¸Xôð r   ©Úforce_removeÚenvrA   Úreturnc                ó2  — t        | d«      rQ|�>dt        j                  | j                  «      j                  v r| j                  |¬«       y| j                  «        yt        | d«      r| j                  «        yt        | d«      r| j                  «        yy)a}  Tear down one environment via cleanup()/stop()/terminate(), whichever it has.

    ``force_remove`` is forwarded to ``cleanup()`` only when given and the backend's
    signature accepts it (``DockerEnvironment``, issue #20561; other backends don't).
    Shared by ``cleanup_vm``, the idle reaper and the prompt-time backend probe so
    the signature check lives in one place.
    ÚcleanupNrA   r@   ÚstopÚ	terminate)ÚhasattrÚinspectÚ	signaturerE   Ú
parametersrF   rG   )rB   rA   s     r   Ú_cleanup_envrL   T   sr   € ô ˆs�IÔØÐ#¨¼'×:KÑ:KÈCÏKÉKÓ:X×:cÑ:cÑ(cØ�K‰K \ˆKÕ2à�K‰K�MÜ	��fÔ	Ø�‰�
Ü	��kÔ	"Ø�‰�ð 
#r   z,Cleaned up inactive environment for task: %s©rA   Údone_msgrN   c                ó   — 	 t        | |¬«       t        j                  ||«       y# t        $ r^}t	        |«      }d|v sd|j                  «       v rt        j                  d|«       nt        j                  d||«       Y d}~yY d}~yd}~ww xY w)ux   ``_cleanup_env`` plus outcome logging. A 404/"not found" error means the
    sandbox is already gone â€” logged at info.r@   Ú404z	not foundz*Environment for task %s already cleaned upz-Error cleaning up environment for task %s: %sN)rL   r%   Úinfor'   r   Úlowerr&   )rB   r5   rA   rN   ÚeÚ	error_strs         r   Ú_teardown_envrU   g   s{   € ðXÜ�S |Õ4Ü�‰�H˜gÕ&øÜò XÜ˜“Fˆ	Ø�IÑ °	·±Ó0AÑ!AÜ�K‰KÐDÀgÕNä�N‰NÐJÈGÐUV×WÑWô OûðXús   ‚#& ¦	B¯ABÂBc                 ó@   — 	 ddl m}  || «       y# t        $ r Y yw xY w)zZInvalidate the file_ops cache entry so ShellFileOperations can't reference a dead sandbox.r   )Úclear_file_ops_cacheN)Útools.file_toolsrW   ÚImportError)r5   rW   s     r   Ú_clear_file_ops_cacherZ   u   s$   € ðÝ9Ù˜WÕ%øÜò Ùðús   ‚ ‘	œc                 óì   — ddl m}m}m}m}m} |5  |j                  | d«      }|j                  | d«       ddd«       |5  |j                  | d«       ddd«       S # 1 sw Y   Œ(xY w# 1 sw Y   S xY w)uþ   Pop *task_id* from the env cache, activity map and creation locks; return
    the env (or None). Callers run the (slow) teardown OUTSIDE the lock â€”
    Modal/Docker teardown can block 10-15s and would stall every concurrent
    terminal/file tool call.r   ©Ú_active_environmentsÚ_creation_locksÚ_creation_locks_lockÚ	_env_lockÚ_last_activityN)r   r]   r^   r_   r`   ra   Úpop)r5   r]   r^   r_   r`   ra   rB   s          r   Ú_unregister_envrc   ~   sw   € ÷
õ ð 
ñ *Ø"×&Ñ& w°Ó5ˆØ×Ñ˜7 DÔ)÷*ð 
ñ +Ø×Ñ˜G TÔ*÷+à€J÷*ð *ú÷+à€Jús   ‘%AÁ A)ÁA&Á)A3Úlifetime_secondsc           	      óª  — ddl m}m}m}m}m} t        j                  «       }	 ddlm} t        |j                  «       «      D ]  }|j                  |«      sŒ|||<   Œ 	 |5  t        |j                  «       «      D �	�
cg c]  \  }	}
||
z
  | kD  sŒ|	‘Œ }}	}
|D �	cg c]  }	|	|j                  |	d«      f‘Œ }}	|D ]  }	|j                  |	d«       Œ |5  |D ]  }	|j                  |	d«       Œ 	 ddd«       ddd«       D ]  \  }}|€Œ	t        |«       t!        ||«       Œ! y# t        $ r Y ŒÎw xY wc c}
}	w c c}	w # 1 sw Y   ŒPxY w# 1 sw Y   ŒTxY w)zOClean up environments that have been inactive for longer than lifetime_seconds.r   r\   )Úprocess_registryN)r   r]   r^   r_   r`   ra   r   Útools.process_registryrf   ÚlistÚkeysÚhas_active_processesrY   Úitemsrb   rZ   rU   )rd   r]   r^   r_   r`   ra   Úcurrent_timerf   r5   ÚtÚlastÚstaleÚenvs_to_stoprB   s                 r   Ú_cleanup_inactive_envsrq   �   sg  € ÷õ ô —9‘9“;€LðÝ;Ü˜N×/Ñ/Ó1Ó2ò 	7ˆGØ×4Ñ4°WÕ=Ø*6�˜wÒ'ñ	7ð 
ñ -Ü"& ~×';Ñ';Ó'=Ó">×i‘w�q˜$À,ÐQUÑBUÐXhÓBh’ÐiˆÑiØHMÖNÀ1˜Ð0×4Ñ4°Q¸Ó=Ò>ÐNˆÐNØò 	(ˆAØ×Ñ˜q $Õ'ð	(à!ñ 	-Øò -�Ø×#Ñ# A tÕ,ñ-÷	-÷-ð %ò (‰ˆ�Ø‰?Ü! 'Ô*Ü˜#˜wÕ'ñ(øô ò Ùðüó jùÚN÷	-ð 	-ú÷-ð -ús_   ¤3D# ÁD# Á"E	Á?D2ÂD2ÂE	ÂD8Â6E	ÃD=Ã.E	Ä#	D/Ä.D/Ä2E	Ä=E	ÅE	Å	Ec                 óž   — ddl m}m}m}  || «      }|5  |j	                  |«      xs |j	                  | «      cddd«       S # 1 sw Y   yxY w)z9Return the active BaseEnvironment for *task_id*, or None.r   )r]   r`   Ú_resolve_container_task_idN)r   r]   r`   rs   Úget)r5   r]   r`   rs   Úlookups        r   Úget_active_envrv   °   sM   € ç_Ñ_Ù'¨Ó0€FØ	ñ UØ#×'Ñ'¨Ó/ÒTÐ3G×3KÑ3KÈGÓ3T÷U÷ Uò Uús   •$AÁAc                 ó:  — ddl m}m}m}m}m}m}m}m}m	}	m
}
m}  |«       }|d   }|dk(  ry || «      }t        |«      }|�$|5  t        j                  «       ||<   ddd«       |S  |	| || «      |«      } |
«        |5  |j                  |t        j                   «       «      }ddd«       5  t        |«      }|�|cddd«       S 	 t#        ||||d   |d   | ||| «      ¬«      }|5  |||<   t        j                  «       ||<   ddd«       t&        j+                  d||dd
 «       |cddd«       S # 1 sw Y   |S xY w# 1 sw Y   Œ�xY w# t$        $ r-}t&        j)                  d	||dd
 |«       Y d}~ddd«       yd}~ww xY w# 1 sw Y   Œ€xY w# 1 sw Y   yxY w)uŠ  Lazily create and cache the sandbox env for *task_id* if none is active.

    Lets non-terminal callers (``tools.image_source`` reading container-only
    paths) bring the sandbox up on demand with the same machinery as the
    terminal tool. No-op on local. Returns the env, or ``None`` when local or
    when creation fails (best-effort; the caller's fail-closed path stays intact).

    :func:`terminal_tool` creates the environment on the first terminal command, but nothing else did â€” so
    under a non-local backend (ssh, docker, â€¦) a session whose first action is ``vision_analyze`` on a
    container-only path hit "no active sandbox session" because the SSH/Docker handshake never ran (issue
    #62825). vision reads such paths inside the sandbox (see ``tools.image_source``), so it calls this to
    bring the env up on demand, reusing the same creation machinery as the terminal tool.
    r   )r]   r^   r_   r`   Ú_get_env_configra   rs   Ú_resolve_task_host_cwdÚ_select_imageÚ_start_cleanup_threadÚresolve_task_overridesr1   ÚlocalNr3   r4   )r2   r3   r4   r5   r6   z/Lazy %s environment init failed for task %s: %sé   z-%s environment lazily initialized for task %s)r   r]   r^   r_   r`   rx   ra   rs   ry   rz   r{   r|   rv   r   Ú
setdefaultÚ	threadingÚLockr?   r'   r%   r&   rQ   )r5   r]   r^   r_   r`   rx   ra   rs   ry   rz   r{   r|   r0   r1   Úeffective_task_idÚexistingr2   Ú	task_lockÚnew_envr   s                       r   Úensure_task_envr†   ¸   sÕ  € ÷÷ ÷ ñ ñ
 Ó€FØ�jÑ!€HØ�7ÒØá2°7Ó;ÐäÐ/Ó0€HØÐØñ 	<Ü04·	±	³ˆNÐ,Ñ-÷	<àˆá˜(Ñ$:¸7Ó$CÀVÓL€EáÔà	ñ TØ#×.Ñ.Ð/@Ä)Ç.Á.ÓBRÓSˆ	÷Tð 
ñ Ü!Ð"3Ó4ˆØÐØ÷ñ ð	Ü,Ø˜¨°6¸%±=Ø˜yÑ)Ð3DÙ/°¸Ó@ôˆGð ñ 	<Ø6=Ð Ð!2Ñ3Ü04·	±	³ˆNÐ,Ñ-÷	<ô 	�‰Ø;ØÐ'¨¨Ð+ô	
ð ÷1ñ ÷	<àˆú÷Tð Tûô ò 	Ü�N‰NØAØÐ+¨B¨QÐ/°ôó ÷ð ûð	ú÷	<ð 	<ú÷#ð úsf   ÁD3Á?%E Â.FÃEÃ'FÃ)FÄ#FÄ3D=Å E	Å	FÅE=Å0FÅ=FÆFÆF	Æ
FÆFc                 óh   — t        | «      }|€yt        t        |dd«      xs t        |dd«      «      S )u»  True if *task_id*'s active env persists across turns.

    The agent loop skips per-turn teardown for these (persistent docker,
    daytona, modal, â€¦); non-persistent backends are torn down at end of turn
    to prevent leakage, and the idle reaper handles the rest. Session-scoped
    docker containers count as persistent HERE: their lifetime is the session
    (removed by ``AIAgent.close()`` â†’ ``cleanup_vm`` and the idle reaper).
    FÚ_session_scopedÚ_persistent)rv   ÚboolÚgetattr)r5   rB   s     r   Úis_persistent_envrŒ   ú   s:   € ô ˜Ó
!€CØ
€{ØÜ”˜Ð.°Ó6Ò\¼'À#À}ÐV[Ó:\Ó]Ð]r   c                  óÎ  — ddl m}  d}t        | j                  «       «      D ]  }	 t	        |«       |dz  }Œ t        «       D ]J  }t        d|t        ¬	«      5  t        j                  |d¬
«       t        j                  d|«       ddd«       ŒL |dkD  rt        j                  d|«       |S # t
        $ r#}t        j                  d||d¬«       Y d}~Œ±d}~ww xY w# 1 sw Y   Œ¢xY w)z3Clean up ALL active environments. Use with caution.r   )r]   é   zError cleaning %s: %sTr   Nz!Failed to remove orphaned path %sr   )Úignore_errorszRemoved orphaned: %szCleaned %d environments)r   r]   rh   ri   Ú
cleanup_vmr'   r%   Úerrorr   r
   r"   ÚshutilÚrmtreerQ   )r]   Úcleanedr5   rS   r*   s        r   Úcleanup_all_environmentsr•   	  sâ   € å8Ø€GÜÐ,×1Ñ1Ó3Ó4ò Mˆð	MÜ�wÔØ�q‰L‰GðMô Ó ò 6ˆÜÐ7¸Ä7ÔKñ 	6Ü�M‰M˜$¨dÕ3Ü�K‰KÐ.°Ô5÷	6ð 	6ð6ð
 �‚{Ü�‰Ð-¨wÔ7Ø€Nøô ò 	MÜ�L‰LÐ0°'¸1ÀtˆL×LÑLûð	Mú÷
	6ð 	6ús#   ¦B,Á.CÂ,	CÂ5CÃCÃC$	c                óT   — t        | «      }t        | «       |€yt        || |d¬«       y)uÊ  Manually clean up a specific environment by task_id.

    *force_remove* is forwarded to backends that accept it (currently only
    ``DockerEnvironment``). Default False matches session-lifecycle semantics:
    callers (``AIAgent.close()`` on TUI/gateway session teardown, the per-turn
    cleanup of non-persistent envs) must honor the user's persist-mode
    preference â€” stopping the container here would break the "ONE long-lived
    container shared across sessions" contract. Pass ``force_remove=True``
    only for user-initiated teardown. The idle reaper calls ``env.cleanup()``
    directly, so persist-mode idle envs are likewise no-op'd; only the orphan
    reaper at next startup reclaims them.
    Nz,Manually cleaned up environment for task: %srM   )rc   rZ   rU   )r5   rA   rB   s      r   r�   r�     s0   € ô ˜'Ó
"€CÜ˜'Ô"Ø
€{ØÜØˆW <Ø?ör   c                 ót  — ddl m}m}m}m}  || «      h}| r|j                  | «       g }|5  |D ]:  }|j                  |d«      }|j                  |d«       |€Œ*|j                  |«       Œ< 	 ddd«       |D ]&  }t        d«      5  |j                  «        ddd«       Œ( y# 1 sw Y   Œ5xY w# 1 sw Y   Œ?xY w)zŒDrop any cached env for *task_id* (and its collapsed key) after an
    infrastructure failure, so later calls don't reuse a dead connection.r   )r]   r`   ra   rs   Nz&cleanup of degraded environment failed)
r   r]   r`   ra   rs   Úaddrb   Úappendr
   rE   )	r5   r]   r`   ra   rs   ri   ÚevictedÚkeyrB   s	            r   Ú_evict_environment_for_taskrœ   6  sÆ   € ÷ó ñ ' wÓ/Ð0€DÙØ�‰�ÔØ€GØ	ñ $Øò 	$ˆCØ&×*Ñ*¨3°Ó5ˆCØ×Ñ˜s DÔ)Ø‰Ø—‘˜sÕ#ñ		$÷$ð ò ˆÜÐ<Ó=ñ 	Ø�K‰KŒM÷	ð 	ñ÷$ð $ú÷	ð 	ús   ­,B"ÁB"ÂB.Â"B+Â.B7	)i,  )N)+Ú__doc__r   ÚloggingrI   r’   r€   r   Úpathlibr   Útypingr   r   r   Útools.environments.singularityr   r=   r   r	   r>   r
   Ú	getLoggerr%   r   ÚdictÚ__annotations__r   r   r.   r   Úintr?   rŠ   rL   rU   rZ   rc   rq   rv   r†   rŒ   r•   r�   rœ   r   r   r   ú<module>r¦      sÆ  ðòó Û Û Û Û Û Ý ß &Ñ &Ý ;÷õ .ð 
ˆ×	Ñ	Ð0Ó	1€ð
 ),°uÑ=Ð �4Ó =àÐ ò;òð8 KOòØ��c�‰NðØ&)ðØ58ðØ?BðØMPðàðà$ S™Mðà9AÀ$¹óð$ >Bò �cð ¨H°T©Nð Àdó ð& MQð  cQò X�sð X Sð X¸8ÀD¹>ð XÐ\_ð Xð  VZó Xð 3ð ¨4ó ð˜Só ñ"(¨Só (ðBU˜Có Uñ?˜X c™]ó ?ðD^˜sð ^ tó ^òð, 6;ò ˜ð ¨dó ð.¨°#©ð ¸4ô r   