
    #Yjخ                   4   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ZddlZddlZddlmZmZmZmZmZ ddlmZmZmZmZ  ej        e          Ze	j                            dd          Z dgZ!h d	Z"d
Z#dZ$dIdZ%dJdKdZ&dddLdZ' e
j(        de
j)                  Z*dIdZ+dIdZ,dddMd Z-dNd"Z.d#a/dOd%Z0dPd&Z1dQd)Z2dRd,Z3dSd0Z4dTd3Z5dUd6Z6 G d7 d8          Z7 G d9 d:          Z8dVd>Z9dWdAZ:dXdDZ;dYdFZ< G dG dHe          Z=dS )Zuv  Cua-driver backend (macOS, Windows, Linux).

Speaks MCP over stdio to `cua-driver`. The Python `mcp` SDK is async, so we
run a dedicated asyncio event loop on a background thread and marshal sync
calls through it.

The same `cua-driver call <tool>` surface (click, type_text, hotkey, drag,
scroll, screenshot, launch_app, list_apps, list_windows, get_window_state,
move_cursor, wait) works identically across macOS, Windows, and Linux —
cua-driver's PARITY matrix marks the action tools VERIFIED on macOS and
Windows in the cross-platform Rust port (`cua-driver-rs`).

Linux is the most recent runtime (X11 today, Wayland via XWayland; pure-
Wayland progress tracked upstream). It is enabled in
`check_computer_use_requirements` alongside macOS and Windows. The plumbing
in this file is OS-agnostic; per-host gaps (no DISPLAY, missing AT-SPI,
etc.) surface as specific blocked checks via `hermes computer-use doctor`
rather than failing silently.

Install:
  - **macOS**:
      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh)"
  - **Windows** (PowerShell):
      irm https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1 | iex

After install, `cua-driver` is on $PATH and supports `cua-driver mcp` (stdio
transport) which is what we invoke.

The macOS path uses private SkyLight SPIs (SLEventPostToPid,
SLPSPostEventRecordTo, _AXObserverAddNotificationAndCheckRemote) that aren't
Apple-public and can break on OS updates. The Windows path in cua-driver-rs
uses stable Win32 APIs (SendInput + UI Automation) — not subject to the
same SPI breakage class.
    )annotationsN)AnyDictListOptionalTuple)ActionResultCaptureResultComputerUseBackend	UIElementHERMES_CUA_DRIVER_CMDz
cua-drivermcp>   full screenallscreendesktop
fullscreen)progmanworkerwprogram managershell_traywndtaskbarfinderr   dockCUA_DRIVER_RS_TELEMETRY_ENABLEDreturnboolc                     	 ddl m}   |             pi }|                    d          pi }t          |                    dd                     S # t          $ r Y dS w xY w)u  True when Hermes should disable cua-driver telemetry for this user.

    Reads ``computer_use.cua_telemetry`` from config.yaml. Default is False
    (telemetry off). Any failure to read config fails SAFE — toward the
    privacy-preserving default of telemetry disabled.
    r   )load_configcomputer_usecua_telemetryFT)hermes_cli.configr   getr   	Exception)r   cfgcus      SC:\Users\server\AppData\Local\hermes\hermes-agent\tools\computer_use\cua_backend.py_cua_telemetry_disabledr(   n   s    	111111kmm!rWW^$$*667777   tts   AA 
AAbase_envOptional[Dict[str, str]]Dict[str, str]c                p    t          | | nt          j                  }t                      r
d|t          <   |S )a  Return the environment dict for spawning cua-driver.

    Starts from ``base_env`` (defaults to ``os.environ``) and, when telemetry
    is disabled (the default), injects ``CUA_DRIVER_RS_TELEMETRY_ENABLED=0``.
    When the user has opted in, the var is left untouched so cua-driver uses
    its own default. Used by every cua-driver spawn site (MCP backend, status,
    doctor, install) so the policy is applied consistently.
    N0)dictosenvironr(   _CUA_TELEMETRY_ENV_VAR)r)   envs     r'   cua_driver_child_envr3      s:     8/xxRZ
@
@C   *&)"#J    g      @timeout
driver_cmdstrr6   floatTuple[str, List[str]]c                  	 ddl m} t          j        | dgdd|t          j         |t                                          }n&# t          $ r | t          t                    fcY S w xY w|j	        pd
                                }|j        dk    s|s| t          t                    fS 	 t          j        |          }n-# t          t          f$ r | t          t                    fcY S w xY wt!          |t"                    s| t          t                    fS |                    d          }t!          |t"                    s| t          t                    fS |                    d          }|                    d	          }t!          |t                    rt'          d
 |D                       s| t          t                    fS t!          |t(                    r|s| |fS ||fS )u  Return ``(command, args)`` that spawn cua-driver's stdio MCP server.

    Surface 8 of NousResearch/hermes-agent#47072: instead of hardcoding
    ``["mcp"]`` we ask the driver itself via ``cua-driver manifest``
    (trycua/cua#1961). The manifest carries a stable ``mcp_invocation``
    pointer with both ``command`` and ``args``, so a future cua-driver
    that renames or relocates the subcommand keeps working without a
    Hermes patch.

    Falls back to ``(driver_cmd, ["mcp"])`` for older drivers that don't
    expose ``manifest``, or any indeterminate failure — the wrapper must
    not refuse to start just because the discovery hop failed.
    r   _sanitize_subprocess_envmanifestTcapture_outputtextr6   stdinr2    mcp_invocationargscommandc              3  @   K   | ]}t          |t                    V  d S N
isinstancer8   ).0as     r'   	<genexpr>z*_resolve_mcp_invocation.<locals>.<genexpr>   s,      ,N,NAZ3-?-?,N,N,N,N,N,Nr4   )tools.environments.localr=   
subprocessrunDEVNULLr3   r$   list_CUA_DRIVER_ARGSstdoutstrip
returncodejsonloads
ValueError	TypeErrorrJ   r.   r#   r   r8   )	r7   r6   r=   procoutr>   
invocationrE   rF   s	            r'   _resolve_mcp_invocationr^      s   $2EEEEEE~$dG$ )()=)?)?@@
 
 
  2 2 24 01111112;"
#
#
%
%C!34 011112:c??	" 2 2 24 01111112h%% 24 01111.//Jj$'' 24 01111>>&!!DnnY''GdD!! 2,N,N,N,N,N)N)N 24 01111gs##  7   4D=s$   AA  A'&A')B> >'C('C(z~^\s*(?:-\s+)?\[(\d+)\]\s+(\w+)(?:\s*=\s*"([^"]*)"|\s+"([^"]*)"|\s+\((?!\d+\))([^)]*)\))?(?:\s+(?:\(\d+\)\s+)?id=([^\s\[\]]+))?c                 "    t           j        dk    S )Ndarwinsysplatform r4   r'   	_is_macosre      s    <8##r4   c                 N    t          t          j        t                              S )zCTrue if `cua-driver` is on $PATH or HERMES_CUA_DRIVER_CMD resolves.)r   shutilwhich_CUA_DRIVER_CMDrd   r4   r'   cua_driver_binary_availablerj      s    _--...r4   g       @Optional[Dict[str, Any]]c                   	 ddl m} t          j        t          ddgdd| t          j         |t                                          }n# t          $ r Y dS w xY w|j        pd	                                }|sdS 	 t          j        |          }n# t          t          f$ r Y dS w xY wt          |t                    r|                    d	          rdS |S )
a  Run ``cua-driver check-update --json`` and return its parsed state.

    The payload mirrors the ``check_for_update`` MCP tool:
    ``{current_version, latest_version, update_available, ...}``.

    Returns ``None`` (callers should stay quiet) when the result is
    indeterminate: the binary is missing, the driver is too old to support
    the verb (it predates trycua/cua#1734), the GitHub check failed (an
    ``error`` field is set), or the output didn't parse. Best-effort; never
    raises.
    r   r<   zcheck-updatez--jsonTr?   NrC   error)rN   r=   rO   rP   ri   rQ   r3   r$   rT   rU   rW   rX   rY   rZ   rJ   r.   r#   )r6   r=   r[   r\   datas        r'   cua_driver_update_checkro      s   EEEEEE~nh7dG $ )()=)?)?@@

 

 

    tt;"
#
#
%
%C tz#	"   ttdD!! TXXg%6%6 tKs$   AA
 

AA;B B%$B%Optional[str]c                     t                      } | r|                     d          sdS |                     d          pd}|                     d          pd}d| d| dS )	z~One-line "an update is available" message, or ``None`` when up to date,
    indeterminate, or the driver is too old to report.update_availableNlatest_version?current_versioncua-driver z is available (you have z7); update with `hermes computer-use install --upgrade`.)ro   r#   )statelatestcurrents      r'   cua_driver_update_nudgerz     s     $%%E 		"455 tYY'((/CFii)**1cG	@f 	@ 	@g 	@ 	@ 	@r4   FNonec                 v    t           rdS da dd} t          j        | dd                                           dS )	zEmit an update nudge at most once per process, off-thread so the
    (cached, ~20h) GitHub poll never blocks the first computer_use action.NTr   r{   c                     	 t                      } n# t          $ r Y d S w xY w| rt                              d|            d S d S )Nzcomputer_use: %s)rz   r$   loggerinfo)msgs    r'   _runz!_maybe_nudge_update.<locals>._run3  sc    	)++CC 	 	 	FF	 	1KK*C00000	1 	1s    
zcua-driver-update-check)targetnamedaemonr   r{   )_update_checked	threadingThreadstart)r   s    r'   _maybe_nudge_updater   +  sX      O1 1 1 1 3D  egggggr4   c                 8    t           j        dk    rd} nd} d|  dS )Nwin32za  irm https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1 | iexzs  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh)"zxcua-driver is not installed. Install with one of:
  hermes computer-use install
Or run the upstream installer directly:
zW
Or run `hermes tools` and enable the Computer Use toolset to install it automatically.ra   )	installers    r'   cua_driver_install_hintr   @  sG    
|w8 		3 		a 	a 	a 	ar4   markdownList[UIElement]c           
        g }t                               |           D ]}|                    d          p@|                    d          p+|                    d          p|                    d          pd}|                    t	          t          |                    d                    |                    d          |d	                     |S )
u  Parse UIElement list from get_window_state AX tree markdown.

    Last-resort fallback for cua-driver builds that don't carry the
    canonical ``structuredContent.elements`` array (see
    ``_parse_elements_from_structured`` — Surface 2 of #47072 prefers
    that path).

    Captures the label whichever form cua-driver used: ``= "value"``,
    ``"quoted"``, ``(parenthesised)``, or ``id=Label``. Bounds always
    come back ``(0, 0, 0, 0)`` because the markdown surface doesn't
    carry them — yet another reason to prefer the structured path;
    element-index clicks don't need them (the driver resolves the index
    to a frame internally).
                rC         r   r   r   r   )indexrolelabelbounds)_ELEMENT_LINE_REfinditergroupappendr   int)r   elementsmr   s       r'   _parse_elements_from_treer   U  s     H&&x00  

JaggajjJAGGAJJJ!''!**J	aggajj//	
 
 
 	 	 	 	 Or4   raw_elementsList[Dict[str, Any]]c           
        g }| D ]}t          |t                    s|                    d          }t          |t                    sDt          |                    d          t                    r|                    d          nd}t          |                    d          t                    r|                    d          nd}t          |                    d          t                    r|                    d          nd}d}|r	 t          |                    dd	                    t          |                    d
d	                    t          |                    dd	                    t          |                    dd	                    f}n# t
          t          f$ r d}Y nw xY w|                    d          }t          |t                    r|r|nd}	|                    t          |||||	                     |S )uG  Surface 2 of NousResearch/hermes-agent#47072: read the canonical
    ``structuredContent.elements`` array cua-driver-rs emits on every
    ``get_window_state`` response (trycua/cua#1961).

    Each entry has at minimum ``element_index``, ``role``, ``label``;
    ``frame`` (``{x, y, w, h}``) is included whenever the AT-SPI /
    AXFrame call returned usable bounds. Older code parsed the same
    information out of the markdown tree via a regex (lossy: bounds
    were always ``(0, 0, 0, 0)``) — this path preserves the real
    frame so downstream consumers (e.g. ``UIElement.center()``) work
    against pixel coordinates instead of just the index lookup.

    Unknown / malformed entries are skipped rather than failing the
    whole walk — the wrapper degrades to "fewer elements" rather than
    "no elements" on a bad row.
    element_indexr   rC   r   frameNr   xr   ywhelement_token)r   r   r   r   r   )	rJ   r.   r#   r   r8   rZ   rY   r   r   )
r   r   rawidxr   r   r   r   	raw_tokentokens
             r'   _parse_elements_from_structuredr   q  s   " !#H  #t$$ 	ggo&&#s## 	",SWWV__c"B"BJswwv$.swww/?/?$E$EM   2$.swww/?/?$F$FP   D,8 		&&		#q))**		#q))**		#q))**		#q))**	 z* & & &%&
 GGO,,	'	377OIO		4	
 
 
 	 	 	 	 Os   B
FF,+F,r   bytesTuple[int, int]c                F   |                      d          rit          |           dk    rVt                              | dd         d          }t                              | dd         d          }|dk    r
|dk    r||fS |                      d          rd}t          |           }|d	z   |k     r| |         d
k    r|dz  }| |dz            }|dz  }|dv sd|cxk    rdk    rn n@|dz   |k    rnt                              | ||dz            d          }|dk     s	||z   |k    rn{|dv ri|dk    rbt                              | |dz   |dz            d          }t                              | |dz   |dz            d          }|dk    r
|dk    r||fS n||z  }|d	z   |k     dS )zCBest-effort PNG/JPEG dimension sniffing without extra dependencies.s   PNG

         bigr   s   r   	      r   >               >                                             r   r   )r   r   )
startswithlenr   
from_bytes)r   widthheightinmarkersegment_lens          r'   _image_dimensions_from_bytesr     s   
~~*++ !CBs2b5z511BrE
E22199!&= 
~~k"" HH!eaii1v~~QQZFFA%%)?)?)?)?4)?)?)?)?)?1uqyy..Qq1uWu==KQ!k/A"5"5    !## ^^CAa!e,<eDDFNN3q1uQU{+;UCCEqyyVaZZ$f},A/ !eaii2 4r4   	full_textTuple[str, str]c                    |                      dd          }|d         }t          |          dk    r|d         nd}||fS )z?Split get_window_state text into (summary_line, tree_markdown).
r   r   rC   )splitr   )r   linessummarytrees       r'   _split_tree_textr     sC    OOD!$$EAhG5zzA~~5882DD=r4   keysTuple[Optional[str], List[str]]c                    h d}dddd}d t          j        d|           D             }g }d}|D ]4}|                    ||          }||v r|                    |           2|}5||fS )	zParse a key string like 'cmd+s' into (key, modifiers).

    Returns (key, modifiers) where key is the non-modifier key and modifiers
    is a list of modifier names (cmd, shift, option, ctrl).
    >   fnaltcmdctrlshiftoptionrF   controlr   r   r   )rF   r   r   c                    g | ]<}|                                 |                                                                 =S rd   )rU   lower)rK   ps     r'   
<listcomp>z$_parse_key_combo.<locals>.<listcomp>  s7    NNN1AGGIINQWWYY__NNNr4   z[+\-]N)rer   r#   r   )r   MODIFIER_NAMESKEY_ALIASESparts	modifierskeypart
normalizeds           r'   _parse_key_combor     s     [ZZN#HHHKNN4(@(@NNNEI
C   __T400
''Z((((CC	>r4   c                  4    e Zd ZdZddZddZddd
ZddZdS )_AsyncBridgezNRuns one asyncio loop on a daemon thread; marshals coroutines from the caller.r   r{   c                R    d | _         d | _        t          j                    | _        d S rH   )_loop_threadr   Event_readyselfs    r'   __init__z_AsyncBridge.__init__  s#    :>
37o''r4   c                H     j         r j                                         rd S  j                                         d
 fd}t	          j        |dd           _          j                                           j                            d          st          d	          d S )Nr   r{   c                    t          j                     _        t          j         j                    j                                         	  j                                         	  j                                         d S # t          $ r Y d S w xY w# 	  j                                         w # t          $ r Y w w xY wxY wrH   )	asyncionew_event_loopr   set_event_loopr   setrun_forevercloser$   r   s   r'   r   z _AsyncBridge.start.<locals>._run  s     /11DJ"4:...KOO
&&(((J$$&&&&&    DDJ$$&&&&    DsB   B 'B 
BBB?B/.B?/
B<9B?;B<<B?Tzcua-driver-loop)r   r   r         @r5   z)cua-driver asyncio bridge failed to startr   )	r   is_aliver   clearr   r   r   waitRuntimeError)r  r   s   ` r'   r   z_AsyncBridge.start  s    < 	DL1133 	F
	 
	 
	 
	 
	 
	 !'tDGXYYY{,, 	LJKKK	L 	Lr4         >@r6   Optional[float]r   c                :   ddl m} | j        r | j        r| j                                        s7t          j        |          r|                                 t          d           ||| j                  }|t          d          |	                    |          S )Nr   )safe_schedule_threadsafecua-driver bridge not startedr5   )
agent.async_utilsr  r   r   r  r  iscoroutiner
  r  result)r  coror6   r  futs        r'   rP   z_AsyncBridge.run  s    >>>>>>z 	@ 	@T\5J5J5L5L 	@"4(( 

>???&&tTZ88;>???zz'z***r4   c                    | j         r=| j                                         r$| j                             | j         j                   | j        r| j                            d           d | _        d | _         d S )Ng       @r5   )r   
is_runningcall_soon_threadsafestopr   joinr   s    r'   r  z_AsyncBridge.stop  sp    : 	=$*//11 	=J++DJO<<<< 	+Lc***


r4   Nr   r  )r6   r  r   r   )__name__
__module____qualname____doc__r  r   rP   r  rd   r4   r'   r   r     ss        XX( ( ( (
L L L L,	+ 	+ 	+ 	+ 	+     r4   r   c                      e Zd ZdZd)dZd*dZd*d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d0d            Zed1d            Zed2d!            Zed2d"            Zd*d#Zd3d&Zd4d3d(ZdS )5_CuaDriverSessionu  Holds the mcp ClientSession. Spawned lazily; re-entered on drop.

    Lifecycle ownership: a single long-running coroutine
    (`_lifecycle_coro`) opens both the stdio_client and ClientSession
    contexts, populates capabilities, sets `_ready_event`, and then waits
    on `_shutdown_event`. When shutdown is signalled the same coroutine
    closes the contexts — keeping anyio's cancel-scope task-identity
    invariant intact (the bridge schedules each `bridge.run(coro)` as a
    NEW task, so opening contexts in one and closing them in another
    raises "Attempted to exit cancel scope in a different task").
    Tool calls run in their own short-lived tasks; they only touch the
    session object, never the surrounding contexts.
    bridger   r   r{   c                    || _         d | _        t          j                    | _        d| _        i | _        d| _        t          j                    | _	        d | _
        d | _        d | _        d S )NFrC   )_bridge_sessionr   Lock_lock_started_capabilities_capability_versionr   _ready_event_shutdown_event_lifecycle_future_setup_error)r  r&  s     r'   r  z_CuaDriverSession.__init__3  sf    ^%%
 .0(* %O--8<!%59r4   c                2    | j         st          d          d S )Nzcua-driver session not started)r,  r  r   s    r'   _require_startedz"_CuaDriverSession._require_startedG  s(    } 	A?@@@	A 	Ar4   c           	       K   ddl }ddlm}m} ddlm} ddlm} t          j	                    | _
        |                                }d| _        	 t                      st          t                                d| _        t!          t"                    \  }}|                                }	 ||| |t%                                          }
 ||
          4 d{V \  }}d	| _         |||          4 d{V }|                                 d{V  |                                }d
| _        |                     |           d{V  || _        d| _        | j                                         t0                              d|                                |z
  |	|z
  ||	z
             | j
                                         d{V  ddd          d{V  n# 1 d{V swxY w Y   ddd          d{V  n# 1 d{V swxY w Y   n3# t6          $ r&}|| _        | j                                          d}~ww xY wd| _        dS # d| _        w xY w)u}  Long-lived owner of the stdio MCP contexts. Opens, signals
        ready, blocks on shutdown, then cleans up. enter + exit happen
        in the SAME asyncio task, so anyio's cancel-scope invariant
        holds — fixing the "Attempted to exit cancel scope in a
        different task than it was entered in" warning emitted by the
        previous _aenter/_aexit split.
        r   N)ClientSessionStdioServerParameters)stdio_clientr<   zbinary-checkzmanifest-discovery)rF   rE   r2   zmcp-initializezcapability-discoveryreadyzBcua-driver session ready in %.1fs (manifest=%.1fs, mcp_init=%.1fs))timer   r6  r7  mcp.client.stdior8  rN   r=   r  r   r0  	monotonic_startup_phaserj   r  r   r^   ri   r3   
initialize_populate_capabilitiesr)  r/  r  r~   r   r  BaseExceptionr2  )r  _timer6  r7  r8  r=   _t0rF   rE   _t_manifestparamsreadwritesession_t_inites                   r'   _lifecycle_coroz!_CuaDriverSession._lifecycle_coroK  s      	<<<<<<<<111111EEEEEE  '}oo -6	!.00 >"#:#<#<===
 #7D3ODDMGT//++K** -,-A-C-CDD  F $|F++ 6 6 6 6 6 6 6}e&6#(=u55 6 6 6 6 6 6 6!,,.........#oo//G +AD'55g>>>>>>>>>$+DM*1D'%))+++KK;))C/#c)+-   .33555555555+6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 66 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 60  	 	 	 !"D!!###	 !DMMMDDM    st   BH G?;CG
G?
G&	&G?)G&	*G?-H ?
H		H H	H I 
I!H<<II 	IrG  r   c                  K   	 |                                  d{V }t          |dg           pg D ]}t          |dd          }t          |t                    s)t          |dd          }|(t          |dd          pi }|                    d          }t          |t
                    rd |D             | j        |<   t                      | j        |<   t          |dd          }|(t          |dd          pi }|                    d          }t          |t                    r	|| _        dS dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)	u   Surface 4: cache per-tool capability sets + capability_version
        from tools/list. Soft prerequisite — discovery failure leaves
        the map empty and supports_capability degrades to False.Ntoolsr   capabilitiesmodel_extrac                <    h | ]}t          |t                    |S rd   rI   )rK   cs     r'   	<setcomp>z;_CuaDriverSession._populate_capabilities.<locals>.<setcomp>  s7     5 5 5:a+=+=55 5 5r4   capability_versionz5cua-driver tools/list capability discovery failed: %s)
list_toolsgetattrrJ   r8   r#   rR   r-  r  r.  r$   r~   debug)	r  rG  
tools_listtool	tool_namecapsextracvrI  s	            r'   r?  z(_CuaDriverSession._populate_capabilities  s     	U&1133333333J
GR88>B : :#D&$77	!)S11 t^T::< $D->>D"E 99^44DdD)) :5 5#'5 5 5D&y11 58EED&y11
 %94@@Bz
M4@@FBYY344"c"" .+-(((. . 	U 	U 	ULLPRSTTTTTTTTT	Us   D)D1 1
E!;EE!c                    | j         5  | j        r	 d d d            d S | j                                         |                                  d| _        d d d            d S # 1 swxY w Y   d S )NT)r+  r,  r(  r   _start_lifecycle_lockedr   s    r'   r   z_CuaDriverSession.start  s    Z 	! 	!} 	! 	! 	! 	! 	! 	! 	! 	! L   ((*** DM	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	!s   	A4AA#&A#c                   t          j                    | _        d| _        d| _        | j        j        }|t          d          t          j	        | 
                                |          | _        | j                            d          sI|                                  t          | dd          }ddlm} t          d	| d
 |             d          | j        t          d| j                   | j        dS )z^Spawn the lifecycle owner and wait for it to reach ready.
        Caller must hold self._lock.Nr  r  r5   r=  unknownr   )display_hermes_homezEcua-driver session never reached ready (timeout 30s; stuck in phase: z.). Run `hermes computer-use doctor` and check z&/logs/agent.log for the phase timings.z!cua-driver session setup failed: )r   r   r/  r2  r0  r(  r   r  r  run_coroutine_threadsaferJ  r1  r  _signal_shutdown_lockedrT  hermes_constantsr`  )r  loopphaser`  s       r'   r]  z)_CuaDriverSession._start_lifecycle_locked  s@    &O-- # |!<>???!(!A  ""D"
 "
  %%d%33 	((*** D"2I>>E<<<<<<Q#(Q Q '&((Q Q Q   (GD4EGG $% )(r4   c                    | j         5  | j        s	 d d d            d S d| _        |                                  d d d            d S # 1 swxY w Y   d S )NF)r+  r,  _stop_lifecycle_lockedr   s    r'   r  z_CuaDriverSession.stop  s    Z 	* 	*= 	* 	* 	* 	* 	* 	* 	* 	* "DM'')))		* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*s   	AAA
A
c                \   |                                   | j        }|dS 	 |                    d           na# t          j        j        $ r t                              d           Y n1t          $ r%}t                              d|           Y d}~nd}~ww xY wd| _        dS # d| _        w xY w)zbSignal shutdown + wait for the lifecycle coroutine to unwind.
        Caller must hold self._lock.Nr  r5   z*cua-driver session shutdown timed out (5s)zcua-driver shutdown error: %s)	rb  r1  r  
concurrentfuturesTimeoutErrorr~   warningr$   )r  r  rI  s      r'   rg  z(_CuaDriverSession._stop_lifecycle_locked  s     	$$&&&$;F
	*JJsJ####!. 	I 	I 	INNGHHHHH 	? 	? 	? NN:A>>>>>>>>	?
 &*D"""TD"))))s8   8 B" .B&B" (	B1BB" BB" "	B+c                    | j         j        }| j        }|D|D|                                r2	 |                    |j                   dS # t          $ r Y dS w xY wdS dS dS )z8Set the asyncio shutdown event from the caller's thread.N)r(  r   r0  r  r  r  r  )r  rd  events      r'   rb  z)_CuaDriverSession._signal_shutdown_locked  s    |!$ 1doo6G6G 1))%)44444     1 1 1 1s   A	 	
AAr   r8   rE   Dict[str, Any]c                f   K   | j                             ||           d {V }t          |          S rH   )r)  	call_tool_extract_tool_result)r  r   rE   r  s       r'   _call_tool_asyncz"_CuaDriverSession._call_tool_async  s=      }..tT::::::::#F+++r4   N
capabilityrW  rp   r   c                    |)| j                             |t                                v S t          fd| j                                         D                       S )u  Return True when the connected cua-driver advertises the given
        capability token (trycua/cua#1961 capability vocabulary).

        When ``tool`` is given, scope the check to that specific tool's
        advertised capability set. When omitted, return True if ANY tool
        advertises the capability — useful for "is this feature available
        anywhere on the driver" probes.

        Always returns False before the session is started (so consumers
        on a dead/uninitialised wrapper degrade rather than crash).
        Nc              3      K   | ]}|v V  	d S rH   rd   )rK   rY  rt  s     r'   rM   z8_CuaDriverSession.supports_capability.<locals>.<genexpr>!  s(      NN$:%NNNNNNr4   )r-  r#   r  anyvalues)r  rt  rW  s    ` r'   supports_capabilityz%_CuaDriverSession.supports_capability  s_     !3!7!7cee!D!DDDNNNN$2D2K2K2M2MNNNNNNr4   c                    || j         v S )um  Return True when ``tools/list`` advertised a tool by this name.

        Used to route capture(): cua-driver dropped the standalone
        ``screenshot`` tool and folded full-window PNG capture into
        ``get_window_state`` (whose own description notes it "Also captures
        a PNG screenshot of the specified window"). Older drivers that still
        expose ``screenshot`` keep using it; newer ones fall through to
        ``get_window_state``.

        Returns False when discovery hasn't populated the map yet — callers
        treat that as "unknown" and probe defensively rather than trusting it.
        )r-  )r  r   s     r'   	_has_toolz_CuaDriverSession._has_tool#  s     t)))r4   c                *    t          | j                  S )zTrue once ``tools/list`` populated the per-tool map. When False,
        ``_has_tool`` answers are not trustworthy (discovery failed or the
        session hasn't started) and capture() should probe defensively.)r   r-  r   s    r'   capabilities_discoveredz)_CuaDriverSession.capabilities_discovered2  s    
 D&'''r4   c                    | j         S )u   Driver-advertised capability vocabulary version (empty string
        when the driver predates the field — older builds had no version).)r.  r   s    r'   rR  z$_CuaDriverSession.capability_version9  s     ''r4   excr$   c                    | j         j        }t          | j         dd          }|dv p4|                    d          od|v pt	          | t
          t          f          S )zHReturn True for MCP/stdio failures that are recoverable by reconnecting.r!  rC   >   EndOfStreamBrokenResourceErrorClosedResourceErroranyioResource)	__class__r   rT  r   rJ   BrokenPipeErrorEOFError)r  r   modules      r'   _is_closed_session_errorz*_CuaDriverSession._is_closed_session_error?  sf     }%b99QQ <!!'**AzT/A<#:;;	
r4   c                @    t          |           }d|v pd|v pd|v pd|v S )u  Return True for the cua-driver daemon-proxy EAGAIN congestion error.

        On macOS the ``cua-driver mcp`` bridge forwards calls to the CuaDriver
        daemon over a non-blocking unix socket. Heavier ops (notably
        ``get_window_state``, which walks the AX tree and captures a PNG) can
        come back as an ``McpError`` carrying ``Resource temporarily
        unavailable (os error 35)`` — POSIX EAGAIN — when the socket buffer is
        momentarily full. This is transient by definition: the same call
        succeeds when retried after a short pause (which is why spaced-out
        single calls work while rapid/large ones intermittently fail). Detect
        it by message so we can retry with backoff rather than surfacing an
        empty 0x0 capture to the model. See the EAGAIN diagnosis in
        references/catalog-add-troubleshooting (apple-music skill) and the
        cua-driver daemon-proxy note.
        z Resource temporarily unavailablezos error 35zdaemon transport errorzdaemon proxy)r8   )r  r   s     r'   _is_transient_daemon_errorz,_CuaDriverSession._is_transient_daemon_errorJ  sE    " #hh.#5 %#%'3.% $		
r4   c                   | j         rH	 |                                  n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wd| _         i | _        d| _        |                                  d| _         dS )zRecreate the MCP session after the daemon/stdin transport was closed.
        Caller must hold self._lock (the reconnect-once retry path holds it).z6cua-driver session cleanup before reconnect failed: %sNFrC   T)r,  rg  r$   r~   rU  r-  r.  r]  r  rI  s     r'   _restart_session_lockedz)_CuaDriverSession._restart_session_lockedc  s     = 	ZZ++---- Z Z ZUWXYYYYYYYYZ#% $$&&&s    
AAAr6   r9   c                   ddl }ddl}ddl}ddlm} t          |          }d}	|dk    r7d|vr3|                    dd          \  }
}	t          j        |
           |	|d<   t          d	|t          j        |          g}d
}d}d}d}	 t          |          D ]h}	 |                    |ddt          d|           |t                                          }n(# t           $ r}t#          d| d|           |d}~ww xY w|j        pd                                }|dd         p|j        pddd         }t+          d |                    d          |                    d          fD             d          }|dk    r;	 t          j        ||d                   }n# t          j        $ r d}Y nw xY w||} nF||dz
  k     r;t2                              d||dz   ||           |                    |           |dz  }j|t#          d| d| d|           g }d}t9          |t
                    r|nd}d}t9          |t
                    rj|                    d          du p|                    d          du }|                    d           }|s|                    d!          p|	}|rt          j                            |          r	 tA          |d"          5 }tC          j"        |#                                          $                    d#          }ddd           n# 1 swxY w Y   n3# t           $ r&}t2          %                    d$||           Y d}~nd}~ww xY w|r|&                    |           |                    d%          }|)|                    d&          }|| d'nd}|r| d(| n|}||||d)|	rEt          j                            |	          r'	 t          j'        |	           S # tP          $ r Y S w xY wS S # |	rEt          j                            |	          r'	 t          j'        |	           w # tP          $ r Y w w xY ww w xY w)*u  Fallback transport: invoke ``cua-driver call <tool> <json>`` as a
        subprocess instead of going through the stdio MCP bridge.

        The ``cua-driver mcp`` stdio bridge can persistently fail to forward
        heavier calls (notably ``get_window_state``) to the daemon with POSIX
        EAGAIN, while the plain ``cua-driver call`` path — which talks to the
        daemon over its own socket — keeps working. When the MCP path gives up,
        we retry over the CLI and remap the JSON into the same dict shape that
        ``_extract_tool_result`` produces, so callers (capture(), _action(),
        list_windows parsing) are transport-agnostic.

        For ``get_window_state`` we route the screenshot to a temp file via
        ``screenshot_out_file`` so the daemon returns a tiny JSON body (a path)
        instead of a multi-megabyte base64 blob — the large payload is what
        congests the daemon socket and triggers EAGAIN in the first place. We
        read the PNG back from disk and base64-encode it ourselves. The CLI
        call is itself retried a few times with backoff, since the underlying
        daemon socket can still be momentarily busy.
        r   Nr<   get_window_statescreenshot_out_file	cua_shot_z.png)prefixsuffixcallr   g      ?rC   Tg      .@)r@   rA   r6   r2   zcua-driver CLI fallback for z failed to spawn:    c              3  &   K   | ]}|d k    |V  dS )Nrd   )rK   r   s     r'   rM   z7_CuaDriverSession._call_tool_via_cli.<locals>.<genexpr>  s&      JJ1!r''Q''''JJr4   {[r  )defaultr   zMcua-driver CLI fallback for %s got no JSON (attempt %d/%d); retrying in %.1fsr   z returned no JSON after z attempts: FisErroris_errorscreenshot_png_b64screenshot_file_pathrbasciiz.cua-driver CLI fallback: failed reading %s: %stree_markdownelement_countz	 elementsr   )rn   imagesstructuredContentr  ))rO   tempfiler:  rN   r=   r.   mkstempr/   r
  ri   rW   dumpsrangerP   maxr3   r$   r  rT   rU   stderrminfindrX   JSONDecodeErrorr~   rl  sleeprJ   r#   pathexistsopenbase64	b64encoderE  decoderU  r   removeOSError) r  r   rE   r6   _subprocess	_tempfilerA  r=   	call_args	shot_filefdr   attemptsbackoffparsedlast_errattemptr[   rI  r\   r   	candidater  rn   
structuredr  shotfpathfhr   ecr   s                                    r'   _call_tool_via_cliz$_CuaDriverSession._call_tool_via_clir  s   ( 	)((($$$$EEEEEEJJ	#'	%%%*?y*P*P%--[-PPMB	HRLLL/8I+,dj.C.CDN	 ?? ! !j&??DtSwEWEW445I5K5KLL +  DD ! j j j&'ad'a'a^_'a'abbhiij {(b//11tt9A):DSD(AJJ# >JJJ   B;;)$(Js566{$;$;		/ ) ) )$(			) ,!*X\))NN=gk8W  
 KK(((qLG~"74 7 77 7,47 7   !#FD3=fd3K3K)UQUJH&$'' E "::i00D8ZFJJz<R<RVZ<Zzz"677 e"JJ'=>>K)E e!6!6 ee!%eT!2!2 Sb'-'7		'B'B'I'I''R'RS S S S S S S S S S S S S S S( e e e"LL)Y[`bcdddddddde (MM$'''zz/22#O44B24.....bG3:Dg/////D %/#	   RW^^I66 Ii((((   D y RW^^I66 Ii((((   D s   P =CP 
C="C88C==BP  FP F1.P 0F11DP L/ :L#L/ #L''L/ *L'+L/ .P /
M9MP MA P !O66
PP"Q+Q ?Q 
Q
QQQr  c                |   |                                   	 | j                            |                     ||          |          S # t          $ r}|                     |          r8t                              d||           |                     |||          cY d }~S | 	                    |          s t                              d|           | j
        5  |                                  d d d            n# 1 swxY w Y   | j                            |                     ||          |          cY d }~S d }~ww xY w)Nr5   zIcua-driver MCP transport failed on %s (%s); falling back to CLI transportz:cua-driver MCP session closed during %s; reconnecting once)r4  r(  rP   rs  r$   r  r~   rl  r  r  r+  r  )r  r   rE   r6   rI  s        r'   rq  z_CuaDriverSession.call_tool  s   	X<##D$9$9$$E$Ew#WWW 	X 	X 	X..q11 D4591   ..tT7CCCCCCCC0033  NNWY]^^^ / /,,.../ / / / / / / / / / / / / / /<##D$9$9$$E$Ew#WWWWWWWW	XsN   /A 
D;AD6D;8D6C6*D66C:	:D6=C:	>2D60D;6D;)r&  r   r   r{   r   )rG  r   r   r{   r   r8   rE   ro  r   ro  rH   )rt  r8   rW  rp   r   r   )r   r8   r   r   r   r   r   r8   )r  r$   r   r   )r   r8   rE   ro  r6   r9   r   ro  r  )r   r!  r"  r#  r  r4  rJ  r?  r   r]  r  rg  rb  rs  ry  r{  propertyr}  rR  staticmethodr  r  r  r  rq  rd   r4   r'   r%  r%  $  s        : : : :(A A A AL! L! L! L!\!U !U !U !UF! ! ! !#% #% #% #%J* * * ** * * *&	 	 	 	, , , ,
O O O O O * * * * ( ( ( X( ( ( ( X(
 
 
 
 \
 
 
 
 \
0   s s s sjX X X X X X Xr4   r%  
mcp_resultr   ro  c                   d}g }g }t          | dd          du }t          | dd          pd}g }t          | dg           pg D ]}t          |dd          }|dk    r'|                    t          |dd	          pd	           @|d
k    rPt          |dd          }	|	r=|                    |	           t          |dd          pd	}
|                    |
           |rud                    d |D                       }	 |                                                    d          rt          j        |          n|}n# t
          j        $ r |}Y nw xY w|||||dS )u  Convert an mcp CallToolResult into a plain dict.

    cua-driver returns a mix of text parts, image parts, and structuredContent.
    We flatten into:
      {
        "data": <text or parsed json>,
        "images": [b64, ...],
        "image_mime_types": [mime, ...],   # parallel to `images`, "" when absent
        "structuredContent": <dict|None>,
        "isError": bool,
      }
    structuredContent is populated from the MCP result's structuredContent field
    (MCP spec §2024-11-05+) and takes precedence for structured data like
    list_windows window arrays.

    `image_mime_types` is the explicit `mimeType` cua-driver emits on every
    image part as of trycua/cua#1961 (Surface 7 of
    NousResearch/hermes-agent#47072). Each entry corresponds index-for-index
    with `images`; an empty string entry signals the part carried no
    mimeType (older cua-driver build), and the caller should fall back to
    base64-prefix sniffing.
    Nr  FTr  contenttyperA   rC   imagern   mimeTyper   c              3     K   | ]}||V  	d S rH   rd   )rK   ts     r'   rM   z'_extract_tool_result.<locals>.<genexpr>/  s'      77Q71777777r4   )r  r  )rn   r  image_mime_typesr  r  )rT  r   r  rU   r   rW   rX   r  )r  rn   r  r  r  r  text_chunksr   ptypeb64mimejoineds               r'   rr  rr    s   . DF"$ z9e44<H!(5H$!O!O!WSWJK
Ir228b 	. 	.fd++F??wtVR88>B????g$--C .c"""tZ66<" ''--- 77k77777	)/)B)B:)N)NZ4:f%%%TZDD# 	 	 	DDD	 ,'  s   ;=D9 9EEr\   #tuple[Optional[str], Optional[str]]c                   |                      d          pg }|r=|d         r5|                      d          pg }|r|d         r|d         nd}|d         |fS |                      d          pi }|                     d          p|                     d          }|r0|                     d          p|                     d	          pd}||fS d
S )u  Pull a (png_b64, mime_type) pair out of a flattened tool result.

    cua-driver delivers window screenshots in two shapes depending on tool +
    transport:

      * As an MCP ``image`` content part — surfaced by ``_extract_tool_result``
        in ``out["images"]`` with a parallel ``image_mime_types`` entry. This
        is what ``get_window_state`` emits over the stdio MCP transport.
      * As a base64 field inside ``structuredContent`` —
        ``screenshot_png_b64`` (+ ``screenshot_mime_type``). This is what
        ``get_window_state`` returns when its structured payload carries the
        image instead of a content part (newer driver builds; also the shape
        seen via the ``cua-driver call`` CLI surface).

    Checking both makes capture() robust to either delivery shape, so the
    image never silently drops just because the driver moved it between the
    content list and structuredContent. Returns ``(None, None)`` when neither
    location carries an image.
    r  r   r  Nr  r  png_b64screenshot_mime_type	mime_type)NNr#   )r\   r  mimesr  r  r  s         r'   _image_from_tool_resultr  =  s    ( WWX$"F &) *++1r 7U1X7uQxx4ay$,--3J
..-
.
.
K*..2K2KC
 NN122 ~~k** 	
 Dy:r4   valueOptional[int]c                    t          | t                    st          | t          t          f          sdS 	 t          |           }n# t          $ r Y dS w xY w|dk    r|ndS )zCReturn a positive integer, rejecting booleans and malformed values.Nr   )rJ   r   r   r8   rY   )r  r  s     r'   _positive_intr  d  st    % jc
&C&C tU   ttaZZ66T)s   A 
AAraw_windowsc                   g }| D ]}t          |                    d                    }t          |                    d                    }||K|                    d          }t          |t          t          f          rt          |t
                    s|nd}|                    |                    dd          |||                    dd	           |                    d
d          |d           |S )u   Normalise cua-driver ``list_windows`` entries, dropping unusable ones.

    Every downstream operation needs both an integer ``pid`` (for
    get_window_state / action tools) and ``window_id`` (for screenshot /
    element clicks), so a window missing either is uncapturable.

    Crucially, on X11 a window's PID comes from the *optional*
    ``_NET_WM_PID`` property — the desktop root, panels, and
    override-redirect popups routinely omit it, so the driver reports
    ``pid: null`` for them. Coercing every entry unconditionally
    (``int(w["pid"])``) let one such window abort enumeration of the real,
    targetable windows. We skip the unusable entries instead so capture()
    and focus_app() still find the windows that matter.

    ``z_index`` follows CUA Driver semantics: higher = closer to front.
    Wayland may return ``z_index: null`` (undefined stacking order); we
    treat null as the lowest priority so real windows still sort above
    desktop/root windows, and the backmost never ends up selected as the
    capture target.
    pid	window_idNz_indexr   app_namerC   is_on_screenTtitler  r  r  
off_screenr  r  )r  r#   rJ   r   r9   r   r   )r  windowsr   pid_intwindow_id_intz_rawr  s          r'   _ingest_windowsr  o  s    * %'G  e--%aeeK&8&899?m3i  %ec5\::a:eUYCZCZa%%`aj"--&eeND999UU7B''
 
 	 	 	 	 Nr4   c                     e Zd ZdZddZddZddZddZdd	ZdddZ	ddZ
ddZddZ	 	 	 	 dddZdddd d!dd"dd,Zddddd dd-dd3Zd4ddddd5dd8Zdd:Zdd<Zddd>Zdd?Zdd@ZdddCZdddddAdDddIZddJZddKddLZddMZddOZddPZdQdRdSdTdd[Zdd\dd_Zddddd`ddeZdddddfddjZ dd\ddkZ!dAdlddoZ"ddpZ#ddqZ$dAdQdrddvZ%ddwZ&ddxZ'ddyZ(ddzZ)dd~Z*dddddZ+ddZ,ddZ-dS )CuaDriverBackendz@Default computer-use backend. Cross-platform via cua-driver MCP.r   r{   c                    t                      | _        t          | j                  | _        d | _        d | _        d | _        d | _        i | _        dt          j
                    j        d d          | _        d S )Nzhermes-   )r   r(  r%  r)  _active_pid_active_window_id	_last_app_last_target_snapshot_tokensuuiduuid4hex_session_idr   s    r'   r  zCuaDriverBackend.__init__  sv    #~~)$,77*.04(, AE 13$ !B$*,,*:3B3*? A Ar4   c                V   t                       ddlm}  |dd           dd l}|                                 | j                                         	 | j                            dd| j        i           d S # t          $ r&}t                              d|           Y d }~d S d }~ww xY w)	Nr   )ensureztool.computer_useF)promptstart_sessionrG  z:cua-driver start_session failed (continuing anonymous): %s)r   tools.lazy_depsr  	importlibinvalidate_cachesr)  r   rq  r  r$   r~   rU  )r  _lazy_ensurer  rI  s       r'   r   zCuaDriverBackend.start  s     	;:::::(7777 	##%%%	ZM##OiAQ5RSSSSS 	Z 	Z 	ZLLUWXYYYYYYYYY	Zs   "A8 8
B(B##B(c                l   | j         j        rV	 | j                             dd| j        i           n2# t          $ r%}t
                              d|           Y d }~nd }~ww xY w	 | j                                          | j                                         d S # | j                                         w xY w)Nend_sessionrG  z7cua-driver end_session failed (continuing teardown): %s)	r)  r,  rq  r  r$   r~   rU  r  r(  r  s     r'   r  zCuaDriverBackend.stop  s     =! 	[[''	4CS7TUUUU [ [ [VXYZZZZZZZZ[	 M   LDLs    "1 
A AA $B B3r   c                >    t           j        dvrdS t                      S )N)r`   r   linuxF)rb   rc   rj   r   s    r'   is_availablezCuaDriverBackend.is_available  s#    
 <;;;5*,,,r4   c                L    d| _         d| _        d| _        d| _        i | _        dS )zGForget a capture/focus target so a failed lookup cannot misroute input.N)r  r  r  r   r  r   s    r'   _clear_active_targetz%CuaDriverBackend._clear_active_target  s/    !%  "r4   rC   moder8   messager
   c           
     X    |                                   t          |dddg d|d          S )zAReturn an empty capture after disarming any prior target context.r   NrC   )r  r   r   r  r   appwindow_titlepng_bytes_len)r  r
   )r  r  r  s      r'   _failed_capturez CuaDriverBackend._failed_capture  sC    !!### 	
 	
 	
 		
r4   r   rE   ro  c                j   	 | j                             ||          }n## t          $ r |                                   w xY w|                    d          du r[|                    d          }|                                  t          d| dt          |t                    r|rd| ndz             |S )zKCall a capture-stage tool and disarm state on transport or logical failure.r  Trn   rv   z failedz: rC   )r)  rq  r$   r  r#   r  rJ   r8   )r  r   rE   r\   r  s        r'   _call_capture_toolz#CuaDriverBackend._call_capture_tool  s    	-))$55CC 	 	 	%%'''	 779%%ggfooG%%'''+d+++%/%=%=Q'Q>>>>rS   
s     >r   c                   |                      dd| j        d          }|                    d          pi                     d          pg }t          |          }|                    d d           |r|S t
                              d           	 | j                            dd| j        dd	          }n4# t          $ r'}t
          
                    d
|           g cY d}~S d}~ww xY w|                    d          du r0t
          
                    d           |                                  g S |                    d          pi                     d          pg }t          |          }|                    d d           |S )u  Load normalized visible windows, with the shared CLI recovery path.

        Windows are sorted by ``z_index`` **descending**: CUA Driver
        defines higher values as closer to the front, so the frontmost
        window ends up at index 0 — which is what ``capture()`` and
        ``focus_app()`` pick as the default target.  ``_ingest_windows``
        already normalised null ``z_index`` (Wayland) to 0, so those
        windows sort to the back.
        list_windowsT)on_screen_onlyrG  r  r  c                    | d         S Nr  rd   r   s    r'   <lambda>z0CuaDriverBackend._load_windows.<locals>.<lambda>3  
    1Y< r4   )r   reversezScua-driver list_windows returned no windows over MCP; re-fetching via CLI transportg      4@z3cua-driver CLI re-fetch for list_windows failed: %sNr  z:cua-driver CLI re-fetch for list_windows returned an errorc                    | d         S r"  rd   r#  s    r'   r$  z0CuaDriverBackend._load_windows.<locals>.<lambda>J  r%  r4   )r  r  r#   r  sortr~   rl  r)  r  r$   rm   r  )r  r\   r  r  cli_outr  s         r'   _load_windowszCuaDriverBackend._load_windows#  s    %%#0@AA
 
 ww2339r>>yIIOR!+..//>>> 	N,	
 	
 	
	m66#'D4DEE GG
  	 	 	LLNPSTTTIIIIII	 ;;y!!T))LLUVVV%%'''I{{#677=2BB9MMSQS!+..//>>>s   $B6 6
C' C"C'"C'r  r  c                   |                                                                 sg S fd|D             }|r|S 	 |                                 }n5# t          $ r(}t                              d||           g }Y d}~nd}~ww xY wt                      t                      |D ]t          t                    r	                    d          du r/	                    d          }t          |t                    st          |t          t          f          sv	 t          |          }n# t          $ r Y w xY w|dk    rfdd	D             }|v r                    |           t          fd
|D                       r                    |           fd|D             }	|	r|	S fd|D             }
|
r|
S fd|D             }|r|S fd|D             S )a  Resolve ``app=`` through exact names before convenience substrings.

        Linux ``list_windows`` can omit an app name while ``list_apps`` retains
        name/bundle-ID metadata. Exact direct names and exact metadata aliases
        must win over substring matches: querying ``Code`` must not silently
        select ``Visual Studio Code`` merely because it is frontmost.
        c           	         g | ]O}t          |                    d d                                                                                    k    M|PS r  rC   r8   r#   rU   r   rK   r   	app_lowers     r'   r   z;CuaDriverBackend._match_windows_for_app.<locals>.<listcomp>[  s\     
 
 
Cj" 5 566<<>>DDFFFF FFFr4   z1computer_use list_apps fallback failed for %r: %sNrunningFr  r   c                    h | ]f}t                              |          xt                    ,                                @                                                                gS rd   )rJ   r#   r8   rU   r   )rK   r   raw_appr  s     r'   rQ  z:CuaDriverBackend._match_windows_for_app.<locals>.<setcomp>z  sr       C(8(88u3?? EJKKMM##%%  r4   )	bundle_idbundleIdr   r  display_namec              3      K   | ]}|v V  	d S rH   rd   )rK   aliasr0  s     r'   rM   z:CuaDriverBackend._match_windows_for_app.<locals>.<genexpr>  s(      ==EY%'======r4   c                B    g | ]}|                     d           v |S r  r  )rK   r   
exact_pidss     r'   r   z;CuaDriverBackend._match_windows_for_app.<locals>.<listcomp>  s-    KKKe
0J0J!0J0J0Jr4   c           	         g | ];}t          |                    d d                                                    v 9|<S r-  )r8   r#   r   r/  s     r'   r   z;CuaDriverBackend._match_windows_for_app.<locals>.<listcomp>  sP     
 
 
Cj" 5 566<<>>>> >>>r4   c                B    g | ]}|                     d           v |S r:  r  )rK   r   partial_pidss     r'   r   z;CuaDriverBackend._match_windows_for_app.<locals>.<listcomp>  s-    OOO!!%%,,,2N2NA2N2N2Nr4   c           	         g | ]p}t          |                    d d                                                    s9t          |                    dd                                                    v n|qS )r  rC   r  r.  r/  s     r'   r   z;CuaDriverBackend._match_windows_for_app.<locals>.<listcomp>  s|     
 
 
quuZ,,--3355
 Sw!3!344::<<<< <<<r4   )rU   r   	list_appsr$   r~   rU  r  rJ   r.   r#   r   r   r8   rY   addrw  )r  r  r  direct_exactrunning_appsr  raw_pidr  aliasesmetadata_exactdirect_partialmetadata_partialr0  r;  r>  r3  r  s               @@@@@r'   _match_windows_for_appz'CuaDriverBackend._match_windows_for_appM  s    IIKK%%''	 	I
 
 
 

 
 
  	 	>>++LL 	 	 	 LLLcSVWWWLLLLLL	  #uu
!$# 	& 	&Ggt,, I0F0F%0O0Okk%((G'4(( 
7S#J0O0O 'll   axx    X  G
 G##s####====W===== &  %%%KKKKWKKK 	"!!
 
 
 

 
 
  	"!!OOOOwOOO 	$##

 
 
 

 
 
 	
s*   A 
B
"BB
"D22
D?>D?somNrp   r  r  r  c                |  # ||_|||                      |d          S t          |          }t          |          }|||                      |d          S |pd||ddddg}nP	 |                                 }n## t          $ r |                                   w xY w|s|                      |          S |r|p|rn|                                                                t          v rAd6d##fd|D             }|s|                      |d|d          S t          |d           }n:|8|6|r4| 	                    ||          }	|	s|                      |d|d          S |	}t          d |D             |d                   }
|
d         | _        |
d         | _        i | _        |
d         }|s| j        s|| _        | j        | j        d| _        d}d}g }dx}}d}|dk    r| j                            d          p| j        j         }d}|r;|                     d| j        dd| j        d          }t+          |          \  }}|sd}||                     d| j        | j        | j        d          }t+          |          \  }}t-          |d          t.                    r|d          nd}t1          |          \  }}t3          j        d!|          }|r|                    d"          }|st8                              d#| j                   	 | j                            d| j        | j        | j        dd$          }|                    d%          d&u r|                                  n%|                    d'          r|d'         d         }d(}n.# t          $ r&}t8                               d)|           Y d}~nd}~ww xY wn|                     d| j        | j        | j        d          }d7d+} ||          rt8                              d,| j        | j                   	 | j                            d| j        | j        | j        dd$          }|                    d%          d&u r|                                  n ||          s|}n2# t          $ r%}t8                               d-|           Y d}~nd}~ww xY wt-          |d          t.                    r|d          nd}t1          |          \  }}t3          j        d.|          }|                    d/          pi                     d0          }t-          |tB                    r|rtE          |          }n|rtG          |          ng }d1 |D             | _        t+          |          \  }}t3          j        d!|          }|r|                    d"          }d}|rf	 tI          j%        |d2          } tM          |           }tO          |           \  }!}"|!r|"r|!}|"}n%# t          $ r tM          |          d3z  d4z  }Y nw xY wtQ          |||||||||5	  	        S )8u   Capture the frontmost on-screen window or an exact known target.

        Maps hermes `capture(mode, app)` → cua-driver `list_windows` +
        `get_window_state` (ax/som) or `screenshot` (vision).
        Nz3<capture targeting requires both pid and window_id>z?<capture targeting requires positive integer pid and window_id>rC   Fr   r  r   ro  r   r   c                    |                      dd           d|                      dd                                           t          fdt          D                       S )Nr  rC    r  c              3      K   | ]}|v V  	d S rH   rd   )rK   r   haystacks     r'   rM   zGCuaDriverBackend.capture.<locals>._is_desktop_window.<locals>.<genexpr>  s(      NN48+NNNNNNr4   )r#   r   rw  _DESKTOP_WINDOW_NAMES)r   rO  s    @r'   _is_desktop_windowz4CuaDriverBackend.capture.<locals>._is_desktop_window  sb    eeJ33JJaeeGR6H6HJJPPRRNNNN8MNNNNNNr4   c                *    g | ]} |          |S rd   rd   )rK   r   rQ  s     r'   r   z,CuaDriverBackend.capture.<locals>.<listcomp>  s*    CCCQ-?-?-B-BCqCCCr4   z'<no desktop/shell window found for app=z; cua-driver captures one window at a time and exposes no whole-virtual-desktop or per-monitor capture. Call list_apps / capture(app='<AppName>') to target a specific window instead. On Windows the taskbar is 'Shell_TrayWnd' and the desktop is 'Progman'.>c                B     t           fddD                       rdndS )Nc              3     K   | ]G}|                     d d           d                     dd                                           v V  HdS )r  rC   rM  r  N)r#   r   )rK   r   r   s     r'   rM   z=CuaDriverBackend.capture.<locals>.<lambda>.<locals>.<genexpr>  sr       ' ' AEE*b11HHAEE'24F4FHHNNPPP' ' ' ' ' 'r4   )r   r   r   r   r   r   r   )rw  r#  s   `r'   r$  z*CuaDriverBackend.capture.<locals>.<lambda>  sH    3 ' ' ' '[' ' ' $ $ aa  r4   )r   z!<no on-screen window matched app=u   ; call list_apps to see available app names or bundle IDs (macOS reports localized names, e.g. '計算機' instead of 'Calculator'; some Linux/Qt apps only resolve via list_apps metadata)>c              3  *   K   | ]}|d          
|V  dS )r  Nrd   )rK   r   s     r'   rM   z+CuaDriverBackend.capture.<locals>.<genexpr>  s+      AAQ<AqAAAAAAr4   r  r  r  r  r  vision
screenshotjpegU   )r  formatqualityrG  r  )r  r  rG  rn   zAXWindow\s+"([^"]+)"r   zbcua-driver vision capture returned no image over MCP (window_id=%s); re-fetching via CLI transportr  r  Tr  z	image/pngz8cua-driver CLI re-fetch for vision screenshot failed: %sr\   c                   |                      d          rdS |                      d          pi }|                     d          s|                     d          rdS t          |                      d          t                    r|                      d          nd}t          |pd          \  }}|o|                                 S )Nr  Fr  r   r  rn   rC   )r#   rJ   r8   r   rU   )r\   sc_txt_trs        r'   _gws_is_emptyz/CuaDriverBackend.capture.<locals>._gws_is_empty  s    778$$ ! 5gg1228b 77:&& !#''2F*G*G ! 5)3CGGFOOS)I)IQcggfooor(332-288::..r4   zrcua-driver get_window_state returned an empty result over MCP (pid=%s window_id=%s); re-fetching via CLI transportz7cua-driver CLI re-fetch for get_window_state failed: %sz(\d+)\s+elements?r  r   c                6    i | ]}|j         	|j        |j         S rd   )r   r   )rK   rI  s     r'   
<dictcomp>z,CuaDriverBackend.capture.<locals>.<dictcomp>  s6     % % %?%% % %r4   )validater   r   )	r  r   r   r  r   r  r  r  image_mime_type)r   ro  r   r   )r\   ro  r   r   ))r  r  r*  r$   r  rU   r   _SCREEN_CAPTURE_SENTINELSsortedrI  nextr  r  r  r  r   r)  r{  r}  r  r  r  rJ   r8   r   r   searchr   r~   rl  r  r#   rm   rR   r   r   r  	b64decoder   r   r
   )$r  r  r  r  r  
target_pidtarget_window_idr  r   filteredr   r  r  rf  r   r   r   r  use_screenshotsc_outgws_outrA   r`  r   wtr)  cli_excrb  r   r   sc_elementsr  r   detected_widthdetected_heightrQ  s$                                      @r'   capturezCuaDriverBackend.capture  sy   , ?i3{i/++O   's++J,Y77!%5%=++[    I2!-#  GG,,..   ))+++  2++D111 ;9,,9J9J9L9LPi9i9iO O O O DCCC'CCCG ++J# J J J
 
 
    GG [Y.3.227C@@H 
++<C < < <	 	 	 G AA'AAA71:NN!%=!'!4 !#*%  	&dn 	&%DN#/
 
 "&)-$&8 ''55 =}<<  04F "00 %)%;"(#%#'#3	   ,C6+J+J( " "F~11&#/%)%;#'#3   ,C7+K+K( +5WV_c*J*JRwvPR*4004Y6== /#%88A;;L  D*  
"m>>*#'#3)-)?'+'7 
  G {{9--55113333 X.. 6")("3A"6*5    LLRT[       1< --"+!%!7#/  G"/ / / / }W%% K$d&<  
"m>>*#'#3)-)?'+'7 
  G {{9--55113333*]733 *")    LLQSZ       
 '1#&F&FN76??BD,T22MGT 	.88A #;;':;;ArFFzRRK+t,, K K:;GG>BJ4T:::% %!% % %D! (?w'G'G$G_ 2D99B +!xx{{ 		66&w??? #C2Ns2S2S/! -o -*E,F 6 6 6 #Gq 0A 56 %'+

 

 

 
	
sP   &A; ; BB N 
N>N99N>"A(R 
R:R55R:?X X#"X#leftr   )elementr   r   buttonclick_countr   ry  r   r   rz  r{  r   r   Optional[List[str]]r	   c                  | j         }|t          ddd          S |pd                                }|dvrt          ddd|d          S |d	k    rd
nd}	||d}
|)| j        t          d|	d          S ||
d<   | j        |
d<   nD|0|.| j        t          d|	d          S ||
d<   ||
d<   | j        |
d<   nt          d|	d          S |r||
d<   |                     |	|
          S )NFclick*   No active window — call capture() first.okactionr  rx  >   rx  rightmiddlezunknown button u"    — expected left, right, middle.r   double_click)r  rz  z,No active window_id for element_index click.r   r  z)No active window_id for coordinate click.r   r   zclick requires element= or x/y.modifier)r  r	   r   r  _action)r  ry  r   r   rz  r{  r   r  button_normrW  rE   s              r'   r~  zCuaDriverBackend.click  s    ;5(TV V V V '..009995(f&(f(f(fh h h h!,!1!1~~w'*kBB%-#uT,Z\ \ \ \$+D! $ 6D]q}%-#uT,WY Y Y YDIDI $ 6D5(IK K K K 	)(D||D$'''r4   )from_element
to_elementfrom_xyto_xyrz  r   r  r  r  Optional[Tuple[int, int]]r  c                  | j         }|t          ddd          S d|i}|0|.| j        t          ddd          S ||d<   ||d<   | j        |d	<   n||| j        t          ddd
          S t          |d                   t          |d                   c|d<   |d<   t          |d                   t          |d                   c|d<   |d<   | j        |d	<   nt          ddd          S |                     d|          S )NFdragr  r  r  z+No active window_id for element-based drag.r  r  r  z(No active window_id for coordinate drag.r   r   from_xfrom_yto_xto_yzGdrag requires from_element/to_element or from_coordinate/to_coordinate.)r  r	   r  r   r  )	r  r  r  r  r  rz  r   r  rE   s	            r'   r  zCuaDriverBackend.drag  sb    ;5(TV V V V %s|#
(>%-#uV,Y[ [ [ [#/D !+D $ 6D U%6%-#uV,VX X X X-0__c'!*oo*DNDN),U1XE!H&DL$v, $ 6D5(qs s s s||FD)))r4   r   )amountry  r   r   r   	directionr  c          	     t   | j         }|t          ddd          S ||t          dt          d|                    d}|| j        ||d<   | j        |d	<   nM|K|I| j        t          ddd
          S | j                            dd          r
||d<   ||d<   | j        |d	<   |                     d|          S )NFscrollr  r  r   2   )r  r  r  r   r  z*No active window_id for coordinate scroll.zinput.scroll.coordinatesrW  r   r   )r  r	   r  r  r  r)  ry  r  )	r  r  r  ry  r   r   r   r  rE   s	            r'   r  zCuaDriverBackend.scroll>  s    ;5(TV V V V "!SV__-- 
  

 4#9#E$+D! $ 6D]q}%-#uX,XZ Z Z Z }00* 1    S	S	 $ 6D||Hd+++r4   rA   c                ~    | j         }| j        }||t          ddd          S |                     d|||d          S )NF	type_textr  r  )r  r  rA   r  r  r	   r  )r  rA   r  r  s       r'   r  zCuaDriverBackend.type_textg  s]    *	;)+5(TV V V V||K9VZ)[)[\\\r4   r   c                   | j         }| j        }||t          ddd          S t          |          \  }}|st          ddd| d          S |r|                     d||||gz   d          S |                     d	|||d
          S )NFr   r  r  zCould not parse key from ''.hotkey)r  r  r   	press_key)r  r  r   )r  r  r	   r   r  )r  r   r  r  key_namer   s         r'   r   zCuaDriverBackend.keyo  s    *	;)+5(TV V V V /t44) 	O5(MT(M(M(MO O O O  	@<<#I3<z3I+K +K L L L <<Sy5=.? .? @ @ @r4   r  c                    | j         }| j        }||t          ddd          S |t          ddd          S ||||d}|                     d|          S )zBSet a value on an element. Handles AXPopUpButton selects natively.NF	set_valuer  r  z,set_value requires element= (element index).)r  r  r   r  r  )r  r  ry  r  r  rE   s         r'   r  zCuaDriverBackend.set_value  s    *	;)+5(TV V V V?5(VX X X X "$	 
  
 ||K...r4   c           	     (   | j                             dd| j        i          }|                    d          }t	          |t
                    r0t	          |                    d          t                    r|d         S |                    d          }t	          |t                    r|S t	          |t
                    r0t	          |                    d          t                    r|d         S t	          |t                    rg }|                                D ]v}t          j
        d|          }|r]|                    |                    d                                          t          |                    d                    d	           w|S g S )
Nr@  rG  r  appsrn   z(.+?)\s+\(pid\s+(\d+)\)r   r   )r   r  )r)  rq  r  r#   rJ   r.   rR   r8   
splitlinesr   rj  r   r   rU   r   )r  r\   r  rn   r  liner   s          r'   r@  zCuaDriverBackend.list_apps  sg   m%%kIt?O3PQQWW011
j$'' 	&Jz~~f7M7Mt,T,T 	&f%% wwvdD!! 	KdD!! 	 j&1A1A4&H&H 	 <dC   	D)) V VI8$?? VKK)9)9););CPQ

OO T TUUUK	r4   c                *    |                                  S rH   )r*  r   s    r'   r  zCuaDriverBackend.list_windows  s    !!###r4   Fraise_windowc                   	 |                                  }n## t          $ r |                                   w xY w|                     ||          }|r|d         nd}|rn|d         | _        |d         | _        i | _        |d         | _        | j        | j        d| _        t          ddd	|d          d
| j         d| j         d          S |                                  t          ddd| d          S )u`  Target an app for subsequent actions without stealing system focus.

        cua-driver background-automation never needs to bring a window to the
        front: capture(app=...) already selects the right window via
        list_windows. We implement focus_app as a pure window-selector —
        enumerate on-screen windows, find the best match for *app*, and store
        its pid/window_id so that subsequent click/type calls hit the right
        process.

        raise_window=True is intentionally ignored: stealing the user's focus
        is exactly what this backend is designed to avoid.
        r   Nr  r  r  rV  T	focus_appz	Targeted z (pid z	, window z) without raising window.r  Fz#No on-screen window found for app 'r  )
r*  r$   r  rI  r  r  r  r  r   r	   )r  r  r  r  matchedr   s         r'   r  zCuaDriverBackend.focus_app  sf   	((**GG 	 	 	%%'''	 --gs;;
  '0D 	%e}D%+K%8D"$&D!#J/DN'!3! !D  TF:$6 T Td>N T T"&"8T T T   
 	!!###u[$Q#$Q$Q$QS S S 	Ss     7)r4  r   urlsadditional_arguments creates_new_application_instancer4  r  r  r  c                  |s|st          d          d| j        i}|r||d<   |r||d<   |rt          |          |d<   |rt          |          |d<   |rd|d<   | j                            d	|          }|d
         p	d|d         iS )u  Idempotent launch. Returns ``{pid, bundle_id, name, windows[]}``
        so callers can skip an extra ``list_windows`` round-trip before
        ``get_window_state``.

        ``creates_new_application_instance=True`` forces a new instance
        even if the app is already running — use it when concurrent
        runs may touch the same app so each session gets its own
        isolated window.z,launch_app requires either bundle_id or namerG  r4  r   r  r  Tr  
launch_appr  rn   )rY   r  rR   r)  rq  )r  r4  r   r  r  r  rE   r\   s           r'   r  zCuaDriverBackend.launch_app  s    "  	M 	MKLLL )4+;< 	* )D 	 DL 	&::DL 	F+/0D+E+ED'(+ 	<7;D34m%%lD99&'@FCK+@@r4   c               L    |                      ddt          |          i          S )zYTerminate by pid. Equivalent to ``kill -9`` on POSIX,
        ``taskkill /F`` on Windows.kill_appr  r  r   )r  r  s     r'   r  zCuaDriverBackend.kill_app  s#     ||JC(9:::r4   )r  c               x    dt          |          i}|t          |          |d<   |                     d|          S )zActivate a window so subsequent foreground-dispatched input
        lands on it. cua-driver's docstring notes this is the cheaper
        path than per-call SetForegroundWindow flashes.r  Nr  bring_to_front)r   r  )r  r  r  rE   s       r'   r  zCuaDriverBackend.bring_to_front  s?    
 !&s3xx0  #ID||,d333r4   c                h    |                      dt          |          t          |          d          S )uH  Move the agent-cursor *overlay* to a screen point. This is a
        visual hint — it does NOT move the real OS pointer (cua-driver
        explicitly avoids stealing pointer focus). The overlay glides
        smoothly to the target, so consumers use it before a click to
        give a visible "where the agent is going" cue.move_cursor)r   r   r  )r  r   r   s      r'   r  zCuaDriverBackend.move_cursor  s,     ||MQc!ff+E+EFFFr4   r   c                    | j                             dd| j        i          }|                    d          pi }t	          |                    dd                    t	          |                    dd                    fS )zPReturn the *real* OS cursor position in screen points
        (origin top-left).get_cursor_positionrG  r  r   r   r   )r)  rq  r  r#   r   )r  r\   scs      r'   r  z$CuaDriverBackend.get_cursor_position  st     m%%!It/?#@
 
 WW())/R266#q>>""CsA$7$777r4   c                t    | j                             dd| j        i          }|                    d          pi S )zReturn the logical size of the main display in points plus
        its backing scale factor. Shape:
        ``{width, height, backing_scale_factor}``.get_screen_sizerG  r  r)  rq  r  r#   r  r\   s     r'   r  z CuaDriverBackend.get_screen_size$  sB     m%%	4+;<
 
 ww*++1r1r4   g      ?rY  rZ  )factorr[  r\  r9   r   r   r  r[  r\  c                  | j                             dt          |          t          |          t          |          t          |          t          |          t          |          |t          |          | j        d	          S )zReturn a JPEG / PNG of a sub-region of a window, optionally
        scaled. cua-driver supports zoom-to-rect for callers that need
        a higher-resolution view of a specific element.zoom)	r  r   r   r   r   r  r[  r\  rG  )r)  rq  r   r9   r  )	r  r  r   r   r   r   r  r[  r\  s	            r'   r  zCuaDriverBackend.zoom-  sn     }&&vYqauQxxeAhhFmmW'0
 0
   	r4   )	cursor_idenabledr  c               ^    dt          |          i}|r||d<   |                     d|          S )z:Toggle the agent cursor overlay's visibility for this run.r  r  set_agent_cursor_enabled)r   r  )r  r  r  rE   s       r'   r  z)CuaDriverBackend.set_agent_cursor_enabledC  s;     !*4==9 	* )D||6===r4   )glide_msdwell_msidle_hide_msr  r  r  r  r  c                   i }|t          |          |d<   |t          |          |d<   |t          |          |d<   |r||d<   |                     d|          S )u   Tune the overlay's motion timings — glide duration, post-click
        dwell, idle-hide delay. Each None means "leave at current value".Nr  r  r  r  set_agent_cursor_motion)r9   r  )r  r  r  r  r  rE   s         r'   r  z(CuaDriverBackend.set_agent_cursor_motionK  st      "$XD$XD##(#6#6D  	* )D||5t<<<r4   )gradient_colorsbloom_color
image_pathr  r  r  r  c                   i }|t          |          |d<   |||d<   |||d<   |r||d<   |                     d|          S )u   Customise the cursor body. ``gradient_colors`` are CSS hex
        strings tip→tail; ``bloom_color`` is the radial halo; an
        ``image_path`` (.svg/.png/.ico) replaces the silhouette
        entirely. Empty values revert to the palette default.Nr  r  r  r  set_agent_cursor_style)rR   r  )r  r  r  r  r  rE   s         r'   r  z'CuaDriverBackend.set_agent_cursor_style]  si      "&&*?&;&;D"#""-D!!+D 	* )D||4d;;;r4   c                   d| j         i}|r||d<   | j                            d|          }|                    d          pi S )zReturn ``{x, y, config: {cursor_color, cursor_icon, ...},
        enabled}`` for this run's cursor (or the named ``cursor_id``).rG  r  get_agent_cursor_stater  )r  r)  rq  r#   )r  r  rE   r\   s       r'   r  z'CuaDriverBackend.get_agent_cursor_stateq  sS     !*4+;< 	* )Dm%%&>EEww*++1r1r4   )record_video
output_dirr  c                   | j                             d|t          |          | j        d          }|                    d          pi S )a;  Enable trajectory recording (per-turn screenshots + action
        JSON) to ``output_dir``. ``record_video=True`` ALSO captures
        the main display to ``<output_dir>/recording.mp4`` (H.264).
        Recording ownership is keyed by this run's session id so
        concurrent runs don't fight over the recorder.start_recording)r  r  rG  r  )r)  rq  r   r  r#   )r  r  r  r\   s       r'   r  z CuaDriverBackend.start_recording}  sV     m%%&7$ ..':
 :
  
 ww*++1r1r4   c                t    | j                             dd| j        i          }|                    d          pi S )zDisable recording and finalise the mp4 (if video was on).
        Returns the recorder's final state including ``last_video_path``.stop_recordingrG  r  r  r  s     r'   r  zCuaDriverBackend.stop_recording  sE     m%%&6t'9
   ww*++1r1r4   c                t    | j                             dd| j        i          }|                    d          pi S )zReturn the current recorder state without changing it.
        Shape: ``{recording, enabled, output_dir, next_turn,
        last_video_path, last_error, owner, video_active}``.get_recording_staterG  r  r  r  s     r'   r  z$CuaDriverBackend.get_recording_state  sB     m%%!It/?#@
 
 ww*++1r1r4   )dry_runspeed_factortrajectory_dirr  r  c                   | j                             d|t          |          t          |          | j        d          S )zReplay a prior recording's turn stream by re-invoking each
        turn's tool call in lexical order. ``dry_run=True`` logs without
        actually firing the tools.replay_trajectory)r  r  r  rG  )r)  rq  r   r9   r  )r  r  r  r  s       r'   r  z"CuaDriverBackend.replay_trajectory  sH     }&&':,G}}!,//'	=
 =
   	r4   c                F    | j                             dd| j        i          S )zBootstrap ffmpeg for ``start_recording(record_video=True)``
        on Linux / Windows. macOS records natively via ScreenCaptureKit
        and doesn't need ffmpeg.install_ffmpegrG  )r)  rq  r  r   s    r'   r  zCuaDriverBackend.install_ffmpeg  s+     }&&y$*:;
 
 	
r4   c                t    | j                             dd| j        i          }|                    d          pi S )z-Return the current cua-driver runtime config.
get_configrG  r  r  r  s     r'   r  zCuaDriverBackend.get_config  s@    m%%9d&67
 
 ww*++1r1r4   c                H    |                      dt          |                    S )u   Set cua-driver config keys. Common keys include
        ``max_image_dimension`` (image-output resizing), recording
        flags, etc. Unknown keys are passed through verbatim — cua-driver
        validates against its own schema.
set_config)r  r.   )r  configs     r'   r  zCuaDriverBackend.set_config  s    
 ||L$v,,777r4   c                    | j                             dd| j        i          }|                    d          p	d|d         iS )a.  Return a lightweight snapshot of running regular apps +
        on-screen visible windows with bounds, z-order, owner pid.
        Roughly the data ``list_windows`` exposes, in one call. Most
        callers should prefer ``capture()`` / ``focus_app()`` which
        already use this shape internally.get_accessibility_treerG  r  rn   r  r  s     r'   r  z'CuaDriverBackend.get_accessibility_tree  sK     m%%$y$2B&C
 
 ww*++DF/DDr4   r  	page_argsr   c                   t          |          || j        d}|                    |           | j                            d|          S )az  Interact with a browser page loaded in a running app (Chrome,
        Safari, Edge, ...). cua-driver routes through CDP / Apple Events
        / AX tree depending on the target. ``action`` + ``page_args``
        shape depends on the requested operation (e.g. ``action="eval"``
        takes ``js: str``); see cua-driver's ``page`` tool description
        for the full grammar.)r  r  rG  page)r   r  updater)  rq  )r  r  r  r  rE   s        r'   r  zCuaDriverBackend.page  sN     s88' 
  

 	I}&&vt444r4   r  r5   rk   r6   c                   |rt          |          ni }|                    d| j                   | j                            |||          S )u  Call any cua-driver MCP tool by name with arbitrary args.
        ``session`` is injected (preserves the caller's explicit one
        via setdefault). For tools the wrapper doesn't already type-
        wrap, this is the supported escape hatch — preferred over
        reaching for ``self._session.call_tool`` directly because it
        keeps the session-id contract consistent with everything else.rG  r5   )r.   
setdefaultr  r)  rq  )r  r   rE   r6   payloads        r'   rq  zCuaDriverBackend.call_tool  sN     !%,$t***"9d&6777}&&tWg&FFFr4   rW  c                    |                     d          }t          |t                    sdS | j                             |          }|sdS | j                            d|          sdS ||d<   dS )a  Surface 6: when the wrapper is about to call a token-capable
        tool with `element_index`, look up the matching `element_token`
        from the last snapshot and attach it. cua-driver-rs's contract
        for combined args is documented in trycua/cua#1961:

          "element_token takes precedence over element_index when both
           supplied. Returns an explicit 'stale' error if the snapshot
           has been superseded."

        Gated on the per-tool capability claim so we don't send the
        field to drivers that predate the surface (which would reject
        the schema with `additionalProperties: false`).
        r   Nzaccessibility.element_tokensr  r   )r#   rJ   r   r  r)  ry  )r  rW  rE   r   r   s        r'   _maybe_attach_element_tokenz,CuaDriverBackend._maybe_attach_element_token  s     hh''#s## 	F%))#.. 	F}00* 1 
 
 	 F %_r4   c           	     F   |                      ||           |                    d| j                   	 | j                            ||          }nG# t
          $ r:}t                              d|           t          d|d|           cY d }~S d }~ww xY w|d          }d}|d         }t          |t                    r$t          |                    d	d                    }nt          |t                    r|}t          |||t          |t                    r|ni 
          S )NrG  zcua-driver %s call failedFzcua-driver error: r  r  rC   rn   r  )r  r  r  meta)r  r  r  r)  rq  r$   r~   	exceptionr	   rJ   r.   r8   r#   )r  r   rE   r\   rI  r  r  rn   s           r'   r  zCuaDriverBackend._action		  sS    	((t444
 		4#3444	Y-))$55CC 	Y 	Y 	Y8$???5?WTU?W?WXXXXXXXXX	Y Y6{dD!! 	$((9b1122GGc"" 	Gr$)3D$)?)?!GRI I I 	Is   A 
B/BBBr   r  )rC   )r  r8   r  r8   r   r
   r  )r   r   )r  r   r  r8   r   r   )rJ  NNN)
r  r8   r  rp   r  r  r  r  r   r
   )ry  r  r   r  r   r  rz  r8   r{  r   r   r|  r   r	   )r  r  r  r  r  r  r  r  rz  r8   r   r|  r   r	   )r  r8   r  r   ry  r  r   r  r   r  r   r|  r   r	   )rA   r8   r   r	   )r   r8   r   r	   rH   )r  r8   ry  r  r   r	   )F)r  r8   r  r   r   r	   )r4  rp   r   rp   r  r|  r  r|  r  r   r   ro  )r  r   r   r	   )r  r   r  r  r   r	   )r   r   r   r   r   r	   )r   r   )r   ro  )r  r   r   r9   r   r9   r   r9   r   r9   r  r9   r[  r8   r\  r   r   ro  )r  r   r  rp   r   r	   )
r  r  r  r  r  r  r  rp   r   r	   )
r  r|  r  rp   r  rp   r  rp   r   r	   )r  rp   r   ro  )r  r8   r  r   r   ro  )r  r8   r  r   r  r9   r   ro  )r   r	   )r  r   r  r8   r  r   r   ro  )r   r8   rE   rk   r6   r9   r   ro  )rW  r8   rE   ro  r   r{   )r   r8   rE   ro  r   r	   ).r   r!  r"  r#  r  r   r  r  r  r  r  r*  rI  rw  r~  r  r  r  r   r  r@  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rq  r  r  rd   r4   r'   r  r    s       JJ#B #B #B #BLZ Z Z Z@        - - - -# # # #
 
 
 
 
    ( ( ( (TM
 M
 M
 M
d !!#'I
 I
 I
 I
 I
^
 "&)-1( 1( 1( 1( 1( 1(l '+$(-1+/)- *  *  *  *  *  *L !%)-&, &, &, &, &, &,R] ] ] ]@ @ @ @*/ / / / /&   ,$ $ $ $)S )S )S )S )Sh $("$(4816A A A A A AB; ; ; ; 374 4 4 4 4 4G G G G8 8 8 82 2 2 2 !     . =A> > > > > > =A<@@D;?	= = = = = =& GK<@;?:>	< < < < < <* ;?2 2 2 2 2 2 .32 2 2 2 2 22 2 2 22 2 2 2 +003     
 
 
 
2 2 2 28 8 8 8	E 	E 	E 	E5 5 5 5$
G&*
G 
G 
G 
G 
G 
G& & & &4I I I I I Ir4   r  r  rH   )r)   r*   r   r+   )r7   r8   r6   r9   r   r:   )r6   r9   r   rk   )r   rp   r   r  )r   r8   r   r   )r   r   r   r   )r   r   r   r   )r   r8   r   r   )r   r8   r   r   )r  r   r   ro  )r\   ro  r   r  )r  r   r   r  )r  r   r   r   )>r#  
__future__r   r  r  concurrent.futuresri  rW   loggingr/   r   rg   rO   rb   r   r  typingr   r   r   r   r   tools.computer_use.backendr	   r
   r   r   	getLoggerr   r~   r0   r#   ri   rS   rg  rP  r1   r(   r3   r^   compile	MULTILINEr   re   rj   ro   rz   r   r   r   r   r   r   r   r   r   r%  rr  r  r  r  r  rd   r4   r'   <module>r     s  ! !F # " " " " "         				 				      



      3 3 3 3 3 3 3 3 3 3 3 3 3 3            
	8	$	$" *..!8,GG7  VUU   ;    &    $ 3 3 3 3 3 3P 2:. L	 	  $ $ $ $/ / / /
 14 & & & & & &R       *   *   82 2 2 2j$ $ $ $N      2/ / / / / / / /l^X ^X ^X ^X ^X ^X ^X ^XB5 5 5 5p$ $ $ $N* * * *% % % %XDI DI DI DI DI) DI DI DI DI DIr4   