
    j&"                       d 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ZddlmZmZ 	 ddlZe	j                            d           Zn# e$ r dZdZY nw xY wddgZ G d	 de          Z G d
 d          ZdS )u  PTY bridge for `hermes dashboard` chat tab.

Wraps a child process behind a pseudo-terminal so its ANSI output can be
streamed to a browser-side terminal emulator (xterm.js) and typed
keystrokes can be fed back in.  The only caller today is the
``/api/pty`` WebSocket endpoint in ``hermes_cli.web_server``.

Design constraints:

* **POSIX-only.**  This module depends on ``fcntl``, ``termios``, and
  ``ptyprocess``, none of which exist on native Windows Python.  Native
  Windows ConPTY is a different API (Windows 10 build 17763+) and would
  need a separate Windows implementation (``pywinpty``) — that's tracked
  as a future enhancement.  On native Windows, importing this module
  raises :class:`ImportError` and the dashboard's ``/chat`` tab shows a
  WSL-recommended banner instead of crashing.  Every other feature in the
  dashboard (sessions, jobs, metrics, config editor) works natively.
* **Zero Node dependency on the server side.**  We use :mod:`ptyprocess`,
  which is a pure-Python wrapper around the OS calls.  The browser talks
  to the same ``hermes --tui`` binary it would launch from the CLI, so
  every TUI feature (slash popover, model picker, tool rows, markdown,
  skin engine, clarify/sudo/approval prompts) ships automatically.
* **Byte-safe I/O.**  Reads and writes go through the PTY master fd
  directly — we avoid :class:`ptyprocess.PtyProcessUnicode` because
  streaming ANSI is inherently byte-oriented and UTF-8 boundaries may land
  mid-read.
    )annotationsN)OptionalSequencewinF	PtyBridgePtyUnavailableErrorc                      e Zd ZdZdS )r   zRaised when a PTY cannot be created on this platform.

    Today this means native Windows (no ConPTY bindings) or a dev
    environment missing the ``ptyprocess`` dependency.  The dashboard
    surfaces the message to the user as a chat-tab banner.
    N)__name__
__module____qualname____doc__     4/usr/local/lib/hermes-agent/hermes_cli/pty_bridge.pyr   r   5   s           r   c                      e Zd ZdZd&dZed'd            Zeddd	d
dd(d            Zed)d            Z	d'dZ
d*d+dZd,d!Zd-d"Zd.d#Zd/d$Zd.d%ZdS )0r   u  Thin wrapper around ``ptyprocess.PtyProcess`` for byte streaming.

    Not thread-safe.  A single bridge is owned by the WebSocket handler
    that spawned it; the reader runs in an executor thread while writes
    happen on the event-loop thread.  Both sides are OK because the
    kernel PTY is the actual synchronization point — we never call
    :mod:`ptyprocess` methods concurrently, we only call ``os.read`` and
    ``os.write`` on the master fd, which is safe.
    proc'ptyprocess.PtyProcess'c                :    || _         |j        | _        d| _        d S NF)_procfd_fd_closed)selfr   s     r   __init__zPtyBridge.__init__I   s    
r   returnboolc                *    t          t                    S )z.True if a PTY can be spawned on this platform.)r   _PTY_AVAILABLE)clss    r   is_availablezPtyBridge.is_availableP   s     N###r   NP      )cwdenvcolsrowsargvSequence[str]r$   Optional[str]r%   Optional[dict]r&   intr'   'PtyBridge'c                  t           sSt          j                            d          rt	          d          t
          t	          d          t	          d          |t          j                                        n|                                }|	                    d          sd|d<   t
          j
                            t          |          ||||f          } | |          S )	a  Spawn ``argv`` behind a new PTY and return a bridge.

        Raises :class:`PtyUnavailableError` if the platform can't host a
        PTY.  Raises :class:`FileNotFoundError` or :class:`OSError` for
        ordinary exec failures (missing binary, bad cwd, etc.).
        r   z^Pseudo-terminals are unavailable on this platform. Hermes Agent supports Windows only via WSL.NzgThe `ptyprocess` package is missing. Install with: pip install ptyprocess (or pip install -e '.[pty]').z!Pseudo-terminals are unavailable.TERMzxterm-256color)r$   r%   
dimensions)r   sysplatform
startswithr   
ptyprocessosenvironcopyget
PtyProcessspawnlist)r    r(   r$   r%   r&   r'   	spawn_envr   s           r   r:   zPtyBridge.spawnU   s       	K|&&u-- )B   !)4  
 &&IJJJ +.+RZ__&&&388::	}}V$$ 	1 0If$**JJd|	 + 
 
 s4yyr   c                4    t          | j        j                  S N)r,   r   pidr   s    r   r?   zPtyBridge.pid   s    4:>"""r   c                    | j         rdS 	 t          | j                                                  S # t          $ r Y dS w xY wr   )r   r   r   isalive	Exceptionr@   s    r   is_alivezPtyBridge.is_alive   sT    < 	5	
**,,--- 	 	 	55	s   %1 
??皙?timeoutfloatOptional[bytes]c                F   | j         rdS 	 t          j        | j        gg g |          \  }}}n# t          t          f$ r Y dS w xY w|sdS 	 t          j        | j        d          }n8# t          $ r+}|j        t          j        t          j	        hv rY d}~dS  d}~ww xY w|sdS |S )u  Read up to 64 KiB of raw bytes from the PTY master.

        Returns:
            * bytes — zero or more bytes of child output
            * empty bytes (``b""``) — no data available within ``timeout``
            * None — child has exited and the master fd is at EOF

        Never blocks longer than ``timeout`` seconds.  Safe to call after
        :meth:`close`; returns ``None`` in that case.
        Nr   i   )
r   selectr   OSError
ValueErrorr5   readerrnoEIOEBADF)r   rF   readable_dataexcs         r   rM   zPtyBridge.read   s     < 	4	#]DH:r2wGGNHa$ 	 	 	44	 	3	748U++DD 	 	 	yUY444ttttt		
  	4s-   !- AA
A% %
B/BBBrS   bytesNonec                (   | j         s|sdS t          |          }|ru	 t          j        | j        |          }nC# t
          $ r6}|j        t          j        t          j        t          j	        hv rY d}~dS  d}~ww xY w|dk    rdS ||d         }|sdS dS )z;Write raw bytes to the PTY master (i.e. the child's stdin).Nr   )
r   
memoryviewr5   writer   rK   rN   rO   rP   EPIPE)r   rS   viewnrT   s        r   rY   zPtyBridge.write   s    < 	t 	F$ 		HTXt,,   9EK EEEFFFFF Avv8D  		 		 		 		 		s   9 
A9*A43A44A9c                    | j         rdS t          j        dt          d|          t          d|          dd          }	 t	          j        | j        t          j        |           dS # t          $ r Y dS w xY w)z:Forward a terminal resize to the child via ``TIOCSWINSZ``.NHHHH   r   )
r   structpackmaxfcntlioctlr   termios
TIOCSWINSZrK   )r   r&   r'   winsizes       r   resizezPtyBridge.resize   s~    < 	F+fc!TllC4LL!QGG	K'"4g>>>>> 	 	 	DD	s   %A& &
A43A4c                n   | j         rdS d| _         t          j        t          j        t          j        fD ]}| j                                        s n	 | j                            |           n# t          $ r Y nw xY wt          j
                    dz   }| j                                        r[t          j
                    |k     rDt          j        d           | j                                        rt          j
                    |k     D	 | j                            d           dS # t          $ r Y dS w xY w)u   Terminate the child (SIGTERM → 0.5s grace → SIGKILL) and close fds.

        Idempotent.  Reaping the child is important so we don't leak
        zombies across the lifetime of the dashboard process.
        NTg      ?g{Gz?)force)r   signalSIGHUPSIGTERMSIGKILLr   rB   killrC   time	monotonicsleepclose)r   sigdeadlines      r   rs   zPtyBridge.close   sJ    < 	F M6>6>B 		! 		!C:%%'' 
$$$$   ~''#-H*$$&& !4>+;+;h+F+F
4    *$$&& !4>+;+;h+F+F	J4((((( 	 	 	DD	s$   A--
A:9A:	D& &
D43D4c                    | S r>   r   r@   s    r   	__enter__zPtyBridge.__enter__   s    r   c                .    |                                   d S r>   )rs   )r   _excs     r   __exit__zPtyBridge.__exit__   s    

r   )r   r   )r   r   )r(   r)   r$   r*   r%   r+   r&   r,   r'   r,   r   r-   )r   r,   )rE   )rF   rG   r   rH   )rS   rU   r   rV   )r&   r,   r'   r,   r   rV   )r   rV   )r   r-   )r
   r   r   r   r   classmethodr!   r:   propertyr?   rD   rM   rY   rh   rs   rw   rz   r   r   r   r   r   >   s<            $ $ $ [$ 
 ""* * * * * [*X # # # X#       <   "	 	 	 	   :        r   )r   
__future__r   rN   rc   r5   rJ   rk   r`   r1   re   rp   typingr   r   r4   r2   r3   r   ImportError__all__RuntimeErrorr   r   r   r   r   <module>r      sS   8 # " " " " "   				    



   % % % % % % % %00777NN   JNNN
 -
.    ,   o o o o o o o o o os   A 	A"!A"