
    `gj
                   f   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j6                  e      Zdd	 	 	 	 	 	 	 	 	 	 	 	 	 d+dZe	j>                  jA                  dd	      Z!d
gZ"h dZ#dZ$dZ%d,dZ&d-d.dZ'dd	 	 	 	 	 d/dZ( e
jR                  de
jT                        Z+d,dZ,d,dZ-ddd0dZ.d1dZ/da0d2dZ1d3dZ2d4dZ3d5dZ4d6dZ5d7dZ6d8d Z7 G d! d"      Z8 G d# d$      Z9d9d%Z:d:d&Z;d;d'Z<d<d(Z= G d) d*e      Z>y)=uv  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requested_deliveryc                  t        |t              r|ni dfd} |d      }t        |t              sd} |d      }t        |t              sd} |d      }	t        |	t              sd}	 |d      }
t        |
t              sd}
 |d      }t        |t              sd} |d      xs  |d	      }t        |t              sd}t        |t              r|nd}t	        || ||||	|
|||
      S )a  Build an ActionResult, lifting cua-driver's structured verdict.

    All structured fields are additive: a driver that omits
    ``structuredContent`` (or any individual field) leaves the corresponding
    ActionResult attribute ``None``, so callers and tests see unchanged
    behavior on old drivers. See the action response shape in
    cua-driver's mcp-tool-notes and NousResearch/hermes-agent#67052.
    c                P    | v rj                  |       S j                  |       S Nget)keymetascs    Q/root/.hermes/venv/lib/python3.12/site-packages/tools/computer_use/cua_backend.py_pickz"_action_result_from.<locals>._pickQ   s&    "966#;xx}    verifiedNeffect
escalationpathdegradedcodereason_code)okactionmessager   r   r   r   r   r   delivery_moder   )r   strreturnr   )
isinstancedictboolr%   r	   )namer!   r#   r   
structuredr   r   r   r   r   r   r   r   r$   r   s      `          @r   _action_result_fromr,   >   s    " "*d3B Z Hh%8_Ffc"|$Jj$'
=DdC Z Hh%=0E-0DdC  +55G*M&SWM# r   HERMES_CUA_DRIVER_CMDz
cua-drivermcp>   full screenallscreendesktop
fullscreen)progmanworkerwprogram managershell_traywndtaskbarfinderr2   dockCUA_DRIVER_RS_TELEMETRY_ENABLEDc                     	 ddl m}   |        xs i }|j                  d      xs i }t        |j                  dd             S # t        $ r Y y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=   r   r)   	Exception)r=   cfgcus      r   _cua_telemetry_disabledrD      sT    	1m!rWW^$*6777 s   AA 	AAc                f    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.
    0)r(   osenvironrD   _CUA_TELEMETRY_ENV_VAR)base_envenvs     r   cua_driver_child_envrL      s/     8/xRZZ
@C &)"#Jr   g      @timeoutc               >   	 ddl m} t        j                  | dgdd|t        j                   |t                           }|j                  xs dj                         }|j                  dk7  s|s| t        t              fS 	 t        j                  |      }t!        |t"              s| t        t              fS |j%                  d      }t!        |t"              s| t        t              fS |j%                  d      }|j%                  d	      }t!        |t              rt'        d
 |D              s| t        t              fS t!        |t(              r|s| |fS ||fS # t        $ r | t        t              fcY S w xY w# t        t        f$ r | t        t              fcY S w xY w)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textrN   stdinrK    mcp_invocationargscommandc              3  <   K   | ]  }t        |t                y wr   )r'   r%   ).0as     r   	<genexpr>z*_resolve_mcp_invocation.<locals>.<genexpr>   s     ,NAZ3-?,Ns   )tools.environments.localrQ   
subprocessrunDEVNULLrL   rA   list_CUA_DRIVER_ARGSstdoutstrip
returncodejsonloads
ValueError	TypeErrorr'   r(   r   r0   r%   )	
driver_cmdrN   rQ   procoutrR   
invocationrY   rZ   s	            r   _resolve_mcp_invocationrp      s~   $2E~~$dG$$ ))=)?@
 ;;"
#
#
%C!34 01112::c? h%4 0111./Jj$'4 0111>>&!DnnY'GdD!,N,N)N4 0111gs#7 4D=-  24 01112 	" 24 01112s#   ?E E9 E65E69 FF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 r   r   	_is_macosrw   (  s    <<8##r   c                 F    t        t        j                  t                    S )zCTrue if `cua-driver` is on $PATH or HERMES_CUA_DRIVER_CMD resolves.)r)   shutilwhich_CUA_DRIVER_CMDrv   r   r   cua_driver_binary_availabler|   ,  s    _-..r   g       @c                   	 ddl m} t        j                  t        ddgdd| t        j
                   |t                           }|j                  xs dj                         }|sy	 t        j                  |      }t        |t              r|j!                  d	      ry|S # t        $ r Y yw xY w# t        t        f$ r Y yw xY w)
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   rP   zcheck-updatez--jsonTrS   NrW   error)r_   rQ   r`   ra   r{   rb   rL   rA   re   rf   rh   ri   rj   rk   r'   r(   r   )rN   rQ   rm   rn   datas        r   cua_driver_update_checkr   1  s    E~~nh7dG $$ ))=)?@

 ;;"
#
#
%Czz# dD!TXXg%6K   	" s$   AB! (B0 !	B-,B-0CCc                     t               } | r| j                  d      sy| j                  d      xs d}| j                  d      xs 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`.)r   r   )statelatestcurrents      r   cua_driver_update_nudger   Z  sd     $%E		"45YY'(/CFii)*1cG
fX5gY ?? 	@r   Fc                 j    t         ryda dd} t        j                  | dd      j                          y)zEmit an update nudge at most once per process, off-thread so the
    (cached, ~20h) GitHub poll never blocks the first computer_use action.NTc                 j    	 t               } | rt        j                  d|        y y # t        $ r Y y w xY w)Nzcomputer_use: %s)r   rA   loggerinfo)msgs    r   _runz!_maybe_nudge_update.<locals>._runs  s<    	)+C KK*C0   		s   
& 	22zcua-driver-update-check)targetr*   daemonr&   None)_update_checked	threadingThreadstart)r   s    r   _maybe_nudge_updater   k  s3     O1 3Degr   c                 >    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.rs   )	installers    r   cua_driver_install_hintr     s?    
||w8 	3 		4 + a	ar   c           
     d   g }t         j                  |       D ]  }|j                  d      xs; |j                  d      xs( |j                  d      xs |j                  d      xs d}|j                  t	        t        |j                  d            |j                  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).
                rW         r   r   r   r   )indexrolelabelbounds)_ELEMENT_LINE_REfinditergroupappendr   int)markdownelementsmr   s       r   _parse_elements_from_treer     s     H&&x0 
JaggajJAGGAJJ!''!*J	aggaj/	
 	 Or   c           
     F   g }| D ]  }t        |t              s|j                  d      }t        |t              s7t        |j                  d      t              r|j                  d      nd}t        |j                  d      t              r|j                  d      nd}t        |j                  d      t              r|j                  d      nd}d}|rk	 t        |j                  dd	            t        |j                  d
d	            t        |j                  dd	            t        |j                  dd	            f}|j                  d      }t        |t              r|r|nd}	|j                  t        |||||	              |S # t
        t        f$ r d}Y ^w xY w)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   rW   r   frameNr   xr   ywhelement_token)r   r   r   r   r   )	r'   r(   r   r   r%   rk   rj   r   r   )
raw_elementsr   rawidxr   r   r   r   	raw_tokentokens
             r   _parse_elements_from_structuredr     sk   " !#H #t$ggo&#s#",SWWV_c"Bswwv$.swww/?$E 2$.swww/?$F D,8&		#q)*		#q)*		#q)*		#q)*	 GGO,	'	37I	4	
 	3@ O z* &%&s   A*FF F c                   | j                  d      rNt        |       dk\  r@t        j                  | dd d      }t        j                  | dd d      }|dkD  r	|dkD  r||fS | j                  d      rd}t        |       }|d	z   |k  r| |   d
k7  r|dz  }| |dz      }|dz  }|dv sd|cxk  rdk  rn n6|dz   |kD  r	 yt        j                  | ||dz    d      }|dk  s||z   |kD  r	 y|dv rS|dk\  rLt        j                  | |dz   |dz    d      }t        j                  | |dz   |dz    d      }|dkD  r	|dkD  r||fS 	 y||z  }|d	z   |k  ry)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s2bz51Br
E219!&= 
~~k"H!eai1v~QQZFFA%)?4)?1uqy   ..Qq1uu=KQ!k/A"5     !# ^^CAa!e,<eDFNN3q1uQU+;UCEqyVaZ$f},  A/ !eai2 r   c                b    | j                  dd      }|d   }t        |      dkD  r|d   nd}||fS )z?Split get_window_state text into (summary_line, tree_markdown).
r   r   rW   )splitr   )	full_textlinessummarytrees       r   _split_tree_textr     s:    OOD!$EAhG5zA~582DD=r   c                2   h d}dddd}t        j                  d|       D cg c]1  }|j                         s|j                         j                         3 }}g }d}|D ],  }|j	                  ||      }||v r|j                  |       +|}. ||fS c c}w )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optionrZ   controlr   r   r   )rZ   r   r   z[+\-]N)rer   rf   lowerr   r   )	keysMODIFIER_NAMESKEY_ALIASESpparts	modifiersr   part
normalizeds	            r   _parse_key_combor     s     [N#HHK(*4(@N1AGGIQWWY__NENI
C  __T40
'Z(C 	> Os
   B Bc                  2    e Zd ZdZddZddZdd	dZddZy)
_AsyncBridgezNRuns one asyncio loop on a daemon thread; marshals coroutines from the caller.c                R    d | _         d | _        t        j                         | _        y r   )_loop_threadr   Event_readyselfs    r   __init__z_AsyncBridge.__init__1  s    :>
37oo'r   c                P     j                   r j                   j                         ry  j                  j                          d fd}t	        j
                  |dd       _          j                   j                           j                  j                  d      st        d      y )	Nc                    t        j                          _        t        j                   j                          j                  j                          	  j                  j                          	  j                  j                          y # t        $ r Y y w xY w# 	  j                  j                          w # t        $ r Y w w xY wxY wr   )	asyncionew_event_loopr  set_event_loopr  setrun_forevercloserA   r  s   r   r   z _AsyncBridge.start.<locals>._run;  s     //1DJ""4::.KKOO

&&(JJ$$&  JJ$$&  sB   B 0B 	BBCB76C7	C CCCTzcua-driver-loop)r   r   r*         @rM   z)cua-driver asyncio bridge failed to startr   )	r  is_aliver  clearr   r   r   waitRuntimeError)r  r   s   ` r   r   z_AsyncBridge.start6  s    <<DLL113
	 !''tDGXY{{,JKK -r   c                6   ddl m} | j                  r&| j                  r| j                  j	                         s0t        j                  |      r|j                          t        d       ||| j                        }|t        d      |j                  |      S )Nr   )safe_schedule_threadsafecua-driver bridge not startedrM   )
agent.async_utilsr  r  r  r  r  iscoroutiner  r  result)r  cororN   r  futs        r   ra   z_AsyncBridge.runL  sv    >zzT\\5J5J5L""4(

>??&tTZZ8;>??zz'z**r   c                   | j                   rI| j                   j                         r/| j                   j                  | j                   j                         | j                  r| j                  j                  d       d | _        d | _         y )Ng       @rM   )r  
is_runningcall_soon_threadsafestopr  joinr  s    r   r!  z_AsyncBridge.stopW  s\    ::$**//1JJ++DJJOO<<<LLc*
r   Nr         >@)rN   Optional[float]r&   r   )__name__
__module____qualname____doc__r  r   ra   r!  rv   r   r   r   r   .  s    X(
L,	+r   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dd       Zed d       Zed!d       Zed!d       ZddZd"dZ eddh      Zd#d"dZy)$_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.
    c                    || _         d | _        t        j                         | _        d| _        i | _        d| _        t        j                         | _	        d | _
        d | _        d | _        y )NFrW   )_bridge_sessionr   Lock_lock_started_capabilities_capability_versionr  _ready_event_shutdown_event_lifecycle_future_setup_error)r  bridges     r   r  z_CuaDriverSession.__init__s  s^    ^^%
 .0(* %OO-8<!%59r   c                2    | j                   st        d      y )Nzcua-driver session not started)r1  r  r  s    r   _require_startedz"_CuaDriverSession._require_started  s    }}?@@ r   c           	       K   ddl }ddlm}m} ddlm} ddlm} t        j                         | _
        |j                         }d| _        	 t               st        t                     d| _        t!        t"              \  }}|j                         }	 ||| |t%                           }
 ||
      4 d{   \  }}d	| _         |||      4 d{   }|j'                          d{    |j                         }d
| _        | j)                  |       d{    || _        d| _        | j,                  j/                          t0        j3                  d|j                         |z
  |	|z
  ||	z
         | j                  j5                          d{    ddd      d{    ddd      d{    d| _        d| _        y7 7 7 7 7 <7 .# 1 d{  7  sw Y   >xY w7 5# 1 d{  7  sw Y   ExY w# t6        $ r'}|| _        | j,                  j/                           d}~ww xY w# d| _        d| _        w xY w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_clientrP   zbinary-checkzmanifest-discovery)rZ   rY   rK   zmcp-initializezcapability-discoveryreadyzBcua-driver session ready in %.1fs (manifest=%.1fs, mcp_init=%.1fs)F)timer.   r<  r=  mcp.client.stdior>  r_   rQ   r  r  r5  	monotonic_startup_phaser|   r  r   rp   r{   rL   
initialize_populate_capabilitiesr.  r4  r  r   r   r  BaseExceptionr7  r1  )r  _timer<  r=  r>  rQ   _t0rZ   rY   _t_manifestparamsreadwritesession_t_inites                   r   _lifecycle_coroz!_CuaDriverSession._lifecycle_coro  s)     	<1E  '}}oo -@	".0"#:#<==
 #7D3ODMGT//+K* --A-CDF $F+ 6 6}e&6#(u5 6 6!,,...#oo/G +AD'55g>>>$+DM*1D'%%))+KK;)C/#c)+- ..33555+6 66 6H !DM "DM]66. ? 6+6 6 6 66 6 6 60  	 !"D!!#	 !DM "DMs   A	IA+H 7G8H ;G,GG,G+G,/GGA8GGGG,$G%G,)H 4G*5H 9IH G,GGGG,G'	GG'	#G,*H ,G>2G53G>:H 	H1
"H,,H11H4 4IIc                  K   	 |j                          d{   }t        |dg       xs g D ]  }t        |dd      }t        |t              s!t        |dd      }|"t        |dd      xs i }|j	                  d      }t        |t
              r/|D ch c]  }t        |t              s| c}| j                  |<   t               | j                  |<    t        |dd      }|"t        |dd      xs i }|j	                  d      }t        |t              r|| _        yy7 
c c}w # t        $ r }	t        j                  d|	       Y d}	~	yd}	~	ww xY w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_extracapability_versionz5cua-driver tools/list capability discovery failed: %s)
list_toolsgetattrr'   r%   r   rc   r2  r  r3  rA   r   debug)
r  rM  
tools_listtool	tool_namecapsextraccvrO  s
             r   rE  z(_CuaDriverSession._populate_capabilities  sQ    	U&1133J
GR8>B :#D&$7	!)S1t^T:< $D->D"E 99^4DdD)#'5:a+=5D&&y1 58ED&&y1:( %94@Bz
M4@FBYY34"c"+-( #3 45  	ULLPRSTT	UsX   ED) D!A;D) D$)D$-A2D) E!D) $D) )	E2EEEEc                    | j                   5  | j                  r
	 d d d        y | j                  j                          | j	                          d| _        d d d        y # 1 sw Y   y xY w)NT)r0  r1  r-  r   _start_lifecycle_lockedr  s    r   r   z_CuaDriverSession.start  sW    ZZ 	!}}	! 	! LL ((* DM	! 	! 	!s   A1AA'c                   t        j                         | _        d| _        d| _        | j
                  j                  }|t        d      t        j                  | j                         |      | _        | j                  j                  d      s:| j                          t        | dd      }ddlm} t        d	| d
 |        d      | j                  #t        d| j                         | j                  y)z^Spawn the lifecycle owner and wait for it to reach ready.
        Caller must hold self._lock.Nr  r$  rM   rC  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  r4  r7  r5  r-  r  r  r  run_coroutine_threadsaferP  r6  r  _signal_shutdown_lockedrW  hermes_constantsrd  )r  loopphaserd  s       r   ra  z)_CuaDriverSession._start_lifecycle_locked  s    &OO- # ||!!<>??!(!A!A  "D"
   %%d%3((* D"2I>E<##(' *>&())OQ  (3D4E4E3FG$$% )r   c                    | j                   5  | j                  s
	 d d d        y d| _        | j                          d d d        y # 1 sw Y   y xY w)NF)r0  r1  _stop_lifecycle_lockedr  s    r   r!  z_CuaDriverSession.stop3  sG    ZZ 	*==	* 	* "DM'')		* 	* 	*s   AAAc                L   | j                          | j                  }|y	 |j                  d       d| _        y# t        j                  j
                  $ r t        j                  d       Y <t        $ r }t        j                  d|       Y d}~_d}~ww xY w# d| _        w xY w)zbSignal shutdown + wait for the lifecycle coroutine to unwind.
        Caller must hold self._lock.Nr  rM   z*cua-driver session shutdown timed out (5s)zcua-driver shutdown error: %s)	rf  r6  r  
concurrentfuturesTimeoutErrorr   warningrA   )r  r  rO  s      r   rk  z(_CuaDriverSession._stop_lifecycle_locked:  s     	$$&$$;
	*JJsJ# &*D" !!.. 	INNGH 	? NN:A>>	?
 &*D"s3   ; 2B-B /B7BB BB 	B#c                    | j                   j                  }| j                  }|1|.|j                         r	 |j	                  |j
                         yyyy# t        $ r Y yw xY w)z8Set the asyncio shutdown event from the caller's thread.N)r-  r  r5  r  r   r  r  )r  rh  events      r   rf  z)_CuaDriverSession._signal_shutdown_lockedM  sg    ||!!$$ 1doo6G))%))4 7H 1   s   A 	A#"A#c                l   K   | j                   j                  ||       d {   }t        |      S 7 wr   )r.  	call_tool_extract_tool_result)r  r*   rY   r  s       r   _call_tool_asyncz"_CuaDriverSession._call_tool_asyncX  s/     }}..tT::#F++ ;s    424Nc                    |&| j                   j                  |t                     v S t        fd| j                   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).
        c              3  &   K   | ]  }|v  
 y wr   rv   )r\   r\  
capabilitys     r   r^   z8_CuaDriverSession.supports_capability.<locals>.<genexpr>k  s     N$:%N   )r2  r   r  anyvalues)r  ry  rZ  s    ` r   supports_capabilityz%_CuaDriverSession.supports_capability]  sK     !3!3!7!7ce!DDDN$2D2D2K2K2MNNNr   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.
        )r2  )r  r*   s     r   	_has_toolz_CuaDriverSession._has_toolm  s     t))))r   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)   r2  r  s    r   capabilities_discoveredz)_CuaDriverSession.capabilities_discovered|  s    
 D&&''r   c                    | j                   S )u   Driver-advertised capability vocabulary version (empty string
        when the driver predates the field — older builds had no version).)r3  r  s    r   rU  z$_CuaDriverSession.capability_version  s     '''r   c                    | j                   j                  }t        | j                   dd      }|dv xs/ |j                  d      xr d|v xs t	        | t
        t        f      S )zHReturn True for MCP/stdio failures that are recoverable by reconnecting.r'  rW   >   EndOfStreamBrokenResourceErrorClosedResourceErroranyioResource)	__class__r&  rW  r   r'   BrokenPipeErrorEOFError)excr*   modules      r   _is_closed_session_errorz*_CuaDriverSession._is_closed_session_error  sd     }}%%b9QQ <!!'*AzT/A<#:;	
r   c                D    t        |       }d|v xs d|v xs
 d|v xs 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)r%   )r  r   s     r   _is_transient_daemon_errorz,_CuaDriverSession._is_transient_daemon_error  sC    " #h.#5 %#%'3.% $		
r   c                    | j                   r	 | j                          d| _         i | _        d| _        | j                          d| _         y# t        $ r }t        j	                  d|       Y d}~Qd}~ww xY w)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: %sNFrW   T)r1  rk  rA   r   rX  r2  r3  ra  r  rO  s     r   _restart_session_lockedz)_CuaDriverSession._restart_session_locked  sp     ==Z++- #% $$&  ZUWXYYZs   A 	A4A//A4c                   ddl }ddl}ddl}ddlm} t        |      }d}	|dk(  r4d|vr0|j                  dd      \  }
}	t        j                  |
       |	|d<   t        d	|t        j                  |      g}d
}d}d}d}	 t        |      D ]  }	 |j                  |ddt        d|       |t                           }|j$                  xs dj'                         }|dd xs |j(                  xs ddd }t+        d |j-                  d      |j-                  d      fD        d      }|dk7  r	 t        j.                  ||d       }||} n=||dz
  k  st2        j5                  d||dz   ||       |j7                  |       |dz  } |t#        d| d| d|       g }d}t9        |t
              r|nd}d}t9        |t
              r|j;                  d      du xs |j;                  d      du }|j;                  d       }|s~|j;                  d!      xs |	}|rgt        j<                  j?                  |      rH	 tA        |d"      5 }tC        jD                  |jG                               jI                  d#      }ddd       |r|jM                  |       |j;                  d%      }|%|j;                  d&      }|| d'nd}|r| d(| n|}||||d)|	r7t        j<                  j?                  |	      r	 t        jN                  |	       S S S # t         $ r}t#        d| d|       |d}~ww xY w# t        j0                  $ r d}Y w xY w# 1 sw Y   xY w# t         $ r!}t2        jK                  d$||       Y d}~d}~ww xY w# tP        $ r Y S w xY w# |	rFt        j<                  j?                  |	      r&	 t        jN                  |	       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   NrP   get_window_statescreenshot_out_file	cua_shot_z.png)prefixsuffixcallr   g      ?rW   Tg      .@)rT   rU   rN   rK   zcua-driver CLI fallback for z failed to spawn:    c              3  ,   K   | ]  }|d k7  s	|  yw)Nrv   )r\   r   s     r   r^   z7_CuaDriverSession._call_tool_via_cli.<locals>.<genexpr>  s     J1!r'QJ   
{[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   )r   imagesstructuredContentr  ))r`   tempfiler@  r_   rQ   r(   mkstemprG   r  r{   rh   dumpsrangera   maxrL   rA   r  re   rf   stderrminfindri   JSONDecodeErrorr   rp  sleepr'   r   r   existsopenbase64	b64encoderK  decoderX  r   removeOSError) r  r*   rY   rN   _subprocess	_tempfilerG  rQ   	call_args	shot_filefdr   attemptsbackoffparsedlast_errattemptrm   rO  rn   r   	candidater  r   r+   r  shotfpathfhr   ecr   s                                    r   _call_tool_via_cliz$_CuaDriverSession._call_tool_via_cli  s   ( 	)$EJ	#'	%%*?y*P%--[-PMB	HHRL/8I+,djj.CDN	 ? !j&??DtSwEW45I5KL + D {{(b//1t9A):DS(AJ# >J B;)$(JJs56{$;	 !,!*X\)NN=gk8W
 KK(qLG?!B ~"24&8PjH:7  !#FD3=fd3KQUJH&$' "::i0D8ZFJJz<RVZ<Zzz"67"JJ'=>K)E!6e!%eT!2 Sb'-'7'7	'B'I'I''RS MM$'zz/2#O4B24.I.bG3:gYb/D %/#	 RWW^^I6IIi( 7yG ! j&)EdVK]^_]`'abhiij  // )$(	)JS S( e"LL)Y[`bcdde&   RWW^^I6IIi(  7ys   ?N .L<A1N .L1N C&N <M 3M;M AN 6N	L.L))L..N 1M
N 	M

N MM 	N"M>9N >NN 	NN"O8OO	OOOOstart_sessionend_sessionc                   | j                   s4|| j                  vr&t        j                  d|       | j	                          | j                          	 | j                  j                  | j                  ||      |      S # t        $ r}| j                  |      r/t        j                  d||       | j                  |||      cY d }~S | j                  |      s t        j                  d|       | j                  5  | j                          d d d        n# 1 sw Y   nxY w| j                  j                  | j                  ||      |      cY d }~S d }~ww xY w)Nz=cua-driver session not active on %s; (re)starting before callrM   zIcua-driver MCP transport failed on %s (%s); falling back to CLI transportz:cua-driver MCP session closed during %s; reconnecting once)r1  _LIFECYCLE_CALLSr   rp  r   r:  r-  ra   rv  rA   r  r  r  r0  r  )r  r*   rY   rN   rO  s        r   rt  z_CuaDriverSession.call_tool6  s-    }}T-B-B!BNNOQU JJL	X<<##D$9$9$$Ew#WW 	X..q14591 ..tT7CC003 NNWY]^ /,,./ / /<<##D$9$9$$Ew#WW	XsB   ,A? ?	E:EE4E<D	ED	3EEE)r8  r   r&   r   r   )rM  r   r&   r   r*   r%   rY   Dict[str, Any]r&   r  r   )ry  r%   rZ  Optional[str]r&   r)   )r*   r%   r&   r)   r&   r)   r&   r%   )r  rA   r&   r)   )r*   r%   rY   r  rN   floatr&   r  r#  )r&  r'  r(  r)  r  r:  rP  rE  r   ra  r!  rk  rf  rv  r}  r  propertyr  rU  staticmethodr  r  r  r  	frozensetr  rt  rv   r   r   r+  r+  d  s    :(AV"p!UF!#%J**&	,
O * ( ( ( (
 
 
 
 
0sp !/=!AB'Xr   r+  c                ^   d}g }g }t        | dd      du }t        | dd      xs d}g }t        | dg       xs g D ]~  }t        |dd      }|dk(  r!|j                  t        |dd	      xs d	       6|d
k(  s<t        |dd      }	|	sL|j                  |	       t        |dd      xs d	}
|j                  |
        |rOdj                  d |D              }	 |j                         j	                  d      rt        j                  |      n|}|||||dS # t
        j                  $ r |}Y w xY w)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typerU   rW   imager   mimeTyper   c              3  &   K   | ]	  }|s|  y wr   rv   )r\   ts     r   r^   z'_extract_tool_result.<locals>.<genexpr>  s     7Q17s   )r  r  )r   r  image_mime_typesr  r  )rW  r   r"  rf   r   rh   ri   r  )
mcp_resultr   r  r  r  r+   text_chunksr   ptypeb64mimejoineds               r   ru  ru  `  sP   . DF"$ z9e4<H!(5H$!O!WSWJK
Ir28b 	.fd+F?wtVR8>B?g$-Cc"tZ6<" ''-	. 7k77	)/)B)B:)N4::f%TZD ,'  ## 	D	s   6D D,+D,c                ^   | j                  d      xs g }|r/|d   r*| j                  d      xs g }|r
|d   r|d   nd}|d   |fS | j                  d      xs i }|j                  d      xs |j                  d      }|r,|j                  d      xs |j                  d	      xs d}||fS y
)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)NNr   )rn   r  mimesr  r+   r  s         r   _image_from_tool_resultr    s    ( WWX$"F&)*+1r U1XuQx4ay$,-3J
..-
.
K*..2KC
NN12 ~~k* 	
 Dyr   c                    t        | t              st        | t        t        f      sy	 t        |       }|dkD  r|S dS # t        $ r Y yw xY w)zCReturn a positive integer, rejecting booleans and malformed values.Nr   )r'   r)   r   r%   rj   )valuer  s     r   _positive_intr    sQ    %jc
&CU aZ6)T)  s   = 	A	A	c                   g }| D ]  }t        |j                  d            }t        |j                  d            }||<|j                  d      }t        |t        t        f      rt        |t
              s|nd}|j                  |j                  dd      |||j                  dd       |j                  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_idz_indexr   app_namerW   is_on_screenTtitler  r  r  
off_screenr  r  )r  r   r'   r   r  r)   r   )raw_windowswindowsr   pid_intwindow_id_intz_rawr  s          r   _ingest_windowsr    s    * %'G e-%aeeK&89?m3i %ec5\::eUYCZ%`aj"-&eeND99UU7B'
 	 Nr   c            	      6   e Zd ZdZdCdZdCdZdCdZdDdZdCdZdEdFdZ	dGdZ
dHd	Z	 	 	 	 	 	 dId
Z	 	 	 	 dJ	 	 	 	 	 	 	 	 	 dKdZ	 	 	 	 	 	 	 	 	 	 dLdZddddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dMdZddddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dNdZdddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dOdZddd	 	 	 dPdZddd	 	 	 dQdZdRdSdZdHdZdHdZdTdUdZdddddd	 	 	 	 	 	 	 	 	 	 	 dVd ZdWd!Zdd"	 	 	 dXd#ZdYd$ZdZd%Zd[d&Zd'd(d)d*	 	 	 	 	 	 	 d\d+Zdd,	 	 	 d]d-Zddddd.	 	 	 	 	 	 	 	 	 d^d/Z ddddd0	 	 	 	 	 	 	 	 	 d_d1Z!dd,	 	 	 d`d2Z"dd3	 	 	 dad4Z#d[d5Z$d[d6Z%dd'd7	 	 	 	 	 dbd8Z&d[d9Z'd[d:Z(dcd;Z)d[d<Z*	 	 	 	 ddd=Z+dRd>d?	 	 	 ded@Z,dfdAZ-dgdBZ.y)hCuaDriverBackendz@Default computer-use backend. Cross-platform via cua-driver MCP.c                    t               | _        t        | j                        | _        d | _        d | _        d | _        d | _        i | _        dt        j                         j                  d d  | _        y )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__  sm    #~)$,,7*.04(, AE 13$ #*$**,*:*:3B*?)@ Ar   c                >   t                ddlm}  |dd       dd l}|j	                          | j
                  j                          	 | j
                  j                  dd| j                  i       y # t        $ r }t        j                  d|       Y d }~y d }~ww xY w)	Nr   )ensureztool.computer_useF)promptr  rM  z:cua-driver start_session failed (continuing anonymous): %s)r   tools.lazy_depsr  	importlibinvalidate_cachesr.  r   rt  r  rA   r   rX  )r  _lazy_ensurer  rO  s       r   r   zCuaDriverBackend.start  s     	;(7 	##%	ZMM##OiAQAQ5RS 	ZLLUWXYY	Zs   
(A3 3	B<BBc                   | j                   j                  r)	 | j                   j                  dd| j                  i       	 | j                   j                          | j                  j                          y # t        $ r }t
        j                  d|       Y d }~Zd }~ww xY w# | j                  j                          w xY w)Nr  rM  z7cua-driver end_session failed (continuing teardown): %s)	r.  r1  rt  r  rA   r   rX  r!  r-  r  s     r   r!  zCuaDriverBackend.stop?  s     ==!![''	4CSCS7TU	 MM LL  [VXYZZ[
 LLs#   (A6 B" 6	B?BB"B>c                <    t         j                  dvryt               S )N)rr   r   linuxF)rt   ru   r|   r  s    r   is_availablezCuaDriverBackend.is_availableO  s    
 <<;;*,,r   c                J    d| _         d| _        d| _        d| _        i | _        y)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_targetX  s)    !%  "r   c           
     H    | j                          t        |dddg d|d      S )zAReturn an empty capture after disarming any prior target context.r   NrW   )moder   r   r  r   appwindow_titlepng_bytes_len)r  r
   )r  r  r#   s      r   _failed_capturez CuaDriverBackend._failed_capture`  s3    !!# 	
 		
r   c                B   	 | j                   j                  ||      }|j	                  d      du rP|j	                  d      }| j                          t        d| dt        |t              r|rd| z         dz         |S # t        $ r | j                           w xY w)zKCall a capture-stage tool and disarm state on transport or logical failure.r  Tr   r   z failedz: rW   )r.  rt  rA   r  r   r  r'   r%   )r  r*   rY   rn   r#   s        r   _call_capture_toolz#CuaDriverBackend._call_capture_tooln  s    	--))$5C 779%ggfoG%%'dV7+%/%='Ry>S OQS  
  	%%'	s   B Bc                   | j                  dd| j                  d      }|j                  d      xs i j                  d      xs g }t        |      }|j	                  d d       |r|S t
        j                  d       	 | j                  j                  dd| j                  dd	      }|j                  d      du r't
        j                  d       | j                          g S |j                  d      xs i j                  d      xs g }t        |      }|j	                  d d       |S # t        $ r"}t
        j                  d
|       g cY d}~S d}~ww xY w)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_onlyrM  r  r  c                    | d   S Nr  rv   r   s    r   <lambda>z0CuaDriverBackend._load_windows.<locals>.<lambda>  
    1Y< r   )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&  rv   r'  s    r   r(  z0CuaDriverBackend._load_windows.<locals>.<lambda>  r)  r   )r!  r  r   r  sortr   rp  r.  r  rA   r~   r  )r  rn   r  r  cli_outr  s         r   _load_windowszCuaDriverBackend._load_windows~  sI    %%#0@0@A
 ww239r>>yIOR!+./>N,	
	mm66#'D4D4DEG ;;y!T)LLUV%%'I{{#67=2BB9MSQS!+./>  	LLNPSTI	s   *D. .	E7EEEc           
        |j                         j                         sg S |D cg c]>  }t        |j                  dd            j                         j                         k(  r|@ }}|r|S 	 | j	                         }t               }t               }|D ]	  }	t        |	t              r|	j                  d      du r(|	j                  d      }
t        |
t              st        |
t        t        f      s`	 t        |
      }|dk  rrd	D ch c]Q  }t        |	j                  |      x}t              r.|j                         r|j                         j                         S }}|v r|j                  |       t        fd
|D              s|j                  |        |D cg c]  }|j                  d      |v s| }}|r|S |D cg c]/  }t        |j                  dd            j                         v r|1 }}|r|S |D cg c]  }|j                  d      |v s| }}|r|S |D cg c]X  }t        |j                  dd            j                         s-t        |j                  dd            j                         v r|Z c}S c c}w # t
        $ r$}t        j                  d||       g }Y d}~@d}~ww xY w# t        $ r Y >w xY wc c}w c c}w c c}w c c}w c c}w )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.
        r  rW   z1computer_use list_apps fallback failed for %r: %sNrunningFr  r   )	bundle_idbundleIdr*   r  display_namec              3  &   K   | ]  }|v  
 y wr   rv   )r\   alias	app_lowers     r   r^   z:CuaDriverBackend._match_windows_for_app.<locals>.<genexpr>  s     =EY%'=rz  r  )rf   r   r%   r   	list_appsrA   r   rX  r  r'   r(   r)   r   rj   addr{  )r  r  r  r   direct_exactrunning_appsr  
exact_pidspartial_pidsraw_appraw_pidr  r   r  aliasesmetadata_exactdirect_partialmetadata_partialr6  s                     @r   _match_windows_for_appz'CuaDriverBackend._match_windows_for_app  s    IIK%%'	I 
Cj" 56<<>DDFF 
 
 	>>+L  #u
!$# 	&Ggt,I0F%0Okk%(G'4(
7S#J0O'l ax YC(88u3?EKKM ##%G 
 G#s#=W==  %+	&. &-Ke
0J!KK!! 
Cj" 56<<>> 
 
 !!'.O!!%%,,2NAOO## 
quuZ,-335Sw!34::<< 
 	
w
  	 LLLcSVWL	"  
 L
 P
s\   AJ2J 9K
AK)KK4K$K)'K)5AK.	K#KK
	KKNc                   ||Q||| j                  |d      S t        |      }t        |      }||| j                  |d      S |xs d||ddddg}n$	 | j                         }|s| j                  |      S |k|i|rg|j                         j                         t        v rCd/d}|D 	cg c]  }	 ||	      s|	 }
}	|
s| j                  |d	|d
      S t        |
d       }n2|0|.|r,| j                  ||      }|s| j                  |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                   j#                  d      xs | j                   j$                   }d}|r=| j'                  d| j                  dd| j(                  d      }t+        |      \  }}|sd}|| j'                  d| j                  | j                  | j(                  d      }t+        |      \  }}t-        |d   t.              r|d   nd}t1        |      \  }}t3        j4                  d|      }|r|j7                  d      }|svt8        j;                  d| j                         	 | j                   j=                  d| j                  | j                  | j(                  dd      }|j?                  d       d!u r| j	                          n|j?                  d"      r
|d"   d   }d#}n| j'                  d| j                  | j                  | j(                  d      }d0d%} ||      rt8        j;                  d&| j                  | j                         	 | j                   j=                  d| j                  | j                  | j(                  dd      }|j?                  d       d!u r| j	                          n
 ||      s|}t-        |d   t.              r|d   nd}t1        |      \  }}t3        j4                  d(|      }|j?                  d)      xs i j?                  d*      } t-        | tB              r| rtE        |       }n|rtG        |      ng }|D !ci c]%  }!|!jH                  r|!jJ                  |!jH                  ' c}!| _        t+        |      \  }}t3        j4                  d|      }|r|j7                  d      }d}"|r9	 tM        jN                  |d+      }#tQ        |#      }"tS        |#      \  }$}%|$r|%r|$}|%}tU        ||||||||"|.	      S # t        $ r | j	                           w xY wc c}	w # t        $ r }t8        jA                  d$|       Y d}~d}~ww xY w# t        $ r!}t8        jA                  d'|       Y d}~d}~ww xY wc c}!w # t        $ r tQ        |      d,z  d-z  }"Y w xY w)1u   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>rW   Fr   r  c                    | j                  dd       d| j                  dd       j                         t        fdt        D              S )Nr  rW    r  c              3  &   K   | ]  }|v  
 y wr   rv   )r\   r*   haystacks     r   r^   zGCuaDriverBackend.capture.<locals>._is_desktop_window.<locals>.<genexpr>9  s     N48+Nrz  )r   r   r{  _DESKTOP_WINDOW_NAMES)r   rH  s    @r   _is_desktop_windowz4CuaDriverBackend.capture.<locals>._is_desktop_window7  sF    eeJ34AaeeGR6H5IJPPRN8MNNNr   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                4     t         fddD              rdS dS )Nc              3     K   | ];  }|j                  d d       dj                  dd       j                         v  = yw)r  rW   rF  r  N)r   r   )r\   r   r   s     r   r^   z=CuaDriverBackend.capture.<locals>.<lambda>.<locals>.<genexpr>M  sJ      ' AEE*b12!AEE'24F3GHNNPP's   AA)r4   r5   r6   r9   r2   r   r   )r{  r'  s   `r   r(  z*CuaDriverBackend.capture.<locals>.<lambda>M  s%    3 '[' $a   r   )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    r	|  yw)r  Nrv   )r\   r   s     r   r^   z+CuaDriverBackend.capture.<locals>.<genexpr>b  s     AQ<qAr  r  r  r  r  r  vision
screenshotjpegU   )r  formatqualityrM  r  )r  r  rM  r   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: %sc                H   | j                  d      ry| j                  d      xs i }|j                  d      s|j                  d      ryt        | j                  d      t              r| j                  d      nd}t        |xs d      \  }}|xr |j	                          S )Nr  Fr  r   r  r   rW   )r   r'   r%   r   rf   )rn   sc_txt_trs        r   _gws_is_emptyz/CuaDriverBackend.capture.<locals>._gws_is_empty  s    778$ gg128b 77:&#''2F*G )3CGGFOS)Icggfor(32-288:..r   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   )validater   r   )	r  r   r   r  r   r  r  r  image_mime_type)r   r  r&   r)   )rn   r  r&   r)   )+r  r  r.  rA   r  rf   r   _SCREEN_CAPTURE_SENTINELSsortedrC  nextr  r  r  r  r  r.  r  r  r!  r  r  r'   r%   r   r   searchr   r   rp  r  r   r~   rc   r   r   r   r   r  	b64decoder   r   r
   )&r  r  r  r  r  
target_pidtarget_window_idr  rJ  r   r2   filteredr   r  r  r\  r   r   r   r  use_screenshotsc_outgws_outrU   rX  r   wtr-  cli_excrZ  r   r   sc_elementsrO  r  r   detected_widthdetected_heights&                                         r   capturezCuaDriverBackend.capture  s   , ?i3{i/++O  's+J,Y7!%5%=++[   I2!-# G,,. ++D11 ;9,9J9J9LPi9iO #*CQ-?-BqCGC++A# II J
 
 G [Y.3227C@H++;C7 C; <	 	 G A'A71:N!%=!'!4 !#*% dnn%DN##//
 "&)-$&8 ''5 =}}<<<  04F00 %)%;%;"(#%#'#3#3	 ,C6+J( "F~11&#//%)%;%;#'#3#3 ,C7+K( +5WV_c*JwvPR*404YY6=#%88A;L D**
"mm>>*#'#3#3)-)?)?'+'7'7
 G {{9-5113 X.")("3A"6*5 --"++!%!7!7#//G"/ W%K$$d&<&<
"mm>>*#'#3#3)-)?)?'+'7'7
 G {{9-5113*73") '1#&F76?BD,T2MGT 		.8A #;;':;ArFFzRK+t,:;G>B4T: "%?? (%D! (?w'G$G_ 2D9B!xx{6&&w? #C2Ns2S/!o*E,F %'+

 
	
g  ))+. DZ ! LLRT[ p ! LLQSZ 6%2  6 #Gq 0A 56sb   U( ,V:VA>V .A-V7 .*W$8W) (V	V4V//V47	W! WW!)XXc                    |r|dk(  ry|dk7  rt        d|dd|d      S | j                  j                  d	|
      st        d|ddd      S d|d<   |rd|d<   y)a8  Attach delivery_mode to an input-action args dict.

        Background is the default and never needs a flag. Foreground is only
        sent when the driver advertises support for it; on an older driver
        that lacks the capability we refuse with a structured
        ``foreground_unsupported`` result instead of silently downgrading to
        background (which would land the input somewhere the model didn't
        expect). Returns an ActionResult to short-circuit on refusal, or None
        to proceed. See NousResearch/hermes-agent#67052 phase B.
        
backgroundN
foregroundFbad_delivery_modezunknown delivery_mode u    — use background|foreground.)r!   r"   r   r#   zinput.delivery_moderZ  foreground_unsupportedzThis cua-driver build does not support foreground delivery (no `input.delivery_mode` capability). Update cua-driver to escalate to the foreground rung.)r!   r"   r   r$   r#   r$   Tbring_to_front)r	   r.  r}  )r  r"   rY   r$   rt  s        r   _apply_deliveryz CuaDriverBackend._apply_deliveryD  s    "  =L(.A00AA`a 
 }}00! 1 
  .F*E	  !-_%)D!"r   leftr   F)elementr   r   buttonclick_countr   r$   rt  c                  | j                   }	|	t        ddd      S |xs dj                         }
|
dvrt        ddd|d      S |d	k(  rd
nd}|	|
d}|/| j                  t        d|d      S ||d<   | j                  |d<   nF|6|4| j                  t        d|d      S ||d<   ||d<   | j                  |d<   nt        d|d      S |r||d<   | j	                  ||||      }||S | j                  ||      S )NFclick*   No active window — call capture() first.r!   r"   r#   rv  >   rv  rightmiddlezunknown button u"    — expected left, right, middle.r   double_click)r  rx  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  ru  _action)r  rw  r   r   rx  ry  r   r$   rt  r  button_normrZ  rY   refusals                 r   r{  zCuaDriverBackend.clickn  sd    ;5(TV V '..0995*9&Ce(fh h!,!1~w'*kB%%-#uT,Z\ \$+D! $ 6 6D]q}%%-#uT,WY YDIDI $ 6 6D5(IK K(D&&tT=.QN||D$''r   )from_element
to_elementfrom_xyto_xyrx  r   r$   rt  c               
   | j                   }	|	t        ddd      S d|	i}
|6|4| j                  t        ddd      S ||
d<   ||
d<   | j                  |
d	<   n|r|p| 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 | j	                  d|
||      }||S | j                  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   ru  r  )r  r  r  r  r  rx  r   r$   rt  r  rY   r  s               r   r  zCuaDriverBackend.drag  sQ    ;5(TV V %s|#
(>%%-#uV,Y[ [#/D !+D $ 6 6D U%6%%-#uV,VX X-0_c'!*o*DNDN),U1XE!H&DL$v, $ 6 6D5(qs s&&vt]NSN||FD))r   r   )amountrw  r   r   r   r$   rt  c          	        | j                   }	|	t        ddd      S |	|t        dt        d|            d}
|!| j                  ||
d<   | j                  |
d	<   nT|R|P| j                  t        ddd
      S | j
                  j                  dd      r
||
d<   ||
d<   | j                  |
d	<   | j                  d|
||      }||S | j                  d|
      S )NFscrollr|  r}  r   2   )r  	directionr  r   r  z*No active window_id for coordinate scroll.zinput.scroll.coordinatesrr  r   r   )	r  r	   r  r  r  r.  r}  ru  r  )r  r  r  rw  r   r   r   r$   rt  r  rY   r  s               r   r  zCuaDriverBackend.scroll  s    ;5(TV V "!SV_- 

 4#9#9#E$+D! $ 6 6D]q}%%-#uX,XZ Z }}00* 1  S	S	 $ 6 6D&&x}nUN||Hd++r   )r$   rt  c                   | j                   }| j                  }||t        ddd      S |||d}| j                  d|||      }||S | j	                  d|      S )NF	type_textr|  r}  )r  r  rU   )r  r  r	   ru  r  )r  rU   r$   rt  r  r  rY   r  s           r   r  zCuaDriverBackend.type_text  sv    **	;)+5(TV V'*DQ&&{D-XN||K..r   c               f   | j                   }| j                  }||t        ddd      S t        |      \  }}|st        ddd| d      S |r4||||gz   d}| j	                  d|||      }	|	|	S | j                  d|      S |||d	}| j	                  d
|||      }	|	|	S | j                  d
|      S )NFr   r|  r}  zCould not parse key from ''.)r  r  r   hotkey)r  r  r   	press_key)r  r  r	   r   ru  r  )
r  r   r$   rt  r  r  key_namer   rY   r  s
             r   r   zCuaDriverBackend.key  s    **	;)+5(TV V /t4)5*DTF"(MO O +.Y,5
,B$DD**8T=.YG"<<$//YxHD**;m^\G"<<T22r   c                    | j                   }| j                  }||t        ddd      S |t        ddd      S ||||d}| j                  d|      S )zBSet a value on an element. Handles AXPopUpButton selects natively.F	set_valuer|  r}  z,set_value requires element= (element index).)r  r  r   r  )r  r  r	   r  )r  r  rw  r  r  rY   s         r   r  zCuaDriverBackend.set_value%  s{    **	;)+5(TV V?5(VX X "$	 
 ||K..r   c           	        | j                   j                  dd| j                  i      }|j                  d      }t	        |t
              r$t	        |j                  d      t              r|d   S |j                  d      }t	        |t              r|S t	        |t
              r$t	        |j                  d      t              r|d   S t	        |t              r{g }|j                         D ]d  }t        j                  d|      }|s|j                  |j                  d      j                         t        |j                  d            d	       f |S g S )
Nr7  rM  r  appsr   z(.+?)\s+\(pid\s+(\d+)\)r   r   )r*   r  )r.  rt  r  r   r'   r(   rc   r%   
splitlinesr   r`  r   r   rf   r   )r  rn   r+   r   r  liner   s          r   r7  zCuaDriverBackend.list_apps8  s   mm%%kIt?O?O3PQWW01
j$'Jz~~f7Mt,Tf%% wwvdD!KdD!j&1A4&H<dC D) VII8$?KK)9)9);CPQ
O TUV K	r   c                "    | j                         S r   )r.  r  s    r   r#  zCuaDriverBackend.list_windowsN  s    !!##r   c                   	 | j                         }| j                  ||      }|r|d   nd}|rr|d   | _        |d   | _        i | _        |d   | _        | j                  | j
                  d| _        t        ddd	|d    d
| j                   d| j
                   d      S | j                          t        ddd| d      S # t        $ r | j                           w xY w)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  rN  T	focus_appz	Targeted z (pid z	, window z) without raising window.r}  Fz#No on-screen window found for app 'r  )
r.  rA   r  rC  r  r  r  r  r  r	   )r  r  raise_windowr  matchedr   s         r   r  zCuaDriverBackend.focus_appQ  s   	((*G
 --gs;
  'D%e}D%+K%8D"$&D!#J/DN''!33!D  #F:$6#7vd>N>N=O P""&"8"8!99RT 
 	!!#u[&I#b$QS 	S3  	%%'	s   C C)r1  r*   urlsadditional_arguments creates_new_application_instancec                   |s|st        d      d| j                  i}|r||d<   |r||d<   |rt        |      |d<   |rt        |      |d<   |rd|d<   | j                  j	                  d	|      }|d
   xs 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 namerM  r1  r*   r  r  Tr  
launch_appr  r   )rj   r  rc   r.  rt  )r  r1  r*   r  r  r  rY   rn   s           r   r  zCuaDriverBackend.launch_app  s    " KLL )4+;+;< )DDL:DL+/0D+ED'(+7;D34mm%%lD9&'@FCK+@@r   c               <    | j                  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::r   )r  c               `    dt        |      i}|t        |      |d<   | j                  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  r  rt  )r   r  )r  r  r  rY   s       r   rt  zCuaDriverBackend.bring_to_front  s8    
 !&s3x0  #ID||,d33r   c                P    | j                  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!f+EFFr   c                    | j                   j                  dd| j                  i      }|j                  d      xs i }t	        |j                  dd            t	        |j                  dd            fS )zPReturn the *real* OS cursor position in screen points
        (origin top-left).get_cursor_positionrM  r  r   r   r   )r.  rt  r  r   r   )r  rn   r   s      r   r  z$CuaDriverBackend.get_cursor_position  sg     mm%%!It/?/?#@
 WW()/R266#q>"CsA$777r   c                |    | j                   j                  dd| j                  i      }|j                  d      xs 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_sizerM  r  r.  rt  r  r   r  rn   s     r   r  z CuaDriverBackend.get_screen_size  s?     mm%%	4+;+;<
 ww*+1r1r   g      ?rQ  rR  )factorrS  rT  c                   | j                   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  rS  rT  rM  )r.  rt  r   r  r  )	r  r  r   r   r   r   r  rS  rT  s	            r   r  zCuaDriverBackend.zoom  s[     }}&&vYqauQxeAhFmW''0
  	r   )	cursor_idc               N    dt        |      i}|r||d<   | j                  d|      S )z:Toggle the agent cursor overlay's visibility for this run.enabledr  set_agent_cursor_enabled)r)   r  )r  r  r  rY   s       r   r  z)CuaDriverBackend.set_agent_cursor_enabled  s1     !*4=9 )D||6==r   )glide_msdwell_msidle_hide_msr  c                   i }|t        |      |d<   |t        |      |d<   |t        |      |d<   |r||d<   | j                  d|      S )u   Tune the overlay's motion timings — glide duration, post-click
        dwell, idle-hide delay. Each None means "leave at current value".r  r  r  r  set_agent_cursor_motion)r  r  )r  r  r  r  r  rY   s         r   r  z(CuaDriverBackend.set_agent_cursor_motion  se      "$XD$XD##(#6D  )D||5t<<r   )gradient_colorsbloom_color
image_pathr  c               t    i }|t        |      |d<   |||d<   |||d<   |r||d<   | j                  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.r  r  r  r  set_agent_cursor_style)rc   r  )r  r  r  r  r  rY   s         r   r  z'CuaDriverBackend.set_agent_cursor_style  s^      "&&*?&;D"#""-D!!+D )D||4d;;r   c                   d| j                   i}|r||d<   | j                  j                  d|      }|j                  d      xs i S )zReturn ``{x, y, config: {cursor_color, cursor_icon, ...},
        enabled}`` for this run's cursor (or the named ``cursor_id``).rM  r  get_agent_cursor_stater  )r  r.  rt  r   )r  r  rY   rn   s       r   r  z'CuaDriverBackend.get_agent_cursor_state	  sM     !*4+;+;< )Dmm%%&>Eww*+1r1r   )record_videoc                   | j                   j                  d|t        |      | j                  d      }|j	                  d      xs 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)
output_dirr  rM  r  )r.  rt  r)   r  r   )r  r  r  rn   s       r   r  z CuaDriverBackend.start_recording	  sK     mm%%&7$ .'':
 
 ww*+1r1r   c                |    | j                   j                  dd| j                  i      }|j                  d      xs i S )zDisable recording and finalise the mp4 (if video was on).
        Returns the recorder's final state including ``last_video_path``.stop_recordingrM  r  r  r  s     r   r  zCuaDriverBackend.stop_recording,	  sB     mm%%&6t''9
  ww*+1r1r   c                |    | j                   j                  dd| j                  i      }|j                  d      xs 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_staterM  r  r  r  s     r   r  z$CuaDriverBackend.get_recording_state4	  s?     mm%%!It/?/?#@
 ww*+1r1r   )dry_runspeed_factorc               |    | j                   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)trajectory_dirr  r  rM  )r.  rt  r)   r  r  )r  r  r  r  s       r   r  z"CuaDriverBackend.replay_trajectory=	  s>     }}&&':,G}!,/''	=
  	r   c                R    | j                   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_ffmpegrM  )r.  rt  r  r  s    r   r  zCuaDriverBackend.install_ffmpegJ	  s+     }}&&y$*:*:;
 	
r   c                |    | j                   j                  dd| j                  i      }|j                  d      xs i S )z-Return the current cua-driver runtime config.
get_configrM  r  r  r  s     r   r  zCuaDriverBackend.get_configT	  s=    mm%%9d&6&67
 ww*+1r1r   c                8    | j                  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7r   c                    | j                   j                  dd| j                  i      }|j                  d      xs 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_treerM  r  r   r  r  s     r   r  z'CuaDriverBackend.get_accessibility_treed	  sH     mm%%$y$2B2B&C
 ww*+DF/DDr   c                   t        |      || j                  d}|j                  |       | j                  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"   rM  page)r   r  updater.  rt  )r  r  r"   	page_argsrY   s        r   r  zCuaDriverBackend.pageq	  sC     s8'' 

 	I}}&&vt44r   r$  rM   c                   |rt        |      ni }|j                  d| j                         | 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.rM  rM   )r(   
setdefaultr  r.  rt  )r  r*   rY   rN   payloads        r   rt  zCuaDriverBackend.call_tool	  sC     !%$t*"9d&6&67}}&&tWg&FFr   c                    |j                  d      }t        |t              sy| j                  j                  |      }|sy| j                  j                  d|      sy||d<   y)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_tokensrr  r   )r   r'   r   r  r.  r}  )r  rZ  rY   r   r   s        r   _maybe_attach_element_tokenz,CuaDriverBackend._maybe_attach_element_token	  sf     hh'#s#%%))#.}}00* 1 
  %_r   c           
        | j                  ||       |j                  d| j                         	 | j                  j	                  ||      }|d    }|d   }|j                  d      xs i }d	}t        |t              rt        |j                  d
d	            }nt        |t              r|}|s+t        |t              rt        |j                  d
d	            }i }	t        |t              r|	j                  |       t        |t              r|	j                  |       t        ||||	||j                  d            S # t
        $ r1}t        j                  d|       t        d|d|       cY d }~S d }~ww xY w)NrM  zcua-driver %s call failedFzcua-driver error: r}  r  r   r  rW   r#   r$   r   )r  r  r  r.  rt  rA   r   	exceptionr	   r   r'   r(   r%   r  r,   )
r  r*   rY   rn   rO  r!   r   r+   r#   r   s
             r   r  zCuaDriverBackend._action	  s[    	((t4
 		4#3#34	Y--))$5C Y6{WW017R
dD!$((9b12Gc"G:j$7*..B78G  "dD!KKj$'KK
#"4WdJ6:hh6OQ 	Q)  	Y8$?5ASTUSV?WXX	Ys   D= =	E7&E2,E72E7r   r  )rW   )r  r%   r#   r%   r&   r
   r  )r&   List[Dict[str, Any]])r  r  r  r%   r&   r  )somNNN)
r  r%   r  r  r  Optional[int]r  r  r&   r
   )
r"   r%   rY   r  r$   r  rt  r)   r&   zOptional[ActionResult])rw  r  r   r  r   r  rx  r%   ry  r   r   Optional[List[str]]r$   r  rt  r)   r&   r	   )r  r  r  r  r  Optional[Tuple[int, int]]r  r  rx  r%   r   r  r$   r  rt  r)   r&   r	   )r  r%   r  r   rw  r  r   r  r   r  r   r  r$   r  rt  r)   r&   r	   )rU   r%   r$   r  rt  r)   r&   r	   )r   r%   r$   r  rt  r)   r&   r	   r   )r  r%   rw  r  r&   r	   )F)r  r%   r  r)   r&   r	   )r1  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&   Tuple[int, int])r&   r  )r  r   r   r  r   r  r   r  r   r  r  r  rS  r%   rT  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	   )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*   r%   rY   Optional[Dict[str, Any]]rN   r  r&   r  )rZ  r%   rY   r  r&   r   )r*   r%   rY   r  r&   r	   )/r&  r'  r(  r)  r  r   r!  r  r  r  r!  r.  rC  rm  ru  r{  r  r  r  r   r  r7  r#  r  r  r  rt  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rt  r  r  rv   r   r   r  r    s   J#BLZ@  -#
 (TM
+M
25M
	M
d !!#'I
I
 I
 	I

 !I
 
I
X
(( ( %	(
 ( 
 (Z "&)-'+$6( 6( 	6(
 6( 6( 6( '6( %6( 6( 
6(v '+$(-1+/)-'+$%* $%* "	%*
 +%* )%* %* '%* %%* %* 
%*V !%)-'+$+, +, 	+,
 +, +, +, '+, %+, +, 
+,\ FJ)./"&/3?/ @D#(3 3-93:/&,$)Sh $("$(4816A !A 	A
 "A 2A +/A 
AB; 374"/4;G4G82 !*-#1. =A>,9>EQ> =A<@@D;?	=*9=*9= />= ,9	= EQ	=& GK<@;?:>	<0C<,9< ,9< +8	< DP	<* ;?2*72CQ2 .32&*27E222 +003#'(-8F
28	E55"05$
G&*
G#
G/=
G&4 Qr   r  )r*   r%   r!   r)   r#   r%   r   r  r+   r  r   r  r&   r	   r  r   )rJ   zOptional[Dict[str, str]]r&   zDict[str, str])rl   r%   rN   r  r&   zTuple[str, List[str]])rN   r  r&   r  )r&   r  r   r  )r   r%   r&   List[UIElement])r   r  r&   r  )r   bytesr&   r  )r   r%   r&   zTuple[str, str])r   r%   r&   zTuple[Optional[str], List[str]])r  r   r&   r  )rn   r  r&   z#tuple[Optional[str], Optional[str]])r  r   r&   r  )r  r  r&   r  )?r)  
__future__r   r  r  concurrent.futuresrm  rh   loggingrG   r   ry   r`   rt   r   r	  typingr   r   r   r   r   tools.computer_use.backendr	   r
   r   r   	getLoggerr&  r   r,   rH   r   r{   rd   r]  rI  rI   rD   rL   rp   compile	MULTILINEr   rw   r|   r   r   r   r   r   r   r   r   r   r   r   r+  ru  r  r  r  r  rv   r   r   <module>r     s  !F #      	 	   
   3 3  
		8	$ )-<
<< < 	<
 < &< <\ **..!8,G7  V   ; &$ 33 3 	3P 2::. LL	  $/
 14 &R **82j$N2/ /lyX yXx5p$N*%XTQ) TQr   