
    `gj                   ~   d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlmZ ddlmZ ddlmZmZmZ dZdZ ej,                  d	ej.                        Z ej,                  d
ej.                        ZdZdZdZdZdZdOdZdPdZ dQdZ!dRdZ"dRdZ#dSdZ$dTdZ%dUdZ&dVdZ'dWdZ(dXdYdZ)	 dZddd	 	 	 	 	 	 	 d[dZ*dVdZ+dOdZ,dRd Z-d\d!Z.d\d"Z/d\d#Z0d\d$Z1d]d%Z2	 	 	 	 	 	 	 	 	 	 d^d&Z3dRd'Z4	 	 	 	 	 	 	 	 	 	 d^d(Z5d_d)Z6d\d*Z7d`d+Z8dad,Z9dbd-Z:dcd.Z;ddd/Z<ded0Z=dfd1Z>dgd2Z?dhd3Z@did4ZA	 	 	 	 djd5ZBdXdkd6ZCdld7ZD	 	 dm	 	 	 	 	 dnd8ZEdod9ZF	 dZdddd:	 	 	 	 	 	 	 	 	 dpd;ZGdqdrd<ZHdsd=ZIdPd>ZJdPd?ZKdVd@ZLdVdAZMdVdBZNdtdCZOdudDZPdPdEZQdZdvdFZRdPdGZSdwdHZTdxdIZUdydJZVdXdzdKZWdPdLZXd{d|dMZYdPdNZZy)}a<  Windows gateway service backend (Scheduled Task + Startup-folder fallback).

This mirrors the contract exposed by ``launchd_install`` / ``launchd_start`` /
``launchd_status`` etc. on macOS and ``systemd_install`` / ``systemd_start`` on
Linux. It uses ``schtasks`` under the hood with ``/SC ONLOGON`` and restart-on-
failure XML settings, and falls back to a ``%APPDATA%\...\Startup\<name>.vbs``
dropper when Scheduled Task creation is denied (locked-down corporate boxes).

Design notes
------------
* ``schtasks /Create /SC ONLOGON /RL LIMITED`` means the task runs at the
  CURRENT USER's next logon without any elevation prompt. Manual starts and
  install ``--start-now`` use the direct detached ``pythonw`` launcher instead
  of ``schtasks /Run`` so start/restart behavior is consistent.
* We write a shared ``gateway.cmd`` wrapper plus a console-less ``gateway.vbs``
  launcher. Scheduled Task and Startup-folder persistence both route through
  VBS/wscript; immediate manual starts route through direct ``subprocess`` spawn.
* Status = merge of "is the schtasks entry registered?" + "is the startup
  login item present?" + "is there a gateway process running?" so the status
  command keeps working regardless of which install path was taken.
* Quoting is tricky: schtasks parses ``/TR`` itself and cmd.exe parses the
  generated ``gateway.cmd``. Those are DIFFERENT parsers. We keep two
  separate quote helpers (same pattern OpenClaw uses) and never cross them.
* All of this is Windows-only. ``import`` paths are still safe on POSIX but
  the functions raise if called on non-Windows.
    )annotationsN)Path)escape)windows_detach_flags&windows_detach_flags_without_breakawaywindows_hide_flags      uh   (access is denied|acceso denegado|přístup byl odepřen|schtasks timed out|schtasks produced no output)z"(access is denied|acceso denegado)Hermes_Gatewayz5Hermes Agent Gateway - Messaging Platform IntegrationPT30SPT1Mi  c                 T    	 t        j                  d      xs dS # t        $ r Y yw xY w)ad  Best-effort console encoding for decoding ``schtasks.exe`` output.

    On localized Windows (e.g. Chinese), ``schtasks`` emits text in the OEM/ANSI
    code page rather than UTF-8. Decoding with the wrong codec raised
    ``UnicodeDecodeError`` inside ``subprocess``' reader threads. Prefer the
    locale's preferred encoding and fall back to UTF-8.
    Futf-8)localegetpreferredencoding	Exception     M/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/gateway_windows.py_schtasks_encodingr   A   s/    **51<W< s    	''c                 @    t         j                  dk7  rt        d      y )Nwin32zgateway_windows is Windows-only)sysplatformRuntimeErrorr   r   r   _assert_windowsr   S   s    
||w<== r   c                8   t        |       }	 ddlm} t         |             }|j                         }|j                         }t        j
                  j                  t        |            }t        j
                  j                  t        |            }t        j
                  j                  ||g      |k(  r@t        j
                  j                  t        |      t        |            }t        ||z        S 	 t        |      S # t        $ r Y t        |      S w xY w)a]  Render Hermes-owned paths under the configured HERMES_HOME spelling.

    Windows installs may keep ``%LOCALAPPDATA%\hermes`` as a symlink/junction to
    another drive. Runtime state should still identify itself by the configured
    AppData path, so launcher files must not bake in the resolved target when a
    path lives under HERMES_HOME.
    r   get_hermes_home)r   hermes_cli.configr   resolveospathnormcasestr
commonpathrelpathr   )	r#   	candidater   homeresolved_homeresolved_candidatehome_keycandidate_keyrels	            r   _preserve_hermes_home_pathr/   X   s     T
I5O%&&..077##C$67((-?)@A77x78HD''//#&8"93};MNCtcz?" E
 y>  y>s   C)D 	DDc                    d| v sd| v rt        d|       | syt        j                  d|       s| S d| j                  dd      z   dz   S )a  Quote a single argument for use INSIDE a .cmd file, for cmd.exe parsing.

    cmd.exe splits on spaces/tabs outside of double quotes. Embedded quotes
    are doubled. We also refuse line breaks because they'd terminate the
    logical command line mid-script.
    
z,refusing to quote value containing newline: ""[ \t"]")
ValueErrorresearchreplacevalues    r   _quote_cmd_script_argr<   u   sX     u}GyQRR99Y&sD))C//r   c                b    t        j                  d|       s| S d| j                  dd      z   dz   S )zQuote a single argument for schtasks.exe's /TR parser.

    Schtasks uses a different quoting convention than cmd.exe: embedded
    quotes are backslash-escaped, and the whole thing is wrapped in double
    quotes if it contains whitespace or quotes.
    r4   r5   z\")r7   r8   r9   r:   s    r   _quote_schtasks_argr>      s2     99Y&sE**S00r   c           
        t                t        j                  d      }|y	 t        j                  |g| ddt               dt        t                     }|j                  |j                  xs d|j                  xs dfS # t        j                  $ r ddd	t         d
fcY S t        $ r}ddd| fcY d}~S d}~ww xY w)u   Run ``schtasks.exe`` with a hard timeout. Return (code, stdout, stderr).

    If schtasks wedges, returns code=124 with a synthetic stderr string —
    same convention OpenClaw uses, so the fallback detection regex matches.
    schtasksN)    zschtasks.exe not found on PATHTr9   )capture_outputtextencodingerrorstimeoutcreationflagsrB   |   zschtasks timed out after srA   zschtasks invocation failed: )r   shutilwhich
subprocessrunr   _SCHTASKS_TIMEOUT_Sr   
returncodestdoutstderrTimeoutExpiredOSError)argsr@   proces       r   _exec_schtasksrX      s     ||J'H8;~~
 ()' -.
  !2DKK4E2FF$$ MR45H4IKLL ;25aS9::;s$   AB  B>$B>,B93B>9B>c                T    | dk(  xs" t        t        j                  |xs d            S )NrI   rB   )bool_FALLBACK_PATTERNSr8   )codedetails     r   _should_fall_backr^      s&    3;G$1882FGGr   c                F    t        t        j                  | xs d            S )NrB   )rZ   _ACCESS_DENIED_PATTERNr8   )r]   s    r   _is_access_deniedra      s    &--fl;<<r   c                     t                	 t        t        j                  j                  j                               S # t        $ r Y yw xY w)z9Return True when the current Windows process is elevated.F)r   rZ   ctypeswindllshell32IsUserAnAdminr   r   r   r   _is_running_as_adminrg      s=    FMM))779:: s   0= 	A	A	c                 N    ddl m}   |        }|rt        j                  |      S g S )z9Return CLI args that preserve the current Hermes profile.r   )_profile_arg)hermes_cli.gatewayri   shlexsplit)ri   profile_args     r   _current_profile_cli_argsrn      s"    /.K'25;;{#::r   c                   t                ddgt               d| }|r|j                  |       t        j                  |      }t        t        t              j                         j                  j                        }t        t        j                        }	 t        j                  j                  j!                  dd|||d      }|d
k  rt%        d|  d| d       y	y# t"        $ r}t%        d|  d|        Y d}~y	d}~ww xY w)a7  Launch an elevated gateway subcommand via UAC and return True on handoff.

    Use pythonw.exe for the elevated child so approving UAC does not leave a
    second elevated console window sitting open after the handoff. All operator
    decisions are already collected in the parent shell before this point.
    -mhermes_cli.maingatewayNrunasr   u&   ⚠ Could not launch elevated gateway z	 prompt: F    u   ⚠ Elevated gateway z' prompt was not started (ShellExecuteW=)T)r   rn   extendrM   list2cmdliner%   r   __file__r!   parent_derive_venv_pythonwr   
executablerc   rd   re   ShellExecuteWr   print)command
extra_argsrU   paramscwdelevated_pythonresultexcs           r    _launch_elevated_gateway_commandr      s     #V&?&AV9VgVDJ$$T*F
d8n$$&--44
5C*3>>:O&&44
 |%gY.UV\U]]^_`  6wiyNOs   .C   	D)C??DF)	start_nowstart_on_loginc                  t         j                  j                  d      }t         j                  j                  d      }t         j                  j                  d      }	 ||rdndt         j                  d<   ||rdndt         j                  d<   dt         j                  d<   dg}| r|j                  d       ||j                  |rd	nd
       ||j                  |rdnd       t	        d|      d|fd|fd|ffD ];  \  }}|!t         j                  j                  |d       )|t         j                  |<   = S # d|fd|fd|ffD ];  \  }}|!t         j                  j                  |d       )|t         j                  |<   = w xY w)zFLaunch an elevated gateway install via UAC and return True on handoff. HERMES_GATEWAY_INSTALL_START_NOW%HERMES_GATEWAY_INSTALL_START_ON_LOGINHERMES_GATEWAY_ELEVATED_HANDOFFN10z--elevated-handoffz--forcez--start-nowz--no-start-nowz--start-on-loginz--no-start-on-logininstall)r"   environgetappendr   pop)	forcer   r   old_start_nowold_start_on_loginold_handoffr   keyolds	            r   _launch_elevated_installr      s    JJNN#EFM(OP**..!BCK& DMSSVBJJ9:%IW#]`BJJ>?8;

45*+
i( ym>NO%N0H]^/	:F 0?46HI.<
 	&HC
 {

sD)"%

3	&/?46HI.<
 	&HC
 {

sD)"%

3	&s   BD= =AFc                     t        d      S )zHLaunch an elevated gateway uninstall via UAC and return True on handoff.	uninstall)r   r   r   r   _launch_elevated_uninstallr     s    +K88r   c                 V    t                ddlm}   |        }|st        S t         d| S )zScheduled Task name, scoped per profile.

    Default profile: ``Hermes_Gateway``
    Named profile X: ``Hermes_Gateway_<X>``
    r   )_profile_suffix_)r   rj   r   _TASK_NAME_DEFAULT)r   suffixs     r   get_task_namer     s0     2F!! !6(++r   c                0    t        j                  dd|       S )z/Remove characters illegal in Windows filenames.z[<>:"/\\|?*\x00-\x1f]r   )r7   subr:   s    r   _sanitize_filenamer   -  s    66*C77r   c                     t                ddlm}  t         |              dz  }|j	                  dd       |t        t                      dz  S )a  The generated ``gateway.cmd`` wrapper kept beside the VBS launcher.

    Lives under ``%LOCALAPPDATA%\hermes\gateway-service\<task_name>.cmd``
    (or ``<HERMES_HOME>/gateway-service/<task_name>.cmd`` so per-profile
    Hermes installs stay self-contained).
    r   r   zgateway-serviceTparentsexist_ok.cmd)r   r    r   r   mkdirr   r   )r   
script_dirs     r   get_task_script_pathr   2  sN     1o'(+<<JTD1-mo>?tDDDr   c                    t         j                  j                  dd      j                         } | rt	        |       dz  dz  dz  dz  dz  S t         j                  j                  dd      j                         xs. t         j                  j                  d	d      j                         }|st        d
      t	        |      dz  dz  dz  dz  dz  dz  dz  S )NAPPDATArB   	MicrosoftWindowsz
Start MenuProgramsStartupUSERPROFILEHOMEuH   neither APPDATA nor USERPROFILE is set — cannot resolve Startup folderAppDataRoaming)r"   r   r   stripr   r   )appdatauserprofiles     r   _startup_dirr   A  s    jjnnY+113GG}{*Y6E
RU^^^**..399;arzz~~fVX?Y?_?_?aKeff[
	
	 	 		
 	 	 		r   c                 X    t                t               t        t                      dz  S )N.vbsr   r   r   r   r   r   r   get_startup_entry_pathr   T  %    >1-/BC4HHHr   c                 X    t                t               t        t                      dz  S )Nr   r   r   r   r   _legacy_startup_entry_pathr   Y  r   r   c                    ddl m} 	  |       }|r&t        |      }|j                         rt	        |      S t	        |       S # t
        $ r Y t	        |       S w xY w)a  Return a stable cwd for detached/startup gateway runs.

    Mirror the POSIX service invariant: anchor at ``HERMES_HOME`` whenever it
    exists so Scheduled Task / Startup launches do not fail at the ``cd`` step
    after a transient checkout or worktree is moved away. Fall back to the
    source checkout only if ``HERMES_HOME`` cannot be used yet. Preserve the
    configured spelling instead of resolving symlinks so AppData installs backed
    by a junction/symlink still identify themselves as AppData.
    r   r   )r    r   r   is_dirr%   r   )project_rootr   r)   	home_paths       r   _stable_gateway_working_dirr   b  sb     2 T
I!9~% |  |s   .A 	AAc                &   ddt          g}|j                  dt        |              |j                  d| d       |j                  d       |j                  d       t        |       \  }}}|j                  dt	        |       d       t	        t        t              j                         j                  j                        g|D cg c]  }t	        |       c}}	|j                  d	d
j                  g |	d       d       |ddg}
|r|
j                  |j                                |
j                  ddg       |j                  dj                  d |
D                     |j                  d       dj                  |      dz   S c c}w )u,  Build the ``gateway.cmd`` wrapper content (CRLF-terminated).

    The script:
      - cd's into a stable working directory
      - exports HERMES_HOME, PYTHONIOENCODING, VIRTUAL_ENV
      - invokes ``pythonw -m hermes_cli.main [--profile X] gateway run``
        directly so the wrapper cmd.exe exits without a visible gateway console

    We intentionally do NOT inline PATH overrides here — cmd.exe inherits
    the per-user PATH the Scheduled Task was created with, and forcibly
    rewriting PATH tends to break Homebrew/nvm-style installations.
    z	@echo offzrem zcd /d zset "HERMES_HOME=r5   zset "PYTHONIOENCODING=utf-8"zset "HERMES_GATEWAY_DETACHED=1"zset "VIRTUAL_ENV=zset "PYTHONPATH=;z%PYTHONPATH%rp   rq   rr   rN    c              3  2   K   | ]  }t        |        y wN)r<   ).0as     r   	<genexpr>z,_build_gateway_cmd_script.<locals>.<genexpr>  s     Fq/2Fs   z	exit /b 0
)_TASK_DESCRIPTIONr   r<   _resolve_detached_pythonr/   r   rx   r!   ry   joinrv   rl   )python_pathworking_dirhermes_homerm   linespythonw_pathvenv_dirextra_pythonpathentrypythonpath_entries	prog_argss              r   _build_gateway_cmd_scriptr   }  s   $ D!2 345E	LL6/<=>?	LL$[M34	LL/0	LL23/G/T,L(, 
LL$%?%I$J!LM"4>#9#9#;#B#B#I#IJ9I	J
$U
+	J 
LL#CHH-R/A-R>-R$S#TTUVWt%67I**,-i'( 
LLFIFFG	LL;;u&&! 
Ks   Fc                ^    d| v sd| v rt        d|       d| j                  dd      z   dz   S )zQuote a value as a VBScript double-quoted string literal.

    VBScript escapes an embedded double-quote by doubling it. A newline cannot
    appear inside a literal, so refuse it (same guard as ``_quote_cmd_script_arg``).
    r1   r2   z5refusing to quote VBScript value containing newline: r5   r3   )r6   r9   r:   s    r   _quote_vbs_stringr     sA     u}PQVPYZ[[sD))C//r   c                   t        |       \  }}}|ddg}|r|j                  |j                                |j                  ddg       t        j                  |      }t        t        t              j                         j                  j                        }	t        j                  j                  |	g|D 
cg c]  }
t        |
       c}
      }dt         dddd	d
t        d       dt        |       d
t        d       dt        d       d
t        d       dt        d       d
t        d       dt        t        |             dt        d       dddt        d       dt        |t        j                  z          dddt        d       dt        |       ddt        |       dt        |       dg}dj                  |      dz   S c c}
w )u  Build a console-less ``gateway.vbs`` launcher (CRLF-terminated).

    The Scheduled Task runs this through ``wscript.exe`` instead of ``cmd.exe``.

    Why: issue #45599 root cause #1. Driving the gateway through ``cmd.exe``
    allocates a console, and during logon Windows broadcasts ``CTRL_CLOSE_EVENT``
    to console process groups — reaping cmd.exe and the half-initialized gateway
    with ``STATUS_CONTROL_C_EXIT`` (``0xC000013A``). Task Scheduler treats that
    code as a user cancel, so the ``RestartOnFailure`` policy never fires and the
    gateway silently disappears on every reboot.

    ``wscript.exe`` and ``pythonw.exe`` are both GUI-subsystem executables with
    no console, so this launcher receives no console control events. It mirrors
    ``_build_gateway_cmd_script`` (same env + argv via ``_resolve_detached_python``)
    but sets the environment on the WScript.Shell process and ``Run``s pythonw
    directly — no cmd.exe anywhere in the chain.
    rp   rq   rr   rN   ' Option ExplicitzDim sh, env, existing_pp&Set sh = CreateObject("WScript.Shell")z#Set env = sh.Environment("PROCESS")z	env.Item(HERMES_HOMEz) = PYTHONIOENCODINGr   HERMES_GATEWAY_DETACHEDr   VIRTUAL_ENVzexisting_pp = env.Item(
PYTHONPATHru   zIf Len(existing_pp) > 0 Thenz  env.Item(z & existing_ppElsezEnd Ifzsh.CurrentDirectory = sh.Run 
, 0, Falser   )r   rv   rl   rM   rw   r/   r   rx   r!   ry   r"   pathsepr   r   r   )r   r   r   rm   r   r   r   r   command_line	repo_rootr   static_pythonpathr   s                r   _build_gateway_vbs_scriptr     s   . 0H/T,L(,t%67I**,-i'(**95L*4>+A+A+C+J+J+Q+QRI

	WEUVE07VW
  "0-
%m45T:KK:X9YZ
%&89:$?PQX?Y>Z[
%&?@AFWX[F\E]^
%m45T:KLfgoLp:q9rs ""3L"A!B!D&
'56d;LM^acakakMk;l:mm{|
'56d;LM^;_:`a
 !2;!? @A #L12*=+E. ;;u&&5 Ws   7G
c                    t        | j                  d            }t        j                  d|g      }dt         dddt        |       ddd	d
t        |       dg}dj                  |      dz   S )a  The tiny .vbs that goes in the Startup folder and chains hidden.

    Defense-in-depth: bail out silently if the target script is gone. Test
    fixtures historically wrote Startup entries pointing at pytest tmp_path
    directories that vanish after the test session. Without the existence
    guard, every subsequent Windows login could attempt a stale launcher. The
    check + ``WScript.Quit 0`` keeps that case silent.
    r   zwscript.exer   r   zDim fso, sh, targetz	target = z4Set fso = CreateObject("Scripting.FileSystemObject")z1If Not fso.FileExists(target) Then WScript.Quit 0r   r   r   r   )r%   with_suffixrM   rw   r   r   r   )script_pathtargetr~   r   s       r   _build_startup_launcherr     s     ((01F%%}f&=>G
 
%f-./>;0
#G,-Z8	E ;;u&&r   c                    t                ddlm}  ddlm}m}m} t         |             }t        |      }t        t         |                    } ||      }t        ||||      }t               }	|	j                  d      }
|
j                  |dd       |
j                  |	       t!        ||||      }|	j                  d      }|j#                  |j$                  dz         }|j                  |dd       |j                  |       |	S )	zEGenerate and write the gateway.cmd wrapper. Return its absolute path.r   r   PROJECT_ROOTri   get_python_path.tmpr   rB   rE   newliner   )r   r    r   rj   r   ri   r   r/   r   r%   r   r   r   r   
write_textr9   r   	with_namename)r   r   ri   r   r   r   r   rm   contentr   tmpvbs_contentvbs_pathvbs_tmps                 r   _write_task_scriptr    s    1  -_->?K-l;Kd?,-.K{+K'[+{[G&(K

!
!&
)CNN7WbN9KK
 ,KkS^_K&&v.H  !78G{WbAOOHr   c                 *   t         j                  j                  d      xs@ t         j                  j                  d      xs t         j                  j                  d      } | syd| v r| S t         j                  j                  d      }|r| d|  S | S )zCReturn ``DOMAIN\USER`` if available, else bare USERNAME, else None.USERNAMEUSERLOGNAMEN\
USERDOMAIN)r"   r   r   )usernamedomains     r   _resolve_task_userr  4  sv    zz~~j)`RZZ^^F-C`rzz~~V_G`HxZZ^^L)F&,fXRz":(:r   c                    |rdt        |       dnd}dt        t               dt         d| dt         dt         d	t        t        |             d
S )u  Render a Task Scheduler XML definition with safe long-running defaults.

    ``launcher_path`` is the console-less ``.vbs`` the task runs via
    ``wscript.exe`` — not the ``.cmd`` (see ``_build_gateway_vbs_script`` /
    issue #45599 root cause #1).
    z
      <UserId>z	</UserId>rB   z<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Description>zp</Description>
  </RegistrationInfo>
  <Triggers>
    <LogonTrigger>
      <Enabled>true</Enabled>
      <Delay>zU</Delay>
    </LogonTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">a  
      <LogonType>InteractiveToken</LogonType>
      <RunLevel>LeastPrivilege</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>true</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>false</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
    <Priority>7</Priority>
    <RestartOnFailure>
      <Interval>z</Interval>
      <Count>z</Count>
    </RestartOnFailure>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>wscript.exe</Command>
      <Arguments>//B //Nologo "z/"</Arguments>
    </Exec>
  </Actions>
</Task>
)r   r   _TASK_LOGON_DELAY_TASK_RESTART_INTERVAL_TASK_RESTART_COUNTr%   )	task_namelauncher_pathuseruser_principals       r   _build_scheduled_task_xmlr  ?  s     DH't~Y?RN *+, -
    ! ++ ,0 (( )!" #  !'s='9 :; <W/ /r   c                f    |j                  d      }|j                  t        | ||      dd       |S )Nz	.task.xmlzutf-16rB   r   )r   r   r  )r  r  r  xml_paths       r   _write_scheduled_task_xmlr  y  s>    ((5H!)]DA  
 Or   c                0   t        ddd| g      \  }}}|xs |xs dj                         }|dk7  r)|r'd|j                         vrt        |      r
dd| d	| fS t	               }|j                  d
      }t        | ||      }ddd| dt        |      g}	|rg |	d|ddgng }
|
j                  |	       d}d}	 |
D ]>  }t        |      \  }}}|dk(  rdd| fc 	 |j                  d       S ||xs |xs d}}@ 	 	 |j                  d       |r(d|j                         vr|j                          d| d}dd| d	|j                          fS # t        $ r Y S w xY w# t        $ r Y \w xY w# 	 |j                  d       w # t        $ r Y w w xY wxY w)ar  Create or replace the Scheduled Task. Returns (success, detail).

    Always recreate instead of ``/Change``. Older Hermes builds and failed
    experiments may have left repeat/restart settings on the task; ``/Change``
    preserves those stale triggers and can make the gateway relaunch every
    minute. Delete+create gives us a clean ONLOGON task every install.
    /Delete/F/TNrB   r   zcannot findFzschtasks /Delete failed (code z): r   z/Createz/XMLz/RUz/NPz/ITrA   TzCreated Scheduled Task )
missing_okz (delete detail: ru   zschtasks /Create failed (code )rX   r   lowerra   r  r   r  r%   r   unlinkrT   )r  r   delete_code
delete_out
delete_errdelete_detailr  r  r  basevariants	last_codelast_errargvr\   outerrs                    r   _install_scheduled_taskr.    s    +9)T5R[9\*]'KZ3:3::<MaMm=CVCVCX.X]+;K=M?[\\ D  ++F3M(M4HHtUIvs8}ED7;2$22t2U2E23HOODIH
 	;D+D1ND#sqy 7	}EFF	OOtO, #')9)9rxI		;	OOtO, m.A.A.CCnn&''8qI3I;c(..BRASTUU	  		w 			OOtO, 		sZ   :!E1 E/E1 ?E" 	EE"	E.-E.1F3FF	FFFFc                H   t               }|j                  j                  dd       |j                  d      }|j	                  t        |       dd       |j                  |       t               }	 |j                         r|j                          |S # t        $ r Y |S w xY w)z=Write the Startup-folder fallback launcher. Returns its path.Tr   r   r   rB   r   )r   ry   r   r   r   r   r9   r   existsr"  rT   )r   r   r  legacy_entrys       r   _install_startup_entryr2    s    "$E	LLtd3


F
#CNN*;7'SUNVKK-/L ! L  Ls   2 B 	B! B!c                    t        |       }|j                  |j                  dz   |j                  z         }|j	                         rt        |      S | S )u  Given a ``python.exe`` path, return the sibling ``pythonw.exe`` if present.

    ``pythonw.exe`` is the console-less variant. Using it for detached
    daemons means there's no console handle to inherit from the spawning
    shell, which is what lets the gateway survive a parent-shell exit on
    Windows. Falls back to the original ``python.exe`` if the ``w`` variant
    isn't there — caller must still set CREATE_NO_WINDOW in that case.
    w)r   r   stemr   r0  r%   )
python_exepr(   s      r   rz   rz     sI     	ZAAFFSL18834I9~r   c                   | dz  }	 |j                  d      j                         }i }|D ]K  }d|vr|j                  dd      \  }}|j	                         ||j	                         j                         <   M |S # t        $ r i cY S w xY w)Nz
pyvenv.cfgr   rE   =rA   )	read_text
splitlinesrT   rl   r   r!  )r   cfg_pathr   parsedrawr   r;   s          r   _read_pyvenv_cfgr@    s    ,&H""G"4??A  F 4c>YYsA&
U&+kkmsyy{  "#	4
 M  	s    A; ;B	B	c                F   t        |       }|j                  j                  }t        |       }t        |      }|j	                  dd      }d|v rP|rNt        |      dz  }|dz  dz  }|j                         r(|j                         rt        |      |t        |      gfS ||g fS )a  Return (windowed_python, venv_dir, extra_pythonpath) for detached runs.

    uv-created Windows venv launchers are special: ``venv\Scripts\pythonw.exe``
    starts hidden, but then respawns the base interpreter as console
    ``python.exe``.  That child opens a visible Windows Terminal tab.  For uv
    venvs, use the base ``pythonw.exe`` directly and put the repo + venv
    site-packages on ``PYTHONPATH`` so imports still resolve without the venv
    launcher.
    r)   rB   uvzpythonw.exeLibzsite-packages)r   ry   rz   r@  r   r0  r%   )r6  r7  r   windowedcfgr)   base_pythonwsite_packagess           r   r   r     s     	ZAxxH#J/H
8
$C7762Ds{tDzM1 5(?: ]%9%9%;%x#m2D1EFFh##r   c                    |D cg c]  }|s|	 }}|sy t         j                  j                  dd      }|r|j                  |       t         j                  j                  |      | d<   y c c}w )Nr   rB   )r"   r   r   r   r   r   )env_overlayentriesr   clean_entriesexistings        r   _prepend_pythonpathrM    s^    (/9u5U9M9zz~~lB/HX& "

 >K :s
   A,A,c            
        t                ddlm}  ddlm}m}m} t        t         |                   \  }}}t        |      }t        |      }t        t         |                    }	 ||	      }
|ddg}|
r|j                  |
j                                |j                  ddg       |	dd	t        |      d
}t        ||r|g|D cg c]  }t        |       c}n|g       |||fS c c}w )u   Build (argv, working_dir, env_overlay) for the gateway subprocess.

    Same logical command as what gateway.cmd runs, but assembled as a
    native argv for direct ``subprocess.Popen`` invocation — no cmd.exe
    layer in between.
    r   r   r   rp   rq   rr   rN   r   r   )r   r   r   r   )r   r    r   rj   r   ri   r   r   r/   r   r%   r   rv   rl   rM  )r   r   ri   r   r6  r   r   r   r   r   rm   r+  rI  r   s                 r   _build_gateway_argvrO    s    1  .F"?#45.*J* .l;L-l;Kd?,-.K{+K/0DK%%'(KKE"# ###&1(;	K  
ZHXYu3E:YZ^	 k))	 Zs   C(c                   | s| di fS t         j                  dk7  r| di fS ddlm} ddlm} | d   }| dd }	 t        |      \  }}}|g|}t        |      }	t        |      }
	 t        t         |             j                               }dd	t        |      d
}|r||d<   t        ||r|
g|n|
g       ||	|fS # t        $ r | di fcY S w xY w# t        $ r d}Y Pw xY w)u*  Rewrite a console-``python.exe`` gateway argv into a windowless one.

    The post-update restart paths build their respawn command from
    ``get_python_path()`` which returns the venv's console ``python.exe``.
    On Windows — especially with uv-created venvs — launching that
    interpreter (even with ``CREATE_NO_WINDOW``) leaves a persistent
    console window: ``venv\Scripts\python.exe`` is a launcher shim that
    re-execs the *base* console interpreter, which allocates its own
    conhost.  ``CREATE_NO_WINDOW`` cannot suppress that second window.
    See ``_resolve_detached_python`` for the gory details.

    This mirrors what ``_build_gateway_argv`` / ``_spawn_detached`` do for
    a clean start: swap the interpreter for the windowless ``pythonw.exe``
    (base interpreter for uv venvs) and return the cwd + env overlay
    (VIRTUAL_ENV, PYTHONPATH) the base interpreter needs to resolve the
    ``hermes_cli`` package without the venv launcher's site config.

    Returns ``(new_argv, working_dir, env_overlay)``.  ``new_argv``
    preserves every argument after the interpreter (``-m hermes_cli.main
    [--profile X] gateway run [--replace]``) verbatim.  On non-Windows, or
    if ``run_argv`` doesn't start with a resolvable python, the argv is
    returned unchanged with an empty overlay.
    rB   r   r   r   )r   rA   Nr   r   )r   r   r   r   )r   r   r    r   rj   r   r   r   r   r%   r   r!   rM  )run_argvr   r   r6  restwindowless_pythonr   r   new_argvr   r   r   rI  s                r   windowless_gateway_restart_specrU  ,  s)   4 R
||wR1/!JAB<D 8P9
58%5 ")D)H-l;K|$L$0199;<
 $#&8}#K
 %0M"-=)()L> [+--/   R   s#   B8 "'C 8C	C	CCc                   t                t               \  }}}i t        j                  |}t	               }ddlm} t         |             dz  }|j                  dd       |dz  }	 t        |dd      5 }	t        j                  ||||dt        j                  |	|		      }
d
d
d
       |
j                  S # 1 sw Y   
j                  S xY w# t        $ rg t               }t        |dd      5 }	t        j                  ||||dt        j                  |	|		      }
d
d
d
       n# 1 sw Y   nxY wY 
j                  S w xY w)u  Launch the gateway as a fully detached background process.

    We spawn ``pythonw.exe -m hermes_cli.main gateway run``
    directly — NOT through a cmd.exe shim — because on Windows a cmd.exe
    child inherits the parent session's console handle and tends to get
    reaped when the spawning shell exits. pythonw.exe has no console, and
    combined with DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP |
    CREATE_NO_WINDOW + DEVNULL stdio + a fresh env, the resulting process
    is independent of whichever shell started it.

    Arg ``script_path`` is accepted for API symmetry with older callers
    but ignored — we don't need it now that we go direct.

    Returns the spawned PID so callers can verify the process actually
    came up.
    r   r   logsTr   zgateway-stdio.logab)	buffering)r   envrH   	close_fdsstdinrQ   rR   N)r   rO  r"   r   r   r    r   r   r   openrM   PopenDEVNULLrT   r   pid)r   r+  r   rI  rZ  flagsr   log_dir	stray_loglog_fhrV   flags_no_breakaways               r   _spawn_detachedrf  s  sI   " %8%:"D+{ (RZZ
';
'C !"E 2?$%.GMM$M.--I)TQ/ 
	6### ((	D
	8 88O9
	8 88O#  
 DE)TQ/ 
	6##0 ((	D
	 
	 
	 88O#sH   )C 7,B7#C 7C<C C !D>/,D$	D>$D-	)D>=D>c                    t         j                  j                  |       }|y |j                         j	                         }|dv ry|dv ryy )N>   r   yonyestrueT>   r   nnoofffalseF)r"   r   r   r   r!  )r   r?  r;   s      r   _install_choice_from_envrp    sK    
**..
C
{IIKE//00r   c                    t        d      }t        d      }| |} ||}| || |fS ddlm} | 	 |dd      } |	 |dd      }| |fS )zEReturn (start_now, start_on_login), asking before any UAC escalation.r   r   r   prompt_yes_noz$Start the gateway now after install?TzGStart the gateway automatically on Windows login with a Scheduled Task?)rp  hermes_cli.setuprs  )r   r   env_start_nowenv_start_on_loginrs  s        r   _prompt_install_choicesrw    s    
 --OPM12YZ!	+!;.((.!"H$O	&U
 n$$r   c           	        t        d|j                         d    d       t        |       }t        d|        t        d|         ddlm}m} t         |             }|r7t        ddj                  t        t        |             d	       t                y|r$t               }t        d
| d	       t                y |       }|rd| dnd}	t        d       t        d|	        t                y)z>Install the Startup-folder fallback and optionally start once.$   ↻ Scheduled Task install blocked (r   #   ) — using Startup folder fallback"   ✓ Installed Windows login item:   Task script: find_gateway_pidsri   "   ✓ Gateway already running (PID: , ru   direct spawn (PID hermes  gateway starthermes gateway start8   ℹ Startup fallback installed; gateway not started now.  Start manually with: N)r}   r<  r2  rj   r~  ri   listr   mapr%   rf  _report_gateway_start_print_next_steps)
r   r   r]   r   r~  ri   running_pidsr`  rm   	start_cmds
             r   _install_startup_fallbackr    s    	01B1B1DQ1G0HHk
lm";/E	.ug
67	OK=
)* C)+,L2499Sl=S3T2UUVWX  
 23%q9: 	 #n=Hgk].9Nd	HI'	{34r   )r   r   elevated_handoffc          	     0   t                t        ||      \  }}|swt        d       |rSt               }|r-t        ddj	                  t        t        |             d       yt               }t        d| d       yt        d       t        d       yt               }t               }t               s|s}d	d
lm} t        d       t        d        |dd      r?t        | ||      r%t        d       |rt        d       yt        d       yt        d       nt        d       t        ||d       yt!        ||      \  }	}
|	rt        d|
        t        d|        t        d       |rgt               }|r7t        ddj	                  t        t        |             d       t#                yt               }t        d| d       t#                yt        d       t        d       t#                yt%        |
      rt               sd	d
lm} t        d|
j'                         d	    d       t        d        |dd      r?t        | ||      r%t        d       |rt        d       yt        d       yt        d       nt        d       t)        d|
      rt        d|
j'                         d	    d       t+        |      }t        d|        t        d|        d	d lm}m} t3         |             }|r7t        ddj	                  t        t        |             d       t#                y|r$t               }t        d| d       t#                y |       }|rd!| d"nd#}t        d$       t        d%|        t#                yt5        d&|
       )'u/  Install the gateway as a Windows Scheduled Task (with Startup fallback).

    Idempotent: re-running updates the task to point at the current python/
    project paths. ``force`` is accepted for API parity with ``launchd_install``
    / ``systemd_install`` but isn't needed — we always reconcile.
    u-   ℹ Skipped Windows login auto-start install.r  r  ru   r  u<   ℹ Gateway not started and no auto-start service installed.z&  Run later with: hermes gateway startNr   rr  uS   ↻ Scheduled Task install may need administrator approval on this Windows account.zZ  UAC is Windows' admin approval prompt; it is needed to create/update the Scheduled Task.  Open the UAC prompt now?F)r   r   r   u4   ✓ Launched elevated Hermes gateway install prompt.zY  Approve the Windows UAC prompt; the elevated install will start the gateway afterwards.A  Approve the Windows UAC prompt, then run: hermes gateway statusuR   ⚠ Falling back to Startup folder because elevation was unavailable or cancelled.z4  Skipped elevation. Falling back to Startup folder.z#administrator approval was not usedu   ✓ r|  u3   ℹ Gateway auto-start installed for Windows login.u   ℹ Gateway not started now.z+  Start manually with: hermes gateway startu9   ↻ Scheduled Task install needs administrator approval (rA   ry  rz  r{  r}  r  r  r  r  r  z Windows gateway install failed: )r   rw  r}   _gateway_pidsr   r  r%   rf  r  r   r  rg   rt  rs  r   r  r.  r  ra   r<  r^   r2  rj   r~  ri   r  r   )r   r   r   r  r  r`  r  r   rs  okr]   r   r~  ri   rm   r  s                   r   r   r      s     7	> RI~=>(?L:499SlE[;\:]]^_` 	 &'%(:3%q&AB 	 PQ:;I$&K  !*:2cdjk5u='eyYghLMuv  ]^fgHI!+y:_`(K@JB	VHo}-.CD(?L:499SlE[;\:]]^_` 	 &'%(:3%q&AB 	 01?@  )=)?2I&J[J[J]^_J`Iaabcdjk5u='eyYghLMuv  ]^fgHI F#4V5F5F5H5K4LLopq&{325':;}-. 	G-/06tyyS,AW7X6YYZ[\ 	 !#C!$6se1"=> 	 '.KAL'+n=RhILM+I;78 9&B
CCr   c                    ddl m} t        j                         | z   }t        j                         |k  rAt         |             }|r|S t        j                  |       t        j                         |k  rAg S )u   Poll for a live gateway process for up to ``timeout_s`` seconds.

    Returns the list of PIDs found. Empty list means nothing came up in
    time — the caller should surface that to the user as a failed start.
    r   r~  )rj   r~  timer  sleep)	timeout_s
interval_sr~  deadlinepidss        r   _wait_for_gateway_readyr  ~  s^     5yy{Y&H
))+
 %'(K

:	 ))+
 
 Ir   c                0   t               }|r0t        d|  ddj                  t        t        |             d       y t        d|  d       t        d       dd	lm} t        d
t         |              d       t        d
t         |              d       y )Nu   ✓ Gateway started via z (PID: r  ru   u   ⚠ Launched gateway via z#, but no process detected after 6s.z#  Check the log for startup errors:r   r   z	    type z\logs\gateway.logz\logs\gateway-stdio.log)r  r}   r   r  r%   r    r   r   )viar  r   s      r   r  r    s    "$D(WTYYs3~5N4OqQR)#.QRS345	$0122EFG	$0122KLMr   c                     ddl m}  t         |              }t                t        d       t        d       t        d| d       y )Nr   r   zNext steps:z;  hermes gateway status                      # Check statusz  type z#\logs\gateway.log       # View logs)r    r   r   r}   )r   r   s     r   r  r    s:    1()K	G	-	
GH	GK= E
FGr   c                    t                t               } t               }|j                  d      }t	               }t               }d}t               rt        ddd| g      \  }}}|j                         }	|dk(  rd}t        d|        nt        |	      rpt               sfdd	lm}
 t        d
|	xs d d       t        d        |
dd      r-t               rt        d       t        d       yt        d       nt        d       nt        d| d|	        |df|df|df|dffD ]'  \  }}	 |j                          t        d| d|        ) t               r|st        d|         yyy# t         $ r Y Rw xY w)zKRemove both the Scheduled Task and the Startup-folder fallback, if present.r   Fr  r  r  r   Tu   ✓ Removed Scheduled Task rr  u;   ↻ Scheduled Task uninstall needs administrator approval (zaccess deniedru   zS  UAC is Windows' admin approval prompt; it is needed to remove the Scheduled Task.r  u6   ✓ Launched elevated Hermes gateway uninstall prompt.r  Nu;   ⚠ Elevated uninstall prompt was unavailable or cancelled.z4  Skipped elevation. Scheduled Task was not removed.u#   ⚠ schtasks /Delete returned code : zWindows login itemzlegacy Windows login itemzTask scriptzTask launcheru   ✓ Removed u%   ⚠ Scheduled Task still registered: )r   r   r   r   r   r   is_task_registeredrX   r   r}   ra   rg   rt  rs  r   r"  FileNotFoundError)r  r   vbs_script_pathstartup_entrylegacy_startup_entryscheduled_task_removedr\   _outr-  r]   rs  r#   labels                r   r   r     s   I&(K!--f5O*,M57"()T5))LMdC19%)"/	}=>v&/C/E6OPVPiZiOjjklmgh95A-/RS]^STLM7vRxHI 
,-	:;	m$	/*	 
e	KKMLr$01
 $:5i[AB %; ! 		s   *!E**	E65E6c                 @    t        ddt               g      \  } }}| dk(  S )N/Queryr  r   )rX   r   )r\   r  _errs      r   r  r    s&    %x&HID$19r   c                 f    t               j                         xs t               j                         S r   )r   r0  r   r   r   r   is_startup_entry_installedr    s&    !#**,U0J0L0S0S0UUr   c                 .    t               xs
 t               S )zGTrue when either the schtasks entry or the Startup fallback is present.)r  r  r   r   r   is_installedr    s    ?#=#??r   c                 t   t        ddt               dddg      \  } }}| dk7  ri S i }|j                         D ]~  }|j                         }|rd|vr|j	                  d      \  }}}|j                         j                         }|j                         }|dv sb|d	k(  r|j                  d
|       z|||<    |S )zDParse ``schtasks /Query /V /FO LIST`` and pull the interesting keys.r  r  z/Vz/FOLISTr   :>   statuslast resultlast run timelast run resultr  r  )rX   r   r<  r   	partitionr!  
setdefault)	r\   r,  r-  infor?  liner   r   r;   s	            r   query_task_statusr    s    #XumotUTZ$[\ND#sqy	D~~ "yy{s$s+Qiik!OOm# 159!S	" Kr   c                 .    ddl m}  t         |              S )z3Reuse the cross-platform PID scanner in gateway.py.r   r  )rj   r~  r  r  s    r   r  r    s    4!#$$r   c            
     
   ddl } ddlm}m} ddlm} t         |             }|dz  }|dz  }|dz  }|dz  d	z  }t                t        d
       dGd}	|j                         }
d}|
rl	 | j                  |j                  d            }|j                  d      t        |j                  d            nd}t        d |	d      dd| d| d       nt        d |	d      dd|        d}|j                         }|r(	 ddlm}  ||      }t        d |	|      dd|        nt        d |	d      dd|        d}	 ddlm}  |d      }t        d |	|du      dd |        ||n|}|4	 dd"lm} t#         ||            }t        d# |	|      dd$| d%|        nt        d# |	d      dd'       |j                         r	 | j                  |j                  d            }|j                  d(      }|j                  d)      }d*}|r_	 |j%                  |j'                  d+d,            }|j)                  |j*                        }t        ||z
  j-                               }d-| d.}|d/k(  }t        d0 |	|      dd1||        nt        d0 |	d      dd3|        |j                         r)	 t/        |d4      5 }|j1                  dd5       |j3                         }|j1                  t5        d|d6z
               |j7                         j9                  dd78      j;                         } ddd       t=        d9 t?               D        d*      }!|!rk	 | j                  |!      }"|"j                  d:d;      }#|"j                  dd;      }$|"j                  d<d;      }%|#d=v }&t        d> |	|&      dd?|# d@|$ dA|%        nt        d> |	d      ddD|        yyyt        d> |	d      ddF|        y# t        $ r#}t        d |	d      dd|        Y d}~;d}~ww xY w# t        $ r#}t        d |	d      dd|        Y d}~d}~ww xY w# t        $ r#}t        d |	d      dd!|       Y d}~d}~ww xY w# t        $ r#}t        d# |	d      dd&|       Y d}~d}~ww xY w# t        $ r Y Sw xY w# t        $ r#}t        d0 |	d      dd2|        Y d}~Fd}~ww xY w# 1 sw Y   xY w# t        $ r t        d> |	d      ddB|!ddC         Y Jw xY w# t        $ r"}t        d> |	d      ddE|        Y d}~yd}~ww xY w)Hu  Print PASS/FAIL per individual probe of gateway liveness.

    The default ``status`` output collapses several signals into one
    ✓ / ✗ line, which is great when they agree and confusing when they
    don't. The deep-probe block shows each underlying check independently
    so the user can see exactly which signal is wrong.

    Probes:
      [1] PID file present
      [2] Lock file present and held by some process
      [3] gateway.status.get_running_pid() returns a PID
      [4] _pid_exists(pid) — OS confirms the process is alive
      [5] gateway_state.json exists and parses (and is fresh-ish)
      [6] Last lifecycle event in gateway-exit-diag.log
    r   N)datetimetimezoner   zgateway.pidzgateway.lockzgateway_state.jsonrW  zgateway-exit-diag.logzDeep probes:c                    | rdS dS )NPASSFAILr   )r  s    r   _markz!_print_deep_probes.<locals>._mark"  s    v''r   r   r9  r`  z  [1] T4sz  PID file present: z (pid=ru   Fz#  PID file present but unreadable: z  PID file missing: )is_gateway_runtime_lock_activez  [2] z$  Lock file held by a live process: z  Could not probe lock: z  Lock file missing: get_running_pid)cleanup_stalez  [3] z  get_running_pid() => z  get_running_pid() raised: )_pid_existsz  [4] z  _pid_exists(z) => z  _pid_exists raised: z  No candidate PID to verifygateway_state
updated_atrB   Zz+00:00z
 (updated zs ago)runningz  [5] z  gateway_state.json state=z-  gateway_state.json present but unreadable: z  gateway_state.json missing: rb   i   r9   )rF   c              3  B   K   | ]  }|j                         s|  y wr   )r   )r   lns     r   r   z%_print_deep_probes.<locals>.<genexpr>v  s     IbbhhjrIs   tag?ts)zgateway.startz  [6] z  Last lifecycle event: tag=z pid=z ts=z   Last lifecycle line not JSON: x   z  exit-diag log empty: z  exit-diag log unreadable: z  exit-diag log missing: )r  rZ   returnr%   ) jsonr  r  r    r   r   r}   r0  loadsr;  r   intr   gateway.statusr  r  r  rZ   fromisoformatr9   nowutctotal_secondsr]  seektellmaxreaddecoder<  nextreversed)'r  r  r  r   r)   pid_path	lock_path
state_path	diag_pathr  
pid_exists	pid_valuedatar   	lock_heldlock_presentr  running_pidr  candidate_pidr  alive
state_datar  r  age_str
updated_dtr  age_secondsr  fhsizetail
last_eventeventr  r`  r  healthys'                                          r   _print_deep_probesr    s     +1!"Dm#H~%I,,Jv 77I	G	.( "J I	V::h00'0BCD040KDHHUO,QUIF5;r**>xjykYZ[\ 	uU|B'';H:FG I##%L	KE6yAIF5+B//ST]S^_` 	uU|B''<YKHI #KM2%E:u[45b99PQ\P]^_
 $/#:K	M 	K2]34EF5<+>-eWUV 	uU|B''CDE 	`J$8$8'$8$JKJ&NN?;M#5JG!)!7!7
8J8J3PX8Y!ZJ",,x||4C"%sZ'7&F&F&H"IK *;-v>G )+BF59R.(CMCTU\T]^_ 	uU|B''Ej\RS 	Oi& P"1wwyAtd{+,wwy''	'BMMOP IHTNI2NJh JJz2E))E3/C))E3/C4-B!%77GF5>""55QRUQVV[\_[``degdhij uU|B//FykRS h k 	uU|B''@LMs  	VF5<++NseTUU	V  	KF5<++CC5IJJ	K  MuU|B''CC7KLLM  	KF5<++A#IJJ	K$ !   	`F5<++XY\X]^__	`P P ! hF5<"33ST^_c`cTdSefgh  	OF5<++GuMNN	Os   (A*O?  &P. )Q 42R AS AR; 4S >T2 
A0S::&T2 !A)T 
T2 ?	P+P&&P+.	Q7QQ	R	&RR		R8R33R8;	SS SS 	S7S22S7:T?T2 $T/+T2 .T//T2 2	U;UUc           	        t                t               }t               }t               }t	               }|rIt        d|        t               }|rodD ])  }||v st        d|j                          d||           + n@|r3t               }|j                         s
t               }t        d|        nt        d       |r-t        ddj                  t        t        |             d	       nt        d
       | rNt                t        d|        t        dt                       t        dt                       t                |s'|s$|s!t                t        d       t        d       yyyy)z6Print a status report for the Windows gateway service.u   ✓ Scheduled Task registered: )r  r  r  z  r  u"   ✓ Windows login item installed: u!   ✗ Gateway service not installedu"   ✓ Gateway process running (PID: r  ru   u   ✗ No gateway process detectedz  Task name:        z  Task script:      z  Startup entry:    zTo install:z  hermes gateway installN)r   r   r  r  r  r}   r  titler   r0  r   r   r  r%   r   r  )deepr  task_installedstartup_installedr  r  r   r   s           r   r  r    sR   I')N24?D/	{;< "E ;$;Bsyy{m2d3i[9:; 
&(||~.0E25':;122499Sd^3L2MQOP/0$YK01$%9%;$<=>$%;%=$>?@ 	"3Dm() =A"3>r   c            	        t                t               } | r-t        ddj                  t	        t
        |              d       yt               }t               }|sc|saddlm	} t        d        |dd	      st        d
       yt        d       t               }t               }|s|st        d       t        d       yt               }t        d| d       y)zCStart the gateway using the canonical detached Windows launch path.r  r  ru   Nr   rr  u$   ✗ Gateway service is not installedz0  Install it now so the gateway starts on login?Tz  Run: hermes gateway installFr   u5   ⚠ Gateway install did not complete in this process.zD  If a UAC prompt opened, approve it, then run: hermes gateway startr  )r   r  r}   r   r  r%   r  r  rt  rs  r   rf  r  )r  r  r  rs  r`  s        r   startr    s     ?L2499Sl=S3T2UUVWX')N24"3245OQUV12e+-68&7IJXY
 
C.se156r   c                L   | dk  ry	 ddl m}m} 	  ||        t        j                         t        |d      z   }t        j                         |k  r6 ||       syt        j                  d       t        j                         |k  r6y# t        $ r Y yw xY w# t        $ r Y w xY w)a  Write the planned-stop marker and wait for the gateway PID to exit.

    Windows cannot deliver POSIX signals to a Python asyncio loop
    (``loop.add_signal_handler`` raises NotImplementedError), so writing
    the marker is the ONLY way to ask a running gateway to drain
    in-flight agents and persist ``resume_pending`` before exit. The
    gateway's planned-stop watcher thread (gateway/run.py) polls for
    the marker and drives the same shutdown path the SIGTERM handler
    would have on POSIX.

    Returns True if the PID exited within the timeout, False if it
    didn't (caller should escalate to schtasks /End + taskkill).
    r   F)write_planned_stop_markerr        ?T      ?)	r  r  r  ImportErrorr   r  	monotonicr  r  )r`  drain_timeoutr  r  r  s        r   _drain_gateway_pidr    s     axI!#& ~~#mS"99H
..
X
%3

3 ..
X
%   
   	s"   B B 	BB	B#"B#c                     	 ddl m}  t         |        xs d      }t	        dt        |d            S # t        $ r d}Y #w xY w)z3Return a bounded Windows gateway stop grace period.r   )_get_restart_drain_timeout      >@r  )rj   r  floatr   r  min)r  
configureds     r   _windows_stop_drain_timeoutr    sI    A57?4@

 sC
D)**	  
s   2 A A c                   	 ddl m}m} t	        j
                         }d}t               }| D ];  }|dk  s	||k(  s||v r|j                  |       	  ||      s- ||d       |dz  }= |S # t        $ r Y yw xY w# t        $ r Y Zt        $ r t        d|        Y rt        $ r}t        d| d|        Y d	}~d	}~ww xY w)
z<Force-kill known gateway PIDs without a broad process sweep.r   )r  terminate_pidTr  rA   u"   ⚠ Permission denied to kill PID zFailed to kill PID r  N)r  r  r  r
  r"   getpidsetaddProcessLookupErrorPermissionErrorr}   rT   )r  r  r  own_pidkilledseenr`  r   s           r   #_force_terminate_known_gateway_pidsr    s    = iikGFUD 6!8sg~
	6s##T*aKF6 M+   " 	 	>6se<= 	6'uBse455	6s;   A, A;A;,	A87A8;	CCC&B<<Cc                    g }| | dkD  r|j                  |        	 t               D ]  }|dkD  s	||vs|j                  |         	 |S # t        $ r Y |S w xY w)zFCollect gateway PIDs for the active profile, preserving primary first.r   )r   r  r   )primary_pidr  r`  s      r   _collect_gateway_stop_pidsr"  "  sn    D;?K  ? 	!CQw3d?C 	!
 K  Ks   A
 A
 A
 
	AAc                    t                ddlm}   |        }t        |      d}|t	        |t                     }|}t               rWt        ddt               g      \  }}}|dk(  rd}n5d|xs d	j                         vrt        d
| d|j                                 j                  fdt               D               t              }|rd}t        d| d       |r|rt        d       yt        d       yt        d       y)u  Stop the gateway.

    Writes the planned-stop marker first so the gateway can drain
    in-flight agents and persist ``resume_pending`` before exit (the
    gateway's marker-watcher thread picks this up — Windows asyncio
    can't deliver SIGTERM to the loop, so the marker is our only IPC).
    Then escalates with bounded Windows process termination against the
    known gateway PID(s).
    r   r  FNz/Endr  Tznot runningrB   u    ⚠ schtasks /End returned code r  c              3  ,   K   | ]  }|vs|  y wr   r   )r   r`  	stop_pidss     r   r   zstop.<locals>.<genexpr>S  s     YSCyDXSYs   	u   ✓ Killed z gateway process(es)u%   ✓ Gateway stopped (drained cleanly)u   ✓ Gateway stoppedu   ✗ No gateway was running)r   r  r  r"  r  r  r  rX   r   r!  r}   r   rv   r  )	r  r`  drainedstopped_anyr\   r  r-  r  r%  s	           @r   stopr(  0  s     . 
C*3/IG
$S*E*GHK(&%)IJdC19K39""3"3"554TF"SYY[MJK
 Y$>$@YY0;FF8#7899:'(*+r   c                (   ddl m} t        j                         t	        | |      z   }t        j                         |k  r? |       t               syt        j                  |       t        j                         |k  r? |       du xr t                S )a  Block until no gateway process is detectable, or the timeout elapses.

    ``stop()`` can return while the previous gateway is still draining
    in-flight agents (the drain runs up to the restart-drain timeout). Uses the
    authoritative ``get_running_pid()`` (lock + liveness + start-time +
    gateway-shape) plus the now-strict ``_gateway_pids()`` scan so a relaunch
    never races a still-alive old process.
    r   r  NT)r  r  r  r  r  r  r  )r  r  r  r  s       r   _wait_for_gateway_absentr*  a  su     /~~#i"<<H
..
X
%$]_

: ..
X
% $<]_)<<r   c                    t                t                t        d      s5t        d       t	        t                      t        d      st        d      t        j                  d       t                t        d      st        d      y	)
a  Stop the gateway then start it again.

    Waits for the old gateway to be authoritatively gone before relaunching --
    otherwise ``start()``'s "already running" guard sees the still-draining old
    process and no-ops, and when that process later exits nothing replaces it (a
    silent outage). Fails loudly if the process can't be cleared or the relaunch
    doesn't produce a running gateway.
    r  )r  uK   ⚠ Gateway still present after stop; forcing termination before restart...g      $@zwGateway process still detected after force kill; refusing to start a duplicate. Investigate stray PIDs before retrying.r  g      .@zrGateway restart did not produce a running gateway process. Check logs/gateway.log and run `hermes gateway status`.N)r   r(  r*  r}   r  r"  r   r  r  r  r  r   r   r   restartr,  t  sy     F#d3[\+,F,HI'$7M  	JJsO	G"T2F
 	
 3r   )r  r%   )r  None)r#   z
str | Pathr  r%   )r;   r%   r  r%   )rU   	list[str]r  ztuple[int, str, str])r\   r  r]   r%   r  rZ   )r]   r%   r  rZ   )r  rZ   )r  r.  r   )r~   r%   r   zlist[str] | Noner  rZ   )F)r   rZ   r   bool | Noner   r/  r  rZ   )r  r   )r   r   r  r%   )
r   r%   r   r%   r   r%   rm   r%   r  r%   )r   r   r  r%   )r  
str | None)r  r%   r  r   r  r0  r  r%   )r  r%   r  r   r  r0  r  r   )r  r%   r   r   r  ztuple[bool, str])r   r   r  r   )r6  r%   r  r%   )r   r   r  dict[str, str])r6  r%   r  ztuple[str, Path, list[str]])rI  r1  rJ  r.  r  r-  )r  %tuple[list[str], str, dict[str, str]])rQ  r.  r  r2  )r   zPath | Noner  r  )r   r%   r  r/  )NN)r   r/  r   r/  r  ztuple[bool, bool])r   r   r   rZ   r]   r%   r  r-  )
r   rZ   r   r/  r   r/  r  rZ   r  r-  )g      @g?)r  r  r  r  r  	list[int])r  r%   r  r-  )r  r1  )r  r3  )r   rZ   r  r-  )r`  r  r  r  r  rZ   )r  r  )r  r3  r  r  )r!  z
int | Noner  r3  )r  r	  )r  r  r  r  r  rZ   )[__doc__
__future__r   rc   r   r"   r7   rk   rK   rM   r   r  pathlibr   xml.sax.saxutilsr   hermes_cli._subprocess_compatr   r   r   rO   _SCHTASKS_NO_OUTPUT_TIMEOUT_Scompile
IGNORECASEr[   r`   r   r   r  r  r  r   r   r/   r<   r>   rX   r^   ra   rg   rn   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r.  r2  rz   r@  r   rM  rO  rU  rf  rp  rw  r  r   r  r  r  r   r  r  r  r  r  r  r  r  r  r  r  r"  r(  r*  r,  r   r   r   <module>r<     s  6 #   	 	    
   #    " RZZoMM  $$I2==Y % K    $>
:0 	1 ;DH=;B !& ""&	!&!& !&  	!&
 
!&H9, 8
E&I
I6-'-'-' -' 	-'
 	-'`0<'<'<' <' 	<'
 	<'~'0J;7t'VX  $2?(*VD.D.*D.NIX	 ""&%%% %48 {D ""&"{D{D {D  	{D
 {D 
{D|"	NH-Ch
V@
,%BNJ(*V7B!H
+8.,b=&
r   