
    `gje                    R   d Z ddlmZ ddlm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mZ ddlmZmZ ddlmZ ddlmZmZmZmZ  ej2                  e      Zdd	Zdd
ZddZddZddZ ddZ!ddZ"	 	 d	 	 	 	 	 ddZ#ddZ$e G d d             Z% G d d      Z&y) u  ACP session manager — maps ACP sessions to Hermes AIAgent instances.

Sessions are persisted to the shared SessionDB (``~/.hermes/state.db``) so they
survive process restarts and appear in ``session_search``.  When the editor
reconnects after idle/restart, the ``load_session`` / ``resume_session`` calls
find the persisted session in the database and restore the full conversation
history.
    )annotations)get_hermes_homeN)datetimetimezone)	dataclassfield)Lock)AnyDictListOptionalc                0    ddl m}  |t        |             S )a  Translate Windows ACP cwd values when Hermes itself is running in WSL.

    Windows ACP clients can launch ``hermes acp`` inside WSL while still sending
    editor workspaces as Windows drive paths (``E:\Projects``) or
    ``\\wsl.localhost\`` UNC paths. Store and execute against the POSIX form so
    agents, tools, and persisted ACP sessions all agree on the usable workspace.
    Native Linux/macOS keeps the original cwd unchanged.
    r   )translate_cwd_for_wsl_backend)hermes_constantsr   str)cwdr   s     F/root/.hermes/venv/lib/python3.12/site-packages/acp_adapter/session.py_translate_acp_cwdr      s     ?(S22    c                J   t        | xs d      j                         }|sd}t        j                  j	                  |      }ddlm}  ||      }||}n2t        j                  d|      rd|d   j                          d|dd   }t        j                  j                  |      S )	N.r   )windows_path_to_wslz^/mnt/[A-Za-z]/z/mnt/   /   )r   stripospath
expanduserr   r   rematchlowernormpath)r   rawexpandedr   
translateds        r   _normalize_cwd_for_comparer'   +   s    
cjS/


!Cww!!#&H 5$X.J	$h	/8A;,,./q!"?77H%%r   c                   t        | xs d      j                         }|r|S t        |xs d      j                         }|r|S t        j                  j	                  t        |xs d      j                  d            }|xs dS )N z/\z
New thread)r   r   r   r   basenamerstrip)titlepreviewr   explicitpreview_textleafs         r   _build_session_titler1   >   ss    5;B%%'Hw}"%++-L77C	rN11%89D<r   c                    | y t        | t              r| j                         r| S 	 t        j                  t        |       t        j                        j                         S # t        $ r Y y w xY w)N)tz)

isinstancer   r   r   fromtimestampfloatr   utc	isoformat	Exception)values    r   _format_updated_atr;   I   s\    }%%++-%%eElx||DNNPP s   ;A# #	A/.A/c                   | t        d      S t        | t        t         f      rt        |       S t        |       j	                         }|st        d      S 	 t        j                  |j                  dd            j                         S # t        $ r+ 	 t        |      cY S # t        $ r t        d      cY cY S w xY ww xY w)Nz-infZz+00:00)
r6   r4   intr   r   r   fromisoformatreplace	timestampr9   )r:   r$   s     r   _updated_at_sort_keyrB   T   s    }V}%#u&U|
e*


CV}!%%ckk#x&@AKKMM !	!: 	!= 	!!s0   2B	 		B=
B B= B94B=8B99B=c                 p    t        |      }|j                  dt        j                         t	        | i | y)zBest-effort human-readable output sink for ACP stdio sessions.

    ACP reserves stdout for JSON-RPC frames, so any incidental CLI/status output
    from AIAgent must be redirected away from stdout. Route it to stderr instead.
    fileN)dict
setdefaultsysstderrprint)argskwargss     r   _acp_stderr_printrL   e   s.     &\F
fcjj)	46r   c                    | sy	 ddl m}  || dt        |      i       y# t        $ r t        j                  dd       Y yw xY w)aK  Bind a task/session id to the editor's working directory for tools.

    Zed can launch Hermes from a Windows workspace while the ACP process runs
    inside WSL. In that case ACP sends cwd as e.g. ``E:\Projects\POTI``;
    local tools need the WSL mount equivalent or subprocess creation fails
    before the command can run.
    Nr   )register_task_env_overridesr   z(Failed to register ACP task cwd overrideTexc_info)tools.terminal_toolrN   r   r9   loggerdebug)task_idr   rN   s      r   _register_task_cwdrU   p   sJ     PC#Ge5G5L-MN P?$OPs      AAc                    g }t        | xs dg      D ]  }|s||vs|j                  |        t        |xs g       D ]   }d| }|s||vs|j                  |       " |S )zGReturn ACP toolsets plus explicit MCP server toolsets for this session.
hermes-acpzmcp-)listappend)toolsetsmcp_server_namesr%   nameserver_nametoolset_names         r   _expand_acp_enabled_toolsetsr_      s    
 HX/,0 "D(OOD!" ,23 *k]+<x7OOL)*
 Or   c                t    | sy	 ddl m}  ||        y# t        $ r t        j	                  dd       Y yw xY w)z6Remove task-specific cwd overrides for an ACP session.Nr   )clear_task_env_overridesz%Failed to clear ACP task cwd overrideTrO   )rQ   ra   r9   rR   rS   )rT   ra   s     r   _clear_task_cwdrb      s;    M@ ) M<tLMs     77c                      e Zd ZU dZded<   ded<   dZded<   dZded	<    ee
      Z	ded<   dZ
ded<   dZded<    ee
      Zded<    ee
      Zded<   dZded<   dZded<   y)SessionStatez9Tracks per-session state for an ACP-managed Hermes agent.r   
session_idr
   agentr   r   r)   model)default_factoryList[Dict[str, Any]]historyNcancel_eventFbool
is_running	List[str]queued_promptsruntime_lockcurrent_prompt_textinterrupted_prompt_text)__name__
__module____qualname____doc____annotations__r   rg   r   rX   rj   rk   rm   ro   r	   rp   rq   rr    r   r   rd   rd      sy    COJCNE3O$)$$?G!?L#J %d ;NI;d3L#3!!#%S%r   rd   c                      e Zd ZdZddZdddZddZddZdddZdddZ	dd	Z
dd
ZddZd ZddZddZddZddddd	 	 	 	 	 	 	 	 	 	 	 ddZy)SessionManagerzThread-safe manager for ACP sessions backed by Hermes AIAgent instances.

    Sessions are held in-memory for fast access **and** persisted to the
    shared SessionDB so they survive process restarts and are searchable
    via ``session_search``.
    Nc                L    i | _         t               | _        || _        || _        y)a  
        Args:
            agent_factory: Optional callable that creates an AIAgent-like object.
                           Used by tests. When omitted, a real AIAgent is created
                           using the current Hermes runtime provider configuration.
            db:            Optional SessionDB instance. When omitted, the default
                           SessionDB (``~/.hermes/state.db``) is lazily created.
        N)	_sessionsr	   _lock_agent_factory_db_instance)selfagent_factorydbs      r   __init__zSessionManager.__init__   s$     35V
+r   c           
        ddl }t        |      }t        t        j                               }| j                  ||      }t        |||t        |dd      xs d|j                               }| j                  5  || j                  |<   ddd       t        ||       | j                  |       t        j                  d||       |S # 1 sw Y   ?xY w)z:Create a new session with a unique ID and a fresh AIAgent.r   N)re   r   rg   r)   )re   rf   r   rg   rk   zCreated ACP session %s (cwd=%s))	threadingr   r   uuiduuid4_make_agentrd   getattrEventr}   r|   rU   _persistrR   info)r   r   r   re   rf   states         r   create_sessionzSessionManager.create_session   s     %&
  JC @!%"-3"*
 ZZ 	/).DNN:&	/:s+e5z3G	/ 	/s   9CCc                    | j                   5  | j                  j                  |      }ddd       |S | j                  |      S # 1 sw Y   xY w)zReturn the session for *session_id*, or ``None``.

        If the session is not in memory but exists in the database (e.g. after
        a process restart), it is transparently restored.
        N)r}   r|   get_restorer   re   r   s      r   get_sessionzSessionManager.get_session   sN     ZZ 	3NN&&z2E	3L}}Z((	3 	3   AAc                    | j                   5  | j                  j                  |d      du}ddd       | j                  |      }s|rt	        |       |xs |S # 1 sw Y   /xY w)zFRemove a session from memory and database. Returns True if it existed.N)r}   r|   pop_delete_persistedrb   )r   re   existed
db_existeds       r   remove_sessionzSessionManager.remove_session   se    ZZ 	Gnn((T:$FG	G++J7
jJ'$*$	G 	Gs   AA#c           
     P   ddl }t        |      }| j                  |      }|yt        t	        j
                               }| j                  |||j                  xs d      }t        |||t        |d|j                        xs |j                  t        j                  |j                        |j                               }| j                  5  || j                  |<   ddd       t!        ||       | j#                  |       t$        j'                  d||       |S # 1 sw Y   ?xY w)z1Deep-copy a session's history into a new session.r   N)re   r   rg   rg   re   rf   r   rg   rj   rk   zForked ACP session %s -> %s)r   r   r   r   r   r   r   rg   rd   r   copydeepcopyrj   r   r}   r|   rU   r   rR   r   )r   re   r   r   originalnew_idrf   r   s           r   fork_sessionzSessionManager.fork_session   s    %##J/TZZ\"  ..(D ! 

 %(..9KX^^MM("2"23"*
 ZZ 	+%*DNN6"	+63'e1:vF	+ 	+s   DD%c                   |rt        |      nd}| j                         }i }|4	 |j                  dd      D ]  }t        |      |t	        |d         <    	 | j                  5  t        | j                  j                               }g }| j                  j                         D ]   }t        |j                        }	|	d	k  r|rt        |j                        |k7  r:|j!                  |j"                  i       }
t%        d
 |j                  D        |
j!                  d      xs d      }|j'                  |j"                  |j                  |j(                  |	t+        |
j!                  d      ||j                        t-        |
j!                  d      xs' |
j!                  d      xs t/        j.                               d       # 	 ddd       |j1                         D ]  \  }}|v rt3        |j!                  d      xs d	      }|d	k  r/d}|j!                  d      }|r&	 t5        j6                  |      j!                  dd      }|rt        |      |k7  r{j'                  |||j!                  d      xs d|t+        |j!                  d      |j!                  d      |      t-        |j!                  d      xs |j!                  d            d        j=                  d d       |S # t
        $ r t        j                  dd       Y w xY w# 1 sw Y   SxY w# t4        j8                  t:        f$ r Y w xY w)zCReturn lightweight info dicts for all sessions (memory + database).Nacpi  sourcelimitidz#Failed to load ACP sessions from DBTrO   r   c              3     K   | ]p  }|j                  d       dk(  rZt        |j                  d      xs d      j                         r.t        |j                  d      xs d      j                          r yw)roleusercontentr)   N)r   r   r   ).0msgs     r   	<genexpr>z/SessionManager.list_sessions.<locals>.<genexpr>)  sc      776?f4SWWY=O=USU9V9\9\9^ CGGI.4"5;;=s   A6A8r-   r)   r,   last_active
started_at)re   r   rg   history_lenr,   
updated_atmessage_countr   model_configr   rg   c                6    t        | j                  d            S )Nr   )rB   r   )items    r   <lambda>z.SessionManager.list_sessions.<locals>.<lambda>W  s    &:488L;Q&R r   )keyreverse)r'   _get_dblist_sessions_richrE   r   r9   rR   rS   r}   setr|   keysvalueslenrj   r   r   re   nextrY   rg   r1   r;   timeitemsr>   jsonloadsJSONDecodeError	TypeErrorsort)r   r   normalized_cwdr   persisted_rowsrowseen_idsresultssr   	persistedr-   sidr   session_cwdmcs                   r   list_sessionszSessionManager.list_sessions  s   <?3C8T\\^46>S00T0J ?C59#YN3s4y>2? ZZ 	4>>..01HG^^**, !!))n!#!&@&G>&Y*..q||R@	#$99
 MM),2 &'ll uu!"'2!5immG6LgWXW\W\!]&8%MM-8fIMM,<Wf[_[d[d[f'		@ ',,. 	HCh 8 =A>M!K(B"&**R."4"4UC"@K "<["I^"[NN!")/R,-cggg.>	@RT_`01G1`377S_K`a !	2 	R\`a}  SBTRS	 	X ,,i8 s0   2K3 $E$L&%L'3 LLL$'MMc                    t        |      }| j                  |      }|y||_        t        ||       | j	                  |       |S )zBUpdate the working directory for a session and its tool overrides.N)r   r   r   rU   r   )r   re   r   r   s       r   
update_cwdzSessionManager.update_cwdZ  sF     %  ,=	:s+er   c                   | j                   5  t        | j                  j                               }| j                  j	                          ddd       D ]  }t        |       | j                  |         | j                         }|=	 |j                  dd      }|D ]#  }|d   }t        |       |j                  |       % yy# 1 sw Y   |xY w# t        $ r t        j                  dd       Y yw xY w)	zPRemove all sessions (memory and database) and clear task-specific cwd overrides.Nr   i'  r   r   z&Failed to cleanup ACP sessions from DBTrO   )r}   rX   r|   r   clearrb   r   r   search_sessionsdelete_sessionr9   rR   rS   )r   session_idsre   r   rowsr   r   s          r   cleanupzSessionManager.cleanupe  s    ZZ 	#t~~2245KNN  "	# & 	/JJ'"":.	/ \\^>V))e)D +Cd)C#C(%%c*+ 	# 	#  VEPTUVs   >C	;C C C54C5c                    | j                   5  | j                  j                  |      }ddd       | j                  |       yy# 1 sw Y   xY w)zPersist the current state of a session to the database.

        Called by the server after prompt completion, slash commands that
        mutate history, and model switches.
        N)r}   r|   r   r   r   s      r   save_sessionzSessionManager.save_sessiony  sK     ZZ 	3NN&&z2E	3MM%  	3 	3r   c                    | j                   | j                   S 	 ddlm} t               } ||dz        | _         | j                   S # t        $ r t
        j                  dd       Y yw xY w)	a  Lazily initialise and return the SessionDB instance.

        Returns ``None`` if the DB is unavailable (e.g. import error in a
        minimal test environment).

        Note: we resolve ``HERMES_HOME`` dynamically rather than relying on
        the module-level ``DEFAULT_DB_PATH`` constant, because that constant
        is evaluated at import time and won't reflect env-var changes made
        later (e.g. by the test fixture ``_isolate_hermes_home``).
        Nr   )	SessionDBzstate.db)db_pathz)SessionDB unavailable for ACP persistenceTrO   )r   hermes_stater   r   r9   rR   rS   )r   r   hermes_homes      r   r   zSessionManager._get_db  sm     ($$$	.)+K )+
2J KD$$$ 	LLDtLT	s   ,A  A*)A*c                   | j                         }|y|j                  rt        |j                        nd}d|j                  i}t	        |j
                  dd      }t	        |j
                  dd      }t	        |j
                  dd      }t        |t              r#|j                         r|j                         |d<   t        |t              r#|j                         r|j                         |d<   t        |t              r#|j                         r|j                         |d<   t        j                  |      }	 |j                  |j                        }	|	,|j                  |j                  d|d|j                  i       n	 |j                  |j                  ||       |j
                  }
t	        |
dd      }|duxr ||u xr t#        t	        |
dd            }|sE	 |j%                  |j                        }|j'                  |j                  |j(                  |       yy# t        $ r t        j!                  dd	
       Y w xY w# t        $ r d}Y ]w xY w# t        $ r% t        j+                  d|j                  d	
       Y yw xY w)zWrite session state to the database.

        Creates the session record if it doesn't exist, then replaces all
        stored messages with the current in-memory history.
        Nr   providerbase_urlapi_moder   )re   r   rg   r   z%Failed to update ACP session metadataTrO   _session_db_session_db_createdF)active_onlyz Failed to persist ACP session %s)r   rg   r   r   r   rf   r4   r   r   dumpsr   re   r   update_session_metar9   rR   rS   rl   has_archived_messagesreplace_messagesrj   warning)r   r   r   	model_strsession_metar   r   r   cwd_jsonexistingrf   agent_dbagent_owns_persistencehas_archiveds                 r   r   zSessionManager._persist  sV    \\^: ).C$	uyy)5;;
D95;;
D95;;
D9h$)9'/~~'7L$h$)9'/~~'7L$h$)9'/~~'7L$::l+;	`~~e&6&67H!!$// #"'!3	 " Y**5+;+;XyQ( KKEumT:H$ GNG(=uEF #
 *)#%#;#;E<L<L#ML ##$$emm $  *5 ! YLL!HSWLXYN ! )#(L)
  	`NN=u?O?OZ^N_	`s[   9A	I H#  =I I	 9(I # II II 	II II +JJc           
        ddl }| j                         }|y	 |j                  |      }|y|j                  d      dk7  ryd}|j                  d	      }|j                  d
      }d}|j                  d      }	|	rw	 t        j                  |	      }
t        |
t              rQ|
j                  dd      }|
j                  d      xs |}|
j                  d      xs |}|
j                  d      xs |}|j                  d      xs d}	 |j                  |d      }	 | j                  ||||||      }t!        ||||xs t#        |dd      xs d||j%                               }| j&                  5  || j(                  |<   ddd       t+        ||       t        j-                  d|t/        |             |S # t        $ r t        j                  d|d       Y yw xY w# t        j                  t        f$ r Y w xY w# t        $ r t        j                  d|d       g }Y 	w xY w# t        $ r t        j                  d|d       Y yw xY w# 1 sw Y   xY w)zELoad a session from the database into memory, recreating the AIAgent.r   Nz%Failed to query DB for ACP session %sTrO   r   r   r   billing_providerbilling_base_urlr   r   r   r   r   rg   )repair_alternationz*Failed to load messages for ACP session %s)re   r   rg   requested_providerr   r   z+Failed to recreate agent for ACP session %sr)   r   z-Restored ACP session %s from DB (%d messages))r   r   r   r9   rR   rS   r   r   r   r4   rE   r   r   get_messages_as_conversationr   r   rd   r   r   r}   r|   rU   r   r   )r   re   r   r   r   r   r   restored_base_urlrestored_api_moder   metarg   rj   rf   r   s                  r   r   zSessionManager._restore  sv   \\^:	..,C
 ; 778%  WW%78GG$67 WW^$zz"~dD)((5#.C)-*)=)SAS&(,(<(Q@Q%(,(<(Q@Q%  (D	55t 6 G	$$%#5** % E !<75'26<""*
 ZZ 	/).DNN:&	/:s+CZQTU\Q]^I  	LL@*W[L\	2 (()4   	NNG^bNcG	  	NNH*_cNd		/ 	/sT   F7 <A6G G> H( 1I7!GGG;:G;>#H%$H%(!IIIc                    | j                         }|y	 |j                  |      S # t        $ r t        j	                  d|d       Y yw xY w)z?Delete a session from the database. Returns True if it existed.Fz'Failed to delete ACP session %s from DBTrO   )r   r   r9   rR   rS   )r   re   r   s      r   r   z SessionManager._delete_persistedA  sQ    \\^:	$$Z00 	LLBJY]L^	s   & !A
	A
)rg   r   r   r   c               H   | j                   | j                         S ddlm} ddlm} ddlm}	  |       }
|
j                  d      }d}d }t        |t              r0t        |j                  d      xs |      }|j                  d      }n0t        |t              r |j                         r|j                         }|
j                  d	      xs i j                         D cg c]+  \  }}t        |t              r|j                  d
d      dur|- }}}dt        dg|      d|| j                         |xs |d}	  |	|xs |      }|j                  |j                  d      |xs |j                  d      |xs |j                  d      |j                  d      |j                  d      t!        |j                  d      xs g       d       t)        ||        |di |}||_        t,        |_        |S c c}}w # t"        $ r t$        j'                  dd       Y Pw xY w)Nr   )AIAgent)load_config)resolve_runtime_providerrg   r)   defaultr   mcp_serversenabledTFr   rW   )r[   )platformenabled_toolsets
quiet_modere   
session_dbrg   )	requestedr   r   api_keycommandrJ   )r   r   r   r  r  rJ   z7ACP session falling back to default provider resolutionrO   rx   )r~   	run_agentr  hermes_cli.configr  hermes_cli.runtime_providerr  r   r4   rE   r   r   r   r_   r   updaterX   r9   rR   rS   rU   r   rL   	_print_fn)r   re   r   rg   r   r   r   r  r  r  config	model_cfgdefault_modelconfig_providerr\   cfgconfigured_mcp_serversrK   runtimerf   s                       r   r   zSessionManager._make_agentN  s    *&&((%1HJJw'	i&	i 8 IMJM'mmJ7O	3'IOO,=%OO-M %jj7=2DDF"
cc4(CGGIt,DE,Q "
 "
  <!7! $,,.+m

	c.9K9^_GMM 'J 7 ( CGKK
,C ( CGKK
,C&{{95&{{95 V!4!:;	 	:s+!&!   ,U"
<  	cLLR]aLb	cs   !0G8<BG> > H! H!NN)r   )r   r   returnrd   )re   r   r  Optional[SessionState])re   r   r  rl   )re   r   r   r   r  r  )N)r   
str | Noner  ri   r  None)re   r   r  r  )r   rd   r  r  )re   r   r   r   rg   r  r   r  r   r  r   r  )rs   rt   ru   rv   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rx   r   r   rz   rz      s     *)%<HT	V(	!,S`jN`	$ !)-##E E 	E
 E 'E E Er   rz   )r   r   r  r   )r   r  r  r   )r,   r
   r-   r
   r   r  r  r   )r:   r
   r  r  )r:   r
   r  r6   r  )rT   r   r   r   r  r  r  )rZ   List[str] | Noner[   r   r  rn   )rT   r   r  r  )'rv   
__future__r   r   r   r   r   loggingr   r    rG   r   r   r   r   dataclassesr   r   r   r	   typingr
   r   r   r   	getLoggerrs   rR   r   r'   r1   r;   rB   rL   rU   r_   rb   rd   rz   rx   r   r   <module>r&     s    # ,    	 	 
   ' (  , ,			8	$3&& !"P$ "&)-& $M & & & d dr   