
     YjO$                       U d Z ddlmZ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mZ ddlmZ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ZddlZddlZddlZddl Z ddl!Z!ddl"Z#ddl$Z#ddl%Z%ddl&m'Z'm(Z( ddl)Z#ddl*m+Z+ ddl,m-Z-m.Z.m/Z/m0Z0m1Z1 ddl2Z2 e+e3          j4        j4        5                                Z6 e7e6          ej8        vr$ej8        9                    d e7e6                     dd	l:m;Z;m<Z< dd
l=m>Z>m?Z?m@Z@mAZAmBZBmCZCmDZDmEZEmFZFmGZGmHZHmIZImJZJmKZKmLZLmMZMmNZNmOZOmPZPmQZQ ddlRmSZSmTZTmUZUmVZVmWZWmXZXmYZY ddlZm[Z\m]Z^m_Z` ddlambZb 	 ddlcmdZdmeZemfZfmgZgmhZhmiZimjZjmkZk ddllmmZm ddlnmoZompZpmqZqmrZr ddlsmtZt ddlumvZvmwZw ddlxmyZy nw# ez$ ro 	 ddl{m|Z}  e}dd           ddlcmdZdmeZemfZfmgZgmhZhmiZimjZjmkZk ddllmmZm ddlnmoZompZpmqZqmrZr ddlsmtZt ddlumvZvmwZw ddlxmyZy n# e~$ r  edej         d          w xY wY nw xY wdej        v r e+ej        d                   n e+e3          j4        dz  Z ej        e          Zdddded dfd!Zdd"Zd efd#Zedd&            Zdd'Zd$d%d ej        fd(Zd$d%d ee7e+f         fd)Z edd*e;e+          Zdd,lmZ e                    e           ej                            d-          p ej        d.          Zd/Zd0Zg Ze/e         ed1<   d2Zd3Ze                    emd4d5gd5g6           dd7lmZ d8ehd efd9Z ed:h          Zee7         ed;<   d8ehd<e7d efd=Zd8ehd dfd>Z eh d?          Zeed@<   ddAe7dBed efdCZdDe7dEe7d efdFZe                    dG          d8ehfdH            Ze                    dG          d8ehfdI            Ze                    dG          d8ehfdJ            Ze                    dG          d8ehfdK            Ze                    dG          d8ehfdL            Zd e/e7         fdMZi dNdOdP e            dQdRdSdTdUdVdWdXdYdUdVdZdOd[g d\dQd]dOd^d_d`gdQdadOdbg dcdQdddOdeg dfdQdgdOdhdidjgdQdkdOdlg dmdQdndOdog dpdQdqdOdrg dsdQdtdOdug dvdQdwdOdxg dydQdzdOd{d|d}gdQd~dOdg ddQddOdg ddQddOdg ddQdOdg ddQdOdddgdQddddZe.e7e.e7e-f         f         ed<   i ddddddddddddddddddddddddddUddddddZe.e7e7f         ed<   g dZde-d e7fdZ	 dde.e7e-f         de7d e.e7e.e7e-f         f         fdZ ee?          ZedW         Zi Ze.e7e.e7e-f         f         ed<   e                                D ]\  ZZeee<   edRk    reedW<   eZ G d dev          Z G d dev          Z G d dev          Z G d dev          Z G d dev          Z G d dev          Z G d dev          Z G d dev          Z G d dev          Z G d dev          Z G d dev          Z G dĄ dev          Z G dƄ dev          Z G dȄ dev          Z G dʄ dev          Z G d̄ dev          Zdddddddddddddd֜Ze.e7e7f         ed<   dZde7d e7fdڄZ G dۄ dev          Z G d݄ dev          Z G d߄ dev          Z G d dev          Zde7dRe7d ee7e7f         fdZ	 dddde7dRe7de7de7d efdZ ej        d          Z	  e ej        dd                    Zn3# eef$ r) e                    d ej        d                     dZY nw xY wdZd eeedz  f         fdZd efdZd efdZdddddddddZdZdZdZ e+d          Z ed0           G d d                       Zh dZ eh d          Z eddh          Zde7d efdZd<e+d efdZdZd	Zd
ZdZi ddddddddddddddddddddddd d!d"d#d$d%d&d'd(d)d*d+d!d,d-d.d/d0d1d2d3d4d5d1d6d6d/d7Zi d8d9d:ddϐd;d<dd=dd>ddАd?d@dAdBdCdѐdDdҐdEdӐdFdGdHdIddJddԐdKdՐdLdMdiZdNe7d e+fdOZd<e+d e7fdPZdQed efdRZd<e+d ee+ej        f         fdSZdTe+d e7dz  fdUZd e7fdVZdWe7d e7fdXZd ee+         fdYZe                    dZ          d<e7fd[            Zdd\d<e+d]ed e+fd^ZdNe7e+z  d e+fd_Z d`e+dae+d efdbZdNe7dz  d e7fdcZd8ehd efddZd efdeZd efdfZd0dgd8ehdhed efdiZddjdNe7dz  d8ehdked eee+e7f         fdlZdmed e.e7e-f         fdnZdmedae+d e.e7e-f         fdoZ	dpe7d eee7f         fdqZ
dؐZ eh dr          ZdsZeeee7f         dtf         edu<   dve7dz  d e7fdwZdQed e7dz  fdxZdyed eee7e7f         fdzZe                    d{          ddyeːd|e0e7         fd}            Ze                    d~          dd8ehd<e0e7         fd            Ze                    d          d8ehd<e7fd            Ze                    d:          d8ehd<e7fd            Ze                    d          dyed8ehfd            ZdZe                    d           eedt           efdt           efd0          fd8ehdeid<e7defd            Ze                    d          dyed8ehfd            Ze                    d~          dyed8ehfd            Ze                    d          d<e7fd            Ze                    d          d<e7fd            Z G d dev          Ze                    d          dyefd            Ze                    d          d<e7fd            Z e                    d          d<e7fd            Z!e                    d          d             Z"ddl:m#Z$ d Z%d<e7d e7fdZ& G d dev          Z' G d dev          Z( G d dev          Z) G d dev          Z* G d dev          Z+ G d dev          Z,e                    d          d<e7fd            Z-e                    d          d<e7fd            Z.e                    d          d<e7fd            Z/e                    d          d<e7fd            Z0e                    d          dd<e7de7de0e7         fd            Z1e                    d          	 dd<e7de7de7de0e7         def
d            Z2e                    d          d<e7de7fd            Z3e                    d          d<e7fd            Z4e                    d          dd<e7de0e7         fd            Z5e                    d          d<e7fd            Z6e                    dæ          dĐe(fdń            Z7e                    dƦ          dĐe(fdǄ            Z8e                    dȦ          dĐe(fdɄ            Z9e                    dʦ          dĐe)fd˄            Z:e                    d̦          dĐe'fd̈́            Z;e                    dΦ          dĐe'fdτ            Z<e                    dЦ          dĐe*fdф            Z=e                    dҦ          dĐe+fdӄ            Z>e                    dԦ          dĐe,fdՄ            Z?d֐dאdؐdِdڐdېdܐdݐdؐdޜ	Z@e.e7e1e7ef         f         ed<    eh d          ZAde+de0e         d e.e7ef         fdZBd e.e7e-f         fdZCe                    d          dd|e0e7         fd            ZDdZEde7de7d e0e         fdZFde7de7de7de7d e.e7e7f         f
dZGe                    d          d             ZHe                    d          d             ZI G d dev          ZJeK                    d          dĐeJfd            ZLe                    d          d             ZMe                    d          dd|e0e7         fd            ZN G d dev          ZO G d dev          ZPe                    d          dde7d|e0e7         fd             ZQe                    d          dĐeOfd            ZReK                    d          dĐePfd            ZSde7fdZTe                    d          d             ZUe                    d          d             ZVe                    d	          d
             ZWe                    d          d             ZX G d dev          ZYe                    d          ddĐeYdz  fd            ZZ eD            dz  Z[e+ed<   dZ\dZ]dZ^i ddddddddddd d!d"d#d$d%d&d'd(d)d*d+d,d-d.d/d0d1d2d3d4d5d6d7Z_e.e7e7f         ed8<   i Z`e.e7eja        f         ed9<   i Zbe.e7e1e7dtf         f         ed:<   i Zce.e7e.e7e-f         f         ed;<   dde7d=e7d>ed dfd?Zdd e7fd@ZedAe/e7         de7d eja        fdBZfd<e+dCed e/e7         fdDZgd|e0e7         dEe7d e/e7         fdFZhd|e0e7         dEe7d e7fdGZi ejj        dH          Zk ejj        dI          Zl ejj        dJ          ZmdKe7dLe7de7d dfdMZndd|e0e7         d e1eja        ef         fdNZodd|e0e7         d ee7e-f         fdOZpe                    dP          dd|e0e7         fdQ            Zqe                    dR          d8ehfdS            Zre                    dT          dU             ZsddCed e/e.e7e-f                  fdWZte                    dX          ddYefdZ            Zue                    d[          dyefd\            Zv G d] d^ev          Zwd_e.e7e-f         d e7fd`Zxdaye0e7         eda<   dbe0e7         d efdcZze                    dd          de             Z{e                    df          dyewfdg            Z|e                    dh          dde7djefdk            Z}dlZ~dme/e.e7e-f                  d e/e.e7e-f                  fdnZe                    do          	 	 	 	 	 	 	 	 	 	 ddredsedtedue7dve7dwe7dxe7dye7dzed|e0e7         fd{            Ze                    d|          	 	 	 	 	 	 	 	 	 ddredsedtedue7dve7d|e7dwe7dxe7dzefd            Ze                    d          dde7dred|e0e7         fd            Zde.e7e-f         d e.e7e-f         fdZde7d e7fdZde-d e7fdZde7fdZde7d e.e7e-f         fdZde-d e/e7         fdZde7d e.e7e-f         fdZde7d e.e7e-f         fdZdddddZde7d e7fdZde7d e7fdZde7d efdZdde0e7         dred e7fdZd e.e7e7f         fdZddddde7de7de7de7de0ej                 de0e7         d e.e7e-f         fdZdddde-de7d/eded ej        f
dZde.e7e-f         d efdZde/e7         d e/e.e7e-f                  fdZde/e.e7e7f                  d e/e.e7e-f                  fdZde7d e.e7e-f         fdZde7de-d e/e.e7e-f                  fdZd<e+d e.e7e-f         fdZde7d e.e7e-f         fdZde0e7         d e7fdZddde-d|ed efdZde.e7e-f         d e-fdZde.e7e-f         dQe.e7e-f         d e-fdZde.e7e-f         dQe.e7e-f         d efdZ	 dde.e7e-f         dQe.e7e-f         de0e.e7e.e7e-f         f                  d efdZde.e7e-f         dQe.e7e-f         d e.e7e-f         fdZde7de-d e.e7e-f         fdZde.e7e-f         de-d e-fdZde7de-de.e7e-f         d dfdZde7de-d efdZd e/e.e7e-f                  fdZde7d dfdZde7de-de.e7e-f         d dfdZ ejj        d          Zde7d dfdZde\d e+fdZde\d e.e7e-f         fdZde^dQe.e7e-f         d e7fdÄZde^dQe.e7e-f         d efdĄZde\d e.e7e-f         fdńZde^de7d e7fdƄZde\de.e7e-f         d dfdǄZe                    dȦ          dde7de0e7         fdʄ            Ze                    d˦          de7defd̄            ZeK                    dȦ          dde7dede0e7         fd̈́            Ze                    dΦ          dd|e0e7         fdτ            Ze                    dЦ          dф             Ze                    dҦ          dӄ             Zdddddi dԜZeed<   e                    d֦          dd|e0e7         fdׄ            ZdؐZe1e7dtf         ed<   e                    dڦ          	 	 	 	 dd|e0e7         dededefdބ            Ze                    dߦ          dde7fd            Ze                    d          dd|e0e7         fd            Ze                    d          dd|e0e7         fd            ZeK                    d          ddeԐd|e0e7         fd            Ze                    d          ddeѐd|e0e7         fd            Z	 dde7de7dRe7de7de7de7fdZde7de7d ee7e7f         fdZde.e7e-f         d e.e7e-f         fdZeK                    dΦ          dded|e0e7         fd            Zd efdZe                    d          dd|e0e7         fd            ZeK                    d          dded|e0e7         fd            ZǐdddddZee7ee7e7f         f         ed<   ddd e/e7         fdZe                    d          ded8ehfd            Ze                    d          dded|e0e7         fd            Ze                    d          	 dded8ehd|e0e7         fd            Zi dd dddddddddd	d
dddddddddddddddddddddddddd d!d"dd#d$d%d&d'd(dd)d*d+d,d-d-dd.d/d0d1d2d2dd3d4d5d6d7d7dd8d9d:d;d<d<dd=d>d?d@dAdBddCdDdEdFdGdHdIdJdKdLdMddNdOdPdQdRdSddTdUdVdWdXdYddZd[d\d]d^d_dd`dadbdcddddedfidgdhdd(didjdkd1dld(ddmdndodpd(ddqZee7ee7e-f         f         edr<   dsZee7dtf         edt<   i dudvdwd dxdydzd{d|d}d~dd|dddd0ddddd0ddddd0ddddd|dddd|dddd0ddddd|dddd0ddddd|dddd|dddd6dxdddd0ddddd|dddd0dddd|ddd0dddd|ddd|ddd0dddd|ddd0dddd|ddd|ddd0dddd0dddd0dddÐd|dĐdd0ddƜZee7ee7e-f         f         ed<   d eee7e-f         dtf         fdȄZd ee7         fdɄZ eh dʣ          ZҐdKe7d ee7dtf         fd˄ZӐdKe7d ee7dtf         fd̄ZԐdKe7dee7e-f         de-dz  d ee7dtf         fdτZ	 ddKe7de-dz  d ee7e-f         fdЄZ֐dKe7d ee7e-f         dz  fdфZאdLe7d ee7e-f         fd҄ZؐdKe7fdӄZ	 ddee7e-f         dee7e7f         dedz  ded ee7e-f         f
dׄZڐdKe7ded dfdلZېdڐZh dۣZe G d܄ dݦ                      Zi Zee7ef         ed<    e j                    Zded e7fdZde-d e7fdZde-d e7fdZd e+fdZde-d e7dz  fdZde+d ee7dz  e7dz  e7dz  f         fdZde+d dfdZde+de7d eja        fdZdeja        dz  d dfdZde7deja        d dfdZde7de+de7d dfdZddZde+d dfdZde7ded ee7e-f         fdZdd|e0e7         d ee7e-f         fdZe                    d          defd            Ze                    d          de7fd            Ze                    d          	 dde7deȐd|e0e7         fd            Ze                    d          de7fd            ZdZde; Ze G d d                      Zi Zee7ef         ed <    e j                    Zd e7fdZde7d efdZddZde-d e7dz  fdZde7de7d e7fdZdddde7d<e7dee7e-f         dz  d	e7dz  d ee7e-f         f
d
Zdddde7d<e7dee7e-f         dz  d	e7dz  d ee7e-f         f
dZ e                    d          defd            Ze                    d          de7fd            Zdd|e0e7         d ee7e-f         fdZe                    d          	 dde7deƐd|e0e7         fd            Ze                    d          de7fd            Ze                    d          dd|e0e7         fd            ZdKe7de0e7         d e0e7         fdZeK                    d          	 ddKe7deĐd|e0e7         fd            Ze                    d          ddKe7d|e0e7         fd            Z	dde0e7         ded e7fdZ
d e.e7e-f         fdZd e.e7e-f         fd Zd e.e7e-f         fd!Zd"d#d$d%d&dd'd(d)d$d*d+dd'd,d-d.d/d0dd'd1d2d$d3d4dd'd5d6d$d7d8dd'd9d:d.d;d<ed'd=d>d?d@dAed'dBdCd.dDdEed'fZee.e7e-f         dtf         edF<   dGe7d e.e7e-f         fdHZde.e7e-f         d e0e7         fdIZde.e7e-f         de.e7e-f         d e0e7         fdJZd ee.e7e-f                  fdKZe                    dL          dd|e0e7         fdM            Ze                    dN          	 ddGe7d8ehd|e0e7         fdO            ZdPZi Ze.e7e.e7e-f         f         edQ<    e j                    Z	 ddRlmZmZmZmZ m!Z"m#Z$ d0Z%n# ez$ r dZ%Y nw xY wdSZ&ddTZ'd|e0e7         d e0e7         fdUZ(d|e0e7         d dfdVZ)	 ddGe7dWe7d|e0e7         d ee7e.e7e-f         f         fdXZ*	 ddYe7de0e7         d e0e7         fdZZ+d[e7d\e7d]ed dfd^Z,dd|e0e7         d e.e7e-f         fd_Z-	 ddYe7d`e7d|e0e7         d e.e7e-f         fdaZ.	 ddGe7d|e0e7         d e.e7e-f         fdbZ/dYe7d dfdcZ0dYe7d dfddZ1dYe7d dfdeZ2de-d e7fdfZ3de-d e7fdgZ4dYe7d dfdhZ5e                    di          	 ddGe7d8ehd|e0e7         fdj            Z6 G dk dlev          Z7e                    dm          	 ddGe7dĐe7d8ehd|e0e7         fdn            Z8e                    do          	 ddGe7dYe7d|e0e7         fdp            Z9e                    dq          	 ddYe7d8ehd|e0e7         fdr            Z:dYe7fdsZ; G dt duev          Z< G dv dwev          Z=dؐZ>d8ehd efdxZ?d|e0e7         dme/e.e7e-f                  d e.e7e-f         fdyZ@e                    dz          dĐe<fd{            ZAe                    d|          d8ehfd}            ZBe                    d~          dd|e0e7         fd            ZCe                    d          dd|e0e7         fd            ZDe                    d          dd|e0e7         fd            ZEd|e0e7         fdZFe                    d          ddYe7d|e0e7         fd            ZGe                    d          	 ddYe7d|e0e7         fd            ZHe                    d          	 	 	 ddYe7d|e0e7         dre0e         dsefd            ZIe                    d          ddYe7d|e0e7         fd            ZJ G d dev          ZKeL                    d          dYe7dĐeKfd            ZMe                    d          ddYe7d|e0e7         fd            ZN G d dev          ZOdĐeOfdZPe                    d          dĐeOfd            ZQe                    d          	 	 	 	 	 dde7djede0e7         de0e7         de0e7         f
d            ZR G d dev          ZS G d dev          ZTddde-ded e0e7         fdZUde-d e0e/e7                  fdZVde-de+d e0e7         fdZWde.e7e-f         d dfdZXde.e7e-f         de+d e.e7e-f         fdZYde0e/e7                  de7d dfdZZd e/e.e7e-f                  fdZ[d|e0e7         d e1e7e+f         fdZ\de.e7e-f         d|e7de+d e.e7e-f         fdZ]de0e7         de7fdZ^de7d e0e7         fdZ_dd|e7fdZ`d Zae                    d          dd|e7fd            Zbdde7d|e0e7         fdZce                    d          dde7d|e0e7         fd            Zddde7d|e0e7         drefdZee                    d          dde7d|e0e7         drefd            ZfddĐeSd|e7fdZge                    d          ddĐeSd|e7fd            Zhe                    d          d             Zidde7dĐeTd|e0e7         fdZjeK                    d          dde7dĐeTd|e0e7         fdÄ            Zkdde7d|e0e7         fdĄZle                    dŦ          dde7d|e0e7         fdƄ            Zmdde7d|e0e7         fdǄZne                    dȦ          dde7d|e0e7         fdɄ            Zodde7d|e0e7         fdʄZpe                    d˦          dde7d|e0e7         fd̄            Zqdde7d|e0e7         fd̈́Zre                    d          dde7d|e0e7         fd΄            Zsd|e7de7d efdτZte                    dЦ          d8ehfdф            Zu G d҄ dev          Zve                    dԦ          dՄ             Zwe                    d֦          ddĐevd|e7fdׄ            Zx G d؄ dev          Zy G dڄ dev          ZzdĐeyd ee7e.e7e-f         e0e7         f         fd܄Z{de.e7e-f         d e.e7e7f         fdބZ|de7de.e7e-f         d e.e7e-f         fdZ}e                    d          dd|e0e7         fd            Z~e                    d          ddĐeyd|e0e7         fd            ZeK                    d          ddĐezd|e0e7         fd            Ze                    d          dde7d|e0e7         fd            Ze                    d          dde7d|e0e7         fd            Ze                    d          dde7d|e0e7         fd            Z G d dev          ZeK                    d          	 dde7dĐed|e0e7         fd            Ze                    d          dd|e0e7         fd            Z G d dev          Ze                    d          ddĐed|e0e7         fd            Zde7d e7fdZe_                    dd            G d dev          Z G d dev          Zd Ze                    d          d             Ze                    d          dĐefd             Ze                    d          dĐefd            Ze                    d          d             Z G d dev          Zde7de.e7e-f         de7d e.e7e-f         fdZe                    d	          d
             Ze                    d          d             Ze                    d	          dĐefd            Ze                    d          de7fd            Z G d dev          ZeK                    d          de7dĐefd            Ze                    d          dd|e0e7         fd            Ze                    d          dd|e0e7         fd            Z G d dev          Zde-ded e.e7e-f         fdZe                    d          d             Ze                    d          dĐefd            Ze                    d          de7defd             Z G d! d"ev          Z G d# d$ev          Ze                    d%          d&             ZeK                    d'          dĐefd(            Ze                    d)          dĐefd*            Ze                    d+          d,             Ze                    d-          d.             Z G d/ d0ev          Zd e+fd1Zd e+fd2Ze                    d3          dĐefd4            Ze                    d5          d6e7fd7            Z G d8 d9ev          Ze                    d:          dĐefd;            Zdve7dz  d e7fd<Ze                    d=           eedt           efd          fdeidYefd>            Ze                    d?          d@             Z G dA dBev          Ze                    d?          dĐefdC            Z G dD dEev          Ze                    d?          dĐefdF            Ze                    dG          dH             Ze                    dI          dJ             Z G dK dLev          Zd|e0e7         d e/e7         fdMZdEe7dLe7d e7fdNZe                    dO          ddĐed|e0e7         fdP            Z G dQ dRev          Ze                    dS          ddĐed|e0e7         fdT            Z G dU dVev          Ze                    dW          	 dde0e         d|e0e7         fdX            ZdYdZd[d\d]d^d_d`dadbdc
Zd efddZdd|e0e7         d efdeZe                    df          dd|e0e7         fdg            Ze                    dh          	 dde7dwe7dred|e0e7         fdi            Ze                    dj          ddke7d|e0e7         fdl            Ze                    dm          ddke7d|e0e7         fdn            Z G do dpev          Z G dq drev          Z G ds dtev          Z G du dvev          Z G dw dxev          Z G dy dzev          Z G d{ d|ev          Z͐dde7d|e-d e-fd}Zd e.e7e-f         fd~Zd e/e.e7e-f                  fdZАde7d e+fdZѐde7d e7fdZҐde+de7dRe7d dfdZӐde+de/d         d efdZԐde+de/e7         d efdZe                    d          d             Ze                    d          dĐefd            Ze                    d          d             Ze                    d          dĐefd            Ze                    d          de7fd            Ze                    d          de7fd            ZeL                    d          de7dĐefd            Ze                    d          de7fd            Ze                    d          de7fd            ZeK                    d          de7dĐefd            ZeK                    d          de7dĐefd            ZeK                    d          de7dĐefd            Ze                    d          de7dĐefd            Z e j                    Zed|e0e7         fd            Zed|e0e7         fd            Z G d dev          Ze                    d          dd|e0e7         fd            ZeK                    d          ddĐed|e0e7         fd            Z G d dev          Z G d dev          ZddZe                    d          dde7d|e0e7         fd            Ze                    d          dĐefd            ZeK                    d          dĐefd            Ze                    d          dd|e0e7         fd            Z G d dev          ZeK                    d          dde7dĐed|e0e7         fd            Ze                    d          dde7d|e0e7         fd            Z G d dev          ZdddZde7ded e0e7         fdZde7de7fdZde7dede0e7         d e0e         fdZe                    d¦          	 dde7de0e7         d|e0e7         fdÄ            Z G dĄ dev          ZeK                    dƦ          	 dde7dĐed|e0e7         fdǄ            ZeK                    dȦ          	 dde7dĐed|e0e7         fdɄ            Z G dʄ dev          ZeK                    d̦          dde7dĐed|e0e7         fd̈́            Z G d΄ dev          Ze                    dЦ          	 dde7dĐed|e0e7         fdф            Ze                    dҦ          dd|e0e7         fdӄ            Z e                    dԦ          dd|e0e7         fdՄ            Z G dք dev          Ze                    dئ          dd|e0e7         fdل            ZeK                    dئ          ddĐed|e0e7         fdڄ            Zded e/e.e7e-f                  fd܄Zde/e.e7e-f                  de/e.e7e-f                  d e/e.e7e-f                  fd߄Zde/e.e7e-f                  d e/e.e7e-f                  fdZdded|e0e7         fdZe                    d          dded|e0e7         fd            Z	dded|e0e7         fdZ
e                    d          dded|e0e7         fd            Zej                            d          r6	 ddlmZmZ d0ZnY# ez$ r dZdZ G d de          ZY n:w xY w	 ddlmZmZ d0Zn## ez$ r dZdZ G d de          ZY nw xY w ejj        d          ZdZddlmZmZmZ  eddde          ZddZ ejj        d          Z eh d          Zddd e0e7         fdZddd efdZ ddd e0e7         fdZ!ddd efdZ"ddd e0e7         fdZ#ddd efdZ$d e7fdZ%ddd ee0e7         e7f         fd Z&ddd efdZ'	 	 	 	 dde0e7         de0e7         d|e0e7         de0e7         d eee7         e0e7         e0e         f         f
dZ( eddh          Z)d e0e7         fdZ*d e0e7         fd	Z+	 	 	 	 dde0e7         de0e7         d|e0e7         de0e7         d eee7         e0e7         e0e         f         f
d
Z,de7d e0e7         fdZ-d$e-de7dye7d dfdZ.dejd e0e7         fdZ/d$d%de7d e+fdZ0d<e+d e0e7         fdZ1d<e+d dfdZ2d5e7d e7fdZ3dZ4dZ5dZ6dZ7da8e0e	j9        j:                 ed<    e j                    Z;d e	j9        j:        fdZ<d e7fdZ=dejd e0e7         fdZ>de-de7ded|e0e7         d e-f
dZ?dejdej        dye.e7e-f         d dfd Z@dejdej        d!e-d"ed df
d#ZAd$e-d ee0ee7e-f                  e0e7         f         fd%ZBeC                    d&          dejd dfd'            ZDeC                    d(          dejd dfd)            ZEeC                    d*          dejd dfd+            ZFeC                    d,          dejd dfd-            ZGeC                    d.          dejd dfd/            ZHde0e7         d e7fd0ZId e7fd1ZJd2edfd3ZKd|d4d5d6d7d8d9d6d:d;d<d6d=d>d?d6d@dAdBd6dCdDdEd6dFdGdHd6dIdJdKd6gZLdde-dMe7dNed e0e.e7e-f                  fdOZMdPdQdRdSdTdUZNe.e7e7f         edV<   dWdXdYZOe.e7e7f         edZ<   h d[ZPh d\ZQh d]ZRh d^ZSd_ZTdQe.e7e-f         d e0e.e7e-f                  fd`ZUd efdaZVe                    db          dc             ZW G dd deev          ZXeK                    df          dĐeXfdg            ZYdhZZ eh di          Z[e                    dj          dk             Z\ G dl dmev          Z]eK                    dj          dĐe]fdn            Z^doe-dpe+d e0e7         fdqZ_d efdrZ`daae0e         eds<   ddted efduZbe                    dv          dw             Zce                    dx          dy             Zd G dz d{ev          Zed|e.e7e-f         d e.e7e-f         fd}Zfd e.e7e-f         fd~Zge                    d          d8ehfd            Zhe                    d          d8ehdĐeefd            Zide7d e7fdZje                    d          d8ehde7fd            Zke                    d          d8ehde7fd            Zle                    d          d8ehde7fd            Zme                    d          d8ehde7fd            Zn G d dev          ZoeK                    d          d8ehdĐeofd            Zp G d dev          Zqe                    d          d8ehde7dĐeqfd            Zre                    d          de7de7fd            Zsd Zt et             dd,lumZv e                    ev            eKe           ddded efdZwded dfdZxdAe7dedede7d df
dZy	 	 	 	 	 	 ddAe7dededBede7defdZzdS (  u7  
Hermes Agent — Web UI server.

Provides a FastAPI backend serving the Vite/React frontend and REST API
endpoints for managing configuration, environment variables, and sessions.

Usage:
    python -m hermes_cli.main web          # Start on http://127.0.0.1:9119
    python -m hermes_cli.main web --port 8080
    )asynccontextmanagercontextmanagerN)	dataclassdatetimetimezone)windows_detach_flagswindows_hide_flags)Path)AnyDictListOptionalTuple)__version____release_date__)cfg_getDEFAULT_CONFIGOPTIONAL_ENV_VARS clear_model_endpoint_credentialsget_config_pathget_env_pathget_hermes_homeload_configload_envread_raw_configsave_configsave_env_valueremove_env_valuecheck_config_versiondetect_install_methodformat_docker_update_message%recommended_update_command_for_method
redact_keywrite_platform_config_field_deep_merge)derive_gateway_busyderive_gateway_drainableget_running_pid_cachedget_running_pidget_runtime_status_running_pidparse_active_agentsread_runtime_status)MemoryProviderProviderFieldget_memory_provider)env_var_enabled)FastAPIFileFormHTTPExceptionRequest
UploadFile	WebSocketWebSocketDisconnect)CORSMiddleware)FileResponseHTMLResponseJSONResponseResponse)StaticFiles)	BaseModel	SecretStr)run_in_threadpool)ensureztool.dashboardF)promptz3Web UI requires fastapi and uvicorn.
Install with: z- -m pip install 'fastapi' 'uvicorn[standard]'HERMES_WEB_DISTweb_dist<   
stop_eventzthreading.Eventintervalreturnc                     ddl m}  |            }t                              d|j        |           |                    | |           dS )u  Tick the cron scheduler from inside the desktop dashboard backend.

    The scheduler tick loop normally lives in ``hermes gateway run`` — but the
    desktop app spawns a ``hermes dashboard`` backend, not a gateway, so a cron
    a user creates in the app would never fire. We run the resolved cron
    scheduler provider here (no live adapters; delivery falls back to the
    per-platform send path).

    Cross-process safe: the built-in provider's ``cron.scheduler.tick`` takes
    the ``cron/.tick.lock`` file lock, so this never double-fires alongside a
    real gateway on the same HERMES_HOME — whichever process grabs the lock
    first wins the tick.
    r   resolve_cron_schedulerz:Desktop cron scheduler started (provider=%s, interval=%ds)rI   N)cron.scheduler_providerrM   _loginfonamestart)rH   rI   rM   providers       JC:\Users\server\AppData\Local\hermes\hermes-agent\hermes_cli\web_server.py_start_desktop_cron_tickerrV      sZ     ?>>>>>%%''HIIJHM[cdddNN:N11111    c                  2    	 dd l } d S # t          $ r Y d S w xY w)Nr   )hermes_cli.gateway	Exception)
hermes_clis    rU   _warm_gateway_moduler\      s;    !!!!!!   s    
c                  T    	 ddl m}   |             S # t          $ r ddlm} |cY S w xY w)Nr   )_get_restart_drain_timeout)%DEFAULT_GATEWAY_RESTART_DRAIN_TIMEOUT)rY   r^   ImportErrorgateway.restartr_   )r^   r_   s     rU   _resolve_restart_drain_timeoutrb      sa    5AAAAAA))+++ 5 5 5IIIIII44445s    ''appr2   c                T  K   i | j         _        t          j                    | j         _        i | j         _        t          j                    | j         _        t          j                                        d t                     d }d }t          j        d          dk    rEt          j                    }t          j        t          |fdd          }|                                 t          j        t%          t&                              }	 d W V  |                                 t&                                           d {V  ||                                 d S d S # |                                 t&                                           d {V  ||                                 w w xY w)NHERMES_DESKTOP1Tzdesktop-cron-tickertargetargsdaemonrR   )stateevent_channelsasyncioLock
event_lockpty_active_session_fileschat_argv_lockget_event_looprun_in_executorr\   osgetenv	threadingEventThreadrV   rS   create_task
run_reaperPTY_REGISTRYcancel	close_allset)rc   	cron_stopcron_threadpty_reaper_tasks       rU   	_lifespanr      s     !CI"<>>CI)+CI&
  '|~~CI ,,T3GHHH
 +/I-1K	y!""c))O%%	&-&	
 
 
 	 )*\*B*BCCO   $$&&&&&&&&& MMOOOOO !  	   $$&&&&&&&&& MMOOOO !s   	E AF'c                     	 | j         j        | j         j        fS # t          $ rD i | j         _        t	          j                    | j         _        | j         j        | j         j        fcY S w xY w)aw  Return (event_channels, event_lock) from app.state.

    Lazily initialises the state if the lifespan hasn't run (e.g. when
    TestClient is constructed without a ``with`` block).  The lifespan
    path is preferred because it guarantees the Lock is created on the
    correct event loop, but the lazy path lets existing non-``with``
    TestClient usages keep working.
    )rk   rl   ro   AttributeErrorrm   rn   rc   s    rU   _get_event_stater      sl    >y')=== > > >#%	 &|~~	y')=====>s    AA('A(c                     	 | j         j        S # t          $ r, t          j                    | j         _        | j         j        cY S w xY w)zReturn the chat-argv resolution lock from app.state.

    Mirrors :func:`_get_event_state`: prefers the lifespan-initialised Lock
    (created on the correct event loop) but lazily initialises it for
    non-``with`` TestClient usages.
    )rk   rq   r   rm   rn   r   s    rU   _get_chat_argv_lockr      sN    (y'' ( ( (#*<>>	 y''''(s    3AAc                 l    	 | j         j        S # t          $ r i | j         _        | j         j        cY S w xY w)z?Return channel -> active-session-file state for dashboard PTYs.)rk   rp   r   r   s    rU   _get_pty_active_session_filesr      sH    2y11 2 2 2-/	*y11112s    "33Hermes Agent)titleversionlifespan)routerHERMES_DASHBOARD_SESSION_TOKEN    zX-Hermes-Session-TokenT_reveal_timestamps      z*^https?://(localhost|127\.0\.0\.1)(:\d+)?$*)allow_origin_regexallow_methodsallow_headers)PUBLIC_API_PATHSrequestc                    | j                             t          d          }|r@t          j        |                                t                                                    rdS | j                             dd          }dt           }t          j        |                                |                                          S )a=  True if the request carries a valid dashboard session token.

    The dedicated session header avoids collisions with reverse proxies that
    already use ``Authorization`` (for example Caddy ``basic_auth``). We still
    accept the legacy Bearer path for backward compatibility with older
    dashboard bundles.
     TauthorizationBearer )headersget_SESSION_HEADER_NAMEhmaccompare_digestencode_SESSION_TOKEN)r   session_headerauthexpecteds       rU   _has_valid_session_tokenr   D  s     _(()=rBBN $-   t?33D)))Ht{{}}hoo.?.?@@@rW   z/api/files/download_QUERY_TOKEN_API_PATHSpathc                     |t           vrdS | j                            dd          }t          |          o=t	          j        |                                t                                                    S )NFtokenr   )r   query_paramsr   boolr   r   r   r   )r   r   r   s      rU   _has_valid_query_tokenr   ^  s]    )))u $$Wb11E;;W4.u||~~~?T?T?V?VWWWrW   c                     t          | j        j        dd          r)t          | j        dd          dS t          dd          t	          |           st          dd          dS )uQ  Authorize a sensitive endpoint, raising 401 if the caller isn't allowed.

    Two auth schemes protect the dashboard, exactly one active per bind:

    * **Loopback / ``--insecure`` mode** (``auth_required`` False): the
      ephemeral ``_SESSION_TOKEN`` is injected into the SPA HTML and echoed
      back via ``X-Hermes-Session-Token`` (or the legacy ``Bearer`` header).
      Validate it here.
    * **Gated / OAuth mode** (``auth_required`` True): ``_SESSION_TOKEN`` is
      NOT injected (the SPA authenticates with a session cookie), so there is
      no token to check. The ``gated_auth_middleware`` has already verified the
      cookie before the request reached this handler — any non-public ``/api/``
      route it lets through carries a verified ``request.state.session``. The
      legacy ``auth_middleware`` likewise short-circuits in this mode. Requiring
      the (absent) token here would 401 every cookie-authenticated request,
      making plugin install/enable/disable and the other ``_require_token``
      endpoints permanently unreachable behind the gate. Defer to the gate.
    auth_requiredFsessionN  Unauthorizedstatus_codedetail)getattrrc   rk   r5   r   )r   s    rU   _require_tokenr   e  s|    & w{ /599 D 7=)T22>FNCCCC#G,, DNCCCCD DrW   >   	localhost::1	127.0.0.1_LOOPBACK_HOST_VALUEShostallow_publicc                     | t           vS )u  Return True iff the dashboard auth gate must be active.

    Truth table:
      host == loopback        → False (no auth — local-only, trusted operator)
      host != loopback        → True  (gate engages — OAuth or password required)

    "Loopback" is 127.0.0.1, localhost, ::1. RFC1918 / CGNAT / link-local are
    deliberately treated as PUBLIC — a hostile device on the same LAN is exactly
    the threat model the gate is designed for.

    ``allow_public`` (the legacy ``--insecure`` escape hatch) NO LONGER disables
    the gate. It is accepted for backward-compat with old launch scripts and
    desktop shells but is ignored: a non-loopback bind ALWAYS requires an auth
    provider (OAuth or the bundled password provider). This closes the
    unauthenticated-public-dashboard hole behind the June 2026 ``hermes-0day``
    MCP-persistence campaign, where ``--insecure --host 0.0.0.0`` left the
    config/MCP/agent surface open to internet scanners.
    )r   )r   r   s     rU   should_require_authr     s    & ,,,rW   host_header
bound_hostc                    | sdS |                                  }|                    d          r<|                    d          }|dk    r|d|         }n8|                     d          }n"d|v r|                    dd          d         n|}|                                }|d	v rd
S |                                }|t
          v r	|t
          v S ||k    S )a#  True if the Host header targets the interface we bound to.

    Accepts:
    - Exact bound host (with or without port suffix)
    - Loopback aliases when bound to loopback
    - Any host when bound to 0.0.0.0 (explicit opt-in to non-loopback,
      no protection possible at this layer)
    F[]   z[]:r   >   ::0.0.0.0T)strip
startswithfindrsplitlowerr   )r   r   hclose	host_onlybound_lcs         rU   _is_accepted_hostr     s      u 	A||C ;sB;;!E'
IIII+.!88AHHS!$$Q''	!!I
 &&&t !!H(((111   rW   httpc                    K   t          t          j        dd          }|r>| j                            dd          }t          ||          st          dddi          S  ||            d{V S )	u  Reject requests whose Host header doesn't match the bound interface.

    Defends against DNS rebinding: a victim browser on a localhost
    dashboard is tricked into fetching from an attacker hostname that
    TTL-flips to 127.0.0.1. CORS and same-origin checks don't help —
    the browser now treats the attacker origin as same-origin with the
    dashboard. Host-header validation at the app layer catches it.

    See GHSA-ppp5-vxwm-4cf7.
    r   Nr   r     r   zVInvalid Host header. Dashboard requests must use the hostname the server was bound to.r   content)r   rc   rk   r   r   r   r=   )r   	call_nextr   r   s       rU   host_header_middlewarer     s       L$77J o))&"55 j99 		@    7#########rW   c                   K   | j         j        }|                    d          rPt          | j        dd          p9t          | j        j        dd          pt          |           pt          | |          }|r|                    d          }t          |          dk    r|d         r	 dd	l
m}m}  |            } |            }n,# t          $ r t                      }t                      }Y nw xY wt                      }	t!          fd
|	D             d          }
|
r|
                    d          nd}|dk    r|v s|vrt%          dddi          S n|dk    r|v rt%          dddi          S  ||            d{V S )u"  Block requests to disabled plugin API routes at request time.

    :func:`_mount_plugin_api_routes` gates at import time, but if a plugin
    is disabled *after* the dashboard is already running, its FastAPI router
    remains mounted until restart.  This middleware enforces the enabled/
    disabled policy on every request to ``/api/plugins/{name}/...`` so that
    runtime config changes take effect immediately.

    Registered BEFORE the auth middlewares (so it executes AFTER them): a
    request that hasn't cleared auth must get auth's 401 first, never this
    gate's 404 — otherwise an unauthenticated caller could fingerprint which
    plugins are installed/enabled by reading the status code. We only reach
    the enabled/disabled check for a request that auth already let through.
    /api/plugins/token_authenticatedFr   /      r   _get_enabled_set_get_disabled_setc              3   N   K   | ]}|                     d           k    |V   dS rR   Nr   .0pplugin_names     rU   	<genexpr>z+_plugin_api_runtime_gate.<locals>.<genexpr>  s7      LLqquuV}}/K/K/K/K/K/KLLrW   Nsourceuser  r   Plugin not foundr   bundled)urlr   r   r   rk   rc   r   r   splitlenhermes_cli.plugins_cmdr   r   rZ   r~   _get_dashboard_pluginsnextr   r=   )r   r   r   _authedpartsr   r   enabled_setdisabled_setpluginspluginr   r   s               @rU   _plugin_api_runtime_gater     s/       ;D'' .
 GM#8%@@ 5w{(/5AA5'005 &gt44	 	  $	JJsOOE5zzQ#Ah 	-        '7&6&8&8'8'8':':$ - - -&)ee'*uu-
 566G!LLLLGLLL F 6<GVZZ111F''&,66+[:X:X#/,/)13E(F$ $ $  ;Y
  9,,&,66#/,/)13E(F$ $ $  7#########s   +C &C10C1c                 6   K   ddl m}  || |           d {V S )Nr   )gated_auth_middleware)$hermes_cli.dashboard_auth.middlewarer  )r   r   r  s      rU   _dashboard_auth_gater  :  s=      JJJJJJ&&w	:::::::::rW   c                   K   t          | j        dd          r ||            d{V S t          | j        j        dd          r ||            d{V S | j        j        }|                    d          r;|t          vr2t          |           s#t          | |          st          dddi	          S  ||            d{V S )
zERequire the session token on all /api/ routes except the public list.r   FNr   z/api/r   r   r   r   )
r   rk   rc   r   r   r   _PUBLIC_API_PATHSr   r   r=   )r   r   r   s      rU   auth_middlewarer  @  s      w}3U;; (Yw''''''''' w{ /599 (Yw''''''''';Dw D0A$A$A'00 	9OPWY]9^9^ 	!>2    7#########rW   c                 6   K   ddl m}  || |           d{V S )u  Outermost auth seam: non-interactive bearer-token auth for opted-in routes.

    Registered LAST so it runs FIRST (Starlette middleware is outermost-last).
    A registered token route is fully owned here — authenticate by token,
    attach the principal + ``token_authenticated`` flag, and let the downstream
    cookie/session gates skip enforcement. Non-token routes pass straight
    through untouched.
    r   )token_auth_middlewareN)$hermes_cli.dashboard_auth.token_authr	  )r   r   r	  s      rU   _token_auth_seamr  W  s@       KJJJJJ&&w	:::::::::rW   c                      ddg} 	 ddl m} |                      |                       n&# t          $ r |                     dg           Y nw xY wt	          t
                              |                     S )a  Discovered memory providers for the ``memory.provider`` select.

    Directory-scan only (no provider imports), so it's safe at module import
    time. ``""`` (built-in) is always first; discovery failures degrade to the
    bundled defaults rather than dropping the field.
    r   builtinr   )list_memory_provider_nameshoncho)plugins.memoryr  extendrZ   listdictfromkeys)optionsr  s     rU   _memory_provider_optionsr  j  s     9oG#======11334444 # # #z"""""# g&&'''s   #*  AAzmemory.providerselectzMemory provider plugin)typedescriptionr  modelstringz0Default model (e.g. anthropic/claude-sonnet-4.6)generalr  r  categorymodel_context_lengthnumberz=Context window override (0 = auto-detect from model metadata)zterminal.backendzTerminal execution backend)localdockersshmodaldaytonasingularityzterminal.modal_modezModal sandbox modesandboxfunctionztts.providerzText-to-speech provider)edge
elevenlabsopenaineuttszstt.providerzSpeech-to-text provider)r!  groqr+  xair*  zstt.elevenlabs.model_idzElevenLabs Scribe model	scribe_v2	scribe_v1zdisplay.skinzCLI visual theme)defaultaresmonoslatezdashboard.themezWeb dashboard visual theme)r1  midnightemberr3  	cyberpunkrosezdisplay.resume_displayz$How resumed sessions display history)minimalfulloffzdisplay.busy_input_modez%Input behavior while agent is running)	interruptqueuesteerzapprovals.modezDangerous command approval mode)manualsmartr;  zcontext.enginezContext management enginer1  customzhuman_delay.modezSimulated typing delay mode)r;  typingfixedzlogging.levelzLog level for agent.log)DEBUGINFOWARNINGERRORzagent.service_tierz#API service tier (OpenAI/Anthropic))r   autor1  flexz(Reasoning effort for delegated subagents)r   r9  lowmediumhighxhighmaxultrazWhen the chat app / gateway updates Hermes (no terminal prompt), what to do with uncommitted local source edits. 'stash' keeps them and re-applies them after the update; 'discard' throws them away. Terminal updates always ask, regardless of this setting.stashdiscardr   zRefresh an already-installed cua-driver during hermes update. Disable this on non-admin macOS accounts where /Applications is not writable.)r  r  )zdelegation.reasoning_effortz%updates.non_interactive_local_changeszupdates.refresh_cua_driver_SCHEMA_OVERRIDESprivacysecuritycontextagentskillscronnetworkcheckpoints	approvalshuman_delaydisplay	dashboardcode_executionprompt_cachinggoalsupdates
onboardingtelegramdiscordcomputer_use_CATEGORY_MERGE)r  rV  terminalr]  
delegationmemorycompressionrT  browservoicettssttloggingre  	auxiliaryvaluec                     t          | t                    rdS t          | t                    rdS t          | t                    rdS t          | t                    rdS t          | t
                    rdS dS )z*Infer a UI field type from a Python value.booleanr   r  objectr  )
isinstancer   intfloatr  r  rr  s    rU   _infer_typerz    sz    % y% x% x% v% x8rW   r   configprefixc                    i }|                                  D ],\  }}|r| d| n|}|dv r|r|                    d          d         }nt          |t                    r|}nd}t          |t                    r$|                    t          ||                     t          |          |                    dd                              dd                                          |d}|t          v r |                    t          |                    t                              |d	         |d	                   |d	<   |||<   .|S )
uF   Walk DEFAULT_CONFIG and produce a flat dot-path → field schema dict..>   _config_versionr   r  u    → _ r  r  )itemsr   rv  r  update_build_schema_from_configrz  replacer   rR  rg  r   )r{  r|  schemakeyrr  full_keyr  entrys           rU   r  r  !  sk   
 )+Fllnn % %
U(.7f$$s$$$C ***  	!||C((+HHt$$ 	!HH HeT"" 	%MM3E8DDEEEE $E**'//W==EEc3OOUUWW$% %E ,,,.x8999 / 3 3E*4EuZGX Y YE*$F8MrW   _ordered_schemac                   4    e Zd ZU eed<   dZee         ed<   dS )ConfigUpdater{  Nprofile)__name__
__module____qualname__r  __annotations__r  r   str rW   rU   r  r  W  s/         LLL!GXc]!!!!!rW   r  c                   L    e Zd ZU eed<   eed<   dZee         ed<   dZeed<   dS )EnvVarUpdater  rr  Nr  r   api_key)r  r  r  r  r  r  r   r  r  rW   rU   r  r  \  sJ         	HHHJJJ!GXc]!!! GSrW   r  c                   4    e Zd ZU eed<   dZee         ed<   dS )EnvVarDeleter  Nr  r  r  r  r  r  r  r   r  rW   rU   r  r  h  /         	HHH!GXc]!!!!!rW   r  c                   4    e Zd ZU eed<   dZee         ed<   dS )EnvVarRevealr  Nr  r  r  rW   rU   r  r  m  r  rW   r  c                   .    e Zd ZU i Zeeef         ed<   dS )MemoryProviderConfigUpdatevaluesNr  r  r  r  r   r  r   r  r  rW   rU   r  r  r  *         FDcNrW   r  c                   .    e Zd ZU i Zeeef         ed<   dS )MemoryProviderSetupRequestr  Nr  r  rW   rU   r  r  v  r  rW   r  c                   |    e Zd ZU dZee         ed<   i Zee	e	f         ed<   g Z
ee	         ed<   dZee	         ed<   dS )MessagingPlatformUpdateNenabledenv	clear_envr  )r  r  r  r  r   r   r  r  r   r  r  r   r  r  rW   rU   r  r  z  sh         "GXd^"""Cc3hItCy "GXc]!!!!!rW   r  c                   *    e Zd ZU dZee         ed<   dS )TelegramOnboardingStartNbot_name)r  r  r  r  r   r  r  r  rW   rU   r  r    s&         "Hhsm"""""rW   r  c                   @    e Zd ZU ee         ed<   dZee         ed<   dS )TelegramOnboardingApplyallowed_user_idsNr  r  r  r  r   r  r  r  r   r  rW   rU   r  r    s6         3i!GXc]!!!!!rW   r  c                   ^    e Zd ZU dZee         ed<   dZee         ed<   dZee         ed<   dS )WhatsAppOnboardingStartbotmoder   allowed_usersNr  	r  r  r  r  r   r  r  r  r  r  rW   rU   r  r    sN         D(3-#%M8C=%%%!GXc]!!!!!rW   r  c                   ^    e Zd ZU dZee         ed<   dZee         ed<   dZee         ed<   dS )WhatsAppOnboardingApplyNr  r  r  r  r  rW   rU   r  r    sN         D(3-#'M8C='''!GXc]!!!!!rW   r  c                   4    e Zd ZU eed<   dZee         ed<   dS )AudioTranscriptionRequestdata_urlN	mime_type)r  r  r  r  r  r  r   r  rW   rU   r  r    s/         MMM#Ix}#####rW   r  c                   2    e Zd ZU eed<   eed<   dZeed<   dS )ManagedFileUploadr   r  T	overwriteN)r  r  r  r  r  r  r   r  rW   rU   r  r    s4         
IIIMMMItrW   r  c                   4    e Zd ZU eed<   dZee         ed<   dS )ChatImageUploadr  Nfilename)r  r  r  r  r  r  r   r  rW   rU   r  r    s/         MMM"Hhsm"""""rW   r  c                       e Zd ZU eed<   dS )ManagedDirectoryCreater   Nr  r  r  r  r  r  rW   rU   r  r             
IIIIIrW   r  c                   (    e Zd ZU eed<   dZeed<   dS )ManagedFileDeleter   F	recursiveN)r  r  r  r  r  r  r   r  rW   rU   r  r    s+         
IIIItrW   r  z.aac.flacz.m4a.mp3z.mp4.ogg.wav.webm)z	audio/aac
audio/flacz	audio/m4az	audio/mp3	audio/mp4
audio/mpeg	audio/ogg	audio/wavz
audio/wave
audio/webmzaudio/x-m4azaudio/x-wav
video/webm_AUDIO_MIME_EXTENSIONSi  r  c                     | pd                     dd          d                                                                         }t                              |d          S )Nr   ;r   r   r  )r   r   r   r  r   )r  
normalizeds     rU   _audio_extension_for_mimer    sN    /r((a00399;;AACCJ!%%j':::rW   c                       e Zd ZU dZeed<   eed<   eed<   dZeed<   dZeed<   dZeed<   d	Z	e
ed
<   dZee         ed<   dS )ModelAssignmentu  Payload for POST /api/model/set — assign a provider/model to a slot.

    scope="main"        → writes model.provider + model.default
    scope="auxiliary"   → writes auxiliary.<task>.provider + auxiliary.<task>.model
    scope="auxiliary" with task=""  → applied to every auxiliary.* slot
    scope="auxiliary" with task="__reset__"  → resets every slot to provider="auto"
    scoperT   r  r   taskbase_urlr  Fconfirm_expensive_modelNr  )r  r  r  __doc__r  r  r  r  r  r  r   r  r   r  rW   rU   r  r    s           JJJMMMJJJD#NNN Hc GS$)T)))!GXc]!!!!!rW   r  c                   F    e Zd ZU dZeed<   dZeed<   dZee         ed<   dS )MoaModelSlotr   rT   r  Nreasoning_effort)	r  r  r  rT   r  r  r  r  r   r  rW   rU   r  r    sF         HcE3OOO '+hsm*****rW   r  c                       e Zd ZU g Zee         ed<    e            Zeed<   dZe	e
         ed<   dZe	e
         ed<   dZeed<   dZe	e         ed<   dZe	e         ed	<   d
Zeed<   dS )MoaPresetPayloadreference_models
aggregatorNreference_temperatureaggregator_temperature   
max_tokensreference_max_tokensfanoutTr  )r  r  r  r  r  r  r  r  r  r   rx  r  r  rw  r  r  r  r  r   r  rW   rU   r  r    s         +-d<(---+|~~J--- .28E?111.2HUO222J +/(3-... FHSM   GTrW   r  c                       e Zd ZU dZeed<   dZeed<   i Zeee	f         ed<   g Z
ee         ed<    e            Zeed<   dZee         ed	<   dZee         ed
<   dZeed<   dZee         ed<   dZee         ed<   dZeed<   dZee         ed<   dS )MoaConfigPayloadr1  default_presetr   active_presetpresetsr  r  Nr  r  r  r  r  r  Tr  r  )r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   rx  r  r  rw  r  r  r  r   r  r  rW   rU   r  r    s        #NC###M3+-GT#''(--- ,.d<(---+|~~J----18E?111.2HUO222J*.(3-... FHSM   GT!GXc]!!!!!rW   r  rT   c                 :   ddl m} ddlm}m} ddlm} ddlm}m	} | pd
                                }|pd
                                }	 ||          }
	 t                      }n# t          $ r i }Y nw xY wt          |t                    r|                    d          nd} ||t          |t                    r|ni           } ||t          |t                    r ||          ng           }|	|j        |	fS |	|j        |	fS |
|vrd	|	v r	 |                    d
i           }t          |t                    rIt#          |                    dd          pd          
                                                                nd}n# t          $ r d}Y nw xY wddlm} |r ||          |v r ||          }
|}nd}
d}|
|v rU|
                    d          s@	  ||	|
          }|r|}	n.# t          $ r! t*                              d||	d           Y nw xY w||	fS )u  Normalize a main-slot (provider, model) pair before persisting.

    The Models page has two assignment paths and only one of them was safe:

    - The "Change" picker sends a real Hermes provider slug — fine.
    - The per-card "Use as → Main model" menu sends ``entry.provider``
      from the analytics rows, falling back to the model's VENDOR prefix
      (``modelVendor("anthropic/claude-opus-4.6") == "anthropic"``) when
      the session row has no ``billing_provider`` (older sessions, NULL
      rows).  That wrote ``provider: anthropic`` +
      ``default: anthropic/claude-opus-4.6`` to config — a vendor-prefixed
      OpenRouter slug on the NATIVE Anthropic provider.  New sessions then
      400 against api.anthropic.com ("model: anthropic/claude-opus-4.6 not
      found") and the user reads it as "changing models does nothing".

    Two repairs, both at this single chokepoint so every caller inherits:

    1. Vendor-name → Hermes-provider mapping: when the provider string is
       not a known Hermes provider/alias (e.g. ``moonshotai``, ``x-ai`` is
       known but ``poolside`` isn't) but the model is a vendor-prefixed
       aggregator slug, keep the user's CURRENT aggregator if they're on
       one, else fall back to openrouter.
    2. Model-format normalization for the resolved provider via
       ``normalize_model_for_provider`` (e.g. ``anthropic/claude-opus-4.6``
       on native anthropic → ``claude-opus-4-6``).
    r   )get_compatible_custom_providers)_KNOWN_PROVIDER_NAMESnormalize_provider)normalize_model_for_provider)resolve_custom_providerresolve_user_providerr   	providersNr   r  rT   )_AGGREGATOR_PROVIDERS
openrouterrA  z$model normalization failed for %s/%sTexc_info)hermes_cli.configr  hermes_cli.modelsr  r  hermes_cli.model_normalizer  hermes_cli.providersr  r  r   r   rZ   rv  r  r   idr  r   r  r   rP   debug)rT   r  r  r  r  r  r  r  prov_inmodel_in	canonicalcfguser_providersuser_providercustom_providercur_cfgcur_providerr  normalized_models                      rU    _normalize_main_model_assignmentr    s
   6 BAAAAAKKKKKKKKGGGGGGSSSSSSSS~2$$&&G""$$H""7++Imm   -7T-B-BLSWW[)))N)):nd#C#CK M .-0:30E0EM'',,,2 O  ))"!8++---#//	gggr**G gt,,5GKK
B//5266<<>>DDFFF24 L  	 	 	LLL	;;;;;; 	#..|<<@UUU**<88I"GG$I"G ))))2F2Fx2P2P)	a;;HiPP ,+ 	a 	a 	aJJ=w[_J`````	a Hs7   A$ $A32A3A6F FFG+ +(HH	model_cfgr   r  r  c                    t          | t                    si } t          |                     d          pd                                                                          }|                                                                }|| d<   || d<   |                                r|                                | d<   n |                     d          r||k    rd| d<   |                                r.|                                | d<   |                     dd           n,|                     d          r||k    rt          | d	           ||k    rt          | d
           |                     dd           | S )u  Apply a main-slot model assignment to a ``model`` config dict in place.

    Sets ``provider``/``default``, then reconciles ``base_url``:

    - An explicitly supplied ``base_url`` is always persisted (covers
      ``custom``/local endpoints and any provider whose key is bound to a
      non-default host).
    - Otherwise, a stale ``base_url`` is cleared ONLY when switching to a
      *different* provider — that URL belonged to the old provider. When the
      provider is unchanged and no new URL is supplied, the existing
      ``base_url`` is preserved. This keeps a user's custom endpoint (e.g. a
      Xiaomi MiMo Token Plan host, ``https://token-plan-*.xiaomimimo.com/v1``)
      alive when they merely re-pick a model under the same provider — picking
      a model previously wiped it, forcing the registry default and breaking
      Token Plan keys.

    The runtime resolver reads ``model.base_url`` from config (it ignores
    ``OPENAI_BASE_URL``) and only honors it when the configured provider matches
    and the pool entry is on the registry default, so preserving it here is what
    lets the override actually route. The hardcoded ``context_length`` override
    is always dropped since the new model may have a different context window.

    Returns the same dict (coerced to a fresh dict if the input wasn't one) so
    callers can assign it straight back onto the model config.
    rT   r   r1  r  r  apiNF)clear_api_mode)clear_api_keycontext_length)rv  r  r  r   r   r   popr   )r  rT   r  r  r  prev_providernew_providers          rU   _apply_main_model_assignmentr  g  sz   8 i&& 		j117R88>>@@FFHHM>>##))++L$Ij Ii~~ # ( 0 0	*	z	"	" #|}'D'D !#	*
 }} J&}}	)eT""""	y	!	! Jlm&C&C(5IIII}$$(%HHHHMM"D)))rW   GATEWAY_HEALTH_URLGATEWAY_HEALTH_TIMEOUT3u>   Invalid GATEWAY_HEALTH_TIMEOUT value %r — using default 3.0sg      @g      ?c                     t           sdS t                               d          } |                     d          r| dt          d                    } n-|                     d          r| dt          d                    } |  d|  dfD ]}	 t          j                            |d          }t          j                            |t                    5 }|j	        d	k    r8t          j        |                                          }d
|fcddd           c S 	 ddd           n# 1 swxY w Y   # t          $ r Y w xY wdS )u>  Probe the gateway via its HTTP health endpoint (cross-container).

    .. deprecated::
        Driven by the deprecated ``GATEWAY_HEALTH_URL`` /
        ``GATEWAY_HEALTH_TIMEOUT`` env vars.  Scheduled for removal alongside
        a move to a first-class dashboard config key.  See
        :data:`_GATEWAY_HEALTH_URL` for context.

    Uses ``/health/detailed`` first (returns full state), falling back to
    the simpler ``/health`` endpoint.  Returns ``(is_alive, body_dict)``.

    Accepts any of these as ``GATEWAY_HEALTH_URL``:
    - ``http://gateway:8642``                (base URL — recommended)
    - ``http://gateway:8642/health``         (explicit health path)
    - ``http://gateway:8642/health/detailed`` (explicit detailed path)

    This is a **blocking** call — run via ``run_in_executor`` from async code.
    )FNr   z/health/detailedNz/healthGET)methodtimeout   T)_GATEWAY_HEALTH_URLrstripendswithr   urllibr   r6   urlopen_GATEWAY_HEALTH_TIMEOUTstatusjsonloadsreadrZ   )baser   reqrespbodys        rU   _probe_gateway_healthr1    s   &  { %%c**D}}'(( '.s-..../	y	!	! '%s9~~o%&***t,<,<,<=  	.((e(<<C''5L'MM &QU;#%%:diikk22D:& & & & & & & & & &%& & & & & & & & & & & & & & &  	 	 	H	;s=   AD05D$D0D0$D(	(D0+D(	,D00
D=<D=c                  >   ddl m} m} |                                 sdS  |d          }	 |                    dd          }t          j                    t          fd|D                       |                                 S # |                                 w xY w)a  Return the dashboard status active-session count.

    This is best-effort status garnish, not a critical path.  Use a read-only
    connection so /api/status never tries to initialise or migrate state.db
    while another Hermes process is writing to it.
    r   )DEFAULT_DB_PATH	SessionDBT)	read_only2   )limitcompact_rowsc           	   3      K   | ]L}|                     d           5|                     d|                     dd                    z
  dk     HdV  MdS )ended_atNlast_active
started_atr   ,  r   r   )r   snows     rU   r   z0_count_status_active_sessions.<locals>.<genexpr>  sm       
 
uuZ  (quu]AEE,,B,BCCCsJJ JJJJ
 
rW   )hermes_stater3  r4  existslist_sessions_richtimesumr   )r3  r4  dbsessionsr?  s       @rU   _count_status_active_sessionsrG    s     87777777
 !!## q	T	"	"	"B	((r(EEikk 
 
 
 

 
 
 
 
 	







s   AB Bc                  j  K   t          j                    } 	 t          j        |                     d t                    t
                     d {V S # t           j        $ r# t                              dt
                     Y n1t          $ r%}t                              d|           Y d }~nd }~ww xY wdS )Nr   z</api/status active session count exceeded %.2fs; returning 0z0/api/status active session count unavailable: %sr   )
rm   get_running_loopwait_forrs   rG  _STATUS_ACTIVE_SESSIONS_TIMEOUTTimeoutErrorrP   r  rZ   )loopexcs     rU   _status_active_sessionsrO    s      #%%DL%  'DEE3
 
 
 
 
 
 
 
 
 	
  
 
 


J+	
 	
 	
 	
 	
  L L L

EsKKKKKKKKL1s   9A /B0	B0B++B0	image/png
image/jpeg	image/gif
image/webpimage/svg+xmlz	image/bmpimage/x-icon).png.jpg.jpeg.gif.webp.svg.bmp.icoHERMES_DASHBOARD_FILES_ROOTi  @z	/opt/data)frozenc                   4    e Zd ZU eed<   edz  ed<   eed<   dS )ManagedFilesPolicydefault_pathNlocked_rootcan_change_path)r  r  r  r   r  r   r  rW   rU   ra  ra    s:         rW   ra  >   .hg.svn.next.venv.cache.turbodistvenvbuildrh   __pycache__node_modules.git>   	auth.json	auth.lockbws_cache.json.git-credentialsgoogle_oauth.jsongoogle_token.json.anthropic_oauth.jsongoogle_oauth_pending.jsonwebhook_subscriptions.jsoncredentialsconfig.yamlz
mcp-tokenspairingrR   c                     |                                  }|dk    s|                    d          s|dk    rdS |t          v S )af  Return True for a basename the managed-files API must never expose.

    Covers ``.env`` / ``.env.<suffix>`` / ``.envrc`` variants plus the
    canonical Hermes credential-store basenames (see
    ``_SENSITIVE_MANAGED_FILE_BASENAMES`` above).

    Case-insensitive so ``.ENV`` / ``.Env.local`` / ``Auth.JSON`` on
    case-insensitive filesystems (macOS/Windows mounts) can't slip past
    the guard.

    Basename-only: for the directory-tree credential stores
    (``mcp-tokens/``, ``pairing/``) that the canonical guards also deny,
    use :func:`_is_sensitive_path`, which the API call sites route through.
    .envz.env.z.envrcT)r   r   !_SENSITIVE_MANAGED_FILE_BASENAMES)rR   lowereds     rU   _is_sensitive_filenamer  [  sJ     jjllG&G..w777h;N;Nt777rW   c                 j    t          | j                  rdS t          d | j        D                       S )uf  Return True for any path the managed-files API must never expose.

    Combines the basename denylist (:func:`_is_sensitive_filename`) with a
    credential-directory-tree check: a path is sensitive if its own basename
    is sensitive OR any of its path components is a credential directory
    (``mcp-tokens`` / ``pairing``). The component match is case-insensitive
    and needs no HERMES_HOME resolution, so it blocks these trees wherever
    they sit under the operator-configured managed root — closing the gap
    the canonical guards cover as directory trees but a basename-only check
    would miss.

    Read-side only: this guards list/read/download (the #57505 exfil surface).
    The write endpoints (upload/mkdir/delete) are a separate threat class
    handled by the write-path checks; extending this guard to them is out of
    scope for this fix.
    Tc              3   L   K   | ]}|                                 t          v V   d S N)r   _SENSITIVE_MANAGED_DIR_NAMESr   parts     rU   r   z%_is_sensitive_path.<locals>.<genexpr>  s0      SStzz||;;SSSSSSrW   )r  rR   anyr   r   s    rU   _is_sensitive_pathr  p  s:    " di(( tSS
SSSSSSrW   i   i   i   i   z.ccz.confiniz.cppcpp.csscssz.csvcsvz.gogoz.graphqlgraphqlz.hz.hpp.htmlhtmlz.javajava.js
javascript.jsonr*  z.jsxjsxz.ktkotlinz.lualuaz.mdmarkdownpythonrubyrustshellsqlxmltoml
typescripttsxtextyaml).mjsz.pyz.rbz.rsz.shz.sqlr[  z.tomlz.tsz.tsxz.txtz.xmlz.yamlz.ymlz.zshz.avizvideo/x-msvideor\  r  rY  rX  rW  r  z.mkvzvideo/x-matroskaz.movzvideo/quicktimer  z	video/mp4r  .opuszaudio/ogg; codecs=opusrV  r[  r  r  rZ  raw_pathc                    t          | pd                                          }|st          dd          d|v rt          dd          	 |                                                    d          rZt
          j                            |          }|j        r|j        dvrt          t
          j
                            |j                  }t          |                                          }|                                st          j                    |z  }|                    d	
          S # t$          t&          t          f$ r t          dd          w xY w)Nr   r   zPath is requiredr    Invalid pathzfile:>   r   r   Fstrict)r  r   r5   r   r   r&  parseurlparsenetloc
ValueErrorr   url2pathnamer   r   
expanduseris_absolutecwdresolveOSErrorRuntimeError)r  rawparsed	candidates       rU   _fs_pathr    sP   
hn"


#
#
%
%C H4FGGGGs{{NCCCCD99;;!!'** 	;\**3//F} !6G!G!G  .--fk::CII((**	$$&& 	/

Y.I   ...\:. D D DNCCCCDs   C!D/ /)Ec                     | j                                         }|t          v rt          |         S t          j        t          |                     \  }}|pdS )Napplication/octet-stream)suffixr   _FS_MIME_TYPES	mimetypes
guess_typer  )r   r  guessedr  s       rU   _fs_mime_typer    sQ    [  Ff%%%c$ii00JGQ000rW   datac                 t    | sdS d| v rdS t          d | D                       }|t          |           z  dk    S )NF    Tc              3   .   K   | ]}|d k     |dvdV  dS )r   >   	   
      r   Nr  )r   bytes     rU   r   z#_fs_looks_binary.<locals>.<genexpr>  s4      QQ44"99[9P9PQ9P9P9P9PQQrW   gQ?)rD  r   )r  
suspiciouss     rU   _fs_looks_binaryr    sO     u}}tQQ4QQQQQJD		!D((rW   c                    t          t          |                     }	 |                                }n# t          $ r t	          dd          t
          $ r t	          dd          t          $ r t	          dd          t          $ r%}t	          dt          |          pd          d }~ww xY wt          j        |j	                  rt	          dd          t          j
        |j	                  st	          dd	          ||fS )
Nr   File not foundr     File is not readabler   r  Path points to a directoryzOnly regular files can be read)r  r  statFileNotFoundErrorr5   NotADirectoryErrorPermissionErrorr  S_ISDIRst_modeS_ISREG)r   rh   strN  s       rU   _fs_regular_filer    sB   c$ii  F	P[[]] F F F4DEEEE F F F4DEEEE L L L4JKKKK P P PCHH4NOOOOP|BJ R4PQQQQ<
## V4TUUUU2:s   3 AB0 B++B0rS   c                     | }t          d          D ]P}	 |dz                                  rt          |          c S n# t          $ r Y  d S w xY w|j        }||k    r d S |}Qd S )Nr6  rp  )rangerA  r  r  parent)rS   	directoryr  r  s       rU   _fs_find_git_rootr    s    I2YY 	 		F"**,, &9~~%%%& 	 	 	444	!Y44		4s   %>
AAc                      t                                          d          pi } t          |                     d          p t          j                            d          pd                                          }|ru|dvrq	 t          |                                                              d          }|	                                rt          |          S n# t          t          f$ r Y nw xY wt          t          j                              S )Nrh  r  TERMINAL_CWDr   >   r  rH  r~  Fr  )r   r   r  rt   environr   r   r  r  is_dirr  r  r  )cfg_terminalr  r  s      rU   _fs_default_cwdr    s    ==$$Z006BL
lu%%M)G)GM2
N
N
T
T
V
VC
 s...	S		,,..66e6DDI!! &9~~%&& 	 	 	D	txzz??s    AC C-,C-r  c                     	 ddddd}t           j        dk    rt                      |d<   t          j        dd| d	d
gfi |}|j        dk    r|j                                        ndS # t          $ r Y dS w xY w)NT   F)capture_outputr  r!  checkwin32creationflagsgit-Cbranchz--show-currentr   r   )	sysplatformr
   
subprocessrun
returncodestdoutr   rZ   )r  
run_kwargsresults      rU   _fs_git_branchr    s    "	&
 &

 <7""*<*>*>J'D#x)9:
 

 
 )/(9Q(>(>v}""$$$BF   rrs   A$A' '
A54A5c                      t                      } | dz  | dz  | dz  g}g }|D ]A}	 |                    |                                           +# t          t          f$ r Y >w xY w|S )u  Directories ``GET /api/media`` is allowed to read from.

    Confined to where the agent and attach pipeline actually write media on the
    gateway host — its images dir and cache subtree. This stops an authenticated
    client from reading image-extension files anywhere on disk (e.g. a renamed
    key or a screenshot outside the cache) merely because the suffix passes the
    allowlist.
    imagesscreenshotscache)r   appendr  r  r  )homerootsoutroots       rU   _media_serve_rootsr  ,  s     DH_d]2D7NCEC  	JJt||~~&&&&& 	 	 	H	Js   'AA A z
/api/mediac                   K   	 t          |                                                                           n&# t          t          f$ r t          dd          w xY wj                                        t          vrt          dd          t                      }t          fd|D                       st          dd                                          st          d	d
                                          j        t          k    rt          dd          t          j                                                                      d          }ddt          j                                                  d| iS )u  Return a gateway-local image file as a base64 data URL.

    Lets remote clients (the desktop app over the network, or the web dashboard
    in a browser) display images the agent wrote to *this* machine's filesystem
    — they can't read the gateway's local disk directly.

    Auth-gated by the session token like every other /api route. Restricted to
    an image-extension allowlist, a size cap, AND the gateway's own media roots
    (resolved, symlink-safe) so it can't be used to read arbitrary files.
    r   r  r   i  zUnsupported media typec              3   6   K   | ]}|k    p|j         v V  d S r  parents)r   r  rh   s     rU   r   zget_media.<locals>.<genexpr>U  s3      JJDv~7!7JJJJJJrW   r  zPath outside media rootsr   r    File too largeasciir  data:;base64,)r   r  r  r  r  r5   r  r   _MEDIA_CONTENT_TYPESr  r  is_filer  st_size_MEDIA_MAX_BYTESbase64	b64encode
read_bytesdecode)r   r  encodedrh   s      @rU   	get_mediar  @  s     Dd&&((0022\" D D DNCCCCD }$8884LMMMM  EJJJJEJJJJJ P4NOOOO>> F4DEEEE{{}}///4DEEEEv002233::7CCG^ 4V]5H5H5J5J K^^U\^^__s	   39 #Arequire_existsr  c                    	 |                                                      |          S # t          $ r |rt          dd           t          t
          f$ r t          dd          w xY w)Nr  r   Path not foundr   r   r  )r  r  r  r5   r  r  )r   r  s     rU   _canonical_pathr  a  s    D  (((???    	JC8HIIII\" D D DNCCCCDs
   '* A A*c                 F   t          |                                           }	 |                    dd           |                                }n-# t          t
          f$ r}t          dd|           d }~ww xY w|                                st          dd          |S )NTr	  exist_ok  z#Managed files root is unavailable: r   z%Managed files root is not a directory)r   r  mkdirr  r  r  r5   r  )r  r  resolvedrN  s       rU   _ensure_managed_rootr$  l  s    >>$$&&Da

4$
///<<>>\" a a a4_Z]4_4_````a?? ]4[\\\\Os   +A A9 A44A9r  rh   c                      || k    p| |j         v S r  r  )r  rh   s     rU   _path_is_underr&  x  s    T>3TV^33rW   c                 v    t          | pd                                          }d|v rt          dd          |S )Nr   r  r   r  r   )r  r   r5   )r  r  s     rU   
_path_textr(  |  s@    x~2$$&&D~~NCCCCKrW   c                     t          | j        j        dd          rdS | j        j        pd                                }| j        r| j        j        nd                                }h d}||v p||v S )Nr   Fr   >   r   r   
testclient
testserverr   r   )r   rc   rk   r   hostnamer   clientr   )r   r   client_hostlocal_hostss       rU   _local_dashboard_requestr0    s}    w{ /599 uK &B--//D*1.@7>&&bGGIIKSSSK;<+"<<rW   c                     t           j                            dd                                          } | sdS 	 ddlm}  |                                                                d          }nL# t          t          f$ r8 t          |                                                               d          }Y nw xY w|t          k    S )NHERMES_HOMEr   Fr   )get_default_hermes_rootr  )rt   r  r   r   hermes_constantsr3  r  r  r  r  r   _HOSTED_MANAGED_FILES_ROOT)r  r3  r  s      rU    _default_hermes_root_is_opt_datar6    s    
*..
+
+
1
1
3
3C u<<<<<<<&&((3355==U=KK\" < < <Cyy##%%--U-;;<---s   6A/ /AB87B8c                      t                      rdS 	 ddlm}   |             sdS n# t          $ r Y dS w xY w	 t	          t
                    }|dk    rdS n# t          $ r Y nw xY wdS )u  Return true when the dashboard should not offer ``hermes update``.

    Containerized dashboards are updated by the outer launcher/image, not by an
    in-browser local update action. Keep this dashboard capability separate
    from install-method detection: manual git/pip installs inside containers can
    still behave like their actual install method in the CLI.

    However, when the install method is ``git`` (a bind-mounted checkout inside
    a container — e.g. the hermes-webui image sharing the Hermes source tree),
    the dashboard's ``hermes update`` button is the correct update path and
    should not be suppressed. Other containerized install methods remain
    externally managed unless their apply path is proven safe inside the
    running container filesystem.
    Tr   )is_containerFr  )r6  r4  r8  rZ   r!   PROJECT_ROOT)r8  r  s     rU   *_dashboard_local_update_managed_externallyr:    s     ()) t111111|~~ 	5	   uu&|44U??5    4s   % 
33A 
A! A!)create_rootr;  c                   t           j                            t          d                                          }|r?|rt          |          nt          t          |                    }t          ||d          S t                      r/|rt          t                    nt          }t          ||d          S t          t          j                              }t          |d d          S )Nr   F)rb  rc  rd  T)rt   r  r   _MANAGED_FILES_ROOT_ENVr   r$  r  r   ra  r6  r5  r  )r   r;  raw_forced_rootr  r  s        rU   _managed_files_policyr?    s    jnn%<bAAGGIIO ^8Co#O444Y]^mYnYnIoIo!tW\]]]] ()) ^CNn#$>???Tn!tW\]]]]49;;''D4TSWXXXXrW   	for_writerA  c                   t          |          }t          |           }|j        }|	|r|dv r|}n|s|j        }nt	          |                                          }|I|                                s5t          d |j        D                       rt          dd          ||z  }n%|                                st          dd          d|j        v rt          dd          |r3|
                                st          |j                  }||j        z  }nt          ||           }|!t          ||          st          d	d
          ||t          |          fS )N>   r~  r   c              3   "   K   | ]
}|d k    V  dS )..Nr  r  s     rU   r   z(_resolve_managed_path.<locals>.<genexpr>  s&      <<D44<<<<<<<rW   r   zPath cannot contain '..'r   zPath must be absoluterD  r  r  Path outside managed files root)r?  r(  rc  rb  r   r  r  r  r   r5   rA  r  r  rR   r&  r  )	r  r   rA  policyr  r  r  r  r#  s	            rU   _resolve_managed_pathrG    s    #7++FhDD););		 	Q'		JJ))++	I$9$9$;$;<<IO<<<<< X#<VWWWWy(II&&(( 	QC8OPPPPy4NOOOO L))++ L !122IN*"9]KKKtX > >4UVVVV8S]]**rW   rF  c                 R    | j         t          | j                   nd }||| j        dS )N)r  rc  rd  )rc  r  rd  )rF  rc  s     rU   _managed_response_metarI    s;    -3-?-K#f()))QUK"!1  rW   c                 <   	 |                                 }n&# t          t          f$ r t          dd          w xY w| j        &t          | j        |          st          dd          	 |                                }n&# t          $ r}t          dd|           d }~ww xY w|                                }|rd n t          j	        |j
                  d         pd	}|j
        p|j
        pt          |          t          |          ||rd n|j        |j        |d
S )Nr   r  r   r  rE  r!  zCould not stat path: r   r  )rR   r   is_directorysizemtimer  )r  r  r  r5   rc  r&  r  r  r  r  rR   r  r  st_mtime)rF  rh   r#  r  rN  r  r  s          rU   _managed_file_entryrO    sS   D>>##\" D D DNCCCCD%nV=OQY.Z.Z%4UVVVVS]]__ S S S4QC4Q4QRRRRS __FjY%9(-%H%H%K%iOiI=x}=HH.BJ  s     #:+B   
B#
BB#r  c                 
   | pd                                 }|                    d          rd|vrt          dd          |                    dd          \  }}|dd                              d	d          d
         pd}d|vrt          dd          	 t	          j        |d          }n+# t          j        t          f$ r t          dd          w xY wt          |          t          k    rt          dd          ||fS )Nr   r  ,r   z!Upload payload must be a data URLr   r   r   r  r   r  ;base64z%Upload payload must be base64 encodedTvalidatez"Upload payload is not valid base64r
  File is too large)r   r   r5   r   r  	b64decodebinasciiErrorr  r   _MANAGED_FILE_MAX_BYTES)r  r  headerr  r  r  s         rU   _decode_data_urlr[    s-   N!!##D??7## Ys$4WXXXXjja((OFGqrr
  a((+I/II4[\\\\Z$777NJ' Z Z Z4XYYYYZ
4yy***4GHHHH?s   B- -(C>   r\  rY  rW  rV  rX  rZ  ))s   PNG

rV  )s   rW  )s   GIF87arY  )s   GIF89arY  )s   BMr\  ._CHAT_IMAGE_MAGICr  c                     t          t          | pd                                                    j        }t	          j        dd|          }|                                                    d          }|pdS )Nr   z[\x00-\x1f]+r  r~  pasted-image)r   r  r   rR   resub)r  r  s     rU   _sanitize_chat_image_filenamera  6  se    SR((..00116IY77I!!'',,I&&rW   c                     | d d         }|                     d          r|dd         dk    rdS t          D ]\  }}|                     |          r|c S d S )N   s   RIFF      s   WEBPrZ  )r   r\  )r  headsigexts       rU   _chat_image_extensionri  =  st    9Dw D2J'$9$9w%  S??3 	JJJ	4rW   payloadc                 j   t          | j                  \  }}|                                                    d          st	          dd          t          |          t          k    rt          dz  }t	          dd| d          t          |          }|t          vrt	          dd	          |||fS )
Nzimage/r   zUpload payload must be an imager      r
  zImage is too large; cap is z MBzUnsupported image type)	r[  r  r   r   r5   r   _CHAT_IMAGE_UPLOAD_MAX_BYTESri  _CHAT_IMAGE_ALLOWED_EXTENSIONS)rj  r  r  mbrh  s        rU   _decode_chat_image_uploadrp  G  s    &w'788OD)??''11 W4UVVVV
4yy///)k:4YRT4Y4Y4YZZZZ

%
%C
0004LMMMMCrW   z/api/chat/image-uploadr  c           	        K   t          |           \  }}}t          |          5 }|pt                      }t          |          dz  }	 |                    dd           n@# t
          $ r t          dd          t          $ r}t          dd|           d	}~ww xY wt          t          | j	                            j
        pd
}	t          j        dd|	                              d          pd
}	t          j                                        d          }
|d|
 dt#          j        d           d|	 | z  }	 |                    |           n@# t
          $ r t          dd          t          $ r}t          dd|           d	}~ww xY w	 d	d	d	           n# 1 swxY w Y   dt)          |          |j        t-          |          |dS )u  Persist a browser-provided chat image where the embedded TUI can read it.

    The dashboard /chat page runs Hermes inside an xterm.js PTY. Browser
    clipboard image bytes are not visible to the server-side clipboard, so the
    page uploads them here, then drives the TUI's ``/image <path>`` command
    with the returned gateway-visible path. Files land under
    ``HERMES_HOME/images/`` — the same directory ``clipboard.paste`` /
    ``image.attach`` already use.
    r  Tr  r  zImage directory is not writabler   r!  z"Could not create image directory: Nr^  z[^A-Za-z0-9_.-]+r  z._-z%Y%m%d_%H%M%S
dashboard_r   zCould not write image: )okr   rR   bytesr  )rp  _profile_scoper   r   r"  r  r5   r  ra  r  stemr_  r`  r   r   r?  strftimesecrets	token_hexwrite_bytesr  rR   r   )rj  r  r  r  rh  scoped_homer  img_dirrN  rv  tsrh   s               rU   upload_chat_imager~  U  s      5W==D)S		 	  YK/o//t**x'	dMM$M6666 	[ 	[ 	[C8YZZZZ 	d 	d 	dC8b]`8b8bcccc	d 1'2BCCDDI[^v)355;;EBBTn\^^$$_55OOOW->q-A-AOODO#OOO	Yt$$$$ 	[ 	[ 	[C8YZZZZ 	Y 	Y 	YC8WRU8W8WXXXX	Y %!Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y. FT  sZ   #F 	A! F !$BBBBF =EF $F7FFF  F$'F$z
/api/filesc                 @  K   t          ||           \  }}|                                st          dd          |                                st          dd          	 fd|                                D             }n@# t
          $ r t          dd          t          $ r}t          d	d
|           d }~ww xY w|                    d            j        }d }|j	        |k    r|||k    rt          |j	                  }|||dt                    S )Nr   r  r   r   zPath is not a directoryc                 N    g | ]!}t          |          t          |          "S r  )r  rO  )r   childrF  s     rU   
<listcomp>z&list_managed_files.<locals>.<listcomp>  sA     
 
 
%e,,
..
 
 
rW   r  zDirectory is not readabler!  zCould not read directory: c                 b    | d          t          | d                                                   fS )NrK  rR   r  r   items    rU   <lambda>z$list_managed_files.<locals>.<lambda>  s+    tN';#;Sf=N=N=T=T=V=V"W rW   r  )r   r  entries)rG  rA  r5   r  iterdirr  r  sortrc  r  r  rI  )	r   r   rh   display_pathr  rN  rc  r  rF  s	           @rU   list_managed_filesr    s     #8w#G#G FFL==?? F4DEEEE==?? O4MNNNN	X
 
 
 
))
 
 

  Q Q Q4OPPPP X X X4VQT4V4VWWWWX LLWWLXXX$KF}K$76[;P;PV]##  !
(
(	 s   # B $C(B<<Cz/api/files/readc                 8  K   t          ||           \  }}}|                                st          dd          |                                st          dd          t	          |          rt          dd          	 |                                j        }n&# t          $ r}t          dd	|           d }~ww xY w|t          k    rt          d
d          t          j
        |j                  d         pd}	 t          j        |                                                              d          }n@# t           $ r t          dd          t          $ r}t          dd|           d }~ww xY w|j        |||d| d| dt#          |          S )Nr   r  r   r   Path is not a filer  (Access to sensitive files is not allowedr!  Could not stat file: r
  rU  r   r  r  r  zCould not read file: r  r  )rR   r   rL  r  r  )rG  rA  r5   r  r  r  r  r  rY  r  r  rR   r  r  r  r  r  rI  )	r   r   rF  rh   r  rL  rN  r  r  s	            rU   read_managed_filer    s     #8w#G#G FFL==?? F4DEEEE>> J4HIIII&!! `4^____S{{}}$ S S S4QC4Q4QRRRRS%%%4GHHHH$V[11!4R8RIS"6#4#4#6#677>>wGG L L L4JKKKK S S S4QC4Q4QRRRRS 8I88w88  !
(
( s0   B 
B?&B::B? 9D: :$E7E22E7c                 H  K   t          ||           \  }}}|                                st          dd          |                                st          dd          t	          |          rt          dd          	 |                                j        }n&# t          $ r}t          dd	|           d
}~ww xY w|t          k    rt          dd          t          j
        |j                  d         pd}t          t          |          ||j        d          S )u  Stream a managed file as an attachment download.

    Remote clients (desktop app, browser dashboard) open agent-written files
    that live on *this* gateway's disk, not theirs. Auth-gated like every other
    managed-files route — ``auth_middleware`` additionally accepts the session
    token as a ``?token=`` query param here so a shell/browser-opened download
    (which can't set the session header) still authenticates. See ``/api/pty``
    for the same query-token precedent.
    r   r  r   r   r  r  r  r!  r  Nr
  rU  r   r  
attachmentr   
media_typer  content_disposition_type)rG  rA  r5   r  r  r  r  r  rY  r  r  rR   r;   r  )r   r   rF  rh   _display_pathrL  rN  r  s           rU   download_managed_filer    sN      %:$$H$H!FFM==?? F4DEEEE>> J4HIIII&!! `4^____S{{}}$ S S S4QC4Q4QRRRRS%%%4GHHHH$V[11!4R8RI[[!-	   s   B 
B?&B::B?z/api/files/uploadc                 `  K   t          | j        |d          \  }}}|                                r%|                                rt	          dd          |                                r| j        st	          dd          t          | j                  \  }}	 |j        	                    dd           |
                    |           n@# t          $ r t	          dd	          t          $ r}t	          d
d|           d }~ww xY wdt          ||          |dt          |          S )NTr@    'A directory already exists at that pathr   File already existsr  r  File is not writabler!  Could not write file: rs  r  r   )rG  r   rA  r  r5   r  r[  r  r  r"  rz  r  r  rO  rI  )rj  r   rF  rh   r  r  
_mime_typerN  s           rU   upload_managed_filer    st     #8wZ^#_#_#_ FFL}} _6==?? _4]^^^^}} Kw0 K4IJJJJ'(899D*TD48884     L L L4JKKKK T T T4RS4R4RSSSST $VV44  !
(
(	 s   1C $D
1DD
rl  z/api/files/upload-streamfiler  c                 L  K   t          || d          \  }}}|                                r%|                                rt          dd          |                                r|st          dd          	 |j                            dd           n@# t          $ r t          dd	          t          $ r}t          d
d|           d }~ww xY wt          j	        d|j
         ddt          |j                            \  }}	t          |	          }
d}d}	 t          j        |d          5 }	 |                    t                      d {V }|snD|t#          |          z  }|t$          k    rt          dd          |                    |           g	 d d d            n# 1 swxY w Y   t          j        |
|           d}nJ# t          $ r  t          $ r t          dd	          t          $ r}t          d
d|           d }~ww xY w	 |s|
                    d           |                                 d {V  n7# |s|
                    d           |                                 d {V  w xY wdt/          ||          |dt1          |          S )NTr@  r  r  r   r  r  r  r  r!  z#Could not create parent directory: r~  .uploadr|  r  dirr   Fwbr
  rU  r  
missing_okr  )rG  rA  r  r5   r  r"  r  r  tempfilemkstemprR   r  r   rt   fdopenr,  _UPLOAD_CHUNK_BYTESr   rY  writer  unlinkr   rO  rI  )r   r  r   r  rF  rh   r  rN  tmp_fdtmp_nametmp_pathtotalrenamedr  chunks                  rU   upload_managed_file_streamr  
  s      $9wRV#W#W#W FFL}} _6==?? _4]^^^^}} Ky K4IJJJJaD48888 L L L4JKKKK a a a4_Z]4_4_````a
  '!6;!!!)V]9K9K  FH H~~HEGYvt$$ 	!!"ii(;<<<<<<<< U#222'C@STTTT		%   ! 		! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	
8V$$$    L L L4JKKKK T T T4RS4R4RSSSST 	  	-OOtO,,,jjll  	-OOtO,,,jjll $VV44  !
(
(	 sg   :B $C;CC G 5A)F+G +F//G 2F/3G I .H<HHI 4J z/api/files/mkdirc                   K   t          | j        |d          \  }}}|                                r%|                                st	          dd          	 |                    dd           n@# t          $ r t	          dd          t          $ r}t	          d	d
|           d }~ww xY wdt          ||          |dt          |          S )NTr@  r  z"A file already exists at that pathr   r  r  zDirectory is not writabler!  zCould not create directory: r  )
rG  r   rA  r  r5   r"  r  r  rO  rI  )rj  r   rF  rh   r  rN  s         rU   create_managed_directoryr  J  s     #8wZ^#_#_#_ FFL}} Zv}} Z4XYYYYZTD1111 Q Q Q4OPPPP Z Z Z4XSV4X4XYYYYZ $VV44  !
(
(	 s   A0 0$B-B((B-c                 h  K   t          | j        |          \  }}}|j        ||j        k    rt          dd          |j        |k    rt          dd          |                                st          dd          	 |                                r1| j        rt          j	        |           n)|
                                 n|                                 nE# t          $ r8}|                                r	| j        sdnd}t          |d	|           d }~ww xY wd
|dt          |          S )Nr   z$Cannot delete the managed files rootr   z!Cannot delete the filesystem rootr   r  r  r!  zCould not delete path: Trs  r   )rG  r   rc  r5   r  rA  r  r  shutilrmtreermdirr  r  rI  )rj  r   rF  rh   r  rN  r   s          rU   delete_managed_filer  _  s[     #8w#O#O FFL%&F4F*F*F4Z[[[[}4WXXXX==?? F4DEEEE
]==?? 	  f%%%%MMOOO ] ] ]#]]__OW5FOccC<[VY<[<[\\\\] OO0Fv0N0NOOs   AC 
D%3DDz/api/fs/listc           	      *  K   t          |           }	 g }t          j        |          5 }|D ]X}|j        t          v r|                    |j        t          ||j        z            |                    d          d           Y	 d d d            n# 1 swxY w Y   |                    d            d|iS # t          $ r g ddcY S t          $ r g d	dcY S t          $ r g d
dcY S t          $ r }g t          |dd           pddcY d }~S d }~ww xY w)NF)follow_symlinks)rR   r   isDirectoryc                 V    | d          | d                                          | d         fS )Nr  rR   r   r  s    rU   r  zfs_list.<locals>.<lambda>  s,    4+>'>V@R@R@T@TVZ[aVb&c rW   r  r  ENOENT)r  errorENOTDIREACCESstrerrorz
read-error)r  rt   scandirrR   _FS_READDIR_HIDDENr   r  r  r  r  r  r  r  r   )r   rh   r  scanr  rN  s         rU   fs_listr  x  s     d^^FXZ 	4  :!333!J 344#(<<<#F#F       	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	ccddd7## 2 2 211111 3 3 3	22222 2 2 211111 X X XZ(F(F(V,WWWWWWWWXsS   B8 ABB8 BB8 BB8 8D	DD)	D2DDDz/api/fs/read-textc           	        K   t          t          |                     \  }}|j        t          k    rt	          dd          t          |j        t                    }	 |                    d          5 }|                    |          }d d d            n# 1 swxY w Y   nL# t          $ r t	          dd          t          $ r%}t	          dt          |          pd          d }~ww xY wt          |d d	                   |j        t                              |j                                        d
          t#          |          t          |          |                    dd          |j        t          k    dS )Nr
  r  r   rbr  r  r   File read failedr  r  utf-8r  errors)binarybyteSizelanguagemimeTyper   r  	truncated)r  r  r  _FS_TEXT_SOURCE_MAX_BYTESr5   min_FS_TEXT_PREVIEW_MAX_BYTESopenr,  r  r  r  r  _FS_PREVIEW_LANGUAGE_BY_EXTr   r  r   r  r  )r   rh   r  bytes_to_readhandler  rN  s          rU   fs_read_textr    s     !(4..11JFB	z---4DEEEE
$>??MT[[ 	.&;;}--D	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. L L L4JKKKK T T TCHH4R@RSSSST #4;//J/33FM4G4G4I4I6RR!&))FGI66Z"<<  s<   B! 3B	B! BB! BB! !$C* C%%C*c                   $    e Zd ZU eed<   eed<   dS )FsWriteTextr   r   Nr  r  rW   rU   r  r    s"         
IIILLLLLrW   r  z/api/fs/write-textc                   K   t          | j                  }| j        pd}t          |                    d                    t
          k    rt          dd          	 |                                }nY# t          $ r d}Y nKt          $ r t          dd          t          $ r%}t          d	t          |          pd
          d}~ww xY w|*t          j        |j                  rt          d	d          |*t          j        |j                  st          d	d          |j                                        st          d	d          |                    d|j         dt'          j                               }	 |                    |d           t'          j        ||           nl# t          $ r( |                    d           t          dd          t          $ r/}|                    d           t          dd|           d}~ww xY wdt          |          t          |                    d                    dS )aJ  Overwrite (or create) a UTF-8 text file for the in-app spot editor.

    Mirrors the local Electron ``hermes:fs:writeText`` hardening: the path is
    resolved + validated by ``_fs_path``, the parent directory must already
    exist (we never build directory trees), only regular files may be replaced,
    and the payload is size-capped. The write is staged to a sibling temp file
    and ``os.replace``-d into place so a crash mid-write can't truncate the
    original. Stale-on-disk detection is the client's job (re-read before save),
    so both transports behave identically.
    r   r  r
  zContent too larger   Nr  r  r   r  r  z!Only regular files can be writtenzParent directory does not existr~  z.hermes-tmp-encodingTr  r!  r  )rs  r   r  )r  r   r   r   r   _FS_TEXT_WRITE_MAX_BYTESr5   r  r  r  r  r  r  r  r  r  r  	with_namerR   rt   getpid
write_textr  r  )rj  rh   r  r  rN  tmps         rU   fs_write_textr    s      gl##F? bD
4;;w  #;;;4GHHHHP'-{{}}    L L L4JKKKK P P PCHH4NOOOOP 
~$,rz22~4PQQQQ	~dl2:66~4WXXXX=!! W4UVVVV


Ev{EE	EE
F
FCTtg...

3 L L L

d
###4JKKKK T T T

d
###4RS4R4RSSSST FT[[=Q=Q9R9RSSSs6   A2 2C #C# CC?,F, ,:H&*HHz/api/fs/read-data-urlc                   K   t          t          |                     \  }}|j        t          k    rt	          dd          	 t          j        |                                                              d          }nL# t          $ r t	          dd          t          $ r%}t	          dt          |          pd          d }~ww xY wd	d
t          |           d| iS )Nr
  r  r   r  r  r  r   r  dataUrlr  r  )r  r  r  _FS_DATA_URL_MAX_BYTESr5   r  r  r  r  r  r  r  r  )r   rh   r  r  rN  s        rU   fs_read_data_urlr    s      !(4..11JFB	z***4DEEEET"6#4#4#6#677>>wGG L L L4JKKKK T T TCHH4R@RSSSSTG}V44GGgGGHHs   9A> >$C" CCz/api/fs/git-rootc                    K   t          |           }	 |                                }t          j        |j                  r|n|j        }n# t
          $ r |}Y nw xY wdt          |          iS )Nr  )r  r  r  r  r  r  r  )r   rh   r  rS   s       rU   fs_git_rootr    sy      d^^F[[]],rz22E   %e,,--s   6A
 
AAz/api/fs/default-cwdc                  F   K   t                      } | t          |           dS )N)r  r  )r  r  )r  s    rU   fs_default_cwdr    s'      


C."5"5666rW   )web_gitc                    K   t          j                    }	  |j        d| g|R   d{V S # t          $ r%}t	          dt          |          pd          d}~ww xY w)zIRun a (blocking) git op off the event loop; map a failed mutation to 400.Nr   zgit operation failedr   )rm   rI  rs   r  r5   r  )fnri   rM  rN  s       rU   _git_opr  		  s      #%%DX)T)$:T:::::::::: X X XCHH4V@VWWWWXs   , 
A AAc                 :    t          t          |                     S r  )r  r  r  s    rU   	_git_pathr  	  s    x~~rW   c                       e Zd ZU eed<   dS )GitPathBodyr   Nr  r  rW   rU   r  r  	  r  rW   r  c                   4    e Zd ZU eed<   dZee         ed<   dS )GitFileBodyr   Nr  )r  r  r  r  r  r  r   r  rW   rU   r  r  	  s/         
IIID(3-rW   r  c                   2    e Zd ZU eed<   eed<   dZeed<   dS )GitCommitBodyr   messageFpushN)r  r  r  r  r  r  r   r  rW   rU   r  r  	  s4         
IIILLLD$rW   r  c                       e Zd ZU eed<   dZee         ed<   dZee         ed<   dZee         ed<   dZ	ee         ed<   dS )GitWorktreeAddBodyr   NrR   r  r-  existingBranch)
r  r  r  r  r  rR   r   r  r-  r  r  rW   rU   r  r  $	  sk         
IIID(3- FHSM   D(3-$(NHSM(((((rW   r  c                   2    e Zd ZU eed<   eed<   dZeed<   dS )GitWorktreeRemoveBodyr   worktreePathFforceNr  r  r  r  r  r  r   r  rW   rU   r  r  ,	  s7         
IIIE4rW   r  c                   $    e Zd ZU eed<   eed<   dS )GitBranchSwitchBodyr   r  Nr  r  rW   rU   r  r  2	  s"         
IIIKKKKKrW   r  z/api/git/statusc                 `   K   t          t          j        t          |                      d {V S r  )r  _web_gitrepo_statusr  r  s    rU   git_status_router  7	  s1      -y?????????rW   z/api/git/worktreesc                 d   K   dt          t          j        t          |                      d {V iS )N	worktrees)r  r  worktree_listr  r  s    rU   git_worktrees_router  <	  s6      wx'=yOOOOOOOOPPrW   z/api/git/branchesc                 d   K   dt          t          j        t          |                      d {V iS Nbranches)r  r  branch_listr  r  s    rU   git_branches_router  A	  s6      gh&:IdOOLLLLLLLLMMrW   z/api/git/base-branchesc                 d   K   dt          t          j        t          |                      d {V iS r  )r  r  base_branch_listr  r  s    rU   git_base_branches_router  F	  s6      gh&?4QQQQQQQQRRrW   z/api/git/review/listuncommittedr  r-  c                 d   K   t          t          j        t          |           ||           d {V S r  )r  r  review_listr  )r   r  r-  s      rU   git_review_list_router  K	  s5      -ytLLLLLLLLLrW   z/api/git/review/diffstagedc           	      l   K   dt          t          j        t          |           ||||           d {V iS Ndiff)r  r  review_diffr  )r   r  r  r-  r   s        rU   git_review_diff_router%  P	  sB       '("6	$uVZ\bccccccccddrW   z/api/git/file-diffc                 f   K   dt          t          j        t          |           |           d {V iS r"  )r  r  file_diff_vs_headr  )r   r  s     rU   git_file_diff_router(  W	  s8      '("<iootTTTTTTTTUUrW   z/api/git/review/commit-contextc                 `   K   t          t          j        t          |                      d {V S r  )r  r  review_commit_contextr  r  s    rU   git_commit_context_router+  \	  s1      74IIIIIIIIIrW   z/api/git/review/rev-parserefc                 f   K   dt          t          j        t          |           |           d {V iS )Nsha)r  r  review_rev_parser  )r   r,  s     rU   git_rev_parse_router0  a	  s8      !:IdOOSQQQQQQQQRRrW   z/api/git/review/ship-infoc                 `   K   t          t          j        t          |                      d {V S r  )r  r  review_ship_infor  r  s    rU   git_ship_info_router3  f	  s1      2IdOODDDDDDDDDrW   z/api/git/review/stager0  c                 v   K   t          t          j        t          | j                  | j                   d {V S r  )r  r  review_stager  r   r  r0  s    rU   git_stage_router7  k	  s9      .	$)0D0DdiPPPPPPPPPrW   z/api/git/review/unstagec                 v   K   t          t          j        t          | j                  | j                   d {V S r  )r  r  review_unstager  r   r  r6  s    rU   git_unstage_router:  p	  s9      0)DI2F2F	RRRRRRRRRrW   z/api/git/review/revertc                 v   K   t          t          j        t          | j                  | j                   d {V S r  )r  r  review_revertr  r   r  r6  s    rU   git_revert_router=  u	  s9      /491E1EtyQQQQQQQQQrW   z/api/git/review/commitc                    K   t          t          j        t          | j                  | j        | j                   d {V S r  )r  r  review_commitr  r   r   r  r6  s    rU   git_commit_router@  z	  s?      /491E1Et|UYU^_________rW   z/api/git/review/pushc                 j   K   t          t          j        t          | j                             d {V S r  )r  r  review_pushr  r   r6  s    rU   git_push_routerC  	  s5      -y/C/CDDDDDDDDDrW   z/api/git/review/create-prc                 j   K   t          t          j        t          | j                             d {V S r  )r  r  review_create_prr  r   r6  s    rU   git_create_pr_routerF  	  s5      2Idi4H4HIIIIIIIIIrW   z/api/git/worktree/addc                    K   d | j         | j        | j        | j        d                                D             }t          t          j        t          | j	                  |           d {V S )Nc                     i | ]
\  }}|||S r  r  )r   r  rr  s      rU   
<dictcomp>z*git_worktree_add_route.<locals>.<dictcomp>	  s6     	 	 	C 	U	 	 	rW   )rR   r  r-  r  )
rR   r  r-  r  r  r  r  worktree_addr  r   )r0  r  s     rU   git_worktree_add_routerK  	  s      	 	 IkI"1	
 

 %''	 	 	G .	$)0D0DgNNNNNNNNNrW   z/api/git/worktree/removec                    K   t          t          j        t          | j                  t          | j                  | j                   d {V S r  )r  r  worktree_remover  r   r  r  r6  s    rU   git_worktree_remove_routerN  	  s[       )DI"6"6	$BS8T8TVZV`        rW   z/api/git/branch/switchc                 v   K   t          t          j        t          | j                  | j                   d {V S r  )r  r  branch_switchr  r   r  r6  s    rU   git_branch_switch_routerQ  	  s9      /491E1Et{SSSSSSSSSrW   )porti!  )rR  i!  )rR  i!  )webhook_porti="  )rR  !  )rS  rT  )rS  i  )rS  i  )	webhook
api_servermsgraph_webhookfeishuwecom_callbackbluebubblessmswhatsapp_cloudline_PORT_BINDING_PLATFORM_PORTS>   fatalstoppeddisconnectedprofile_homeruntimec                 t   |pi                      d          pi }d |                                D             }|si S i }	 t          | dz  d          5 }t          j        |          pi }ddd           n# 1 swxY w Y   t          |                     d          t                    r|                     d          ni }|pi                      d          |                     d          fD ]p}t          |t                    s|                                D ]C\  }	}
t          |
t                    r)|                    |	i                               |
           Dqn# t          $ r i }Y nw xY wi }|D ]}t          |         \  }}|                     |          pi }t          |                     d          t                    r|                     d          ni }|                     ||pi                      ||                    }	 t          |          ||<   # t          t          f$ r |||<   Y w xY w|S )	a  Best-effort map of ``platform -> host TCP port`` for one profile's gateway.

    Reads the platforms the running gateway reported in its
    ``gateway_state.json`` and resolves each port-binding platform's port from
    the profile's ``config.yaml`` (top-level ``platforms:`` wins over
    ``gateway.platforms:``, matching ``load_gateway_config`` precedence),
    falling back to the adapter default.  Display-only: env-var port overrides
    (e.g. ``WEBHOOK_PORT`` in that profile's .env) are not resolved here.
    	platformsc                     g | ]A\  }}|t           v r3t          |t                    r|                    d           t          v?|BS )rk   )r^  rv  r  r   _PLATFORM_DEAD_STATES)r   rR   rk   s      rU   r  z+_profile_platform_ports.<locals>.<listcomp>	  s^       u///ud## 0IIg&;;; 	 <;;rW   r{  r  r  Ngatewayextra)r   r  r  r  	safe_loadrv  r  
setdefaultr  rZ   r^  rw  	TypeErrorr  )rb  rc  re  activeblocksfr  gateway_cfgsrc	plat_name
plat_blockportsrR   port_keydefault_portblockri  r  s                     rU   _profile_platform_portsrx  	  s    B##K006BI  ) 1 1  F  	 F,.AAA 	*Q.##)rC	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*,6swwy7I7I4,P,PXcggi(((VX !&B++K88#''+:N:NO 	H 	HCc4(( ), H H%	:j$// H%%i44;;JGGGH	H     E ' '!=d!C,

4  &B&071C1CT&J&JR		'"""PRii5;B"3"3Hl"K"KLL	'c((E$KK:& 	' 	' 	'&E$KKK	'LsH   E A6*E 6A::E =A:>CE E-,E-	HH54H5c                     	 ddl m} m} ddlm}  |d          }n2# t
          $ r% t                              dd           g dg dcY S w xY wd	 |D             }g }d
}|D ]\  }}	  | |          sn# t
          $ r Y w xY w	  ||dz            }	n# t
          $ r d}	Y nw xY wd |	pi                     d          pg D             }
|dk    rt          |
          dk    rd}|t          ||	          d}|
r|
|d<   |                    |           |rd}n.t          |          dk    rd}nt          |          dk    rd}nd}|||dS )uA  Enumerate profiles and the gateways serving them for ``/api/status``.

    Returns ``{"profiles": [...], "gateway_mode": ..., "gateways": [...]}``:

    * ``profiles`` — every profile on the host (default + named), from
      ``profiles_to_serve(True)`` (the cheap enumeration chokepoint — no
      per-profile config reads or skill counts).
    * ``gateways`` — one entry per profile with a LIVE gateway process:
      ``{"profile", "ports", "served_profiles"?}``.  Liveness reuses
      ``_check_gateway_running`` so this agrees with the profiles sidebar.
    * ``gateway_mode`` — ``"multiplex"`` when the default gateway serves
      multiple profiles (gateway.multiplex_profiles), ``"single"`` for one
      live gateway, ``"multiple"`` for independent per-profile gateways,
      ``"none"`` when nothing is running.
    r   )_check_gateway_runningprofiles_to_serve)r-   Tz+profile/gateway topology enumeration failedr  unknown)profilesgateway_modegatewaysc                     g | ]\  }}|S r  r  )r   rR   _homes      rU   r  z5_collect_profile_gateway_topology.<locals>.<listcomp>
  s    333kdET333rW   Fzgateway_state.jsonNc                 ,    g | ]}t          |          S r  r  r   r   s     rU   r  z5_collect_profile_gateway_topology.<locals>.<listcomp>
  s    QQQQ#a&&QQQrW   served_profilesr1  r   )r  rt  	multiplexmultiplesinglenone)hermes_cli.profilesrz  r{  gateway.statusr-   rZ   rP   r  r   r   rx  r   )rz  r{  r-   homesprofile_namesr  r  rR   r  rc  servedr  r  s                rU   !_collect_profile_gateway_topologyr  	  s&    KQQQQQQQQ666666!!$'' K K K

@4
PPP	rJJJJJK 43U333M%'HI  
d	))$//  	 	 	H		))$1E*EFFGG 	 	 	GGG	QQGMr#6#67H#I#I#ORQQQ9VqI,T7;;!
 !
  	.'-E#$ 	X			X!		%tRRRs3    ,A
A&A33
B ?B BB"!B"z/api/statusc           	        K   d }| pd                                 }|r;|                                dk    r#t          |          }|                                 	 t	                      \  }}t                      }|d u}d }|sYt          rRt          j                    }|	                    d t                     d {V \  }	}|	rd}|r|                    d          }d }
i }d }d }d 	 ddlm}  |            }d |                                D             n# t          $ r d Y nw xY wt!                      }|}||r|                    d          r|}|s|t#          |          }|d}|}|r|                    d          }
|                    d	          pi } fd
|                                D             }|                    d          }|                    d          }|s|
dv r|
nd}
i }n
|r||
dv rd}
|r|
|d}
t'                       d {V }t)          |pi                     dd                    }t+          ||
|          }t-          ||
          }t          j                    	                    d t.                     d {V }t1          t3          t4          j        dd                    }g }	 ddlm} d  |            D             }n# t          $ r Y nw xY wd}	 ddlm}  |            }n# t          $ r d}Y nw xY wi dt@          dtB          d|d|dtE                       d|d|
d |d!|d"|d|d#|d$|d%|d&|d|d'|d(|i}t          j                    	                    d tF                     d {V }|d)         |d)<   |d*         |d*<   |sr|$                    tK          tM                                tK          tO                                tK          tQ                                |t          |d+         d,           || |j)        tU          j+                      S S # | |j)        tU          j+                      w w xY w)-Nr   currentTpidr   )load_gateway_configc                     h | ]	}|j         
S r  ry  )r   r  s     rU   	<setcomp>zget_status.<locals>.<setcomp>U
  s'     , , ,#+, , ,rW   gateway_statere  c                 $    i | ]\  }}|v 	||S r  r  )r   r  rr  configured_gateway_platformss      rU   rI  zget_status.<locals>.<dictcomp>p
  s5     % % %"U::: :::rW   exit_reason
updated_at>   r`  startup_failedr`  >   Nr`  runningactive_agents)gateway_runningr  r  )r  r  r   Flist_providersc                     g | ]	}|j         
S r  rR   r  s     rU   r  zget_status.<locals>.<listcomp>
  s    @@@af@@@rW   r|  )get_nous_session_validityr   release_dateconfig_versionlatest_config_versioncan_update_hermesr  gateway_platformsgateway_exit_reasongateway_updated_atgateway_busygateway_drainablerestart_drain_timeoutactive_sessionsauth_providersnous_session_validr}  r~  r  )hermes_homeconfig_pathenv_pathgateway_pidgateway_health_urlr  ),r   r   _config_profile_scope	__enter__r    r)   r#  rm   rI  rs   r1  r   gateway.configr  get_connected_platformsrZ   r-   r+   r  rO  r,   r'   r(   rb   r   r   rc   rk   hermes_cli.dashboard_authr  hermes_cli.authr  r   r   r:  r  r  r  r   r   r   __exit__r  r  ) r  status_scoperequested_profilecurrent_ver
latest_verr  r  remote_health_bodyrM  aliver  r  r  r  r  gateway_configlocal_runtimerc  runtime_pidr  r  r  r  r  r   r  _list_providersr  r  r)  topologyr  s                                   @rU   
get_statusr  &
  s     L B--//  !.4466)CC,->??   K3"6"8"8Z
 -..%T1*. 		@#6 		@+--D.2.B.B+/ / ) ) ) ) ) )%E%  @"&% @"4"8"8"?"?K"$"!8<$	0::::::0022N, ,/=/U/U/W/W, , ,((  	0 	0 	0+/(((	0
 ,--?1?6H6L6L_6]6]?(G  	*=#<8GGK&"&) 	.#KK88M 'K 8 8 >B+7% % % %&7&=&=&?&?% % %!
 #*++m"<"<!(\!:!:" .1>B_1_1_en$&!!  .%7%C !$555$-M  	&}49K9W%M 7 9 9999999 ,W],?,?QR,S,STT*+''
 
 

 5+'
 
 
 '.&>&@&@&P&P0'
 '
 !
 !
 !
 !
 !
 !
 WSYGGHH$&	SSSSSS@@oo.?.?@@@NN 	 	 	D	 '	+AAAAAA!:!:!<!< 	+ 	+ 	+!*	+
{
,
 k
 $Z	

  %O%Q%Q!Q
 
 ]
  !2
 "#6
 !"4
 ]
 L
  !2
 $%:
 
  ]!
" n#
$ !"4%
 
B !133CC3
 
 
 
 
 
 
 
 &j1z!).!9~  	MM"?#4#455"?#4#455//*&9$Z0     #!L!3<>>222 $<#!L!3<>>222 $s{   B
P9 %.D P9 D# P9 "D##FP9 <K P9 
K$!P9 #K$$P9 *K; :P9 ;L
P9 	L

DP9 9 QiU  r   platform_labelc                     | pd|pdfD ]N}t          j        d|          }|s	 t          |                    d                    c S # t          $ r Y Kw xY wdS )zAExtract the Windows NT build number from stdlib platform strings.r   z%(?:^|[^\d])10\.0\.(\d{5,})(?:[^\d]|$)r   N)r_  searchrw  groupr  )r   r  rr  matchs       rU   _windows_build_numberr    s    -R!526  	BEJJ 		u{{1~~&&&&& 	 	 	H	4s   !A
AAsystemreleasec                     | dk    r=|dk    r7t          ||          }|%|t          k    rt          j        dd|d          }d}| |||d	S )
zAReturn host OS fields for display while preserving stdlib detail.Windows10Nz^Windows-10(?=-)z
Windows-11r   )count11)rt   
os_release
os_versionr  )r  _WINDOWS_11_MIN_BUILDr_  r`  )r  r  r   r  rm  s        rU   _display_system_platformr    s}     w$%g~>>*?!?!?V#	  N G "	  rW   z/api/system/statsc                    K   ddl } i t          |                                 |                                 |                                 |                                            |                                 |                                 |                                 |                                 t          t          j                    d}	 ddl}|                                }|j        |j        |j        |j        d|d<   	 |                    t'          t)                                          }|j        |j        |j        |j        d|d<   n# t,          $ r Y nw xY w	 |                    d	
          |d<   t1          |dd          }|rt3           |                      |d<   n# t,          $ r Y nw xY w	 |                                }t7          t9          j                    |z
            |d<   n# t,          $ r Y nw xY w	 |                                }|j        |                                j         t7          |!                                          |"                                d|d<   n# t,          $ r Y nw xY wd|d<   nQ# t,          $ rD d|d<   	 t3          t          j#                              |d<   n# tH          tJ          f$ r Y nw xY wY nw xY w|S )a!  Host + process system stats for the System page.

    OS / Python / host identity from stdlib; CPU / memory / disk / uptime from
    psutil when available, with graceful degradation when it isn't.  Read-only
    and non-sensitive (no env values, no paths beyond the hermes home root).
    r   N)r  r  r   r  )archr,  python_versionpython_implhermes_version	cpu_count)r  	availableusedpercentrj  )r  r  freer  disk皙?rN   cpu_percent
getloadavgload_avguptime_seconds)r  rsscreate_timenum_threadsprocessTpsutilF)&r  r  r  r  r   machinenoder  python_implementationr   rt   r  r  virtual_memoryr  r  r  r  
disk_usager  r   r  rZ   r  r   r  	boot_timerw  rC  Processr  memory_infor  r  r  r  r  r   )	_platformrQ   r  vmdulabootprocs           rU   get_system_statsr  0  s?      !   
"##%%%%''%%''$--//	
 
 
 !!##NN$$#2244 6688%\^^  D 2""$$XGz	
 
X		""3'8'8#9#9::B:	 DLL  	 	 	D		"("4"4c"4"B"BDt44B .#'::Z  	 	 	D		##%%D%(t);%<%<D!"" 	 	 	D			>>##Dx''))-"4#3#3#5#566#//11	 DOO  	 	 	D	X   X	#BMOO44D( 	 	 	D	 Ks   6J AE J 
EJ EJ !AF( 'J (
F52J 4F55J 9:G4 3J 4
H>J  HJ A+I1 0J 1
I>;J =I>>J K#J;:K;KKKKKz/api/curatorc                    K   	 ddl m}  n&# t          $ r}t          dd|           d }~ww xY w	 |                                 }n# t          $ r i }Y nw xY wt          | dd          t          | dd	          t          | d
d           |                    d          t          | dd           t          | dd           t          | dd           dS )Nr   curatorr!  zCurator unavailable: r   
is_enabledT	is_pausedFget_interval_hourslast_run_atget_min_idle_hoursget_stale_after_daysget_archive_after_days)r  pausedinterval_hoursr  min_idle_hoursstale_after_daysarchive_after_days)rV  r   rZ   r5   
load_state
_safe_callr   )r   rN  rk   s      rU   get_curator_statusr    s     S!!!!!!! S S S4QC4Q4QRRRRS""$$    g|T::Wk599$W.BDIIyy//$W.BDII&w0FMM(2JDQQ  s!    
.).A AAc                       e Zd ZU eed<   dS )CuratorPauser  Nr  r  r  r   r  r  rW   rU   r  r             LLLLLrW   r  z/api/curator/pausedc                    K   ddl m} |                    t          | j                             dt          | j                  dS )Nr   r  T)rs  r  )rV  r   
set_pausedr   r  )r0  r   s     rU   set_curator_pausedr    sN      tDK(()))$t{"3"3444rW   z/api/curator/runc                     K   	 t          ddgd          } n&# t          $ r}t          dd|           d}~ww xY wd| j        dd	S )
zDTrigger a curator review now (backgrounded; tail via action status).r   r  curator-runr!  zFailed to run curator: r   NTrs  r  rR   _spawn_hermes_actionrZ   r5   r  r  rN  s     rU   run_curatorr    ss      U#Y$6FF U U U4Sc4S4STTTTUtx???    
:5:z/api/learning/graphc                    K   	 ddl m} t          |           5   |            cddd           S # 1 swxY w Y   dS # t          $ r, t                              d           t          dd          w xY w)zLearning graph payload for the desktop panel.

    Profile-scoped view of learned, non-base skills plus memory chunks, with
    graph links derived from skill relations and memory-skill overlap.
    r   )build_learning_graphNzGET /api/learning/graph failedr!  zFailed to build learning graphr   )agent.learning_graphr   ru  rZ   rP   	exceptionr5   )r  r   s     rU   get_learning_graphr#    s      V======G$$ 	* 	*''))	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* V V V78884TUUUUVs!   = 
0= 4= 4= 6A3c                   4    e Zd ZU eed<   dZee         ed<   dS )LearningNodeRefr  Nr  r  r  rW   rU   r%  r%    s/         GGG!GXc]!!!!!rW   r%  c                   >    e Zd ZU eed<   eed<   dZee         ed<   dS )LearningNodeEditr  r   Nr  r  r  rW   rU   r'  r'    s8         GGGLLL!GXc]!!!!!rW   r'  z/api/learning/noder  c                    K   ddl m} t          |          5   ||           }ddd           n# 1 swxY w Y   |                    d          s%t	          d|                    dd                    |S )	zXCurrent content of a journey node (skill SKILL.md or memory chunk), for an edit prefill.r   )node_detailNrs  r   r   	not foundr   )agent.learning_mutationsr)  ru  r   r5   )r  r  r)  ress       rU   get_learning_noder-    s       544444		 	   k"oo              774== UCGGI{4S4STTTTJ   044c                   K   ddl m} t          | j                  5   || j                  }ddd           n# 1 swxY w Y   |                    d          s%t          d|                    dd                    |S )	uM   Delete a journey node — skills are archived (restorable), memories removed.r   )delete_nodeNrs  r   r   zdelete failedr   )r+  r0  ru  r  r  r   r5   )r0  r0  r,  s      rU   delete_learning_noder1    s       544444		%	% # #k$'""# # # # # # # # # # # # # # #774== YCGGI4W4WXXXXJs   :>>c                   K   ddl m} t          | j                  5   || j        | j                  }ddd           n# 1 swxY w Y   |                    d          s%t          d|                    dd                    |S )	z<Rewrite a journey node's content (SKILL.md or memory chunk).r   )	edit_nodeNrs  r   r   zedit failedr   )r+  r3  ru  r  r  r   r   r5   )r0  r3  r,  s      rU   update_learning_noder4    s       322222		%	% / /i../ / / / / / / / / / / / / / /774== WCGGI}4U4UVVVVJ   A  AAfn_namec                     	 t          | |d           }t          |          r
 |            n|S # t          $ r |cY S w xY wr  )r   callablerZ   )modr6  r1  r  s       rU   r  r    sW    S'4((||0rrttt0   s   +. ==z/api/portalc                    K   t                      pi } i }	 ddlm}  |            pi }n# t          $ r i }Y nw xY wg }	 ddlm}  ||           }||                                D ]}}t          |dd          rd}n@t          |dd          rt          |dd           r|j        }nt          |dd          rd}nd	}|	                    t          |d
d          |d           ~n*# t          $ r t                              d           Y nw xY wt          |                     d          t                    r|                     d          ni }t          |                    d                    |                    d          |                    d          t!          |pi                     d          pd          d|dS )Nr   )get_nous_auth_status)get_nous_subscription_featuresmanaged_by_nousFzvia Nous Portalrm  current_providerznot configuredlabelr   )r?  rk   zportal features failedr  	logged_inportal_base_urlinference_base_urlrT   z3https://portal.nousresearch.com/manage-subscription)r@  
portal_urlinference_urlrT   subscription_urlfeatures)r   r  r;  rZ   hermes_cli.nous_subscriptionr<  r  r   r>  r   rP   r"  rv  r   r  r   r  )	r  r   r;  rF  r<  featsfeatrk   r  s	            rU   get_portal_statusrJ    s$     
--
2CD888888##%%+    H1OOOOOO..s33 	W 	W4!2E:: --EET8U33 -FXZ^8_8_ - 1EET8U33 -$EE,E'$*D*Du U UVVVV 1 1 1/000001 %/swww/?/?$F$FN   BI$((;//00hh011"677b--j99?R@@Q  s   ) 88B%C$ $$D
Dz/api/ops/prompt-sizec                     K   	 t          dgd          } n&# t          $ r}t          dd|           d }~ww xY wd| j        ddS )Nprompt-sizer!  Failed: r   Tr  r  r  s     rU   run_prompt_sizerN  2  sp      F#]O]CC F F F4Ds4D4DEEEEFtx???    
949z/api/ops/dumpc                     K   	 t          dgd          } n&# t          $ r}t          dd|           d }~ww xY wd| j        ddS )Ndumpr!  rM  r   Tr  r  r  s     rU   run_dumprR  ;  sp      F#VHf55 F F F4Ds4D4DEEEEFtx888rO  z/api/ops/config-migratec                     K   	 t          ddgd          } n&# t          $ r}t          dd|           d }~ww xY wd| j        ddS )	Nr{  migrateconfig-migrater!  rM  r   Tr  r  r  s     rU   run_config_migraterV  D  su      F#Xy$9;KLL F F F4Ds4D4DEEEEFtx1ABBBr  c                   ,    e Zd ZU dZeed<   dZeed<   dS )DebugShareRequestTredactr"  linesN)r  r  r  rY  r   r  rZ  rw  r  rW   rU   rX  rX  M  s4          FDE3rW   rX  z/api/ops/debug-sharec                   K   ddl m} | pt                      }	 t          j        |t          dt          t          |j                  d                    t          |j
                             d{V }na# t          $ r}t          dd| 	          d}~wt          $ r3}t                              d
           t          dd| 	          d}~ww xY wd|j        |j        |j        |j        dS )a  Upload a redacted debug report + full logs and return the paste URLs.

    Unlike the other diagnostics actions (doctor, dump, prompt-size) this is
    *synchronous*: the whole point of ``debug share`` is the set of shareable
    URLs it produces, so we run the upload in a worker thread and return the
    structured ``{urls, failures, redacted, ...}`` payload directly. The
    dashboard renders those as real, copyable links instead of scraping a log
    tail. Pastes auto-delete after 6 hours (handled inside the share core).
    r   )build_debug_sharer   i  )	log_linesrY  N  zUpload failed: r   zdebug share failedr!  rM  T)rs  urlsfailuresredactedauto_delete_seconds)hermes_cli.debugr\  rX  rm   	to_threadrN  r  rw  rZ  r   rY  r  r5   rZ   rP   r"  r_  r`  ra  rb  )r0  r\  r.  r  rN  s        rU   run_debug_share_endpointre  V  sI      322222

%#%%CF(!SSY6677
##
 
 
 
 
 
 
 
 

  M M M4Kc4K4KLLLL F F F+,,,4Ds4D4DEEEEF
 OO%9  s$   AA8 8
CBC#.CClogs_ACTION_LOG_DIRi   i    i   gateway-restartzgateway-restart.loggateway-startzgateway-start.loggateway-stopzgateway-stop.loghermes-updatezhermes-update.logdoctorzaction-doctor.logsecurity-auditzaction-security-audit.logbackupzaction-backup.logimportzaction-import.logcheckpoints-prunezaction-checkpoints-prune.logzskills-installzaction-skills-install.logzskills-uninstallzaction-skills-uninstall.logskills-updatezaction-skills-update.logr  zaction-curator-run.logrL  zaction-prompt-size.logrQ  zaction-dump.logrU  zaction-config-migrate.logtools-post-setupzaction-tools-post-setup.log_ACTION_LOG_FILES_ACTION_PROCS_ACTION_COMMANDS_ACTION_RESULTSr   r   	exit_codec           	      n   t           |          }t                              dd           t          |z  }t          |dd          5 }|                    d|  dt          j        d           d	                                           |                    |                    d
d                     |                    d          s|                    d           ddd           n# 1 swxY w Y   t          
                    | d           t          
                    | d           |ddt          | <   dS )zBRecord a non-spawned action result and write it to the action log.Tr  abr   	buffering
=== z completed %Y-%m-%d %H:%M:%S ===
r  r  r  
   
Nrw  r  )rs  rg  r"  r  r  rC  rw  r   r%  rt  r  ru  rv  )rR   r   rw  log_file_namelog_pathlog_files         rU   _record_completed_actionr    sk   %d+M$666.H	h	*	*	* "hPTPPdm4G&H&HPPPWWYY	
 	
 	
 	w~~gi~@@AAA%% 	"NN5!!!" " " " " " " " " " " " " " " dD!!!t$$$*3DAAODs   BC''C+.C+c                  `   t           j        dk    rt           j        S t           j        } |                                                     d          r^t
          j                            t
          j                            |           d          }t
          j        	                    |          r|S | S )z=Prefer pythonw.exe for detached dashboard actions on Windows.r  z
python.exezpythonw.exe)
r  r  
executabler   r%  rt   r   joindirnameisfile)exepythonws     rU   _dashboard_spawn_executabler    s    
|w~
.C
yy{{L)) ',,rws33]CC7>>'"" 	NJrW   
subcommandc                    t           |         }t                              dd           t          |z  }t          |dd          }|                    d| dt          j        d           d	                                           t                      d
dg| }i t          j
        ddi}|                    dd           t          t                    t          j        |t          j        |d}t"          j        dk    rt'                      |d<   nd|d<   t          j        |fi |}|                                 t,                              |d           t/          |           t0          |<   |t2          |<   |S )zSpawn ``hermes <subcommand>`` detached and record the Popen handle.

    Uses the running interpreter's ``hermes_cli.main`` module so the action
    inherits the same venv/PYTHONPATH the web server is using.
    Tr  ry  r   rz  r|  z	 started r}  r~  -mzhermes_cli.mainHERMES_NONINTERACTIVErf   _HERMES_GATEWAYN)r  stdinr  stderrr  r  r  start_new_session)rs  rg  r"  r  r  rC  rw  r   r  rt   r  r  r  r9  r  DEVNULLSTDOUTr  r  r	   Popenr   rv  tupleru  rt  )	r  rR   r  r  r  cmd
action_envpopen_kwargsr  s	            rU   r  r    s    &d+M$666.HHda000HNNJJJ.A B BJJJQQSS   '(($0A
OJ
OC >BJ= 7==JNN$d+++ <  ##$ $L |w(<(>(>_%%,0()C00<00D NNd###":..TM$KrW   nc                    |dk    s|                                  sg S 	 |                                 j        }n# t          $ r g cY S w xY w|dk    rg S t	          d|t
          z
            }|}t          }d}g }d}	 |                     d          5 }	||k    r||k    rt          |||z
            }
||
z  }|		                    |           |	
                    |
          }|                    |           ||                    d          z  }t          |dz  t                    }||k    r||k    |dk    r1|		                    |dz
             |	
                    d          dk    }ddd           n# 1 swxY w Y   n# t          $ r g cY S w xY wd                    t          |                                        d	d
                                          }|r|r
|dd         }|| d         S )zBReturn the last ``n`` lines of ``path`` without loading huge logs.r   Fr  r  r  r   NrW   r  r  r  )rA  r  r  r  rN  _ACTION_LOG_TAIL_MAX_BYTES$_ACTION_LOG_TAIL_INITIAL_CHUNK_BYTESr  r  seekr,  r   r   _ACTION_LOG_TAIL_MAX_CHUNK_BYTESr  reversedr  
splitlines)r   r  rL  
min_offsetoffset
chunk_sizenewline_countchunksdrop_partial_first_liner  	read_sizer  rZ  s                rU   _tail_linesr    s|   AvvT[[]]v	yy{{"   			qyy	Q99::JF5JMF#YYt__ 	B:%%-1*<*<
FZ,?@@	)#F###I..e$$$U!3!33 N4 
 :%%-1*<*< zzFQJ'''*0++a..E*A'	B 	B 	B 	B 	B 	B 	B 	B 	B 	B 	B 	B 	B 	B 	B    			 	&!!""			*	*	 

  5 abb	!:sF   8 AA:E> CE2&E> 2E66E> 9E6:E> >FFverbc                 *    t          |           d|gz   S )Nrh  )_profile_cli_argsr  r  s     rU   _gateway_subcommandr  #  s    W%%D(999rW   c                 N    d                     dgt          | |                    S )Nr  hermes)r  r  r  s     rU   _gateway_display_commandr  '  s'    88XC 3GT B BCDDDrW   z\d+:[A-Za-z0-9_-]{30,}z\d+z[UW][A-Z0-9]{2,}platform_idr  c                 j   |sdS | dk    r|dk    r+t                               |          st          dd          |dk    rId |                    d	          D             }t	          d
 |D                       rt          dd          dS | dk    rdS |dk    r&|                    d          st          dd          |dk    r&|                    d          st          dd          |dk    r>d |                    d	          D             }d |D             }|rt          dd          dS dS )z@Reject platform credentials that are clearly in the wrong field.Nrd  TELEGRAM_BOT_TOKENr   uW   Telegram bot token must be the complete token from @BotFather, such as 123456789:ABC…r   TELEGRAM_ALLOWED_USERSc                 ^    g | ]*}|                                 |                                 +S r  r   r  s     rU   r  z1_validate_messaging_env_value.<locals>.<listcomp>=  s-    RRRTZZ\\R

RRRrW   rQ  c              3   L   K   | ]}t                               |           V   d S r  )_TELEGRAM_USER_ID_RE	fullmatchr   user_ids     rU   r   z0_validate_messaging_env_value.<locals>.<genexpr>>  s4      WW7+55g>>>WWWWWWrW   z@Telegram allowed users must be comma-separated numeric user IDs.slackSLACK_BOT_TOKENzxoxb-zTSlack Bot Token must start with xoxb-. Paste the bot token from OAuth & Permissions.SLACK_APP_TOKENzxapp-zkSlack App Token must start with xapp-. Paste the app-level token from Basic Information > App-Level Tokens.SLACK_ALLOWED_USERSc                 ^    g | ]*}|                                 |                                 +S r  r  r  s     rU   r  z1_validate_messaging_env_value.<locals>.<listcomp>V  s-    NNNTNDJJLLNNNrW   c                 R    g | ]$}|d k    t                               |          "|%S )r   )_SLACK_MEMBER_ID_REr  r  s     rU   r  z1_validate_messaging_env_value.<locals>.<listcomp>W  s<     
 
 
#~~&9&C&CG&L&L~ ~~rW   zKSlack allowed user IDs must be comma-separated member IDs like U01ABC2DEF3.)_TELEGRAM_BOT_TOKEN_REr  r5   r   r  r   )r  r  rr  user_idsinvalids        rU   _validate_messaging_env_valuer  1  s    j  &&&/E/O/OPU/V/V&p    ***RRS1A1ARRRHWWhWWWWW # #]    	g
(8(8(A(Ai
 
 
 	
 (8(8(A(A A
 
 
 	
 ### ONU[[-=-=NNN
 
#
 
 

  	d    $#	 	rW   c                 *   t          | d          }t                              d          }|V|                                Bt                              d          }||t          |          k    r|dfS t          d          t          |d          dfS )a  Spawn ``hermes gateway restart``, reusing an in-flight restart.

    Multiple dashboard paths can request a restart in quick succession
    (restart button double-click, or a stale cached frontend firing its own
    restart after the server already auto-restarted post-onboarding). Two
    concurrent ``hermes gateway restart`` children race each other on the
    manual kill-and-start path, so reuse the live one instead.

    Returns ``(proc, reused)``.
    restartrh  NTz7gateway restart already in progress for another profileF)r  rt  r   pollru  r  r  r  )r  r  existingexisting_commands       rU   _spawn_gateway_restartr  c  s     %Wi88J  !233H 7+//0ABB#'75;L;L'L'LT>!TUUU
,=>>EErW   c                 
   	 t          |           \  }}nC# t          $ r6}t                              d           dt	          |          dcY d}~S d}~ww xY w|r t                              d|j                   dd|j        dS )	z@Best-effort gateway restart after enabling the webhook platform.z6Failed to auto-restart gateway after enabling webhooksFrestart_startedrestart_errorNz:Webhook enable: reusing in-flight gateway restart (pid %s)Trh  r  restart_actionrestart_pidr  rZ   rP   r"  r  rQ   r  r  r  reusedrN  s       rU   %_restart_gateway_after_webhook_enabler  x  s    
-g66ff 
 
 
OPPP$ XX
 
 	
 	
 	
 	
 	
 	

  
		HH	
 	
 	

  +x      
A+A
AAz/api/gateway/restartc                    K   	 t          |           \  }}nJ# t          $ r  t          $ r3}t                              d           t          dd|           d}~ww xY wd|j        ddS )	z8Kick off a ``hermes gateway restart`` in the background.zFailed to spawn gateway restartr!  zFailed to restart gateway: r   NTrh  r  )r  r5   rZ   rP   r"  r  )r  r  _reusedrN  s       rU   restart_gatewayr    s      Y.w77gg    Y Y Y89994WRU4W4WXXXXY x!  s    A.AAz/api/gateway/drainc           	        K   ddl m}m}m} 	 |                                  d{V }n# t
          $ r i }Y nw xY wt          |pi                     dd                                                    	                                }t          | j        dd          }t          |dd          pd}|d	k    r, |            }t                              d
||           dd	|dS |dk    rt          dd|d           |t          |          t          |pi                     dd                              }	t                              d||	d                    dd|	d          |            |	d         dS )uK  Begin or cancel an external (NAS-driven) gateway drain.

    Authenticated by the non-interactive token-auth seam: the
    ``dashboard_auth/drain`` plugin registers this exact path as a token route
    and verifies the ``Authorization`` bearer secret. If that plugin isn't
    active (no ``HERMES_DASHBOARD_DRAIN_SECRET``), the route is NOT a token
    route, so on a gated bind the cookie gate handles it (a browser session can
    still drive it from the dashboard) and on a loopback bind the legacy
    session-token gate applies — either way it is never unauthenticated on a
    network-exposed bind.

    Body: ``{"action": "drain"}`` (begin) or ``{"action": "cancel"}`` (cancel).
    Begin writes the ``.drain_request.json`` marker the gateway's
    ``_drain_control_watcher`` observes (flip to ``draining`` + refuse new
    turns); cancel removes it (revert to ``running`` + re-accept). Idempotent
    on both sides. This endpoint only writes/removes the marker — the gateway
    process owns the actual state transition (there is no HTTP control channel
    into the running gateway; the marker IS the channel, decisions.md Q-B).

    The force-override (D6: "unless a user commands it") is NOT here — an
    immediate, drain-skipping action maps onto the existing
    ``POST /api/gateway/restart`` force path, which supersedes a drain.
    r   )clear_drain_requestdrain_requestedwrite_drain_requestNactiondraintoken_principal	principalr^  r|   z1Gateway drain CANCEL requested by %s (existed=%s)T)rs  r  was_drainingr   zUnknown drain action z; expected 'drain' or 'cancel'r   suppress_notificationF)r  r  z>Gateway drain BEGIN requested by %s (suppress_notification=%s)requested_at)rs  r  r  drainingr  )gateway.drain_controlr  r  r  r*  rZ   r  r   r   r   r   rk   rP   rQ   r5   r   )
r   r  r  r  r0  r  principal_objr  existedrj  s
             rU   gateway_drainr    s     2         \\^^######   $*"!!(G4455;;==CCEEF GM+<dCCM{D99H[I%%''		EyRYZZZhHHHS6SSS
 
 
 	

 "!i.."DJB#3#34KU#S#STT  G 	IIH'(   / $O%%!()@!A  s   ) 88z/api/hermes/updatec                    K   t                      rd} t          d| d           dddd| dd	S t          t                    }|d
k    r6t	                      } t          d| d           dddd| t          |          d	S 	 t          dgd          }n@# t          $ r3}t          	                    d           t          dd|           d}~ww xY wd|j        ddS )z-Kick off ``hermes update`` in the background.z}Hermes updates are managed outside this dashboard in containerized environments. The built-in local updater is disabled here.rk  r   )rw  FN#dashboard_update_managed_externallymanaged outside dashboard)rs  r  rR   r  r   update_commandr"  docker_update_unsupportedr  zFailed to spawn hermes updater!  zFailed to start update: r   Tr  )r:  r  r!   r9  r"   r#   r  rZ   rP   r"  r5   r  )r   install_methodr  rN  s       rU   update_hermesr    sB      233 
 	
 	!'QGGGG#:9
 
 	
 +<88N!!.00 'QGGGG#0CNSS
 
 	
V#XJ@@ V V V67774Ts4T4TUUUUV x  s   ?B 
C.C		C   c                    	 t          j        ddt          t                    ddddt	          |            gddd	          }|j        d
k    rg S g }|j                                        D ]q}|                                s|	                    d          g dz   dd         }|\  }}}}|
                    |dd         ||t	          |pd
          d           r|S # t          $ r g cY S w xY w)u[  Commits the local checkout is behind ``origin/main`` by, newest first.

    Logs the SAME range the behind-count uses (``HEAD..origin/main`` — see
    ``banner._check_via_local_git``), NOT the branch's ``@{upstream}``. On a
    feature-branch checkout ``@{upstream}`` is the branch's own tip (zero
    commits), which would leave the changelog empty even though the count is
    non-zero. Pinning to ``origin/main`` keeps count and changelog consistent.

    Best-effort: returns [] if not a git checkout, origin/main is unreachable,
    or git is unavailable. Never raises into the request path.
    r  r  logz--format=%H%x1f%s%x1f%an%x1f%ctzHEAD..origin/mainz-nTr   )r  r  r!  r   )r   r   r   0Nr      )r.  summaryauthorat)r  r  r  r9  rw  r  r  r  r   r   r   rZ   )	r  r  rowsr]  r   r.  r  r  r  s	            rU   _recent_upstream_commitsr    sA   !nL!!1#SVV  
 
 
 >QI%'J))++ 	 	D::<< ZZ''*;*;*;;RaR@E',$C&"KKrr7&$bgA,,	        			s   AC BC C.-C.z/api/hermes/update/checkr  c                 p  K   t                      rdt          ddddddS t          t                    }t	          |          }|t          dd|dv |dd}|dk    rt                      |d	<   |S 	 d
dlm} | r5	 t                      dz  	                                 n# t          $ r Y nw xY wt          j        |           d{V }n,# t          $ r t                              d           d}Y nw xY w||d<   |d|d	<   n7|d
k    rd|d	<   n+d|d<   |dv r"t          j        t                      d{V |d<   |S )u}  Report whether a Hermes update is available, without applying it.

    Powers the dashboard's "check before you update" flow: the System page
    shows the commit-behind count and asks the user to confirm before
    ``POST /api/hermes/update`` actually runs ``hermes update``.

    Returns:
        install_method: 'git' | 'pip' | 'docker' | 'nixos' | 'homebrew' | ...
        current_version: installed Hermes version string
        behind: commits behind upstream (>=1), 0 if up to date,
                -1 if behind by an unknown count (nix/pypi), or null if the
                check could not run (offline, no remote, etc.)
        update_available: convenience bool (behind is non-zero and not null)
        can_apply: True when the dashboard's update button can apply it
                   in place (git/pip); False for docker/nix/homebrew where the
                   user must update out-of-band
        update_command: the recommended command for this install method
        message: human-readable guidance for non-applyable methods
        commits: for git/pip installs that are behind, a list of the commits
                 the local checkout is behind upstream by — each
                 {sha, summary, author, at}. Absent/empty otherwise. The
                 desktop's remote update overlay renders this as "what's
                 changed". Additive: existing consumers ignore it.
    zmanaged-runtimeNFr  zPHermes updates are managed outside this dashboard in containerized environments.)r  current_versionbehindupdate_available	can_applyr  r   )r  pipr"  r   r   )check_for_updatesz.update_checkzUpdate check failedr  u5   Couldn't reach the update source — try again later.zYou're on the latest version.Tr  commits)r:  r   r!   r9  r#   r"   hermes_cli.bannerr  r   r  r  rm   rd  rZ   rP   r"  r  )r  r  r  rj  r  r  s         rU   check_hermes_updater  D  s     4 233 
/* %9.
 
 	
 +<88N:>JJN )&!#~5( G !!9;;	
777777 	 ""_4<<>>>>    ():;;;;;;;;   ,--- GH~T		1<	&*"# ^++'.'89Q'R'R!R!R!R!R!R!RGINs6   .C 7#B C 
B(%C 'B((C &C/.C/z/api/audio/transcribec                   K   | j         pd                                }|                    d          rd|vrt          dd          |                    dd          \  }}d|vrt          dd	          | j        p%|d
d                              dd          d         pd                                }|                    dd          d                                         }|                    d          s|dk    st          dd          	 t          j        |d          }n+# t          j
        t          f$ r t          dd          w xY w|st          dd          t          |          t          k    rt          dd          d}	 t          |          }t          j        d|d          5 }	|	                    |           |	j        }d d d            n# 1 swxY w Y   ddlm}
 t+          j                    }|                    d |
|           d {V }nJ# t          $ r  t0          $ r3}t2                              d           t          dd|           d }~ww xY w	 |r&	 t7          j        |           n># t:          $ r Y n2w xY wn-# |r&	 t7          j        |           w # t:          $ r Y w w xY ww xY w|                    d          s&t          d|                    d          pd           dt?          |                    d!          pd                                          |                    d"          d#S )$Nr   r  rQ  r   zInvalid audio payloadr   r   rR  z$Audio payload must be base64 encodedr   r  r   r  zaudio/r  z"Payload must be an audio recordingTrS  z!Audio payload is not valid base64zAudio recording is emptyr
  zAudio recording is too largezhermes-desktop-voice-F)r|  r  delete)transcribe_audioz"Desktop voice transcription failedr!  zTranscription failed: successr  zTranscription failed
transcriptrT   )rs  r  rT   ) r  r   r   r5   r   r  r   r  rV  rW  rX  r  r   _MAX_TRANSCRIPTION_UPLOAD_BYTESr  r  NamedTemporaryFiler  rR   tools.transcription_toolsr  rm   rI  rs   rZ   rP   r"  rt   r  r  r   r  )rj  r  rZ  r  r  normalized_mime_typeaudio_bytes	temp_pathr  r  r  rM  r  rN  s                 rU   transcribe_audio_uploadr    sR      &B--//Hw'' M3h+>+>4KLLLLnnS!,,OFG$J
 
 
 	

 	HVABBZ--c155a8HLegg  %??32215;;==''11
<//$H
 
 
 	
Y&w>>>NJ' Y Y Y4WXXXXY  P4NOOOO
;9994RSSSSI*955(*
 
 
 	! IIk"""I	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	?>>>>>'))++D2BINNNNNNNN    T T T;<<<4RS4R4RSSSST 	  		)$$$$   	9 		)$$$$   	 ::i   
::g&&@*@
 
 
 	
 &**\228b99??AAJJz**  s   D' '(E&H" 7G H"  G$$H" 'G$(9H" !J "I)6.I$$I))J 0J 
JJK J/.K /
J<9K ;J<<K c                       e Zd ZU eed<   dS )TTSSpeakRequestr  Nr  r  rW   rU   r  r    r  rW   r  rm  c                    t          |                     d          p|                     d          pd                                          }t          |                     d          pd                                          }|r| d| dn|S )NrR   voice_idVoicer  r    ()r  r   r   )rm  rR   r  s      rU   _elevenlabs_voice_labelr     s    uyy  DEIIj$9$9DWEEKKMMD599Z((.B//5577H%-7d!!h!!!!47rW   _voice_list_last_error	signaturec                 0    | da dS | t           k    rdS | a dS )zReturn True if ``signature`` is new and should be logged now.

    Passing ``None`` clears the latch (call on success). Idempotent per
    signature: the same error logs once until it changes.
    NFT)r!  )r"  s    rU   _voice_list_error_logged_oncer$    s1     !%u***u&4rW   z/api/audio/elevenlabs/voicesc            	      V  K   t                                          d          p t          j                            d          pd                                } | sdg dS t
          j                            dd| d          	 t          j	                    }d	t          t          t          f         ffd
}|                    d|           d{V }n# t
          j        j        $ r}|j        dv r=t#          d|j                   rt$                              d|           dg ddcY d}~S t#          d|j                   rt$                              d|           t+          dd          d}~wt,          $ rM}t#          t          |                    rt$                              d|           t+          dd          d}~ww xY wt#          d           g }|                    d          pg D ]}t/          |t0                    st          |                    d          pd                                          }|sQ|                    |t          |                    d          p|          t5          |          d           |                    d            d|dS )zReturn ElevenLabs voices when an API key is configured.

    The desktop UI uses this for the ``tts.elevenlabs.voice_id`` dropdown.
    Only non-secret voice metadata is returned; the API key stays server-side.
    ELEVENLABS_API_KEYr   F)r  voicesz#https://api.elevenlabs.io/v1/voicesapplication/json)Acceptz
xi-api-keyr   rJ   c                      t           j                            d          5 } t          j        |                                                     d                    cd d d            S # 1 swxY w Y   d S )Nr  r   r  )r&  r   r'  r*  r+  r,  r  )responser   s    rU   _fetchz%get_elevenlabs_voices.<locals>._fetch  s    '''<< Cz(--//"8"8"A"ABBC C C C C C C C C C C C C C C C C Cs   9A))A-0A-Nr   r  zhttp-u>   ElevenLabs voices unavailable: %s — check ELEVENLABS_API_KEYunauthorized)r  r'  r  z ElevenLabs voice list failed: %sr^  z Could not load ElevenLabs voicesr   r'  r  rR   )r  rR   r?  c                 n    t          |                     d          pd                                          S )Nr?  r   )r  r   r   r  s    rU   r  z'get_elevenlabs_voices.<locals>.<lambda>I  s+    TXXg%6%6%<"!=!=!C!C!E!E rW   r  T)r   r   rt   r  r   r&  r   r6   rm   rI  r   r  r   rs   r  	HTTPErrorcoder$  rP   rQ   warningr5   rZ   rv  r  r   r   r  )	r  rM  r-  rj  rN  r'  rm  r  r   s	           @rU   get_elevenlabs_voicesr4  	  s       zz~~233arz~~FZ7[7[a_ahhjjG 2"b111n$$-(!
 
 %  GX'))	CS#X 	C 	C 	C 	C 	C 	C ,,T6::::::::<! X X X 8z!!,-?SX-?-?@@ 		TVY   "'"~NNNNNNNN();););<< 	BLL;SAAA4VWWWW X X X(S22 	BLL;SAAA4VWWWWX "$'''FX&&,"  %&& 	uyy,,23399;; 	 		&))5X66,U33
 
 	 	 	 	 KKEEKFFF000s4   AC G
'A E0'G
-AE00G
=AGG
z/api/audio/speakc                   K   | j         pd                                }|st          dd          	 ddlm} t          j                    }|                    d||           d{V }n@# t          $ r3}t          
                    d           t          d	d
|           d}~ww xY w	 t          |t                    rt          j        |          n|}n# t          $ r t          d	d          w xY w|                    d          s&t          d|                    d          pd          |                    d          }|rt           j                            |          st          d	d          t           j                            |          d                                         }dddddd                    |d          }		 t+          |d          5 }
|
                                }ddd           n# 1 swxY w Y   n&# t.          $ r}t          d	d|           d}~ww xY w	 t!          j        |           n:# t.          $ r Y n.w xY w# 	 t!          j        |           w # t.          $ r Y w w xY wxY wt3          j        |                              d          }dd|	 d| |	|                    d          dS )aD  Synthesize speech and return audio as base64 data URL.

    Used by the desktop voice-conversation mode to play back assistant
    responses without exposing the on-disk file path. Reuses the
    existing TTS provider chain (Edge / OpenAI / ElevenLabs / etc.)
    configured in ``~/.hermes/config.yaml`` under ``tts.``.
    r   r   zText is requiredr   r   )text_to_speech_toolNzDesktop voice TTS failedr!  zSpeech synthesis failed: zInvalid TTS responser  r  zSpeech synthesis failed	file_pathzAudio file missingr   r  r  r  r  )r  r  r  r  r  r  zCould not read audio: r  Tr  r  rT   )rs  r  r  rT   )r  r   r5   tools.tts_toolr6  rm   rI  rs   rZ   rP   r"  rv  r  r*  r+  r   rt   r   r  splitextr   r  r,  r  r  r  r  r  )rj  r  r6  rM  result_jsonrN  r  r7  rh  r  fhr  r  s                rU   
speak_textr<  M  s      LB%%''D H4FGGGGW666666')) 007JDQQQQQQQQ W W W12224UPS4U4UVVVVWL,6{C,H,HYK(((k L L L4JKKKKL ::i   
::g&&C*C
 
 
 	

 

;''I JBGNN955 J4HIIII
'

9
%
%a
(
.
.
0
0C  
c#| 	)T"" 	$b''))K	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ T T T4RS4R4RSSSST	Ii     	 	 	D		Ii     	 	 	D	 {++227;;G8I88w88JJz**	  s   6A) )
B&3.B!!B&*+C C2H G<0H <H  H H H I 
H+H&&H++I /I 
III;I+*I;+
I85I;7I88I;z/api/actions/{name}/statusr"  rZ  c           	        K   t                               |           }|t          dd|            t          |z  }t	          |t          t          |d          d                    }t                              |           }|Ot                              |           }d}|r|                    d          nd}|r|                    d	          nd}	n|	                                }|du }|j
        }	|k	 |                    d
           n# t          $ r Y nw xY w||	dt          | <   t                              | d           t                              | d           | |||	|dS )zCTail an action log and report whether the process is still running.Nr   zUnknown action: r   r     Frw  r  r   r  )rR   r  rw  r  rZ  )rs  r   r5   rg  r  r  rN  rt  rv  r  r  waitrZ   r  ru  )
rR   rZ  r  r  tailr  r  r  rw  r  s
             rU   get_action_statusrA    s      &))$//M4Mt4M4MNNNN.HxS]]D!9!9::DT""D| $$T**/5?FJJ{+++4	#)3fjjtIIKK	t#h 		!	$$$$   2;C$H$HOD!dD)))  t,,,   s   6D 
DD)system_promptmodel_configrF  c                 P    | D ]"}t           D ]}|                    |d            #| S r  )_SESSION_LIST_HEAVY_FIELDSr  )rF  r>  r  s      rU   _strip_session_list_rowsrF    sA      - 	 	CEE#t	OrW   z/api/sessionsexcludecreatedr7  r  min_messagesarchivedorderr   exclude_sources
cwd_prefixr:  c
                    |dvrt          dd          |dvrt          dd          d}
|	rt          |	          \  }
}	 t          |	          }	 t          d|          }|d	k    }|d
k    }d |pd                    d          D             }|                    |pd|pd|pd| |||||dk    | 
  
        }|                    |pd|pd|pd|||d          }t          j                    }|D ]}|                    d          du o0||                    d|                    dd                    z
  dk     |d<   |
r|
|d<   |
dk    |d<   t          |                    d                    |d<   |st          |           ||| |d|                                 S # |                                 w xY w# t           $ r  t          $ r, t                              d           t          dd          w xY w)a  List sessions.

    ``archived`` controls how soft-archived sessions are treated:
    ``exclude`` (default) hides them, ``only`` returns just the archived ones
    (used by the desktop "Archived sessions" settings panel), and ``include``
    returns both.

    ``order`` controls pagination order: ``created`` (default, by original
    start time) or ``recent`` (by latest activity across the compression
    chain). ``recent`` keeps a long-running conversation on the first page
    after it auto-compresses into a fresh continuation id.

    Rows omit ``system_prompt``/``model_config`` (the payload-dominating
    fields no list UI reads) unless ``full=1`` is passed.
    rG  onlyincluder   /archived must be one of: exclude, only, includer   rH  recent%order must be one of: created, recentNr   rP  rQ  c                 :    g | ]}|                                 |S r  r  r   r>  s     rU   r  z get_sessions.<locals>.<listcomp>  s%    WWW!QWWYYWAWWWrW   r   rQ  rT  )
r   rL  rM  r7  r  min_message_countinclude_archivedarchived_onlyorder_by_last_activer8  T)r   rM  rL  rX  rY  rZ  exclude_childrenr:  r;  r<  r=  	is_activer  r1  is_default_profilerJ  )rF  r  r7  r  zGET /api/sessions failedr!  Internal server error)r5   _cron_profile_home_open_session_db_for_profilerN  r   rB  session_countrC  r   r   rF  r   rZ   rP   r"  )r7  r  rI  rJ  rK  r   rL  rM  r:  r  profile_namer  rE  rX  rZ  rY  exclude_listrF  r  r?  r>  s                        rU   get_sessionsre    s   8 555D
 
 
 	
 ))):
 
 
 	
 #'L 6,W55a7M)'220	 #A| 4 4$.M'94
 XW(=2'D'DS'I'IWWWL,,~ , 4&.$"3!1+%*h%6 "&X -  H $$~&.$ , 4"3!1+!% %  E )++C 	8 	8EE*%%- Squu]AEE,4J4JKKKsR +   H#/AiL.:i.GA*+ $QUU:%6%6 7 7* 3(222 (55TZ[[HHJJJJBHHJJJJ    M M M12224KLLLLMs&   F8 D8F 
F8 F55F8 8A G8z/api/profiles/sessionsrT  allc	                   ! |dvrt          dd          |dvrt          dd          ddlm}	 dd	lm}
 g }|r0|d
k    r*t          |          \  }}|                    ||f           nz	 |
                                }d |D             }n,# t          $ r t          
                    d           g }Y nw xY w|s*|                    d|
                    d          f           t          d|          }|dk    }|dk    }|pd}d |pd                    d          D             }t          t          | |z   |           d          }g }d}i }g }t          j                    }|D ]\  }}t!          |          dz  }|                                s-	  |	|d          }n<# t          $ r/}|                    |t%          |          d           Y d}~pd}~ww xY w	 |                    ||pd|d||||dk    | 	  	        }|                    ||pd|||d          }||z  }|||<   |D ]}||d<   |dk    |d<   |                    d          du o0||                    d|                    d d                    z
  d!k     |d"<   t-          |                    d#                    |d#<   |                    |           n<# t          $ r/}|                    |t%          |          d           Y d}~nd}~ww xY w|                                 # |                                 w xY w|dk    rdnd !|                    !fd$d%           |||| z            } |st3          |            | ||| ||d&S )'u  Unified, read-only session list aggregated across ALL profiles.

    Intentionally process-light: this opens each profile's ``state.db`` directly
    from disk — it does NOT spawn a dashboard backend per profile. Each returned
    session is tagged with its owning ``profile`` so the desktop renders one
    browsable list and only spins up a profile's backend when the user actually
    interacts (sends a message). A user with a single (default) profile gets the
    same rows as ``/api/sessions``, just tagged ``profile="default"``.

    Rows omit ``system_prompt``/``model_config`` unless ``full=1`` — same
    list projection as ``/api/sessions``.
    rO  r   rR  r   rS  rU  r   r4  r}  rf  c                 *    g | ]}|j         |j        fS r  )rR   r   )r   rQ   s     rU   r  z)get_profiles_sessions.<locals>.<listcomp>O  s!    @@@$	49-@@@rW   z0GET /api/profiles/sessions: list_profiles failedr1  rP  rQ  Nc                 :    g | ]}|                                 |S r  r  rW  s     rU   r  z)get_profiles_sessions.<locals>.<listcomp>]  s%    OOO!QWWYYOAOOOrW   r   rQ  r!  state.dbT)db_pathr5  )r  r  rT  )	r   rL  r7  r  rX  rY  rZ  r[  r8  )r   rL  rX  rY  rZ  r\  r  r^  r:  r;  r<  r=  r]  rJ  c                 \    |                                p|                      d          pdS )Nr<  r   r   )r>  sort_keys    rU   r  z'get_profiles_sessions.<locals>.<lambda>  s'    aeeHooI|1D1DI rW   r  reverse)rF  r  profile_totalsr7  r  r  )r5   r@  r4  r[   r}  r`  r   list_profilesrZ   rP   r"  get_profile_dirrN  r   r  rC  r   rA  r  rB  rb  r   r   r   r  rF  )"r7  r  rI  rJ  rK  r  r   rL  r:  r4  profiles_modtargetsrR   r  infosrX  rZ  rY  source_filterrd  per_profilemergedr  rr  r  r?  rm  rE  rN  r   profile_totalr>  windowro  s"                                    @rU   get_profiles_sessionsr}  (  s   0 5554effff)))4[\\\\&&&&&&333333&(G Q7e##'00
dd|$$$$	 ..00E@@%@@@GG 	 	 	NNMNNNGGG	  	QNNI|'C'CI'N'NOPPPA|,,&M9, NdMOO 52<<SAAOOOL c%&.%00#66K#%FE%'N#%F
)++C / /
dt**z)~~ 		 7d;;;BB 	 	 	MMdSXX>>???HHHH	#	(($ , 4!"3!1+%*h%6!%X )  D ,,$ , 4"3!1+!% -  M ]"E#0N4  ! !#)*.)*;&'EE*%%- Squu]AEE,4J4JKKKsR + !%QUU:%6%6 7 7*a    !  	@ 	@ 	@MMdSXX>>????????	@ HHJJJJBHHJJJJ % 1 1}}|H
KKIIIISWKXXXF6E>)*F ) ((((  s[   - B &B76B7F!!
G+%GGC$KL
K<%K72L7K<<LL+z/api/sessions/searchqc                 |  K   | r|                                  sdg iS 	 t          |          	 t          dt          t	          |pd          d                    i dt
          dt
          ffdi dt
          dt
          ffd	i d
t
          dt          ddffd}                    | d          D ]}|                    d          }|                    d          pd                                 }|pd| } |||d|                    d          |                    d          |                    d          d           ddl	}g }	 |j
        d|                                            D ]Z}
|
                    d          s|
                    d          r|	                    |
           B|	                    |
dz              [d                    |	          }t          dz  d          }                    ||          }|D ]}t!                    k    r ny ||d         |                    d d          |                    d!          |                    d          |                    d          |                    d"          d           dt#                                                    i                                 S #                                  w xY w# t(          $ r  t*          $ r, t,                              d#           t)          d$d%&          w xY w)'aS  Search sessions by ID plus full-text message content using FTS5.

    Direct session-id matches are surfaced first, then FTS message-content
    matches. Results are deduped by compression lineage, not by raw
    ``session_id``. Auto-compression rotates a conversation onto a fresh
    session id (and leaves the old segment's messages in the FTS index), so one
    logical chat can own many ``sessions`` rows that all match the same query.
    Branches also use ``parent_session_id``, but they are real alternate
    conversations; don't collapse branch-specific hits back into the parent.
    resultsr   r  d   
session_idrJ   c                    | s| S | v r|          S g }| }t                      }| }|r(||vr#|                    |           |                    |           |v r	|         }n	                     |          }n# t          $ r d }Y nw xY w|s|}nt          |t                    r|                    d          nd }|s|}n	                     |          }n# t          $ r d }Y nw xY w|s|}n_|                    d          }|                    d          }	|                    d          dk    o|d uo	|	d uo|	|k    }
|
s|}n	|}|r||v#|D ]}||<   |S )Nparent_session_idr:  r<  
end_reasonrk  )r~   addr   get_sessionrZ   rv  r  r   )r  chaincurvisitedr  r>  r  parent_sessionparent_ended_atr<  is_compression_edger  rE  
root_caches               rU   compression_rootz)search_sessions.<locals>.compression_root  s   ! &%%++%j11 %%! #!c00KK$$$LL%%%j(()#!NN3//$ ! ! ! ! ";Ea;N;NXQUU#6777TXF! ".)+)?)?$ . . .)-.) "&4&8&8&D&DO!"|!4!4J&**<88MI :+47:&d2: '/9	 ( / " CG  #!c00H " , ,D'+Jt$$s$   &A< <B
BC C*)C*root_idc                     | v r|          S | }	                      |           }|r|}n# t          $ r Y nw xY w|| <   |S r  )get_compression_tiprZ   )r  tipr#  rE  	tip_caches      rU   lineage_tipz$search_sessions.<locals>.lineage_tip  ss    i''$W--!55g>>H '&    D%(	'"
s   + 
88raw_sidrj  Nc                     | sd S  |           }|v st                    k    rd S t          |          } |          |d<   ||d<   ||<   d S )Nr  lineage_root)r   r  )r  rj  r  r  r  
safe_limitseens      rU   add_lineage_resultz+search_sessions.<locals>.add_lineage_result  st     F''004<<3t99
#:#:Fw--(3D(9(9%*.'$T


rW   T)r7  rY  r  previewr   zSession ID: r   r  r<  )snippetroler   r  session_startedr   z"[^"]*"|\S+"r   r  r   r6  )queryr7  r  r  r  zGET /api/sessions/search failedr!  zSearch failedr   )r   ra  rN  r  rw  r  r  search_sessions_by_idr   r_  findallr   r%  r   r  search_messagesr   r  r  r   r5   rZ   rP   r"  )r~  r7  r  r  rowsidr  r  r_  termsr   prefix_queryfetch_limitmatchesmr  rE  r  r  r  r  r  s                  @@@@@@@rU   search_sessionsr    s       AGGII 2SE)'22L	QC$4$4c : :;;J  "J/S /S / / / / / / /b !IS S       " D	%C 	%$ 	%4 	% 	% 	% 	% 	% 	% 	% 	% 	% //VZ/[[  ggdmm779--3::<<!9%9C%9%9""#* $"%''("3"3!$!1!1+.77<+@+@ 	 	 	 	 IIIE#NAGGII>> . .##C(( .ENN3,?,? .LL''''LL----88E??L j1nb11K((|;(OOG  t99
**E""lO#$55B#7#7 !f"#%%//!"w+,551B+C+C 	 	 	 	 tDKKMM223HHJJJJBHHJJJJ    E E E8999ODDDDEs$   K; JK" K; "K88K; ;A L;c                 B   t          |           } |                     d          }t          |t                     r`|                    dd          }|                    d|                    dd                    | d<   t          |t                    r|nd| d<   nd| d<   | S )a  Normalize config for the web UI.

    Hermes supports ``model`` as either a bare string (``"anthropic/claude-sonnet-4"``)
    or a dict (``{default: ..., provider: ..., base_url: ...}``).  The schema is built
    from DEFAULT_CONFIG where ``model`` is a string, but user configs often have the
    dict form.  Normalize to the string form so the frontend schema matches.

    Also surfaces ``model_context_length`` as a top-level field so the web UI can
    display and edit it.  A value of 0 means "auto-detect".
    r  r  r   r1  rR   r   r  )r  r   rv  rw  )r{  	model_valctx_lens      rU   _normalize_config_for_webr  K  s     &\\F

7##I)T"" +-- 0!44#--	9==3L3LMMw4>w4L4L)SRS%&&)*%&MrW   c                 z    |                      dd                               dd                                          S )Nr  r  -)r  r   r  s    rU   _memory_provider_labelr  b  s2    <<S!!))#s3399;;;rW   c                 |    t          | pd                                          }|                                dv rdS |S )Nr   >   built-inr  r  )r  r   r   )rR   rT   s     rU   _normalize_memory_provider_namer  f  s=    4:2$$&&H~~:::rOrW   c                     	 ddl m}  ||           S # t          $ r! t                              d| d           Y d S w xY w)Nr   )load_memory_providerz!Failed to load memory provider %sTr  )r  r  rZ   rP   r  )rR   r  s     rU   _load_memory_providerr  m  se    777777##D)))   

6t
LLLtts    '>>c                 |   	 ddl m}  ||           }|i S |dz  }|                                si S |                    d          5 }t	          j        |          pi }d d d            n# 1 swxY w Y   t          |t                    r|ni S # t          $ r" t          
                    d| d           i cY S w xY w)	Nr   )find_provider_dirzplugin.yamlz	utf-8-sigr  z.Failed to read memory provider manifest for %sTr  )r  r  rA  r  r  rj  rv  r  rZ   rP   r  )rR   r  provider_dirmanifest_pathr  manifests         rU   _memory_provider_manifestr  w  s+   444444((..I$}4##%% 	I55 	4~f--3H	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4%h55=xx2=   

CTTX
YYY			s?   B B B A+B +A//B 2A/3B )B;:B;c                 H    t          | t                    sg S d | D             S )Nc                     g | ]D}t          |                                          #t          |                                          ES r  r  r   r   r  s     rU   r  z _string_list.<locals>.<listcomp>  s=    EEE$3t99??3D3DECIIOOEEErW   )rv  r  ry  s    rU   _string_listr    s-    eT"" 	EE%EEEErW   c                    t          |           }g }|                    d          pg D ]}t          |t                    st	          |                    d          pd                                          t	          |                    d          pd                                          t	          |                    d          pd                                          d}|d         s|d         s|d         r|                    |           t          |                    d                    |t          |                    d                    d	S )
Nexternal_dependenciesrR   r   installr  )rR   r  r  pip_dependenciesrequires_env)r  r  required_env)r  r   rv  r  r  r   r   r  )rR   r  r  r  deps        rU   _memory_provider_setup_manifestr    sI   (..H24||344: 	. 	.#t$$ 	-2..44663779--344::<<))/R006688
 

 v; 	.#i. 	.CL 	.!((--- )6H)I)IJJ!6$X\\.%A%ABB  rW   c                 H    t          |           }t          |          |d<   |S )Ndependencies_installed)r  '_memory_provider_dependencies_installed)rR   setups     rU   _memory_provider_setup_infor    s'    +D11E&Me&T&TE
"#LrW   r  mem0hindsight_client	hindsight)z	honcho-aimem0aizhindsight-clientzhindsight-allr  c                 `    t          j        d| d          d                                         S )Nz
[\[<>=!~;]r   )maxsplitr   )r_  r   r   )r  s    rU   #_memory_provider_dependency_packager    s*    8M3333A6<<>>>rW   c                 ~    t          |           }t                              ||                    dd                    S )Nr  r  )r  _MEMORY_PROVIDER_IMPORT_NAMESr   r  )r  packages     rU   _memory_provider_import_namer    s3    1#66G(,,Wgooc36O6OPPPrW   c                 n    t          |           }|sdS 	 t          |           dS # t          $ r Y dS w xY w)NFT)r  
__import__r`   )r  import_names     rU   _dependency_importabler    sV    .s33K u;t   uus   & 
44  c                 h    t          | pd          }t          |          |k    r|S |d |          dS )Nr   z
... truncated ...)r  r   )rr  r7  r  s      rU   _trim_setup_outputr    s@    u{D
4yyE6E6l////rW   c                  ^   t           j                                        } t          j                    }|dz  dz  |dz  dz  |dz  dz  t          d          g}|                     dd          }t           j                            d |D                       }|r|t           j        z   |z   | d<   | S )	Nz.brv-clibinz.localz.npm-globalz/usr/local/binPATHr   c              3   \   K   | ]'}|                                 t          |          V  (d S r  )rA  r  )r   r   s     rU   r   z-_memory_provider_setup_env.<locals>.<genexpr>  s5      OO4OSYYOOOOOOrW   )rt   r  copyr   r  r   pathsepr  )r  r  
extra_binsexisting_pathr|  s        rU   _memory_provider_setup_envr    s    
*//

C9;;DzE!x%}u$	J GGFB''MZ__OO:OOOOOF :rz)M9FJrW   )command	completedr  kindr)  r  r  r  c           	          | ||||d n|j         |dnt          |j                  t          |p
|dn|j                  dS )Nr   )r  rR   r)  r  r  r  r  )r  r  r  r  )r  rR   r)  r  r  r  s         rU   _command_resultr    sb     '/ddY5I!)""/A)BR/S/S$U%]Y5FrrIL\^^  rW      )r  r!  r!  c          
      Z    t          j        | ||rdnd t                      dd|d          S )Nz	/bin/bashTF)r  r  r  r  r  r!  r  )r  r  r  )r  r]  r  r!  s       rU   _run_setup_commandr    sD     >"'1;;T&((	 	 	 	rW   r  c                 X   t          |                     d                    }|                     d          pg }t          d |D                       }d}|D ]}t          |t                    st          |                    d          pd                                          }t          |                    d          pd                                          }|s|rd}	 t          t          j	        |          |d	
          }n# t          $ r d}Y w xY w|j        dk    rd}|o|S )Nr  r  c              3   4   K   | ]}t          |          V  d S r  r  r   r  s     rU   r   z:_memory_provider_dependencies_installed.<locals>.<genexpr>  s+      II',,IIIIIIrW   Tr  r   r  Fr  r]  r!  r   )r  r   rf  rv  r  r  r   r  shlexr   rZ   r  )	r  r  r  pip_okexternal_okr  	check_cmdinstall_cmdr  s	            rU   r  r    s]   #EII.@$A$ABB!II&=>>D"II8HIIIIIFK$    #t$$ 	((.B//5577	#''),,23399;; 	 $#	*I&&!  II
  	 	 	KH	 1$$K!k!s   #$DDDdependenciesc           
         d | D             }| sg S |s&t          dd                    |           d          gS t          j        d          }|r:|dddt          j        d	g|}d
t          j         dd                    |           }n7t          j        dddd	g|}t          j         dd                    |           }	 t          ||d          }nL# t          $ r?}t          dd                    |          d|t          |                    gcY d }~S d }~ww xY wt          dd                    |          |j	        dk    rdnd||          gS )Nc                 0    g | ]}t          |          |S r  r  r  s     rU   r  z=_install_memory_provider_pip_dependencies.<locals>.<listcomp>#  s&    NNNs2H2M2MNsNNNrW   r  , already_installedr  rR   r)  uvr  z--pythonz--quietzuv pip install --python r  r  z -m pip install    r  failedr  rR   r)  r  r  r   	installedr  rR   r)  r  r  )
r  r  r  whichr  r  r  rZ   r  r  )r  missinguv_pathr  r]  r  rN  s          rU   )_install_memory_provider_pip_dependenciesr  "  s   NNlNNNG 	 
TYY|-D-DM`aaa
 	
 l4  G I	:s~yc[bcQS^QQchhw>O>OQQ>4	9OwO^HHSXXg5F5FHH
&wMMM		 	
 	
 	
YYw''#hh  
 	
 	
 	
 	
 	
 	
	
 	7##"+"6!";";;;	
 	
 	
 s   C 
D4DDDc                    g }| D ]m}|                     d          pd}|                     d          pd}|                     d          pd}|r	 t          t          j        |          |d          }|j        dk    r(|                    t          d	|d
||                     |                    t          d	||rdnd||                     nO# t          $ rB}|                    t          d	||rdnd|t          |                               Y d }~nd }~ww xY w|s!|rI	 t          ||dd          }nL# t          $ r?}|                    t          d|d|t          |                               Y d }~}d }~ww xY w|                    t          d||j        dk    rdnd||                     |r|j        dk    r	 t          t          j        |          |d          }	|                    t          d	||	j        dk    rdnd||	                     !# t          $ r?}|                    t          d	|d|t          |                               Y d }~ed }~ww xY wo|S )NrR   
dependencyr  r   r  r  r  r   external_checkr  r
  r  r  r  Tr=  )r]  r  r!  external_installr	  verified)	r   r  r  r   r  r   r  rZ   r  )
r  r  r  rR   r  r  r  rN  r  
post_checks
             rU   ._install_memory_provider_external_dependenciesr  K  s    %'G f fwwv.,GGG$$*	ggi((.B (	$*K	**%    #q((NN'!1!%#6$-&+     #-!,7EyyX )"'     -  	 	 	#-!,7EyyX )!#hh         	@   7	,'	    
 
 
#/!' +!#hh     
 NN+*1*<*A*A;;x'%      W/144!3I.. ) "" " "J
 NN'!1!%1;1F!1K1K::QY$-&0      ! 	 	 	NN'!1!%#+$-"%c((         	 NsI   $C
D8DD*D>>
F4FFAH&&
I/04I**I/c                    t          |           }t          |           }||st          dd|            t          |           }g }|                    t          |d                              |                    t          |d                              |s%|                    t          d| d                     t          d	 |D                       }d
 t                      D             }|| ||                    |           dS )Nr   Unknown memory provider: r   r  r  r  no_declared_stepsr  c              3   *   K   | ]}|d          dvV  dS )r)  >   r  Nr  )r   r  s     rU   r   z1_install_memory_provider_setup.<locals>.<genexpr>  s,      FFFVHZ/FFFFFFrW   c                      i | ]}|d          |S r  r  r   r  s     rU   rI  z2_install_memory_provider_setup.<locals>.<dictcomp>      QQQSFSQQQrW   )rs  rT   r  r)  )r  r  r5   r  r  r  r  r   r  rf  "_discover_memory_provider_statusesr   )rR   rT   r  r  r  rs  statusess          rU   _install_memory_provider_setupr    s8   $T**H(..H4VPT4V4VWWWW+D11EGNN<UCU=VWWXXXNN6u=T7UVV    
*  	
 	
 	
 
FFgFFF	F	FBQQ,N,P,PQQQH,,t$$	  rW   c                    g }|tt          |d          rd	 |                                }t          |t                    rd |D             }n-# t          $ r  t
                              d| d           Y nw xY wg }|D ]}t          |                    d          pd          	                                }|s<|                    d          p|                    d	          pg }t          |t                    sg }t          |                    d
          p|                    d          pd          	                                
                                }|                    d          rd}n6|rd}n1|dv s(t          |                    d          t                    rd}nd}g }	|D ]*}
t          |
          }|	                    ||dd           +t          |                    d          pd          }|                    |t          |                    d          p'|                    dd                                                    ||t          |                    d          pd          t          |                    dd                    |                    dd          |	t          |                    d          pd          t          |                    d          t                    r|                    d          nd t          |                    d          pd          pd d           |S )Nget_config_schemac                 <    g | ]}t          |t                    |S r  )rv  r  r   fields     rU   r  z5_normalize_memory_provider_schema.<locals>.<listcomp>  s'    PPP
5$8O8OPePPPrW   z,Failed to read memory provider schema for %sTr  r  r   choicesr  r  r  secretr  >   r   rt  r1  rt  r  rr  r?  r  r  r?  r  r  placeholderrequiredFr   whenenv_var)r  r?  r  r  r(  r)  r1  r  r   r*  _env_key)hasattrr!  rv  r  rZ   rP   r3  r  r   r   r   r   r   r  r   r  )rR   rT   
raw_schemar  fieldsr  r%  explicit_kindr  r  choicerr  r  s                rU   !_normalize_memory_provider_schemar2    s9   ')J2E F F	^,,..C#t$$ QPPPPP
 	^ 	^ 	^LLGX\L]]]]]	^ $&F % %#''%..&B''--// 	'')$$@	(:(:@b'4(( 	GCGGFOODswwvD"EEKKMMSSUU778 	DD 	DD111Z	@R@RTX5Y5Y1DDD 	P 	PFKKENNUU2NNOOOO#''-006B77))JS[[c-B-B-H-H-J-JKK&sww}55;<<SWWZ7788wwy"--swwu~~+,,'1#''&//4'H'HRCGGFOOOdCGGI..4"55=
 
 	 	 	 	 Ms   5A 'A65A6c                    |                                  si S 	 t          j        |                     d                    }n/# t          $ r" t
                              d| d           i cY S w xY wt          |t                    r|ni S )Nr  r  z"Failed to read JSON config from %sTr  )	rA  r*  r+  	read_textrZ   rP   r  rv  r  )r   r  s     rU   _read_json_filer5    s    ;;== 	z$..'.::;;   

7
MMM			 dD))144r1s   (A )A-,A-c                 @   t                      }i }||  dz  || z  dz  fD ]$}|                    t          |                     %	 t                      }n# t          $ r i }Y nw xY wt          |t                    r|                    d          ni }t          |t                    ro|                    |           }t          |t                    r|                    |           |                    d          }t          |t                    ri ||}t          |t                    r|                    d          ni }| dk    rTt          |t                    r?|                    d          }	t          |	t                    r|                    |	           |S )zIBest-effort read of existing provider config across legacy/native stores.r  config.jsonrj  provider_configr   holographiczhermes-memory-store)r   r  r5  r   rZ   rv  r  r   )
rR   r  r  r   r  
memory_cfgprovider_cfg
legacy_cfgplugins_cfgholographic_cfgs
             rU   %_read_memory_provider_existing_valuesr?    s    "##KF 	nnn$d]* - - 	od++,,,,mm    '1d&;&;C"""J*d## .!~~d++lD)) 	(MM,'''^^$566
j$'' 	.-
-f-F )33(=(=E#'')$$$2K}K!>!>%//*?@@ot,, 	+MM/***Ms   A A%$A%env_keyc                     | sdS t                      }t          |                    |           p t          j                            |           pd          S Nr   )r   r  r   rt   r  )r@  env_on_disks     rU   _env_lookuprD  ?  sJ     r**K{w''H2:>>'+B+BHbIIIrW   r1  c                <   t          | t                    r| S | | dk    r|S t          | t          t          f          rt          |           S t	          |                                                                           }|dv rdS |dv rdS t          d|            )Nr   >   rf   onyestrueT>   r  nor;  falseFzInvalid boolean value: )rv  r   rw  rx  r  r   r   r  )rr  r1  r  s      rU   _coerce_boolrL  F  s    % }%#u&& E{{u::##%%D)))t***u
6u66
7
77rW   r$  c                 l    |                      dd          }| d         dk    rt          |d          S |S )Nr1  r   r  rt  FrE  )r   rL  )r$  r1  s     rU   _field_defaultrN  U  s<    ii	2&&GV}	!!GU3333NrW   c                    | d         dk    rdS |                     | d                   }|dv r"t          |                      d                    }|dv rt          |           }| d         dk    rQd |                      d	g           D             }t          |          }||v r|nt          t          |                     S | d         d
k    r-t	          |t	          t          |           d                    S t          |          S )Nr  r&  r   r  rB  r,  r  c                     h | ]
}|d          S ry  r  r   opts     rU   r  z_field_value.<locals>.<setcomp>g      DDDC3w<DDDrW   r  rt  FrE  )r   rD  rN  r  rL  )r$  r  rr  alloweds       rU   _field_valuerU  \  s
   V}  rHHU5\""E
EIIj1122
u%%V}  DD599Y+C+CDDDE

((uuc.2G2G.H.HHV}	!!E<u8M8MW\+]+]+]^^^^u::rW   c                     | d         dk    rJt          t          |                     d                    p|                    | d                             S t          | |          }|dvS )Nr  r&  r,  r  rB  )r   rD  r   rU  )r$  r  rr  s      rU   _field_is_setrW  o  sb    V}  K		* 5 566P$((5<:P:PQQQ%%E
""rW   fields_by_keyc                 l   |                      d          }t          |t                    r|sdS |                                D ]p\  }}|pi                      t	          |                    pt	          |          ddd d}t          ||          }t	          |          t	          |          k    r dS qdS )Nr*  Tr  r   )r  r  r1  r,  F)r   rv  r  r  r  rU  )r$  r  rX  r*  dep_keyr   	dep_fieldactuals           rU   _field_visibler]  v  s    
 99VDdD!!  t!ZZ\\ 	 	"(b--c'll;; 
w<<	@
 @
	 i..v;;#h--''55 (4rW   c                 4   | d         | d         | d         | d         | d         | d         | d         dk    rdnt          | |          t          | |          |                     d	g           |                     d
d          |                     d          d}|S )Nr  r?  r  r  r(  r)  r&  r   r  r   r*  )r  r?  r  r  r(  r)  rr  is_setr  r   r*  )rU  rW  r   )r$  r  r  s      rU   _public_memory_provider_fieldr`    s    U|wf]+]+*%V}00l5$6O6Ot,,99Y++yy##		&!! E LrW   c                     t          |           fdt          | |          D             }| t          |           |t          |           dS )Nc                 0    g | ]}t          |          S r  )r`  r   r$  r  s     rU   r  z,_memory_provider_payload.<locals>.<listcomp>  s3        	&eT22  rW   rR   r?  r/  r  )r?  r2  r  r  )rR   rT   r/  r  s      @rU   _memory_provider_payloadre    so    066D   6tXFF  F
 '--,T22	  rW   r  c                    | d         dk    r-t          |t          t          |           d                    S t          ||nd                                          }| d         dk    r]|st          t          |                     }d |                     dg           D             }||vrt          d	| d
          d          |S |pt          |           S )Nr  rt  FrE  r   r  c                     h | ]
}|d          S ry  r  rQ  s     rU   r  z'_coerce_schema_field.<locals>.<setcomp>  rS  rW   r  Invalid value for 'r  ')rL  rN  r  r   r   r  )r$  r  rr  rT  s       rU   _coerce_schema_fieldrj    s    V}	!!CnU6K6KUZ)[)[)[\\\\sB//5577EV}   	/u--..EDD599Y+C+CDDDB5<BBBCCC)N5)))rW   r  c                 z   |t          |d          r	 ddlm} n@# t          $ r3 |                    |t          t                                           Y d S w xY wt          |          j        |j        ur1|                    |t          t                                           d S t                      }|	                    d          }t          |t                    si }||d<   |	                    |           }t          |t                    si }|                    |           ||| <   t	          |           d S )Nr   r   )r.   rj  )r-  agent.memory_providerr.   rZ   r   r  r   r  r   r   rv  r  r  )rR   rT   r  _BaseMemoryProviderr  r:  r  s          rU   #_save_memory_provider_native_configrn    sL   - @ @	SSSSSSS 	 	 	  _->->)?)?@@@FF	 >>%-@-LLL  _->->)?)?@@@F
--C""Jj$'' #
"HnnT""Ggt$$ NN6Jts    9AAc                     t          |           t          | |          }d |D             fd|D             }d |D             }|sdS t          fd|D                       S )Nc                      i | ]}|d          |S r  r  r#  s     rU   rI  z2_memory_provider_is_configured.<locals>.<dictcomp>      ===UU5\5===rW   c                 6    g | ]}t          |          |S r  )r]  )r   r$  r  rX  s     rU   r  z2_memory_provider_is_configured.<locals>.<listcomp>  s:       ^E4%O%O  rW   c                 <    g | ]}|                     d           |S )r)  r   r#  s     rU   r  z2_memory_provider_is_configured.<locals>.<listcomp>  s)    RRREIIj<Q<QRuRRRrW   Tc              3   8   K   | ]}t          |          V  d S r  )rW  rc  s     rU   r   z1_memory_provider_is_configured.<locals>.<genexpr>  s-      GGe}UD))GGGGGGrW   )r?  r2  rf  )rR   rT   r/  visible_fieldsrequired_fieldsr  rX  s        @@rU   _memory_provider_is_configuredrw    s    066D.tX>>F==f===M    !  N SR.RRRO tGGGGGGGGGGrW   c            
         i } 	 ddl m}  |            D ]F\  }}}t          |          t          |pd          t          |          dd| t          |          <   Gn*# t          $ r t
                              d           Y nw xY wt                      }d}|                    d          }t          |t                    r"t          |                    d                    }|r|| vr
|d	dd
d| |<   g }t          |           D ]}| |         }	|	d         rd nt          |          }
t          |          }|	d         rdnt          ||
          }|	d         rg nt!          ||
          }|	d         rd}n@|	d         s|                    dd
          sd}n|sd}n|	d         s|rd}n|	d         sd}nd}|                    ||	d         |	d         |||d           |S )Nr   )discover_memory_providersr   F)rR   r  r  r  z discover_memory_providers failedrj  rT   z"Configured provider was not found.Tr  r  r  unavailableneeds_configreadyr  )rR   r  r  
configuredr)  r  )r  ry  r  r   rZ   rP   r"  r   r   rv  r  r  sortedr  r  rw  r2  r   )
discoveredry  rR   r  r  r  rm  memr  r  rT   r  r}  schema_fieldsr)  s                  rU   r  r    s   ,.J;<<<<<<,E,E,G,G 	 	(D+yD		";#4"55!)__ 	% %Js4yy!!	  ; ; ;9:::::; --CF
''(

C#t F01D1DEE 
&
**?	
 

6 ')Iz""  y>J44/DT/J/J+D11!)n`UU2PQUW_2`2`
!)nc2STXZb2c2cy> 	FF[! 		%))4Ld*S*S 		"FF 	#FF[! 	m 	#FF[! 	"FFF}-[)$
 
 	 	 	 	 s   AA $BBc           
         | sd S d t                      D             }|                    |           }|t          dd|  d          |d         dk    r2t          dd|  d	|d                             d
d           d          d S )Nc                      i | ]}|d          |S r  r  r  s     rU   rI  z2_require_memory_provider_ready.<locals>.<dictcomp>  r  rW   r   zUnknown memory provider ''.r   r)  r|  zMemory provider 'z' is not ready (r  r  z'). Configure it in the dashboard first.)r  r   r5   r  )rR   r  r  s      rU   _require_memory_provider_readyr    s     QQ,N,P,PQQQH
,,t

C
{7t777
 
 
 	
 8}]D ] ]M))#s33] ] ]
 
 
 	
  rW   c                    t          |           }t          | |          }d |D             }i }i }|D ]}t          |i |||          s|d         dk    rlt          |                    |d                   pd                                          }	|	r-|                    d          r|	|t          |d                   <   |d         |v r||d                  n(|                    |d         t          |                    }
t          ||
          ||d         <   t          | ||            |j	                    D ]\  }}t          ||           d S )Nc                      i | ]}|d          |S r  r  r#  s     rU   rI  z8_write_memory_provider_config_values.<locals>.<dictcomp>4  rq  rW   r  r&  r  r   r,  )r?  r2  r]  r  r   r   rN  rj  rn  r  r   )rR   rT   r  r  r/  rX  config_valuesrx  r$  	submittedr  r@  r&  s                rU   $_write_memory_provider_config_valuesr  -  s   
 5T::H.tX>>F==f===M$&M G G Ge%B%BM%BMRR 	=H$$FJJuU|44:;;AACCI <UYYz22 <2;E*-../ U|v%% 5<  eElN5,A,ABB 	
 ';5#&F&FeEl##'hFFF(7=?? ( (w''''( (rW   z ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$c                 f    t                               | pd          st          dd|            dS )a]  Reject provider names that could traverse outside the plugin dirs.

    ``name`` is interpolated into filesystem paths by ``find_provider_dir()``
    and gates which plugin manifest's setup commands run. A strict charset
    allowlist (no path separators, no dots) makes traversal impossible
    regardless of how the downstream lookup evolves.
    r   r   r  r   N)_MEMORY_PROVIDER_NAME_REr  r5   r  s    rU   #_require_valid_memory_provider_namer  R  sH     $--djb99 X4VPT4V4VWWWWX XrW   c                 4    t                      | j        z  dz  S )Nr7  )r   rR   rT   s    rU   _declared_provider_file_pathr  d  s    x},}<<rW   c                 :    t          t          |                     S r  )r5  r  r  s    rU   _read_declared_provider_filer  h  s    7AABBBrW   c                    | j         g| j        R D ]*}|                    |          }|rt          |          c S +t	                      }| j        D ]*}|                    |          }|rt          |          c S +| j        S r  )r  aliasesr   r  r   env_fallbacksr1  )r$  r  
source_keyrr  rC  r@  s         rU   _declared_read_field_valuer  l  s    y15=11  
$$ 	u::	 **K&  (( 	u::	 =rW   c                     t                      }| j        g| j        R D ]}|r|                    |          r dS t	          fd| j        g| j        R D                       S )NTc              3   B   K   | ]}                     |          V  d S r  r   )r   r  r  s     rU   r   z)_declared_field_is_set.<locals>.<genexpr>  s/      RR
txx
##RRRRRRrW   )r   r@  r  r   r  r  r  )r$  r  rC  r@  s    `  rU   _declared_field_is_setr  {  s    **KM8E$788   	{w// 	44RRRRuy6Q5=6Q6QRRRRRRrW   c                    t          |           }g }| j        D ]}|j        |j        |j        |j        |j        d |j        D             d}|j        rd|d<   t          ||          |d<   nOt          ||          }|j        dk    r||                                vr|j        }||d<   t          |          |d<   |                    |           | j        | j        |dS )Nc                 8    g | ]}|j         |j        |j        d S )r'  r'  rQ  s     rU   r  z._declared_provider_payload.<locals>.<listcomp>  s8        )ciXX  rW   )r  r?  r  r  r(  r  r   rr  r_  r  rR   r?  r/  )r  r/  r  r?  r  r  r(  r  	is_secretr  r  allowed_valuesr1  r   r   rR   )rT   r  r/  r$  r  rr  s         rU   _declared_provider_payloadr    s   '11D#%F  9[J , ,  =  
!
 
!
 ? 		*E'N4UDAAE(OO.ud;;EzX%%%u7K7K7M7M*M*M"E'N"5kkE(OeMHNfMMMrW   c                     |pd                                 }| j        dk    r9|s| j        }||                                 vrt	          d| j         d          |S |p| j        S )Nr   r  rh  ri  )r   r  r1  r  r  r  )r$  r  rr  s      rU   _coerce_declared_field_valuer    sx    YBEzX 	"ME,,....?59???@@@!EM!rW   c                    t          |           }i }i }| j        D ]}|j        rOt          |                    |j                  pd                                          }|r|j        r
|||j        <   X|j        |v r||j                 n,t          |                    |j        |j                            }t          |t          |                    ||j        <   t          |           }|j                            dd           |                    |           ddlm}	  |	||d            |j                    D ]\  }
}t#          |
|           d S )Nr   Tr  r   atomic_json_write  )r  )r  r/  r  r  r   r  r   r@  r1  r  r  r  r"  r  utilsr  r  r   )rT   r  r  json_valuesrx  r$  r  r  r   r  r@  r&  s               rU    _update_declared_provider_configr    s   +H55H"$K G O O? 	FJJuy117R88>>@@I 3U] 3)2& yF"" 59X\\%)U];;<< 	
 ">eSXX!N!NEI'11DKdT222OOK   ''''''dH51111(7=?? ( (w''''( (rW   z#/api/memory/providers/{name}/configsurfacec                    K   t          |            |dk    r&t          |           }|| | g dS t          |          S t          |           }|| | g t	          |           dS t          | |          S )Ndeclaredr  rd  )r  get_declared_memory_providerr  r  r  re  )rR   r  r  rT   s       rU   get_memory_provider_configr    s      '---*/55 !42>>>)(333$T**H trD_`dDeDefff#D(333rW   z"/api/memory/providers/{name}/setupc                   K   t          |            t          |           }|#t          |           st          dd|            ||j        r~	 t          | ||j                   nf# t          $ r$}t          dt          |                    |d }~wt          $ r- t          
                    d|            t          dd          w xY wt          |           S )Nr   r  r   r   z5Failed to persist memory provider setup values for %sr!  r_  )r  r  r  r5   r  r  r  r  rZ   rP   r"  r  )rR   r0  rT   rN  s       rU   setup_memory_providerr    s     '---$T**H 9$ ? ?
 4VPT4V4VWWWW	Q0xMMMM 	K 	K 	KCCAAAsJ 	Q 	Q 	QNNRTXYYYC8OPPPP	Q *$///s   A' '
C
1B:C
c                   K   t          |            |dk    rt          |           }|t          dd|            	 t          ||j        pi            ddiS # t
          $ r$}t          dt          |                    |d }~wt          $ r- t          	                    d|            t          d	d
          w xY wt          |           }|t          dd|            |j        pi }	 t          | ||           t          |            t                      }|                    d          }t          |t                     si }||d<   | |d<   t#          |           d| dS # t          $ r  t
          $ r$}t          dt          |                    |d }~wt          $ r- t          	                    d|            t          d	d
          w xY w)Nr  r   r  r   rs  Tr   z5PUT /api/memory/providers/%s/config (declared) failedr!  r_  rj  rT   rs  rm  z*PUT /api/memory/providers/%s/config failed)r  r  r5   r  r  r  r  rZ   rP   r"  r  r  r  r   r   rv  r  r   )	rR   r0  r  r  rN  rT   r  r{  memory_configs	            rU   update_memory_provider_configr    sI     '---*/55C8ZTX8Z8Z[[[[	Q,Xt{7HbIII$< 	K 	K 	KCCAAAsJ 	Q 	Q 	QNNRTXYYYC8OPPPP	Q %T**H4VPT4V4VWWWW[BFM,T8VDDD&t,,,

8,,-.. 	-M,F8$(j!Fd+++    G G GCHH===3F M M MCTJJJ4KLLLLMs0   A 
B<#B:B<.A7E& &G:F:Gz/api/configc                    K   t          |           5  t          t                                }d d d            n# 1 swxY w Y   d |                                D             S )Nc                 D    i | ]\  }}|                     d           ||S r  r   r   kvs      rU   rI  zget_config.<locals>.<dictcomp>(  s/    EEETQ1<<3D3DEAqEEErW   )ru  r  r   r  )r  r{  s     rU   
get_configr  #  s      		 	  : :*;==99: : : : : : : : : : : : : : : FEV\\^^EEEEs   :>>z/api/config/defaultsc                     K   t           S r  )r   r  rW   rU   get_defaultsr  +  s      rW   z/api/config/schemac                  $   K   t           t          dS )N)r/  category_order)CONFIG_SCHEMA_CATEGORY_ORDERr  rW   rU   
get_schemar  0  s      #GGGrW   r  rT   auto_context_lengthconfig_context_lengtheffective_context_lengthcapabilities_EMPTY_MODEL_INFOz/api/model/infoc                    	 t          |           5  t                      }ddd           n# 1 swxY w Y   |                    dd          }t          |t                    rl|                    d|                    dd                    }|                    dd          }|                    dd          }|                    d          }n|rt          |          nd}d}d}d}|st	          t          |	          S 	 d
dlm}  ||||d          }n# t          $ r d
}Y nw xY wd
}	t          |t                    r|d
k    r|}	|	d
k    r|	n|}
i }	 d
dlm}  |||          }|'|j        |j        |j        |j        |j        |j        d}n# t          $ r Y nw xY w||||	|
|dS # t&          $ r  t          $ r1 t(                              d           t	          t                    cY S w xY w)a.  Return resolved model metadata for the currently configured model.

    Calls the same context-length resolution chain the agent uses, so the
    frontend can display "Auto-detected: 200K" alongside the override field.
    Also returns model capabilities (vision, reasoning, tools) when available.
    Nr  r   r1  rR   rT   r  r  r  r   )get_model_context_length)r  r  rT   r  get_model_capabilitiesrT   r  supports_toolssupports_visionsupports_reasoningcontext_windowmax_output_tokensmodel_familyr  zGET /api/model/info failed)ru  r   r   rv  r  r  r  agent.model_metadatar  rZ   rw  agent.models_devr  r  r  r  r  r  r  r5   rP   r"  )r  r  r  
model_namerT   r  
config_ctxr  auto_ctxconfig_ctx_inteffective_ctxcapsr  mcs                 rU   get_model_infor  ?  s   G'G$$ 	  	 --C	  	  	  	  	  	  	  	  	  	  	  	  	  	  	 GGGR((	 i&& 		"y)--2K2KLLJ }}Z44H }}Z44H"'788JJ+4<Y"JHHJ 	>)H====		EEEEEE// !!&*	  HH  	 	 	HHH	 j#&& 	(:>>'N +91*<*<( 	??????''LLLB~&(&7')'9*,*?&(&7)+)=$&O   	 	 	D	   #+%3(5 
 
 	
     	 ' ' '3444%&&&&&'sv   F* ,F* 0F* 0C
F*  D F* D%"F* $D%%.F* <F F* 
FF* FF* *AG/.G/)visionweb_extractrk  
skills_hubapprovalmcptitle_generationtriage_specifierkanban_decomposerprofile_describerr   _AUX_TASK_SLOTSz/api/model/optionsrefreshinclude_unconfiguredexplicit_onlyc                    	 ddl m}m} t          |           5   | |            t	          |          t	          |          ddddt	          |          t	          |          t	          |           
  
        cddd           S # 1 swxY w Y   dS # t
          $ r  t          $ r, t                              d           t          dd	          w xY w)
u  Return authenticated providers + their curated model lists.

    REST equivalent of the ``model.options`` JSON-RPC on tui_gateway, so the
    dashboard Models page can render the picker without a live chat session.
    The response shape matches ``model.options`` 1:1 so ``ModelPickerDialog``
    can share the same types.

    ``profile`` scopes the picker context (current model/provider, custom
    providers from config, per-profile .env auth state) so the Models page
    reads the SAME profile /api/model/set writes.

    ``refresh`` busts the per-provider model-id disk cache so every row
    re-fetches its live catalog — used by the picker's explicit "Refresh
    Models" control. Normal opens leave it false to stay on the 1h cache.
    r   build_models_payloadload_picker_contextT)	r  r  picker_hintscanonical_orderpricingr  r  probe_custom_providersprobe_current_custom_providerNzGET /api/model/options failedr!  zFailed to list model optionsr   )	hermes_cli.inventoryr  r  ru  r   r5   rZ   rP   r"  )r  r  r  r  r  r  s         rU   get_model_optionsr    sS   ,TRRRRRRRR G$$ 	 	''##%%"=11%)*>%?%?! $!W'+G}}26w--.?  	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	     T T T67774RSSSSTs0   B AB8B B		B B	B A Cz/api/model/recommended-defaultc                    | pd                                                                 }|dk    r	 ddlm}m}m}m}m}m}m	} ddl
m}	  |            }
 |d          pi } |d          }d}	  |	d          pi }|                    dd          pd}n# t          $ r d}Y nw xY w|r" ||
||          \  }
} ||
|d	          \  }
}n ||
||          \  }
} ||
d
          }d|t          |          dS # t          $ r# t                              d           ddddcY S w xY w	 ddlm}m} ddlm}  | |                      }|                    dg           D ]o}t)          |                    dd                                                    |k    r4d |                    d          pg D             }| |||
          ddc S p|dddS # t          $ r# t                              d           |dddcY S w xY w)a  Return the recommended default model for a freshly-authenticated provider.

    Mirrors the model-curation `hermes model` does so GUI onboarding lands on a
    sensible default instead of blindly taking the first curated entry. For
    Nous this honors the user's free/paid tier: free users get a free model,
    paid users get the full curated default. For any other provider it falls
    back to the first curated model (same as before).

    Response: {"provider": str, "model": str, "free_tier": bool | None}
    where free_tier is True/False for Nous and None otherwise. `model` may be
    empty if nothing could be resolved (caller degrades gracefully).
    r   nousr   )get_curated_nous_model_idsget_pricing_for_providercheck_nous_free_tierpartition_nous_models_by_tierpick_silent_default_model&union_with_portal_free_recommendations&union_with_portal_paid_recommendations)get_provider_auth_stateTforce_freshrA  )	free_tierr  )rT   r  r  z0GET /api/model/recommended-default (nous) failedNr  )r  r  slugc                 ,    g | ]}t          |          S r  r  r   r  s     rU   r  z1get_recommended_default_model.<locals>.<listcomp>  s    DDDQ#a&&DDDrW   modelsz)GET /api/model/recommended-default failed)r   r   r   r  r  r  r  r  r  r   r  r  r   rZ   r   rP   r"  r  r  r  r  )rT   r  r  r  r  r  r  r  r   r  	model_idsr  r  rC  rk   _unavailabler  r  r  rj  r  r  s                         rU   get_recommended_default_modelr    s_    N!!##))++Dv~~'	H                  @?????2244I..v66<"G,,>>>IJ //77=2"YY'8"==C

      


   
%K%Kw
& &"	7 +H*Gw$+ + +'	<< &L%Kw
& &"	7 .-i&IIIE &T)__UUU 	H 	H 	HNNMNNN &$GGGGG	HBRRRRRRRR??????&&':':'<'<==;;{B// 	x 	xC37762&&''--//477DD3778+<+<+BDDD$(3L3LV^b3c3c3crvwwwww 8 !2DAAA B B BBCCC 2DAAAAABsO   =C: .%B C: B# C: "B##AC: :*D'&D'+B%G G *HHz/api/model/auxiliaryc                 8   	 t          |           5  t                      }ddd           n# 1 swxY w Y   |                    di           }t          |t                    si }g }t
          D ]}t          |                    |          t                    r|                    |i           ni }|                    |t          |                    dd          pd          t          |                    dd          pd          t          |                    dd          pd          d           |                    di           }t          |t                    r`t          |                    dd          pd          t          |                    d	|                    d
d                    pd          d}nd|rt          |          ndd}||dS # t          $ r  t          $ r, t                              d           t          dd          w xY w)u  Return current auxiliary task assignments.

    Shape:
      {
        "tasks": [
          {"task": "vision", "provider": "auto", "model": "", "base_url": ""},
          ...
        ],
        "main": {"provider": "openrouter", "model": "anthropic/claude-opus-4.7"},
      }

    ``profile`` scopes the read — without it, the Models page would show
    the dashboard profile's auxiliary pins while /api/model/set wrote the
    selected profile's (read/write asymmetry).
    Nrq  rT   rH  r  r   r  )r  rT   r  r  r1  rR   r  )tasksmainzGET /api/model/auxiliary failedr!  zFailed to read auxiliary configr   )ru  r   r   rv  r  r  r   r  r5   rZ   rP   r"  )r  r  aux_cfgr  slotslot_cfgr  r  s           rU   get_auxiliary_modelsr  &  sp   "WG$$ 	  	 --C	  	  	  	  	  	  	  	  	  	  	  	  	  	  	 ''+r**'4(( 	G# 	 	D0:7;;t;L;Ld0S0S[w{{4,,,Y[HLLZ @ @ JFKKX\\'266<"==Z < < BCC	      GGGR((	i&& 	R	j" = = CDDY]]9immFB6O6OPPVTVWW DD
 !#y-PS^^^bQQD---    W W W89994UVVVVWs(   G ,G 0G 0F$G A Hz/api/model/moac                 n   	 ddl m} t          |           5  t                      } |t	          |t
                    r|                    d          ni           cddd           S # 1 swxY w Y   dS # t          $ r  t          $ r, t          
                    d           t          dd          w xY w)	z=Return the configured Mixture-of-Agents provider/model slots.r   )normalize_moa_configmoaNzGET /api/model/moa failedr!  zFailed to read MoA configr   )hermes_cli.moa_configr  ru  r   rv  r  r   r5   rZ   rP   r"  )r  r  r  s      rU   get_moa_modelsr  Y  s2   
Q>>>>>>G$$ 	Y 	Y--C''*S$:O:O(WUWXX	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y     Q Q Q23334OPPPPQs0   A4 AA'A4 'A++A4 .A+/A4 4A B4c                   	 	 ddl m}m} dt          dt          fd	dt
          dt          f	fdt          | j        p|          5  t                      }| j	        r4| j
        | j        fd| j	                                        D             d	}nH t          | j        | j        | j        | j        | j        | j        | j        | j        
                    } ||          }|r't+          ddd                    |          z              ||          }||d<   t/          |           ddi|cddd           S # 1 swxY w Y   dS # t*          $ r  t0          $ r, t2                              d           t+          dd          w xY w)z3Persist the Mixture-of-Agents provider/model slots.r   )r  validate_moa_payloadr  rJ   c                 b    d |                                                                  D             S )Nc                     i | ]
\  }}|||S r  r  r  s      rU   rI  z6set_moa_models.<locals>._slot_dict.<locals>.<dictcomp>q  s    JJJTQAMAqMMMrW   )r  r  )r  s    rU   
_slot_dictz"set_moa_models.<locals>._slot_dicto  s*    JJTYY[[%6%6%8%8JJJJrW   presetc           	          fd| j         D              | j                  | j        | j        | j        | j        | j        | j        dS )Nc                 &    g | ]} |          S r  r  )r   r  r  s     rU   r  z8set_moa_models.<locals>._preset_dict.<locals>.<listcomp>u  s#    $Z$Z$Z$ZZ%5%5$Z$Z$ZrW   r  r  r  r  r  r  r  r  r   )r  r  s    rU   _preset_dictz$set_moa_models.<locals>._preset_dicts  s^    $Z$Z$Z$Z&BY$Z$Z$Z(j):;;)/)E*0*G$/(.(C -!>	 	 	rW   c                 .    i | ]\  }}| |          S r  r  )r   rR   r  r!  s      rU   rI  z"set_moa_models.<locals>.<dictcomp>  s)    ddd|tVll6&:&:dddrW   )r  r  r  r     zInvalid MoA config: ; r   r  rs  TNzPUT /api/model/moa failedr!  zFailed to save MoA config)r  r  r  r  r  r  ru  r  r   r  r  r  r  r  r  r  r  r  r  r  r  r5   r  r   rZ   rP   r"  )
r0  r  r  r  r  r  problemsr  r!  r  s
           @@rU   set_moa_modelsr&  i  sr   >QTTTTTTTT	K\ 	Kd 	K 	K 	K 	K
	!1 
	d 
	 
	 
	 
	 
	 
	 DL3G44 &	. &	.--C| &*&9%)%7ddddt|OaOaOcOcddd  #l$)-)>#'?.2.H/3/J#'?-1-F#{ $	 	 	 & ,+C00H # #1DIIh4G4GG   
 .-c22J#CJ$-*-M&	. &	. &	. &	. &	. &	. &	. &	. &	. &	. &	. &	. &	. &	. &	. &	. &	. &	.N     Q Q Q23334OPPPPQs2   AE C*D?2E ?EE EE A Fz/api/model/setc                 B   	
K    j         pd                                                                	 j        pd                                 j        pd                                 j        pd                                                                
 j        pd                                 j        pd                                	dvrt          dd          	 rO j	        sH	 ddl
m} t          j        |           d	{V }n# t          $ r d	}Y nw xY w|d
	d|j        dS  	
fd}t          j        |           d	{V S # t          $ r  t          $ r, t                               d           t          dd          w xY w)u!  Assign a model to the main slot or an auxiliary task slot.

    Writes to ``~/.hermes/config.yaml`` — applies to **new** sessions only.
    The currently running chat PTY (if any) is not affected; use the
    ``/model`` slash command inside a chat to hot-swap that specific session.
    r   >   r  rq  r   z#scope must be 'main' or 'auxiliary'r   r   )expensive_model_warning)rT   r  NFT)rs  r  rT   r  confirm_requiredconfirm_messagec            	          t          j        p          5  t                     cd d d            S # 1 swxY w Y   d S r  )ru  r  _apply_model_assignment_sync)r  r  r0  r  r  rT   r  r  s   rU   _apply_assignmentz/set_model_assignment.<locals>._apply_assignment  s     788  38UD(G                  s   9= =zPOST /api/model/set failedr!  zFailed to save model assignment)r  r   r   rT   r  r  r  r  r5   r  hermes_cli.model_cost_guardr(  rm   rd  rZ   r   rP   r"  )r0  r  r(  r3  r-  r  r  r  rT   r  r  s   ``   @@@@@@rU   set_model_assignmentr/    sY      Z2$$&&,,..E#**,,HZ2$$&&EIO""$$**,,D#**,,H|!r((**G)))4YZZZZ(W
  	5 	OOOOOO !( 1+%%	! ! !          "" ("(,'.  	 	 	 	 	 	 	 	 	 	 	 	 &'8999999999    W W W34444UVVVVWs7   '	E 1$D E D%"E $D%%E 8%E A Fr  c                 	   t                      }| dk    r|r|st          dd          t          ||          \  }}t          |                    di           ||||          }||d<   g }|                                                                dk    re	 ddlm}	 dd	l	m
}
  |
|d
d          } |	||d          }t          |          }n,# t          $ r t                              dd           Y nw xY wt          |           |                                                                dv rP|rN	 ddlm}m}  |||| ||                     n,# t          $ r t                              dd           Y nw xY w|                                                                }g }|                    di           }t'          |t(                    rt*          D ]}|                    |          }t'          |t(                    s-t-          |                    dd          pd                                          }|rj|                                dvrT|                                |k    r<|                    ||t-          |                    dd          pd          d           dd|||                    dd          ||dS |                    d          }t'          |t(                    si }|dk    rt*          D ]b}|                    |          }t'          |t(                    si }d|d<   d|d<   |                    dd           t3          |           |||<   c||d<   t          |           ddddS |st          dd           |r|gnt5          t*                    }|D ]}|t*          vrt          dd!|           |                    |          }t'          |t(                    si }t-          |                    d          pd                                                                          }|                                                                }||d<   ||d<   ||k    r+|d"k    r%|                    dd           t3          |           |||<   ||d<   t          |           dd|||d#S )$u  Synchronous body of POST /api/model/set.

    Runs inside ``_profile_scope`` (in a worker thread) so every
    load_config/save_config lands in the requested profile.  Raises
    HTTPException for validation errors — the async wrapper re-raises them.
    r  r   z$provider and model required for mainr   r  r  r   )apply_nous_managed_defaults)_get_platform_toolscliFinclude_default_mcp_serversT)enabled_toolsetsr  z#apply_nous_managed_defaults skippedr  >   r!  rA  )_auto_provider_name_save_custom_providerr  z%custom_providers registration skippedrq  rT   r   >   r   rH  )r  rT   r  r  )rs  r  rT   r  r  gateway_tools	stale_aux	__reset__rH  N)rs  r  resetzprovider required for auxiliaryzunknown auxiliary task: rA  )rs  r  r  rT   r  )r   r5   r  r  r   r   r   rG  r1  hermes_cli.tools_configr2  r~  rZ   rP   r  r   hermes_cli.mainr7  r8  rv  r  r  r  r   r  r   r  )r  rT   r  r  r  r  r  r  r9  r1  r2  r  changedr7  r8  r  r:  r  r  r  slot_providerauxrv  r  s                           rU   r,  r,    s    --C 	`u 	`C8^____:8UKK%0GGGR  (E8W
 
	 !G $&>>!!##v--QTTTTTTGGGGGG--E   65%, $  
 !'w Q Q Q 

@4
PPPPPQ
 	C >>!!##':::x:SVVVVVVVV%%,,X66	      S S S 

BT
RRRRRS  ~~''--// "	''+r**gt$$ 	'  ";;t,,!(D11  #HLLR$@$@$FB G G M M O O!	%++--\AA%++--==$$ $$1!$X\\'2%>%>%D"!E!E& &     !j"55*"
 
 	
 ''+

Cc4   {# 	! 	!Dwwt}}Hh-- #)HZ  "HWLLT***,X666 CIIKC[4@@@ W4UVVVV7tff$"7"7G  &&C8YSW8Y8YZZZZ774==(D)) 	HHLL44:;;AACCIIKK~~''--//'!=((\X-E-ELLT***,X666D		C  s$   7C &C87C85 E &E?>E?r  r  c                 <   | pd                                 }|sd|fS 	 ddlm}m}m} n# t
          $ r d|fcY S w xY w	  |||          }n# t
          $ r d}Y nw xY w|r|d         |d         fS d|v r'	  ||          |v }n# t
          $ r d}Y nw xY w|sd|fS d|fS )	u  Infer which provider serves ``model_val`` when the flat Config-page Model
    field changes, given the previously-saved ``prev_provider``.

    Returns ``(provider, model)``; ``provider`` is empty when no switch is
    warranted (leave the existing provider untouched). Two signals, in order:

    1. Curated-catalog detection (``detect_provider_for_model``) — handles the
       ~28 OpenRouter-curated models and direct provider-static catalogs.
    2. Vendor-slug heuristic — a ``vendor/model`` slug cannot belong to a
       single-model / non-aggregator provider (e.g. ``ollama-local``). When the
       current provider is not an aggregator that serves vendor-prefixed slugs,
       route to an aggregator. ``_normalize_main_model_assignment`` (called by
       the caller) keeps the user's current aggregator when they're already on
       one, else falls back to openrouter — the same chokepoint logic as
       ``POST /api/model/set``.
    r   r   )r  detect_provider_for_modelr  Nr   r   Fr  )r   r   r  rC  r  rZ   )r  r  rR   r  rC  r  detectedcur_is_aggregators           rU   _infer_provider_on_model_changerF    sU   " O""$$D 4x	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	

    4x,,T=AA    ({HQK''
 d{{	& 2 2= A AEZ Z 	& 	& 	& %	&  	&%%t8Os/   
) ::A AA4B BBc                 8   t          |           } |                     dd           |                     dd          }t          |t                    s*	 t          |          }n# t          t
          f$ r d}Y nw xY w|                     d          }t          |t                    rt|rq	 t                      }|                    d          }t          |t                     rt          |                    d          pd          	                                }t          |                    d          pd          	                                }||k    ry|rwt          ||          \  }}|rb|	                                                                |                                k    r&t          ||          \  }	}
t          ||	|
          }|
}||d<   |dk    r||d	<   n|                    d	d           || d<   n|dk    r||d
| d<   n# t          $ r Y nw xY w| S )u9  Reverse _normalize_config_for_web before saving.

    Reconstructs ``model`` as a dict by reading the current on-disk config
    to recover model subkeys (provider, base_url, api_mode, etc.) that were
    stripped from the GET response.  The frontend only sees model as a flat
    string; the rest is preserved transparently.

    Also handles ``model_context_length`` — writes it back into the model dict
    as ``context_length``.  A value of 0 or absent means "auto-detect" (omitted
    from the dict so get_model_context_length() uses its normal resolution).
    _model_metaNr  r   r  r1  r   rT   r  )r1  r  )r  r  rv  rw  rl  r  r   r  r   r   rF  r   r  r  rZ   )r{  ctx_overrider  disk_config
disk_modelprev_defaultr  r  resolved_modelnorm_provider
norm_models              rU   _denormalize_config_from_webrP    sh    &\\F JJ}d### ::4a88LlC(( 	|,,LL:& 	 	 	LLL	 

7##I)S!! /i /-	%--K$11J*d++ (":>>)#<#<#BCCIIKK #JNN:$>$>$D" E E K K M M ,,,3R!=4 40L. $ /(:(:(<(<(B(B(D(DH[H[H]H](](]
 5U(.5 51z &B&z& &
 %/	(1
9%!##3?J/00NN#3T:::",w !!(&2# #w  	 	 	D	Ms%   A" "A87A8*EH
 

HHc                 p  K   	 t          | j        p|          5  t                      }t          | j                  }t          t          ||                     d d d            n# 1 swxY w Y   ddiS # t          $ r  t          $ r, t          
                    d           t          dd          w xY w)Nrs  TzPUT /api/config failedr!  r_  r   )ru  r  r   rP  r{  r   r&   r5   rZ   rP   r"  )r0  r  r  incomings       rU   update_configrS    s     MDL3G44 		9 		9 '((H3DK@@HHh77888		9 		9 		9 		9 		9 		9 		9 		9 		9 		9 		9 		9 		9 		9 		9 d|    M M M/0004KLLLLMs0   A5 A A&A5 &A**A5 -A*.A5 5A B5c                     	 ddl m}  n# t          $ r i cY S w xY w	 ddlm} n# t          $ r i }Y nw xY wd |                                D             }i } |             D ]}|j        dk    r|j        D ];}||v r|                    ||j	        |j
        |j        |j        pdddd	d
           <|j        r5|                    |j        |j	        |j
        |j
         ddddd	d
           |j        dk    rydD ]v}|                    |i           }|j	        |j
        |                    d          p|j
         d| d|                    d          d|                    dd          d	d
||<   w|j        dk    rq|                    di           }|j	        |j
        |                    d          p	|j
         d|                    d          d|                    dd          d	d
|d<   |S )u  Map provider env vars → desktop card metadata, derived from the catalog.

    Returns ``{env_var: {provider, provider_label, description, url, is_password,
    advanced}}`` for every API-key provider in the unified ``provider_catalog()``
    (i.e. the ``hermes model`` universe). This is what lets the desktop Keys tab
    render a card for a provider even when its env var was never hand-added to
    ``OPTIONAL_ENV_VARS`` — closing the drift where CLI-configurable providers
    (openai-api, kilocode, novita, tencent-tokenhub, copilot, …) were missing
    from the GUI.

    Hand ``OPTIONAL_ENV_VARS`` prose is layered ON TOP of this in the endpoint;
    this only supplies membership + grouping + sensible fallbacks.
    r   provider_catalog)r   c                 |    h | ]9\  }}|pi                      d           r|pi                      d           dk    7|:S )r  rT   r   r  s      rU   r  z1_catalog_provider_env_metadata.<locals>.<setcomp><  sY       aG==$$*+'rz)B)Bj)P)P 	
)P)P)PrW   keysNTFrT   )rT   provider_labelr  r   is_passwordadvancedr  z base URL overrideaws_sdk)
AWS_REGIONAWS_PROFILEr  r  r  r   r[  vertexVERTEX_CREDENTIALS_PATHu+    — service account JSON path (or use ADC))hermes_cli.provider_catalogrV  rZ   r  r   r  tabapi_key_env_varsrk  r  r?  r  
signup_urlbase_url_env_var	auth_typer   )rV  _OPT_non_provider_keysmetadr+  aws_varr  s           rU   _catalog_provider_env_metadatarl  !  s   @@@@@@@   			???????    jjll  
 D F F5F?? ) 	 	G,,,OO !&'g#$=</4#' % *      	OO" !&'g&'g#A#A#A#( $ *   $ ;)##8 
 
88GR00 !&'g#+<<#>#>#ZQWBZBZPWBZBZBZ#<<..#( (Z > > *! !W  ;(""xx 92>>HF"#''||M::  KgJJJ||E**$$LLT::&	/ 	/D*+ Ks   	 # 22z/api/envc           	        K   t          |           5  t                      d d d            n# 1 swxY w Y   t                      t                      dddt          dt
          dt          dt
          ffd}i }t          j                    D ]\  }} |||          ||<   D ]}||vr ||i           ||<   D ](}||v s|v r ||i d          }d|d	<   d|d
<   |||<   )|S )NF)rA  var_namerQ   rA  rJ   c                                        |           }                     |           pi }t          |          |rt          |          nd |                     d          p|                     dd          |                     d          |                     d          n|                     d          |                     d          p|                     dd          |                     d|                     dd                    |                     dg           |                     d	|                     d	d                    | v |                     d
d          |                     dd          |dS )Nr  r   r   r  passwordrZ  Ftoolsr[  rT   rY  )r_  redacted_valuer  r   r  rZ  rq  r[  channel_managedrT   rY  rA  )r   r   r$   )rn  rQ   rA  rr  cat_metacatalog_metachannel_keysrC  s        rU   _rowzget_env_vars.<locals>._row  sP   ))##H--3 5kk38Bj///d88M22Uhll=RT6U6U&*hhuoo&A488E???x||TYGZGZ,,LZ0L0L88J]E0R0RSSXXgr**X\\*e-L-LMM  (<7 !Z44&ll+;R@@ /
 
 	
rW   Tr  rZ  )	ru  r   _channel_managed_env_keysrl  r  r  r   r   r  )	r  rw  r  rn  rQ   r  ru  rv  rC  s	         @@@rU   get_env_varsry    s     		 	  ! !jj! ! ! ! ! ! ! ! ! ! ! ! ! ! !,..L133L:? 
 
 
s 
$ 
4 
D 
 
 
 
 
 
 
 
> F+133 0 0$4$//x ! 2 26!!#tHb11F8    v\!9!9d8R---"J!MxMs   044c                 v  K   	 t          | j        p|          5  t          | j        | j                   d d d            n# 1 swxY w Y   d| j        dS # t
          $ r$}t          dt          |                    |d }~wt          $ r, t          
                    d           t          dd          w xY w)NTrs  r  r   r   zPUT /api/env failedr!  r_  )ru  r  r   r  rr  r  r5   r  rZ   rP   r"  )r0  r  rN  s      rU   set_env_varr|    s     MDL3G44 	1 	148TZ000	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	148,,, G G G
 CHH===3F M M M,---4KLLLLMs9   A AA AA A	A 
B8 A??9B8)z https://openrouter.ai/api/v1/keybearer)z https://api.openai.com/v1/modelsr}  )zhttps://api.x.ai/v1/modelsr}  )z7https://generativelanguage.googleapis.com/v1beta/modelsr  )OPENROUTER_API_KEYOPENAI_API_KEYXAI_API_KEYGEMINI_API_KEY_CREDENTIAL_PROBESr/  c                    	 | j         sg S |                                 }n# t          $ r g cY S w xY wt          |t                    r|                    d          n|}t          |t                    sg S g }|D ]}t          |t                    r7t          |                    d          pd                                          }n#t          |pd                                          }|r|	                    |           |S )a1  Extract model ids from an OpenAI-compatible ``/v1/models`` response.

    Tolerant of the common shapes: ``{"data": [{"id": ...}]}`` (OpenAI / vLLM /
    llama.cpp) and a bare ``{"data": ["id", ...]}``. Returns ``[]`` on any
    parse/HTTP error so a slightly non-standard endpoint never hard-blocks.
    r  r  r   )

is_successr*  rZ   rv  r  r   r  r  r   r   )r/  rj  r  idsr  mids         rU   _parse_model_idsr    s    	I))++   			",Wd";";H7;;vDdD!! 	C  dD!! 	*dhhtnn*++1133CCdjb//''))C 	JJsOOOJs       //z/api/providers/validatec                   K   t          |           ddl}| j        pd                                }| j        pd                                }|sddddS |dk    r|                    d	          d
z   }| j        pd                                }|rdd| ind}	 |                    |                    d                    5 }|	                    ||          }	ddd           n# 1 swxY w Y   dddt          |	          dS # t          $ r ddd| ddcY S w xY wt          	                    |          }
|
sddddS |
\  }}ddi}i }|dk    r	d| |d<   n||d<   	 |                    |                    d                    5 }|	                    |||          }	ddd           n# 1 swxY w Y   n# t          $ r	 ddddcY S w xY w|	j        dv rddddS |	j        dk    s|	j        rddddS ddd|	j         ddS )a]  Live-probe a provider credential before it's saved.

    Returns {ok, reachable, message}. ok=True means the provider accepted the
    key; ok=False + reachable=True means the key is bad (caller should block);
    reachable=False means the network probe couldn't run (caller may save with
    a warning rather than hard-blocking offline users).
    r   Nr   FTzEnter a value first.)rs  	reachabler   OPENAI_BASE_URLr   z/modelsAuthorizationr   g       @r   r*  )rs  r  r   r  zCould not reach r~  r)  r(  r}  r        $@)r   paramsz/Could not reach the provider to verify the key.r.  z9That API key was rejected. Double-check it and try again.  zProvider returned HTTP z for this key.)r   httpxr  r   rr  r$  r  ClientTimeoutr   r  rZ   r  r   r  )r0  r   r  r  rr  r   r  r   r-  r/  prober   r  s                rU   validate_provider_credentialr    sY      7LLL8>r
 
 
"
"CZ2$$&&E S$;QRRR ll3)+ <%2,,..<CM?$7g$7$788	[emmC&8&899 8Vzz#wz778 8 8 8 8 8 8 8 8 8 8 8 8 8 8TbL\]aLbLbccc 	[ 	[ 	[e@YSV@Y@Y@YZZZZZ	[ ""3''E ?2>>>IC+,GFx#4U#4#4  uo\\%--"5"5\66 	C&::c76:BBD	C 	C 	C 	C 	C 	C 	C 	C 	C 	C 	C 	C 	C 	C 	C o o o%<mnnnnno :%%$;vwww3$/"===d7qQUQa7q7q7qrrrsl   )D C&D &C**D -C*.D DD!)F; 
F/#F; /F33F; 6F37F; ;GGc                   K   	 t          | j        p|          5  t          | j                  }d d d            n# 1 swxY w Y   |st	          d| j         d          d| j        dS # t          $ r  t
          $ r$}t	          dt          |                    |d }~wt          $ r, t          	                    d           t	          dd	          w xY w)
Nr    not found in .envr   Tr{  r   zDELETE /api/env failedr!  r_  )
ru  r  r   r  r5   r  r  rZ   rP   r"  )r0  r  removedrN  s       rU   remove_env_varr  B  sM     MDL3G44 	1 	1&tx00G	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	YC488W8W8WXXXX48,,,    G G G CHH===3F M M M/0004KLLLLMs4   A+ ;A+ ?A+ ?'A+ +C?B9Cz/api/env/revealc                 `  K   t          |           t          j                    }|t          z
  fdt          D             t          dd<   t	          t                    t
          k    rt          dd          t                              |           t          | j	        p|          5  t                      }ddd           n# 1 swxY w Y   |                    | j                  }|t          d| j         d          t                              d| j                   | j        |d	S )
zReturn the real (unredacted) value of a single env var.

    Protected by:
    - Ephemeral session token (generated per server start, injected into SPA)
    - Rate limiting (max 5 reveals per 30s window)
    - Audit logging
    c                      g | ]
}|k    |S r  r  )r   tcutoffs     rU   r  z"reveal_env_var.<locals>.<listcomp>g  s    III1a&jjQjjjrW   Nr  z,Too many reveal requests. Try again shortly.r   r   r  zenv/reveal: %s)r  rr  )r   rC  _REVEAL_WINDOW_SECONDSr   r   _REVEAL_MAX_PER_WINDOWr5   r   ru  r  r   r   r  rP   rQ   )r0  r   r  r?  rC  rr  r  s         @rU   reveal_env_varr  V  sb      7 )++C))FIIII(:IIIqqq
"8884bccccc""" 
/	0	0 ! !jj! ! ! ! ! ! ! ! ! ! ! ! ! ! !OODH%%E}tx4S4S4STTTTII)))8e,,,s   +CC
C
Telegramz1Run Hermes from Telegram DMs, groups, and topics.z1https://core.telegram.org/bots/features#botfather)r  r  TELEGRAM_PROXY)r  )rR   r  docs_urlenv_varsr  Discordz5Connect Hermes to Discord DMs, channels, and threads.z+https://discord.com/developers/applications)DISCORD_BOT_TOKENDISCORD_ALLOWED_USERSDISCORD_REPLY_TO_MODE)r  r  SlackzbUse Hermes from Slack via Socket Mode. Add allowed Slack member IDs so connected bots can respond.zhttps://api.slack.com/apps)r  r  r  )r  r  
mattermost
Mattermostz:Connect Hermes to Mattermost channels and direct messages.zhttps://mattermost.com/deploy/)MATTERMOST_URLMATTERMOST_TOKENMATTERMOST_ALLOWED_USERS)r  r  matrixMatrixz/Use Hermes in Matrix rooms and direct messages.z%https://matrix.org/ecosystem/servers/)MATRIX_HOMESERVERMATRIX_ACCESS_TOKENMATRIX_USER_IDMATRIX_ALLOWED_USERS)r  r  r  signalSignalz)Connect through a signal-cli REST bridge.z0https://github.com/bbernhard/signal-cli-rest-api)SIGNAL_HTTP_URLSIGNAL_ACCOUNTSIGNAL_ALLOWED_USERS)r  r  whatsappWhatsAppzBUse Hermes through the bundled WhatsApp bridge with QR-based auth.z"https://github.com/tulir/whatsmeow)WHATSAPP_ENABLEDWHATSAPP_MODEWHATSAPP_DM_POLICYWHATSAPP_ALLOWED_USERSr  homeassistantzHome Assistantz7Control your smart home from Hermes via Home Assistant.z2https://www.home-assistant.io/docs/authentication/)HASS_URL
HASS_TOKENemailEmailz,Talk to Hermes through an IMAP/SMTP mailbox.z@https://hermes-agent.nousresearch.com/docs/user-guide/messaging/)EMAIL_ADDRESSEMAIL_PASSWORDEMAIL_IMAP_HOSTEMAIL_SMTP_HOSTr[  zSMS (Twilio)z*Send and receive text messages via Twilio.zhttps://www.twilio.com/console)TWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKENdingtalkDingTalku+   Connect Hermes to DingTalk groups (钉钉).zGhttps://open.dingtalk.com/document/orgapp/the-robot-development-process)DINGTALK_CLIENT_IDDINGTALK_CLIENT_SECRETrX  zFeishu / Larkz Use Hermes inside Feishu / Lark.zKhttps://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/intro)FEISHU_APP_IDFEISHU_APP_SECRETFEISHU_ENCRYPT_KEYFEISHU_VERIFICATION_TOKEN)r  r  google_chatzGoogle Chatz0Connect Hermes to Google Chat via Cloud Pub/Sub.zKhttps://hermes-agent.nousresearch.com/docs/user-guide/messaging/google_chat)rR   r  r  wecomzWeCom (group bot)z&Send-only WeCom group bot via webhook.z8https://developer.work.weixin.qq.com/document/path/91770)WECOM_BOT_IDWECOM_SECRET)r  rY  zWeCom (app)z+Two-way WeCom integration via callback app.z8https://developer.work.weixin.qq.com/document/path/90930)WECOM_CALLBACK_CORP_IDWECOM_CALLBACK_CORP_SECRETWECOM_CALLBACK_AGENT_IDWECOM_CALLBACK_TOKENWECOM_CALLBACK_ENCODING_AES_KEY)r  r  r  weixinzWeixin / WeChat (Personal)zBConnect a personal WeChat account through Tencent's iLink Bot API.zGhttps://hermes-agent.nousresearch.com/docs/user-guide/messaging/weixin/)WEIXIN_ACCOUNT_IDWEIXIN_TOKENWEIXIN_BASE_URL)r  r  rZ  zBlueBubbles (iMessage)z5Use Hermes through iMessage via a BlueBubbles server.zhttps://bluebubbles.app/)BLUEBUBBLES_SERVER_URLBLUEBUBBLES_PASSWORDBLUEBUBBLES_ALLOWED_USERS)r  r  zQQ Botz5Connect Hermes to a QQ Bot from the QQ Open Platform.zhttps://q.qq.com)	QQ_APP_IDQQ_CLIENT_SECRETQQ_ALLOWED_USERS)r  r  r  zEhttps://hermes-agent.nousresearch.com/docs/user-guide/messaging/teamsu   Yuanbao (元宝)z"Connect Hermes to Tencent Yuanbao.)rR   r  r  r  z
API serverzIExpose Hermes as an OpenAI-compatible HTTP API for tools like Open WebUI.)API_SERVER_ENABLEDAPI_SERVER_KEYAPI_SERVER_PORTAPI_SERVER_HOSTAPI_SERVER_MODEL_NAMEWebhooksz>Receive events from GitHub, GitLab, and other webhook sources.zIhttps://hermes-agent.nousresearch.com/docs/user-guide/messaging/webhooks/)WEBHOOK_ENABLEDWEBHOOK_PORTWEBHOOK_SECRET)qqbotteamsyuanbaorV  rU  _PLATFORM_OVERRIDES)rd  re  r  r  r  r  r  rZ  r  r  r[  r  rX  r  r  rY  r  r  r  rV  rU  _PLATFORM_ORDERr  z8signal-cli REST API base URL, e.g. http://127.0.0.1:8080zSignal bridge URL)r  rD   r   r  z6Signal account phone number registered with the bridgezSignal account)r  rD   r  z3Comma-separated Signal users allowed to use the botzAllowed Signal usersr  z#Enable the WhatsApp gateway adapterzEnable WhatsApp)r  rD   r[  r  zWhatsApp bridge modezWhatsApp moder  z+How WhatsApp direct messages are authorizedzWhatsApp DM policyr  z5Comma-separated WhatsApp users allowed to use the botzAllowed WhatsApp usersr  z>Home Assistant base URL, e.g. https://homeassistant.local:8123zHome Assistant URLr  uB   Long-lived access token from Home Assistant (Profile → Security)zHome Assistant access token)r  rD   rp  r  z&Email address to send and receive fromzEmail addressr  z&Email account password or app passwordzEmail passwordr  z&IMAP server host (e.g. imap.gmail.com)z	IMAP hostr  z&SMTP server host (e.g. smtp.gmail.com)z	SMTP hostr  zTwilio Account SIDr  zTwilio Auth Tokenr  zWeCom group bot IDzWeCom Bot IDr  zWeCom group bot secretzWeCom SecretzWeCom corp IDzWeCom Corp IDzWeCom app corp secretzWeCom Corp SecretzWeCom app agent IDzWeCom Agent IDz!WeCom callback verification tokenzWeCom TokenzWeCom callback AES encoding keyzWeCom AES KeyzFiLink Bot account ID obtained through QR login in hermes gateway setupziLink Bot account IDzAiLink Bot token obtained through QR login in hermes gateway setupziLink Bot tokenzMiLink API base URL saved by QR login (default: https://ilinkai.weixin.qq.com)ziLink API base URLzFeishu / Lark app IDzApp IDzFeishu / Lark app secretz
App secretzFeishu / Lark encrypt keyzEncrypt keyz Feishu / Lark verification tokenzVerification tokenzDingTalk client ID (App key)z	Client IDz#DingTalk client secret (App secret)zClient secret)r  r  r  r  r  r  r  r  r  r  r  r  r  r  _MESSAGING_ENV_FALLBACKSc                     ddl m}  t                      }g }| j                                        D ]Y}|j        dk    r|j        |v r|                    |j                   |                    t          |j                             Z	 ddl	m
} |                                D ]N}|j        |v r|                    |j                   |                    t          |j        |                     On,# t          $ r t                              dd           Y nw xY wd t!          t"                    D             |                    fd	
           t'          |          S )a  Build the messaging catalog from the gateway's Platform enum + plugin registry.

    Built-in platforms come from ``gateway.config.Platform`` (LOCAL is excluded).
    Plugin platforms come from ``gateway.platform_registry.plugin_entries()``,
    which lets newly installed adapters (e.g. IRC) appear without a code change
    here. Per-platform UI metadata (description, docs URL, env-var picks) lives
    in :data:`_PLATFORM_OVERRIDES`; anything not overridden gets reasonable
    defaults derived from the platform id and required_env.
    r   )Platformr!  )platform_registryz$plugin platform registry unavailableTr  c                     i | ]\  }}||	S r  r  )r   idxr  s      rU   rI  z/_messaging_platform_catalog.<locals>.<dictcomp>  s    AAA(#sS#AAArW   c                                          | d         t          t                              | d                                         fS )Nr  rR   )r   r   r  r   )erK  s    rU   r  z-_messaging_platform_catalog.<locals>.<lambda>  s4    uyy4#o*>*>??6ARARS rW   r  )r  r  r~   __members__r  rr  r  r   _build_catalog_entrygateway.platform_registryr  plugin_entriesrR   rZ   rP   r  	enumerater  r  r  )r  r  r  memberr  plugin_entryrK  s         @rU   _messaging_platform_catalogr	    s    ('''''UUD$&G&--// ; ;<7""<4+FL99::::	J??????-<<>> 	R 	RL D((HH\&'''NN/0A<PPQQQQ		R
  J J J

9D
IIIIIJ BAi&@&@AAAELLSSSS     >>s   A)C6 6&DDc                  &   	 t                      } t                      D ]+}|                     |                    dd                     ,t	          |           S # t
          $ r- t                              dd           t	                      cY S w xY w)a  Env-var keys owned by a Channels page platform card.

    The Channels page is the canonical surface for configuring messaging
    platform credentials (with connection status, test, enable toggle and
    gateway restart). The Keys/Env page consults this set to hide those vars
    so the same fields aren't duplicated in a plainer UI. Best-effort: if the
    gateway catalog can't be built, nothing is flagged and Keys shows it all.
    r  r  z+could not build channel-managed env key setTr  )r~   r	  r  r   	frozensetrZ   rP   r  )rX  r  s     rU   rx  rx    s    022 	3 	3EKK		*b112222   

@4
PPP{{s   AA 4BB>   GATEWAY_PROXY_KEYGATEWAY_PROXY_URLGATEWAY_ALLOW_ALL_USERSc                     ddddddd}| |v r||          S |                                                      dd	          d	z   fS )
z4Env-var prefixes owned by a messaging platform card.)EMAIL_)HASS_)QQ_QQBOT_)TWILIO_)
WECOM_BOT_r  )WECOM_CALLBACK_)r  r  r  r[  r  rY  r  r  )upperr  )r  r  s     rU   _platform_env_prefixesr	  1  sd     #"/.+ +G g{##''S11C799rW   c                 T   t          |           }g }t          j                    D ]Z\  }|                    d          dk    rt          v r)t          fd|D                       sE|                               [t          t          t          |                              S )zLAll messaging-category env vars for a platform (override + plugin + prefix).r  	messagingc              3   B   K   | ]}                     |          V  d S r  r  )r   r|  rR   s     rU   r   z._discover_platform_env_vars.<locals>.<genexpr>I  s/      BBv4??6**BBBBBBrW   )
r	  r   r  r   _MESSAGING_KEYS_PAGE_KEYSr  r   r  r~  r~   )r  prefixesrX  rQ   rR   s       @rU   _discover_platform_env_varsr	  @  s    %k22HD'-//  
d88J;..,,,BBBBBBBBB 	DD		""###rW   overrider 	  c                    t          |           }d|v r2t          t                              g |d         |R                     S |E|j        r>t          t                              g t          |j                  |R                     S |S )z5Canonical env-var list for a messaging platform card.r  )r	  r  r  r  r  )r  r	  r 	  r  s       rU   _merge_platform_env_varsr	  O  s     -[99JXT]]#GXj%9#GJ#G#GHHIIIL$=T]]#SU<+D%E%E#S
#S#STTUUUrW   c                    t                               | i           }t          | ||          }d|v rt          |d                   }n|t          |j        pd          }nd}|                    d          r	|d         }n9||j        r|j        }n(|                     dd                                          }|                    d          }|s|	|j        pd}| ||pd|                    dd          ||d	S )
Nr  r  rR   r  r  r  r   r  )r  rR   r  r  r  r  )	r  r   r	  r  r  r?  r  r   install_hint)r  r 	  r	  r  r  rR   r  s          rU   r  r  ]  s%    #&&{B77H'X|LLH!!Xn566		!\6<"==||F 5		!l&8	!!""3,,2244,,}--K 6<3"/52 "(bLLR00$  rW   c                 H    t                      D ]}|d         | k    r|c S d S )Nr  )r	  )r  r  s     rU   _catalog_lookupr	    s9    ,..  ;+%%LLL &4rW   c           	      b   t          j        |           pt                              |           pi }|                    dd          |                    d|           |                    dd          |                    d          |                    dd          |                    dd          d	S )
Nr  r   rD   helpr   rp  Fr[  )r  rD   r	  r   rZ  r[  )r   r   r  )r  rQ   s     rU   _messaging_env_infor 	    s     %%P)A)E)Ec)J)JPbDxxr22((8S))$$xxxx
E22HHZ//  rW   c                 z    ddl m}m}  |            } ||           }|j                            |          }|||fS )Nr   )r  r  )r  r  r  re  r   )r  r  r  r{  r  platform_configs         rU   _gateway_platform_configr#	    sZ    <<<<<<<<  ""Fx$$H&**844O8_,,rW   r  rC  scopedc                  	   | d         }|r|                     d          ni }t          |t                    r|                     |i           ni }t                      d upt	          |          d u}g }| d         D ]}	                     |	          p|rdnt          j        |	d          }
|                    |	|	| d         v t          |
          |
rt          |
          nd dt          |	                     |r	 t                      }|                     d          pi }|                     |          }t          |t                    si }t          |                     d                    }|                     d          }t          |t                    r|nd }n# t          $ r d	}d }Y nw xY wt          fd
| d         D                       }n	 t          |          \  }}}t          |o|j                  }t          |o|                    ||                    }|r |j        r|j                                        nd }n5# t          $ r( d	}t          fd| d         D                       }d }Y nw xY wt          |t                    r|                     d          nd }t          |t                    r|                     d          nd }t          |t                    r|                     d          nd }|sd}n|sd}n|r|sd}n|s|s	|dk    rd}n|s|sd}t          |t                    r|                     d          nd }t          |t                    r|                     d          nd }|dk    r|pd}|p|}d }|dk    r                     d          p|rdnt          j        dd                                          }                     d          p|rdnt          j        dd                                          }|dv r|ndt          |          t          |          d}|| d         | d         | d         ||||||t          |t                    r|                     d          nd ||d}|||d <   |S )!Nr  re  r  r   r  )r  r)  r_  rr  r  home_channelFc              3   B   K   | ]}                     |          V  d S r  r   r   r  rC  s     rU   r   z._messaging_platform_payload.<locals>.<genexpr>  s/      OO#--OOOOOOrW   c              3   l   K   | ].}                     |          pt          j        |d           V  /dS r   N)r   rt   ru   r(	  s     rU   r   z._messaging_platform_payload.<locals>.<genexpr>  sT         $$:	#r(:(:     rW   rk   r  r  disablednot_configuredpending_restartr  gateway_stopped
error_codeerror_messager  r  r     r  	self-chat)r  allowed_users_sethome_channel_setrR   r  r  r  )r  rR   r  r  r  r}  r  rk   r/	  r0	  r  r&	  r  whatsapp_setup)r   rv  r  r*   r+   rt   ru   r   r   r$   r 	  r   rZ   rf  r#	  r  _is_platform_connectedr&	  to_dictr   )r  rC  rc  r$	  r  runtime_platformsruntime_platformr  r  r  rr  r  platforms_cfgplat_cfgr  hcr&	  r}  r  r  r"	  rk   runtime_gateway_stateruntime_gateway_errorr/	  r0	  r5	  whatsapp_modeallowed_users_valuerj  s    `                            rU   _messaging_platform_payloadrA	    s    +K4;CK000 '..	k2...  	% 	?)'22$>  HZ  
 

 $$Nv)M29S";M;M5#88u++7<"F*U"3"3"3$	 
 &c**	
 	
 	
 	
  ' 
	 --CGGK006BM$((55Hh-- 8<<	2233Gn--B!+B!5!5?224LL 	  	  	 GLLL	  OOOO~9NOOOOO

	 8P9 95NHo ?F/FGGG U"99(OTT J #'6'C,44666 L
  	  	  	 G     0    J  LLL	  *44Dd)K)KUW%%%QU 
 =GwPT<U<U_GKK888[_:DWd:S:S]GKK666Y] " " 	 	" 	"!"" "%555  "U "! &--	\***  &--	_--- 
    3#3
%>)>Nj  OOO,, B@")OR"@"@
%'' 	
 OO455 KI"),Db"I"I
%'' 	
 &36J%J%JMMPR!%&9!:!: $\ 2 2
 
 f]+*% * & *D11  ...$# G& !$2 !Ns&   >B!F   F10F1A2I
 
/I<;I<r  c                 (    t          | d|           d S )Nr  )r%   )r  r  s     rU   _write_platform_enabledrC	  2  s    Y@@@@@rW   X  >   r  expired	cancelled	connectedc                       e Zd ZU ej        dz  ed<   eed<   eed<   eed<   eed<   eed<   dZedz  ed<   d	Z	eed
<   dZ
edz  ed<   dZedz  ed<   dZedz  ed<   dZedz  ed<   dZedz  ed<   dS )_WhatsAppOnboardingSessionNr  r  r  session_path
expires_atexpires_at_tsr  startingr)  
qr_payload
account_idaccount_nameaccount_phoner  )r  r  r  r  r  r  r  rx  r  r)  rN	  rO	  rP	  rQ	  r  r  rW   rU   rI	  rI	  :  s         

T
!!!!
IIIOOOGS4ZFC!Jd
!!!!Jd
!!!#L#*### $M3:$$$E3:rW   rI	  _whatsapp_onboarding_sessionsr}  c                     t          j        | t          j                                                                      dd          S )N+00:00Z)r   fromtimestampr   utc	isoformatr  )r}  s    rU   _utc_iso_from_tsrY	  O  s4    !"hl33==??GGRUVVVrW   c                     t          | pd                                                                          }|dvrt          dd          |S )Nr  r1	  r   z+WhatsApp mode must be 'bot' or 'self-chat'.r   )r  r   r   r5   )rr  r  s     rU   #_normalize_whatsapp_onboarding_moder[	  S  sP    u~$$&&,,..D'''4abbbbKrW   c                     t          | pd                                          }|sdS d                    d |                    d          D                       S )Nr   rQ  c              3   j   K   | ].}|                                 |                    d d          V  /dS )r  r   N)r   r  r  s     rU   r   z4_normalize_whatsapp_allowed_users.<locals>.<genexpr>^  s=      UUd

UDLLb))UUUUUUrW   )r  r   r  r   )rr  r  s     rU   !_normalize_whatsapp_allowed_usersr^	  Z  sW    
ekr


 
 
"
"C r88UUciinnUUUUUUrW   c                  &    ddl m}   | dd          S )Nr   get_hermes_dirzplatforms/whatsapp/sessionzwhatsapp/session)r4  ra	  r`	  s    rU   _whatsapp_session_pathrb	  a  s(    //////>68JKKKrW   c                     t          | pd                                          }|sd S |                    dd          d                             dd          d         }t          j        dd|          }|pd S )Nr   @r   r   r   z\D+)r  r   r   r_  r`  )rr  r  r  digitss       rU   _whatsapp_phone_from_identifierrf	  g  sv    
ekr


 
 
"
"C t		#q!!!$**32215IVFB	**F>TrW   rJ	  c                 `   | dz  }	 t          j        |                    d                    }n# t          $ r Y dS w xY wd d dt          dd ffd} ||                    d                      ||                    d	                      ||           t                    fS )
N
creds.jsonr  r  )NNNr  rJ   c                 H   t          | t                    sd S AdD ]>}t          |                     |          pd                                          }|r| n?BdD ]A}t          |                     |          pd                                          }|r| d S @d S d S )N)r  jidlidr   )rR   verifiedNamenotifypushName)rv  r  r  r   r   )r  r  rr  rO	  rP	  s      rU   collectz6_whatsapp_linked_account_from_session.<locals>.collectz  s    )T** 	F+  IMM#..4"55;;== !&JE E  IMM#..4"55;;== #(LEE   rW   meaccount)r*  r+  r4  rZ   r   r   rf	  )rJ	  
creds_pathrj  ro	  rO	  rP	  s       @@rU   %_whatsapp_linked_account_from_sessionrs	  p  s    ,J *Z1171CCDD        "J#L3 4       " GGKKGGKK	""###GG|%DZ%P%PPPs   (2 
A A 
bridge_dirc           
         | dz                                   rdS ddlm}m} ddlm}  |d          }|st          dd	           |d
d          }	 t          j        |ddgt          |           dd| |            t                                }nK# t          j        $ r}t          dd	          |d}~wt          $ r}t          dd| 	          |d}~ww xY w|j        dk    ri|j        p|j        pd                                }|r/d                    |                                dd                   }t          dd|pd 	          dS )zDInstall bridge dependencies when the dashboard is the setup surface.ro  Nr   find_node_executablewith_hermes_node_path)env_intnpmr!  z8npm was not found. WhatsApp setup needs Node.js and npm.r   WHATSAPP_NPM_INSTALL_TIMEOUTr=  r  z--silentT)r  r  r  r!  r  r  z2Installing WhatsApp bridge dependencies timed out.z0Failed to install WhatsApp bridge dependencies: r   r  iz(npm install failed for WhatsApp bridge: z	no output)rA  r4  rw	  rx	  r  ry	  r5   r  r  r  r
   TimeoutExpiredr  r  r  r  r   r  r  )	rt	  rw	  rx	  ry	  rz	  r!  r  rN  r   s	            rU   $_ensure_whatsapp_bridge_dependenciesr}	    s   ^#++-- LLLLLLLL

u
%
%C 
M
 
 
 	

 g4c::G)Z(J%%'',..
 
 
 $   G
 
 
 	    KcKK
 
 
 	 A-66=6B==?? 	:YYv0022344899FUf>SUU
 
 
 	
	 s$   ?B C"B44CCCr  c                    ddl m} ddlm}m}  |            }|dz  }|                                st          dd| d           |d	          }|st          dd
          t          |           |                     dd            |            }||d<   d|d<   t          j
        |t          |          dddt          |           gt          |          t          j        t          j        dddd|t                      
  
        S )Nr   )resolve_whatsapp_bridge_dirrv	  z	bridge.jsr!  z(WhatsApp bridge script was not found at r~  r   r  z4Node.js was not found. WhatsApp setup needs Node.js.Tr  r  r|  r  z--pair-onlyz--pair-jsonz	--sessionr  r  )	r  r  r  r  r  r  r  r  r  )!gateway.platforms.whatsapp_commonr	  r4  rw	  rx	  rA  r5   r}	  r"  r  r  r  PIPEr  r
   )	rJ	  r  r	  rw	  rx	  rt	  bridge_scriptr  r  s	            rU   _spawn_whatsapp_pairing_processr	    sr   MMMMMMLLLLLLLL,,..J,M!! 
NmNNN
 
 
 	
  ''D 
I
 
 
 	

 )444td333


!
!CC )C	
 
OO (**#   rW   r  c                     | d S |                                  d S 	 |                                  |                     d           d S # t          $ r+ 	 |                                  Y d S # t          $ r Y Y d S w xY ww xY w)Nr   r   )r  	terminater?  rZ   kill)r  s    rU   _terminate_whatsapp_pairingr	    s    |yy{{		!	   	IIKKKKKK 	 	 	DDD	s)   *A 
A=A**
A94A=8A99A=
pairing_idc                    	 |j         }|G|D ]C}|                                }|s	 t          j        |          }n# t          j        $ r Y Aw xY wt          |                    d          pd                                          }t          5  t                              |           }|r	|j	        |ur	 d d d             d S |dk    rOt          |                    d          pd                                          }|r||_
        d|_        d |_        n |dk    r|                    d          }	t          |	t                    rt          |	                    d          pd                                          }
t          |	                    d          pd                                          }|
pd |_        |pd |_        t#          |
          |_        d|_        d |_        nO|d	k    r1d	|_        t          |                    d	          pd
          |_        n|dk    r|j        dk    rd|_        d d d            n# 1 swxY w Y   E|                                }n# t(          $ rx}t          5  t                              |           }|r2|j	        |u r)|j        t*          vrd	|_        t          |          |_        d d d            n# 1 swxY w Y   Y d }~d S d }~ww xY wt          5  t                              |           }|r	|j	        |ur	 d d d            d S |j        dv r	 d d d            d S d	|_        |dk    rdnd| d|_        d d d            d S # 1 swxY w Y   d S )Neventr   qrwaitingrG	  r   r  rR   r  zWhatsApp pairing failed.ra  rM	  >   rE	  rF	  rG	  r   9WhatsApp pairing process exited before pairing completed.z*WhatsApp pairing process exited with code r~  )r  r   r*  r+  JSONDecodeErrorr  r   _whatsapp_onboarding_lockrR	  r  rN	  r)  r  rv  r  rO	  rP	  rf	  rQ	  r?  rZ   &_WHATSAPP_ONBOARDING_TERMINAL_STATUSES)r	  r  streamr]  r  rj  r	  recordr	  r   rO	  rP	  r  rN  s                 rU   _watch_whatsapp_pairingr	    s   , !2 !2jjll "jooGG+   HGKK006B77==??. 2 2:>>zJJF! V[%<%<2 2 2 2 2 2 2 2 2 }} T!2!2!8b99??AA 002F-,5FM+/FL+--&{{622%dD11 _),TXXd^^-Ar)B)B)H)H)J)JJ+.txx/?/?/E2+F+F+L+L+N+NL0:0BdF-2>2F$F/3RS]3^3^F0(3'+'))(/'*7;;w+?+?+]C]'^'^.00V]j5P5P(112 2 2 2 2 2 2 2 2 2 2 2 2 2 22 YY[[

   & 	( 	(266zBBF (&+--&-Gm2m2m '"3xx		( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	(
 	 
# 
 
.22:>> 	D00
 
 
 
 
 
 
 
 =AAA
 
 
 
 
 
 
 
   Q HGKjKKK 	
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
s   &I( >I( AI( AA I( 'I7I( E5I:I( I
	
I( I
	I( (
K*2K%9AKK%K	K%K	K%%K*4'M&(
M&?M&&M*-M*c                    t           5  t                              |           }|r|j        t          v r	 d d d            d S d|_        d d d            n# 1 swxY w Y   	 t          ||          }n|# t          $ ro}t           5  t                              |           }|r)|j        t          vrd|_        t          |          |_        d d d            n# 1 swxY w Y   Y d }~d S d }~ww xY wt           5  t                              |           }|r|j        t          v rt          |           	 d d d            d S ||_
        d|_        d d d            n# 1 swxY w Y   t          | |           d S )N
installingr  rM	  )r	  rR	  r   r)  r	  r	  rZ   r  r  r	  r  r	  )r	  rJ	  r  r	  r  rN  s         rU   _run_whatsapp_pairingr	  9  sz   	" % %.22:>> 	*PPP% % % % % % % % %	% % % % % % % % % % % % % % %.|TBB   & 	( 	(266zBBF (&-/UUU '"3xx		( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	(
 	 
# # #.22:>> 	*PPP'---	# # # # # # # #
 "# # # # # # # # # # # # # # # J-----sl   ,AAAA A1 1
C*;C%ACC%C	C%C	C%%C*4;E<EEEc                     t          j                     } g }t                                          D ]\  }}|j        5|j        t
          vr'|j                                        d|_        d|_        |j        | k    r0|j        t
          vr"t          |j                   d|_        d|_        |j        t
          v r#|j        dz   | k    r|
                    |           |D ]}t                              |d            d S )Nr  r	  rE	  z-WhatsApp QR setup expired. Start a new setup.r=  )rC  rR	  r  r  r)  r	  r  r  rL	  r	  r   r  )r?  
remove_idsr	  r	  s       rU   #_prune_whatsapp_onboarding_sessionsr	  U  s   
)++CJ;AACC * *
FK#%KKK  "".#FMVFL3&&6=@f+f+f'444%FMJFL=BBBvG[^aGaehGhGhj)))  < <
%))*d;;;;< <rW   c                     t                                           D ]J}|j        t          |           k    r0|j        t
          vr"d|_        d|_        t          |j                   Kd S )NrF	  z-Superseded by a newer WhatsApp setup session.)	rR	  r  rJ	  r  r)  r	  r  r	  r  )rJ	  r  s     rU   '_supersede_whatsapp_onboarding_sessionsr	  j  si    188:: 7 7 C$5$555(/Qw:w:w)HOLHN'666	7 7rW   r	  c                 v    | |j         |j        |j        |j        |j        |j        |j        |j        |j        d
S )N)
r	  r)  rN	  rK	  r  r  rO	  rP	  rQ	  r  )	r)  rN	  rK	  r  r  rO	  rP	  rQ	  r  r	  r	  s     rU   _whatsapp_onboarding_payloadr	  r  sG     -''-'+-  rW   c                 
   	 t          |           \  }}nC# t          $ r6}t                              d           dt	          |          dcY d }~S d }~ww xY w|r t                              d|j                   dd|j        dS )Nz8Failed to auto-restart gateway after WhatsApp onboardingFr  z?WhatsApp onboarding: reusing in-flight gateway restart (pid %s)Trh  r  r  r  s       rU   *_restart_gateway_after_whatsapp_onboardingr	    s    
-g66ff 
 
 
QRRR$ XX
 
 	
 	
 	
 	
 	
 	

  
		MH	
 	
 	

  +x  r  z(/api/messaging/whatsapp/onboarding/startc                 .  K   t          | j                  }t          | j                  }| j        }t          |          5  t                      }t          j                    t          z   }t          |          }|dz  
                                rt          j        d          }t          |          \  }}	}
t          d ||t          |          |||d||	|
          }t           5  t#                       t%          |           |t&          |<   d d d            n# 1 swxY w Y   t)          ||          cd d d            S 	 d d d            n# 1 swxY w Y   t          j        d          }t          d ||t          |          |||          }t           5  t#                       t%          |           |t&          |<   d d d            n# 1 swxY w Y   t+          j        t.          |||fd                                           t)          ||          S )Nrh	  rc  rG	  )r  r  r  rJ	  rK	  rL	  r  r)  rO	  rP	  rQ	  )r  r  r  rJ	  rK	  rL	  r  T)rh   ri   rj   )r[	  r  r^	  r  r  r  rb	  rC   _WHATSAPP_ONBOARDING_TTL_SECONDSrY	  rA  rx  token_urlsafers	  rI	  r  r	  r	  r	  rR	  r	  rv   rx   r	  rS   )r0  r  r  effective_profilerJ	  rL	  rK	  r	  rO	  rP	  rQ	  r	  s               rU   start_whatsapp_onboardingr	    s(     .ty99D5d6HIIM	0	1	1 D D-//	&FF%m44
<'//11 	D .r22J6[\h6i6i3Jm/+ ..%+)"%)+  F + C C35557EEE<B-j9C C C C C C C C C C C C C C C 0
FCC1D D D D D D D D	D	D D D D D D D D D D D D D D D4 &r**J'#&&#!  F 
# ; ;+---/===4:%j1; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
 $,-   eggg'
F;;;sI   B%E&(DED	E!D	"EEE(GGGz//api/messaging/whatsapp/onboarding/{pairing_id}c                 $  K   t           5  t                       t                              |           }|st	          dd          |j        dk    rt	          d|j        pd          t          | |          cd d d            S # 1 swxY w Y   d S )Nr   8WhatsApp setup session was not found. Start a new setup.r   rE	    zWhatsApp setup expired.)r	  r	  rR	  r   r5   r)  r  r	  r	  s     rU   get_whatsapp_onboarding_statusr	    s      	" 
@ 
@+---.22:>> 	Q    =I%%C8aHabbbb+J??
@ 
@ 
@ 
@ 
@ 
@ 
@ 
@ 
@ 
@ 
@ 
@ 
@ 
@ 
@ 
@ 
@ 
@s   A.BB	B	z5/api/messaging/whatsapp/onboarding/{pairing_id}/applyc                 N  K   t           5  t                       t                              |           }|st	          dd          |j        dk    rt	          dd          t          |j        p|j                  }t          |j	        |j	        n|j	                  }|dk    r|s|j
        p|j        pd}|j        }d d d            n# 1 swxY w Y   |j        p|p|}	 t          |          5  t          d	|           t          d
d           |rt          d|           t          dd           t          dd           d d d            n# 1 swxY w Y   nt# t          $ r  t           $ r$}t	          dt#          |                    |d }~wt$          $ r1}t&                              d           t	          dd          |d }~ww xY wt           5  t                              | d            d d d            n# 1 swxY w Y   t-          |          }	dd|	d          d|	S )Nr   r	  r   rG	  r  z$WhatsApp setup is not connected yet.r2	  r   r  r  r|  r  r  rI  r  Tr   z WhatsApp onboarding apply failedr!  zFailed to save WhatsApp setup.r  )rs  r  needs_restart)r	  r	  rR	  r   r5   r)  r[	  r  r^	  r  rQ	  rO	  r  r  r   rC	  r  r  rZ   rP   r"  r  r	  )
r	  r0  r  r	  r  r  record_profiler	  rN  restart_results
             rU   apply_whatsapp_onboardingr	    s]      
# ( (+---.22:>> 	Q    =K''C8^____2493KLL9$($6$>F  DDV
 
 ;}"0KF4EKM!( ( ( ( ( ( ( ( ( ( ( ( ( ( ($ AA>"#455 	6 	6?D111/;;; H7GGG -v666#J555	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6     G G GCHH===3F   9:::3
 
 
 	 
# < <%))*d;;;< < < < < < < < < < < < < < < @@QRRN+,=>>  	 sm   B4C

CC!E 0AEE EE EE G/FG,GGG>>HHc                    K   t           5  t                              | d           }d d d            n# 1 swxY w Y   |rd|_        t	          |j                   ddiS )NrF	  rs  T)r	  rR	  r  r)  r	  r  r	  s     rU   cancel_whatsapp_onboardingr	    s      	" E E.22:tDDE E E E E E E E E E E E E E E 1##FK000$<   266z+https://setup.hermes-agent.nousresearch.comzHermesDashboard/c                   j    e Zd ZU eed<   eed<   eed<   dZedz  ed<   dZedz  ed<   dZedz  ed<   dS )_TelegramOnboardingPairing
poll_tokenrK	  rL	  N	bot_tokenbot_usernameowner_user_id)	r  r  r  r  r  rx  r	  r	  r	  r  rW   rU   r	  r	  "  sl         OOOOOO IsTz   #L#*### $M3:$$$$$rW   r	  _telegram_onboarding_pairingsc                      t          j        dt                                                                        d          S )NTELEGRAM_ONBOARDING_URLr   )rt   ru    _TELEGRAM_ONBOARDING_DEFAULT_URLr   r$  r  rW   rU   _telegram_onboarding_base_urlr	  0  s,    
	+-MNN		rW   c                    	 |                      dd          }t          j        |          }|j         |                     t          j                  }|                                S # t          $ r t          j                    dz   cY S w xY w)NrU	  rT	  )tzinforD	  )	r  r   fromisoformatr	  r   rW	  	timestamprZ   rC  )rr  r  r  s      rU   _parse_expiry_tsr	  8  s    !]]311
'
33= ^^8<^88F!!! ! ! !y{{S    !s   A$A' ' B
	B
c                      t          j                     fdt                                          D             } | D ]}t                              |d            d S )Nc                 0    g | ]\  }}|j         k    |S r  )rL	  )r   r	  r	  r?  s      rU   r  z7_prune_telegram_onboarding_pairings.<locals>.<listcomp>E  s5       J3&& 	&&&rW   )rC  r	  r  r  )rE	  r	  r?  s     @rU   #_prune_telegram_onboarding_pairingsr	  C  sv    
)++C   "?"E"E"G"G  G
  < <
%))*d;;;;< <rW   c                     t          | pd                                          }t                              |          r|S d S rB  )r  r   r  r  )rr  r  s     rU   _normalize_telegram_user_idr	  N  s@    U[b!!''))J%%j11 4rW   fallbackc                 <    ddddddd                     | |          S )Nz2Telegram pairing was not found. Start a new setup.z*Telegram setup expired. Start a new setup.z6Telegram setup was already claimed. Start a new setup.z-Telegram setup service rejected this request.z)Telegram setup service is not configured.z/Telegram could not finish bot setup. Try again.)	not_foundrE	  claimedr/  )telegram_manager_bot_token_not_configuredtelegram_token_fetch_failedr   )r  r	  s     rU   "_telegram_onboarding_error_messager	  U  s4    I?KG5`'X  
c%rW   r0  bearer_tokenr  r	  c                   dd l }dt          d}i }|
d|d<   ||d<   |rd| |d<   t                       | }	 |                    |                    d          	          5 } |j        | |fd
|i|}	|	                                 d d d            n# 1 swxY w Y   n# |j        $ r}
	 |
j        	                                }n# t          $ r i }Y nw xY wt          |                    d          p|                    d          pd          }t          |d          }|
j        j        dk    rdnd}|dv rd}t          ||          |
d }
~
w|j        $ r}
t          dd          |
d }
~
wt          $ r}
t          dd          |
d }
~
ww xY w	 |		                                }n$# t          $ r}
t          dd          |
d }
~
ww xY wt#          |t$                    st          dd          |S )Nr   r(  )r)  
User-AgentContent-Typer*  r   r  r  r   r   r  r)  r   z)Telegram setup service returned an error.r   r^  >   r	  rE	  r	  r   z9Telegram setup service is unavailable. Try again shortly.z4Telegram setup service returned an invalid response.)r  _TELEGRAM_ONBOARDING_USER_AGENTr	  r  r  r   raise_for_statusHTTPStatusErrorr,  r*  rZ   r  r   r	  r   r5   RequestErrorrv  r  )r  r   r0  r	  r  r   request_kwargsr   r-  r,  rN  r  r  r   r   s                  rU   !_telegram_onboarding_request_syncr	  `  s    LLL %5 G &(N"4!%v <#;\#;#; *,,
4d
4
4C \\%--"5"5\66 	(&%v~    !	 H %%'''	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	(   M M M	\&&((FF 	 	 	FFF	FJJw''E6::h+?+?E2FF37
 
 !\5<<cc#***KFCCCL   N
 
 
 	    N
 
 
 	   I
 
 
 	
 fd## 
I
 
 
 	
 Ms   )B! #&B	B! BB! BB! !
F,CECECA8EFE,,F9FFF) )
G
3GG
c                N   K   t          j        t          | |||           d {V S )Nr	  )rm   rd  r	  )r  r   r0  r	  s       rU   _telegram_onboarding_requestr	    sO       ")!         rW   z(/api/messaging/telegram/onboarding/startc                   K   | j         pd                                pd}t          ddd|i           d {V }t          |                    d          pd                                          }t          |                    d          pd                                          }t          |                    d	          pd                                          }t          |                    d
          pd                                          }t          |                    d          p|                                          }t          |                    d          pd                                          }|r|r|r|st          dd          t          5  t                       t          ||t          |                    t          |<   d d d            n# 1 swxY w Y   |||||dS )Nr   POSTz/v1/telegram/pairingsr  r6  r	  r   r	  rK	  	deep_linkrN	  suggested_usernamer^  7Telegram setup service returned an incomplete response.r   )r	  rK	  rL	  )r	  r	  r	  rN	  rK	  )r  r   r	  r  r   r5   _telegram_onboarding_lockr	  r	  r	  r	  )	r0  r  rj  r	  r	  rK	  r	  rN	  r	  s	            rU   start_telegram_onboardingr	    sQ     /6688JNH0(#        G W[[..4"55;;==JW[[..4"55;;==JW[[..4"55;;==JGKK,,23399;;IW[[..;)<<BBDDJW[[)=>>D"EEKKMM 
Z 
z 
 
L
 
 
 	

 
# 
 
+---4N!!*:665
 5
 5
%j1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 !0    s   6G  G$'G$z//api/messaging/telegram/onboarding/{pairing_id}c                   K   t           5  t                       t                              |           }|st	          dd          |j        r"d|j        |j        |j        dcd d d            S |j	        }d d d            n# 1 swxY w Y   t          ddt          j                            | d	           |
           d {V }t          |                    d          pd                                          }|dk    rIt           5  t                              |           }|r|j        nd}d d d            n# 1 swxY w Y   d|dS |dk    rt          |                    d          pd                                          }t          |                    d          pd                                          }|st	          dd          t!          |                    d                    }	t           5  t                              |           }|st	          dd          ||_        |pd |_        |	|_        d|j        |j        |j        dcd d d            S # 1 swxY w Y   |dv rYt           5  t                              | d            d d d            n# 1 swxY w Y   t	          dt%          |d                    t	          dd          )Nr   8Telegram setup session was not found. Start a new setup.r   r|  )r)  r	  r	  rK	  r  z/v1/telegram/pairings/r   )safe)r	  r)  r	  )r)  rK	  r   r	  r^  r	  r	  >   r	  rE	  r	  z9Telegram setup is no longer available. Start a new setup.z2Telegram setup service returned an unknown status.)r	  r	  r	  r   r5   r	  r	  r	  rK	  r	  r	  r&  r  quoter  r   r	  r  r	  )
r	  r	  r	  rj  r)  r  rK	  r	  r	  r	  s
             rU   get_telegram_onboarding_statusr	    su     	" ' '+---.22:>> 	Q     	! & 3!'!5$/	 ' ' ' ' ' ' ' ' &
' ' ' ' ' ' ' ' ' ' ' ' ' ' '" 1J!3!3JR!3!H!HJJ        G
 X&&,"--3355F& 	? 	?377
CCG/6>++BJ	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? $:>>>G,,23399;;	7;;~66<"==CCEE 	P    4GKK4P4PQQ& 	 	266zBBF # #U     )F"."6$F#0F ! & 3!'!5$/	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	" '''& 	@ 	@)--j$???	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@5K 
 
 
 	
 C   sO   AB/BB	B
&D<<E E ;AI""I&)I&8J  J$'J$c                 
   	 t          |           \  }}nC# t          $ r6}t                              d           dt	          |          dcY d}~S d}~ww xY w|r t                              d|j                   dd|j        dS )	az  Best-effort gateway restart after saving Telegram QR onboarding.

    The QR flow naturally pulls users into Telegram on another device. If the
    saved token waits on a separate dashboard restart click, Hermes appears
    broken from the chat side. Keep the config save authoritative, but report
    restart failures so the UI can fall back to the existing manual banner.
    z8Failed to auto-restart gateway after Telegram onboardingFr  Nz?Telegram onboarding: reusing in-flight gateway restart (pid %s)Trh  r  r  r  s       rU   *_restart_gateway_after_telegram_onboardingr	  &  s    
-g66ff 
 
 
QRRR$ XX
 
 	
 	
 	
 	
 	
 	

  
		MH	
 	
 	

  +x  r  z5/api/messaging/telegram/onboarding/{pairing_id}/applyc                 |  K   g }t                      }|j        D ]R}t          |          }|st          dd          ||vr*|                    |           |                    |           S|st          dd          t          5  t                       t          	                    |           }|st          dd          |j
        }|j        }	|st          dd          	 d d d            n# 1 swxY w Y   |j        p|}
	 t          |
          5  t          d	|           t          d
d                    |                     t!          dd           d d d            n# 1 swxY w Y   nt# t          $ r  t"          $ r$}t          dt%          |                    |d }~wt&          $ r1}t(                              d           t          dd          |d }~ww xY wt          5  t                              | d            d d d            n# 1 swxY w Y   t/          |
          }dd|	|d          d|S )Nr   z*Allowed Telegram user IDs must be numeric.r   z*Add at least one allowed Telegram user ID.r   r	  r  z Telegram setup is not ready yet.r  r  rQ  rd  Tz Telegram onboarding apply failedr!  zFailed to save Telegram setup.r  )rs  r  r	  r	  )r~   r  r	  r5   r  r   r	  r	  r	  r   r	  r	  r  ru  r   r  rC	  r  r  rZ   rP   r"  r  r	  )r	  r0  r  r  r  raw_idr  r	  r	  r	  r	  rN  r	  s                rU   apply_telegram_onboardingr	  B  s      55D' 	0 	0088
 	C    T!!HHZ   ##J/// 
?
 
 
 	

 
#  +---.22:>> 	Q    $	* 	9   	                /-.. 	6 	6/;;;3SXX>N5O5OPPP#J555	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6     G G GCHH===3F   9:::3
 
 
 	 
# < <%))*d;;;< < < < < < < < < < < < < < < @@QRRN $+,=>>	 
  sn   AC11C58C5E1 AE%E1 %E))E1 ,E)-E1 1G"F$$G"1,GG",HHHc                    K   t           5  t                              | d            d d d            n# 1 swxY w Y   ddiS )Nrs  T)r	  r	  r  )r	  s    rU   cancel_telegram_onboardingr	    s      	" < <%))*d;;;< < < < < < < < < < < < < < <$<r	  z/api/messaging/platformsc                 $  K   t          |           5 t                      t                      t          t	                                t          | d          fdt                      D             dcd d d            S # 1 swxY w Y   d S )NrS   c           	      :    g | ]}t          |d u          S )Nr$	  )rA	  )r   r  rC  rc  
scoped_dirs     rU   r  z+get_messaging_platforms.<locals>.<listcomp>  sH         ,;
$8N    rW   )r  gateway_start_commandre  )ru  r   r-   r  r   r  r	  )r  rC  rc  r	  s    @@@rU   get_messaging_platformsr	    s       
	 	  
Jjj%''LNN++%=gw%O%O      9::	  	
 	

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
s   A#BB	B	r  c                 j   ddl m}m} | |vrdS |pd                                }|r|                                dk    rddlm}  |            }nt          |           |}|dv rdS t          d          5   |            j	        s	 ddd           dS 	 ddd           n# 1 swxY w Y   d	|  d
| dS )u  Reason enabling ``platform_id`` on the target profile would break a
    multiplexed gateway, or ``None`` when the change is allowed.

    Mirrors the gateway's startup rule (``_start_one_profile_adapters`` in
    gateway/run.py): with ``gateway.multiplex_profiles`` on, the default
    profile owns the single shared HTTP listener and serves every profile via
    the ``/p/<profile>/`` prefix, so a SECONDARY profile must never enable a
    port-binding platform. Without this pre-write check the dashboard happily
    persisted the invalid config and the shared gateway died with
    ``MultiplexConfigError`` on its next start — for ALL profiles. Only
    *enabling* is blocked; disabling/clearing stays allowed so users can
    repair an already-invalid profile.
    r   )PORT_BINDING_PLATFORM_VALUESr  Nr   r  )get_active_profile_name)r1  rA  r1  zCannot enable 'z' on profile 'z': it binds its own listener port, and gateway.multiplex_profiles is on, so the default profile owns the single shared HTTP listener for every profile. Configure this channel on the default profile instead (disabling or clearing it here is still allowed).)
r  r	  r  r   r   r  r	  _resolve_profile_dirr  multiplex_profiles)r  r  r	  r  	requestedr	  rh   s          rU    _multiplex_port_binding_conflictr	    sx     QPPPPPPP666t"(b//11I 		))Y66??????
 )(**Y'''&&&t
 
y	)	)  ""$$7 	       	              
	<+ 	< 	<V 	< 	< 	<s   6B!!B%(B%z&/api/messaging/platforms/{platform_id}c           
      6  K   t          |           }|st          dd|            |j        p|}|j        rAt	          | |          }|r/t
                              d| |pd           t          d|          t          |d                   }	 t          |j        p|          5  |j	        D ]1}||vrt          d| d	|d
                    t          |           2|j                                        D ]\\  }}||vrt          d| d	|d
                    |                                }	|	r!t          | ||	           t          ||	           ]|j        t!          | |j                   d d d            n# 1 swxY w Y   t
                              d| |pd|j        t#          |j                  t#          |j	                             d| dS # t          $ r  t$          $ r- t
                              d|            t          dd          w xY w)Nr   Unknown messaging platform: r   z\Rejected messaging platform update: platform=%s profile=%s (multiplex port-binding conflict)r  r  r  r   z is not configurable for rR   zYMessaging platform updated: platform=%s profile=%s enabled=%s env_keys=%s cleared_keys=%sT)rs  r  z&PUT /api/messaging/platforms/%s failedr!  r_  )r	  r5   r  r  r	  rP   rQ   r~   ru  r  r   r  r  r   r  r   rC	  r~  rZ   r"  )
r  r0  r  r  target_profileconflictallowed_envr  rr  trimmeds
             rU   update_messaging_platformr
    s      K((E 
$P;$P$P
 
 
 	
 \,WN| B3KPP 		B II4+)	    CAAAAeJ'((K'MDL3G44 	C 	C~ & &k))'$'"%OOfOO    !%%%%"hnn.. 	1 	1
Uk))'$'"%OOfOO     ++-- 11+sGLLL"3000|''T\BBB+	C 	C 	C 	C 	C 	C 	C 	C 	C 	C 	C 	C 	C 	C 	C0 			*'iL484>""	
 	
 	
 444    M M M?MMM4KLLLLMs3   G %CE=1G =FG FAG AHz+/api/messaging/platforms/{platform_id}/testc                   K   t          |           }|st          dd|            t          |          5 }t                      }t	          ||t                      |d u          }d d d            n# 1 swxY w Y   |d         s|d          d}d|d	         |d
S |d         s:d |d         D             }|rdd                    |           nd}d|d	         |d
S |d         sd|d	         dd
S |d	         dk    rd|d	         |d          dd
S |                    d          rd|d	         |d         d
S d|d	         dd
S )Nr   r	  r   r	  r  rR   z2 is disabled. Enable it, then restart the gateway.Frk   )rs  rk   r   r}  c                 >    g | ]}|d          
|d         |d         S )r)  r_  r  r  r#  s     rU   r  z+test_messaging_platform.<locals>.<listcomp>*   sE     
 
 
Z 
 */x
%L
 
 
rW   r  zMissing required setup: r  zPlatform setup is incomplete.r  zEGateway is not running. Restart the gateway to connect this platform.rG	  Tz is connected.r0	  z]Setup looks complete, but the gateway has not reported a connection yet. Restart the gateway.)r	  r5   ru  r   rA	  r-   r  r   )r  r  r  r	  rC  rj  r   r  s           rU   test_messaging_platformr
     s>     K((E 
$P;$P$P
 
 
 	
 
	 	  
Jjj-; 3 5 5jPT>T
 
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 

 9 L6]VVVgg&67KKK<  L
 
 ,
 
 
 1;tyy'9'9;;;0 	
 gg&67KKK$% 
W%^
 
 	

 w;&&W%-777
 
 	

 {{?## 
W%/
 
 	
 !r  s   0A33A7:A7   visiblec                 ,   | sdS t          |           rt          | t                    sdS t          |           }d|v r5|                    d          dk    r|                    dd          d         }t          |          |k    r|S d|| d          S )	u  Return ``...XXXXXX`` (last N chars) for safe display in the UI.

    We never expose more than the trailing ``visible`` characters of an
    OAuth access token. JWT prefixes (the part before the first dot) are
    stripped first when present so the visible suffix is always part of
    the signing region rather than a meaningless header chunk.

    Returns the Entra-ID placeholder when handed a callable (Azure Foundry
    bearer provider) — the callable is NEVER invoked here.
    r   z<entra-id-bearer>r~  r  r   r   u   …N)r8  rv  r  r  r   r   )rr  r
  r>  s      rU   _truncate_tokenr
  Z   s      r #z%55 #""E

A
axxAGGCLLA%%HHS!R 
1vvG899rW   c            
          	 ddl m} m} n# t          $ r d} d}Y nw xY wd}| r	  |             }n# t          $ r d}Y nw xY w|r|                    d          rlddd|r
 |            nd dt          |                    d                    |                    d	          t          |                    d
                    dS d}	 ddlm	} |d         j
        }n# t          t          f$ r Y nw xY w	 ddlm} n# t          $ r d}Y nw xY w	 ddlm} n# t          $ r d}Y nw xY w|D ]R}|r ||          ndpt!          j        |          }|s(|r ||          nd}	dd| |	 t          |          dddc S dddS )u/  Status for the "Anthropic API Key" catalog entry.

    Two sources, in priority order:
    1. ``~/.hermes/.anthropic_oauth.json`` — Hermes-managed PKCE flow (what
       this entry's Connect button writes)
    2. ``ANTHROPIC_API_KEY`` → ``ANTHROPIC_TOKEN`` → ``CLAUDE_CODE_OAUTH_TOKEN``
       env vars (registry order) — from ``.env``, the shell, or an external
       secret source like Bitwarden (whose keys are injected into the process
       env during ``load_hermes_dotenv()``, so the same check covers them)

    Claude Code's ``~/.claude/.credentials.json`` is deliberately NOT read
    here — it has its own dedicated catalog entry (``claude-code`` →
    ``_claude_code_only_status``). Reporting it under the API-key entry
    double-counts the token and shadows a real ANTHROPIC_API_KEY.
    r   )read_hermes_oauth_credentials_get_hermes_oauth_fileNaccessTokenThermes_pkcezHermes PKCE (r  	expiresAtrefreshTokenr@  r   source_labeltoken_previewrK	  has_refresh_token)ANTHROPIC_API_KEYANTHROPIC_TOKENCLAUDE_CODE_OAUTH_TOKEN)PROVIDER_REGISTRY	anthropicget_env_value)format_secret_source_suffixr   r+  Fr@  r   )agent.anthropic_adapterr

  r
  r`   rZ   r   r
  r   r  r
  rc  KeyErrorr  r
  hermes_cli.env_loaderr
  rt   ru   )
r

  r
  hermes_credsenv_var_orderr
  r
  r
  varrr  r  s
             rU   _anthropic_oauth_statusr#
  s   s    &	
 	
 	
 	
 	
 	
 	
 	
 	
  & & &(,%!%& L$  	 88::LL 	  	  	 LLL	  
((77 
#kH^,h,B,B,D,D,Ddhkkk,\-=-=m-L-LMM&**;77!%l&6&6~&F&F!G!G
 
 	
 _M555555)+6G"   3333333   +EEEEEEE + + +&*###+  
 
'4>s###$Q29S>> 	5PX,,S111VX",F,,,U33!&
 
 	
 	
 	
 $///sN    
/ >>C C/.C/3C: :D	D	D D#"D#c            
      :   	 ddl m}   |             }n# t          $ r d}Y nw xY w|rq|                    d          r\dddt	          |                    d                    |                    d          t          |                    d	                    d
S dddS )a  Surface Claude Code CLI credentials as their own provider entry.

    Independent of the Anthropic entry above so users can see whether their
    Claude Code subscription tokens are actively flowing into Hermes even
    when they also have a separate Hermes-managed PKCE login.
    r   )read_claude_code_credentialsNr
  Tclaude_code_cliz~/.claude/.credentials.jsonr
  r
  r
  Fr
  )r
  r%
  rZ   r   r
  r   )r%
  credss     rU   _claude_code_only_statusr(
     s    HHHHHH,,..    
=)) 
'9,UYY}-E-EFF))K00!%eii&?&?!@!@
 
 	
 $///s    ""c                      dddddddS )u  Status for copilot-acp — credentials are owned by the Copilot CLI.

    There is no cheap programmatic credential probe for the ACP subprocess, so
    this is a read-only "managed by the Copilot CLI" card (like claude-code):
    Hermes never claims a login state it can't verify.
    Fcopilot_cliz!Managed by the GitHub Copilot CLINr
  r  r  rW   rU   _copilot_acp_statusr+
     s%     ;"  rW   r  Nous Portaldevice_codezhermes auth add nouszhttps://portal.nousresearch.comr  rR   flowcli_commandr  	status_fnopenai-codexzOpenAI OAuth (ChatGPT)zhermes auth add openai-codexz https://platform.openai.com/docs
qwen-oauthzQwen (via Qwen CLI)externalzhermes auth add qwen-oauthz#https://github.com/QwenLM/qwen-codeminimax-oauthzMiniMax (OAuth)zhermes auth add minimax-oauthzhttps://www.minimax.io	xai-oauthz%xAI Grok OAuth (SuperGrok / Premium+)zhermes auth add xai-oauthz@https://hermes-agent.nousresearch.com/docs/guides/xai-grok-oauthzcopilot-acpzGitHub Copilot (ACP)zcopilot /loginz"https://docs.github.com/en/copilotr
  zAnthropic API Keypkcezhermes auth add anthropicz.https://docs.claude.com/en/api/getting-startedclaude-codezAAnthropic OAuth: Required Extra Usage Credits to Use Subscriptionzclaude setup-tokenz+https://docs.claude.com/en/docs/claude-code_OAUTH_PROVIDER_CATALOGprovider_idc           
      	   |4	  |            S # t           $ r}dt          |          dcY d}~S d}~ww xY w	 ddlm} | dk    r|                                }t          |                    d                    d|                    d	          pd
t          |                    d                    |                    d          t          |                    d                    dS | dk    r|                                }t          |                    d                    |                    d          pd|                    d          pdt          |                    d                    dd|                    d          dS | dk    r|	                                }t          |                    d                    d|                    d          pdt          |                    d                    |                    d          t          |                    d                    dS | dk    rh|
                                }t          |                    d                    dd|                    dd            d!d|                    d          d"dS | d#k    r|                                }t          |                    d                    |                    d          pd$|                    d%          p|                    d          pd&t          |                    d                    dd"|                    d          dS |                    |           }t          |t                    r>d|v r9t          |                    d                    |                    d          p|                    d'          p| |                    d(          pU|                    d%          p@|                    d          p+|                    d)          p|                    d*          pd+t          |                    d          p|                    d                    |                    d          p|                    d          t          |                    d                    dS n)# t           $ r}dt          |          dcY d}~S d}~ww xY wddiS ),z@Dispatch to the right status helper for an OAuth provider entry.NF)r@  r  r   )r   r  r@  nous_portalrA  r,
  access_tokenaccess_expires_atr
  r
  r2
  r   openai_codex	auth_modezOpenAI Codexr  last_refresh)r@  r   r
  r
  rK	  r
  rA
  r3
  qwen_cliauth_store_pathzQwen CLIrK	  r5
  minimax_oauthz	MiniMax (regionglobalr  Tr6
  	xai_oauth
auth_storezxAI Grok OAuthrT   r
  r  rR   r   )rZ   r  r[   r   r;  r   r   r
  get_codex_auth_statusget_qwen_auth_statusget_minimax_oauth_auth_statusget_xai_oauth_auth_statusget_auth_statusrv  r  )r:
  r1
  r  hauthr  s        rU   _resolve_provider_statusrO
  B!  s   	99;; 	9 	9 	9!&Q88888888	9S5,,,,,,&  ,,..C!#''+"6"677' #(9 : : Km!01H1H!I!I!gg&9::%)#''2E*F*F%G%G   .((--//C!#''+"6"677''(++=~ # 4 4 F!01C1C!D!D"%* # 7 7   ,&&,,..C!#''+"6"677$ #(9 : : Hj!01H1H!I!I!ggl33%)#''2E*F*F%G%G   /))5577C!#''+"6"677) JCGGHh,G,G J J J!%!ggl33%)   +%%1133C
 "#''+"6"677''(++:{ # 5 5 ^9J9J ^N^!01C1C!D!D"%) # 7 7   ##K00c4   	[C%7%7!#''+"6"677''(++Qswwz/B/BQkGGN++ ww|,,ww011 wwz** wwv	
 !0GGN++Aswwy/A/A" " "ggl33Ssww?R7S7S%)#''2E*F*F%G%G  "  5 5 5"SVV444444445sU   	 
4/44B0S )B S 
B*S 5A-S #B5S E(S 
S)S$S)$S)c                     |                      d          dk    rdS |                      d          dk    rd}t          j        dk    rd| S |S dS )	ua  Shell command that clears an external provider's credentials.

    External providers store their credentials outside Hermes, so the disconnect
    API deliberately refuses them (we never delete files another CLI owns on the
    user's behalf via a silent API call). For the ones we know how to clear we
    instead hand the GUI a command it can *run in the embedded terminal* — the
    user sees exactly what executes, and Hermes then stops resolving the token.

    Claude Code has no scriptable logout (only the interactive ``/logout``), so
    we remove the credential the same way logout does: the macOS Keychain entry
    (``Claude Code-credentials``) and/or the ``~/.claude/.credentials.json``
    file — the two sources ``read_claude_code_credentials()`` consults. Returns
    None for providers we can't safely clear (the GUI shows a manual hint).
    r/
  r4
  Nr  r8
  z!rm -f ~/.claude/.credentials.jsondarwinzKsecurity delete-generic-password -s "Claude Code-credentials" 2>/dev/null; )r   r  r  )rT   rm_files     rU   "_oauth_provider_disconnect_commandrS
  !  sb     ||Fz))t||D]**5<8##jahjjj4rW   c                     |                      d          dk    rt          |           rdS dS |                     d          dk    rdS dS )	zJReturn the manual disconnect path when the API cannot clear this provider.r/
  r4
  uC   Managed outside Hermes — run the disconnect command to remove it.z0Managed by that provider's CLI; remove it there.r   r+  u2   Remove the API key from Settings → Keys instead.N)r   rS
  )rT   r)  s     rU   _oauth_provider_disconnect_hintrU
  !  s[    ||Fz))-h77 	Y YXAAzz(y((CC4rW   c            
         g } t                      }t          D ]J}|d         |v r|                    |d                    |                     t	          |                     K	 ddlm}  |            D ]f}|j        dk    s	|j        |v r|                    |j                   |                     |j        |j	        dd|j         |j
        pddd	           gn# t          $ r Y nw xY w| S )
u  Build the Accounts-tab provider list.

    MEMBERSHIP is the union of:
      1. ``_OAUTH_PROVIDER_CATALOG`` — the explicit, hand-tuned cards that carry
         bespoke flow / status_fn / cli_command (including the api-key Anthropic
         PKCE card and the synthetic claude-code subscription row, which are not
         catalog providers), and
      2. every accounts-tab provider in the unified ``provider_catalog()`` (the
         ``hermes model`` universe) — so any OAuth/external provider added as a
         plugin appears automatically, with sensible defaults, even if no
         explicit card was written for it.

    The explicit catalog wins on metadata; the unified catalog guarantees we
    never silently drop a provider the CLI picker offers. Order: explicit cards
    first (their curated order), then any catalog-only providers appended in
    ``hermes model`` order.
    r  r   rU  accountsr4
  zhermes auth add r   Nr.
  )r~   r9
  r  r   r  ra  rV  rb  r  r?  rd  rZ   )r   r  r  rV  rj  s        rU   _build_oauth_catalogrX
  !  s=   $ "$DUUD ) ! !;$tDKK    @@@@@@!!## 	 	Au
""afnnHHQVKKf":!&::L.B!     		     Ks   $A7C 
C)(C)z/api/providers/oauthc                   K   t          |           5  g }t                      D ]}t          |d         |                    d                    }t	          ||          }|                    |d         |d         |d         |d         |d         |t          |          |du |d	           d	|icddd           S # 1 swxY w Y   dS )
uz  Enumerate every OAuth-capable LLM provider with current status.

    Response shape (per provider):
        id              stable identifier (used in DELETE path)
        name            human label
        flow            "pkce" | "device_code" | "external"
        cli_command     fallback CLI command for users to run manually
        disconnect_command  shell command that clears an external provider's
                            creds (run in the embedded terminal), else null
        docs_url        external docs/portal link for the "Learn more" link
        status:
          logged_in        bool — currently has usable creds
          source           short slug ("hermes_pkce", "claude_code", ...)
          source_label     human-readable origin (file path, env var name)
          token_preview    last N chars of the token, never the full token
          expires_at       ISO timestamp string or null
          has_refresh_token bool

    Membership is derived from the unified provider_catalog() so this stays in
    sync with the `hermes model` picker; _OAUTH_OVERRIDES supplies per-provider
    flow/status/cli metadata.
    r  r1
  rR   r/
  r0
  r  N)	r  rR   r/
  r0
  r  disconnect_hintdisconnect_commanddisconnectabler)  r  )ru  rX
  rO
  r   rU
  r   rS
  )r  r  r   r)  rZ
  s        rU   list_oauth_providersr]
  !  s0     0 
	 	  ( (	%'' 	 	A-agquu[7I7IJJF=aHHOg&	&	 /jM#2&H&K&K"1T"9 
 
 
 
 
 
 Y'!( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (s   BB;;B?B?z"/api/providers/oauth/{provider_id}c                   K   t          |           t          |          5  d t                      D             }|                    |           }|7t	          dd|  dd                    t          |                               t          |i           }|rt	          d|d          d	|           t          | |                    d
                    }t          ||          }|rt	          d|d          d	|           | dk    rd}	 ddl	m
}  |            }	|	                                r|	                                 d}n# t          $ r Y nw xY w	 ddlm}
  |
d          p|}n# t          $ r Y nw xY wt                               d|            t%          |          | dcddd           S 	 ddlm}
m}  |
|           }| dk    r
 |             t                               d| |           t%          |          | dcddd           S # t          $ r>}t                               d|            t	          dt+          |                    d}~ww xY w# 1 swxY w Y   dS )zDDisconnect an OAuth provider. Token-protected (matches /env/reveal).c                      i | ]}|d          |S r  r  r  s     rU   rI  z-disconnect_oauth_provider.<locals>.<dictcomp>-"  s    DDD4!DDDrW   Nr   zUnknown provider: . Available: r  r   rR   z' cannot be disconnected automatically. r1
  r
  Fr   r
  T)clear_provider_authzoauth/disconnect: %s)rs  rT   )rc
  !invalidate_nous_auth_status_cacher  z!oauth/disconnect: %s (cleared=%s)zdisconnect %s failedr!  )r   ru  rX
  r   r5   r  r~  rU
  rO
  r
  r
  rA  r  rZ   r  rc
  rP   rQ   r   rd
  r"  r  )r:
  r   r  catalog_by_idrT   rZ
  r)  clearedr
  
oauth_filerc
  rd
  r  s                rU   disconnect_oauth_providerrh
  #"  s      7		 	  8@ 8@DD-A-C-CDDD $$[11HK H H%)YYvm/D/D%E%EH H    :(BGG 	"6*ddSbdd   
 *+x||K7P7PQQ9(FKK 	"6*ddSbdd    +%%GJJJJJJ3355
$$&& #%%'''"G   ??????--k::Eg   II,k:::w--[AA[8@ 8@ 8@ 8@ 8@ 8@ 8@ 8@^		@^^^^^^^^))+66Gf$$11333II9;PPPw--[AAk8@ 8@ 8@ 8@ 8@ 8@ 8@ 8@l  	@ 	@ 	@NN1;???CA????	@m8@ 8@ 8@ 8@ 8@ 8@ 8@ 8@ 8@ 8@sy   C.I":E
I"
EI"EI"E0/I"0
E=:I"<E==/I":AH
I!9III""I&)I&  _oauth_sessions)_OAUTH_CLIENT_ID_OAUTH_TOKEN_URL_OAUTH_TOKEN_URLS_OAUTH_REDIRECT_URI_OAUTH_SCOPES_generate_pkcez!https://claude.ai/oauth/authorizec                  
   t          j                     t          z
  t          5  fdt                                          D             } | D ]}t                              |d           	 ddd           dS # 1 swxY w Y   dS )z:Drop expired sessions. Called opportunistically on /start.c                 2    g | ]\  }}|d          k     |S )
created_atr  )r   r  sessr  s      rU   r  z&_gc_oauth_sessions.<locals>.<listcomp>"  s-    ]]]dlASV\A\A\A\A\A\rW   N)rC  _OAUTH_SESSION_TTL_SECONDS_oauth_sessions_lockrj
  r  r  )staler  r  s     @rU   _gc_oauth_sessionsrx
  "  s    Y[[55F	 + +]]]]o&;&;&=&=]]] 	+ 	+CT****	++ + + + + + + + + + + + + + + + + +s   AA88A<?A<c                 j    | pd                                 }|r|                                dk    rd S |S )Nr   r  )r   r   )r  r	  s     rU   _oauth_profile_namerz
  "  s>    B%%''I 	))Y66trW   c                 J    t          |           }|rt          |           d S d S r  )rz
  r	  )r  rc  s     rU   _validate_oauth_profiler|
  "  s5    &w//L +\*****+ +rW   r/
  c                     t          j        d          }t          |          }|| ||t          j                    ddd}t          5  |t
          |<   ddd           n# 1 swxY w Y   ||fS )zICreate + register a new OAuth session, return (session_id, session_dict).rc  pendingN)r  rT   r/
  r  rs
  r)  r0	  )rx  r	  rz
  rC  rv
  rj
  )r:
  r/
  r  r  rc  rt
  s         rU   _new_oauth_sessionr
  "  s     

#
#C&w//Likk D 
 $ $#$ $ $ $ $ $ $ $ $ $ $ $ $ $ $9s   AA!$A!r  c                     t           5  t                              |           }|r|                    d          nd}ddd           n# 1 swxY w Y   |pt          |          S )zCReturn the profile that owns an OAuth session, if one was provided.r  N)rv
  rj
  r   rz
  )r  r	  rt
  r  s       rU   _oauth_session_profiler
  "  s    
 
 8 8"":..)-7$((9%%%48 8 8 8 8 8 8 8 8 8 8 8 8 8 8 3)(333s   4AAAr=
  refresh_tokenexpires_at_msc                 F   ddl m}  |            }| ||d}ddlm}  |||dd           	 ddlm}m}m}	m dd	l	}
 |d
          }fd|
                                D             }|D ]7}	 |                    t          |dd                     (# t          $ r Y 4w xY w |d
|
                                j        d	d         d|	d d| ||	  	        }|                    |           d	S # t          $ r&}t"                              d|           Y d	}~d	S d	}~ww xY w)zPersist Anthropic PKCE creds to both Hermes file AND credential pool.

    Mirrors what auth_commands.add_command does so the dashboard flow leaves
    the system in the same state as ``hermes auth add anthropic``.
    r   rb
  )r
  r
  r
  r  r  r  )indentr  )PooledCredential	load_poolAUTH_TYPE_OAUTHSOURCE_MANUALNr
  c                 b    g | ]+}t          |d d                               d          )|,S )r   r   :dashboard_pkce)r   r   )r   r  r
  s     rU   r  z/_save_anthropic_oauth_creds.<locals>.<listcomp>"  sB    xxx!Hb1I1I1T1TXeUvUvUv1w1wxAxxxrW   r  r   r
  zdashboard PKCEr
  )	rT   r  r?  rf  priorityr   r=
  r
  r
  z)anthropic pool add (dashboard) failed: %s)r
  r
  r  r  agent.credential_poolr
  r
  r
  r
  uuidr  remove_entryr   rZ   uuid4hex	add_entryrP   r3  )r=
  r
  r
  r
  rg
  rj  r  r
  r
  r
  r
  poolr  r  r  r
  s                  @rU   _save_anthropic_oauth_credsr
  "  s    ?>>>>>''))J#%" G ('''''j'!%@@@@E	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	y%%xxxxt||~~xxx 	 	A!!'!T2"6"67777      zz||#"%#444%''

 

 

 	u E E E@!DDDDDDDDDEs<   ?C0 .$BC0 
B C0 B  AC0 0
D :DD c           	      0   t           st          dd          t                      \  }}t          dd|           \  }}||d<   ||d<   d	t          d
t
          t          |d|d}t           dt          j	        
                    |           }|d|t          dS )z9Begin PKCE flow. Returns the auth URL the UI should open.i  z/Anthropic OAuth not available (missing adapter)r   r
  r7
  r  verifierrk   rI  r2  S256)r2  	client_idresponse_typeredirect_urir  code_challengecode_challenge_methodrk   ?)r  r/
  auth_url
expires_in)_ANTHROPIC_OAUTH_AVAILABLEr5   _generate_pkce_pairr
  _ANTHROPIC_OAUTH_CLIENT_ID_ANTHROPIC_OAUTH_REDIRECT_URI_ANTHROPIC_OAUTH_SCOPES_ANTHROPIC_OAUTH_AUTHORIZE_URLr&  r  	urlencoderu
  )r  r
  	challenger  rt
  r  r
  s          rU   _start_anthropic_pkcer
  #  s    % g4effff-//Hi";HHHICDDM/5(#!'	 	F 1SS6<3I3I&3Q3QSSH0	  rW   
code_inputc           	         t           5  t                              |           }ddd           n# 1 swxY w Y   |r|d         dk    s|d         dk    rt          dd          |d	         d
k    rd|d	         |                    d          dS |                                                    dd          }|d                                         }|sddddS t          |          dk    r|d         nd}t          j        dt          ||p|d         t          |d         d                                          }d}d}	t          D ]}
t          j                            |
|dddd          }	 t          j                            |d          5 }t          j        |                                                                          }ddd           n# 1 swxY w Y    n# t(          $ r}|}	Y d}~d}~ww xY w|8t           5  d|d	<   d|	 |d<   ddd           n# 1 swxY w Y   dd|d         dS |                    d d          }|                    d!d          }t+          |                    d"          pd#          }|s5t           5  d|d	<   d$|d<   ddd           n# 1 swxY w Y   dd|d         dS t+          t-          j                    d%z            |d%z  z   }	 t/          t1          | |                    5  t3          |||           ddd           n# 1 swxY w Y   nO# t(          $ rB}t           5  d|d	<   d&| |d<   ddd           n# 1 swxY w Y   dd|d         dcY d}~S d}~ww xY wt           5  d'|d	<   ddd           n# 1 swxY w Y   t4                              d(|            d)d'd*S )+z<Exchange authorization code for tokens. Persists on success.NrT   r
  r/
  r7
  r   zUnknown or expired sessionr   r)  r~
  Fr0	  )rs  r)  r   #r   r   r  zNo code providedr   authorization_coderk   r
  )
grant_typer
  r2  rk   r
  code_verifierr(  zhermes-dashboard/1.0)r	  r	  r	  )r  r   r  r  r   zToken exchange failed: r=
  r
  r
  i  zNo access token returned  zSave failed: approvedz2oauth/pkce: anthropic login completed (session=%s)T)rs  r)  )rv
  rj
  r   r5   r   r   r   r*  dumpsr
  r
  r   _ANTHROPIC_OAUTH_TOKEN_URLSr&  r   r6   r'  r+  r,  r  rZ   rw  rC  ru  r
  r
  rP   rQ   )r  r
  r  rt
  r   r2  state_from_callbackexchange_datar  last_exc	_endpointr.  r/  r  r=
  r
  r
  r
  s                     rU   _submit_anthropic_pkcer
  ,#  s$    
 / /"":../ / / / / / / / / / / / / / / R4
#{22d6lf6L6L4PQQQQH~""tH~$((?B[B[\\\ $$S!,,E8>>D Ow;MNNN&)%jj1nn%(("J*/$5W5j)      vxx  FH0  	n$$ 24   % 
 
	''R'88 :DDIIKK$6$6$8$899: : : : : : : : : : : : : : :E 	 	 	HHHHH	 ~! 	I 	I$DN$Hh$H$HD!	I 	I 	I 	I 	I 	I 	I 	I 	I 	I 	I 	I 	I 	I 	I w4;PQQQ::nb11LJJ33MVZZ--566J R! 	? 	?$DN$>D!	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? w4;PQQQ	d*++zD/@AMR2:wGGHH 	T 	T'm]SSS	T 	T 	T 	T 	T 	T 	T 	T 	T 	T 	T 	T 	T 	T 	T R R R! 	8 	8$DN$7A$7$7D!	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 w4;PQQQQQQQQ	R
 
 $ $#X$ $ $ $ $ $ $ $ $ $ $ $ $ $ $IIBJOOO*---s   /330!G#9G
G#G	G#G	G##
G9-G44G9HH#&H#J&&J*-J*'L. L"L. "L&&L. )L&*L. .
M:8M5?MM5M	M5 M	!M5/M:5M:NNNc           
      N  K   | dk    rddl mm} ddl|d         }t	          j        d          pt	          j        d          p|j                            d          |j        |j	        fd}t          j                                        d|           d{V \  }}t          dd	|
          \  }}t          |d	                   |d	<   t          |d                   |d<   t!          j                    t          |d                   z   |d<   |d<   |d<   ||d<   t#          j        t&          |fdd|dd                                                     |d	t          |d                   t          |d                   t          |d                   t          |d                   dS | dk    rt          dd	|
          \  }}	t#          j        t*          |fdd|dd                                                     t!          j                    dz   }
t!          j                    |
k     rt.          5  t0                              |          }ddd           n# 1 swxY w Y   |r"|                    d          s|d         dk    rn1t          j        d           d{V  t!          j                    |
k     t.          5  t0                              |i           }ddd           n# 1 swxY w Y   |                    d          dk    r&t7          d|                    d           pd!"          |                    d          st7          d#d$"          |d	|d         |d%         t          |                    d          pd&          t          |                    d          pd'          dS | d(k    rdd)l m}mmm} ddl |            \  }t	          j        d*          p|                    d          fd+}t          j                                         d|           d{V }t          d(d	|
          \  }}|                    d          }|t          |          nd|d,<   t          |d                   |d<   ||d-<   |d.<   |d<   |d<   d/|d0<   t          |d1                   }||d2<   |d3k    r7|d4z  }tC          dt          |t!          j                    z
                      }nt!          j                    |z   }|}||d<   t#          j        tD          |fdd|dd                                                     |d	t          |d                   t          |d5                   |tC          d6|d,         pd7d8z            dS | d9k    rSdd:l m# ddlfd;}t          j                                        d|           d{V }t          d9d	|
          \  }}t          |d	                   |d	<   t          |d                   |d<   t!          j                    t          |d                   z   |d<   t#          j        tH          |fdd|dd                                                     |d	t          |d                   t          |                    d          p|d5                   t          |d                   t          |d                   dS t7          d<d=|  d>"          )?a!  Initiate a device-code flow (Nous, OpenAI Codex, MiniMax, or xAI).

    Calls the provider's device-auth endpoint via the existing CLI helpers,
    then spawns a background poller. Returns the user-facing display fields
    so the UI can render the verification page link + user code.
    r  r   )_request_device_coder
  NHERMES_PORTAL_BASE_URLNOUS_PORTAL_BASE_URLr   c                                                               d          ddi          5 }  |           fcd d d            S # 1 swxY w Y   d S )N      .@r)  r(  r!  r   )r-  rA  r
  r  r  r  )r-  r
  r
  r  rA  r  s    rU   _do_nous_device_requestz8_start_device_code_flow.<locals>._do_nous_device_request#  s    d++!#56     ((%(7"+#	   	                 s   AAAr-
  r
  rI   r
  rK	  rA  r
  r  Tzoauth-poll-r
  rg   	user_codeverification_uri_complete)r  r/
  r
  verification_urlr
  poll_intervalr2
  zoauth-codex-r  r)  r~
  r  r  r!  r0	  zdevice-auth failedr   i  z2device-auth timed out before returning a user coder
  ri
  r   r5
  )_minimax_pkce_pair_minimax_request_user_codeMINIMAX_OAUTH_CLIENT_IDMINIMAX_OAUTH_GLOBAL_BASEMINIMAX_PORTAL_BASE_URLc                                                               d          ddid          5 }  |           cd d d            S # 1 swxY w Y   d S )Nr
  r)  r(  Tr!  r   follow_redirects)r-  rA  r
  r
  rk   r
  )r-  r
  r
  r
  r  rA  rk   s    rU   _do_minimax_requestz4_start_device_code_flow.<locals>._do_minimax_request#  s    d++!#56!%     11!$35#,                   s   AAAinterval_msr
  rk   rF
  rE
  
expired_inexpired_in_rawl    J)g     @@verification_urir  r>  r
  r6
  )_xai_oauth_request_device_codec                                                               d          ddi          5 }  |           cd d d            S # 1 swxY w Y   d S )N      4@r)  r(  r
  r
  )r-  r
  r  s    rU   _do_xai_device_requestz7_start_device_code_flow.<locals>._do_xai_device_request-$  s    d++!#56    > 55f==	> > > > > > > > > > > > > > > > > >s   AA
A
r   z	Provider z" does not support device-code flow)%r  r
  r
  r  rt   ru   rA  r$  r
  r  rm   rI  rs   r
  r  rw  rC  rv   rx   _nous_pollerrS   _codex_full_login_worker	monotonicrv
  rj
  r   sleepr5   r
  r
  r
  r
  rr   rN  _minimax_pollerr
  _xai_device_poller)r:
  r  r
  pconfigr
  device_dataeffective_scoper  rt
  r  deadliner>  r
  r
  r
  r
  interval_rawr
  rL	  expires_in_secondsr
  r
  r
  r
  r
  r
  r
  r  rA  r  rk   s                        @@@@@@@@@@rU   _start_device_code_flowr
  |#  s7	      f	
 	
 	
 	
 	
 	
 	
 	
 	#F+I.// 'y/00'&
&++	 	
 %		 	 	 	 	 	 	 	 	 .5-E-G-G-W-W).
 .
 (
 (
 (
 (
 (
 (
$_ 'v}gNNN	T!+m"<==]{:677Z!Y[[3{</H+I+II\"1%['WsfT@WcRTSTRTg@W@W	
 	
 	

%'''![566 #K0K$L M Mk,788 Z!899
 
 	
 n$$#NM7SSSQ 	+3&)BQB))	
 	
 	
 %'''>##b(n))% - -#'',,- - - - - - - - - - - - - - - aeeK(( AhK9,D,D-$$$$$$$$$ n)) " 	- 	-##C,,A	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	-55??g%%Co8N8N8fRfgggguu[!! 	nC8lmmmm!; !"4 5aeeL118S99 z!2!2!7a88
 
 	
 o%%	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	%7%7%9%9")UI/00M4M
&++ 		 	 	 	 	 	 	 	 	 	 $244DD%
 
 
 
 
 
 
 
 'wWWW	T #z22!-!9Ct 	]  K 899[ (_W"13[!X
 [677!/---*V3M!$QMDIKK,G(H(H!I!I IKK.8M!/*\"(s2A2w((		
 	
 	

 %'''![566 #K0B$C D D, T-%8%@DT$IJJ
 
 	
 k!!BBBBBB	> 	> 	> 	> 	> 	> $466FF(
 
 
 
 
 
 
 
 '{M7SSS	T!+m"<==]{:677Z!Y[[3{</H+I+II\%(s2A2w((		
 	
 	

 %'''![566 # ;<< 312! ! k,788 Z!899

 

 
	
 C0kK0k0k0k
l
l
lls$   I33I7:I7LL	Lc                 
   ddl m}m} ddlm}m} ddl}t          5  t                              |           }ddd           n# 1 swxY w Y   |sdS |d         }|d         }|d         }	|d         }
|                    d	          }t          d
t          |d         t          j                    z
                      }	 |                    |                    d          ddi          5 } |||||	||
          }ddd           n# 1 swxY w Y    |j        |j                  }t          |                    d          pd          }||                    d          ||                    d	          p||                    dd          |d         |                    d          |                                |r> |j        |                                |z   |j                                                  nd|d
}t'          t)          |                     5   ||dd          }ddl m}  ||           ddd           n# 1 swxY w Y   t          5  d|d<   ddd           n# 1 swxY w Y   t,                              d|            dS # t0          $ rc}t,                              d| |           t          5  d |d<   t5          |          |d!<   ddd           n# 1 swxY w Y   Y d}~dS Y d}~dS d}~ww xY w)"zDBackground poller that drives a Nous device-code flow to completion.r   )_poll_for_tokenrefresh_nous_oauth_from_stater   NrA  r
  r-
  rI   r  rG   rK	  r
  r)  r(  r
  )r-  rA  r
  r-
  r
  r
  r
  rB  
token_typeBearerr=
  r
  tz)
rA  rB  r
  r  r
  r=
  r
  obtained_atrK	  r
  F)timeout_secondsforce_refresh)persist_nous_credentialsr
  r)  z/oauth/device: nous login completed (session=%s)z-nous device-code poll failed (session=%s): %sr  r0	  )r  r
  r
  r   r   r  rv
  rj
  r   rN  rw  rC  r  r  r?  rW	  rX	  rV	  r	  ru  r
  r
  rP   rQ   rZ   r3  r  )r  r
  r
  r   r   r  rt
  rA  r
  r-
  rI   r  r
  r-  
token_datar?  	token_ttl
auth_state
full_stater
  r  s                        rU   r
  r
  P$  s           ,+++++++LLL	 / /"":../ / / / / / / / / / / / / / / ,-O[!I}%KJHHHWERT,/$)++=>>??J++\\%--"5"5J\?]\^^ 	bh( /#'%&  J	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 hl8<((
|449::	.",..1E"F"F"^^G,,5$..x@@&~6'^^O<<==?? '&&s}}'Bx|TTT^^```"&#
 

 2:>>?? 	1 	166 $#  J
 A@@@@@$$Z000	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 " 	( 	('DN	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	(		CZPPPPP + + +DjRSTTT! 	+ 	+$DN$'FFD!	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	++s   AA
A?,J +D	=J 	DJ DDJ  I	=J 	IJ I
J I-!J -I11J 4I15J 
L#K=K&K=&K*	*K=-K*	.K==Lc                    ddl m}m}m}m}m} ddlm}m} ddl}t          5  t                              |           }	ddd           n# 1 swxY w Y   |	sdS |	d         }
|	d         }|	d         }|	d         }|	                    d	          }|	d
         }	 |                    |                    d          ddid          5 } |||
|||||          }ddd           n# 1 swxY w Y    |j        |j                  } |t!          |d                   |          }t#          dt!          ||                                z
                      }d|	                    dd          |
||||                    dd          |d         |d         |                    d          |                                 |j        ||j                                                  |d}t+          t-          |                     5   ||           ddd           n# 1 swxY w Y   t          5  d|	d<   ddd           n# 1 swxY w Y   t.                              d|            dS # t2          $ rc}t.                              d | |           t          5  d!|	d<   t7          |          |	d"<   ddd           n# 1 swxY w Y   Y d}~dS Y d}~dS d}~ww xY w)#u	  Background poller that drives a MiniMax OAuth flow to completion.

    Mirrors `_nous_poller` but calls the MiniMax-specific token endpoint,
    which uses a PKCE-style ``code_verifier`` + ``user_code`` rather than
    the ``device_code`` field used by Nous. On success, builds the same
    auth_state dict that ``_minimax_oauth_login`` (the CLI flow) builds
    and persists via ``_minimax_save_auth_state`` — so the dashboard
    path leaves the system in the same state as
    ``hermes auth add minimax-oauth``.
    r   )_minimax_poll_token"_minimax_resolve_token_expiry_unix_minimax_save_auth_stateMINIMAX_OAUTH_GLOBAL_INFERENCEMINIMAX_OAUTH_SCOPEr   NrA  r
  r
  r
  r
  r
  r
  r)  r(  Tr
  )r-  rA  r
  r
  r
  r
  r
  r
  )r?  r5
  rE
  rF
  r
  r
  r=
  r
  resource_urlr
  )rT   rE
  rA  rB  r
  r  r
  r=
  r
  r
  r
  rK	  r
  r
  r)  z2oauth/device: minimax login completed (session=%s)z0minimax device-code poll failed (session=%s): %sr  r0	  )r  r
  r
  r
  r
  r
  r   r   r  rv
  rj
  r   r  r  r?  rW	  rw  rN  r	  rX	  rV	  ru  r
  rP   rQ   rZ   r3  r  )r  r
  r
  r
  r
  r
  r   r   r  rt
  rA  r
  r
  r
  r
  r
  r-  r
  r?  rL	  expires_in_sr
  r  s                          rU   r
  r
  $  s                 ,+++++++LLL	 / /"":../ / / / / / / / / / / / / / / ,-O[!I[!I)M((=))K*+N3+\\MM$''12!  
 
 	 ,, /##+)'  J	 	 	 	 	 	 	 	 	 	 	 	 	 	 	& hl8<((::
<())s
 
 
 1c-#--//"ABBCC'hhx22."@"($..x@@&~6'8&NN>::==??0(0(,  ikk&
 

" 2:>>?? 	1 	1$$Z000	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1! 	( 	('DN	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	(		F
SSSSS + + +GUVWWW! 	+ 	+$DN$'FFD!	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	++s   A		AA-I$ C"I$ "C&&I$ )C&*DI$  HI$ HI$ H 
I$ *H<0I$ <I  I$ I I$ $
K.#KJ5)K5J9	9K<J9	=KKc           	      t   ddl }ddlm}m}m}m} t          5  t                              |           }ddd           n# 1 swxY w Y   |sdS |d         }t          |d                   }t          dt          |d         t          j                    z
                      }		  |d          }
|                    |                    d          d	d
i          5 } |||
d         ||	|          }ddd           n# 1 swxY w Y   t          |                    dd          pd                                          t          |                    dd          pd                                          t          |                    dd          pd                                          |                    d          t          |                    d          pd                                          pdd}t!          t#          |                     5   |||
t%          j        t(          j                                                                      dd          d            |dd           ddd           n# 1 swxY w Y   t          5  d|d<   ddd           n# 1 swxY w Y   t0                              d|            dS # t4          $ rc}t0                              d| |           t          5  d|d<   t          |          |d <   ddd           n# 1 swxY w Y   Y d}~dS Y d}~dS d}~ww xY w)!z3Background poller for xAI's OAuth device-code flow.r   N)_save_xai_oauth_tokens_xai_oauth_discovery_xai_oauth_poll_device_tokenunsuppress_credential_sourcer-
  rI   rG   rK	  r
  r)  r(  r
  token_endpoint)r   r-
  r
  r
  r=
  r   r
  id_tokenr
  r
  r
  )r=
  r
  r  r
  r
  rT	  rU	  oauth_device_code)	discoveryrA
  r@
  r6
  r
  r)  z.oauth/device: xai login completed (session=%s)z,xai device-code poll failed (session=%s): %sr  r0	  )r  r  r
  r
  r
  r
  rv
  rj
  r   rw  rN  rC  r  r  r  r   ru  r
  r   r?  r   rW	  rX	  r  rP   rQ   rZ   r3  )r  r  r
  r
  r
  r
  rt
  r-
  rI   r
  r  r-  r
  tokensr  s                  rU   r
  r
  $  s   LLL            
 / /"":../ / / / / / / / / / / / / / / }%K4
#$$HRT,/$)++=>>??J,+((..	\\MM$''12  
 
 
	 55()9:'%&  J	
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	  
~r B B HbIIOOQQ !D!D!JKKQQSSJNN:r::@bAAGGII$..66jnn\::FhGGMMOO[S[
 
 2:>>?? 	E 	E""#%\(,77AACCKKHVYZZ-	    )(mDDD!	E 	E 	E 	E 	E 	E 	E 	E 	E 	E 	E 	E 	E 	E 	E" " 	( 	('DN	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	(		BJOOOOO + + +CZQRSSS! 	+ 	+$DN$'FFD!	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	++s   ?AA#7K
 C=1K
 =DK
 DDK
 AI>2K
 >JK
 J
K
 J"K
 "J&&K
 )J&*K
 

L7#L27LL2L	L2"L	#L22L7c                    	 |                                  }n# t          $ r d}Y nw xY wt          |t                    r|                    d          t          t                    r%fddD             }|rd                    |          S t          t                    r(                                r                                S dD ]V}|                    |          }t          |t                    r*|                                r|                                c S Wt          t          | dd          pd                                          }|dd	         S )
z8Best-effort extraction of a short provider error detail.Nr  c                     g | ]l}t                              |d                                                     7t                              |d                                                     mS r   r  )r   r  r  s     rU   r  z/_http_response_error_detail.<locals>.<listcomp>-%  ss       uyyb))**0022EIIc2&&''--//  rW   )r   error_descriptionr2  r  : )r   r   r  r  r   r!  )	r*  rZ   rv  r  r   r  r  r   r   )r/  rj  r   r  rr  r  r  s         @rU   _http_response_error_detailr
  $%  sq   ))++   '4   %G$$eT"" 	(   K  E
  (yy'''eS!! 	!ekkmm 	!;;== = 	% 	%CKK$$E%%% %%++-- %{{}}$$$wtVR((.B//5577D:s    ''c                     t          | dd          }t          |           }|                                }d|v rd|v sd|v rd}nd}|r| d| d	| d
S | d| d
S )z8Dashboard-facing OpenAI Codex device-code start failure.r   r|  deviceauthorienablezOpenAI rejected the device-code login request. Your OpenAI account may need device-code authorization enabled before Hermes can start this dashboard login. Enable device-code authorization in OpenAI, then return here and click Login again.zOpenAI rejected the device-code login request. Please try Login again from the dashboard after checking your OpenAI account settings.z (HTTP r	  r  )r   r
  r   )r/  r)  r   r   r   s        rU   _codex_device_code_start_errorr  >%  s    T=)44F(..FLLNNE5i500H4E4EA 	T 	  655&55F5555''f''''rW   c           
      	   	 ddl }ddlm}m}m} d}|                    |                    d                    5 }|                    | dd|id	d
i          }ddd           n# 1 swxY w Y   |j        dk    rt          t          |                    |                                }|                    dd          }	|                    dd          }
t          dt          |                    dd                              }|	r|
st          d          | d}t          5  t                               |           }|s	 ddd           dS |	|d<   ||d<   |
|d<   ||d<   d|d<   t#          j                    |d         z   |d<   ddd           n# 1 swxY w Y   t#          j                    |d         z   }d}|                    |                    d                    5 }t#          j                    |k     rut#          j        |           |                    | d|
|	dd	d
i          }|j        dk    r|                                }n!|j        dv rut          d|j                   ddd           n# 1 swxY w Y   |+t          5  d|d<   d|d <   ddd           n# 1 swxY w Y   dS |                    d!d          }|                    d"d          }|r|st          d#          |                    |                    d                    5 }|                    |d!|| d$||d%d	d&i'          }ddd           n# 1 swxY w Y   |j        dk    rt          d(|j                   |                                }|                    d)d          }|                    d*d          }|st          d+          dd,lm} t+          t-          |                     5   |||d-           ddd           n# 1 swxY w Y   t          5  d.|d<   ddd           n# 1 swxY w Y   t.                              d/|            dS # t2          $ r}t.                              d0| |           t          5  t                               |           }|rd1|d<   t7          |          |d <   ddd           n# 1 swxY w Y   Y d}~dS Y d}~dS d}~ww xY w)2u  Run the complete OpenAI Codex device-code flow.

    Codex doesn't use the standard OAuth device-code endpoints; it has its
    own ``/api/accounts/deviceauth/usercode`` (JSON body, returns
    ``device_auth_id``) and ``/api/accounts/deviceauth/token`` (JSON body
    polled until 200). On success the response carries an
    ``authorization_code`` + ``code_verifier`` that get exchanged at
    CODEX_OAUTH_TOKEN_URL with grant_type=authorization_code.

    The flow is replicated inline (rather than calling
    _codex_device_code_login) because that helper prints/blocks/polls in a
    single function — we need to surface the user_code to the dashboard the
    moment we receive it, well before polling completes.
    r   N)CODEX_OAUTH_CLIENT_IDCODEX_OAUTH_TOKEN_URLDEFAULT_CODEX_BASE_URLzhttps://auth.openai.comr
  r   z!/api/accounts/deviceauth/usercoder
  r	  r(  )r*  r   r"  r
  r   device_auth_idr   rI   5z8device-code response missing user_code or device_auth_idz/codex/devicer
  ri
  r
  rK	  z/api/accounts/deviceauth/token)r  r
  >   r  r   zdeviceauth/token poll returned rE	  r)  z#Device code expired before approvalr0	  r
  r
  z=device-auth response missing authorization_code/code_verifierz/deviceauth/callback)r
  r2  r
  r
  r
  z!application/x-www-form-urlencoded)r  r   ztoken exchange returned r=
  r
  z*token exchange did not return access_token)_save_codex_tokens)r=
  r
  r
  z7oauth/device: openai-codex login completed (session=%s)z0codex device-code worker failed (session=%s): %sr  )r  r  r  r  r  r  r  postr   r  r  r*  r   rN  rw  rv
  rj
  rC  r
  r
  r  ru  r
  rP   rQ   rZ   r3  r  )r  r  r  r  r  issuerr-  r/  r
  r
  r  r
  r
  rt
  r
  	code_respr  r
  r
  
token_respr  r=
  r
  r  r  r>  s                             rU   r
  r
  T%  s"   e,	
 	
 	
 	
 	
 	
 	
 	
 	
 	

 + \\%--"5"5\66 	&;;<<<!#89');<   D	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 s""=dCCDDDiikkOOK44	$)92>>As;??:s#C#CDDEE 	[ 	[YZZZ$333! 		B 		B"&&z22D 		B 		B 		B 		B 		B 		B 		B 		B !*D'7D#$%3D!",D!(D!%tL/A!AD		B 		B 		B 		B 		B 		B 		B 		B 		B 		B 		B 		B 		B 		B 		B >##d<&88	\\%--"5"5\66 	Y&.""X--
=))){{===,:SS+-?@ #  
 #s** $		I#z11"#WTEU#W#WXXX	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y % N N!*X(M_%N N N N N N N N N N N N N N N F ']]+?DD!or::! 	` 	`^___\\%--"5"5\66 	&%"6.'-$C$C$C!6%2  ()LM % 
 
J	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 !S((R*:PRRSSS""zz."55

?B77 	MKLLL6666662:>>?? 	 	 ,!.      	 	 	 	 	 	 	 	 	 	 	 	 	 	 	
 " 	( 	('DN	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	(		KZXXXXX , , ,GUVWWW! 	, 	,##J//A ,%(%(VV/"		, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	,,sM  9Q  A'Q 'A++Q .A+/B:Q )FQ 8FQ FQ F A
Q *BJ7Q JQ 
JQ J."Q .J22Q 5J26Q ;A(Q #$MQ MQ MBQ 3PQ PQ P
Q  P2&Q 2P66Q 9P6:Q 
S#$#S4S;SS	SS	SS#z(/api/providers/oauth/{provider_id}/startc                    K   t          |           t                       t          |           d t          D             } |vrt	          dd            t           fdt          D                       }|d         dk    rt	          d  d|d	          d
          	 |d         dk    r dk    rt          |          S |d         dk    rt           |           d{V S nU# t          $ r  t          $ r>}t          
                    d            t	          dt          |                    d}~ww xY wt	          dd          )z.Initiate an OAuth login flow. Token-protected.c                     h | ]
}|d          S r`
  r  r  s     rU   r  z$start_oauth_login.<locals>.<setcomp>%  s    666QtW666rW   r   zUnknown provider r   c              3   4   K   | ]}|d          k    |V  dS )r  Nr  )r   r   r:
  s     rU   r   z$start_oauth_login.<locals>.<genexpr>%  s1      VVqqw+?U?U?U?U?U?UVVrW   r/
  r4
  z uses an external CLI; run `r0
  z
` manuallyr7
  r
  r
  r-
  Nzoauth/start %s failedr!  zUnsupported flow)r   rx
  r|
  r9
  r5   r   r
  r
  rZ   rP   r"  r  )r:
  r   r  validcatalog_entryr  s   `     rU   start_oauth_loginr   %  s      7G$$$665666E%4U4U4UVVVVVVVV$;VVVVVMV
**!gg}]?[ggg
 
 
 	
<  F**{k/I/I(9999 M110gNNNNNNNNNN 2    < < <.<<<CFF;;;;< C0B
C
C
CCs   #!C) "C) )D;=9D66D;c                   $    e Zd ZU eed<   eed<   dS )OAuthSubmitBodyr  r2  Nr  r  rW   rU   r"  r"  %  s"         OOO
IIIIIrW   r"  z)/api/providers/oauth/{provider_id}/submitc                    K   t          |           | dk    r?t          j                                        dt          |j        |j        |           d{V S t          dd|            )z5Submit the auth code for PKCE flows. Token-protected.r
  Nr   zsubmit not supported for r   )r   rm   rI  rs   r
  r  r2  r5   )r:
  r0  r   r  s       rU   submit_oauth_coder$  %  s       7k!!-//??($/49g
 
 
 
 
 
 
 
 	
 C0YK0Y0Y
Z
Z
ZZrW   z4/api/providers/oauth/{provider_id}/poll/{session_id}c                 >  K   t           5  t                              |          }ddd           n# 1 swxY w Y   |st          dd          |d         | k    rt          dd          ||d         |                    d	          |                    d
          dS )u  Poll a session's status (no auth — read-only state).

    Shared by the device-code flows (Nous, OpenAI Codex, MiniMax, xAI).
    Each surfaces progress through the same background-worker-updated
    ``status`` field, so a single poll endpoint serves them all.
    Nr   zSession not found or expiredr   rT   r   zProvider mismatch for sessionr)  r0	  rK	  )r  r)  r0	  rK	  )rv
  rj
  r   r5   )r:
  r  r  rt
  s       rU   poll_oauth_sessionr&  &  s       
 / /"":../ / / / / / / / / / / / / / / T4RSSSSJ;&&4STTTT x./22hh|,,	  s   155z*/api/providers/oauth/sessions/{session_id}c                    K   t          |           t          5  t                              | d          }ddd           n# 1 swxY w Y   |dddS d| dS )z0Cancel a pending OAuth session. Token-protected.NFzsession not found)rs  r   T)rs  r  )r   rv
  rj
  r  )r  r   r  rt
  s       rU   cancel_oauth_sessionr(   &  s       7	 5 5"":t445 5 5 5 5 5 5 5 5 5 5 5 5 5 5|(;<<<j111s   AAAc           
         d }|                     |           }|r|                    |          sdg fS t          |dd          p2t          |dd          p!t          |dd          pt          |dd          }g }|i|                    d|f                                          }|D ]<}|                     ||dd	           ||d
d           ||dd          d           =n|                    dd	d          }i }|D ]Y}|                    d          }	|                    d
          }
|	r+|
r)|                    |
g                               |           Zd }|}|g}|h|                    |          r{fd||         D             }|snd|	                    |d           |d	         d         }|                    |           
                    |           |                    |          {||fS )zResolve a session id to the newest child leaf session.

    /model may create child sessions. Dashboard refresh should continue the
    newest child instead of reopening the old parent.
    c                     t          | t                    r|                     |          S 	 | |         S # t          $ r 	 | |         cY S # t          $ r Y Y d S w xY ww xY wr  )rv  r  r   rZ   )r  r  indexs      rU   row_getz+_session_latest_descendant.<locals>.row_get;&  s    c4   	 773<<	s8O 	 	 	5z!!!   ttt	s,   4 
AA	A	
AAAANconn_conn
connection_connectiona  
            WITH RECURSIVE descendants(id, parent_session_id, started_at) AS (
                SELECT id, parent_session_id, started_at FROM sessions WHERE id = ?
                UNION
                SELECT s.id, s.parent_session_id, s.started_at
                FROM sessions s
                JOIN descendants d ON s.parent_session_id = d.id
            )
            SELECT id, parent_session_id, started_at FROM descendants
            r  r   r  r   r<  r  )r  r  r<  '  T)r7  r  r8  c                 n    	 t          |                     d          pd          S # t          $ r Y dS w xY w)Nr<  r           )rx  r   rZ   )r  s    rU   startedz+_session_latest_descendant.<locals>.startedp&  sH    	..3!444 	 	 	33	s   #& 
44c                 B    g | ]}|                     d           v|S r`
  r   )r   rr  s     rU   r  z._session_latest_descendant.<locals>.<listcomp>{&  s-    NNNAaeeDkk6M6Ma6M6M6MrW   rp  )resolve_session_idr  r   executefetchallr   rB  r   rk  r  r  )r  rE  r,  r  r-  r   raw_rowsr  childrenridr  r4  r  r   
candidatesr  s                  @rU   _session_latest_descendantr>  5&  s   	 	 	 



+
+C bnnS)) Rx 	FD!! 	,2w%%	,2|T**	, 2}d++	 	 D<<	 F
 
 (** 	  	 	CKKgc4++%,WS2Eq%I%I%gc<;;     	 $$5$NNH 8 8ggdmm,-- 	86 	8++223777   G5D5D
,,w

 NNNN'!2NNN
 	GT222Q-%G ,,w

  D=rW   c                   @    e Zd ZU ee         ed<   dZee         ed<   dS )BulkDeleteSessionsr  Nr  r  r  rW   rU   r@  r@  &  s3         	cNNN!GXc]!!!!!rW   r@  c                   P    e Zd ZU eeeef                  ed<   dZe	e         ed<   dS )SessionImportrF  Nr  )
r  r  r  r   r   r  r   r  r  r   r  rW   rU   rB  rB  &  s?         4S>""""!GXc]!!!!!rW   rB  c                   K   t                      }|                                 2 3 d {V }t          |          t          |          z   t          k    rt	          dd          |                    |           V6 t          |          S )Nr
  z#Session import payload is too larger   )	bytearrayr	  r   _SESSION_IMPORT_MAX_BYTESr5   r  rt  )r   r0  r  s      rU   _read_session_import_bodyrF  &  s      ;;D~~''       et99s5zz!$===C8]^^^^E ( ;;s   A;c                     t          |           }	 |                    |          |                                 S # |                                 w xY wr  )ra  import_sessionsr   )r  rF  rE  s      rU   _import_sessions_for_profilerI  &  sF    	%g	.	.B!!(++








s	   : Az/api/sessions/bulk-deletec                     K   t           j                  dk    rt          dd          dt          f fd}t	          j        |           d{V }d|d	S )
u  Delete every session in ``body.ids`` in a single DB transaction.

    Backs the dashboard's bulk-select-and-delete flow on the sessions
    page. POST (not DELETE) because most HTTP clients refuse to send a
    request body on DELETE and a body is the natural shape for a list
    of IDs — Starlette accepts both, but POSTing a list keeps proxies,
    curl, and the browser ``fetch`` API consistent.

    Per-row contract matches :meth:`SessionDB.delete_sessions`:

    * Unknown IDs are silently skipped (the response ``deleted`` count
      reflects what really happened, not the input length). This is
      deliberate — UI selection state can race against another tab's
      delete, and we'd rather succeed-on-the-rest than fail-the-whole-
      batch.
    * Children of every deleted parent are orphaned, not cascade-
      deleted.
    * Active and archived sessions ARE deleted when explicitly
      selected — unlike ``DELETE /api/sessions/empty``, the user
      hand-picked the rows so we trust the selection.
    * Like the other session-delete endpoints, this does NOT pass a
      ``sessions_dir`` through; on-disk transcript / request-dump
      cleanup runs at the CLI/agent layer on the next prune pass.

    The response carries the actual deleted count, so the dashboard
    can surface it in a toast. The IDs that were removed are not
    echoed back because the client already knows what it asked to
    delete (unknown IDs are silently skipped — see contract above)
    and can prune its in-memory list directly from the request.
    r!  r   z$ids must contain at most 500 entriesr   rJ   c                      t          j                  } 	 |                     j                  |                                  S # |                                  w xY wr  )ra  r  delete_sessionsr  r   )rE  r0  s    rU   _deletez.bulk_delete_sessions_endpoint.<locals>._delete&  sK    )$,77	%%dh//HHJJJJBHHJJJJs   A ANTrs  deleted)r   r  r5   rw  rm   rd  )r0  rM  rO  s   `  rU   bulk_delete_sessions_endpointrP  &  s      J 48}}s9
 
 
 	
S       %g........G7+++rW   z/api/sessions/importc                   K   	 t          |            d{V }t                              |          }n.# t          $ r  t          $ r}t          dd          |d}~ww xY w	 t          j        t          |j        |j	                   d{V }n1# t          $ r$}t          dt          |                    |d}~ww xY w|                    dd          st          d|          |S )a"  Import one or more sessions exported from the dashboard or CLI.

    This is intentionally separate from ``/api/ops/import``: that endpoint
    restores a whole Hermes backup archive, while this endpoint is scoped to
    session rows/messages and is safe to use from the Sessions page.
    Nr   zInvalid session import payloadr   rs  F)rF  rB  model_validate_jsonr5   r  rm   rd  rI  r  rF  r  r   )r   raw_bodyr0  rN  r  s        rU   import_sessions_endpointrT  &  s2     _27;;;;;;;;00::    _ _ _4TUUU[^^_G()Et|UYUbcccccccc G G GCHH===3FG ::dE"" <F;;;;Ms-   /4 AAA#+B 
B=B88B=z/api/sessions/empty/countc                 Z    K   dt           f fd}dt          j        |           d{V iS )u   Return the number of empty, ended, non-archived sessions.

    Drives the dashboard's "Delete empty (N)" button — when N is 0 the
    UI hides the affordance so users aren't presented with a button
    that does nothing. Cheap, single-COUNT query.
    rJ   c                      t                    } 	 |                                 |                                  S # |                                  w xY wr  )ra  count_empty_sessionsr   rE  r  s    rU   _countz-count_empty_sessions_endpoint.<locals>._count'  sE    )'22	**,,HHJJJJBHHJJJJ	   : Ar  Nrw  rm   rd  )r  rY  s   ` rU   count_empty_sessions_endpointr\   '  sV      C       7,V4444444455rW   z/api/sessions/emptyc                 `    K   dt           f fd}t          j        |           d{V }d|dS )uX  Delete every empty (``message_count == 0``), ended,
    non-archived session in a single transaction.

    Safety contract mirrors :meth:`SessionDB.delete_empty_sessions`:

    * Active sessions are skipped (``ended_at IS NULL``) so a live
      agent isn't yanked mid-handshake.
    * Archived sessions are skipped — the user explicitly chose to
      keep those rows.
    * Children of deleted parents are orphaned, not cascade-deleted.

    Like the single-session ``DELETE /api/sessions/{id}`` endpoint
    below, this doesn't pass a ``sessions_dir`` through — the on-disk
    transcript / request-dump cleanup is wired at the CLI/agent layer
    but the web server historically leaves file cleanup to the next
    prune-on-startup pass. Matching that pre-existing trade-off keeps
    the two delete endpoints' DB-vs-disk behaviour consistent.
    rJ   c                      t                    } 	 |                                 |                                  S # |                                  w xY wr  )ra  delete_empty_sessionsr   rX  s    rU   rM  z/delete_empty_sessions_endpoint.<locals>._delete&'  sE    )'22	++--HHJJJJBHHJJJJrZ  NTrN  r[  )r  rM  rO  s   `  rU   delete_empty_sessions_endpointr`  '  s^      (S       %g........G7+++rW   z/api/sessions/statsc                   K   t          |           }	 |                    d          }|                    d          }|                    d          }|                                }i }	 |                    ddd          D ]B}t	          |                    d          pd          }|                    |d	          d
z   ||<   Cn# t          $ r Y nw xY w|||||d|                                 S # |                                 w xY w)zSession-store statistics for the Sessions page (mirrors `hermes sessions stats`).

    Registered before ``/api/sessions/{session_id}`` so the literal ``stats``
    path isn't captured as a session id by the parameterized route.
    T)rY  F)rZ  r1  )r7  rY  r8  r   r3  r   r   )r  active_storerJ  messages	by_source)ra  rb  message_countrB  r  r   rZ   r   )	r  rE  r  rb  rJ  rc  rd  r>  rq  s	            rU   get_session_statsrf  1'  s;      
&g	.	.B  $ 77'''??##$#77##%%$&		**\`*aa ; ;!%%//2U33!*sA!6!6!:	#;  	 	 	D	 (  "
 
 	







s1   AC4 ,AC C4 
CC4 C
C4 4D
c                     ddl m} | s
 |            S t          |           \  }} |t          |          dz            S )uk  Open a SessionDB for read paths, optionally for another profile.

    ``profile`` None/empty → this process's own ``state.db`` (the common,
    single-profile case). A named profile opens that profile's on-disk
    ``state.db`` directly so the primary backend can serve cross-profile reads
    (transcripts, detail) without spawning that profile's backend.
    r   rh  rl  )rm  )r@  r4  r`  r   )r  r4  _namer  s       rU   ra  ra  P'  sY     '&&&&& y{{$W--KE49T$ZZ*45555rW   z/api/sessions/{session_id}c                 :  K   t          |          }	 |                    |           }|r|                    |          nd }|st          dd          |rt	          |          d         |d<   ||                                 S # |                                 w xY w)Nr   Session not foundr   r   r  )ra  r7  r  r5   r`  r   )r  r  rE  r  r   s        rU   get_session_detailrk  _'  s      	%g	.	.B	##J//),6"..%%%$ 	MC8KLLLL 	@!3G!<!<Q!?GI








s   AB Bz,/api/sessions/{session_id}/latest-descendantc                     K    fd}t          j        |           d {V \  }}|st          dd          |r|d         n ||t          |o||d         k              dS )Nc                      t                    } 	 t          |           |                                  S # |                                  w xY wr  )ra  r>  r   )rE  r  r  s    rU   _lookupz.get_session_latest_descendant.<locals>._lookupt'  sC    )'22	-j"==HHJJJJBHHJJJJs	   6 Ar   rj  r   r   )requested_session_idr  r   r?  )rm   rd  r5   r   )r  r  rn  latestr   s   ``   rU   get_session_latest_descendantrq  o'  s      
      !*733333333LFD I4GHHHH+/ ?QZ247!233	  rW   z#/api/sessions/{session_id}/messagesc                     K    fd}t          j        |           d {V }|t          dd          |\  }}}|||t          |          ddS )Nc                  b   t                    } 	 |                               }|s	 |                                  d S |                     |          }t	          d          nd }|||                     ||          f|                                  S # |                                  w xY w)Nr!  r7  r  )ra  r7  r   resolve_resume_session_idr  get_messages)rE  r  _limitr7  r  r  r  s      rU   _readz#get_session_messages.<locals>._read'  s    )'22		''
33C  HHJJJJJ ..s33C(-(9S___tF6& Q QQHHJJJJBHHJJJJs   B  AB B.r   rj  r   )r7  r  returned)r  rc  
pagination)rm   rd  r5   r   )	r  r  r7  r  rx  r  r  rw  rc  s	   ````     rU   get_session_messagesr{  '  s              $U++++++++F~4GHHHH"CH
 
  rW   c                 J    K    fd}t          j        |           d {V S )Nc                     t                    } 	 |                               }|sddd|                                  S |                     |           ddi|                                  S # |                                  w xY w)NT)rs  already_absentrs  )ra  r7  r   delete_sessionrE  r  r  r  s     rU   rM  z(delete_session_endpoint.<locals>._delete'  s    )'22	 ''
33C <"d;; HHJJJJ c"""$<HHJJJJBHHJJJJs   A/ A/ /B)rm   rd  )r  r  rM  s   `` rU   delete_session_endpointr  '  sK      
     ( "7+++++++++rW   c                   ^    e Zd ZU dZee         ed<   dZee         ed<   dZ	ee         ed<   dS )SessionRenameNr   rJ  r  )
r  r  r  r   r   r  r  rJ  r   r  r  rW   rU   r  r  '  sP         E8C=#Hhtn### "GXc]!!!!!rW   r  c                 |  K   t          |j                  }	 |                    |           }|st          dd          |j        |j        t          dd          |j        O	 |                    ||j        pd           n0# t          $ r#}t          dt          |                    d}~ww xY w|j        |	                    ||j                   d|
                    |          pdd	}|j        t          |j                  |d
<   ||                                 S # |                                 w xY w)a   Update a session: rename (or clear its title) and/or archive it.

    ``title`` renames (empty/null clears the title); ``archived`` soft-hides or
    restores the session. Either field may be omitted. ``profile`` targets
    another profile's session.
    r   rj  r   Nr   z5Nothing to update; provide 'title' and/or 'archived'.r   T)rs  r   rJ  )ra  r  r7  r5   r   rJ  set_session_titler  r  set_session_archivedget_session_titler   r   )r  r0  rE  r  r  r  s         rU   rename_session_endpointr  '  sf      
&dl	3	3B##J// 	MC8KLLLL:$-"7N    :!D$$S$**:;;;; D D D#CFFCCCCD =$##C777r';';C'@'@'FBGG=$!%dm!4!4F:








s1   AD% 'B D% 
B2B--B22AD% %D;z!/api/sessions/{session_id}/exportc                 t    K    fd}t          j        |           d{V }|t          dd          |S )z6Export a single session (metadata + messages) as JSON.c                      t                    } 	 |                               }|r|                     |          nd 	 |                                  S # |                                  w xY wr  )ra  r7  export_sessionr   r  s     rU   _exportz(export_session_endpoint.<locals>._export'  sg    )'22	''
33C-0:2$$S)))d:HHJJJJBHHJJJJs   -A A+Nr   rj  r   )rm   rd  r5   )r  r  r  r  s   ``  rU   export_session_endpointr  '  sh            "7++++++++D|4GHHHHKrW   c                   h   e Zd ZU dZee         ed<   dZee         ed<   dZ	ee         ed<   dZ
ee         ed<   dZee         ed<   dZee         ed<   dZee         ed	<   dZee         ed
<   dZee         ed<   dZee         ed<   dZee         ed<   dZee         ed<   dZee         ed<   dZee         ed<   dZee         ed<   dZee         ed<   dZee         ed<   dZee         ed<   dZee         ed<   dZee         ed<   dZee         ed<   dZee         ed<   dZeed<   dZ eed<   dS )SessionPruneZ   older_than_daysNr   r  started_beforestarted_after
title_liker  rM  rI  max_messages
model_likerT   r  chat_id	chat_typebranch_like
min_tokensr  min_costmax_costmin_tool_callsmax_tool_callsFrY  dry_run)!r  r  r  r  r   rx  r  r   r  r  r  r  r  r  rM  rI  rw  r  r  rT   r  r  r  r  r  r  r  r  r  r  rY  r   r  r  rW   rU   r  r  '  s        ')OXe_))) FHSM   !GXc]!!!&*NHUO***%)M8E?))) $J$$$ $J$$$ $J$$$"&L(3-&&&"&L(3-&&& $J$$$"Hhsm"""!GXc]!!!!GXc]!!!#Ix}###!%K#%%% $J$$$ $J$$$ $Hhuo$$$ $Hhuo$$$$(NHSM((($(NHSM((("d"""GTrW   r  c                      j         dup j        du} j         j        dk     r|st          dd          t	           fddD                       }d j        v } j        }|s|r|sd} j        rt           j                  d         nt                      }t           j                  }	 t          d(i d|d	 j        pdd
 j         d j        d j        pdd j        pdd j        pdd j        d j        d j        pdd j        pdd j        pdd j        pdd j        pdd j        pdd j        d j        d j        d j        d j        d j        d j        rdnd} j        ra |j        d(i |}dd tA          |          |r|d          d!         nd|r|d"         d!         ndd# |D             d$|!                                 S |d%z  }	 |j"        d(d&|	#                                r|	ndi|}
d|
d'|!                                 S # |!                                 w xY w))zIDelete ended sessions matching filters (mirrors `hermes sessions prune`).Nr   r   zolder_than_days must be >= 1r   c              3   <   K   | ]}t          |          d uV  d S r  )r   )r   ro  r0  s     rU   r   z"_prune_sessions.<locals>.<genexpr>%(  sF       	 	 	a$	 	 	 	 	 	rW   )r   r  r  rM  rI  r  r  rT   r  r  r  r  r  r  r  r  r  r  r  r   r  r  r  r  rM  rI  r  r  rT   r  r  r  r  r  r  r  r  r  r  rJ  FTr   r<  r   c           	          g | ]I}|d          |d         |                     d          |                     d          |d         |d         dJS )r  r   r   r  r<  re  )r  r   r   r  r<  re  r   r   r6  s     rU   r  z#_prune_sessions.<locals>.<listcomp>W(  sh     
 
 
   g"#H+!"w!"w&'o)*?); 
 
 
rW   )rs  r  matchedoldest_started_atnewest_started_atrF  rF  sessions_dir)rs  r  r  )$r  r  r  r5   r  model_fields_setr  r`  r   ra  r  r   r  r  rM  rI  r  r  rT   r  r  r  r  r  r  r  r  r  r  rY  r  list_prune_candidatesr   r   prune_sessionsrA  )r0  
has_window_attr_filters_set_older_than_explicit_effective_older_thanrb  rE  filtersr   r  r  s   `          rU   _prune_sessionsr  (  s    	4'I4+=T+I  'D,@1,D,DZ,D4RSSSS  	 	 	 	
	 	 	 	 	 -0EE 0 %' %0D % $:>,]%dl33A66OL]L]L	%dl	3	3B5 
 
 
11
K'4
  ..
 ,,	

 /4
 /4
 /4
 **
 **
 /4
 m+t
 \)T
 \)T
 ~-
 )1T
  !
" #
$ ]]%
& ]]'
(  ..)
*  ..+
, "2=TT-
0 < 	+2+66g66Dt99>B%LT!W\%:%:?C%MT"Xl%;%;
 
 "
 
 
 4 	



 $j0#"# 
 
)5)<)<)>)>HD

 
 w//








s   0D H( %.H( (H>z/api/sessions/prunec                 F   K   t          j        t          |            d{V S )zGDelete ended sessions matching filters without blocking the event loop.N)rm   rd  r  r6  s    rU   prune_sessions_endpointr  m(  s-       "?D999999999rW   z	/api/logsr  level	componentr  c                   K   ddl m}m} |                    |           }|st	          dd|            t                      dz  |z  }|                                s| g dS 	 ddlm}	 n# t          $ r i }	Y nw xY w|r|
                                d	k    r|nd }
|rg|                                d
k    rO|	                    |          }|7t	          dd| dd                    t          |	                               nd }t          |
p|p|          } |||st          |d          nd||
|          }|r9|                                fd|D             t          |d           d          }| |dS )Nr   )
_read_tail	LOG_FILESr   zUnknown log file: r   rf  )r  rZ  )COMPONENT_PREFIXESALLrf  zUnknown component: ra
  r  r!  r>  )has_filters	min_levelcomponent_prefixesc                 @    g | ]}|                                 v |S r  r  )r   lneedles     rU   r  zget_logs.<locals>.<listcomp>(  s+    ;;;v':':!':':':rW   )hermes_cli.logsr  r  r   r5   r   rA  hermes_loggingr  r`   r	  r   r  r~  r   r  )r  rZ  r  r  r  r  r  log_namer  r  r  comp_prefixesr  r  r  s                 @rU   get_logsr  x(  s#      65555555}}T""H Q4O4O4OPPPP  6)H4H?? +r*** 5555555        C5;;==E#9#9tI 	Y__&&%//*..y99 MY M M%)YYv6H/I/I%J%JM M    ! y;M;V<<KZ9#eS///T(	  F  O;;;;V;;;S__<L<M<MN6***s   %A, ,A;:A;c                   :   e Zd ZU dZeed<   eed<   dZeed<   dZeed<   dZe	e
e                  ed<   dZe	e         ed	<   dZe	e         ed
<   dZe	e         ed<   dZe	e         ed<   dZe	e         ed<   dZe	e
e                  ed<   dZe	e         ed<   dZeed<   dS )CronJobCreater   rD   schedulerR   r!  deliverNrW  r  rT   r  scriptcontext_fromr6  workdirFno_agent)r  r  r  rD   r  r  rR   r  rW  r   r   r  rT   r  r  r  r   r6  r  r  r   r  rW   rU   r  r  (  s        FCMMMD#NNNGS"&FHT#Y&&&E8C="Hhsm""""Hhsm""" FHSM   "&L(3-&&&,0htCy)000!GXc]!!!HdrW   r  c                       e Zd ZU eed<   dS )CronJobUpdaterb  N)r  r  r  r  r  r  rW   rU   r  r  (           MMMMMrW   r  strip_trailing_slashr  c                    | d S t          |                                           }|r|                    d          }|pd S )Nr   )r  r   r$  )rr  r  r  s      rU   _cron_optional_textr  (  sF    }tu::D  {{3<4rW   c                     | d S t          | t                    rt          j        d|           }n!t          | t          t
          f          r| }nd S d |D             }|pd S )Nz[\n,]c                     g | ]D}t          |                                          #t          |                                          ES r  r  r  s     rU   r  z%_cron_string_list.<locals>.<listcomp>(  s=    JJJ4D		8I8IJSYY__JJJrW   )rv  r  r_  r   r  r  )rr  	raw_itemsr  s      rU   _cron_string_listr  (  sp    }t% HXu--			ED%=	)	) 		tJJ9JJJE=DrW   c                 P   t          |           }|sdS |dz                                  }t          |                                          }|                                r|                                n||z                                  }	 |                    |          }n'# t          $ r}t          dd|           |d}~ww xY w|                                st          dd|           |	                                st          dd|           t          |          S )zFValidate a dashboard-selected cron script against the profile sandbox.Nscriptsr   zscript must be inside r   zscript does not exist: zscript is not a file: )r  r  r   r  r  relative_tor  r5   rA  r  r  )rr  rb  r  scripts_rootr  r  relativerN  s           rU    _normalize_dashboard_cron_scriptr  (  sU   u%%D t 9,5577LDzz$$&&H&.&:&:&<&<e  """<RZCZBcBcBeBeI((66   :L::
 
 
 	
  [4Yi4Y4YZZZZ Z4XY4X4XYYYYx==s   B" "
C,CCjobc                    t          |                     d                    }t          |                     d                    }t          |                     d                    p!t          |                     d                    }t          |                     d                    }|r|st	          dd          d S |s|s|st	          dd	          d S d S d S )
NrD   r  rW  skillr  r   zno_agent=True requires a scriptr   z2agent cron jobs require a prompt, skill, or script)r  r   r  r   r5   )r  rD   r  rW  r  s        rU   &_validate_dashboard_cron_effective_jobr  (  s    !2!233F !2!233Fswwx0011X5FswwwGWGW5X5XFCGGJ''((H  	8    	 
f 
 
G
 
 
 	

 
 
 
 
 
rW   c                    t          | pi           }dD ]}||v rt          ||                   ||<   d|v rt          |d         |          |d<   d|v rt          |d         d          |d<   d|v rt          |d                   pd|d<   d|v rt          |d                   |d<   d	|v rt          |d	                   |d	<   |S )
a5  Normalize dashboard JSON into cron.jobs.update_job's storage shape.

    This intentionally stays in the dashboard adapter layer: cron/jobs.py is the
    source of truth for scheduling behaviour; the dashboard only translates form
    payloads into the shapes that existing core functions already accept.
    )r  rT   r  r  r  Tr  r  r!  r  r6  )r  r  r  r  )rb  rb  r  r  s       rU   !_normalize_dashboard_cron_updatesr  )  s    gm$$J/ C C*1*S/BBJsO:?x  
  

8 Z!4z""
 "
 "

: J 3Jy4I J J Ug
9##%6z.7Q%R%R
>"Z'')::FX;Y)Z)Z
%&rW   refsrc  c           	      j    | sd S | D ]+}t          |d|          st          dd| d| d          ,d S )Nget_jobr   zcontext_from job 'z' not found in profile 'ri  r   )_call_cron_for_profiler5   )r  rc  r,  s      rU   %_validate_dashboard_cron_context_fromr  ))  s        %lIsCC 	( ( ($( ( (   	 rW   c                      ddl m}  	 d |                                 D             S # t          $ r, t                              d           t          |           cY S w xY w)zCReturn dashboard profile records, falling back to a directory scan.r   ri  c                 ,    g | ]}t          |          S r  _profile_to_dictr  s     rU   r  z'_cron_profile_dicts.<locals>.<listcomp>>)  s!    JJJ ##JJJrW   zJFailed to list profiles for cron dashboard; falling back to directory scan)r[   r}  rs  rZ   rP   r"  _fallback_profile_dicts)ru  s    rU   _cron_profile_dictsr  :)  s{    3333335JJ\-G-G-I-IJJJJ 5 5 5cddd&|444445s   & 3AAc                 x   ddl m} | pd                                pd}	 |                    |          }|                    |           n0# t
          $ r#}t          dt          |                    d}~ww xY w|                    |          st          dd| d	          ||	                    |          fS )
z=Resolve a profile query value to (profile_name, HERMES_HOME).r   ri  r1  r   r   Nr   	Profile '' does not exist.)
r[   r}  r   normalize_profile_namevalidate_profile_namer  r5   r  profile_existsrt  )r  ru  r  canonr  s        rU   r`  r`  D)  s    333333i
&
&
(
(
5IC<33C88**51111 < < <CFF;;;;<&&u-- Z4X4X4X4XYYYY,..u5555s   *A 
A8A33A8r  c                 n    t          |           }||d<   ||d<   t          |          |d<   |dk    |d<   |S )Nr  rc  r  r1  r^  )r  r  )r  r  r  	annotateds       rU   _annotate_cron_jobr  S)  sG    S		I"Ii 'In"4yyIm&-&:I"#rW   r	  	func_namec                   	
 t          |           \  
	ddlm} ddlm}m}  |t          	                    }	 |                    	          5   t          ||          |i |}ddd           n# 1 swxY w Y    ||           n#  ||           w xY wt          |t                    r	
fd|D             S t          |t                    rt          |
	          S |S )a,  Run cron.jobs helpers against the selected profile's cron directory.

    The dashboard is a single process that can inspect many profiles. Route
    storage through cron.jobs' execution-context override so dashboard calls
    cannot retarget a concurrent desktop ticker's load/save transaction.
    r   jobsreset_hermes_home_overrideset_hermes_home_overrideNc                 2    g | ]}t          |          S r  )r  )r   jr  rc  s     rU   r  z*_call_cron_for_profile.<locals>.<listcomp>r)  s&    JJJa"1lD99JJJrW   )r`  rX  r  r4  r  r  r  use_cron_storer   rv  r  r  r  )r	  r  ri   kwargs	cron_jobsr  r  r   r  r  rc  s            @@rU   r  r  \)  s    ,N;;L$&&&&&&       
 %$SYY//E*%%d++ 	D 	D2WY	22DCFCCF	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	#"5))))""5))))&$ KJJJJJ6JJJJ&$ >!&,===Ms/   B A4(B 4A88B ;A8<B Bjob_idc                      t                      D ]Y}t          |                    d          pd          }|s)t          |dd          }t	           fd|D                       r|c S Zd S )NrR   r   	list_jobsTc              3   |   K   | ]6}|                     d           k    p|                     d          k    V  7dS )r  rR   Nr   )r   r  r  s     rU   r   z)_find_cron_job_profile.<locals>.<genexpr>~)  sF      NNAquuT{{f$?f(?NNNNNNrW   )r  r  r   r  r  )r  r  rR   r  s   `   rU   _find_cron_job_profiler	  x)  s    &((  7;;v&&,"-- 	%dK>>NNNNNNNNN 	KKK	4rW   c                    | pd                                 }|                                dk    rt          |dd          S g }t                      D ]y}t	          |                    d          pd          }|s)	 |                    t          |dd                     O# t          $ r t          	                    d|           Y vw xY w|S )Nrf  r  TrR   r   z'Failed to list cron jobs for profile %s)
r   r   r  r  r  r   r  rZ   rP   r"  )r  r	  r  r  rR   s        rU   _list_cron_jobs_syncr  )  s    !E((**IE!!%idCCC!#D#%% L L488F##)r** 		LKK.t[$GGHHHH 	L 	L 	LNNDdKKKKK	LKs   :$B%CCc                    K   t          j        |           rt          d          t          | g|R i | d{V }t          j        |          rt          d          |S )zBRun cron dashboard profile/job I/O outside the FastAPI event loop.z2_run_cron_dashboard_io only accepts sync callablesNz:_run_cron_dashboard_io sync callable returned an awaitable)inspectiscoroutinefunctionrl  rB   isawaitable)funcri   r  r  s       rU   _run_cron_dashboard_ior  )  s      "4(( NLMMM$T;D;;;F;;;;;;;;F6"" VTUUUMrW   z/api/cron/jobsc                 <   K   t          t          |            d {V S r  )r  r  r
  s    rU   list_cron_jobsr  )  s)      '(<gFFFFFFFFFrW   c                     |pt          |           }|st          dd          t          |d|           }|st          dd          |S )Nr   Job not foundr   r  r	  r5   r  r  r  selectedr  s       rU   _get_cron_job_syncr  )  sc    8088H EODDDD
 9f
=
=C EODDDDJrW   z/api/cron/jobs/{job_id}c                 >   K   t          t          | |           d {V S r  )r  r  r  r  s     rU   get_cron_jobr  )  s+      '(:FGLLLLLLLLLrW   c           	         |pt          |           }| }|r=t          |d|           }|r*|                    d          rt          |d                   }	 t	          dt          t          |          d                    }n# t          t          f$ r d}Y nw xY wt          |          }	 |
                    ||d          }t          j                    }	|D ]y}
|
                    d          d	u o0|	|
                    d
|
                    dd                    z
  dk     |
d<   t          |
                    d                    |
d<   |r||
d<   z||d|                                 S # |                                 w xY w)u  Run sessions produced by a cron job, newest first.

    Cron runs are stored as ordinary sessions whose id is
    ``cron_{job_id}_{timestamp}`` (see cron/scheduler.run_job). A job's history
    is therefore every session whose id carries that prefix; ``source='cron'``
    narrows it and the id prefix binds it to this job. Powers the run-history
    list under each job in the desktop cron detail. Same row shape as
    ``/api/sessions`` so the frontend can reuse SessionInfo.

    Backed by ``SessionDB.list_cron_job_runs`` — a bounded ``[prefix, hi)``
    id-range scan, not the compression-chain CTE used for the recents list,
    so the cost scales with the requested window and not the (unbounded) total
    cron history.
    r  r  r   r  r  r   rt  r:  Nr;  r<  r=  r]  rJ  r  )runsr7  )r	  r  r   r  rN  r  rw  rl  r  ra  list_cron_job_runsrC  r   r   )r  r  r7  r  r  r  limit_nrE  r  r?  r>  s              rU   _list_cron_job_runs_syncr!  )  s    8088HI '$Xy&AA 	'3774== 	'CIIaSZZ--..z"    
&h	/	/B$$Yga$HHikk 	( 	(Aj!!T) O155lA0F0FGGG3N kN !z!2!233AjM (')w//








s   +B   BB)B+E) )E?z/api/cron/jobs/{job_id}/runsc                 @   K   t          t          | ||           d {V S r  )r  r!  )r  r  r7  s      rU   r  r  )  s.      '(@&'SXYYYYYYYYYrW   c                    	 t          |          \  }}t          | j        |          }t          | j                  }t          | j                  }t          ||           t          | j                  }t          | j
        |||d           t          |d| j
        pd| j        | j        t          | j                  pd|t          | j                  t          | j                  t          | j        d          ||t          | j                  t          | j                  |          S # t*          $ r  t,          $ r=}t.                              d           t+          d	t3          |          
          d }~ww xY w)N)rD   rW  r  r  
create_jobr   r!  Tr  )rD   r  rR   r  rW  r  rT   r  r  r  r6  r  r  zPOST /api/cron/jobs failedr   r   )r`  r  r  r  rW  r  r  r   r  r  rD   r  r  rR   r  r  r  rT   r  r6  r  r5   rZ   rP   r"  r  )	r0  r  rc  rb  r  rW  r  r  r  s	            rU   _create_cron_job_syncr%  )  s   "<%7%@%@"l1$+|LL"4;//():;;-lLIII&&.k 	0
 0
 	 	 	 &;$"]'55@%dj11(77(TRRR%.t/DEE'55
 
 
 	
"     < < <3444CFF;;;;<s   D*D- -E>8E99E>c                 >   K   t          t          | |           d {V S r  )r  r%  )r0  r  s     rU   create_cron_jobr'  
*  s+      '(=tWMMMMMMMMMrW   z/api/cron/delivery-targetsc                     K   dddddg} 	 ddl m} |                      |                       n*# t          $ r t                              d           Y nw xY wd	| iS )
u  Delivery targets the cron dropdown should offer.

    Always includes the implicit ``local`` option. Beyond that, the list is
    derived dynamically from the configured gateway platforms via
    ``cron.scheduler.cron_delivery_targets()`` — no hardcoded platform list. A
    configured platform that hasn't set its cron home channel is still returned
    with ``home_target_set: false`` so the UI can surface it as "configure a
    home channel first" rather than hiding it.
    r!  zLocal (save only)TN)r  rR   home_target_sethome_env_varr   cron_delivery_targetsz%GET /api/cron/delivery-targets failedrv  )cron.schedulerr,  r  rZ   rP   r"  )rv  r,  s     rU   get_cron_delivery_targetsr.  *  s       '# 		
 	
G@888888,,..//// @ @ @>?????@ws   #0 $AAc                 l   |pt          |           }|st          dd          	 t          |          \  }}t          |d|           }|st          dd          t	          |j        |          }d|v r#t          |                    d          |           h d}|                    |          r"i ||}	d|v r	d|vrd |	d<   t          |	           t          |d	| |          }
n;# t          $ r  t          $ r$}t          d
t          |                    |d }~ww xY w|
st          dd          |
S )Nr   r  r   r  r  >   r  rD   r  rW  r  rW  r  
update_jobr   )r	  r5   r`  r  r  rb  r  r   intersectionr  r  r  )r  r0  r  r  rc  rb  r  rb  execution_fields	effectiver  rN  s               rU   _update_cron_job_syncr4  +*  s   8088H EODDDDG%7%A%A"l),	6JJ 	ICHHHH3L
 
 W$$1N++   ONN((11 	>/8/w/I7""wg'='=%)	'"29===$\<QQ    G G GCHH===3FG EODDDDJs   B?C& &D:DDc                 @   K   t          t          | ||           d {V S r  )r  r4  )r  r0  r  s      rU   update_cron_jobr6  M*  s-      '(=vtWUUUUUUUUUrW   c                     |pt          |           }|st          dd          t          |d|           }|st          dd          |S )Nr   r  r   	pause_jobr  r  s       rU   _pause_cron_job_syncr9  R*  sc    8088H EODDDD
 ;
?
?C EODDDDJrW   z/api/cron/jobs/{job_id}/pausec                 >   K   t          t          | |           d {V S r  )r  r9  r  s     rU   pause_cron_jobr;  \*  s+      '(<fgNNNNNNNNNrW   c                     |pt          |           }|st          dd          t          |d|           }|st          dd          |S )Nr   r  r   
resume_jobr  r  s       rU   _resume_cron_job_syncr>  a*  sc    8088H EODDDD
 <
@
@C EODDDDJrW   z/api/cron/jobs/{job_id}/resumec                 >   K   t          t          | |           d {V S r  )r  r>  r  s     rU   resume_cron_jobr@  k*  +      '(=vwOOOOOOOOOrW   c                     |pt          |           }|st          dd          t          |d|           }|st          dd          |S )Nr   r  r   trigger_jobr  r  s       rU   _trigger_cron_job_syncrD  p*  sc    8088H EODDDD
 =&
A
AC EODDDDJrW   z/api/cron/jobs/{job_id}/triggerc                 >   K   t          t          | |           d {V S r  )r  rD  r  s     rU   trigger_cron_jobrF  z*  s+      '(>PPPPPPPPPrW   c                     |pt          |           }|st          dd          	 t          |d|           }n1# t          $ r$}t          dt	          |                    |d }~ww xY w|st          dd          ddiS )Nr   r  r   
remove_jobr   rs  T)r	  r5   r  r  r  )r  r  r  r  rN  s        rU   _delete_cron_job_syncrI  *  s    8088H EODDDDG(<HH G G GCHH===3FG EODDDD$<s   8 
A&A!!A&c                 >   K   t          t          | |           d {V S r  )r  rI  r  s     rU   delete_cron_jobrK  *  rA  rW   c                    t          |           \  }}ddlm} ddlm} ddlm}m}  |t          |                    }	 |	                    |          5   |            }	t          |	                    |dd                    cddd            ||           S # 1 swxY w Y   	  ||           dS #  ||           w xY w)a  Run ONE due cron job end-to-end for ``profile`` via the resolved
    scheduler provider's ``fire_due`` (store CAS claim + ``run_one_job``).

    Scope both cron storage and the runtime Hermes home so the job's store,
    config, credentials, scripts, skills, and output all belong to the selected
    profile. Runs with no live adapters; delivery falls back to the per-platform
    send path.
    r   r  rL   r  N)adaptersrM  )r`  rX  r  rO   rM   r4  r  r  r  r  r   fire_due)
r  r  _profile_namer  r  rM   r  r  r   rT   s
             rU   _fire_cron_job_for_profilerP  *  st    -W55M4&&&&&&>>>>>>       
 %$SYY//E*%%d++ 	M 	M--//H))&4d)KKLL	M 	M 	M 	M 	M 	M 	M 	#"5))))		M 	M 	M 	M 	M 	M 	M 	M 	M 	#"5)))))""5))))s0    B5 /BB5 B  B5 #B $B5 5Cz/api/cron/firec                 \  K   ddl m} | j                            dd          }|                    d          r|dd                                         nd}t                      }  |            |t          |dd	d
d          t          |dd	dd          pdt          |dd	dd          pd          }|t          ddid          S 	 | 	                                 d{V }n# t          $ r i }Y nw xY wt          |t                    r|pi                     d          nd}|st          ddid          S t          t          |           d{V }|st          d|dd          S t          j        t          j        t$          ||                     t          d|dd          S )ue  Chronos managed-cron fire webhook (NAS -> agent).

    Authenticated by a short-lived NAS-minted JWT (verified by the pluggable
    Chronos fire-verifier), NOT the dashboard session cookie — so this path is
    in ``PUBLIC_API_PATHS`` to bypass the dashboard auth gate, and the JWT is
    the real gate. This is the inbound half of scale-to-zero managed cron: NAS
    POSTs here at fire time, the agent verifies, claims the job (store CAS, so
    at-most-once across replicas / on a NAS retry), runs it, and re-arms the
    next one-shot.

    Lives on the dashboard app (not the api_server adapter) because the
    dashboard is the agent's always-reachable public HTTP surface on hosted
    deployments; the gateway may be idle/scaled down.

    Returns 202 immediately and runs the job in the background so a long agent
    turn never trips NAS's HTTP timeout.
    r   )get_fire_verifierr  r   r   r  NrX  chronosexpected_audiencerE  nas_jwks_urlrC  )r   rT  jwks_or_keyr  r  zinvalid fire tokenr   r   r  zmissing job_idr   gone)r)  r  r"  accepted   )%plugins.cron_providers.chronos.verifyrR  r   r   r   r   r   r   r=   r*  rZ   rv  r  r  r	  rm   ry   rd  rP  )	r   rR  r   r   r  claimsr0  r  r  s	            rU   cron_fire_webhookr]  *  s"     & HGGGGG?33D $	 : :BDHNNE
--C   !#vy:MWYZZZCNBOOOWSWsFI|RHHHPD	  F ~W&:;MMMM\\^^######   +5dD+A+AKdjbh'''tF JW&67SIIII
 ++A6JJJJJJJJG S v@@cRRRR 4gvFF   :@@cRRRRs   C' 'C65C6c                   8    e Zd ZU eed<   i Zeeef         ed<   dS )AutomationBlueprintInstantiate	blueprintr  N)r  r  r  r  r  r  r   r   r  rW   rU   r_  r_  *  s3         NNNFDcNrW   r_  z/api/cron/blueprintsc                    K   	 ddl m} m} d}	 ddlm} d  |            D             }ddg|}n,# t
          $ r t                              dd	
           Y nw xY wg }| D ][} ||          }|r7|                    dg           D ] }|                    d          dk    r||d<   !|	                    |           \d|iS # t
          $ r=}	t          
                    d           t          dt          |	                    d}	~	ww xY w)a  Return the blueprint catalog as form schemas for the dashboard gallery.

    The ``deliver`` slot's options are rewritten from the user's actually
    configured gateway platforms (plus the universal origin/local/all), so the
    form never offers a platform that isn't connected.
    r   )CATALOGblueprint_catalog_entryNr+  c                 H    g | ]}|                     d           |d           S r`
  r   )r   r  s     rU   r  z(list_cron_blueprints.<locals>.<listcomp>*  s+    QQQQQUU4[[Q4QQQrW   originr!  z?cron_delivery_targets unavailable; using static deliver optionsTr  r/  rR   r  r  
blueprintszGET /api/cron/blueprints failedr!  r   )cron.blueprint_catalogrb  rc  r-  r,  rZ   rP   r  r   r   r"  r5   r  )
rb  rc  deliver_optionsr,  re  r  r6  r  ro  r  s
             rU   list_cron_blueprintsri  *  s     <KKKKKKKK	i<<<<<<QQ*?*?*A*AQQQI'=9=OO 	i 	i 	iJJXcgJhhhhh	i  	" 	"A++A..E 78R00 7 7AuuV}}	11'6)NN5!!!!g&& < < <8999CFF;;;;<s8   
C   0 C  &AC  AA&C   
D
8DDz /api/cron/blueprints/instantiatec                    K   	 ddl m}m}m}  || j                  }|t          dd| j                   	  ||| j                  }n,# |$ r$}t          dt          |                    |d}~ww xY w|                    dd           t          j
        t          |d	fi |}t          |           d{V S # t
          $ r  t          $ r=}	t                              d
           t          dt          |	                    d}	~	ww xY w)zDFill a blueprint's slots and create the cron job (form-submit path).r   )fill_blueprintget_blueprintBlueprintFillErrorNr   zUnknown blueprint: r   r#  re  r$  z,POST /api/cron/blueprints/instantiate failedr   )rg  rk  rl  rm  r`  r5   r  r  r  	functoolspartialr  r  rZ   rP   r"  )
r0  r  rk  rl  rm  r`  specrN  _creater  s
             rU   instantiate_blueprintrr  +  sm     <\\\\\\\\\\!M$.11	C8^dn8^8^____	K!>)T[99DD! 	K 	K 	KCCAAAsJ	K
 	4    #$:G\ZZUYZZ+G444444444    < < <EFFFCFF;;;;<s;   5B< A B< A5A00A55AB< <D8DDc                       e Zd ZU eed<   dZee         ed<   dZee         ed<   g Ze	e         ed<   i Z
eeef         ed<   dZee         ed<   dZee         ed<   dZee         ed	<   dS )
MCPServerCreaterR   Nr   r  ri   r  r   r	  r  )r  r  r  r  r  r   r   r  ri   r   r  r   r   r	  rA   r  r  rW   rU   rt  rt  9+  s         
IIIC#!GXc]!!!D$s)Cc3hD(3-(,L(9%,,,!GXc]!!!!!rW   rt  c                   X    e Zd ZU i Zeeeeef         f         ed<   dZe	e         ed<   dS )MCPServersReplaceserversNr  )
r  r  r  rw  r   r  r   r  r  r   r  rW   rU   rv  rv  G+  sH         )+GT#tCH~%&+++!GXc]!!!!!rW   rv  c                    ddl m}m} ddlm} | j        pd                                }|st          d          | j        pd                                }| j	        pd                                }| j
        pd                                                                }| j        | j                                        nd}t          |          t          |          k    rt          d          |d	vrt          d
|           i }	|r| j        rt          d          | j        rt          d          |dk    rG|r ||          nd}
|
r|
                                dk    rt          d           ||          |	d<   n| j        t          d          ||	d<   |dk    rd|	d<   n]|dk    s| j        t          d          ||	d<   | j        rt#          | j                  |	d<   | j        rt%          | j                  |	d<    |||	          }|r(t          d| dd                    |                     ||	|fS )a  Validate a Dashboard MCP create request and build its safe config.

    The returned config never contains the submitted Bearer token. Callers
    persist the token with the shared Bearer helper only after they enter the
    intended profile scope. Keeping this conversion shared makes the
    standalone MCP page and the Profile Builder enforce the same
    transport/auth contract.
    r   )_bearer_auth_headers_strip_bearer_prefix)validate_mcp_server_entryr   zServer name is requiredr  Nz8Provide exactly one of URL (HTTP/SSE) or command (stdio)>   r  oauthrZ  zUnsupported auth mode: z2Arguments are only supported for stdio MCP serversz>Environment variables are only supported for stdio MCP serversrZ  r}  zBearer token is requiredr   z+Bearer token requires header authenticationr   r|  r   z:HTTP authentication is not supported for stdio MCP serversr  ri   r  Server 'z' rejected: r$  )hermes_cli.mcp_configry  rz  hermes_cli.mcp_securityr{  rR   r   r  r   r  r   r   r	  get_secret_valuer   ri   r  r  r  r  )r0  ry  rz  r{  rR   r   r  r   r	  server_configr  issuess               rU   _normalize_mcp_server_creater  M+  s           BAAAAAIO""$$D 423338>r
 
 
"
"C|!r((**GI&&((..00D ( 	**,,,  CyyDMM!!STTT...9499:::$&M
 29 	SQRRR8 	P   8?KS--l;;;QSJ =!1!1!3!3x!?!? !;<<<';';D'A'AM)$$*JKKK"e7??$+M&!6>>T.:L   $+i 9 	4$(OOM&!8 	2#'>>M% &&t];;F KIDIIdii6G6GIIJJJ,,rW   r  c                     i }| pi                                  D ]X\  }}	 |rt          t          |                    nd|t          |          <   7# t          $ r d|t          |          <   Y Uw xY w|S )z5Mask secret-shaped MCP env values for read responses.r   z***)r  r$   r  rZ   )r  r  r  r  s       rU   _redact_mcp_envr  +  s    C!!##    1	 019*SVV,,,rCAKK 	  	  	 CAKKK	 Js   0AA/.A/r  c                 \   |                     d          rdn|                     d          rdnd}|                     d          }|                     d          pi }|s0t          |t                    rt          d |D                       rd	}| ||                     d          |                     d          t	          |                     d
          pg           t          |                     d          pi           ||                     dd          du|                     d          d	S )Nr   r   r  stdior|  r   r   c              3   `   K   | ])}t          |                                          d k    V  *dS )r   Nr  )r   r  s     rU   r   z&_mcp_server_summary.<locals>.<genexpr>+  sH       6 603CO+6 6 6 6 6 6rW   rZ  ri   r  r  TFrq  )	rR   	transportr   r  ri   r  r   r  rq  )r   rv  r  r  r  r  )rR   r  r  r   r   s        rU   _mcp_server_summaryr  +  s/   ''%..\#''):L:L/[wwR[I776??Dggi  &BG Jw-- # 6 67>6 6 6 3 3  wwu~~779%%SWWV__*++swwu~~344779d++58!!  rW   z/api/mcp/serversc                    K   ddl m} t          |           5   |            }d d d            n# 1 swxY w Y   dd t          |                                          D             iS )Nr   )_get_mcp_serversrw  c                 4    g | ]\  }}t          ||          S r  )r  )r   rR   r  s      rU   r  z$list_mcp_servers.<locals>.<listcomp>+  s4     
 
 
/8tSc**
 
 
rW   )r~  r  ru  r~  r  )r  r  rw  s      rU   list_mcp_serversr  +  s      666666		 	  % %""$$% % % % % % % % % % % % % % % 	 
 
<B7==??<S<S
 
 
 s   /33c                   K   ddl m}m}m} 	 t	          |           \  }}}n1# t
          $ r$}t          dt          |                    |d }~ww xY wt          | j	        p|          5   |            }	d d d            n# 1 swxY w Y   ||	v rt          dd| d          	 t          | j	        p|          5  | |||          |d<    |||          st          dd| d	          	 d d d            n# 1 swxY w Y   nU# t          $ r  t          $ r>}t                              d
           t          dt          |                    |d }~ww xY wt          ||          S )Nr   )r  _save_bearer_auth_token_save_mcp_serverr   r   r  r}  z' already existsr   z1' rejected: suspicious command/args configurationzPOST /api/mcp/servers failed)r~  r  r  r  r  r  r5   r  ru  r  rZ   rP   r"  r  )
r0  r  r  r  r  rR   r  r	  rN  r  s
             rU   add_mcp_serverr  +  s              G,H,N,N)m\\ G G GCHH===3FG 
/	0	0 & &##%%& & & & & & & & & & & & & & &x4Ut4U4U4UVVVVGDL3G44 	 	'+B+B4+V+Vi(##D-88 # #]d]]]   	 	 	 	 	 	 	 	 	 	 	 	 	 	 	     G G G5666CHH===3FG t]333sb   " 
AAA)B  BB%D ;3C;/D ;C??D C?D E9EEc                    K   ddl m} t          | j        p|          5   || j                  \  }}ddd           n# 1 swxY w Y   |s$t          dd                    |                    ddiS )	u  Replace the entire ``mcp_servers`` map (the GUI mcp.json editor's save).

    The generic ``/api/config`` endpoint deep-merges maps, so it can never
    delete a server key, drop an ``enabled: false`` flag, or remove a nested
    field — edits looked saved but the stale entry survived on disk.  This
    endpoint sets the whole map so removals actually persist.  Storage stays
    the config.yaml ``mcp_servers`` key the CLI/TUI already read.
    r   )_replace_mcp_serversNr   r$  r   rs  T)r~  r  ru  r  rw  r5   r  )r0  r  r  rs  r  s        rU   replace_mcp_serversr  +  s       ;:::::	/	0	0 8 8))$,77
F8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 GDIIf4E4EFFFF$<s   ?AAz/api/mcp/servers/{name}c                    K   ddl m} t          |          5   ||           }d d d            n# 1 swxY w Y   |st          dd|  d          ddiS )	Nr   )_remove_mcp_serverr   r}  ' not foundr   rs  T)r~  r  ru  r5   )rR   r  r  r  s       rU   remove_mcp_serverr  +  s      888888		 	  + +$$T**+ + + + + + + + + + + + + + + R4Pt4P4P4PQQQQ$<r.  z/api/mcp/servers/{name}/testc                 (   	
K   ddl m}mm t	                    5   |            ddd           n# 1 swxY w Y    vrt          dd  d          i 	                              d          d	k    
	 
fd
}	 t          j        |           d{V \  }}n*# t          $ r}dt          |          g dcY d}~S d}~ww xY w|sddg dS dd |D             	                    dd          	                    dd          dS )zFConnect to the server, list its tools, disconnect.  Returns tool list.r   )r  _oauth_tokens_present_probe_single_serverNr   r}  r  r   r   r|  c                      t                    5                      } r           nd}| |fcd d d            S # 1 swxY w Y   d S )N)detailsT)r  )	rq  token_presentr  r  r  rR   needs_oauth_tokenr  rw  s	     rU   _probe_scopedz&test_mcp_server.<locals>._probe_scoped,  s     #7++ 	( 	(((wt}gNNNE;LV11$777RVM-'	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	(s   'AA	A	Frs  r  rq  u1   OAuth authentication required — no token found.Tc                     g | ]
\  }}||d S rR   r  r  r   r  rj  s      rU   r  z#test_mcp_server.<locals>.<listcomp>:,  s$    BBBDAq1Q//BBBrW   prompts	resources)rs  rq  r  r  )r~  r  r  r  ru  r5   r   rm   rd  rZ   r  )rR   r  r  r  rq  r  rN  r  r  r  r  rw  s   ``     @@@@@rU   test_mcp_serverr  ,  s&               
	 	  % %""$$% % % % % % % % % % % % % % %74Pt4P4P4PQQQQ G  ))&11W<( ( ( ( ( ( ( ( ( ( ( 	
 &-%6}%E%EEEEEEE}} 
 
 
XX
 
 	
 	
 	
 	
 	
 	

  
H
 
 	
 BBEBBB;;y!,,[[a00	  s+   :>>B) )
C3CCCz/api/mcp/servers/{name}/authc                    	
K   ddl m}mm	m
 t                    5   |            }ddd           n# 1 swxY w Y    |vrt          dd  d          t          |                                        d          st          d	d
                              d          r*                    d          dk    rt          d	d          dd<   	
 fd}	 t          j
        |           d{V S # t          $ rE}t          |          }|                                }d|v rd|v sd|v rd  d}d|g dcY d}~S d}~ww xY w)u  Run the OAuth flow for an HTTP MCP server (opens the system browser).

    Mirrors ``hermes mcp login``: wipe cached OAuth state so the probe forces
    a fresh browser flow, connect, then verify a token actually landed on disk
    (some providers serve tools/list unauthenticated — see
    ``_reauth_oauth_server``).  Blocks until the browser flow completes, so it
    runs in a worker thread.  ``auth: oauth`` is persisted only on success.
    r   )r  r  r  r  Nr   r}  r  r   r   r   z2stdio servers authenticate via env keys, not OAuthr   r   r|  uB   This server uses header/API-key auth, not OAuth — check its key.c            
         ddl m} m} t                    5   |            5   |           }|                                }	 ddlm}  |                                           n# t          $ r Y nw xY w	 	 t          

                    dd                    }n# t          t          f$ r d}Y nw xY w 
t          |d                    }n$# t          $ r |                    |            w xY w           s3|                    |           dd	g d
cd d d            cd d d            S  	
           dd |D             dcd d d            cd d d            S # 1 swxY w Y   d d d            d S # 1 swxY w Y   d S )Nr   )HermesTokenStorageforce_interactive_oauth)get_managerconnect_timeoutr3  i;  )r  Fu   The server responded, but no OAuth token was obtained — this provider may require a manually-registered OAuth client (see `hermes mcp login`).r  Tc                     g | ]
\  }}||d S r  r  r  s      rU   r  z1auth_mcp_server.<locals>._run.<locals>.<listcomp>,  s$    JJJDAq1Q77JJJrW   )rs  rq  )tools.mcp_oauthr  r  r  snapshottools.mcp_oauth_managerr  removerZ   rx  r   rl  r  rN  restore)r  r  storagern  r  _cfg_timeoutrq  r  r  r  r  rR   r  s          rU   _runzauth_mcp_server.<locals>._runf,  s   OOOOOOOO #7++ +	 +	-D-D-F-F +	 +	((..G %%''F??????$$T****   '#(1BA)F)F#G#GLL!:. ' ' '#&LLL',,#s</E/E      ''' )(.. 
''';   =+	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	N T3'''JJEJJJ Q+	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	 +	s   F E*#A)(E*)
A63E*5A66E*;#BCB52C4B55CE*!C66(E*F7E*F*E.	.F1E.	2FF	F403regist	forbiddenri  u  ' only allows pre-approved OAuth clients — it rejected client registration (403), so no browser flow can start. Options: add a pre-registered client to this server's entry (oauth: {client_id: ..., client_secret: ...}), or use the provider's stdio / API-key server instead.Fr  )r~  r  r  r  r  ru  r5   r  r   rm   rd  rZ   r  r   )rR   r  r  rw  r  rN  msgr  r  r  r  r  s   ``      @@@@rU   auth_mcp_serverr  @,  sd                 
	 	  % %""$$% % % % % % % % % % % % % % %74Pt4P4P4PQQQQ
wt}

C775>> 
G
 
 
 	
 wwy 
cggfoo88W
 
 
 	
 CK2 2 2 2 2 2 2 2 2 2h8&t,,,,,,,,, 8 8 8#hh
 ))++C<<X00K74J4J=D = = =  cB777777778s+   ;??)D 
E:EEEc                   4    e Zd ZU eed<   dZee         ed<   dS )MCPEnabledToggler  Nr  r  r  r  r   r  r  r   r  r  rW   rU   r  r  ,  /         MMM!GXc]!!!!!rW   r  z/api/mcp/servers/{name}/enabledc                   K   t          |j        p|          5  t                      }|                    d          }t	          |t
                    r| |vrt          dd|  d          t	          ||          t
                    st          dd          t          |j                  ||          d<   t          |           d	d	d	           n# 1 swxY w Y   d
| t          |j                  dS )u$  Enable or disable an MCP server (takes effect on next session/gateway).

    Toggles the ``enabled`` key on the server's config.yaml entry — the same
    flag the agent reads at startup.  Disabled servers stay in config so they
    can be re-enabled without re-entering their settings.
    mcp_serversr   r}  r  r   r   zMalformed server configr  NTrs  rR   r  )
ru  r  r   r   rv  r  r5   r   r  r   )rR   r0  r  r  rw  s        rU   set_mcp_server_enabledr  ,  s5      
/	0	0  mm''-(('4(( 	VD,?,?C8T48T8T8TUUUU'$-.. 	SC8QRRRR#'#5#5i C               dl1C1CDDDs   B*CCCz/api/mcp/catalogc                   
K   	 ddl m
 n@# t          $ r3}t                              d           t          dd|           d}~ww xY wg }	 t          |           5  t          
                                          }
fd|D             }ddd           n# 1 swxY w Y   |D ]U}|j	        }|j
        }|j        }|                    i d	|j        d
|j        d|j        d|j        dt#          |dd          dd t#          |dg           pg D             d|j        dt          |j        pg           d|j        d|r|j        ndd|r|j        ndd|rt          |j                  ng d|j        j        t          |j        j                  ndd|j        pdd|j        dud|                    |j        d          d         d|                    |j        d          d                     Wn4# t
          $ r  t          $ r t                              d!           Y nw xY wg }		 d" 
                                D             }	n# t          $ r Y nw xY w||	d#S )$a  Browse the Nous-approved MCP catalog (the optional-mcps/ manifests).

    Each entry reports whether it's already installed and enabled so the UI
    can show install / enabled state inline.  This is the same catalog
    `hermes mcp catalog` / `hermes mcp install` read.  ``profile`` scopes
    the installed/enabled annotations (the catalog itself is repo-shipped
    and identical for every profile).
    r   mcp_catalogzmcp_catalog import failedr!  zCatalog unavailable: r   Nc                     i | ]<}|j                             |j                                       |j                   f=S r  )rR   is_installedr  )r   r  r  s     rU   rI  z$list_mcp_catalog.<locals>.<dictcomp>,  sT        11!&99;;Q;QRSRX;Y;YZ  rW   rR   r  r   r  rf  r  r  r  c                 8    g | ]}|j         |j        |j        d S )rR   rD   r)  r  r   r  s     rU   r  z$list_mcp_catalog.<locals>.<listcomp>,  s8     ! ! ! VqxQZPP! ! !rW   r  r  ri   r   install_urlinstall_ref	bootstrapdefault_enabledpost_installr   needs_installr	  )FFr  r   zlist_mcp_catalog failedc                 "    g | ]\  }}}|||d S ))rR   r  r   r  )r   r  r  r  s       rU   r  z$list_mcp_catalog.<locals>.<listcomp>-  s8     
 
 
Aq a00
 
 
rW   )r  diagnostics)r[   r  rZ   rP   r"  r5   ru  r  list_catalogr   r  r  r   rR   r  r   r  r   r  ri   r   r,  r  rq  r  r  r   catalog_diagnostics)r  rN  r  catalog_entriesinstalled_stater  r   r  r  r  r  s             @rU   list_mcp_catalogr  ,  s     S******* S S S23334QC4Q4QRRRRS G.2G$$ 	 	";#;#;#=#=>>O   (  O	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 % "	 "	E:DImGNN 
u0 %, Y^	
 WT66::  ! !$T5"55;! ! ! 9,  Y^1r22!" y}#& g?w{{4'( g?w{{4)* GT'"3444R+. ";.: $((C#D#D#D34  2 8b56  d!:78 _00^LLQO9: ?..uz>JJ1M;    	"	F     2 2 20111112 K
 
(<<>>
 
 
     {;;;s]    
A	.AA	G? 0BG? BG? !B"EG? ?.H0/H06I 
I"!I"c                   `    e Zd ZU eed<   i Zeeef         ed<   dZeed<   dZ	e
e         ed<   dS )MCPCatalogInstallrR   r  Tr  Nr  )r  r  r  r  r  r  r   r  r   r  r   r  rW   rU   r  r  -  sW         
IIICc3hFD!GXc]!!!!!rW   r  z/api/mcp/catalog/installc                 Z   	
K   ddl m  j        pd                                }                    |          

t          dd| d           j        p|	 j        rYt          	          5   j        	                                D ]\  }}|rt          ||           	 ddd           n# 1 swxY w Y   
j        kt          |          }	 t          t          	          d	d
|gz   |          }n0# t
          $ r  t          $ r}t          dd|           d}~ww xY wd|d|dS  	
fd}	 t!          j        |           d{V  nT# t
          $ r  t          $ r=}t$                              d           t          dt)          |                    d}~ww xY wd|ddS )aJ  Install a catalog MCP into config.yaml.

    For HTTP/stdio entries with required env vars, those are written to .env
    via the standard env path so the agent can read them at session start.
    Entries that need a git bootstrap (``needs_install``) are installed via
    the CLI action path because the clone can take time.
    r   r  r   Nr   zNo catalog entry 'ri  r   r  r  r!  zInstall failed: T)rs  rR   
backgroundr  c                      t                    5                       j                   d d d            d S # 1 swxY w Y   d S )N)r  )ru  install_entryr  )r0  r	  r  r  s   rU   _install_scopedz2install_mcp_catalog_entry.<locals>._install_scopedP-  s    -.. 	A 	A%%eDK%@@@	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	A 	As   ;??z install_mcp_catalog_entry failedr   F)rs  rR   r  )r[   r  rR   r   	get_entryr5   r  r  ru  r  r   r  _mcp_install_action_namer  r  rZ   rm   rd  rP   r"  r  )r0  r  rR   r  r  r  r  rN  r  r	  r  r  s   `        @@@rU   install_mcp_catalog_entryr  -  s      '&&&&&IO""$$D!!$''E}4P4P4P4PQQQQ /x )-.. 	) 	)(( ) )1 )"1a((()	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) }  *$//	R'!"344y$7OO DD  	 	 	 	R 	R 	RC8P38P8PQQQQ	RDOOOA A A A A A A A>0000000000    > > >9:::CHH====> E:::sB   32B22B69B6#C8 8D%D  D%8E F$'8FF$c                 ^   t          j        dd|                                                               d          dd         pd}t	          j        |                                                                           dd         }d| d| }t          	                    |d| d	           |S )
zUnique per-entry mcp-install action name (+ registered log file), so a
    re-click or a second catalog install doesn't overwrite the first's tracked
    process/log while its git clone is still running.
[^a-z0-9]+r  N0   serverrd  zmcp-install-action-.log
r_  r`  r   r   hashlibsha1r   	hexdigestrs  rk  )rR   r  digestr  s       rU   r  r  ^-  s     6-djjll3399#>>ssCOxD\$++--((2244RaR8F+D++6++F  )?6)?)?)?@@@MrW   computer-use-grantzaction-computer-use-grant.logc                   $    e Zd ZU eed<   eed<   dS )PairingApprover  r2  Nr  r  rW   rU   r  r  t-  s"         MMM
IIIIIrW   r  c                   $    e Zd ZU eed<   eed<   dS )PairingRevoker  r  Nr  r  rW   rU   r  r  y-  s"         MMMLLLLLrW   r  c                  "    ddl m}   |             S )Nr   PairingStore)gateway.pairingr  r  s    rU   _pairing_storer  ~-  s     ,,,,,,<>>rW   z/api/pairingc                  t   K   t                      } |                                 |                                 dS )N)r~
  r
  )r  list_pendinglist_approved)stores    rU   list_pairingr  -  s>      E%%''''))  rW   z/api/pairing/approvec                   K   t                      }| j        pd                                                                }| j        pd                                                                }|r|st          dd          |                    ||          }|rd|dS |                    |          rt          dd| d	          t          d
d| d| d          )Nr   r   zplatform and code are requiredr   T)rs  r   r  z
Platform 'z0' is locked out after too many failed approvals.r   zCode 'z%' not found or expired for platform 'r  )	r  r  r   r   r2  r	  r5   approve_code_is_locked_out)r0  r  r  r2  r  s        rU   approve_pairingr  -  s     E#**,,2244HIO""$$**,,D V4 V4TUUUU$//F ,F+++H%% 
ZZZZ
 
 
 	
 OOO8OOO   rW   z/api/pairing/revokec                 (  K   t                      }| j        pd                                                                }|r| j        st          dd          |                    || j                  rddiS t          dd| j         d	| d
          )Nr   r   z!platform and user_id are requiredr   rs  Tr   zUser z  not found in approved list for r~  )r  r  r   r   r  r5   revoke)r0  r  r  s      rU   revoke_pairingr  -  s      E#**,,2244H Y4< Y4WXXXX||Hdl++ d|
Pt|PPXPPP   rW   z/api/pairing/clear-pendingc                  T   K   t                      } |                                 }d|dS )NT)rs  rf
  )r  clear_pending)r  r  s     rU   clear_pending_pairingr  -  s0      E!!E5)))rW   c                       e Zd ZU eed<   dZee         ed<   g Zee         ed<   dZ	ee         ed<   dZ
ee         ed<   g Zee         ed<   dZeed	<   d
Zeed<   dZee         ed<   dZee         ed<   dS )WebhookCreaterR   Nr  eventsrD   r  rW  r  r  Fdeliver_onlydeliver_chat_idr&  )r  r  r  r  r  r  r   r  r   rD   r  rW  r  r  r   r  r&  r  rW   rU   r
  r
  -  s         
III!%K#%%%FDI FHSM    FHSM   FDIGSL$%)OXc]))) FHSM     rW   r
  routec                 $   | |                     dd          t          |                     d          pg           |                     dd          t          |                     d                    |                     dd          |                     dd          t          |                     d	          pg           |                     d
          | d|  t          |                     d                    |                     dd          dudS )Nr  r   r  r  r  r  rD   r  rW  rs
  z
/webhooks/r&  r  TF)rR   r  r  r  r  rD   r  rW  rs
  r   
secret_setr  )r   r  r   )rR   r  r  s      rU   _webhook_route_summaryr  -  s    yy33uyy**0b1199Y..UYY~6677))Hb))))Hb))uyy**0b11ii--,,d,,599X..//99Y--U:  rW   z/api/webhooksc                     K   dd l m}  |                                 |                                 }|                                 fd|                                D             dS )Nr   c                 8    g | ]\  }}t          ||          S r  )r  )r   rR   r  r  s      rU   r  z!list_webhooks.<locals>.<listcomp>-  s9     
 
 
e #499
 
 
rW   )r  r  subscriptions)hermes_cli.webhookrU  _get_webhook_base_url_load_subscriptions_is_webhook_enabledr  )whsubsr  s     @rU   list_webhooksr  -  s      ######''))H!!##D))++
 
 
 
#zz||
 
 
  rW   z/api/webhooks/enablec                     K   	 t          dd           n># t          $ r1} t                              d           t	          dd          | d } ~ ww xY wt                      }ddd|d          d|S )	NrU  Tz0Failed to enable webhook platform from dashboardr!  z"Failed to enable webhook platform.r   r  )rs  r  r  r	  )rC	  rZ   rP   r"  r5   r  )rN  r	  s     rU   enable_webhooksr  -  s      	40000   IJJJ7
 
 
 	 ;<<N+,=>>	 
  s    
A,AAc                   K   dd l }dd l}dd l}dd lm} |                                st          dd          | j        pd                                	                                
                    dd          }|                    d|          st          dd	          | j        r| j        d
k    rt          dd          | j        p|                    d          }| j        pd| d | j        D             || j        pdd | j        D             | j        pd
|                    d|                                          d}| j        r5| j                                        r| j                                        |d<   | j        rd|d<   | j        rd| j        i|d<   |                                }|||<   |                    |           |                                }	t7          |||	          }
||
d<   |
S )Nr   r   zHWebhook platform is not enabled. Enable it from the Webhooks page first.r   r   r  r  z^[a-z0-9][a-z0-9_-]*$zBInvalid name. Use lowercase alphanumeric with hyphens/underscores.r  uK   Direct delivery requires a real target (telegram, discord, …), not 'log'.r   z Dashboard-created subscription: c                 ^    g | ]*}|                                 |                                 +S r  r  r  s     rU   r  z"create_webhook.<locals>.<listcomp>$.  -    ???QWWYY?17799???rW   c                 ^    g | ]*}|                                 |                                 +S r  r  rW  s     rU   r  z"create_webhook.<locals>.<listcomp>'.  r   rW   z%Y-%m-%dT%H:%M:%SZ)r  r  r&  rD   rW  r  rs
  r  Tr  r  deliver_extrar&  )r_  rx  rC  r  rU  r  r5   rR   r   r   r  r  r  r  r&  r	  r  r  rD   rW  rw  gmtimer  r  r  _save_subscriptionsr  r  )r0  _re_secrets_timer  rR   r&  r  r  r  r  s              rU   create_webhookr(  .  si     ######!!## 
]
 
 
 	

 IO""$$**,,44S#>>D99-t44 
W
 
 
 	

  
T\U22`
 
 
 	

 [6H22266F'T+Td+T+T??dk???+#??dk???<(5nn%95<<>>JJ E { .t{((** .+++--h % $n C"+T-A!Bo!!##DDJ4   ''))H$T5(;;GGHNrW   z/api/webhooks/{name}c                    K   dd l m} | pd                                                                }|                                }||vrt          dd| d          ||= |                    |           ddiS )	Nr   r   r   No subscription named 'ri  r   rs  T)r  rU  r   r   r  r5   r$  )rR   r  r  r  s       rU   delete_webhookr+  =.  s      ######:2




$
$
&
&C!!##D
$4Tc4T4T4TUUUUS	4   $<rW   c                       e Zd ZU eed<   dS )WebhookEnabledToggler  Nr  r  rW   rU   r-  r-  J.  r  rW   r-  z/api/webhooks/{name}/enabledc                 P  K   ddl m} | pd                                                                }|                                }||vrt          dd| d          t          |j                  ||         d<   |                    |           d	|t          |j                  d
S )a   Enable or disable a webhook route.

    Disabled routes stay in the subscriptions file (so they can be
    re-enabled) but the gateway rejects incoming events with 403.  The
    gateway hot-reloads the subscriptions file, so this takes effect on the
    next event without a restart.
    r   Nr   r   r*  ri  r   r  Tr  )	r  rU  r   r   r  r5   r   r  r$  )rR   r0  r  r  r  s        rU   set_webhook_enabledr/  N.  s       $#####:2




$
$
&
&C!!##D
$4Tc4T4T4TUUUU--DIi4   T\0B0BCCCrW   z/api/gateway/startc                    K   	 t          t          | d          d          }nJ# t          $ r  t          $ r3}t                              d           t          dd|           d }~ww xY wd|j        ddS )	NrS   ri  zFailed to spawn gateway startr!  zFailed to start gateway: r   Tr  r  r  r5   rZ   rP   r"  r  r  r  rN  s      rU   start_gatewayr3  l.  s      W#$7$I$I?[[    W W W67774UPS4U4UVVVVW txAAA   # A*.A%%A*z/api/gateway/stopc                    K   	 t          t          | d          d          }nJ# t          $ r  t          $ r3}t                              d           t          dd|           d }~ww xY wd|j        ddS )	Nstoprj  zFailed to spawn gateway stopr!  zFailed to stop gateway: r   Tr  r1  r2  s      rU   stop_gatewayr7  x.  s      V#$7$H$H.YY    V V V56664Ts4T4TUUUUV tx@@@r4  c                   >    e Zd ZU eed<   eed<   dZee         ed<   dS )CredentialPoolAddrT   r  Nr?  )r  r  r  r  r  r?  r   r  rW   rU   r9  r9  .  s:         MMM LLLE8C=rW   r9  r+  c                 n   t          | dd          pd}|t          | dd          t          | dd          t          | dd          t          | dd          t          | dd	          t          | d
d          t          | dd	          |rt          |          ndt          t          | dd                    d
S )z{Redacted, display-safe view of one PooledCredential.

    ``index`` is 1-based to match CredentialPool.remove_index().
    r=
  r   r  Nr?  rf  r   r
  r   last_statusrequest_countr
  )
r+  r  r?  rf  r   r
  r;  r<  r
  has_refresh)r   r$   r   )r  r+  r   s      rU   _pool_entry_summaryr>  .  s    
 E>2..4"EeT4((..UK66%400E:q11umT:: ;;.3;E***GE?DAABB  rW   z/api/credentials/poolc            	        K   ddl m}  ddlm} g } |            }t	          |                                          D ]}	  | |          }n+# t          $ r t                              d|           Y 6w xY w|	                                }|sQ|
                    |d t          |d          D             d           d	|iS )
Nr   r
  )read_credential_poolzload_pool(%s) failedc                 4    g | ]\  }}t          ||          S r  )r>  )r   ir  s      rU   r  z(list_credential_pool.<locals>.<listcomp>.  s4       .2a#Aq))  rW   r   )rS   )rT   r  r  )r
  r
  r  rA  r~  rX  rZ   rP   r"  r  r   r  )r
  rA  r  raw_poolr:
  r
  r  s          rU   list_credential_poolrE  .  s#     //////444444I $#%%Hhmmoo..  	9[))DD 	 	 	NN1;???H	 ,,.. 	# 6?q6Q6Q6Q  
 
 	 	 	 	 ##s   A%A32A3c           	        K   dd l }ddlm}m}m}m} | j        pd                                                                }| j	        pd                                }|r|st          dd          	  ||          }| j        pd                                p&dt          |                                          dz    }	 |||                                j        d d	         |	|d||
          }
|                    |
           nK# t"          $ r>}t$                              d           t          dt)          |                    |d }~ww xY wd|t          |                                          dS )Nr   )r
  r
  AUTH_TYPE_API_KEYr
  r   r   z!provider and api_key are requiredr   zkey #r   r
  )rT   r  r?  rf  r
  r   r=
  z!POST /api/credentials/pool failedTrs  rT   r  )r
  r
  r
  r
  rG  r
  rT   r   r   r  r5   r?  r   r  r
  r
  r
  rZ   rP   r"  r  )r0  _uuidr
  r
  rG  r
  rT   r  r
  r?  r  rN  s               rU   add_credential_pool_entryrJ  .  s                 #**,,2244H|!r((**G Y7 Y4WXXXXGy""!r((**O.Oc$,,..6I6IA6M.O.O  {{}} !$'  
 
 
 	u G G G:;;;CHH===3FG Hs4<<>>7J7JKKKs   1BD 
E9EEz(/api/credentials/pool/{provider}/{index}c                   K   ddl m} | pd                                                                } 	  ||           }|                    |          }nK# t
          $ r>}t                              d           t          dt          |                    |d}~ww xY w|t          dd	          d
| t          |                                          dS )zGRemove a pool entry.  ``index`` is 1-based (matches the list response).r   r@  r   z#DELETE /api/credentials/pool failedr   r   Nr   zNo pool entry at that indexTrH  )r
  r
  r   r   remove_indexrZ   rP   r"  r5   r  r   r  )rT   r+  r
  r
  r  rN  s         rU   remove_credential_pool_entryrM  .  s       0/////B%%''--//HGy""##E** G G G<===CHH===3FG 4QRRRRHs4<<>>7J7JKKKs    A 
B9BBc                       e Zd ZU eed<   dS )MemoryProviderSelectrT   Nr  r  rW   rU   rO  rO   /  s         MMMMMrW   rO  c                       e Zd ZU dZeed<   dS )MemoryResetrf  rh   N)r  r  r  rh   r  r  r  rW   rU   rQ  rQ  /  s"         FCrW   rQ  z/api/memoryc                    K   t                      } d}|                     d          }t          |t                    r"t	          |                    d                    }t                      dz  }i }dD ]<\  }}||z  }|                                r|                                j        nd||<   =|t                      |dS )Nr   rj  rT   memories))	MEMORY.mdrj  )USER.mdr   r   )rm  r  builtin_files)
r   r   rv  r  r  r   rA  r  r  r  )r  rm  r  mem_dirfilesfnamer  r   s           rU   get_memory_statusrZ  
/  s      
--CF
''(

C#t F01D1DEE *,GED A A
s,0KKMM@TYY[[((qc

 799  rW   z/api/memory/providerc                    K   t          | j                  }t          |           t                      }t	          |                    d          t                    si |d<   ||d         d<   t          |           d|dS )Nrj  rT   Tr  )r  rT   r  r   rv  r   r  r   )r0  rT   r  s      rU   set_memory_providerr\   /  s}      .t}==H"8,,,
--Ccggh''.. H (CM*(+++rW   z/api/memory/resetc           	        K   | j         pd                                                                }|dvrt          dd          t	                      dz  }g }g }|dv r|                    d           |d	v r|                    d
           |D ]o}||z  }|                                rT	 |                                 |                    |           F# t          $ r}t          dd| d|           d }~ww xY wpd|dS )Nrf  >   rf  r   rj  r   z#target must be all, memory, or userr   rS  >   rf  rj  rT  >   rf  r   rU  r!  zCould not delete r	  TrN  )	rh   r   r   r5   r   r   rA  r  r  )r0  rh   rW  rO  rv  rY  r   rN  s           rU   reset_memoryr^  ./  sQ     k"U))++1133F...4YZZZZ*,GGG"""{###  y!!! ` `;;== 	``u%%%% ` ` `#<^PU<^<^Y\<^<^____`		` 7+++s   *)C
C:C55C:z/api/ops/doctorc                     K   	 t          dgd          } n@# t          $ r3}t                              d           t	          dd|           d }~ww xY wd| j        ddS )Nrl  zFailed to spawn doctorr!  zFailed to run doctor: r   Tr  r  rZ   rP   r"  r5   r  r  s     rU   
run_doctorra  S/  s      T#XJ99 T T T/0004RS4R4RSSSST tx:::s    
A.AAz/api/ops/security-auditc                     K   	 t          ddgd          } n@# t          $ r3}t                              d           t	          dd|           d }~ww xY wd| j        dd	S )
NrT  auditrm  zFailed to spawn security auditr!  zFailed to run security audit: r   Tr  r`  r  s     rU   run_security_auditrd  ]/  s      \#Z$9;KLL \ \ \78884ZUX4Z4Z[[[[\ tx1ABBB    
A.AAc                   *    e Zd ZU dZee         ed<   dS )BackupRequestNoutput)r  r  r  rh  r   r  r  r  rW   rU   rg  rg  g/  s&          FHSM     rW   rg  c                  $    t                      dz  S )Nbackups)r   r  rW   rU   _dashboard_backup_dirrk  l/  s    y((rW   c                      t          j                                        d          } t                      d|  dt	          j        d           dz  S )Nz%Y-%m-%d-%H%M%Szhermes-backup-r  r   .zip)r   r?  rw  rk  rx  ry  )stamps    rU   _new_dashboard_backup_pathro  p/  sM    LNN##$566E ""%Xe%X%Xg>OPQ>R>R%X%X%XXXrW   z/api/ops/backupc                 L  K   dg}d }| j         pd                                }|r|                    d|g           nvt                      }	 |j                            dd           n&# t          $ r}t          dd|           d }~ww xY w|                    dt          |          g           	 t          |d          }n@# t          $ r3}t                              d	           t          dd
|           d }~ww xY wd|j        dd}|t          |          |d<   |S )Nrn  r   z-oTr  r!  z#Could not create backup directory: r   zFailed to spawn backupzFailed to run backup: r  archive)rh  r   r  ro  r  r"  r  r5   r  r  rZ   rP   r"  r  )r0  ri   rq  rh  rN  r  r,  s          rU   
run_backuprr  u/  ss     :D"GkR&&((F *T6N####,..	N   ==== 	 	 	BSBB   	
 	T3w<<()))T#D(33 T T T/0004RS4R4RSSSST 48X>>H!'llOs0   A) )
B3BB4C 
D.C==Dz/api/ops/backup/downloadrq  c                 2  K   	 t                                                                          d          }t          |                                                               d          }n@# t          $ r t          dd          t          t          f$ r t          dd          w xY wt          ||          st          d	d
          |	                                st          dd          t          t          |          d|j        d          S )NFr  Tr   zBackup not foundr   r   zInvalid backup pathr  z0Backup is outside the dashboard backup directoryzapplication/zipr  r  )rk  r  r  r   r  r5   r  r  r&  r  r;   r  rR   )rq  
backup_dirrh   s      rU   download_dashboard_backupru  /  s:     K*,,7799AAAOO
g))++3343@@ H H H4FGGGG\" K K K4IJJJJK *f-- h4fgggg>> H4FGGGG[[$!-	   s   A)A. .=B+c                   (    e Zd ZU eed<   dZeed<   dS )ImportRequestrq  Fr  Nr	  r  rW   rU   rw  rw  /  s-         LLL E4rW   rw  z/api/ops/importc                   K   | j         pd                                }|st          dd          t          j                            |          st          dd|           d|g}| j        r|                    d           	 t          |d          }n@# t          $ r3}t                              d	           t          d
d|           d }~ww xY wd|j        ddS )Nr   r   zarchive path is requiredr   r   zArchive not found: ro  --forceFailed to spawn importr!  Failed to run import: Tr  )rq  r   r5   rt   r   r  r  r   r  rZ   rP   r"  r  )r0  rq  ri   r  rN  s        rU   
run_importr|  /  s     |!r((**G P4NOOOO7>>'"" U4S'4S4STTTTgDz IT#D(33 T T T/0004RS4R4RSSSST tx:::s   B 
C .CCc                    t          | pd          j                                        }t          j        dd|                              d          }|sd}|                                                    d          s| d}|S )Nz
backup.zipz[^A-Za-z0-9._-]+r  z.-rm  )r   rR   r   r_  r`  r   r%  )r  rR   s     rU   _safe_backup_upload_namer~  /  s    (L)).4466D6%sD1177==D ::<<  (( }}}KrW   z/api/ops/import-uploadc                 V  K   t                      }	 |                    dd           n&# t          $ r}t          dd|           d }~ww xY wt	          | j                  }t          j        t          j	                  
                    d          }|d| dt          j        d	           d| z  }t          j        d
|j         d
dt!          |                    \  }}t#          |          }	d}
d}	 t%          j        |d          5 }	 |                     t*                     d {V }|snD|
t-          |          z  }
|
t.          k    rt          dd          |                    |           g	 d d d            n# 1 swxY w Y   t%          j        |	|           d}nJ# t          $ r  t4          $ r t          dd          t          $ r}t          dd|           d }~ww xY w	 |s|	                    d           |                                  d {V  n7# |s|	                    d           |                                  d {V  w xY wt;          j        |          s'|                    d           t          dd          dt!          |          g}|r|                    d           	 tA          |d          }n@# tB          $ r3}tD          #                    d           t          dd|           d }~ww xY wd|j$        dt!          |          |
dS )NTr  r!  z+Could not create import staging directory: r   z%Y%m%d-%H%M%Szdashboard-import-r  r   r~  r  r  r   Fr  r
  zArchive is too larger  z(Import staging directory is not writablez"Could not write uploaded archive: r  r   z(Uploaded archive is not a valid zip filero  ry  rz  r{  )rs  r  rR   rq  uploaded_bytes)%rk  r"  r  r5   r~  r  r   r?  r   rW	  rw  rx  ry  r  r  rR   r  r   rt   r  r,  r  r   rY  r  r  r  r  r   zipfile
is_zipfiler   r  rZ   rP   r"  r  )r  r  staging_dirrN  	safe_namern  rh   r  r  r  r  r  r  r  ri   r  s                   rU   run_import_uploadr  /  sA     
 ())K
$6666 
 
 
FFF
 
 
 	

 )77IL&&//@@EYuYYw7H7K7KYYiYYYF'!6;!!!  FH
 H~~HEGYvt$$ 	!!"ii(;<<<<<<<< U#222'C@VWWWW		%   ! 		! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	
8V$$$    
 
 
=
 
 
 	
  
 
 
===
 
 
 	

 	  	-OOtO,,,jjll  	-OOtO,,,jjllf%% 
&&&=
 
 
 	

 c&kk"D IT#D(33 T T T/0004RS4R4RSSSST xv;;  s{   * 
AAA9F' A)F8F' FF' FF' &H% '.G.G))G..H% %4I K 
L.L		Lz/api/ops/hooksc                  r  K   ddl m}  ddlm} 	 ddlm} t          |          }n# t          $ r g }Y nw xY wg }	 |                     |                       }n*# t          $ r t          
                    d           Y nw xY wg }|D ]}d}	 |                    |j        |j                  }n# t          $ r Y nw xY wd}	 |                    |j                  }n# t          $ r Y nw xY w|                    |j        |j        |j        |j        |du|pi                     d          |d	           ||d
S )zList configured shell hooks from config.yaml with consent + health.

    Reports each hook's allowlist (consent) status and whether the script is
    currently executable, plus the set of valid hook events so the create
    form can offer them.
    r   r   shell_hooksVALID_HOOKSziter_configured_hooks failedNFapproved_at)r	  matcherr  r!  rT  r  r  )hooksvalid_events)r  r   rV  r  hermes_cli.pluginsr  r~  rZ   iter_configured_hooksrP   r"  allowlist_entry_forr	  r  script_is_executabler   r  r!  r   )	_load_configr  r  r  specsr  rp  r  r  s	            rU   
list_hooksr  0  s      >=====!!!!!!222222k**    E711,,..AA 7 7 75666667 C  	33DJMMEE 	 	 	D	
	$99$,GGJJ 	 	 	D	

Z|||D(!KR,,];;$
 
 	 	 	 	 ,777sA   & 55A $B ?B  B--
B:9B: C
C('C(c                   f    e Zd ZU eed<   eed<   dZee         ed<   dZee         ed<   dZ	e
ed<   dS )
HookCreater	  r  Nr  r!  Tapprove)r  r  r  r  r  r  r   r!  rw  r  r   r  rW   rU   r  r  N0  s^         JJJLLL!GXc]!!!!GXc]!!! GTrW   r  c                   K   ddl m} | j        pd                                }| j        pd                                }|r|st          dd          	 ddlm} ||vr7t          dd| d	d
                    t          |                               n# t
          $ r  t          $ r Y nw xY wt                      }|                    d          }t          |t                    si }||d<   |                    |          }t          |t                    sg }|||<   d|i}| j        r
| j        |d<   | j        t%          | j                  |d<   |                    |           t)          |           d}	| j        rD	 |                    ||           d}	n*# t          $ r t.                              d           Y nw xY wd|||	dS )aO  Add a shell hook to config.yaml (and optionally approve it).

    Shell hooks run arbitrary commands, so this is a privileged action: it
    writes to the ``hooks:`` config block and, when ``approve`` is set, records
    consent in the allowlist so the hook actually fires.  Takes effect on the
    next session / gateway restart.
    r   r  r   r   event and command are requiredr   r  zUnknown event 'z
'. Valid: r  r  r  r  Nr!  FTzhook consent record failed)rs  r	  r  r
  )rV  r  r	  r   r  r5   r  r  r  r~  rZ   r   r   rv  r  r  r  r!  rw  r   r   r  _record_approvalrP   r"  )
r0  r  r	  r  r  r  	hooks_cfgr  	new_entryr
  s
             rU   create_hookr  Y0  sQ      "!!!!!Z2$$&&E|!r((**G V V4TUUUU
222222##ZZZ$))F;DWDW:X:XZZ    $
        --C  Ii&& !	 GmmE""Ggt$$ #"	%!*G 4I| ,#|	)|"4<00	)NN9H| 9	9((888HH 	9 	9 	9NN788888	9 7QQQs%   AB B.-B.F $GGc                   $    e Zd ZU eed<   eed<   dS )
HookDeleter	  r  Nr  r  rW   rU   r  r  0  s"         JJJLLLLLrW   r  c                   K   ddl m} | j        pd                                }| j        pd                                |rst          dd          t                      }|                    d          }d}t          |t                    rt          |                    |          t                    rwt          ||                   }fd	||         D             ||<   t          ||                   |k     }||         s||= |s|                    dd
           t          |           	 |                               n# t          $ r Y nw xY w|st          dd          ddiS )zFRemove a hook from config.yaml and revoke its consent allowlist entry.r   r  r   r   r  r   r  Fc                 p    g | ]2}t          |t                    r|                    d           k    0|3S )r  )rv  r  r   )r   r  r  s     rU   r  zdelete_hook.<locals>.<listcomp>0  sL     
 
 
q$''
,-EE),<,<,G,G ,G,G,GrW   Nr   zNo matching hook foundrs  T)rV  r  r	  r   r  r5   r   r   rv  r  r  r   r  r   r  rZ   )r0  r  r	  r  r  r  beforer  s          @rU   delete_hookr  0  s      "!!!!!Z2$$&&E|!r((**G V V4TUUUU
--C  IG)T"" z)--2F2F'M'M Yu%&&
 
 
 
 '
 
 
	% i&''&0 	!%  	#GGGT"""C7####     N4LMMMM$<s   /E 
EEz/api/ops/checkpointsc                    K   t                      dz  } g }d}|                                 rt          |                                           D ]}|                                sd}d}|                    d          D ]I}|                                r3	 ||                                j        z  }|dz  }9# t          $ r Y Ew xY wJ||z  }|	                    |j
        ||d           ||dS )z8List the /rollback shadow store checkpoints (read-only).rZ  r   r   r   )r   rX  rt  )rF  total_bytes)r   r  r~  r  rglobr  r  r  r  r   rR   )cp_dirrF  r  r  rL  r  ro  s          rU   list_checkpointsr  0  s1      .FHK}} FNN,,-- 	 	E<<>> DE[[%%  99;;  00
"   	 4KOO :     
 !===s   !B66
CCz/api/ops/checkpoints/prunec                     K   	 t          ddgd          } n@# t          $ r3}t                              d           t	          dd|           d }~ww xY wd| j        dd	S )
NrZ  prunerp  z!Failed to spawn checkpoints pruner!  zFailed to prune checkpoints: r   Tr  r`  r  s     rU   prune_checkpointsr  0  s      [#]G$<>QRR [ [ [:;;;4YTW4Y4YZZZZ[ tx1DEEEre  c                   4    e Zd ZU eed<   dZee         ed<   dS )SkillInstallRequest
identifierNr  r  r  rW   rU   r  r  0  s/         OOO!GXc]!!!!!rW   r  c                     | pd                                 }|r|                                dv rg S ddlm} t	          |           d|                    |          gS )u  Return ``["-p", <name>]`` for a validated non-default profile.

    Hub install/uninstall/update run in a fresh ``hermes`` subprocess, and
    ``_apply_profile_override()`` reads ``-p`` from argv in the child — the
    only mechanism that reaches import-time-bound globals like
    ``skills_hub.SKILLS_DIR``. Empty/"current" means the dashboard's own
    profile (no args, legacy behavior).
    r   >   r  r1  r   ri  -p)r   r   r[   r}  r	  r  )r  r	  ru  s      rU   r  r  0  st     B%%''I 	))-CCC	333333###,55i@@AArW   c                 d   t          j        dd|                                                              d          dd         pd}t	          j        |                                                                          dd         }d|  d| d| }t          	                    |d| d	           |S )
ap  Unique per-skill hub action name (+ registered log file).

    ``_spawn_hermes_action`` tracks one process/log per name, so a shared
    "skills-install"/"skills-uninstall" would make concurrent row-level actions
    overwrite each other's status/log while the UI polls per identifier. Slug
    (readable) + hash (collision-proof) keys each action to its own row.
    r  r  Nr  r  rd  zskills-r  r  r  )r  r  r  r  rR   s        rU   _hub_action_namer  1  s     6-ciikk2288==crcBMgD\#**,,''1133BQB7F+T++D++6++D  ';';';';<<<KrW   z/api/skills/hub/installc                   K   | j         pd                                }|st          dd          t          d|          }	 t	          t          | j        p|          dd|dgz   |          }nJ# t          $ r  t          $ r3}t          	                    d           t          d	d
|           d }~ww xY wd|j
        |dS )Nr   r   identifier is requiredr   r  rW  --yeszFailed to spawn skills installr!  zFailed to install skill: Tr  )r  r   r5   r  r  r  r  rZ   rP   r"  r  )r0  r  r  rR   r  rN  s         rU   install_skill_hubr  1  s     /'R..00J N4LMMMMIz22D
W#dl5g66J89
 

     W W W78884UPS4U4UVVVVW tx666   +A. .B5.B00B5c                   4    e Zd ZU eed<   dZee         ed<   dS )SkillUninstallRequestrR   Nr  r  r  rW   rU   r  r  )1  s/         
III!GXc]!!!!!rW   r  z/api/skills/hub/uninstallc                   K   | j         pd                                }|st          dd          t          d|          }	 t	          t          | j        p|          dd|dgz   |          }nJ# t          $ r  t          $ r3}t          	                    d           t          d	d
|           d }~ww xY wd|j
        |dS )Nr   r   zname is requiredr   	uninstallrW  r  z Failed to spawn skills uninstallr!  zFailed to uninstall skill: Tr  )rR   r   r5   r  r  r  r  rZ   rP   r"  r  )r0  r  rR   r  r  rN  s         rU   uninstall_skill_hubr  .1  s     IO""$$D H4FGGGGk400F	Y#dl5g66(KQUW^9__
 
     Y Y Y9:::4WRU4W4WXXXXY tx888r  c                   *    e Zd ZU dZee         ed<   dS )SkillsUpdateRequestNr  )r  r  r  r  r   r  r  r  rW   rU   r  r  A1  s&         !GXc]!!!!!rW   r  z/api/skills/hub/updatec                   K   	 | r| j         nd p|}t          t          |          ddgz   d          }nJ# t          $ r  t          $ r3}t
                              d           t          dd|           d }~ww xY wd|j        dd	S )
NrW  r  rq  zFailed to spawn skills updater!  zFailed to update skills: r   Tr  )r  r  r  r5   rZ   rP   r"  r  )r0  r  r3  r  rN  s        rU   update_skills_hubr  E1  s      	W%)3T\\t?	#i((Hh+??
 
     W W W67774UPS4U4UVVVVW txAAAs   /4 A;.A66A;zOfficial (Nous)zHermes Indexz	skills.shz
Well-Knownz
Direct URLGitHubClawHubzClaude MarketplaceLobeHubz	browse.sh)
officialhermes-index	skills-sh
well-knownr   githubclawhubclaude-marketplacelobehubz	browse-shc           	      z    | j         | j        | j        | j        | j        | j        t          | j        pg           dS )N)rR   r  r   r  trust_levelrepotags)rR   r  r   r  r  r  r  r  )r  s    rU   _skill_meta_to_payloadr  f1  s@    }(l}QV\r""  rW   c                    	 ddl m} | pd                                }|r<|                                dk    r$t	          |          } ||dz  dz  dz            }n
 |            }i }|                                D ][}|                    d          }|rB|                    d	          |                    d
          |                    d          d||<   \|S # t          $ r i cY S w xY w)ah  Map identifier -> installed lock entry for hub-installed skills.

    Lets the UI mark search results that are already installed.  Scoped to
    ``profile``'s skills/.hub/lock.json when provided (HubLockFile takes an
    explicit path, sidestepping the import-time LOCK_FILE binding).
    Best-effort: returns an empty dict if the lock file can't be read.
    r   )HubLockFiler   r  rW  z.hubz	lock.jsonr  rR   r  scan_verdict)rR   r  r  )tools.skills_hubr  r   r   r	  list_installedr   rZ   )r  r  r	  profile_dirlockr  r  idents           rU   _installed_hub_identifiersr  r1  s$   000000]))++	 	!**i77.y99K;{X5>LMMDD;==D((** 	 	EIIl++E !IIf--#(99]#;#;$)IIn$=$= E

 
   			s   CC C)(C)z/api/skills/hub/sourcesc                     K    fd}	 t          j        |           d{V S # t          $ r  t          $ r3}t                              d           t          dd|           d}~ww xY w)u  List the configured skill-hub sources and installed-skill provenance.

    Gives the dashboard something to show BEFORE a search runs — which hubs
    are wired up, their trust tier, and a set of featured skills pulled from
    the centralized index (zero extra API calls).  Without this the Browse-hub
    tab is a blank page with no indication it's even connected to anything.
    ``profile`` scopes the installed-skill provenance to that profile.
    c                     ddl m}  t          	          5   |             }d d d            n# 1 swxY w Y   g }d}g }|D ]}|                                }|t                              ||          d}|dk    r8	 t          t          |dd                    |d<   n# t          $ r d|d<   Y nw xY w|dk    rn	 t          t          |d	d                    }n# t          $ r d}Y nw xY w||d
<   |r5	 d |	                    dd          D             }n# t          $ r g }Y nw xY w|
                    |           t          h d          }|D ]}|o	|d         |v  |d<   |||t          	          dS )Nr   create_source_routerF)r  r?  r  is_rate_limitedrate_limitedr  is_availabler  c                 ,    g | ]}t          |          S r  r  r  s     rU   r  z9list_skills_hub_sources.<locals>._run.<locals>.<listcomp>1  s.     $ $ $:;2155$ $ $rW   r   re  )r7  >   r  r  r  r  r  r  r  
searchable)sourcesindex_availablefeaturedr	  )r  r  r  	source_id_SKILL_HUB_SOURCE_LABELSr   r   r   rZ   r  r   r	  r  )
r  r  r  r  r  rq  r  r  _api_source_idsr  s
            rU   r  z%list_skills_hub_sources.<locals>._run1  sq   999999"7++ 	- 	-**,,G	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	 	C--//C155c3?? E
 h2,0>OQV1W1W,X,XE.))  2 2 2,1E.)))2n$$,&*73+N+N&O&OOO  , , ,&+OOO,%4k"" &&$ $?Bzz"TVz?W?W$ $ $ % & & &#%&JJu $]]]
 
  	[ 	[E'6'Y5;/;Y"ZE,. 3G<<	
 
 	
sB   .22<!BB0/B0:CC('C(3!DD$#D$Nz!skills hub sources listing failedr^  zHub sources failed: r   )rm   rd  r5   rZ   rP   r"  )r  r  rN  s   `  rU   list_skills_hub_sourcesr  1  s      3
 3
 3
 3
 3
jR&t,,,,,,,,,    R R R:;;;4P34P4PQQQQRs   $ A+.A&&A+z/api/skills/hub/searchc                 &  K   | pd                                 sg i g i dS fd}	 t          j        |           d{V S # t          $ r  t          $ r3}t
                              d           t          dd|           d}~ww xY w)	aK  Search the skill hub across all configured sources.

    Network-bound (parallel source search); runs in a thread so the FastAPI
    loop isn't blocked.  Returns structured results the UI installs by
    identifier via POST /api/skills/hub/install, previews via
    /api/skills/hub/preview, and scans via /api/skills/hub/scan.
    r   r  source_counts	timed_outr	  c                  F   ddl m} m} t                    5   |             }d d d            n# 1 swxY w Y   t	          t          d          d          } ||pdd          \  }}}dddd	}i }|D ]c}	|	j        |vr|	||	j        <   |                    |	j        d          |                    ||	j                 j        d          k    r
|	||	j        <   dt          |
                                          d |         }
d
 |
D             ||t                    dS )Nr   )r  parallel_search_sourcesr   r6  rf  r   )r  rx  overall_timeoutr  )r  trusted	communityc                 ,    g | ]}t          |          S r  r  r  s     rU   r  z3search_skills_hub.<locals>._run.<locals>.<listcomp>1  s!    CCCa.q11CCCrW   r  )r  r  r  r  r  rN  r  r   r  r  r  r  )r  r  r  cappedall_resultsr  r  _rankr  r6  dedupedr7  r  r  r   s              rU   r  zsearch_skills_hub.<locals>._run1  s   RRRRRRRR"7++ 	- 	-**,,G	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	-S]]B''0G0G5%QS1
 1
 1
-]I
 !!<< 	' 	'A|4''%&Q\""1=!,,uyyal9K9WYZ/[/[[[%&Q\"t{{}}%%gvg. DC7CCC*"3G<<	
 
 	
s   044Nzskills hub search failedr^  zHub search failed: r   )r   rm   rd  r5   rZ   rP   r"  )r~  r   r7  r  r  rN  r  s    ```  @rU   search_skills_hubr  1  s       W"OOE VRTUUU
 
 
 
 
 
 
 
6Q&t,,,,,,,,,    Q Q Q12224O#4O4OPPPPQs   A	 	B.BBz/api/skills/hub/previewr  c                 P  K   | pd                                 st          dd          fd}	 t          j        |           d{V }n@# t          $ r3}t
                              d           t          dd	|           d}~ww xY w|t          d
d           |S )a  Fetch a hub skill's SKILL.md content + metadata for in-dashboard reading.

    Resolves the identifier across configured sources (same path the CLI
    installer uses), then returns the rendered SKILL.md text and the file
    manifest WITHOUT installing anything.  This is the 'read the actual skill
    before installing' affordance the Browse-hub tab was missing.

    Scoped to ``profile`` so a non-default profile with different hub taps
    resolves against ITS source router, not the default profile's.
    r   r   r  r   c                     ddl m}  ddlm} t	                    5   |            } | |          \  }}}d d d            n# 1 swxY w Y   |s|sd S i }d}|r|j        pi                                 D ]N\  }}	t          |	t                    r/	 |		                    d          ||<   4# t          $ r d||<   Y Ew xY w|	||<   O|                    dd          pd}|p|}
t          |
d          t          |
d	d          pdt          |
d
d          pdt          |
d          pt          |
dd          pdt          |
dd           t          t          |
dd           pg           |t          |                                          d	S )Nr   _resolve_source_meta_and_bundler  r   r  z(binary file)SKILL.mdrR   r  r   r  r  r  r  r  )	rR   r  r   r  r  r  r  skill_mdrX  )hermes_cli.skills_hubr  r  r  r  rX  r  rv  rt  r  UnicodeDecodeErrorr   r   r  r~  rX  )r  r  r  ri  bundle_srcrX  r  relr   r  r  r  s              rU   r  zpreview_skill_hub.<locals>._run2  sC   IIIIII999999"7++ 	Q 	Q**,,G!@!@!P!PD&$	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q  	d 	4 	7!'!3 : : < < 
) 
)Wgu-- 	)5%,^^G%<%<c

- 5 5 5%4c


5 ")E#JJyyR006BHNFAvu--"1mR88>Ba2..4"!!\599BU"1m[AAP[Avt,,FD117R88 EJJLL))

 

 
	
s#   AAAB**B<;B<Nzskills hub preview failedr^  zHub preview failed: r   Skill not found: r   r5   rm   rd  rZ   rP   r"  r  r  r  r  rN  r  s    `   @rU   preview_skill_hubr
  2  s       2$$&&E N4LMMMM%
 %
 %
 %
 %
 %
NR(........ R R R23334P34P4PQQQQR ~4O4O4OPPPPM   A 
B.BBz/api/skills/hub/scanc                 P  K   | pd                                 st          dd          fd}	 t          j        |           d{V }n@# t          $ r3}t
                              d           t          dd	|           d}~ww xY w|t          d
d           |S )uT  Run the install-time security scan on a hub skill WITHOUT installing it.

    Fetches the bundle, quarantines it, and runs the same `scan_skill` /
    `should_allow_install` pipeline the CLI installer uses — then cleans up the
    quarantine.  Returns the verdict, per-finding detail, trust tier, and the
    install-policy decision so the dashboard can show a visual safety result
    on demand (the 'scan' button the Browse-hub tab was missing).

    Scoped to ``profile`` so the bundle resolves against that profile's hub
    source router, matching where an install would pull it from.
    r   r   r  r   c                     dd l } ddlm} ddlm}m} ddlm}m} t                    5   |            } ||          \  }}}	d d d            n# 1 swxY w Y   |sd S |j
        dk    rd}
n$t          |dd          pt          |dd          p}
d }	  ||          } |||
          }||                     |d	
           n# ||                     |d	
           w w xY w ||d          \  }}|d	u rd}n|d}nd}d |j        D             }ddddd}|j        D ] }|j        |v r||j        xx         dz  cc<   !|j        |j
        |j        |j        |j        ||||d
S )Nr   r  )r  quarantine_bundle)
scan_skillshould_allow_installr  r  r   )r   T)ignore_errorsF)r  allowaskrw  c                 P    g | ]#}|j         |j        |j        |j        |j        d $S )severityr  r  r]  r  r  )r   ro  s     rU   r  z0scan_skill_hub.<locals>._run.<locals>.<listcomp>2  sJ     	
 	
 	
  JJ } 	
 	
 	
rW   )criticalrL  rK  rJ  r   )
rR   r  r   r  verdictr  rF  policy_reasonfindingsseverity_counts)r  r  r  r  r  r  tools.skills_guardr  r  r  r   r   r  r  r  
skill_namer  r  r  )_shutilr  r  r  r  r  r  ri  r  r  scan_sourceq_pathr  rT  reasonrF  r  countsro  r  r  s                      rU   r  zscan_skill_hub.<locals>._run_2  s       IIIIIILLLLLLLLGGGGGGGG"7++ 	Q 	Q**,,G!@!@!P!PD&$	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q 	Q  	4=J&&$KK b11 4r22  	;&&v..FZ{;;;F!vT::: !vT:::: " /.vUCCCd??FF_FFF	
 	
 _	
 	
 	
  aBB 	( 	(AzV##qz"""a'""" %m!-~~# %
 
 	
s   AAAC C$Nzskills hub scan failedr^  zHub scan failed: r   r  r  r	  s    `   @rU   scan_skill_hubr#  N2  s      2$$&&E N4LMMMMB
 B
 B
 B
 B
 B
HO(........ O O O/0004M4M4MNNNNO ~4O4O4OPPPPMr  c                       e Zd ZU eed<   dZee         ed<   dZeed<   dZ	eed<   dZ
eed<   dZee         ed<   dZee         ed	<   dZee         ed
<   g Zed         ed<   g Zee         ed<   g Zee         ed<   dS )ProfileCreaterR   N
clone_fromFclone_from_default	clone_all	no_skillsr  rT   r  rt  r  keep_skills
hub_skills)r  r  r  r  r  r&  r   r'  r   r(  r)  r  rT   r  r  r   r*  r+  r  rW   rU   r%  r%  2  s         
III $J$$$  %$$$ItIt!%K#%%%"Hhsm"""E8C=
 ,.K'(---
  Kc
 JS	rW   r%  c                       e Zd ZU eed<   dS )ProfileRenamenew_nameNr  r  rW   rU   r-  r-  2  s         MMMMMrW   r-  c                       e Zd ZU eed<   dS )ProfileSoulUpdater   Nr  r  rW   rU   r0  r0  2  s         LLLLLrW   r0  c                       e Zd ZU eed<   dS )ProfileActiveUpdaterR   Nr  r  rW   rU   r2  r2  2  r  rW   r2  c                       e Zd ZU dZeed<   dS )ProfileDescriptionUpdater   r  N)r  r  r  r  r  r  r  rW   rU   r4  r4  2  s"         KrW   r4  c                   $    e Zd ZU eed<   eed<   dS )ProfileModelUpdaterT   r  Nr  r  rW   rU   r6  r6  2  s"         MMMJJJJJrW   r6  c                       e Zd ZU dZeed<   dS )ProfileDescribeAutoFr  N)r  r  r  r  r   r  r  rW   rU   r8  r8  2  s"         ItrW   r8  c                 H    	 t          | |          S # t          $ r |cY S w xY wr  )r   rZ   )rQ   rR   r1  s      rU   _profile_attrr:  2  s;    tT"""   s    !!c                 d   t          | dd          t          t          | dd                    t          t          | dd                    t          | d          t          | d          t          t          | dd                    t          t          | d	d
          pd
          t          t          | dd                    t          | dd          pdt          t          | dd                    t          | d          t          | d          t          | d          t          | d          d udS )NrR   r   r   
is_defaultFr  rT   has_envskill_countr   r  r  description_autodistribution_namedistribution_versiondistribution_source
alias_pathrR   r   r<  r  rT   r=  r>  r  r  r?  r@  rA  rB  	has_alias)r:  r  r   rw  )rQ   s    rU   r  r  2  s   dFB//M$3344=|UCCDDtW--!$
33dIu==>>=}a@@EAFFd4Eu M MNN$T="==C t5G!O!OPP*41DEE -d4J K K,T3HII"466dB  rW   c                     d }g }                                                                  r | fdd          \  }}|                    dt                    d||dz                                   | fdd           | fd	d
           | fdd           | fdd
          d d d d
d                                            }|                                rt          |                                          D ]}|                                r j        	                    |j
                  s6 ||f fd	d          \  }}|                    |j
        t          |          d
|||dz                                   ||f fd	d           ||f fd	d
           ||f fd	d           ||f fd	d
          d d d d
d           |S )Nc                 <    	  |             S # t           $ r |cY S w xY wr  )rZ   )	callable_r1  s     rU   _safez&_fallback_profile_dicts.<locals>._safe3  s7    	9;; 	 	 	NNN	s   	 c                  .                                    S r  _read_config_modeldefault_homeru  s   rU   r  z)_fallback_profile_dicts.<locals>.<lambda>3  s    (G(G(U(U rW   NNr1  Tr~  c                  .                                    S r  _count_skillsrM  s   rU   r  z)_fallback_profile_dicts.<locals>.<lambda>3  s    )C)CL)Q)Q rW   r   c                  .                                    S r  rz  rM  s   rU   r  z)_fallback_profile_dicts.<locals>.<lambda>3  s    \-P-PQ]-^-^ rW   Fc                  V                                                        dd          S Nr  r   read_profile_metar   rM  s   rU   r  z)_fallback_profile_dicts.<locals>.<lambda>3  s'    )G)G)U)U)Y)YZgik)l)l rW   r   c                  V                                                        dd          S Nr?  FrW  rM  s   rU   r  z)_fallback_profile_dicts.<locals>.<lambda>3  s'    l.L.L\.Z.Z.^.^_qsx.y.y rW   rD  c                 .                         |           S r  rK  r  ru  s    rU   r  z)_fallback_profile_dicts.<locals>.<lambda>"3  s    8W8WX]8^8^ rW   c                 .                         |           S r  rQ  r\  s    rU   r  z)_fallback_profile_dicts.<locals>.<lambda>*3  s    9S9STY9Z9Z rW   c                 .                         |           S r  rT  r\  s    rU   r  z)_fallback_profile_dicts.<locals>.<lambda>+3  s    \=`=`af=g=g rW   c                 V                         |                               dd          S rV  rW  r\  s    rU   r  z)_fallback_profile_dicts.<locals>.<lambda>,3  s(    9W9WX]9^9^9b9bcprt9u9u rW   c                 V                         |                               dd          S rZ  rW  r\  s    rU   r  z)_fallback_profile_dicts.<locals>.<lambda>-3  s2    l>\>\]b>c>c>g>ghz  }B  ?C  ?C rW   )_get_default_hermes_homer  r   r  rA  _get_profiles_rootr~  r  _PROFILE_ID_REr  rR   )ru  rI  r}  r  rT   profiles_rootr  rN  s   `      @rU   r  r  3  s      &(H88::L % U U U U UWcddx%% $v-5577 5!Q!Q!Q!Q!QSTUU$u%^%^%^%^%^`eff 5!l!l!l!l!lnpqq %&y&y&y&y&y  |A  !B  !B!%$(#'
 
 	 	 	" !3355M M113344 	 	E<<>> )D)J)J5:)V)V #e$^$^$^$^$^`lmmOE8OO
E

#$!FN2244$u%%Z%Z%Z%Z%Z\]^^#(5e)g)g)g)g)gin#o#o$u%%u%u%u%u%uwyzz$)Eu  +C  +C  +C  +C  +C  EJ  %K  %K%)(,'+"     " OrW   c                    ddl m} 	 |                    |            n0# t          $ r#}t	          dt          |                    d}~ww xY w|                    |           st	          dd|  d          |                    |           S )	zIValidate ``name`` and resolve to its directory or raise an HTTPException.r   ri  r   r   Nr   r  r  )r[   r}  r  r  r5   r  r  rt  )rR   ru  r  s      rU   r	  r	  73  s    333333<**40000 < < <CFF;;;;<&&t,, Y4W4W4W4WXXXX''---s    
AAAc                 :    t          |            | dk    rdn|  dS )z@Return the shell command used to configure a profile in the CLI.r1  zhermes setupz setup)r	  r  s    rU   _profile_setup_commandrg  C3  s*    !Y..>>tOOOCrW   r  c                 .   ddl m}m}  |t          |                     }	 t	          ||          \  }}t                      }t          |                    di           ||          |d<   t          |            ||           dS #  ||           w xY w)a  Write the main model assignment into a specific profile's config.yaml.

    Scopes ``load_config``/``save_config`` to ``profile_dir`` via the
    context-local HERMES_HOME override so the write lands in the target
    profile's config rather than the dashboard process's active profile.
    Clears any stale ``base_url`` / ``context_length`` the same way
    ``POST /api/model/set`` does, since the new model may differ.
    r   r  r  r  N)	r4  r  r  r  r  r   r  r   r   )r  rT   r  r  r  r   r  s          rU   _write_profile_modelrj  I3  s     VUUUUUUU$$S%5%566E*:8UKK%mm3CGGGR4H4H(TYZZGC""5)))))""5))))s   AB Brw  c                 T   ddl m}m} ddlm} d} |t          |                     }	 t                      }|                    di           }|D ]}		 t          |	          \  }
}}nP# t          $ rC}|	j
        pd                                pd}t                              d||           Y d}~_d}~ww xY w| ||
|          |d	<   |||
<   |d
z  }|rt          |           n'|s%|                    dd           t          |            ||           n#  ||           w xY w|S )a  Write MCP server entries into a specific profile's config.yaml.

    Scopes ``load_config``/``save_config`` to ``profile_dir`` via the
    context-local HERMES_HOME override (same mechanism as
    ``_write_profile_model``) so the entries land in the target profile's
    config rather than the dashboard process's active profile.

    Mirrors the per-server shape the ``POST /api/mcp/servers`` endpoint builds,
    but batched so the whole profile-create write is a single config save.
    Returns the number of servers written.
    r   ri  )r  r  r   z	<unnamed>z,Profile-create: skipping MCP server '%s': %sNr   r   )r4  r  r  r~  r  r  r   rk  r  r  rR   r   rP   r3  r   r  )r  rw  r  r  r  writtenr   r  r  r  rR   r  r	  rN  display_names                  rU   _write_profile_mcp_serversrn  ^3  s    VUUUUUUU======G$$S%5%566E*mmnn]B// 	 	F	,H,P,P)e\\    & 1r88::IkB   
  '#:#:4#N#Ni CIqLGG 	 	 GGM4(((""5))))""5))))Ns6   (D A'&D '
B419B/*D /B44AD D%keepc                    ddl m}m} ddlm}m} d |D             }d} |t          |                     }	 g }	| dz  }
|
                                r7|
                    d          D ]!}|		                    |j
        j                   "t                      } ||          }|	D ]$}||vr||vr|                    |           |dz  }%|r |||            ||           n#  ||           w xY w|S )uA  Disable every installed skill in ``profile_dir`` not in ``keep``.

    Profiles manage skill activation via a *disabled* list — all installed
    skills are active by default and users opt out. The builder's skill step
    uses "replace" semantics: the user picks exactly which seeded built-in /
    optional skills stay active, and everything else gets added to the disabled
    list. (Hub skills are installed separately via subprocess and are active on
    install.) Scoped to the profile via the HERMES_HOME override. Returns the
    number of skills newly disabled.
    r   ri  get_disabled_skillssave_disabled_skillsc                 b    h | ],}||                                 |                                 -S r  r  rW  s     rU   r  z-_disable_unselected_skills.<locals>.<setcomp>3  s2    ;;;a1;;		;;;rW   rW  r   r   )r4  r  r  hermes_cli.skills_configrr  rs  r  r  r  r   r  rR   r   r  )r  ro  r  r  rr  rs  keep_setdisabled_countr   r	  skills_rootmdr  r+	  rR   s                  rU   _disable_unselected_skillsrz  3  sl    VUUUUUUURRRRRRRR;;4;;;HN$$S%5%566E*!	!H, 	1!''
33 1 1  0000mm&&s++ 	$ 	$D8##H(<(<T"""!# 	0  h///""5))))""5))))s   B C$ $C1z/api/profilesc                    K   ddl m}  	 t          j                    }|                    d | j                   d {V }dd |D             iS # t          $ r. t                              d           dt          |           icY S w xY w)Nr   ri  r}  c                 ,    g | ]}t          |          S r  r  r  s     rU   r  z*list_profiles_endpoint.<locals>.<listcomp>3  s!    CCCQ-a00CCCrW   z@GET /api/profiles failed; falling back to profile directory scan)
r[   r}  rm   rI  rs   rs  rZ   rP   r"  r  )ru  rM  r}  s      rU   list_profiles_endpointr}  3  s      333333C'))--dL4NOOOOOOOOCC(CCCDD C C CYZZZ3LAABBBBCs   AA 5BBc           	        K   ddl m} | j        pd                                }|rd}|}| j         }n| j        rd}d}d}n| j        }|rdnd }|}	 |                    | j        || j        || j        | j	                  }|s|
                    |d           |                    | j                  }|s|                    | j                   n# t          t          t          f$ r#}t!          d	t#          |          
          d }~wt$          $ r=}t&                              d           t!          dt#          |          
          d }~ww xY w| j        pd                                }	| j        pd                                }
d}|	rG|
rE	 t/          ||	|
           d}n0# t$          $ r# t&                              d| j                   Y nw xY wd}| j        rG	 t3          || j                  }n0# t$          $ r# t&                              d| j                   Y nw xY wd}| j        rG	 t7          || j                  }n0# t$          $ r# t&                              d| j                   Y nw xY wg }| j        D ]}|pd                                }|s	 t;          d| j        dd|dgt=          d|                    }|                    ||j         d           c# t$          $ r< t&                              d|| j                   |                    |d d           Y w xY wd| j        t#          |          ||||dS )Nr   ri  r   Tr1  F)rR   r&  r(  clone_configr)  r  )quietr   r   zPOST /api/profiles failedr!  z'Setting model for new profile %s failedz-Writing MCP servers for new profile %s failedz2Applying skill selection for new profile %s failedr  rW  r  r  )r  r  z7Spawning hub-skill install %s for new profile %s failed)rs  rR   r   	model_setmcp_writtenskills_disabledhub_installs)!r[   r}  r&  r   r(  r'  create_profilerR   r)  r  seed_profile_skillscheck_alias_collisioncreate_wrapper_scriptr  FileExistsErrorr  r5   r  rZ   rP   r"  rT   r  rj  r  rn  r*  rz  r+  r  r  r   r  )r0  ru  explicit_sourcecloner&  r  r   	collisionr  rT   r  r  r  r  r  r  r  r  s                     rU   create_profile_endpointr  3  s8     333333,"3355O  $
>)	 	 
'"'1YYT
<**!n%n( + 
 
  	?,,T,>>> !66tyAA	 	:..ty999):; < < <CFF;;;; < < <2333CFF;;;;< #**,,HZ2$$&&EI QE Q	Q x777II 	Q 	Q 	QNNDdiPPPPP	Q K W	W4T4;KLLKK 	W 	W 	WNNJDIVVVVV	W O \	\8t?OPPOO 	\ 	\ 	\NNOQUQZ[[[[[	\ *,Lo D D
!r((** 		D'ty(IugF E22 D uTX F FGGGG 	D 	D 	DNNI	  
 uT B BCCCCC	D 	D		"*$  so   A>C E'DE8E

EF# #*GGG3 3*H H -I *I0/I0AKAL%$L%z/api/profiles/activec                     K   ddl m}  	 |                                 pd}n# t          $ r d}Y nw xY w	 |                                 pd}n# t          $ r d}Y nw xY w||dS )uH  Return the sticky active profile and the profile this dashboard
    process is currently running as.

    ``active`` is the sticky default written by ``hermes profile use`` —
    the profile new CLI invocations pick up. ``current`` is the profile
    the running dashboard/gateway is scoped to (derived from HERMES_HOME).
    r   ri  r1  )rm  r  )r[   r}  get_active_profilerZ   r	  )ru  rm  r  s      rU   get_active_profile_endpointr  .4  s       4333330022?i   6688EI   111s   ! 00A AAc                   K   ddl m} 	 |                    | j                   n# t          $ r#}t          dt          |                    d}~wt          $ r#}t          dt          |                    d}~wt          $ r=}t          
                    d           t          dt          |                    d}~ww xY wd	|                    | j                  d
S )u   Set the sticky active profile (mirrors ``hermes profile use``).

    Note: this does not retarget the already-running dashboard process —
    it changes which profile subsequent CLI commands and gateways use.
    r   ri  r   r   Nr   z POST /api/profiles/active failedr!  Tr  )r[   r}  set_active_profilerR   r  r5   r  r  rZ   rP   r"  r  )r0  ru  r  s      rU   set_active_profile_endpointr  C4  s      433333<''	2222 < < <CFF;;;; < < <CFF;;;; < < <9:::CFF;;;;< ,"E"Edi"P"PQQQs,   % 
CACA88C8B==Cz"/api/profiles/{name}/setup-commandc                 (   K   dt          |           iS )Nr  )rg  r  s    rU   get_profile_setup_commandr  W4  s      -d3344rW   z"/api/profiles/{name}/open-terminalc                   K   	 t          |           }t          j                            d          rt	          j        dddd|g           nt          j        dk    rH|                    dd                              d	d
          }d| d}t	          j        dd|g           nddddd|gfddddd|gfddddd|gfdddd| dgfdddd| dgfdddd| dgfddddd|gfddddd|gfdddd|gfddddd|gfg
}|D ]L\  }}t	          j        d|gt          j        t          j                  d k    rt	          j        |            nMt          d!d"#          n# t          $ r#}t          d$t          |          #          d }~wt          $ r#}t          d!t          |          #          d }~wt          $ r  t          $ r>}t                              d%|            t          d&t          |          #          d }~ww xY wd'|d(S ))Nwinzcmd.exez/crS   r   rQ
  \z\\r  z\"z0tell application "Terminal"
activate
do script "z
"
end tell	osascriptz-ezx-terminal-emulatorshz-lczgnome-terminalz--konsolezxfce4-terminalzsh -lc 'ri  zmate-terminal
lxterminaltilix	alacrittykittyxtermr  )r  r  r   r   z$No supported terminal emulator foundr   r   z*POST /api/profiles/%s/open-terminal failedr!  T)rs  r  )rg  r  r  r   r  r  r  callr  r5   r  r  r  rZ   rP   r"  )rR   r  escapedapplescriptterminal_commandsr  
popen_argsr  s           rU   open_profile_terminal_endpointr  \4  s     0<(..<""5)) $	iwGDEEEE\X%%oodF33;;CGGG%    k4=>>>> ')>dESZ([\!$4dD%#QRYdE7CD!$4d<Qw<Q<Q<Q#RS ?D:OW:O:O:O"PQd4Iw4I4I4IJK7D$w?@{D$wGH7D%9:7D$w?@! +<  &
J?j)%-%-   	 
 $Z000E $ #A     < < <CFF;;;; < < <CFF;;;;    < < <CTJJJCFF;;;;< 7+++s0   EE 
H'FHF00H9H  Hz/api/profiles/{name}c                   K   ddl m} 	 |                    | |j                  }n# t          $ r#}t          dt          |                    d }~wt          t          f$ r#}t          dt          |                    d }~wt          $ r>}t                              d|            t          dt          |                    d }~ww xY wd|j        t          |          d	S )
Nr   ri  r   r   r   zPATCH /api/profiles/%s failedr!  T)rs  rR   r   )r[   r}  rename_profiler.  r  r5   r  r  r  rZ   rP   r"  )rR   r0  ru  r   r  s        rU   rename_profile_endpointr  4  s      333333<**4?? < < <CFF;;;;( < < <CFF;;;; < < <6===CFF;;;;< s4yyAAAs,   & 
CAC"B  C9CCc                   K   ddl m} 	 |                    | d          }n# t          $ r#}t	          dt          |                    d}~wt          $ r#}t	          dt          |                    d}~wt          $ r>}t          	                    d	|            t	          d
t          |                    d}~ww xY wdt          |          dS )zDelete a profile. The dashboard collects the user's confirmation in
    its own dialog before this request, so we always pass ``yes=True`` to
    skip the CLI's interactive prompt.r   ri  T)rH  r   r   Nr   zDELETE /api/profiles/%s failedr!  r  )
r[   r}  delete_profiler  r5   r  r  rZ   rP   r"  )rR   ru  r   r  s       rU   delete_profile_endpointr  4  s      
 433333<**4T*:: < < <CFF;;;; < < <CFF;;;; < < <7>>>CFF;;;;< D		***s,   " 
C A

C A55C 9B;;C z/api/profiles/{name}/soulc                    K   t          |           dz  }|                                r@	 |                    d          ddS # t          $ r}t	          dd|           d }~ww xY wd	d
dS )NSOUL.mdr  r  T)r   rA  r!  zCould not read SOUL.md: r   r   F)r	  rA  r4  r  r5   )rR   	soul_pathr  s      rU   get_profile_soulr  4  s      $T**Y6I X	X(22G2DDPTUUU 	X 	X 	XC8VST8V8VWWWW	XU+++s   A 
A&A!!A&c                    K   t          |           dz  }	 |                    |j        d           nA# t          $ r4}t                              d|            t          dd|           d }~ww xY wdd	iS )
Nr  r  r  z PUT /api/profiles/%s/soul failedr!  zCould not write SOUL.md: r   rs  T)r	  r  r   r  rP   r"  r5   )rR   r0  r  r  s       rU   update_profile_soulr  4  s      $T**Y6IUT\G<<<< U U U94@@@4SPQ4S4STTTTU $<s   3 
A1/A,,A1z /api/profiles/{name}/descriptionc                 <  K   ddl m} t          |           }|j        pd                                }	 |                    ||d           nK# t          $ r>}t                              d|            t          dt          |                    d	}~ww xY wd
|ddS )zSet or clear a profile's role description (kanban routing signal).

    Empty string clears the description. Non-empty stores it as a
    user-authored description (``description_auto: false``) so the
    auto-describer won't overwrite it on a sweep.
    r   ri  r   F)r  r?  z'PUT /api/profiles/%s/description failedr!  r   NT)rs  r  r?  )r[   r}  r	  r  r   write_profile_metarZ   rP   r"  r5   r  )rR   r0  ru  r  r  r  s         rU   #update_profile_description_endpointr  4  s       433333&t,,K"))++D<''" 	( 	
 	
 	
 	

  < < <@$GGGCFF;;;;< tGGGs   A 
B9BBz/api/profiles/{name}/modelc                   K   t          |           }|j        pd                                }|j        pd                                }|r|st	          dd          	 t          |||           nK# t          $ r>}t                              d|            t	          dt          |                    d}~ww xY wd||d	S )
a  Set the main model (``model.default`` + ``model.provider``) for a
    specific profile's config.yaml, without touching the dashboard's own
    active profile. Mirrors ``POST /api/model/set`` (main scope) but scoped
    to the named profile via the HERMES_HOME override.
    r   r   zprovider and model are requiredr   z!PUT /api/profiles/%s/model failedr!  NT)rs  rT   r  )
r	  rT   r   r  r5   rj  rZ   rP   r"  r  )rR   r0  r  rT   r  r  s         rU   update_profile_model_endpointr  4  s       't,,K#**,,HZ2$$&&E W5 W4UVVVV<[(E:::: < < <:DAAACFF;;;;< Hu===s   A0 0
B8:9B33B8z"/api/profiles/{name}/describe-autoc                   K   t          |            	 ddlm} |                    | t	          |j                            }nK# t          $ r>}t                              d|            t          dt          |                    d}~ww xY wt	          |j                  |j        |j        t	          |j                  dS )	uo  Auto-generate a profile's description via the auxiliary LLM
    (``auxiliary.profile_describer``). Mirrors ``hermes profile describe
    <name> --auto``.

    A failed generation (no aux client, LLM error, …) is returned as
    ``ok: false`` with a reason rather than an HTTP error so the UI can
    surface it inline and let the operator fix config and retry.
    r   )r  )r  z*POST /api/profiles/%s/describe-auto failedr!  r   N)rs  r!  r  r?  )r	  r[   r  describe_profiler   r  rZ   rP   r"  r5   r  rs  r!  r  )rR   r0  r  outcomer  s        rU   describe_profile_auto_endpointr  4  s       <000000#44TT$.EYEY4ZZ < < <CTJJJCFF;;;;< 7:.* !,,  s   /A 
B9BBc              #   ~  K   | pd                                 }ddlm}m}m} ddlm} ddlm} d}|r|                                dk    r |            }n't          |          } |t          |                    }t          5  |j        }	|j        }
|j        }|j        }||_        |dz  |_        ||_        |dz  |_        	 ||ndV  |	|_        |
|_        ||_        ||_        | ||           n/# |	|_        |
|_        ||_        ||_        | ||           w w xY wddd           dS # 1 swxY w Y   dS )	u  Scope config + skill-directory resolution to ``profile`` for one request.

    Two seams must be redirected for skills/toolsets endpoints:

    1. ``load_config``/``save_config`` resolve ``get_hermes_home()`` at call
       time — the context-local override from ``set_hermes_home_override``
       reaches them (same pattern as ``_write_profile_model``).
    2. ``tools.skills_tool`` and ``tools.skill_manager_tool`` bind
       ``SKILLS_DIR`` at import time, so the override CANNOT reach them.
       Like ``_call_cron_for_profile`` does for cron's module globals,
       temporarily retarget both under a lock and restore them
       immediately after.

    ``profile`` of None/""/"current" means "the dashboard's own profile" —
    config resolution is untouched, but the skill-module globals are still
    retargeted to the *current* ``get_hermes_home()`` so writes land in the
    live home even when the import-time binding is stale (e.g. the process
    imported the modules before a HERMES_HOME override, or under test
    isolation).
    r   r   )r   r  r  )skills_tool)skill_manager_toolNr  rW  )r   r4  r   r  r  rq  r  r  r   r	  r  _SKILLS_PROFILE_LOCKr2  
SKILLS_DIR)r  r	  r   r  r  _skills_tool
_skill_mgrr   r  old_homeold_skills_dirold_mgr_homeold_mgr_skills_dirs                rU   ru  ru   5  s     , B%%''I         
 211111666666E ;	))Y66%o''*955(([)9)9::	 2 2+%0!-'2#. "-"8!,
 +h 6
	2!&!2++<<<'/L$&4L#%1J"$6J! **5111 (0L$&4L#%1J"$6J! **51111 !!2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2s*   ?D2C6*D26,D""D22D69D6c              #     K   | pd                                 }|r|                                dk    rdV  dS ddlm}m} t          |          } |t          |                    }	 |V   ||           dS #  ||           w xY w)u  Await-safe, config-only profile scope for handlers that ``await``.

    Unlike ``_profile_scope`` this touches ONLY the context-local
    ``set_hermes_home_override`` contextvar — it does NOT swap the
    process-global ``skills_tool``/``skill_manager`` module attributes.
    Those globals are shared across all event-loop tasks, so holding them
    across an ``await`` lets a concurrent skills request restore THIS
    request's profile dir on its ``finally`` (cross-contamination). The
    contextvar override is task-local and survives an ``await`` cleanly,
    which is all endpoints that resolve ``get_hermes_home()`` at call time
    (config, env, gateway status) actually need.

    None/""/"current" means the dashboard's own profile — no override.
    r   r  Nr   ri  )r   r   r4  r  r  r	  r  )r  r	  r  r  r  r   s         rU   r  r  [5  s        B%%''I 	))Y66


       
 'y11K$$S%5%566E*""5)))))""5))))s   )A: :Bc                   >    e Zd ZU eed<   eed<   dZee         ed<   dS )SkillTogglerR   r  Nr  )r  r  r  r  r  r   r  r   r  rW   rU   r  r  }5  s8         
IIIMMM!GXc]!!!!!rW   r  z/api/skillsc                   K   ddl m} ddlm} ddlm}m}m}m} t          |           5  t                      } ||          } |d          }	 |            }
 |            } |            }d d d            n# 1 swxY w Y   |	D ]T}|d         |v|d<    ||
                    |d         i                     |d	<   |d         |v rd
n|d         |v rdnd|d<   U|	S )Nr   )_find_all_skills)rr  )_read_bundled_manifest_names_read_hub_installed_namesactivity_count
load_usageT)skip_disabledrR   r  usagehubr   rV  
provenance)tools.skills_toolr  ru  rr  tools.skill_usager  r  r  r  ru  r   r   )r  r  rr  r  r  r  r  r{  r+	  rW  r  bundled_names	hub_namesr>  s                 rU   
get_skillsr  5  s     222222<<<<<<            
	 	  
0 
0&&v..!!555

 5466--//	
0 
0 
0 
0 
0 
0 
0 
0 
0 
0 
0 
0 
0 
0 
0  
 
y0)#^EIIai$<$<=='
vY)++EEi=88 	
,
 Ms   AA::A>A>z/api/skills/togglec                 T  K   ddl m}m} t          | j        p|          5  t                      } ||          }| j        r|                    | j                   n|	                    | j                    |||           d d d            n# 1 swxY w Y   d| j        | j        dS )Nr   rq  Tr  )
ru  rr  rs  ru  r  r   r  rQ  rR   r  )r0  r  rr  rs  r{  r+	  s         rU   toggle_skillr  5  s     RRRRRRRR	/	0	0 / /&&v..< 	$TY''''LL###VX.../ / / / / / / / / / / / / / / 	dlCCCs   A"BBBc                   X    e Zd ZU eed<   eed<   dZee         ed<   dZee         ed<   dS )SkillCreaterR   r   Nr  r  )r  r  r  r  r  r  r   r  r  rW   rU   r  r  5  sL         
IIILLL"Hhsm"""!GXc]!!!!!rW   r  c                   >    e Zd ZU eed<   eed<   dZee         ed<   dS )SkillContentUpdaterR   r   Nr  r  r  rW   rU   r  r  5  s8         
IIILLL!GXc]!!!!!rW   r  c                  N    	 ddl m}   | d           dS # t          $ r Y dS w xY w)zBest-effort: invalidate the skills system-prompt snapshot after a write.

    Mirrors what ``skill_manage`` does so a dashboard-authored skill is picked
    up by the next session without a manual cache reset.
    r    clear_skills_system_prompt_cacheT)clear_snapshotN)agent.prompt_builderr  rZ   r  s    rU   _clear_skills_prompt_cacher  5  sU    IIIIII((======   s    
$$z/api/skills/contentc                   K   ddl m} t          |          5   ||           }|st          dd|  d          |d         dz  }|                                st          dd|  d	          	 |                    d
          }n1# t          $ r$}t          dt          |                    |d}~ww xY w| |t          |          dcddd           S # 1 swxY w Y   dS )zCReturn the raw SKILL.md text for a skill, for the dashboard editor.r   )_find_skillr   zSkill 'z' not found.r   r   r   z' has no SKILL.md.r  r  r!  N)rR   r   r   )tools.skill_manager_toolr  ru  r5   rA  r4  r  r  )rR   r  r  foundr  r   rN  s          rU   get_skill_contentr  5  s      544444		 	  I ID!! 	VC8T$8T8T8TUUUU=:-   	\C8Z$8Z8Z8Z[[[[	K(('(::GG 	K 	K 	KCCAAAsJ	K#h--HHI I I I I I I I I I I I I I I I I Is6   AC0BC
B5B00B55CCCc                 :  K   ddl m} t          | j                  5   || j        | j        | j        pd          }ddd           n# 1 swxY w Y   |                    d          s%t          d|                    dd                    t                       |S )	uk  Create a new custom skill (SKILL.md) from the dashboard editor.

    Calls the same validated write path as the agent's ``skill_manage``
    tool (frontmatter validation, name/category validation, size limit,
    optional security scan) — but bypasses the agent write-approval gate:
    a write from the authenticated dashboard IS the user acting directly.
    r   )_create_skillNr  r   r  zFailed to create skill.r   )
r  r  ru  r  rR   r   r  r   r5   r  )r0  r  r  s      rU   create_skillr  5  s       766666		%	% O Oty$,8MNNO O O O O O O O O O O O O O O::i   dFJJwHa4b4bcccc   Ms   AAAc                 |  K   ddl m} t          | j                  5   || j        | j                  }ddd           n# 1 swxY w Y   |                    d          sN|                    dd          }dt          |                                          v rdnd	}t          ||
          t                       |S )zIReplace the SKILL.md of an existing skill (full rewrite) from the editor.r   )_edit_skillNr  r  zFailed to update skill.r*  r   r   r   )r  r  ru  r  rR   r   r   r  r   r5   r  )r0  r  r  errr)  s        rU   update_skill_contentr  5  s      544444		%	% 6 6TY556 6 6 6 6 6 6 6 6 6 6 6 6 6 6::i   <jj";<<#s3xx~~'7'777Ss;;;;   Mr5  z/api/tools/toolsetsc                 8  K   ddl m}mmm}m} ddlm} ddlm	} t          |           5  t                       |            }fd|D             }fd|D             }d d d            n# 1 swxY w Y   g }	|D ]\  }
}}	 t          t           ||
                              }n# t          $ r g }Y nw xY w |
          }|
||         v }|	                    |
 ||          || | |||                    || ||
          |d	           |	S )Nr   )$_get_effective_configurable_toolsetsr2  _toolset_configuration_platform_toolset_has_keysgui_toolset_label)r  )resolve_toolsetc                 .    h | ]\  }}} |          S r  r  )r   rR   r  r  s      rU   r  zget_toolsets.<locals>.<setcomp>6  s8     
 
 
6@dAq++D11
 
 
rW   c                 .    i | ]}| |d           S )Fr4  r  )r   r  r2  r{  s     rU   rI  z get_toolsets.<locals>.<dictcomp>6  sI     
 
 
  )),1  
 
 
rW   )	rR   r?  r  r  r  r  r  r}  rq  )r=  r  r2  r  r  r  hermes_cli.platformsr  toolsetsr  ru  r   r~  r~   rZ   r   )r  r  r  r  r  r  toolset_rowstarget_platformsenabled_by_platformr  rR   r?  descrq  target_platformr  r2  r  r{  s                   @@@rU   get_toolsetsr    6  sL                   433333((((((		 	  
 
;;==
 
 
 
DP
 
 

 
 
 
 
 -
 
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 F)  eT	3t445566EE 	 	 	EEE	99$??0AA
&&u--'//@@  "#++D&99
 
 	 	 	 	 Ms#   6A11A58A5%B--B<;B<c                   4    e Zd ZU eed<   dZee         ed<   dS )ToolsetToggler  Nr  r  r  rW   rU   r  r  26  r  rW   r  z/api/tools/toolsets/{name}c                   K   ddl m}m}m}m} d  |            D             }| |vrt          dd|             ||           }t          |j        p|          5  t                      }	t           ||	|d                    }
|j
        r|
                    |            n|
                    |             ||	||
           d	d	d	           n# 1 swxY w Y   d
| ||j
        dS )a  Enable/disable a configurable toolset for its configuration platform.

    Most toolsets persist to ``platform_toolsets.cli``. Platform-restricted
    toolsets instead target their supported platform (for example, Discord's
    native toolsets persist to ``platform_toolsets.discord``). The shared
    ``_save_platform_tools`` helper keeps the GUI and CLI in lockstep. Scoped
    to ``body.profile`` when provided. Returns 400 for unknown toolset keys.
    r   )r  r2  _save_platform_toolsr  c                     h | ]\  }}}|	S r  r  r   ts_keyr  s      rU   r  z!toggle_toolset.<locals>.<setcomp>H6      OOO1VOOOrW   r   Unknown toolset: r   Fr4  NT)rs  rR   r  r  )r=  r  r2  r  r  r5   ru  r  r   r~   r  r  rQ  )rR   r0  r  r  r2  r  r  r  r  r{  r  s              rU   toggle_toolsetr
  76  s                 PO(L(L(N(NOOOE54N4N4NOOOO55d;;O	/	0	0 ? ?,1  
 
 < 	"KKOOD!!!V_g>>>? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #<	  s   A)CCCz!/api/tools/toolsets/{name}/configc                   K   ddl m}m}m}m} ddlm d  |            D             }| |vrt          dd|            t          |          5  t                      }|
                    |           }g }	d}
|r |||d	
          D ]}fd|
                    dg           D             } |||d	
          }|r
|
|d         }
|	                    |d         |
                    dd          |
                    dd          ||
                    d          t          |
                    d                    |d           ddd           n# 1 swxY w Y   | |du|	|
dS )a  Return the provider matrix + key status for a toolset's config panel.

    Surfaces the same provider rows the CLI ``hermes tools`` picker shows
    (via ``_visible_providers``), each with its ``env_vars`` annotated with
    current ``is_set`` state so the GUI can render provider selection + key
    entry. Toolsets without a ``TOOL_CATEGORIES`` entry return an empty
    provider list and ``has_category: false``. Returns 400 for unknown keys.
    r   )TOOL_CATEGORIESr  _is_provider_active_visible_providersr
  c                     h | ]\  }}}|	S r  r  r  s      rU   r  z%get_toolset_config.<locals>.<setcomp>u6  r  rW   r   r	  r   NTr  c                     g | ]l}|d          |                     d|d                    |                     d          |                     d          t           |d                              dmS )r  rD   r   r1  )r  rD   r   r1  r_  )r   r   )r   r  r
  s     rU   r  z&get_toolset_config.<locals>.<listcomp>6  s     	 	 	   !x"#%%!E(";"; uuU||#$55#3#3"&}}QuX'>'>"?"? 	 	 	rW   r  rR   badger   tag
post_setuprequires_nous_auth)rR   r  r  r  r  r  r]  )rR   has_categoryr  active_provider)r=  r  r  r  r  r  r
  r5   ru  r   r   r   r   )rR   r  r  r  r  r  r  r{  catr  r  provr  r]  r
  s                 @rU   get_toolset_configr  c6  sS                 0/////OO(L(L(N(NOOOE54N4N4NOOOO		 	     !!$''	 	**3DIII  	 	 	 	 "XXj"55	 	 	 0/f$OOO	 3!8&*6lO   L!XXgr2288E2.. ("&((<"8"8*.txx8L/M/M*N*N!*" "    1                             D 4*	  s   C5EEEc                   4    e Zd ZU eed<   dZee         ed<   dS )ToolsetProviderSelectrT   Nr  r  r  rW   rU   r  r  6  s/         MMM!GXc]!!!!!rW   r  	image_gen	video_gen)r  r  r  provider_rowc                     | dk    r.|                     d          p|                     d          rdndS | dk    r|                     d          S dS )a$  Map a provider picker row to its model-catalog plugin name.

    Plugin-backed rows carry ``image_gen_plugin_name`` / ``video_gen_plugin_name``;
    the managed "Nous Subscription" image row instead carries the legacy
    ``imagegen_backend: "fal"`` marker (same underlying FAL catalog).
    r  image_gen_plugin_nameimagegen_backendfalNr  video_gen_plugin_namer   )r  r  s     rU   _resolve_toolset_model_pluginr$  6  sm      788 
!%%&899CEEt	
  78884rW   r   c                 J    ddl m}m} | dk    r ||          S  ||          S )zHReturn ``(catalog_dict, default_model)`` for a toolset's plugin backend.r   )_plugin_image_gen_catalog_plugin_video_gen_catalogr  )r=  r&  r'  )r  r   r&  r'  s       rU   _toolset_model_catalogr(  6  sY           
 ((555$$[111rW   c                     ddl m}mm} |                    |           }|dS  ||d          }rt          fd|D             d          S t          fd|D             d          S )zFResolve a provider picker row by name, or the active row when omitted.r   )r  r  r  NTr  c              3   N   K   | ]}|                     d           k    |V   dS r   r   )r   r   rT   s     rU   r   z-_find_toolset_provider_row.<locals>.<genexpr>6  s7      BB1f(A(AQ(A(A(A(ABBrW   c              3   8   K   | ]} |d           |V  dS )Tr  Nr  )r   r   r  r{  s     rU   r   z-_find_toolset_provider_row.<locals>.<genexpr>6  s;      MMq//6tLLLMMMMMMMrW   )r=  r  r  r  r   r   )r  r{  rT   r  r  r  r   r  s    ``    @rU   _find_toolset_provider_rowr,  6  s              

f
%
%C
{tc6t<<<D JBBBBBBBDIIIMMMMMDMMMt  rW   z!/api/tools/toolsets/{name}/modelsc                    K   t                               |           }|| dg dddS t          |          5  t                      }t	          | ||          }|rt          | |          nd}|s| dg dddcddd           S t          | |          \  }}|                    |          }	d}
t          |	t                    rR|	                    d          }t          |t                    r(|
                                r|
                                }
|
|vr||v r|nd}
ddd           n# 1 swxY w Y   d |                                D             }| t          |          |r|                    d          nd|||
|dS )u  Return the model catalog for a toolset backend (image/video gen).

    The GUI counterpart of the model picker `hermes tools` runs after a
    backend is selected — e.g. FAL's multi-model catalog (speed / strengths /
    price per model). ``provider`` names a picker row; omitted, the currently
    active provider is used. Toolsets without model catalogs return
    ``has_models: false``.
    NF)rR   
has_modelsr  r  r1  r  c           
          g | ]]\  }}||                     d |          |                     dd          |                     dd          |                     dd          d^S )r]  speedr   	strengthsprice)r  r]  r0  r1  r2  r   )r   model_idri  s      rU   r  z&get_toolset_models.<locals>.<listcomp>7  s|     	 	 	 Hd xx	844XXgr**+r22XXgr**	
 	
	 	 	rW   rR   )rR   r.  rT   r   r  r  r1  )_MODEL_CATALOG_TOOLSETSr   ru  r   r,  r$  r(  rv  r  r  r   r  r   )rR   rT   r  sectionr{  r  r   catalogdefault_modelsection_cfgr  r  r  s                rU   get_toolset_modelsr9  6  s0      &))$//GERD]abbb		 	  J J(vx@@=@J.tS999d 	# J J J J J J J J "8f!E!Ejj))k4(( 	&//'**C#s## &		 &))++'!!'4'?'?mmTG+J J J J J J J J J J J J J J J.	 	 &mmoo	 	 	F 6ll'*4CGGFOOO   s   =D) BD))D-0D-c                   N    e Zd ZU eed<   dZee         ed<   dZee         ed<   dS )ToolsetModelSelectr  NrT   r  )r  r  r  r  r  rT   r   r  r  rW   rU   r;  r;  7  sC         JJJ"Hhsm"""!GXc]!!!!!rW   r;  z /api/tools/toolsets/{name}/modelc                   K   t                               |           }|t          dd|            |j        pd                                }|st          dd          t          |j        p|          5  t                      }t          | ||j	                  }|rt          | |          nd}|st          dd|            t          | |          \  }}	||vrt          dd|d	|          |                    |i           }
t          |
t                    si }
|
||<   ||
d
<   t          |           ddd           n# 1 swxY w Y   d| ||dS )u$  Persist a backend model selection (``image_gen.model`` / ``video_gen.model``).

    Validates the model against the resolved backend's catalog — the same
    write the CLI's post-selection model picker performs. Returns 400 for
    toolsets without model catalogs or unknown model ids.
    Nr   zToolset has no model catalog: r   r   zmodel is requiredz'No model-capable backend is active for zUnknown model z for backend r  T)rs  rR   r  r   )r4  r   r5   r  r   ru  r  r   r,  rT   r$  r(  rk  rv  r  r   )rR   r0  r  r5  r3  r{  r  r   r6  _defaultr8  s              rU   select_toolset_modelr>  "7  s      &))$//G$KT$K$K
 
 
 	
 
 b''))H I4GHHHH	/	0	0  (vt}EE=@J.tS999d 	GGG   
 34@@7""KKKKK   
 ''44+t,, 	*K)F7O'GF-              0 x6JJJs   7CEE
E
z#/api/tools/toolsets/{name}/providerc                   K   ddl m}m} d  |            D             }| |vrt          dd|            t	          |j        p|          5  t                      }	  || |j        |           nC# t          $ r6}t          dt          |          
                    d                    d}~ww xY wt          |           ddd           n# 1 swxY w Y   d	| |j        d
S )u  Persist a provider selection for a toolset (no key prompting).

    Delegates to ``apply_provider_selection`` — the shared, non-interactive
    core extracted from the CLI configurator — so the GUI and ``hermes tools``
    write identical config keys (``web.backend``, ``tts.provider``, etc.).
    API keys and post-setup flows are handled by separate endpoints. Returns
    400 for unknown toolset or provider names.
    r   )apply_provider_selectionr  c                     h | ]\  }}}|	S r  r  r  s      rU   r  z*select_toolset_provider.<locals>.<setcomp>b7  r  rW   r   r	  r   r  NT)rs  rR   rT   )r=  r@  r  r5   ru  r  r   rT   r
  r  r   r   )rR   r0  r  r@  r  r  r{  rN  s           rU   select_toolset_providerrB  Q7  sr            
 PO(L(L(N(NOOOE54N4N4NOOOO	/	0	0  	M$$T4=&AAAA 	M 	M 	MCCs8K8KLLLL	MF               $-@@@s6   CA0/C0
B0:1B++B00CCCc                   D    e Zd ZU eeef         ed<   dZee         ed<   dS )ToolsetEnvUpdater  Nr  )r  r  r  r   r  r  r  r   r  rW   rU   rD  rD  p7  s:         	c3h!GXc]!!!!!rW   rD  z/api/tools/toolsets/{name}/envc                   K   ddl m}m}m} ddlmm} d  |            D             }| |vrt          dd|            t          |j	        p|          5  t                      }|                    |           }	t                      |	rE ||	|d	          D ]6}
|
                    d
g           D ]}                    |d                    7fd|j        D             }|r7t          dd|  dd                    t!          |                               g }g }|j                                        D ]\  }}|rz|                                rf	  |||                                           n0# t&          $ r#}t          dt)          |                    d}~ww xY w|                    |           |                    |           fdD             }ddd           n# 1 swxY w Y   d| |||dS )uX  Persist API keys for a toolset's provider env vars.

    Writes each ``key: value`` to ``~/.hermes/.env`` via ``save_env_value`` —
    the same store ``hermes tools`` writes when it prompts for keys. Keys are
    validated against the env-var allowlist for the toolset's category (the
    union of every visible provider's ``env_vars``), so the GUI can't write an
    arbitrary env var through this endpoint. A blank value is treated as
    "leave unchanged" and skipped. Returns the saved/skipped key lists and the
    refreshed ``is_set`` status. Returns 400 for unknown toolset or env keys.
    r   )r  r  r  )r
  r   c                     h | ]\  }}}|	S r  r  r  s      rU   r  z#save_toolset_env.<locals>.<setcomp>7      RRR<61aRRRrW   r   r	  r   Tr  r  r  c                     g | ]}|v|	S r  r  )r   r  rT  s     rU   r  z$save_toolset_env.<locals>.<listcomp>7  s#    ;;;!7*:*:1*:*:*:rW   zUnknown env var(s) for toolset r	  r  Nc                 B    i | ]}|t           |                    S r  )r   )r   r  r
  s     rU   rI  z$save_toolset_env.<locals>.<dictcomp>7  s-    ===!T--**++===rW   )rs  rR   savedskippedr_  )r=  r  r  r  r  r
  r   r5   ru  r  r   r   r~   r  r  r  r~  r  r   r  r  r   )rR   r0  r  r  r  r  r   valid_tsr{  r  r  r  r|  rJ  rK  r  rr  rN  r)  rT  r
  s                      @@rU   save_toolset_envrM  u7  s              
 @???????RR+O+O+Q+QRRRH84N4N4NOOOO	/	0	0 > >!!$''EE 	***3DIII * **b11 * *AKK%))))* <;;;dh;;; 	]]]6RY??A[A[]]   
 (..** 	$ 	$JC $ $J"N36666! J J J'CCIIIIJS!!!!s####====W===9> > > > > > > > > > > > > > >: uTZ[[[s7   C=G,E65G,6
F# FF##=G,,G03G0c                   4    e Zd ZU eed<   dZee         ed<   dS )ToolsetPostSetupr  Nr  r  r  rW   rU   rO  rO  7  r  rW   rO  z%/api/tools/toolsets/{name}/post-setupc                   K   ddl m}m} d  |            D             }| |vrt          dd|            |j         |            vrt          dd|j                   	 t          t          |j        p|          dd	|j        gz   d
          }nJ# t          $ r  t          $ r3}t          
                    d           t          dd|           d}~ww xY wd|j        d
|j        dS )u  Spawn a provider's post-setup install hook as a background action.

    Post-setup hooks (npm install for browser/Camofox, pip install for
    KittenTTS/Piper/ddgs, cua-driver fetch, etc.) are long-running and
    text-output, so this follows the spawn-action pattern: it launches
    ``hermes tools post-setup <key>`` and the frontend tails the log via
    ``GET /api/actions/tools-post-setup/status``. The ``key`` is validated
    against the declared post-setup allowlist before spawning. Returns 400
    for unknown toolset or post-setup key.

    ``profile`` spawns the hook as ``hermes -p <profile> tools post-setup``.
    Most hooks install machine-level artifacts (repo node_modules, shared
    pip packages) where the scope is inert, but hooks that read config or
    write per-profile state must see the same HERMES_HOME the rest of the
    drawer's writes targeted — so the scope is threaded for consistency.
    r   )r  valid_post_setup_keysc                     h | ]\  }}}|	S r  r  r  s      rU   r  z)run_toolset_post_setup.<locals>.<setcomp>7  rG  rW   r   r	  r   zUnknown post-setup key: rq  z
post-setuprr  z Failed to spawn tools post-setupr!  zFailed to run post-setup: NT)rs  r  rR   r  )r=  r  rQ  r5   r  r  r  r  rZ   rP   r"  r  )rR   r0  r  r  rQ  rL  r  rN  s           rU   run_toolset_post_setuprS  7  sv     (       
 SR+O+O+Q+QRRRH84N4N4NOOOOx,,....$Itx$I$I
 
 
 	

#dl5g66dh/0
 

     
 
 
9:::$F$F$F
 
 
 	


 tx1CDHUUUs   "/B C&.CCz/api/tools/computer-use/statusc                 x   K   ddl m} t          |           5   |            cddd           S # 1 swxY w Y   dS )zCross-platform Computer Use readiness for the desktop card.

    See ``tools.computer_use.permissions.computer_use_status`` for the payload
    shape. Read-only and fast (shells ``cua-driver doctor`` + macOS
    ``permissions status``).
    r   )computer_use_statusN)tools.computer_use.permissionsrU  ru  )r  rU  s     rU   get_computer_use_statusrW  7  s       CBBBBB		 	  % %""$$% % % % % % % % % % % % % % % % % %s   
/33z)/api/tools/computer-use/permissions/grantc                 :  K   t           j        dk    rt          dd          	 t          t	          |           g dz   d          }nJ# t          $ r  t
          $ r3}t                              d           t          dd	|           d
}~ww xY wd|j        ddS )a  Spawn ``hermes computer-use permissions grant`` as a background action.

    macOS-only: ``cua-driver permissions grant`` launches CuaDriver via
    LaunchServices so the TCC dialog is attributed to com.trycua.driver, then
    waits for approval. The frontend polls ``GET /api/actions/computer-use-
    grant/status`` and re-reads ``/status`` once it exits. Windows/Linux have
    no TCC toggles to grant, so this returns 400 there.
    rQ
  r   z3Computer Use permission grants are a macOS concept.r   )zcomputer-usepermissionsgrantr  z.Failed to spawn computer-use permissions grantr!  zFailed to request permissions: NTr  )	r  r  r5   r  r  rZ   rP   r"  r  r2  s      rU   grant_computer_use_permissionsr[  7  s       |xH
 
 
 	

#g&&6667 
 

     
 
 
GHHH$Kc$K$K
 
 
 	


 tx1EFFFs   "A B.B

Bc                   4    e Zd ZU eed<   dZee         ed<   dS )RawConfigUpdate	yaml_textNr  r  r  rW   rU   r]  r]  !8  s/         NNN!GXc]!!!!!rW   r]  z/api/config/rawc                   K   t          |           5  t                      }ddd           n# 1 swxY w Y   |                                sdt          |          dS |                    d          t          |          dS )uc  Raw config.yaml text plus its resolved path.

    ``path`` is resolved inside ``_profile_scope`` so the Config page header
    shows the file the switched profile actually reads/writes — /api/status's
    ``config_path`` is machine-global and always reports the dashboard
    process's own profile, which is wrong under the global profile switcher.
    Nr   )r  r   r  r  )ru  r   rA  r  r4  )r  r   s     rU   get_config_rawr`  &8  s       
	 	  ! !  ! ! ! ! ! ! ! ! ! ! ! ! ! ! !;;== /CII...NNGN44c$iiHHHs   -11c                 b  K   	 t          j        | j                  }t          |t                    st          dd          t          | j        p|          5  t          |d           d d d            n# 1 swxY w Y   ddiS # t           j	        $ r}t          dd|           d }~ww xY w)	Nr   zYAML must be a mappingr   F)merge_existingrs  TzInvalid YAML: )
r  rj  r^  rv  r  r5   ru  r  r   	YAMLError)r0  r  r  r  s       rU   update_config_rawrd  68  s     
J//&$'' 	RC8PQQQQDL3G44 	6 	6 u5555	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 d|> J J J4HQ4H4HIIIIJs<   AB A7+B 7A;;B >A;?B B.B))B.r  c                     	 | j                             d|f          }d |                                D             S # t          $ r g cY S w xY w)a  Per-(model, task) auxiliary usage within the window (issue #23270).

    Reads the task-dimension rows (task != '') that record_auxiliary_usage
    writes into session_model_usage. Returns [] when the table predates the
    task column (older DB opened read-only by newer code).
    ai  
            SELECT u.model,
                   u.task,
                   u.billing_provider,
                   SUM(u.input_tokens) as input_tokens,
                   SUM(u.output_tokens) as output_tokens,
                   SUM(u.cache_read_tokens) as cache_read_tokens,
                   SUM(u.reasoning_tokens) as reasoning_tokens,
                   COALESCE(SUM(u.estimated_cost_usd), 0) as estimated_cost,
                   COUNT(DISTINCT u.session_id) as sessions,
                   SUM(COALESCE(u.api_call_count, 0)) as api_calls,
                   MAX(u.last_seen) as last_used_at
            FROM session_model_usage u
            JOIN sessions s ON s.id = u.session_id
            WHERE s.started_at > ? AND u.task != ''
            GROUP BY u.model, u.task, u.billing_provider
            ORDER BY SUM(u.input_tokens) + SUM(u.output_tokens) DESC
        c                 ,    g | ]}t          |          S r  r  r  s     rU   r  z#_aux_usage_rows.<locals>.<listcomp>d8  s    000AQ000rW   )r.  r8  r9  rZ   )rE  r  r  s      rU   _aux_usage_rowsrh  J8  sn    h  " Y# $ 100000    			s   9< A
Aby_modelaux_rowsc           
         |s| S i }| D ]}|||                     d          pd<   |D ]}|                     d          pd}|                     |          }||dddddd}|||<   |                     d          pd|                     d          pdz   |d<   |                     d          pd|                     d          pdz   |d<   |                     d          pd|                     d          pdz   |d<   |                     d	          pd|                     d	          pdz   |d	<   |                    d
g           }|                    |                     d          pd|                     d          pd|                     d          pd|                     d          pd|                     d	          pdd           t          |                                          }|                    d d           |S )uO  Fold aux usage rows into the sessions-derived per-model list.

    Aux usage lives only in session_model_usage (never in the sessions
    counters), so adding it here cannot double-count. Models that ONLY
    appear via aux calls (e.g. a dedicated vision model) get their own
    entry — previously they were entirely invisible.
    r  r|  Nr   )r  input_tokensoutput_tokensestimated_costrF  	api_callsrl  rm  rn  ro  	aux_tasksr  r   )r  rl  rm  rn  ro  c                 `    |                      d          pd|                      d          pdz   S Nrl  r   rm  r   r6  s    rU   r  z*_merge_aux_into_by_model.<locals>.<lambda>8  .    quu^,,1aeeO6L6L6QPQR rW   Trp  r   rk  r   r  r  r  )	ri  rj  rz  r  rA  r  rh   r  r  s	            rU   _merge_aux_into_by_modelrv  k8  sR     (*F 4 403swww,9--    -IE""> !!""# F #F5M"(**^"<"<"AcggnF]F]Fbab!c~#)::o#>#>#C!P_H`H`Hede"f$*JJ/?$@$@$EA#''RbJcJcJhgh#i %zz+66;!@T@T@YXYZ{!!+r22GGFOO)rGGN338q WW_55:!gg&677<1--2
 
 	 	 	 	 &--//""F
KKRR     MrW   c                    i }| D ]}|                     d          pd}|                    ||ddddg d          }|dxx         |                     d          pdz  cc<   |dxx         |                     d          pdz  cc<   |dxx         |                     d          pdz  cc<   |dxx         |                     d          pdz  cc<   |                     d	          pd
}||d         vr|d                             |           	t          |                                          }|                    d d           |S )z?Aggregate aux usage rows across models into a per-task summary.r  r   r   )r  rl  rm  rn  ro  r  rl  rm  rn  ro  r  r|  r  c                 `    |                      d          pd|                      d          pdz   S rr  r   rs  s    rU   r  z#_aux_task_summary.<locals>.<lambda>8  rt  rW   Trp  ru  )rj  by_taskrA  r  rj  r  r  s          rU   _aux_task_summaryrz  8  s   )+G & &wwv$"t&
 &
   	
.SWW^4499	/cggo66;!;	
sww'788=A=	+#''+..3!3  -I(##hKu%%%'..""##F
KKRR     MrW   daysc           
      ,   ddl m} t          |          }	 t          j                    | dz  z
  }|j                            d|f          }d |                                D             }|j                            d|f          }d |                                D             }t          ||          }	t          ||	          }|j                            d|f          }
t          |

                                          } ||                              | 	          }|                    d
dddddg d          }||t          |	          || ||                    dg           d|                                 S # |                                 w xY w)Nr   )InsightsEngineQ a}  
            SELECT date(started_at, 'unixepoch') as day,
                   SUM(input_tokens) as input_tokens,
                   SUM(output_tokens) as output_tokens,
                   SUM(cache_read_tokens) as cache_read_tokens,
                   SUM(reasoning_tokens) as reasoning_tokens,
                   COALESCE(SUM(estimated_cost_usd), 0) as estimated_cost,
                   COALESCE(SUM(actual_cost_usd), 0) as actual_cost,
                   COUNT(*) as sessions,
                   SUM(COALESCE(api_call_count, 0)) as api_calls
            FROM sessions WHERE started_at > ?
            GROUP BY day ORDER BY day
        c                 ,    g | ]}t          |          S r  rg  r  s     rU   r  z(_get_usage_analytics.<locals>.<listcomp>8  s    111Qa111rW   a  
            SELECT model,
                   SUM(input_tokens) as input_tokens,
                   SUM(output_tokens) as output_tokens,
                   COALESCE(SUM(estimated_cost_usd), 0) as estimated_cost,
                   COUNT(*) as sessions,
                   SUM(COALESCE(api_call_count, 0)) as api_calls
            FROM sessions WHERE started_at > ? AND model IS NOT NULL
            GROUP BY model ORDER BY SUM(input_tokens) + SUM(output_tokens) DESC
        c                 ,    g | ]}t          |          S r  rg  r  s     rU   r  z(_get_usage_analytics.<locals>.<listcomp>8  s    555DGG555rW   a2  
            SELECT SUM(input_tokens) as total_input,
                   SUM(output_tokens) as total_output,
                   SUM(cache_read_tokens) as total_cache_read,
                   SUM(reasoning_tokens) as total_reasoning,
                   COALESCE(SUM(estimated_cost_usd), 0) as total_estimated_cost,
                   COALESCE(SUM(actual_cost_usd), 0) as total_actual_cost,
                   COUNT(*) as total_sessions,
                   SUM(COALESCE(api_call_count, 0)) as total_api_calls
            FROM sessions WHERE started_at > ?
        )r{  rW  )total_skill_loadstotal_skill_editstotal_skill_actionsdistinct_skills_used)r  
top_skillsrq  )dailyri  ry  totalsperiod_daysrW  rq  )agent.insightsr}  ra  rC  r.  r8  r9  rh  rv  r  fetchonegenerater   rz  r   )r{  r  r}  rE  r  r  r  cur2ri  rj  cur3r  insights_reportrW  s                 rU   _get_usage_analyticsr  8  s   ------	%g	.	.BJu-h   Y  21#,,..111x 	! Y	 	 65T]]__555 #2v..+Hh??x 
! Y
 
 dmmoo&&(.,,5545@@ $$X%&%&'(()	  0
 0
     )22 %(("55
 
 	







s   EE= =Fz/api/analytics/usagec                 H   K   t          j        t          | |           d {V S r  )rm   rd  r  r{  r  s     rU   get_usage_analyticsr  9  s.      "#7wGGGGGGGGGrW   c                 v	   t          |          }	 t          j                    | dz  z
  }|j                            d|f          }d |                                D             }t          ||          D ]}|                    |                    d          pd|                    d          pd|                    d          pd	|                    d
          pd	|                    d          pd	|                    d          pd	|                    d          pd	d	|                    d          pd	|                    d          pd	d	|                    d          d	|                    d          pdd           i }|D ]@|                                        d          pdg                                          Ag }|	                                D ]v}	d |	D             }
t          |
          dk    r>|
d	         |	D ]u s                    d          rt          fddD                       }|r:                    d          pd	                    d          pd	z   d<   t                              d          pd	                    d          pd	          d<                       d          pd	                    d
          pd	z   }                    d          pd	}|r||z  nd	d<   |                               |                    fd|	D                        a|                    |	           x|                    d d           g }|D ]׊                    d          pd}d         }i }	 d	dlm}  |||          }|'|j        |j        |j        |j        |j        |j        d}n# t.          $ r Y nw xY w|                    ||d         d
         d         d         d         d          d         d         d!         d         d         |d"           |j                            d#|f          }t1          |                                          }||| d$|                                 S # |                                 w xY w)%zRich per-model analytics for the Models dashboard page.

    Returns token/cost/session breakdown per model plus capability metadata
    from models.dev (context window, vision, tools, reasoning, etc.).
    r~  a  
            SELECT model,
                   billing_provider,
                   SUM(input_tokens) as input_tokens,
                   SUM(output_tokens) as output_tokens,
                   SUM(cache_read_tokens) as cache_read_tokens,
                   SUM(reasoning_tokens) as reasoning_tokens,
                   COALESCE(SUM(estimated_cost_usd), 0) as estimated_cost,
                   COALESCE(SUM(actual_cost_usd), 0) as actual_cost,
                   COUNT(*) as sessions,
                   SUM(COALESCE(api_call_count, 0)) as api_calls,
                   SUM(tool_call_count) as tool_calls,
                   MAX(started_at) as last_used_at,
                   AVG(input_tokens + output_tokens) as avg_tokens_per_session
            FROM sessions WHERE started_at > ? AND model IS NOT NULL AND model != ''
            GROUP BY model, billing_provider
            ORDER BY SUM(input_tokens) + SUM(output_tokens) DESC
        c                 ,    g | ]}t          |          S r  rg  r  s     rU   r  z)_get_models_analytics.<locals>.<listcomp>)9  s    444DGG444rW   r  r|  billing_providerr   rl  r   rm  cache_read_tokensreasoning_tokensrn  rF  ro  last_used_atr  )r  r  rl  rm  r  r  rn  actual_costrF  ro  
tool_callsr  avg_tokens_per_sessionaux_taskc                 <    g | ]}|                     d           |S )r  r   r  s     rU   r  z)_get_models_analytics.<locals>.<listcomp>M9  s*    PPP1aee<N6O6OPQPPPrW   r   c              3   N   K   | ]}                     |          pd d k    V   dS r   Nr   )r   r  r  s     rU   r   z(_get_models_analytics.<locals>.<genexpr>S9  sK       $ $ *q0$ $ $ $ $ $rW   rl  rm  r  r  rn  r  ro  r  r  c              3      K   | ]:u                     d           st          fddD                       6V  ;dS )r  c              3   N   K   | ]}                     |          pd d k    V   dS r  r   )r   r  r6  s     rU   r   z2_get_models_analytics.<locals>.<genexpr>.<genexpr>k9  sJ       : : sqQ.: : : : : :rW   r  N)r   r  )r   r6  rh   s    @rU   r   z(_get_models_analytics.<locals>.<genexpr>h9  s        122 '69 : : : :	$: : : 7 7 ' & rW   c                 `    |                      d          pd|                      d          pdz   S rr  r   rs  s    rU   r  z'_get_models_analytics.<locals>.<lambda>}9  s.    155005A!%%:P:P:UTUV rW   Trp  r  r  Nr  r  r  )r  rT   rl  rm  r  r  rn  r  rF  ro  r  r  r  r  a  
            SELECT COUNT(DISTINCT model) as distinct_models,
                   SUM(input_tokens) as total_input,
                   SUM(output_tokens) as total_output,
                   SUM(cache_read_tokens) as total_cache_read,
                   SUM(reasoning_tokens) as total_reasoning,
                   COALESCE(SUM(estimated_cost_usd), 0) as total_estimated_cost,
                   COALESCE(SUM(actual_cost_usd), 0) as total_actual_cost,
                   COUNT(*) as total_sessions,
                   SUM(COALESCE(api_call_count, 0)) as total_api_calls
            FROM sessions WHERE started_at > ? AND model IS NOT NULL AND model != ''
        )r  r  r  )ra  rC  r.  r8  r9  rh  r   r   rk  r  r   r  rN  r  r  r  r  r  r  r  r  r  r  rZ   r  r  r   )r{  r  rE  r  r  r:  rA  rows_by_modelr   
model_rowsprovider_rows	has_usagetotal_tokensrF  r  rT   r  r  r  r  
totals_curr  r  rh   s                         @@rU   _get_models_analyticsr  9  s    
&g	.	.Bfu-h  " Y# $ 54S\\^^444 #2v.. 	 	COO))6Y$'GG,>$?$?$E2 # 7 7 <1!$!9!9!>Q%(WW-@%A%A%FQ$'GG,>$?$?$D1"%''*:";";"@q GGJ//41 WW[116Q # 7 7*+GGFOO1r     . :< 	M 	MC$$SWWW%5%5%;R@@GGLLLL%''..00 .	( .	(JPP
PPPM=!!Q&&&q)% b bCf}}0B(C(C}  # $ $ $ $	$$ $ $ ! !I ! ! *0**Z*@*@*EA#''R\J]J]Jbab)cF:&-0N1K1K1PqRURYRYZhRiRiRnmn-o-oF>*$*JJ~$>$>$C!

SbHcHcHhgh#iL%zz*55:HRZ7a|h7N7N`aF344F###    )      $ J''''		VV 	 	
 	
 	

  "	 "	Cww1228bHWJDCCCCCC++XZPPP>*,*;+-+=.0.C*,*;-/-A(* D     MM#$ #N 3!$_!5%()<%=$'(:$;"%&6"7"=1
O -!,/ #N 3*-.F*G $     " X%% ' Y 
 j))++,, 
 
 	







s1   NR" <OR" 
O!R"  O!!B,R" "R8z/api/analytics/modelsc                 H   K   t          j        t          | |           d{V S )z?Return model analytics without blocking the serving event loop.N)rm   rd  r  r  s     rU   get_models_analyticsr  9  s0       "#8$HHHHHHHHHrW   r  )WinPtyBridgePtyUnavailableErrorc                       e Zd ZdZdS )r  z,Stub when win_pty_bridge cannot be imported.Nr  r  r  r  r  rW   rU   r  r  9  s        >>DrW   r  )	PtyBridger  c                       e Zd ZdZdS )r  z5Stub on platforms where pty_bridge can't be imported.Nr  r  rW   rU   r  r  9  s        GGDrW   s   \x1b\[RESIZE:(\d+);(\d+)\]g?)PtySessionRegistryRegistryFullrz   i  rc  )ttlmax_sessions
buffer_capread_timeoutwsr8   c                    K   t          j                    d fd}t          j         |                      }	 	 	                                   d{V }n# t          $ r Y n+w xY w|                    d          dk    rn|                    d          }|A|                    d          }t          |t                    r|                    d	          nd
}|st          
                    |          }|r~|                                t          |          k    rY                    t          |                    d                    t          |                    d                               >                    |           Tn# t"          $ r Y nw xY w|                                 	 | d{V  n# t           j        t(          f$ r Y nw xY wt          j        j                   d{V  dS # |                                 	 | d{V  n# t           j        t(          f$ r Y nw xY wt          j        j                   d{V  w xY w)u  Original 1:1 socket<->PTY pump: stream until disconnect, then close the
    bridge. Used when no ``?attach=`` token is supplied (keep-alive opt-in).

    Behavior is identical to the pre-keep-alive ``pty_ws`` body, including the
    #54028 half-open-socket protection (reader EOF → close the WS so the
    writer's ``ws.receive()`` unparks) and the #53227 ``to_thread`` offloads
    for the blocking ``bridge.close()``.
    rJ   Nc                  4  K   	 	                      d j        t                     d {V } | `	 	 t          j        j                   d {V  n# t          $ r Y nw xY w	                                  d {V  d S # t          $ r Y d S w xY w| st          j        d           d {V  	                     |            d {V  nn# t          $ ra Y 	 t          j        j                   d {V  n# t          $ r Y nw xY w	                                  d {V  d S # t          $ r Y d S w xY ww xY w3# 	 t          j        j                   d {V  n# t          $ r Y nw xY w	                                  d {V  w # t          $ r Y w w xY wxY w)NTr   )	rs   r,  _PTY_READ_CHUNK_TIMEOUTrm   rd  r   rZ   r
  
send_bytes)r  bridgerM  r  s    rU   pump_pty_to_wsz$_legacy_pump.<locals>.pump_pty_to_ws:  s     #	"22&+'>        =,'5555555555   hhjj            7  !-*********--..........    '5555555555   hhjj            -6'5555555555   hhjj           s   *D9 A 
AA"A> >
BBD9 -C	 D9 	
D4D9 C5 5
DDD" "
D0/D03D44D9 9F;EF
E(%F'E((F,FF
FFFFTr  websocket.disconnectrt  r  r  rW   r   r  colsr   rJ   N)rm   rI  ry   receiver  r   rv  r  r   
_RESIZE_REr  endr   resizerw  r  r  r9   r|   CancelledErrorrZ   rd  r   )	r  r  r  reader_taskr  r  r  r  rM  s	   ``      @rU   _legacy_pumpr  9  s      #%%D$ $ $ $ $ $ $ $L %nn&6&677K.	JJLL((((((     wwv"888'''""C{wwv.8s.C.CLdkk'*** $$S))E C003u{{1~~#6#6SQ=P=PQQQLL)	*     		&	2 	 	 	D	----------- 		&	2 	 	 	D	----------s   F  A F 
A)%F (A))D*F G? 
F!G?  F!!G? 9G GG?IHIH74I6H77#Iz^[A-Za-z0-9._-]{1,128}$>   r   r*  r   r   c                 >   t          t          j        dd          rdS t          t          j        dd          pd                                                                }|r|t
          vrdS | j        r| j        j        nd}|sd|pd S |t
          v rdS d| d	|pd S )
a_  Return a rejection reason for the client IP, or None when allowed.

    Reasons are short machine-parseable tokens logged on the rejection path
    so a "WS keeps closing" report can be diagnosed from agent.log without a
    repro. ``None`` means the peer IP passed this gate.

    See :func:`_ws_client_is_allowed` for the full policy rationale.
    r   FNr   r   zmissing_or_empty_peer bound=r
  zpeer_not_loopback peer= bound=r   rc   rk   r   r   _LOOPBACK_HOSTSr-  r   r  r   r.  s      rU   _ws_client_reasonr  W:  s     sy/511 t#)\266<"CCEEKKMMJ j77t$&I5")..2K B Bj.?CAAAo%%tL[LL9JsLLLrW   c                    t          t          j        dd          rdS t          t          j        dd          pd                                                                }|r|t
          vrdS | j        r| j        j        nd}|sdS |t
          v S )u  Check if the WebSocket client IP is acceptable.

    Loopback bind: only loopback clients allowed — the legacy
    ``?token=<_SESSION_TOKEN>`` path is the only auth we have, so we
    don't want LAN hosts guessing tokens.

    Explicit non-loopback bind (``--host 0.0.0.0``, ``--host ::``, or a
    specific address such as a Tailscale/LAN IP, always with
    ``--insecure``): allow any peer. The operator explicitly opted into
    non-loopback exposure, so the loopback-only peer restriction does not
    apply. DNS-rebinding is still blocked by the Host/Origin guard in
    :func:`_ws_host_origin_is_allowed`, which mirrors the HTTP layer and
    requires the Host header to match the bound interface — the same
    defence ``_is_accepted_host`` applies to non-loopback HTTP requests.

    Gated mode: any peer is allowed — uvicorn's ``proxy_headers=True``
    (enabled when the OAuth gate is active so cookies can pick up
    ``X-Forwarded-Proto``) rewrites ``ws.client.host`` to the
    X-Forwarded-For value, which is the real internet client IP. The
    OAuth gate + single-use ``?ticket=`` is the auth at that point; the
    Host/Origin guard in :func:`_ws_host_origin_is_allowed` is what
    blocks DNS-rebinding here, not the peer IP.
    r   FTr   r   r  r  s      rU   _ws_client_is_allowedr  r:  s    0 sy/511 t #)\266<"CCEEKKMMJ j77t$&I5")..2K  u/))rW   c                    t          t          j        dd          }|sdS | j                            dd          }t          ||          s
d|pd d| S | j                            dd          }|sdS t          j                            |          }|j	        d	vrdS |j
        sd
| d| S t          |j
        |          sd
| d| S dS )u  Return a Host/Origin rejection reason, or None when allowed.

    Mirrors :func:`_ws_host_origin_is_allowed` but yields a short
    machine-parseable token (``host_mismatch …`` / ``origin_mismatch …``)
    on rejection so the close path can log *why* the upgrade was refused.
    r   Nr   r   zhost_mismatch host=r
  r  re  >   r   httpszorigin_mismatch origin=)r   rc   rk   r   r   r   r&  r  r  schemer  )r  r   r   re  r  s        rU   _ws_host_origin_reasonr  :  s    L$77J t*..,,K[*55 ML[%7CLL
LLLZ^^Hb))F t\""6**F}--- t= EDDD
DDDV]J77 EDDD
DDD4rW   c                 $    t          |           du S )a  Apply the dashboard Host/Origin guard to WebSocket upgrades.

    FastAPI HTTP middleware does not run for WebSocket routes, so the
    DNS-rebinding Host check used for normal dashboard HTTP requests must be
    repeated here before accepting the upgrade.  Browsers also send an Origin
    header on WebSocket handshakes; when present, require it to target the
    same bound dashboard host.
    N)r  r  s    rU   _ws_host_origin_is_allowedr  :  s     ""%%--rW   c                 >    t          |           pt          |           S )zDFirst Host/Origin or peer-IP rejection reason, or None when allowed.)r  r  r  s    rU   _ws_request_reasonr  :  s    !"%%>):2)>)>>rW   c                 >    t          |           ot          |           S )zDReturn True when the WebSocket upgrade matches dashboard boundaries.)r  r  r  s    rU   _ws_request_is_allowedr  :  s    %b))G.CB.G.GGrW   c                      t          t          j        dd          rdS t          t          j        dd          pd                                                                } | r| t
          vrdS dS )uG   Short label for the active WS auth mode — logged on every connection.r   Fgatedr   r   insecureloopback)r   rc   rk   r   r   r  )r   s    rU   _ws_auth_moder  :  si    sy/511 w#)\266<"CCEEKKMMJ j77z:rW   c                    t          t          t          j        dd                    }|rddlm}m} ddlm}m	}m
} | j                            dd          }|rU	  ||           dS # |$ r?} ||j        d	| | j        r| j        j        nd| j        j        
           Y d}~dS d}~ww xY w| j                            dd          }	|	sdS 	  ||	           dS # |$ rI} ||j        t%          |          | j        r| j        j        nd| j        j        
           Y d}~dS d}~ww xY w| j                            dd          }
|
sdS t'          j        |
                                t,                                                    rdS dS )u  Validate WS-upgrade auth; return ``(reason, credential)``.

    ``reason`` is None when the credential is accepted, else a short
    machine-parseable token explaining the rejection (``no_credential``,
    ``token_mismatch``, ``ticket_invalid``, ``internal_invalid``).
    ``credential`` names which credential type was presented (``ticket``,
    ``internal``, ``token``, or ``none``) so the accepted path can log *how*
    a peer authed, not just that it did.

    Loopback / ``--insecure``: legacy ``?token=<_SESSION_TOKEN>`` query
    parameter, constant-time compared.

    Gated (public bind, no ``--insecure``): one of two credentials —

    * ``?ticket=<single-use>`` — a browser-minted, single-use, 30s-TTL ticket
      consumed against the dashboard-auth ticket store. This is what the SPA
      (and native clients) use.
    * ``?internal=<process-credential>`` — the process-lifetime internal
      credential, used only by WS clients the server spawns itself (the
      embedded-TUI PTY child attaching to ``/api/ws`` and ``/api/pub``). It
      is multi-use and never expires so the child can reconnect, and is never
      injected into the SPA — see ``dashboard_auth.ws_tickets`` for the
      threat model.

    The legacy ``?token=`` path is unconditionally rejected in gated mode
    (the SPA bundle isn't carrying the token any longer, and a leaked
    ``_SESSION_TOKEN`` must not grant WS access once the gate is engaged).

    Audit-logs the rejection so operators can debug "WS keeps closing"
    issues from the log.
    r   Fr   )
AuditEvent	audit_log)TicketInvalidconsume_internal_credentialconsume_ticketinternalr   )Nr  z
internal: )r!  ipr   N)internal_invalidr  ticket)no_credentialr  )Nr  )ticket_invalidr  r   )Nr   )token_mismatchr   )r   r   rc   rk   hermes_cli.dashboard_auth.auditr  r  $hermes_cli.dashboard_auth.ws_ticketsr  r  r  r   r   WS_TICKET_REJECTEDr-  r   r   r   r  r   r   r   r   )r  r   r  r  r  r  r  r  rN  r  r   s              rU   _ws_auth_reasonr  :  s8   @ OUCCDDM ). 	JIIIIIII	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 ?&&z266 	6
6++H555''  6 6 6	1---*,);		    6555556 $$Xr22 	+**
	.N6"""!> 	. 	. 	.I-3xx&(i7BINNRV[	    .-----	. O,,E '&&5<<>>>+@+@+B+BCC }$$s0   A) )B-.4B((B-C D+">D&&D+c                 0    t          |           d         du S )zETrue when the WS-upgrade credential is accepted. See _ws_auth_reason.r   N)r  r  s    rU   _ws_auth_okr  3;  s    2q!T))rW   resumesidecar_urlactive_session_filec                    ddl m}m} d}|pd                                }|r'|                                dk    rt          |          } ||dz  d          \  }}	t          j                                        }
	 dd	l	m
}  ||

           n,# t          $ r t                              dd           Y nw xY w|
                    dd           |
                    dd           |
                    dd           |
                    dd           d|
d<   |t          |          |
d<   | r^t!          ||nd          }	 t#          | |          \  }}|                                 n# |                                 w xY w|r|} | |
d<   |r||
d<   |r||
d<   |t'                      x}r||
d<   t)          |          |	rt          |	          nd|
fS )uT  Resolve the argv + cwd + env for the chat PTY.

    Default: whatever ``hermes --tui`` would run.  Tests monkeypatch this
    function to inject a tiny fake command (``cat``, ``sh -c 'printf …'``)
    so nothing has to build Node or the TUI bundle.

    Session resume is propagated via the ``HERMES_TUI_RESUME`` env var —
    matching what ``hermes_cli.main._launch_tui`` does for the CLI path.
    Appending ``--resume <id>`` to argv doesn't work because ``ui-tui`` does
    not parse its argv.

    ``HERMES_TUI_GATEWAY_URL`` is injected so the PTY child can attach to
    this process's in-memory ``tui_gateway`` instance instead of spawning
    its own Python gateway subprocess.

    `sidecar_url` (when set) is forwarded as ``HERMES_TUI_SIDECAR_URL`` so
    the spawned ``tui_gateway.entry`` can mirror dispatcher emits to the
    dashboard's ``/api/pub`` endpoint (see :func:`pub_ws`).

    `active_session_file` (when set) is forwarded as
    ``HERMES_TUI_ACTIVE_SESSION_FILE``. The TUI writes the current session id
    there whenever it creates/resumes/switches sessions, giving the dashboard a
    small cross-process breadcrumb for reconnecting after an unexpected browser
    WebSocket close.

    `profile` (when set) scopes the ENTIRE chat to that profile by pointing
    ``HERMES_HOME`` at the profile dir in the child env. Every spawned
    process (the TUI and the ``tui_gateway.entry`` it launches) resolves
    ``get_hermes_home()`` from that env var at its own import, so the child
    binds the profile's config, skills, memory, and state.db from the start
    — the same propagation ``hermes -p <name>`` performs. The in-process
    ``HERMES_TUI_GATEWAY_URL`` attach is SKIPPED for scoped chats: the
    dashboard's in-memory gateway runs under the dashboard's own profile,
    so a profile-scoped chat must spawn its own gateway subprocess.
    r   )r9  _make_tui_argvNr   r  zui-tuiF)tui_dev)apply_terminal_config_to_env)r  z9Failed to apply terminal config bridge for dashboard chatTr  NODE_ENV
productionHERMES_TUI_DISABLE_MOUSErf   HERMES_TUI_INLINE	COLORTERM	truecolorHERMES_TUI_DASHBOARDr2  HERMES_TUI_RESUMEHERMES_TUI_SIDECAR_URLHERMES_TUI_ACTIVE_SESSION_FILEHERMES_TUI_GATEWAY_URL)r>  r9  r  r   r   r	  rt   r  r  r  r  rZ   rP   r  rk  r  ra  r>  r   _build_gateway_ws_urlr  )r  r  r  r  r9  r  r  r	  argvr  r  r  
_resume_dblatest_resume_latest_pathgateway_ws_urls                   rU   _resolve_chat_argvr  ?;  sc   R =<<<<<<<"&KB%%''I 6Y__&&)33*955|h6FFFID#
*//

C_BBBBBB$$----- _ _ _

NY]
^^^^^_NN:|,,, NN-s333NN&,,, NN;,,,"%C --M 
*1$0IId
 

	*DVZ*X*X'M<J 	#"F#)  4(3$% D0C,- 2444> 	;,:C()::30s3xxxD#55s   <B &B87B8E* *F r   r   c                      t           j                            dd                                          } | r| S t	          t
          j        dd          }|sdS |t          v rdS |S )uo  Return the host the in-container WS client should dial.

    Resolution order:

    1. Explicit ``HERMES_DASHBOARD_WS_HOST`` env var — wins always. Operators
       running the dashboard behind a forward proxy can pin a routable host
       (e.g. ``127.0.0.1``, the container's internal IP, or a sidecar DNS
       name) and bypass auto-detection entirely.
    2. The configured bind host — if it's a wildcard (``0.0.0.0`` / ``::``),
       substitute ``127.0.0.1`` since both the dashboard and its TUI child
       run in the same container.
    3. Any other bind host (loopback or LAN IP) — preserved verbatim.
    HERMES_DASHBOARD_WS_HOSTr   r   Nr   )rt   r  r   r   r   rc   rk   _WILDCARD_HOSTS)explicitr   s     rU   _resolve_client_ws_hostr  ;  sh     z~~8"==CCEEH 39lD11D t{KrW   c                     t                      } t          t          j        dd          }| r|sdS d| v r|                     d          sd|  d| n|  d| }t          t          j        dd          r0dd	lm} t          j        	                    d
 |            i          }n&t          j        	                    dt          i          }d| d| S )a	  ws:// URL the PTY child should attach to for JSON-RPC gateway traffic.

    Loopback / ``--insecure``: ``?token=<_SESSION_TOKEN>``.

    Gated mode: the legacy token path is rejected by ``_ws_auth_ok``, so the
    server-spawned PTY child authenticates with the process-lifetime internal
    credential (``?internal=``). It must NOT use a single-use browser ticket:
    the child reads this URL once at startup and reuses it on every reconnect,
    and a 30s-TTL ticket can expire before a slow cold boot even dials.
    
bound_portNr   r   ]:r   Fr   internal_ws_credentialr  r   ws://z/api/ws?r  r   rc   rk   r   r  r  r&  r  r
  r   )r   rR  r  r  qss        rU   r  r  ;  s     #$$D39lD11D t t $;;ts33; 	DDt  sy/511 ?OOOOOO\##Z1G1G1I1I$JKK\##Wn$=>>'6''2'''rW   c                    K   | ||d}|||d<   t          t                    4 d{V  t          j        t          fi | d{V cddd          d{V  S # 1 d{V swxY w Y   dS )a)  Resolve chat argv without blocking the dashboard event loop.

    ``_resolve_chat_argv`` may run ``npm install`` / ``npm run build`` through
    ``_make_tui_argv``.  Keep that synchronous work off the WebSocket event
    loop so reverse proxies and existing dashboard connections can continue
    to exchange keepalives while the TUI launch command is prepared.  The
    async lock preserves the previous one-build-at-a-time behavior when
    multiple browser tabs connect at once without occupying worker threads
    while queued connections wait.
    r  r  r  Nr  )r   rc   rm   rd  r  )r  r  r  r  r  s        rU   _resolve_chat_argv_asyncr  ;  s:     " " F
 &(;$%"3'' 
 
 
 
 
 
 
 
&
 

 
 
 
 
 
 
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
s   A
A$'A$channelc                    t                      }t          t          j        dd          }|r|sdS d|v r|                    d          sd| d| n| d| }t          t          j        dd          r1dd	lm} t          j        	                     |            | d
          }n't          j        	                    t          | d          }d| d| S )a  ws:// URL the PTY child should publish events to, or None when unbound.

    Loopback / ``--insecure``: uses ``?token=<_SESSION_TOKEN>``.

    Gated mode: authenticates with the process-lifetime internal credential
    (``?internal=``), the same one ``_build_gateway_ws_url`` uses. The PTY
    child is a server-spawned process we trust; the credential is multi-use
    and never expires, so the child can reconnect ``/api/pub`` without a new
    URL. (This previously minted a single-use 30s ticket, which meant the
    child could not reconnect and could miss the window on a slow cold boot.)
    Connections authenticated this way are recorded under the
    ``server-internal`` identity in the audit log.
    r  Nr   r   r  r   Fr   r  )r  r  )r   r  r  z	/api/pub?r  )r  r   rR  r  r  r  s         rU   _build_sidecar_urlr!  <  s    #$$D39lD11D t t#&$;;ts7K7K;TXQaQa[_QaQaFsy/511 	S 	POOOOO\##//11gFF
 
 \##n$Q$QRR(6((B(((rW   c                 h  K   t          |           \  }}|4 d{V  t          |                    |d                    }ddd          d{V  n# 1 d{V swxY w Y   |D ]K}	 |                    |           d{V  # t          $ r  t
                              d|d           Y Hw xY wdS )z=Fan out one publisher frame to every subscriber on `channel`.Nr  z*broadcast send failed for subscriber on %sTr  )r   r  r   	send_textrZ   rP   r3  )rc   r  rj  rl   ro   r  r`  s          rU   _broadcast_eventr$  6<  sw     !1#!6!6NJ 5 5 5 5 5 5 5 5N&&w33445 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5  _ _	_--(((((((((( 	_ 	_ 	_ LLEwY]L^^^^^	__ _s#   $A
A A)B'B/.B/c                 t    | j                             dd          }t                              |          r|ndS )z?Return the channel id from the query string or None if invalid.r  r   N)r   r   _VALID_CHANNEL_REr  r  r  s     rU   _channel_or_close_coder(  E<  s6    o!!)R00G'--g66@77D@rW   c                     t          |           }|                    |          }||S t          j        dd          \  }}t	          j        |           t          |          }|||<   |S )zHReturn the per-channel file where a dashboard TUI writes its active sid.Nzhermes-pty-active-r  )r|  r  )r   r   r  r  rt   r   r   )rc   r  rX  r  fdr  r   s          rU    _active_session_file_for_channelr+  L<  sk    )#..Eyy!!H#+?PPPLBHRLLL>>DE'NKrW   c                    	 t          j        |                     d                    }n# t          t           j        f$ r Y d S w xY wt          |                    d          pd                                          }|pd S )Nr  r  r  r   )r*  r+  r4  r  r	  r  r   r   )r   r  r  s      rU   _read_active_session_filer-  Z<  s    z$..'.::;;T)*   tt TXXl++1r2288::Js   (+ AAc                 V    	 |                      d           d S # t          $ r Y d S w xY w)NTr  )r  r  r  s    rU   _forget_active_session_filer/  d<  sC    t$$$$$   s    
((c                     |                      dd          }t          |          dk    r| S |dd                             dd          dz   S )a  Clamp a WS close reason to the protocol's 123-byte UTF-8 limit.

    RFC 6455 caps the close-frame reason at 123 bytes; uvicorn raises if a
    longer string is passed. Our reasons embed an attacker-controlled origin,
    so truncate defensively rather than crash the close handler.
    r  r  {   Nx   ignorez...)r   r   r  )r  r  s     rU   _ws_close_reasonr4  k<  sP     kk'9--G
7||s4C4=22U::rW   zhermes> g      N@iP  r   _console_executorc                      t           at          5  t           ;t          j                            t
          d          a t          j        d            ddd           n# 1 swxY w Y   t           S )zALazily create the bounded console worker pool (once per process).Nzhermes-console)max_workersthread_name_prefixc                  H    t           ot                               dd          S )NFT)r?  cancel_futures)r5  shutdownr  rW   rU   r  z'_get_console_executor.<locals>.<lambda><  s&    - T)22d2SS rW   )r5  _console_executor_lock
concurrentfuturesThreadPoolExecutor_CONSOLE_EXECUTOR_MAX_WORKERSatexitregisterr  rW   rU   _get_console_executorrC  <  s      # 	 	 ($.$6$I$I ='7 %J % %! T T  	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 s   AAA"%A"c                  &    t                      rdndS )z@Choose local vs hosted command policy for the dashboard console.hostedr!  )r6  r  rW   rU   _dashboard_console_contextrF  <  s    799F88wFrW   c                 f    | j                             d          pd                                }|pd S )Nr  r   )r   r   r   )r  r  s     rU   _console_profile_from_wsrH  <  s2    ""9--3::<<G?drW   enginer]  	confirmedc                    t          |          5  |                     ||          cd d d            S # 1 swxY w Y   d S )NrJ  )ru  r8  )rI  r]  rJ  r  s       rU   _execute_console_linerM  <  s     
	 	  9 9~~di~889 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9s   488	send_lockc                    K   |4 d {V  |                      |           d {V  d d d           d {V  d S # 1 d {V swxY w Y   d S r  )	send_json)r  rN  rj  s      rU   _console_sendrQ  <  s      
  $ $ $ $ $ $ $ $ll7#########$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $s   :
AAr  
command_idc          
      (  K   |j         pd}|j        }|dk    rM|j        r"t          | |d|d|j        |d           d {V  t          | |d|d|t          d           d {V  d S |dk    rGt          | |d||j        pd	|d
           d {V  t          | |d|d|t          d           d {V  d S |dk    rQt          | |d|||j        pd| dt          d           d {V  t          | |d|d|t          d           d {V  d S |dk    r>t          | |d|d           d {V  t          | |d|d|t          d           d {V  d S |dk    rt          | |d|d|dd           d {V  d S t          | |d|d| |d
           d {V  d S )Nr   rs  rh  r  )r  r  r	  r  r  completer  r  r)  r  rD   r  zCommand failed.r  r  r   r  r)  zRun `z`?)r  r  r  r   rD   clear)r  r  exitzUnknown console result status: )r  r)  rh  rQ  _CONSOLE_PROMPTconfirmation_message)r  rN  r  rR  r  r)  s         rU   _console_send_resultr[  <  s      n"G]F~~= 	$$&"M& 
 
 
 
 
 
 
 
 
 " ") 

 

 
	
 
	
 
	
 
	
 
	
 
	
 
	
 	 !==,="	 	
 	
 		
 		
 		
 		
 		
 		
 		
 " !") 

 

 
	
 
	
 
	
 
	
 
	
 
	
 
	
 	###* "!6M:M':M:M:M) 

 

 
	
 
	
 
	
 
	
 
	
 
	
 
	
 " ,") 

 

 
	
 
	
 
	
 
	
 
	
 
	
 
	
 	B	G:+N+NOOOOOOOOO" !") 

 

 
	
 
	
 
	
 
	
 
	
 
	
 
	
 	"  " 

 

 
	
 
	
 
	
 
	
 
	
 
	
 
	
 	

AAA		
 	
	 	 	 	 	 	 	 	 	 	 	rW   r  c                 j   |                      d          }||                      d          }|dS t          |t                    r(	 |                    d          }n# t          $ r Y dS w xY w	 t          j        |          }n# t
          j        $ r Y dS w xY wt          |t                    sdS |d fS )Nr  rt  rO  r  )Nz"Console frames must be UTF-8 JSON.)Nz$Console frames must be JSON objects.)	r   rv  rt  r  r  r*  r+  r	  r  )r  r  rj  s      rU   _console_json_payloadr]  @=  s    !ggfooC
{ggg
{z#u >	>**W%%CC! 	> 	> 	>===	><*S// < < <;;;<gt$$ <;;D=s$   A 
A+*A+/B BBz/api/consolec           	         K    j         r j         j        nd}t          s:t                              d|                                dd           d {V  d S t                     \  }}t                      }|Mt                              d||||                                dt          d|                      d {V  d S t                     }|Ht                              d	||                                d
t          |                     d {V  d S t                     }|Gt                              d|                                dt          |                     d {V  d S                                   d {V  t                     t                      }t          j                    	 ddlm}  |t&          |          r'                                dk    rt+                     n# t,          $ rt}	t/           dt1          |	j                  dd           d {V                       dt          t1          |	j                                       d {V  Y d }	~	d S d }	~	wt4          $ r_}	t                              d           t/           dd|	 dd           d {V                       d           d {V  Y d }	~	d S d }	~	ww xY wt                              d||||pd           t/           d|pdt8          d           d {V  d d ddt0          dt:          dt<          dd f fd d!d"dt0          dt:          dd ffd#}
	 	 	                                   d {V }n# t@          $ r Y nw xY w|!                    d%          }|d&k    rntE          |          \  }}|r!t/           d|t8          d           d {V  |t1          |!                    d%          pd          #                                                                }|d'k    r t/           d(t8          d)           d {V  |d*k    rrR$                                s>d+z  %                                 d d t/           d,d-t8          d.           d {V  nEr#d t/           d,d-t8          d.           d {V  n t/           d,d/t8          d.           d {V  r6$                                s"t/           dd0t8          d           d {V  |d1k    rt1          |!                    d2          ppd          #                                }s"t/           dd3t8          d           d {V  ,|k    r"t/           dd4t8          d           d {V  Td  |
|d$"           d {V  k|d5v rt1          |!                    d          p|!                    d2          pd          #                                }|s"t/           d,d6t8          d.           d {V  މr"t/           dd7t8          d           d {V   |
|           d {V  t/           dd8|pd t8          d           d {V  ;n# tL          $ r Y nw xY wrP$                                s>%                                 	  d {V  d S # t          j'        t4          f$ r Y d S w xY wd S d S # rN$                                s;%                                 	  d {V  w # t          j'        t4          f$ r Y w w xY ww w xY w)9Nr
  z/console refused: embedded chat disabled peer=%s4  embedded chat disabledr2  r!  z7console auth rejected reason=%s mode=%s cred=%s peer=%s1  auth: zconsole refused: %s peer=%s3  zconsole refused: %s8  r   )HermesConsoleEngine)output_limitrU  r  r  r   )r  r   rD   0  zconsole failed to initializezConsole unavailable:   r2  z>console accepted peer=%s mode=%s cred=%s context=%s profile=%sr|  )r  rU  r  rD   r]  rJ  rR  rJ   c                  K   	 t          j                    }t          j        |                    t	                      t          j        t          | |
                    t                     d {V }|k    r	 |k    rd d S d S |j	        dk    r|j
        nd 	t          ||           d {V  |j	        dk    r                    d           d {V  n# t           j        $ r  t           j        $ rI |k    r@d 	t          d|d	| d
           d {V  t          d|d| t           d           d {V  Y nt"          $ r}|k    rsd 	t$                              d           t          d|t)          |          p|j        j        | d
           d {V  t          d|d| t           d           d {V  Y d }~nd }~ww xY w|k    rd d S d S # |k    rd w xY w)N)rJ  r  r   r)  )rR  rX  r
  rj  r  z9Command timed out. Hermes Console returned to the prompt.rV  rT  r!  rU  zconsole command failed)rm   rI  rJ  rs   rC  rn  ro  rM   _CONSOLE_COMMAND_TIMEOUT_SECONDSr)  r  r[  r   r  rL  rQ  rY  rZ   rP   r"  r  	__class__r  )r]  rJ  rR  rM  r  rN  active_taskcommand_generationrI  pending_confirmationr  rN  r  s         rU   run_commandzconsole_ws.<locals>.run_command=  sr     T	#+--D"+$$)++%-"+ '  	 	 9        FF /// ///" 0/ #)-3E"E"E4 ! '%	          }&&hhDh)))))))))E % 	 	 	# 	 	 	///'+$# '(W#'          $ *("+#'"1 
 
 
 
 
 
 
 
 
  	 	 	///'+$7888# '(#&s88#Es}/E#'	 	 	 	 	 	 	 	 	 	 $ *(")#'"1 
 
 
 
 
 
 
 
 
	P ///" 0/z///"""""sE   A0C 5G AG A$GG 	GA9G
G 
GG 
G(FrL  c                Z   K   dz  }t          j         | ||                    d S )Nr   )rJ  rR  )rm   ry   )r]  rJ  rR  rn  ro  rq  s      rU   start_commandz!console_ws.<locals>.start_command>  sA      a'
)K	jIII
 
rW   Tr  r  pingpong)r  rD   r|   r   rT  rF	  )r  r)  rD   idlez%A console command is already running.confirmr  z'No command is waiting for confirmation.z0Confirmation does not match the pending command.>   inputr  rs  zAConfirm or cancel the pending command before running another one.zUnsupported console frame: )(r-  r    _DASHBOARD_EMBEDDED_CHAT_ENABLEDrP   rQ   r   r  r  r3  r4  r  r  acceptrH  rF  rm   rn   hermes_cli.console_enginerf  _CONSOLE_OUTPUT_LIMITr   r	  r5   rQ  r  r   rZ   r"  rY  r   rw  r  r  r   r]  r   doner|   r9   r  )r  peerauth_reasoncredr  host_origin_reasonclient_reasonrU  rf  rN  rs  r  msg_typerj  r  
frame_typer  r]  rn  ro  rI  rp  r  rq  rN  s   `                 @@@@@@@rU   
console_wsr  T=  s     Y/29>>CD+ 		CTJJJhhD)AhBBBBBBBBB'++K??DEtT	
 	
 	
 hhD)9:P;:P:P)Q)QhRRRRRRRRR/33%24FMMMhhD)9:L)M)MhNNNNNNNNN%b))M *M:::hhD)9-)H)HhIIIIIIIII
))++&r**G(**GI!AAAAAA$$.
 
 
  	*w}})33 )))   sz?? 
 
 	
 	
 	
 	
 	
 	
 	
 hhD)9#cj//)J)JhKKKKKKKKK   56668388 
 
 	
 	
 	
 	
 	
 	
 	
 hhDh!!!!!!!!! 	IIH9   
+)%		
 	
	 	 	 	 	 	 	 	 	 (,K*.V# V#4 V#S V#T V# V# V# V# V# V# V# V# V# V# V# V#p =B 
 
 
# 
T 
d 
 
 
 
 
 
 
 
bX	JJLL((((((   wwvH1112377NGU 
# '#("1          W[[006B77==??EEGGJV### &"1          X%% "{'7'7'9'9 "&!+&&&((("&K+/('!$.&1&5           * +/('!$.&1&5           (!$.&,&5           
;#3#3#5#5 
# '#J"1          Y&&gkk)44R8LRPRSSYY[[+ 
'!$+'P&5          222'!$+'Y&5          '+$#mGt<<<<<<<<<<1117;;v..N'++i2H2HNBOOUUWW 
'!$.&*&5          ' '!$+!7 '6          #mD)))))))))#PZ=N3PP-         aX	r      	{//11 	   !!!!!!!!!!*I6   		 	 	 	; 	{//11 	   !!!!!!!!!*I6   		 	s   AH 
K3A)JK3AK..K3?Z< N Z< 
N*&Z< )N**LZ< ;\" <
[	\" [		\" 7\ \\"+]5]]5]0-]5/]00]5z/api/ptyc                   K   | j         r| j         j        nd}t          s:t                              d|           |                     dd           d {V  d S t          |           \  }}t                      }|Mt                              d||||           |                     dt          d|                      d {V  d S t          |           }|Ht                              d	||           |                     d
t          |                     d {V  d S t          |           }|Gt                              d|           |                     dt          |                     d {V  d S |                                  d {V  t                              d|||           t          s9|                     d           d {V  |                     d           d {V  d S | j                            d          pd }|}| j                            d          pd }	t#          |           }
|
rt%          |
          nd }| j                            d          pd                                                                dv }d }|
r:t+          | j        |
          }|rd }t/          |           n|st1          |          }|||	d}|t3          |          |d<   	 t5          d*i | d {V \  n# t6          $ rK}|                     d|j         d           d {V  |                     d           d {V  Y d }~d S d }~wt:          $ rF}|                     d| d           d {V  |                     d           d {V  Y d }~d S d }~ww xY w| j                            d          pd }|}|rr                    d          p|}||s|	r| d|	pd d|pd }fd}|	  |            }n# t<          $ rF}|                     d| d           d {V  |                     d           d {V  Y d }~d S d }~wt>          t@          f$ rF}|                     d| d           d {V  |                     d           d {V  Y d }~d S d }~ww xY wtC          | |           d {V  d S 	 tD          #                    ||           d {V \  }}n# t<          $ rF}|                     d| d           d {V  |                     d           d {V  Y d }~d S d }~wt>          t@          tH          f$ rF}|                     d| d           d {V  |                     d           d {V  Y d }~d S d }~ww xY w|%                    |            d {V  	 	 	 | &                                 d {V }n# tN          $ r Y n5w xY w|                    d!          d"k    rn|                    d#          }|A|                    d$          }tQ          |t2                    r|)                    d%          nd&}|stT          +                    |          }|r|,                                t[          |          k    r^|j.        /                    ta          |1                    d'                    ta          |1                    d(                    )           C|j.        2                    |           ^n# tf          $ r Y nw xY wtD          4                    ||            d S # tD          4                    ||            w xY w)+Nr
  z+pty refused: embedded chat disabled peer=%sr_  r`  ra  z3pty auth rejected reason=%s mode=%s cred=%s peer=%srb  rc  zpty refused: %s peer=%srd  zpty refused: %sre  z$pty accepted peer=%s mode=%s cred=%su   
[31mChat unavailable: the embedded terminal requires a POSIX PTY, which native Windows Python doesn't provide.[0m
[33mInstall Hermes inside WSL2 to use the dashboard's /chat tab — the rest of the dashboard works here.[0m
ri  rj  r  r  freshr   >   rf   rG  rH  rI  r  r  z
[31mChat unavailable: z[0m
attachr  r  c                  2    t          j                   S )N)r  r  )r  spawn)r	  r  r  s   rU   _spawnzpty_ws.<locals>._spawn ?  s    t#6666rW   z
[31mChat failed to start: )r  Tr  r  rt  r  r  rW   r   r  r  r  )5r-  r   ry  rP   rQ   r   r  r  r3  r4  r  r  rz  _PTY_BRIDGE_AVAILABLEr#  r   r   r(  r!  r   r   r+  rc   r/  r-  r  r  r5   r   
SystemExitr  r  r  r  r{   attach_or_spawnr  r  r  r  rv  r   r  r  r  r   r  r  rw  r  r  r9   detach)r  r~  r  r  r  r  r  
raw_resumer  r  r  r  r  r  resolve_kwargsrN  attach_tokenregistry_resumer  r  r   _createdr  r  r  r  r	  r  r  s                             @@@rU   pty_wsr  >  s	     Y/29>>CD+ 		?FFFhhD)AhBBBBBBBBB (++K??DAtT	
 	
 	
 hhD)9:P;:P:P)Q)QhRRRRRRRRR/33%.0BDIIIhhD)9:L)M)MhNNNNNNNNN%b))M &666hhD)9-)H)HhIIIIIIIII
))++II4dD$GGG ! llG
 
 	
 	
 	
 	
 	
 	
 	
 hhDh!!!!!!!!! $$X..6$JFo!!),,4G$R((G18B$W---dK?&&w//52<<>>DDFF K K +/ D>rvwOO 	DF'(;<<<< 	D./BCCF " N
 &034G0H0H,-7II.IIIIIIIIc33   llSCJSSSTTTTTTTTThhDh!!!!!!!!!   llLCLLLMMMMMMMMMhhDh!!!!!!!!!	 ?&&x008DL O Ec E''"566D*_&RR'-RRR?;PbRR7 7 7 7 7 7 7 		VXXFF" 	 	 	,,PPPPQQQQQQQQQ(((%%%%%%%%%FFFFF!7+ 	 	 	,,TCTTTUUUUUUUUU(((%%%%%%%%%FFFFF	 2v&&&&&&&&&".">"> #? #
 #
 
 
 
 
 
 
    llLCLLLMMMMMMMMMhhDh!!!!!!!!!w5   llLCLLLMMMMMMMMMhhDh!!!!!!!!!
 ..

.	&JJLL((((((     wwv"888'''""C{wwv.8s.C.CLdkk'***  $$S))E C00%%3u{{1~~+>+>SUVEXEX%YYYN  %%%-	&.    
 	L"-----L"----s   *L 
N$A MN$;NN$>
P	 	
R.;QR.(;R))R.
%S0 0
V:;T;;V;VV:\ <W \ 
W%!\ $W%%D4\ ] 
\'$] &\''] ]$z/api/wsc                 6  K   t           s|                     d           d {V  d S t          |           s|                     d           d {V  d S t          |           s|                     d           d {V  d S ddlm}  ||            d {V  d S )Nrd  rj  rb  r   )	handle_ws)ry  r   r  r  tui_gateway.wsr  )r  r  s     rU   
gateway_wsr  s?  s      + hhDh!!!!!!!!!r?? hhDh!!!!!!!!!!"%% hhDh!!!!!!!!!((((((
)B--rW   z/api/pubc                 &  K   t           s|                     d           d {V  d S t          |           s|                     d           d {V  d S t          |           s|                     d           d {V  d S t	          |           }|s|                     d           d {V  d S |                                  d {V  	 	 t          | j        ||                                  d {V            d {V  5# t          $ r Y d S w xY wNrd  rj  rb  rh  )
ry  r   r  r  r(  rz  r$  rc   receive_textr9   r'  s     rU   pub_wsr  ?  s     + hhDh!!!!!!!!!r?? hhDh!!!!!!!!!!"%% hhDh!!!!!!!!!$R((G hhDh!!!!!!!!!
))++	M"267"//:K:K4K4K4K4K4K4KLLLLLLLLL	M   s   6D 
DDz/api/eventsc                   K   t           s|                     d           d {V  d S t          |           s|                     d           d {V  d S t          |           s|                     d           d {V  d S t	          |           }|s|                     d           d {V  d S |                                  d {V  t          | j                  \  }}|4 d {V  |                    |t                                
                    |            d d d           d {V  n# 1 d {V swxY w Y   	 	 |                                  d {V  # t          $ r Y nw xY w	 |4 d {V  |                    |          }|-|                    |            |s|                    |d            d d d           d {V  d S # 1 d {V swxY w Y   d S # |4 d {V  |                    |          }|-|                    |            |s|                    |d            d d d           d {V  w # 1 d {V swxY w Y   w xY wr  )ry  r   r  r  r(  rz  r   rc   rk  r~   r  r  r9   r   rQ  r  )r  r  rl   ro   r  s        rU   	events_wsr  ?  si     + hhDh!!!!!!!!!r?? hhDh!!!!!!!!!!"%% hhDh!!!!!!!!!$R((G hhDh!!!!!!!!!
))++!1"&!9!9NJ : : : : : : : :!!'3551155b999: : : : : : : : : : : : : : : : : : : : : : : : : : :6	$ //#########		$
     	6 	6 	6 	6 	6 	6 	6 	6!%%g..DR    6"&&w555	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6: 	6 	6 	6 	6 	6 	6 	6 	6!%%g..DR    6"&&w555	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6sn   *6D22
D<?D<E   
E-*G$ ,E--G$ 9AG
GG$	I-AI2I
IIIIc                 $    ddl m}  ||           S )uA  Normalise an X-Forwarded-Prefix header value.

    Thin re-export of :func:`hermes_cli.dashboard_auth.prefix.normalise_prefix`
    — the single source of truth lives in the dashboard_auth package so
    the gate middleware, the OAuth routes, the cookie helpers, and the
    SPA mount all agree on validation rules.
    r   )normalise_prefix) hermes_cli.dashboard_auth.prefixr  )r  r  s     rU   _normalise_prefixr  ?  s(     BAAAAAC   rW   c                     	 t                      } t          | ddd          rt          t                    sdS t	          fdt
          D                       rdS t                      D ]\}|                    d          k    r|                    d          pi }|                    d	          pi }|                    d
          pi }t          |t                    r|                    dd          nd}t          |t                    r|                    dd          nd}|                    d          pi }|                    d          pt          d         }|                    d          pt          d         }	dt          dt          fd}
d |
|           d |
|           d |
|           d |
|	           d	c S dS # t          $ r  t                              dd           Y dS w xY w)uV  Critical-CSS shim for the active user theme.

    Returns a ``<style>`` block with the ``:root`` CSS variables that
    ``ThemeProvider.applyTheme()`` installs once the
    ``/api/dashboard/themes`` round-trip completes.  The goal is to
    eliminate the green flash where the first paint shows the bundle's
    default Hermes Teal canvas before the SPA flips the configured user
    theme into place.

    Built-in themes return an empty string — their full definitions live
    in ``web/src/themes/presets.ts`` and are applied by the bundle
    before paint, so no shim is needed for them.
    r^  themer1  rE  r   c              3   0   K   | ]}|d          k    V  dS r   r  )r   brm  s     rU   r   z5_render_active_theme_bootstrap_css.<locals>.<genexpr>?  s,      FFqqyF"FFFFFFrW   rR   paletter  	midgroundr
  z#0a0a0az#e5e5e5
typographyfontSansbaseSizer>  rJ   c                 H    t          |                               dd          S )Nz</z<\/)r  r  )r>  s    rU   _escz0_render_active_theme_bootstrap_css.<locals>._esc
@  s    1vv~~dF333rW   z;<style id="hermes-theme-bootstrap">:root{--background-base:z;--midground-base:z;--theme-font-sans:z;--theme-base-size:z;}html,body{background-color:var(--background-base);color:var(--midground-base);font-family:var(--theme-font-sans);font-size:var(--theme-base-size);}</style>ztheme bootstrap render failedTr  )r   r   rv  r  r  _BUILTIN_DASHBOARD_THEMES_discover_user_themesr   r  _THEME_DEFAULT_TYPOGRAPHYrZ   rP   r  )r{  r  r  bgmgbg_hexmg_hextypo	font_sans	base_sizer  rm  s              @rU   "_render_active_theme_bootstrap_cssr  ?  sR   6gyIII 	Z44 	2FFFF,EFFFFF 	2*,, *	 *	Eyy  F**ii	**0bG\**0bB[))/RB1;B1E1ETRVVE9---9F1;B1E1ETRVVE9---9F99\**0bD,,U0I*0UI,,U0I*0UI4 4 4 4 4 4 %)T&\\  %)DLL  &*T)__	 
 &*T)__     r   

2T
BBBrrs   8G  G E+G &G76G7applicationc                 ,   t           j                            d          dk    }|st                                          s0|rdnd|                     d          dt
          ffd            }dS t          d	z  ddt
          ffd|                     d          dt
          dt          fd            }|                     dt          t          dz            d           |                     d          dt
          dt          ffd            }dS )a  Mount the built SPA. Falls back to index.html for client-side routing.

    The session token is injected into index.html via a ``<script>`` tag so
    the SPA can authenticate against protected API endpoints without a
    separate (unauthenticated) token-dispensing endpoint.

    When served behind a path-prefix reverse proxy (e.g.
    ``mission-control.tilos.com/hermes/*`` -> local Caddy -> :9119), the
    proxy injects ``X-Forwarded-Prefix: /hermes`` on every request. We
    rewrite the served ``index.html`` so absolute asset URLs (``/assets/...``)
    and the SPA's runtime ``__HERMES_BASE_PATH__`` honour that prefix
    without rebuilding the bundle.
    HERMES_SERVE_HEADLESSrf   u_   Headless backend (hermes serve): web UI disabled — use `hermes dashboard` for the browser UI.z0Frontend not built. Run: cd web && npm run buildz/{full_path:path}	full_pathc                 .   K   t          did          S )Nr  r   rW  )r=   )r  _msgs    rU   no_frontendzmount_spa.<locals>.no_frontendG@  s      $SAAAArW   Nz
index.htmlr   r|  c           	                              d          }t          rdnd}t          t          t          j        dd                    }|rdnd}|rd| d|  d	| d
}ndt           d| d|  d	| d
	}| r|                    dd|  d          }|                    dd|  d          }|                    dd|  d          }|                    dd|  d          }|                    dd|  d          }|                    dd|  d          }t                      }|r|                    d| dd          }|                    d| dd          }t          |ddi          S )u#  Return index.html with the session token + base-path injected.

        ``prefix`` is the normalised ``X-Forwarded-Prefix`` (e.g. ``/hermes``)
        or empty string when served at root.

        When the OAuth auth gate is active (``app.state.auth_required``),
        the legacy ``_SESSION_TOKEN`` is NOT injected — the SPA reads
        identity from ``/api/auth/me`` over cookie auth instead.  The
        ``__HERMES_AUTH_REQUIRED__`` flag lets the SPA pick the right
        auth scheme for /api/pty and /api/ws (ticket vs token).
        r  r  rI  rK  r   Fz2<script>window.__HERMES_DASHBOARD_EMBEDDED_CHAT__=z;window.__HERMES_BASE_PATH__="z"";window.__HERMES_AUTH_REQUIRED__=z
;</script>z)<script>window.__HERMES_SESSION_TOKEN__="z,";window.__HERMES_DASHBOARD_EMBEDDED_CHAT__=zhref="/assets/zhref="/assets/zsrc="/assets/zsrc="zhref="/favicon.ico"z/favicon.ico"zhref="/fonts//fonts/zhref="/ds-assets//ds-assets/zsrc="/ds-assets/z</head>r   Cache-Control#no-store, no-cache, must-revalidater*  )
r4  ry  r   r   rc   rk   r   r  r  r<   )r|  r  chat_jsr  gated_jsbootstrap_scripttheme_bootstrap_index_paths          rU   _serve_indexzmount_spa.<locals>._serve_indexN@  s    $$g$66<I&&'WSY??@@"/66 	=D 06  4<   N  =D 06  4<     	Q << 02K62K2K2KLLD<<1I1I1I1IJJD<< 57U7U7U7UVVD<<1I&1I1I1IJJD<< 35Qf5Q5Q5QRRD<< 24OF4O4O4OPPD =>> 	K<<	o+F+F+FJJD||I*:'C'C'CQGG$&KL
 
 
 	
rW   z/assets/{filename}.cssr  r   c                 N  K   t           dz  |  dz  }|                                r>|                                                    t                                                     st	          ddid          S t          |j                            d                    }|                    d	          }|r_d
D ]\}|	                    d| d| |           }|	                    d| d| |           }|	                    d| d| |           }]t          |d          S )Nassetsr  r  r*  r   rW  x-forwarded-prefixr  r  )r  z/fonts-terminal/r  r  zurl(zurl("zurl('text/css)r   r  )WEB_DISTr  r  is_relative_tor=   r  r   r   r4  r  r>   )r  r   css_pathr|  r  	asset_dirs         rU   	serve_csszmount_spa.<locals>.serve_css@  sa     h&H):):)::!! 	I)9)9););)J)J*
 *
 	I  + 6CHHHH"7?#6#67K#L#LMM  ' 22 	TW T T	kk"4"4"46PV6PY6P6PQQkk"69"6"68T8T8T8TUUkk"5)"5"57Rv7Ry7R7RSS
;;;;rW   z/assetsr  )r  r  c                   K   t          |j                            d                    }| dk    s|                     d          rt	          dd|  id          S t
          | z  }| ru|                                                    t
                                                    r7|                                r#|	                                rt          |          S  |          S )Nr  r  zapi/r   zNo such API endpoint: /r   rW  )r  r   r   r   r=   r  r  r  rA  r  r;   )r  r   r|  r7  r  s       rU   	serve_spazmount_spa.<locals>.serve_spa@  s      "7?#6#67K#L#LMM !5!5f!=!=@Y@@A    y(	 	+!!##2283C3C3E3EFF	+   ""	+ !!##		+  	***|F###rW   r  )	rt   r  r   r  rA  r  r6   mountr?   )r  	_headlessr  r  r  r  r  r  s        @@@rU   	mount_spar  -@  s   " 
6773>I ))  D 5 5 D	 	 
,	-	-	B 	B 	B 	B 	B 	B 
.	-	B\)K7
 7
S 7
 7
 7
 7
 7
 7
@ __-..<# < < < < /.< ix(7J!K!K!KRZ[[[__())$3 $ $ $ $ $ $ *)$ $ $rW   zHermes Tealu/   Classic dark teal — the canonical Hermes look)rR   r?  r  zdefault-largezHermes Teal (Large)z1Hermes Teal with bigger fonts and roomier spacingz	nous-bluez	Nous Blueu6   Light mode — vivid Nous-blue accents on cream canvasr5  Midnightz"Deep blue-violet with cool accentsr6  Emberu'   Warm crimson and bronze — forge vibesr3  Monou'   Clean grayscale — minimal and focusedr7  	Cyberpunku'   Neon green on black — matrix terminalr8  u   Roséu-   Soft pink and warm ivory — easy on the eyes      ?default_hexdefault_alphac                    | ||dS t          | t                    r| |dS t          | t                    r|                     d|          }|                     d|          }t          |t                    sdS 	 t	          |          }n# t
          t          f$ r |}Y nw xY w|t          dt          d|                    dS dS )zNormalise a theme layer spec from YAML into `{hex, alpha}` form.

    Accepts shorthand (a bare hex string) or full dict form.  Returns
    ``None`` on garbage input so the caller can fall back to a built-in
    default rather than blowing up.
    Nr
  alphar
  r  r3  r  )	rv  r  r  r   rx  rl  r  rN  r  )rr  r  r  hex_val	alpha_valalpha_fs         rU   _parse_theme_layerr  @  s     }"];;;% 6}555% 	F))E;//IIg}55	'3'' 	4	$I&&GG:& 	$ 	$ 	$#GGG	$S#c72C2C)D)DEEE4s   ;B B! B!zQsystem-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serifzDui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace15pxz1.55r  )r  fontMonor  
lineHeightletterSpacingr  z0.5remcomfortable)radiusdensity_THEME_DEFAULT_LAYOUT>   cardringrx  mutedaccentborderpopoverprimaryr  r3  	secondarydestructivecardForegroundmutedForegroundaccentForegroundpopoverForegroundprimaryForegroundsecondaryForegrounddestructiveForeground>   r  herologocrestrZ  sidebar>	   rb  r  pager  footerrZ  r  backdropprogress>   tiledcockpitstandardi   c                     t          | t                    sdS |                     d          }t          |t                    r|                                sdS t          |                     d          t                    r|                     di           ni  t          |                     d          t                    r|                     di           ni d)dt          dt          dt
          d	t          t          t          f         f fd
} |ddd           |ddd           |ddd                               d          p|                     d          pddd}                     d|                     d                    }	 |t          |          nd|d<   n# t          t          f$ r d|d<   Y nw xY wt          |                     d          t                    r|                     di           ni }t          t                    }dD ]E}|                    |          }t          |t                    r|                                r|||<   Ft          |                     d          t                    r|                     di           ni }	t          t                    }
|	                    d          }t          |t                    r|                                r||
d<   |	                    d          }t          |t                    r	|dv r||
d<   |                     di           }i }t          |t                    rQ|                                D ]<\  }}|t          v r.t          |t                    r|                                r|||<   =i }t          |                     d          t                    r|                     di           ni }t          D ]E}|                    |          }t          |t                    r|                                r|||<   F|                    d          }t          |t                    ri }|                                D ]\  }}t          |t                    rj|                    dd                               d!d                                           r.t          |t                    r|                                r|||<   |r||d<   |                     d"          }d}t          |t                    r#|                                r|dt"                   }|                     d#i           }i }t          |t                    r|                                D ]\  }}|t$          vst          |t                    s$i }|                                D ]\  }}t          |t                    r|                    dd                               d!d                                           rUt          |t          t&          t
          f          r3t          |                                          rt          |          ||<   |r|||<   |                     d$          }t          |t                    r|t(          v r|nd%}||                     d&          p||                     d'd           |||
|d(}|r||d<   |r||d<   |||d"<   |r||d#<   |S )*a  Normalise a user theme YAML into the wire format `ThemeProvider`
    expects.  Returns ``None`` if the theme is unusable.

    Accepts both the full schema (palette/typography/layout) and a loose
    form with bare hex strings, so hand-written YAMLs stay friendly.
    NrR   r  colorsr  r  r  r  rJ   c                                          |                      |                     }t          |||          }||n||dS )Nr  )r   r  )r  r  r  rp  r  
colors_srcpalette_srcs        rU   _layerz+_normalise_theme_definition.<locals>._layer#A  sJ    sJNN3$7$788#D+}EE+vv}1]1]]rW   r  z#041c1cr  z#ffe6cb
foregroundz#ffffffr3  warmGlowzrgba(255, 189, 56, 0.35))r  r  r  r  noiseOpacityr  r  )r  r  fontDisplayfontUrlr  r  r  layoutr  r  >   compactspaciousr  colorOverridesr  rA  r  r   r  	customCSScomponentStyleslayoutVariantr  r?  r  )rR   r?  r  r  r  r  r  r  )rv  r  r   r  r   rx  r   r   rl  r  r  r  r  _THEME_OVERRIDE_KEYS_THEME_NAMED_ASSET_KEYSr  isalnum_THEME_CUSTOM_CSS_MAX_THEME_COMPONENT_BUCKETSrw  _THEME_LAYOUT_VARIANTS)!r  rR   r  r  	raw_noisetypo_srcr  r  val
layout_srcr  r  r  overrides_srccolor_overrides
assets_out
assets_srccustom_assets_srccustom_assetscustom_css_val
custom_csscomponent_styles_srccomponent_stylesbucketpropscleanproprr  layout_variant_srclayout_variantr  r  r  s!                                  @@rU   _normalise_theme_definitionr9  A  sf    dD!! t88FDdC   

 t .88K8KT-R-RZ$((9b)))XZK+5dhhx6H6H$+O+OW(B'''UWJ^ ^C ^c ^% ^$sTWx. ^ ^ ^ ^ ^ ^ ^ f\9c::VKC88f\9c::OOJ//e488J3G3GeKe G 0H0HIII&6?6K%	"2"2"2QTz" & & &"%& .88N8NPT-U-U]txxb)))[]H/00Jl " "ll3c3 	"CIIKK 	"!JsO ,6dhhx6H6H$+O+OW(B'''UWJ'((F^^H%%F&# "6<<>> "!xnnY''G'3 $G/U$U$U#y HH-r22M&(O-&& +%++-- 	+ 	+HC***z#s/C/C*		*'*$ "$J+5dhhx6H6H$+O+OW(B'''UWJ& " "nnS!!c3 	"CIIKK 	"!JsO"x00#T** 1(*)//11 	) 	)HC3$$)KKR((00b99AACC) sC(() IIKK	) &)c" 	1#0Jx  XXk**N $J.#&& <>+?+?+A+A <#$:%:$:;

  88$5r::24&-- 117799 	1 	1MFE555Zt=T=T5$&E${{}} - -etS))-S"--55c2>>FFHH- #53U*;<<- E

((**	- #&e**E$K 1+0 (/22 (#..	3EI_3_3_ 	  '""*dxxr22 ' F  3#2  &%x({ 5$4 !Ms   F$ $F=<F=c                  b   t                      dz  } |                                 sg S g }t          |                     d                    D ]b}	 t	          j        |                    d                    }n# t          $ r Y 8w xY wt          |          }||	                    |           c|S )zScan ~/.hermes/dashboard-themes/*.yaml for user-created themes.

    Returns a list of fully-normalised theme definitions ready to ship
    to the frontend, so the client can apply them without a secondary
    round-trip or a built-in stub.
    zdashboard-themesz*.yamlr  r  )
r   r  r~  globr  rj  r4  rZ   r9  r   )
themes_dirr  ro  r  
normaliseds        rU   r  r  A  s     !""%77J 	FJOOH--.. & &	>!++w+"?"?@@DD 	 	 	H	066
!MM*%%%Ms   (A88
BBz/api/dashboard/themesc                    K   t                      } t          | ddd          }t                      }t                      }g }t          D ]2}|                    |d                    |                    |           3|D ]T}|d         |v r|                    |d         |d         |d         |d           |                    |d                    U||d	S )
an  Return available themes and the currently active one.

    Built-in entries ship name/label/description only (the frontend owns
    their full definitions in `web/src/themes/presets.ts`).  User themes
    from `~/.hermes/dashboard-themes/*.yaml` ship with their full
    normalised definition under `definition`, so the client can apply
    them without a stub.
    r^  r  r1  rE  rR   r?  r  )rR   r?  r  
definition)themesrm  )r   r   r  r~   r  r  r   )r{  rm  user_themesr  r@  r  s         rU   get_dashboard_themesrB  A  s       ]]FV['9EEEF'))K55DF&  6a 	 	V9fIwZ]+	
 
 	 	 	 	6///rW   c                       e Zd ZU eed<   dS )ThemeSetBodyrR   Nr  r  rW   rU   rD  rD  A  r  rW   rD  z/api/dashboard/themec                    K   t                      }d|vri |d<   | j        |d         d<   t          |           d| j        dS )z9Set the active dashboard theme (persists to config.yaml).r^  r  T)rs  r  )r   rR   r   )r0  r{  s     rU   set_dashboard_themerF  A  sU       ]]F&   {#'9F; +++rW   r  >   dm-sans	work-sans
space-monosystem-monosystem-sanssource-serifsystem-serifibm-plex-monoibm-plex-sansjetbrains-monoatkinson-hyperlegibleinterfrauncesspectralz/api/dashboard/fontc                  z   K   t                      } t          | ddt                    }|t          vrt          }d|iS )zEReturn the active font override (``"theme"`` = use the theme's font).r^  fontrE  )r   r   _FONT_DEFAULT_ID_FONT_CHOICES)r{  rV  s     rU   get_dashboard_fontrY  A  sB       ]]F6;8HIIID=  D>rW   c                       e Zd ZU eed<   dS )FontSetBodyrV  Nr  r  rW   rU   r[  r[  A  r  rW   r[  c                    K   | j         t          v r| j         nt          }t                      }d|vri |d<   ||d         d<   t	          |           d|dS )a-  Set the dashboard font override (persists to config.yaml).

    Accepts any id in the curated catalog, or ``"theme"`` to clear the
    override and fall back to the active theme's own font. Unknown ids are
    coerced to ``"theme"`` rather than 400'd so a stale client can't wedge
    the picker.
    r^  rV  T)rs  rV  )rV  rX  rW  r   r   )r0  rV  r{  s      rU   set_dashboard_fontr]  B  si       	]224998HD]]F&   {"&F;%%%rW   	api_fielddashboard_dirc                   t          | t                    r|                                 sdS t          |           }|                                rdS 	 ||z                                  }|                                }n# t          t          f$ r Y dS w xY w	 |                    |           n# t          $ r Y dS w xY w| S )u  Validate the manifest's ``api`` field for the plugin loader.

    The web server later imports this file as a Python module via
    ``importlib.util.spec_from_file_location`` (arbitrary code
    execution by design — that's how plugins extend the backend).
    Pre-#29156 the field was used as-is, which meant:

    * An absolute path swallowed the plugin's dashboard directory
      entirely — ``Path('safe/dashboard') / '/tmp/evil.py'`` resolves
      to ``/tmp/evil.py``, so any attacker-controlled manifest could
      point the import at any Python file on disk (GHSA-5qr3-c538-wm9j).
    * A ``../..`` traversal could climb out of the plugin into
      neighbouring directories on the search path.

    Return the original string when the resolved path stays under
    ``dashboard_dir``; return ``None`` (with a warning logged at the
    call site) otherwise so the plugin still loads its static JS/CSS
    but its backend ``api`` is rejected.
    N)
rv  r  r   r   r  r  r  r  r  r  )r^  r_  r  r#  r-  s        rU   _safe_plugin_api_relpathra  B  s    ( i%% Y__->-> tYI t!I-6688$$&&\"   ttT""""   tts$   +A> >BBB- -
B;:B;c                  0   g } t                      }ddlm}  |            }t                      dz  df|dz  df|dfg}t	          d          r.|                    t          j                    dz  dz  d	f           |D ]\  }}|                                st          |
                                          D ]}|                                s|d
z  dz  }|                                s5	 t          j        |                    d                    }	|	                    d|j                  }
|
|v r~|                    |
           t%          |	                    d          t&                    r|	                    di           ni }|                    dd|
           |                    dd          d}|                    d          }t%          |t(                    r|                    d          r||d<   t-          |                    d                    rd|d<   |	                    d          }g }t%          |t.                    rd |D             }|	                    d          }|d
z  }t1          ||          }|r|t2                              d|
|           |                     |
|	                    d|
          |	                    dd           |	                    d!d"          |	                    d#d$          |||	                    d%d&          |	                    d'          t-          |          |t)          |          |d(           # t6          $ r'}t2                              d)||           Y d}~d}~ww xY w| S )*ax  Scan plugins/*/dashboard/manifest.json for dashboard extensions.

    Checks three plugin sources (same as hermes_cli.plugins):
    1. User plugins:    ~/.hermes/plugins/<name>/dashboard/manifest.json
    2. Bundled plugins: <repo>/plugins/<name>/dashboard/manifest.json  (memory/, etc.)
    3. Project plugins: ./.hermes/plugins/  (only if HERMES_ENABLE_PROJECT_PLUGINS)
    r   )get_bundled_plugins_dirr   r   rj  r   HERMES_ENABLE_PROJECT_PLUGINSz.hermesprojectr^  manifest.jsonr  r  rR   rb  r   r   positionr  )r   rg  r	  hiddenTslotsc                 @    g | ]}t          |t                    ||S r  )rv  r  rW  s     rU   r  z/_discover_dashboard_plugins.<locals>.<listcomp>~B  s,    NNN1Z35G5GNANQNNNrW   r  )r_  NzPlugin %s: refusing unsafe api path %r (must be a relative file inside the plugin's dashboard/ directory); backend routes from this plugin will not be mountedr?  r  r   iconPuzzler   z0.0.0r  zdist/index.jsr  )rR   r?  r  rk  r   rb  ri  r  r  has_apir   _dir	_api_filez$Bad dashboard plugin manifest %s: %s)r~   r  rc  r   r1   r   r   r  r  r~  r  rA  r*  r+  r4  r   rR   r  rv  r  r  r   r   r  ra  rP   r3  rZ   )r   
seen_namesrc  bundled_rootsearch_dirsplugins_rootr   r  manifest_filer  rR   raw_tabtab_infooverride_path	slots_srcri  raw_apir_  safe_apirN  s                       rU   _discover_dashboard_pluginsr{  =B  s    GeeJ::::::**,,L			Y	&/		 ),	y!K 677 LDHJJ2Y>	JKKK + F Ff""$$ 	L002233 C	 C	E<<>> !K//AM '')) =z-"9"97"9"K"KLLxx
33:%%t$$$
 2<DHHUOOT1R1RZ$((5"---XZ#KK
D

;; 'J > >  !(J 7 7mS11 9m6N6Ns6S6S 9+8HZ(H--.. .)-HX& !HHW--	#%i.. ONN	NNNE ((5// % 33G=YYY x/LL) g    !XXgt44#'88M2#>#> HHVX66#xx	7;;#"!XXg??88E??#H~~$..!)           C]TWXXXCC	H Ns    /AM 7H'M  
N*NN_dashboard_plugins_cacheforce_rescanc                     t           | rt                      a n3t           r,t          d t           D                       rt                      a t           S )Nc              3   f   K   | ],}t          |d                                                     V  -dS )rn  N)r   r  r  s     rU   r   z)_get_dashboard_plugins.<locals>.<genexpr>B  s;      NN4&	??))+++NNNNNNrW   )r|  r{  r  r}  s    rU   r   r   B  sX    '<'#>#@#@  	! ENN5MNNNNN 	E'B'D'D$##rW   z/api/dashboard/pluginsc                  L  K   t                      } t                      }t          |ddg           pg 	 ddlm}m}  |             |            n,# t          $ r t                      t                      Y nw xY wdt          dt          ffdfd	| D             S )
zPReturn discovered dashboard plugins (excludes user-hidden and non-enabled ones).r^  hidden_pluginsrE  r   r   r   rJ   c                     |                      dd          }|v rdS |                      d          dk    r|v rdS |vrdS n|                      d          dk    r|v rdS dS )NrR   r   Fr   r   r   Tr   )r   rR   r   r   rh  s     rU   
_is_activez)get_dashboard_plugins.<locals>._is_activeB  s    uuVR  6>>555??f$$|##u;&&u 'UU8__	))|##utrW   c                 b    g | ]+} |          d  |                                 D             ,S )c                 D    i | ]\  }}|                     d           ||S r  r  r  s      rU   rI  z4get_dashboard_plugins.<locals>.<listcomp>.<dictcomp>B  s/    ===$!Q1<<+<+<=A===rW   r  )r   r   r  s     rU   r  z)get_dashboard_plugins.<locals>.<listcomp>B  sM       :a====!''))===  rW   )
r   r   r   r   r   r   rZ   r~   r  r   )r   r{  r   r   r  r   r   rh  s       @@@@rU   get_dashboard_pluginsr  B  s
      %&&G]]F6;0@"MMMSQSFNNNNNNNN&&((((**   eeuud t              s   A &A?>A?z/api/dashboard/plugins/rescanc                  J   K   t          d          } dt          |           dS )z#Force re-scan of dashboard plugins.Tr  )rs  r  )r   r   )r   s    rU   rescan_dashboard_pluginsr  B  s,       %$777GW...rW   c                   6    e Zd ZU eed<   dZeed<   dZeed<   dS )_AgentPluginInstallBodyr  Fr  Tr  N)r  r  r  r  r  r  r   r  r  rW   rU   r  r  B  s;         OOOE4FDrW   r  r   c                 >    d |                                  D             S )Nc                 D    i | ]\  }}|                     d           ||S r  r  r  s      rU   rI  z-_strip_dashboard_manifest.<locals>.<dictcomp>B  s/    @@@TQall3.?.?@Aq@@@rW   r  )r   s    rU   _strip_dashboard_manifestr  B  s    @@QWWYY@@@@rW   c                    ( ddl m} m}m}m}m}m}m} t                      }d |D             } |            }	 |            }
t                      }t          |ddg           pg }t                      dz                                  }g } |             D ]\  }}}}}}|h}|r|                    |           ||	z  rd}n
||
z  rd	}nd
}t          |          }|                    |          }|dup|dz  dz                                  }d}	 |                                                    |           d}n# t$          $ r Y nw xY w|dv o"|o t          |                                          }d}d} ||          }|                    d          pg }|rX	 ddlm}  |D ]=}!|                     |!          }"|"r$|"j        r|"                                s	d}d| } n>n# t0          $ r Y nw xY w|                    ||pd|pd||||rt5          |          nd|||o#t          |          dz                                  ||||v d           d |D             ((fd|D             }#t7                      }$g }%	  |            D ]\  }&}'|%                    |&|'d           n# t0          $ r g }%Y nw xY w||#t9           |                      |$ |            |%ddS )zJAgent discovery + dashboard manifests + optional provider picker metadata.r   )_discover_all_plugins_get_current_context_engine_get_current_memory_provider_discover_context_enginesr   r   _read_manifestc                 :    i | ]}t          |d                    |S r  r  r  s     rU   rI  z'_merged_plugins_hub.<locals>.<dictcomp>B  s$    >>>!C&	NNA>>>rW   r^  r  rE  r   r+	  r  inactiveNrf  FT>   r  r   r   provides_tools)registryzhermes auth rp  )rR   r   r  r   runtime_statushas_dashboard_manifestdashboard_manifestr   
can_removecan_update_gitr   auth_commanduser_hiddenc                     h | ]
}|d          S r  r  r  s     rU   r  z&_merged_plugins_hub.<locals>.<setcomp>CC  s    +++1V9+++rW   c                 \    g | ](}t          |d                    vt          |          )S r  )r  r  )r   r   agent_namess     rU   r  z'_merged_plugins_hub.<locals>.<listcomp>DC  s@       qy>>,, 	"!$$,,,rW   r  )memory_providermemory_optionscontext_enginecontext_options)r   orphan_dashboard_pluginsr  )r   r  r  r  r  r   r   r  r   r   r   r   r  r  r   r   rA  r  r  r  tools.registryr  r  check_fnrZ   r   r  r  r  ))r  r  r  r  r   r   _read_plugin_manifest_atdashboard_listdash_by_namer   r   r{  r  plugins_root_resolvedr   rR   r   r  r   dir_strr  r  r  dir_pathdmhas_dash_manifestunder_user_treecan_remove_updater   r  manifest_datar  r  tnamer  orphan_dashboardmemory_providerscontext_enginesr  r  r  s)                                           @rU   _merged_plugins_hubr  B  sz                     ,--N>>~>>>L$$&&L""$$K ]]F"6;8HRTUUU[Y[N,..:CCEE!#D<Q<Q<S<S < <8g{FGS & 	KK\! 	('NN{" 	(&NN'N==d##dNax+/E/W._._.a.a	**+@AAA"OO 	 	 	D	 o%T/Td7mm>R>R>T>T 	
 00::&**+;<<B 
		333333+  E$..u55E  8H8H (,'<d'<'<    	}"&,",&7CE"O";B"?"?"?4+/UT']]V5K4S4S4U4U*(>1
 
 	 	 	 	  ,+d+++K      :;;,.O0022 	E 	EGAt""Ad#C#CDDDD	E    $4>?[?[?]?]^^.99;;.	
 
	 	 	s7   )D99
EEAG  
G-,G-;(J$ $J32J3z/api/dashboard/plugins/hubc                    K   t          |            	 t                      S # t          $ r2}t                              d|           t          dd          |d}~ww xY w)zIUnified agent plugins + dashboard extension metadata (session protected).zplugins/hub failed: %sr!  zFailed to build plugins hub.r   N)r   r  rZ   rP   r3  r5   )r   rN  s     rU   get_plugins_hubr  _C  sw       7]"$$$ ] ] ]-s3334RSSSY\\]s   ! 
A-AAz$/api/dashboard/agent-plugins/installc                 T  K   t          |            ddlm}  ||j                                        |j        |j                  }|                    d          s&t          d|                    d          pd          t          d	
           |
                    dd            |S )Nr   )dashboard_install_plugin)r  r  rs  r   r  zInstall failed.r   Tr  after_install_path)r   r   r  r  r   r  r  r   r5   r   r  )r   r0  r  r  s       rU   post_agent_plugin_installr  jC  s      7??????%%j{  F
 ::d 
::g&&;*;
 
 
 	
 ----
JJ#T***MrW   c                 f    |                      d          } | rd| v sd| v rt          dd          | S )z=Reject path-traversal attempts in plugin name URL parameters.r   rD  r  r   zInvalid plugin name.r   )r   r5   r  s    rU   _validate_plugin_namer  C  sC    ::c??D L44<<44<<4JKKKKKrW   z//api/dashboard/agent-plugins/{name:path}/enablec                    K   t          |            t          |          }ddlm}  ||d          }|                    d          s&t          d|                    d          pd	          |S )
Nr   "dashboard_set_agent_plugin_enabledTr  rs  r   r  zEnable failed.r   r   r  r   r  r   r5   r   rR   r  r  s       rU   post_agent_plugin_enabler  C  s      7 &&DIIIIII//dCCCF::d ]FJJw4G4G4[K[\\\\MrW   z0/api/dashboard/agent-plugins/{name:path}/disablec                    K   t          |            t          |          }ddlm}  ||d          }|                    d          s&t          d|                    d          pd	          |S )
Nr   r  Fr  rs  r   r  zDisable failed.r   r  r  s       rU   post_agent_plugin_disabler  C  s      7 &&DIIIIII//eDDDF::d ^FJJw4G4G4\K\]]]]MrW   z//api/dashboard/agent-plugins/{name:path}/updatec                    K   t          |            t          |          }ddlm}  ||          }|                    d          s&t          d|                    d          pd          t          d	           |S )
Nr   )dashboard_update_user_pluginrs  r   r  zUpdate failed.r   Tr  )r   r  r   r  r   r5   r   )r   rR   r  r  s       rU   post_agent_plugin_updater  C        7 &&DCCCCCC))$//F::d ]FJJw4G4G4[K[\\\\----MrW   z(/api/dashboard/agent-plugins/{name:path}c                    K   t          |            t          |          }ddlm}  ||          }|                    d          s&t          d|                    d          pd          t          d	           |S )
Nr   )dashboard_remove_user_pluginrs  r   r  zRemove failed.r   Tr  )r   r  r   r  r   r5   r   )r   rR   r  r  s       rU   delete_agent_pluginr  C  r  rW   c                   D    e Zd ZU dZee         ed<   dZee         ed<   dS )_PluginProvidersPutBodyNr  r  )r  r  r  r  r   r  r  r  r  rW   rU   r  r  C  s:         %)OXc])))$(NHSM(((((rW   r  z/api/dashboard/plugin-providersc                    K   t          |            ddlm}m} |j        .t          |j                  }t          |            ||           |j         ||j                   ddiS )zHPersist memory provider / context engine selection (writes config.yaml).r   )_save_context_engine_save_memory_providerNrs  T)r   r   r  r  r  r  r  r  )r   r0  r  r  r  s        rU   put_plugin_providersr  C  s       7       
 '9$:NOO&777o...&T0111$<rW   c                       e Zd ZU eed<   dS )_PluginVisibilityBodyrh  Nr  r  rW   rU   r  r  C  r  rW   r  z-/api/dashboard/plugins/{name:path}/visibilityc                   K   t          |            t          |          }t                      }d|vs(t          |                    d          t
                    si |d<   |d                             d          pg }t          |t                    sg }|j        r||vr|                    |           n |j        s||v r|	                    |           ||d         d<   t          |           d||j        dS )zXToggle a plugin's sidebar visibility (persists to config.yaml dashboard.hidden_plugins).r^  r  T)rs  rR   rh  )r   r  r   rv  r   r  r  rh  r   r  r   )r   rR   r0  r{  hidden_lists        rU   post_plugin_visibilityr  C  s      7 &&D]]F&  
6::k3J3JD(Q(Q  {{+//0@AAGRKk4(( { !t;..4    [ !T[004   ,7F;()<<<rW   z1/dashboard-plugins/{plugin_name}/{file_path:path}r7  c                    K   t                      }t           fd|D             d          }|st          dd          	 ddlm}m}  |            } |            }n,# t          $ r t                      }t                      }Y nw xY w|                    d          d	k    r |v s |vrt          dd          n.|                    d          d
k    r |v rt          dd          t          |d                   }||z  
                                }	|	                    |
                                          st          dd          |	                                r|	                                st          dd          |	j                                        }
i dddddddddddddddddddd d!d"d#d$d%d&d'd(d)d*d+d,d-d}|
|vrt          dd          ||
         }t!          |	|d.d/i0          S )1u  Serve static assets from a dashboard plugin directory.

    Only serves files from the plugin's ``dashboard/`` subdirectory.
    Path traversal is blocked by checking ``resolve().is_relative_to()``.

    Restricted to a browser-fetchable suffix allowlist (JS/CSS/JSON/HTML/
    SVG/PNG/JPG/WOFF). The dashboard loads plugin JS via ``<script src>``
    and CSS via ``<link href>``, neither of which can attach a custom
    auth header — so this route stays unauthenticated to keep the SPA
    working. But user-installed plugins ship a ``plugin_api.py``
    backend module that the browser never fetches; it's only imported
    by :func:`_mount_plugin_api_routes` at startup. Without a suffix
    allowlist, anyone on the loopback port can curl the ``.py`` source
    of a private third-party plugin. Reject everything outside the
    browser-asset set.

    User plugins must be in plugins.enabled before their assets are
    served. (#46435, GHSA-mcfc-hp25-cjv7)
    c              3   4   K   | ]}|d          k    |V  dS r   r  r   s     rU   r   z%serve_plugin_asset.<locals>.<genexpr>D  s1      BB6k)A)A1)A)A)A)ABBrW   Nr   r   r   r   r   r   r   r   rn  r  zPath traversal blockedr  r  zapplication/javascriptr  r  r  r  r(  r  z	text/htmlr[  rT  rV  rP  rW  rQ  rX  rY  rR  rZ  rS  r]  rU  z.woff2z
font/woff2z.woffz	font/woffz.ttfzfont/ttfz.otfzfont/otfz.mapr  r  )r  r   )r   r   r5   r   r   r   rZ   r~   r   r   r  r  rA  r  r  r   r;   )r   r7  r   r   r   r   r   r   r-  rh   r  content_typesr  s   `            rU   serve_plugin_assetr  C  s     * %&&GBBBBgBBBDIIF H4FGGGGNNNNNNNN&&((((**   eeuu zz(v%%,&&+[*H*HC8JKKKK +I	H			*	*,&&C8JKKKKvDY''))F  00 N4LMMMM==?? F&.."2"2 F4DEEEE ]  ""F'( 	
 	#	
 	 	 	 	 	 	 	 	 	, 	 	
  	
!" 	"#M& ]""#
 
 
 	
 v&J "GH   s   A &BBc                  *   	 ddl m} m}  |             } |            }n,# t          $ r t	                      }t	                      }Y nw xY wt                      D ]8}|                    d          }|s|                    dd          }|                    d          dk    rA||v rt                              d|           j||vrt                              d	|           n9|                    d          d
k    r ||v rt                              d|           |                    d          dk    r$t          	                    d|d         |           t          |d                   }||z  }	 |                                }	|                                }
|	                    |
           n@# t          t          t          f$ r& t          	                    d|d         |           Y w xY w|                                s$t          	                    d|d         |           	 d|d          }t"          j                            ||          }||j        	t"          j                            |          }|t,          j        |<   	 |j                            |           n/# t          $ r" t,          j                            |d            w xY wt5          |dd          }|#t          	                    d|d                    t6                              |d|d                     t                              d|d                     # t          $ r-}t          	                    d|d         |           Y d}~2d}~ww xY wdS )u  Import and mount backend API routes from plugins that declare them.

    Each plugin's ``api`` field points to a Python file that must expose
    a ``router`` (FastAPI APIRouter).  Routes are mounted under
    ``/api/plugins/<name>/``.

    Backend import is restricted to ``bundled`` and ``user`` sources.
    Project plugins (``./.hermes/plugins/``) ship with the CWD and are
    therefore attacker-controlled in any threat model where the user
    opens a malicious repo; they can extend the dashboard UI via
    static JS/CSS but their Python ``api`` file is never auto-imported
    by the web server.  See GHSA-5qr3-c538-wm9j (#29156).

    Additionally, user plugins must be explicitly enabled via the
    ``plugins.enabled`` allow-list in config.yaml before their backend
    code is imported. Without this gate, an installed-but-not-enabled
    plugin's Python code would execute at dashboard startup — a code
    execution vector that bypasses the user's intent. (#46435,
    GHSA-mcfc-hp25-cjv7)
    r   r   ro  rR   r   r   r   z3Plugin %s: skipping API mount (explicitly disabled)z6Plugin %s: skipping API mount (not in plugins.enabled)r   re  zPlugin %s: ignoring backend api=%s (project plugins may not auto-import Python code; move the plugin to ~/.hermes/plugins/ if you trust it)rn  zKPlugin %s: refusing to import api file outside its dashboard directory (%s)z,Plugin %s declares api=%s but file not foundhermes_dashboard_plugin_Nr   z,Plugin %s api file has no 'router' attributer   )r|  z+Mounted plugin API routes: /api/plugins/%s/z'Failed to load plugin %s API routes: %s)r   r   r   rZ   r~   r   r   rP   r  r3  r   r  r  r  r  r  rA  	importlibutilspec_from_file_locationloadermodule_from_specr  modulesexec_moduler  r   rc   include_routerrQ   )r   r   r   r   r   api_file_namer   r_  api_pathresolved_apiresolved_basemodule_namerp  r9  r   rN  s                   rU   _mount_plugin_api_routesr  CD  s   ,NNNNNNNN&&((((**   eeuu )** QY QY

;// 	jj,,
 ::h6))l**

I   +--

L    . ZZ!!Y..l**

I   ::h9,,LL6 v	   VF^,, =0	#++--L)1133M$$]3333z2 		 		 		
 LL+,26NH   H		    	LLGPVYfggg	YEVF^EEK>99+xPPD|t{2.11$77C (+CK$'',,,,   T222 S(D11F~KVTZ^\\\v.NfVn.N.NOOOIICVF^TTTT 	Y 	Y 	YLLBF6NTWXXXXXXXX	YaQY QYs^    &AA5=F339G0/G0,4M".MJ,+M,,K7MAM
N#"NNr  zuvicorn.Serverc                     | j         rB| j         d         j        r0| j         d         j        d                                         d         S |S )aS  Read the OS-assigned port from a live uvicorn server socket.

    After ``server.startup()`` the socket is bound.  Returns the actual
    port so ephemeral (port-0) discovery works without a pre-bind TOCTOU.
    Falls back to *fallback* if the socket list is empty (shouldn't happen
    but guards against uvicorn internals changing).
    r   r   )rw  socketsgetsockname)r  r	  s     rU   _read_bound_portr  D  sJ     ~ =&.+3 =~a (+7799!<<OrW   actual_portc                 6   t           j                            d          }|sdS d}	 t          |          }|j                            dd           t          j        dt          |           id          }t          j
        d	d
t          |j                  |j         ddd          5 }|                    |           |                                 t          j        |                                           |j        }ddd           n# 1 swxY w Y   t          j        ||           dS # t$          $ r^}|r5	 t          |                              d           n# t$          $ r Y nw xY wt(                              d||           Y d}~dS d}~ww xY w)a  Optionally publish the dashboard port through an atomic ready file.

    Windows Desktop can launch dashboard backends with ``pythonw.exe`` to avoid
    console flashes. That path cannot rely on stdout for the port announcement,
    so Electron passes ``HERMES_DESKTOP_READY_FILE`` and waits for this JSON.
    Normal CLI/dashboard launches still use the stdout READY line below.
    HERMES_DESKTOP_READY_FILENr   Tr  rR  )rQ  r   )
separatorswr  r~  z.tmpF)r  r  r|  r  r  r  z+Failed to write dashboard ready file %r: %s)rt   r  r   r   r  r"  r*  r
  rw  r  r  r  rR   r  flushfsyncfilenor  rZ   r  rP   r3  )r  rh   r  r   rj  r;  rN  s          rU   _write_dashboard_ready_filer  D  s    Z^^788F HQF||$666*fc+&6&67JOOO(DK  i???
 
 
 	 HHWHHJJJHRYY[[!!!wH	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	
8T""""" Q Q Q 	X%%%6666   BFCPPPPPPPPPQsa   BD0 +ADD0 DD0 DD0 0
F:F=#E! F!
E.+F-E..FFopen_browserinitial_profilec                   	 |sdS ddl 	t          j        dk    pWt          t          j                            d                    p+t          t          j                            d                    }|st                              d           dS | dvr| nd}d	| d
| |rddl	m
} d ||           z  	fd}t          j        |d                                           dS )a2  Open the dashboard URL in the user's browser if appropriate.

    Skips on headless Linux (no ``DISPLAY`` / ``WAYLAND_DISPLAY``) to avoid
    TUI browsers (links, lynx) that would SIGHUP the server process.
    Maps ``0.0.0.0`` / ``::`` binds to ``127.0.0.1`` so the browser opens
    a reachable URL.
    Nr   linuxDISPLAYWAYLAND_DISPLAYzzSkipping browser-open: no DISPLAY or WAYLAND_DISPLAY detected (headless Linux). Pass --no-open to suppress this detection.)r   r   r   zhttp://r   )r	  z
/?profile=c                  ~    	 t          j        d                                           d S # t          $ r Y d S w xY w)Nr  )rC  r
  r  rZ   )	_open_url
webbrowsers   rU   _openz"_maybe_open_browser.<locals>._openE  sO    	JsOOOOOI&&&&& 	 	 	DD	s   ). 
<<T)rh   rj   )r  r  r  r   rt   r  r   rP   r  urllib.parser	  rv   rx   rS   )
r   r  r  r   _has_display_display_hostr	  r  r  r  s
           @@rU   _maybe_open_browserr  D  sB      	 	3
y))**	3
01122 
  

K	
 	
 	
 	 (999DD{M7-77+77I ;&&&&&&:%%"8"8:::	      E$///5577777rW   r   #  rR  headlessc           	      n    ddl }	 ddlm}  |             n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wt                     t          j        _	        |r$ t          vrt                              d            t          j        j	        rddlm}	  |	            syg }
	 ddlm} |j        r|
                    d|j                    n# t          $ r Y nw xY wd	}	 dd
lm} ddlm}  |            }|                    d          pi                     d          pi }|                    d          pi                     d          pg }t/          |                    d                    o6t/          |                    d          p|                    d                    }|rt1          |          |z  rd|z   }n# t          $ r Y nw xY w|
r/t3          d  dd                    |
          z   dz   |z             t3          d  d|z             t                              d d                    d  |	            D                                   t          j        _         dv }|                    t           dt/          t          j        j	                  |rdnd|rdnd          |                               fd }t>          j         d!k    rtC          j"         |                       dS 	 dd"l#m$} %                                }nK# t          $ r> d}d}	 tC          j&        tC          j'                               n# t          $ r Y nw xY wY nw xY w| | |            |#           dS tC          j"         |                       dS )$u  Start the web UI server.

    ``initial_profile`` (when set) is appended to the auto-opened browser
    URL as ``?profile=<name>`` so the SPA's profile switcher preselects it
    — used when a profile alias (``<profile> dashboard``) routes to the
    machine dashboard.

    ``headless`` is the ``serve`` path: the JSON-RPC/WS backend with no UI
    build and no SPA mount (mount_spa() honours ``HERMES_SERVE_HEADLESS``), so
    the banner announces the bind rather than a browser URL.
    r   N)start_nous_auth_keepalivez%Nous auth keepalive did not start: %su  --insecure no longer bypasses dashboard authentication. A non-loopback bind (%s) now ALWAYS requires an auth provider (OAuth or the bundled password provider). Configure one — see below — or bind to 127.0.0.1 and reach it over an SSH tunnel / Tailscale.r  )r  u     • nous: u  Configure an auth provider before exposing the dashboard:
  • Password: set dashboard.basic_auth.username + password_hash in config.yaml
    (hash with: python -c "from plugins.dashboard_auth.basic import hash_password; print(hash_password('your-password'))")
  • OAuth: run `hermes dashboard register` (Nous Portal) or install a DashboardAuthProvider plugin.
There is no unauthenticated public-bind option — to keep it local, bind 127.0.0.1 and tunnel in (SSH / Tailscale).r  )_BASIC_AUTH_PLUGIN_KEYSr^  
basic_authr   r+	  usernamepassword_hashrp  zThe 'basic' dashboard-auth plugin is in plugins.disabled but dashboard.basic_auth is configured.
Remove 'basic' from plugins.disabled (or run `hermes plugins enable basic`), then restart the dashboard.

zRefusing to bind dashboard to u    — the auth gate engages on non-loopback binds, but no auth providers are registered.

Bundled providers reported these issues:
r  z

uY    — the auth gate engages on non-loopback binds, but no auth providers are registered.

z=Dashboard binding to %s with auth gate enabled. Providers: %sr  c              3   $   K   | ]}|j         V  d S r  r  r  s     rU   r   zstart_server.<locals>.<genexpr>E  s$      77af777777rW   )r   r   r   r3  r
  )r   rR  	log_levelproxy_headersws_ping_intervalws_ping_timeoutc                    K   j         s                                                               _                                        5                                   d {V  j        r	 d d d            d S t                    } | t          j	        _
        t          |            	rdnd}t          | d|  d           	rt          d
 d|             nt          d	
 d|             t          
|            	 d
dlm}  |t!          j                               n2# t$          $ r%}t&                              d|           Y d }~nd }~ww xY wddt!          j                    dt*          dd ffd                                                    z                                               d {V  j        r                                 d {V  d d d            d S # 1 swxY w Y   d S )N)r	  HERMES_BACKEND_READYHERMES_DASHBOARD_READYz port=T)r  z  Hermes backend listening on r   u     Hermes Web UI → http://r   )install_loop_noise_filterz%loop noise filter install skipped: %sg       @g      @r   rJ   c                                                      }|| z
  }|k    rt                              d|                               |z              d S )Nz1event loop stalled %.1fs (GIL pressure suspected))rC  rP   r3  
call_later)r   r?  drift_hb_interval_hb_loop_hb_stall_threshold_loop_heartbeats      rU   r$  z5start_server.<locals>._serve.<locals>._loop_heartbeatF  sq    mmooh...LLK   ## /33E    rW   )loadedloadlifespan_classr   capture_signalsstartupshould_exitr  rc   rk   r  r  printr  tui_gateway.loop_noiser  rm   rI  rZ   rP   r  rx  r  rC  	main_loopr4  r;  )r  ready_tokenr  rN  r!  r"  r#  r$  r{  r  r   r   r  rR  r  s       @@@@rU   _servezstart_server.<locals>._serveE  s6      } 	KKMMM //77##%% B	( B	(.."""""""""! B	( B	( B	( B	( B	( B	( B	( B	(
 +6DAAAK#.CI '444 5=Z00BZK[5555TBBBB J KtKKkKKLLLLHDHH;HHIIIk<QQQILLLLLL))'*B*D*DEEEE I I I

BCHHHHHHHHI L"%/11H
% 
D 
 
 
 
 
 
 
 
 
 ox}}/M   ""$$$$$$$$$~ (oo'''''''''EB	( B	( B	( B	( B	( B	( B	( B	( B	( B	( B	( B	( B	( B	( B	( B	( B	( B	(sD   #H BH "D10H 1
E ;EH E  BH  HHr  )asyncio_run)loop_factory)(uvicornhermes_cli.nous_auth_keepaliver  rZ   rP   r  r   rc   rk   r   r   r3  r  r  plugins.dashboard_authr  LAST_SKIP_REASONr   r  r   r   r  r   r   r~   r  r  rQ   r   ConfigServerr  r  rm   r  uvicorn._compatr0  get_loop_factoryset_event_loop_policyWindowsSelectorEventLoopPolicy)r   rR  r  r   r   r  r2  r  rN  r  skip_reasons_nous_plugin	_fix_hint	_load_cfgr  _cfg_ba	_disabled
_has_creds_is_loopbackr/  _runner_loop_factoryr{  r  s   ``` ``                 @@rU   start_serverrG  !E  sA   & NNNALLLLLL!!#### A A A

:C@@@@@@@@A 2$77CI  
$999 	
 	
 	
 y P
 	=<<<<<~ F	 ')LGGGGGG0  ''F|'DFF      	I FFFFFFJJJJJJ y{{xx,,277EEK!XXi006B;;JGGM2	 "#''*"5"566 4GGO,,C
0C0C< <
  "3y>>4K#K ") "!"I     	 BT B B B ii--	.
       # # # #%./  
 			KII77nn&6&677777	
 	
 	
  CID <<L^^$TY 39233
 ".74 ,6$   F" ^^F##FH( H( H( H( H( H( H( H( H( H( H(r |wFFHH:::::://11   	)688     	 	 	D	 }555555FFHHso    
AAA:*C% %
C21C28C G 
G&%G&L5 5M=%M*)M=*
M74M=6M77M=<M=)rG   r  )rc   r2   )Fr  )r   r   r  )r  N)r  NF)r   )r  )r"  )
r  r   r   rG  rH  NNNFN)	r  r   r   rG  rT  rf  NNF)r   r  N)r  )NFFF)r
  )NNr   )rV  r  NNN)rf  )Nr  rO  )r   rf  r  Nr*	  )r   N)r  r8   rJ   N)NNNNr  )r   r  TFr   F({  r  
contextlibr   r   rm   rA  r  rW  concurrent.futuresr=  rn  dataclassesr   r   r   r  r   r  importlib.utilr  r*  rp  r  rt   r_  rx  r  r  r  r  r  r  rv   rC  urllib.errorr&  r	  r  hermes_cli._subprocess_compatr	   r
   urllib.requestpathlibr   rB  r   r   r   r   r   r  __file__r  r  r9  r  r   insertr[   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-   hermes_cli.memory_providersr.   DeclaredMemoryProviderr/   DeclaredProviderFieldr0   r  r  r1   fastapir2   r3   r4   r5   r6   r7   r8   r9   fastapi.middleware.corsr:   fastapi.responsesr;   r<   r=   r>   fastapi.staticfilesr?   pydanticr@   rA   starlette.concurrencyrB   r`   tools.lazy_depsrC   _lazy_ensurerZ   r  r  r  r  	getLoggerr  rP   rw  rV   r\   rx  rb   r   r   rn   r   r  r   rc   hermes_cli.memory_oauthr   _memory_oauth_routerr  r   r	  r   r   ry  r   r  r  r  add_middleware&hermes_cli.dashboard_auth.public_pathsr   r  r   r   r	  r   r   r   r   r   r   
middlewarer   r   r  r  r  r  rR  rg  r  rz  r  r  
_mcl_entryr  r  _k_vr  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  ru   r#  r(  r  rl  r3  rK  r1  rG  rO  r  r  r=  rY  r5  ra  r  r  r  r  r  r  r  r  r  r  r  r  r  rt  r  stat_resultr  r  r  r  r  r  r  r  r$  r&  r(  r0  r6  r:  r?  rG  rI  rO  r[  rm  rn  r\  ra  ri  rp  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+  r0  r3  r7  r:  r=  r@  rC  rF  rK  rN  rQ  r^  rg  rx  r  r  r  r  r  r  r  r  putr  r  r#  r%  r'  r-  r1  r4  r  rJ  rN  rR  rV  rX  re  rg  r  r  r  rs  rt  r  ru  rv  r  r  r  r  r  r  compiler  r  r  r  r  r  r  r  r  r  r  r  r  r   r!  r$  r4  r<  rA  rE  rF  re  r}  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  CompletedProcessr  r  r  r  r  r  r2  r5  r?  rD  rL  rN  rU  rW  r]  r`  re  rj  rn  rw  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r&  r/  r,  rF  rP  rS  rl  ry  r|  r  r  r  r  r  r  r  r  r	  rx  r	  r	  r	  r	  r  r	  r 	  r#	  rA	  rC	  r	  r	  rI	  rR	  RLockr	  rY	  r[	  r^	  rb	  rf	  rs	  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+
  r9
  rO
  rS
  rU
  rX
  r]
  rh
  ru
  rj
  rv
  r
  rk
  r
  rl
  _ANTHROPIC_OAUTH_TOKEN_URLrm
  r
  rn
  r
  ro
  r
  rp
  r
  r
  r
  rx
  rz
  r|
  r
  r
  r
  r
  r
  r
  r
  r
  r
  r
  r  r
  r   r"  r$  r&  r(  r>  r@  rB  rE  rF  rI  rP  rT  r\  r`  rf  ra  rk  rq  r{  r  r  patchr  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.  r4  r6  r9  r;  r>  r@  rD  rF  rI  rK  rP  r]  r_  ri  rr  rt  rv  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rk  r  r  r  r  r  r  r  r
  r  r  r  r(  r+  r-  r/  r3  r7  r9  r>  rE  rJ  rM  rO  rQ  rZ  r\  r^  ra  rd  rg  rk  ro  rr  ru  rw  r|  r~  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r
  r#  r%  r-  r0  r2  r4  r6  r8  r:  r  r  r	  rg  rj  rn  rz  r}  r  r  r  r  r  r  r  r  r  r  r  r  r  ru  r  r  r  r  r  r  r  r  r  r  r   r  r
  r  r  r4  r$  r(  r,  r9  r;  r>  rB  rD  rM  rO  rS  rW  r[  r]  r`  rd  rh  rv  rz  r  r  r  r  r  r   hermes_cli.win_pty_bridger  r  r  r  r  hermes_cli.pty_bridger  r  hermes_cli.pty_sessionr  r  rz   r{   r  r&  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r!  r$  r(  r+  r-  r/  r4  rY  rl  r|  r@  r5  r>  r?  r<  rC  rF  rH  rM  rQ  r[  r]  	websocketr  r  r  r  r  r  r  r  r  r  r  r  r  r   r#  r$  r"  r9  r  rB  rD  rF  rW  rX  rY  r[  r]  ra  r{  r|  r   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   hermes_cli.dashboard_auth.routes_dashboard_auth_routerr  r  r  rG  r  rW   rU   <module>rs     s  	 	 	 ; : : : : : : :             ! ! ! ! ! ! ' ' ' ' ' ' ' '              				 				         



                R R R R R R R R           3 3 3 3 3 3 3 3 3 3 3 3 3 3 tH~~$+33553|CH$$HOOAss<(())) 4 4 4 4 4 4 4 4                                           ,                          
 " ! ! ! ! !
                    766666TTTTTTTTTTTT//////--------7777777 
 
 

::::::%e4444	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	;:::::XXXXXXXXXXXX33333311111111;;;;;;; 
 
 
j[ ^[ [ [
 
 	

 	<;
, 3Drz2Q2Q44
,-...W[W[\dWeWeWloyWyw""2 2+< 2 2UY 2 2 2 2*   5 5 5 5 5 * * * *Z> > > >"(Y (7< ( ( ( (2y 2T#t)_ 2 2 2 2 gNK)LLL C B B B B B   ' ( ( (  @AA^EZWEZ[]E^E^/  $(   #% DK $ $ $     D%%	    (     
Ag A$ A A A A. *34I3J)K)K 	# K K KXG X3 X4 X X X XDG D D D D DH $-9 . . . $ $ y   
- -c - -$ - - - -,)!3 )!C )!D )! )! )! )!X $' $ $ $ $: ?$G ?$ ?$ ?$ ?$V ; ; ; ; ;
 $7 $ $ $ $, 
;G 
; 
; 
; 
;$($s) ( ( ( ($o0/++-- o0 I o0 V o0  3PPP !o0* +z* +o04 0=== 5o0> 0 DCC ?o0L 0-   Mo0V )777 Wo0` 3PPP ao0j =--- ko0t >222   uo0~ 8--- o0H 2x( Io0R 4--- So0\ 0888 ]o0f <222 go0r ATTT$ $ G
 Y'	. 	. # #Oo0 o0 o0 4T#s(^+, o o od#z#w# g# G	#
 w# 7# # 9# # g# g# W# y#" '##* 	+#2 G3#c3h   :  s s    " % %cN%% 
#tCH~
% % % %P *).99
 56
-/c4S>)* / / /!!## = =FBOB	W}}2<./" " " " "9 " " "
	 	 	 	 	9 	 	 	" " " " "9 " " "
" " " " "9 " " "
                              " " " " "i " " "# # # # #i # # #" " " " "i " " "
" " " " "i " " "" " " " "i " " "$ $ $ $ $	 $ $ $
    	   # # # # #i # # #
    Y       	    * * S#X    #3 ; ; ; ; ; ;
" " " " "i " " "8+ + + + +9 + + +    y    " " " " "y " " ""Ws W3 W5c? W W W Wv UW5 55 #5,/5;>5NQ5	5 5 5 5p  bi 455 "#eIBI.F$L$LMMI " " "LLH	*++   "" #' 'uT4$;%67 ' ' ' 'Ts    8s    ( 	 	  $ 7 + !T+..  $          4 %.I / / / % % !0  )y*     8 8 8 8 8 8*TT Td T T T T, * , '  + !#!U! E! E	!
 E! 
4! 	! 	#! E! V! V! 
<! V! E! 
8!  E!!" 
:#!$ A! ! ! D

K \ K	
 \ L K   L K K % K O  K!" \#$ \% ,Ds Dt D D D D(1 1 1 1 1 1)5 )T ) ) ) )4 E$*>$?    &T cDj    
 
 
 
 
     &DJ    ( `# ` ` ` `@ ;@ D D D$ D4 DD D D D D	3: 	$ 	 	 	 	4 4t 4 4 4 4 4t     =g =$ = = = =
.$ 
. 
. 
. 
.#D # # # #L DH Y Y Y7 YD YL^ Y Y Y Y. 	#+ #+ #+Dj#+#+ 	#+
 tS()#+ #+ #+ #+L#5 $sCx.     2 D T#s(^    2s uUCZ'8    "  0 !*+]+]+]!^!^ 4 5ucz*C/0   'C$J '3 ' ' ' ' #*       5S;Q         
"##' '_ 'x} ' ' ' $#'T  g Xc]    > 	W C    D 	            F 
'8 7    > "  
$%% tCyyS		d4jj	< <<
< < 	< < < &%<~ 
,B W    ( LP'8 P7 P P P P0 X X X X X2 	S    .    )   
 
+T +T +T +T  +T\ 	 !!
I 
I 
I 
I "!
I 	.C . . . . 	7 7  7 + * * * * *X X XC C        )       )   
    I   ) ) ) ) ) ) ) )    I       )   
 	@ @ @ @ @ 	QC Q Q Q Q 	N3 N N N N 	!""S S S S #"S 	  M Mc M# MXVY] M M M ! M 	  afe e
ee!$e<DSMeZ^e e e ! e 	VC Vs V V V V 	)**J J J J +*J 	$%%S SC Shsm S S S &%S 	$%%EC E E E &%E 
!""Q Q Q Q #"Q 
#$$S+ S S S %$S 
"##R R R R $#R 
"##` ` ` ` $#` 
 !!E{ E E E "!E 
%&&JK J J J '&J 
!""O'9 O O O #"O 
$%%*?    &% 
"##T(; T T T $#T  %$$)!,
< 
< d3c3h#78 
 
 
 "	"F"F"FGG .$ .$ .DQTVYQYN . . . .b9S4S> 9S 9S 9S 9Sx [3 [3hsm [3 [3 [3 [3|  
3 
 
 
 
 
 
  	
  
#s(^   6 	M M Mr   (    9    	5< 5 5 5  5 
@ @ @ 	V Vhsm V V V  V " " " " "i " " "
" " " " "y " " " 	  hsm      !!_    "! 	%5    S     % % %^ 
 !!@ @ "!@ /9 9 9 
#$$C C %$C    	    
 !!   ):T)A       "! Z ())F2 2 2 2' '/ $#,  %,%(% &% (	%
 !% 1% !% !% 7% 1% 5% /% +% +% %  1!%" 5#% 4S>   , .0tC))* / / //1 $sE#s(O+, 1 1 1 .0c4S>)* / / /B B3 B B BT B B B B"	S 	 	 	 	,T#Y ,c ,j>N , , , ,^,d ,s ,tCy , , , ,^:# :c :d3i : : : :Ehsm E3 E3 E E E E
 $$=>> !rz&))  bj!455 /s / /S /T / / / /dF FHSM FU:CSUYCY=Z F F F F* 8C= DQTVYQYN    , 
 !! 8C=    "!  
E E E E  EP 
( (  (V- - -T$sCx.-A - - - -` 	#$$V VT V V V %$Vr 
!""F+D F F F #"FR    i   84S> 8c 8 8 8 8 )-  , , ,Xc] t      	'((@1 @1 )(@1F 
=o = = = =@ 	%&&" "# "c " " " '&"X ? tDcN'; T#s(^@T     !_M _M_M_M _M 	_M
 _M _M _M _M _M c]_M _M _M _MD 	!""{ {{{ { 	{
 { { { { { { { #"{| 	  `E `ES `Ec `E# `E `E `E ! `EFd38n c3h    .< < < < < <# #        C DcN    $F FS	 F F F F# $sCx.    *c d38n     * 	! ! ?S ?S ? ? ? ?Qc Qc Q Q Q Q
     0 0hsm 0C 03 0 0 0 0DcN    * 7;  
  	
  
34 C= 
#s(^   0     	
      &"4S> "d " " " "<&DI &$tTWY\T\~J^ & & & &RltCH~&l	$sCx.l l l l^ c3h    @2C 23 24SRUXCW 2 2 2 2j2$ 24S> 2 2 2 2" "S#X " " " "JJ# J3 J J J J 16 8 8 8 8 8$ 8 8 8 8$sCx. S    S#X d38n     &#c3h #tCH~ #$ # # # # :> S>
sCx. Dd38n!456 
	   *c3h tCH~ RVWZ\_W_R`    "3 # $sCx.    *S#X *S *S * * * * c S $sTWx. ]a    0
H 
H 
H 
H 
H 
H 
H7Dc3h,@ 7 7 7 7t
 
 
 
 
 
((
(( cN( 
	( ( ( (D &2:&IJJ 	Xc 	Xd 	X 	X 	X 	X$=+A =d = = = =C+A Cd3PS8n C C C C&; 4S> VY    S"7 StCH~ SRV S S S SN)? NDcN N N N NB"(= "C "C " " " "(/E (tTWY\T\~ (bf ( ( ( (< 	.//4 43 4# 4 4 4 0/4& 
.//0c 01K 0 0 0 0/0( 	.//)M )Mc )M9S )M^fgj^k )M )M )M 0/)MX F Fhsm F F F F 	    !  	H H H
  !  4    	N' N'HSM N' N' N' N'r$sCx    	!!&	-T -Tc]-T-T -T 	-T -T -T -T` 	)**IB IBC IB IB IB +*IBX 	  /W /W(3- /W /W /W ! /Wd 	Q QHSM Q Q Q Q 	@Q @Q) @QHSM @Q @Q @Q @QF 
9W 9W_ 9Wx} 9W 9W 9W 9Wz UW^ ^^^&)^14^@C^NQ^ ^ ^ ^F/s /3 /5QTVYQY? / / / /dJc3h JDcN J J J JZ M Ml MXc] M M M M(h h h h hV ; ; ; ; ; ;| M ML M8C= M M M M, ID;Z	2 2 DeCHo-.   5 T#Y    6 
#$$:s\ :sG :s :s :s %$:sz JM M| Mhsm M M M M& 
CG- -
-!(-3;C=- - - -FH2JGV/ H2 NA

 /
 
H2& {0Q> 'H24 S4V> 5H2B H;
 W CH2Z BFQ= [H2h [8
  iH2@  PH.2 AH2N EV

 OH2p 
C4?C qH2~ D]DH H2L 9a
 ? MH2d Ia eH2n #?N4) oH2| DN

 }H2^ ,[]L= _H2l (N.

 K
 
mH2D N&I9  	[ #;	  bV
   W_I CH2 H2 H2 T#tCH~-. H H HX$sCx   6G7Q%A G7 O" G7 L( G7 <# G7& -! 'G70 D& 1G7: N* ;G7B W& CG7J [/ KG7T ?! UG7\ ?" ]G7f ? gG7n ? oG7v +&/ wG7@ *% AG7J $8NSSKG7L /  MG7X '! 
 /%# # ,"   
 ; 
 9!( ( `( 
 [#  g&  &<xPP1  3  :&" " 6 
 =! EG7 G7 G7 $sDcN23 G G GT&U4S>3+>%? & & & &R9S>    , &I ' ' '   : :c3h : : : :$S $U38_ $ $ $ $38n * 38_	    26    $'$J 	#s(^       F c3h$)>    	S 	T#s(^ 	 	 	 	-# - - - - 	S SS>Sc3hS D[S 	S
 
#s(^S S S SlA At A A A A A $'  )W)W)W &          HJ tC)C$CD I I I+IO-- W W3 W W W Ws s    VS VS V V V VL L L L L3 3:    Q QsTzSVY]S]_bei_iGiAj Q Q Q QB,
T ,
d ,
 ,
 ,
 ,
^*$ *c *jFV * * * *Zj&6&= $    :
 :
:3C :
 :
 :
 :
 :
z.c . .S .T . . . .8< < < <*7$ 74 7 7 7 7S :T Y]^acf^fYg      QUVY[^V^Q_    * 
4555<*A 5< 5< 5< 655<p 	:;;@S @ @ @ <;@ 
ABBMQ4 4424=Ec]4 4 4 CB4n =>>    ?> $Q  "B["B"B 
% % % % % % % % HJ tC)C$CD I I I+IO-- s    !C !E ! ! ! !< < < <s sTz    c S S     #'#C C CC
C sCx.4
	C
 *C 
#s(^C C C CT #'#  
 sCx.4
	
 * 
#s(^     
455"*A " " " 65"J 	:;;FS F F F <;FR  QUVY[^V^Q_    8 
ABBMQA AA2A=Ec]A A A CBAH =>>    ?> 	#$$
 
8C= 
 
 
 %$
(00)1#0c]0 0 0 0f 	122NR?M ?M?M3?M>Fsm?M ?M ?M 32?MD 
7881 1s 1Xc] 1 1 1 981@   8C=  3  s        2G0c3h G0 G0 G0 G0T0$sCx. 0 0 0 00T#s(^    @ -5  (56  %39  ! 6,  7 2V
 
 &'8(  #2D,  S+A- IL7 tCH~s23 L L L^[ # [ T#s(^ [  [  [  [ |c3h HSM    2
d38n 
d3PS8n 
YabeYf 
 
 
 
/d4S>2 / / / /d 	  '( '( '( '( '( ! '(T 011 "@@ @@@@@@ c]@@ @@ @@ 21@@N % -/c4S>)* / / /%y~'' 
'                "& ' ' '!&'!D + + + +# 8C=    +Xc] +t + + + + " 
 c] 3S#X	   0 #4 44sm4 c]4 4 4 46Ec 6E# 6EVY 6E^b 6E 6E 6E 6Er 8C= DcN    < "M. M.M.M. c]M. 
#s(^	M. M. M. M.d "Qm QmQmc]Qm 
#s(^Qm Qm Qm Qmh=+S =+T =+ =+ =+ =+@Q+ Q+ Q+ Q+ Q+ Q+h=+3 =+4 =+ =+ =+ =+@c c    4( ( ( ( ( (,t, t, t, t, t, t,n 
455 ""D "D"D"D c]"D "D "D 65"DJ    i   
 
566
 "	[ [[
[ [ c]	[ [ [ 76[ 	?@@ "  c]   A@2 899 "2 222 c]2 2 2 :92(N3 N N N Nv" " " " " " " "
" " " " "I " " " - W     (3- 4SRUXCW \`adfiai\j     
%&&1,.@ 1, 1, 1, '&1,h 
 !!G    "!2 	$%%6 6# 6 6 6 &%6" !"", ,(3- , , , #",< 	 Xc]     <6(3- 6 6 6 6 	%&&  x}    '& 	788 " c]   98* 	.// "	 c] C= 	   0/D ()), ,c ,HSM , , , *),6" " " " "I " " " '((c     )(B 	,-- c HSM    .-     9   8O, O O O Od 
  : : : : ! : # 1+ 1+
1+1+ C=1+ }	1+
 SM1+ 1+ 1+ 1+r    I        I    EJ   s T hWZm    
S 
Xd3i%8 
 
 
 
C t QT    .
S#X 
4 
 
 
 
*#s(^ 
#s(^   B
49
 
   "5T$sCx.1 5 5 5 56 6%T	2B 6 6 6 6DcN S  cSVh    8C= S    83 8C=     #    "   	G G# G G G G s Xc]     	"##M Ms MXc] M M M $#M* *S *8C= *PS * * * *Z 	'((Z ZS Z8C= ZPS Z Z Z )(Z#< #< #< #< #< #< #<L 
N N N N N N N 	%&&    '& 6 # ] Xc]    D 	"##V V# V] VXc] V V V $#V  x}     
)**O O Ox} O O O +*O #      
*++P P# P P P P ,+P 3 #     
+,,Q Q3 Q# Q Q Q -,Q
 
# 
 
 
 
 
 %&&P P# P P P P '&P* *S *T * * * *4 
7SW 7S 7S 7S 7S~         Y      
 	  < < ! <B 
,--< <&D <s < < < .-<L" " " " "i " " "" " " " "	 " " "D-
D-
3S#X-.D- D- D- D-Nc3h DcN    c S#X 4S>    , 		 	HSM 	 	 	 	 
 4  4  4#  4  4  4  4F 	 $5     $ %&& #     '& 
())8 8 8hsm 8 8 8 *)8v 
())j8 j8 j8hsm j8 j8 j8 *)j8Z" " " " "y " " "
 	*++@DE E
E%E08E E E ,+E* 	I< I<HSM I< I< I< I<X" " " " "	 " " " 
$%%<; <;*; <;hsm <; <; <; &%<;~3 3       13R S S S    Y   
    I   
      
 !!    "!* 
  
} 
 
 
 ! 
 
&''* * ('*! ! ! ! !I ! ! ! T#s(^ s tTWY\T\~    &    
 !!  "!( /2} 2 2 2 2j "##	s 	 	 	 $#	    9    	'((DC D/C D D D )(D: 
B B# B B B  B 
A A A A A A(         	      s 3 4S>    ( 	 !!$ $ "!$4 
!""L*; L L L #"LB 677L LS L L L 87L2    9   
    )   
   * 	  
,$8 
, 
, 
, ! 
, 
,[ , , , ,H 
; ; ; 
#$$C C %$C! ! ! ! !I ! ! !
)t ) ) ) )YD Y Y Y Y
 
=    6 	#$$S    %$,	 	 	 	 	I 	 	 	 
;= ; ; ; ;"sTz c     
"##tCyy$u++J J
JJ J J $#JZ 	,8 ,8 ,8^        
5RJ 5R 5R 5R 5Rp       
 !J ! ! ! !H 	  > > ! >> 
&''F F ('F&" " " " ") " " "
Bx} Bc B B B B"3 S S     
#$$7 7"5 7 7 7 7 %$7&" " " " "I " " "
 
%&&9 9$9 9HSM 9 9 9 '&9$" " " " ") " " " 
"##IMB B
&
'B9A#B B B $#B& "".  	 	 	 	 	      > 	"##ER ER8C= ER ER ER $#ERP 	!""PT/Q /Q
/Q/Q-0/Q@H/Q /Q /Q #"/Qd 	"##= = =8C= = = = $#=@ 	  [ [S [ [ [ [ ! [F    I   8    I       	       )       y          
    )    c C 3    d38n    &3T$sCx.-A 3 3 3 3l	.s 	.t 	. 	. 	. 	.D D D D D D*d *c *# *$ * * * **,D ,4@Q;R ,WZ , , , ,^!D !S	 !c ! ! ! !H C C C /m m m m m` 	  2 2 ! 2( 
 !!R,? R R R "!R& 	-..5# 5 5 5 /.5 
.//2,s 2, 2, 2, 0/2,j !""B B= B B B #"B "##+ + + + $#+" 	$%%, , , , &%, 	$%%C /@    &% 	+,,HC H?W H H H -,H, 	%&&>c >9K > > > '&>& 
.//s :M    0/P 'y((  72HSM 72 72 72 72t *8C= * * * *B" " " " ") " " "  hsm    > 	
D 
D[ 
D8C= 
D 
D 
D 
D" " " " ") " " "" " " " " " " "
 
 
 
 	I I# I I I I  I$ -[    $ 	%7      	. . . . .  .b" " " " "I " " "
 	%&&( (s (- ((3- ( ( ( '&(V 	,--; ;3 ;# ; ; ; .-;|" " " " "I " " "   # T hsm     	23 	2S 	2 	2 	2 	2s D HSM V^_cVd    & 	,--HL8 8
8!#88@8 8 8 .-8v" " " " " " " " 	+,,BF+K +K
+K'+K2:3-+K +K +K -,+K\ 	.//EIA A
A*A5=c]A A A 0/A<" " " " "y " " "
 	)**3\ 3\ 3\,< 3\xPS} 3\ 3\ 3\ +*3\l" " " " "y " " "
 
122@D.V .V
.V%.V08.V .V .V 32.Vz 	)**
% 
%8C= 
% 
% 
% +*
% 
566G G(3- G G G 76GF" " " " "i " " "
 	I I(3- I I I I 	J J/ JHSM J J J J& $tCH~*>    B-4S>"-.24S>.B-	$sCx.- - - -`T#s(^ 4 d38n9M    8N Ns N(3- N N N Nb 	  H HC Hx} H H H ! Hm m m8C= m m m m` 	 !!I IS I I I I "!I. <5!! 	\\\\\\\\ $   	 %	 	 	 	 	, 	 	 	 	 			HHHHHHHH $   	 %	 	 	 	 	, 	 	 	 	 		 RZ677
  P O O O O O O O O O!!(	  S. S. S. S.l BJ9::  )KKKLLM+ M(3- M M M M6(*k (*d (* (* (* (*V{ x}    D	.; 	.4 	. 	. 	. 	.?; ?8C= ? ? ? ?
H{ Ht H H H H
s    Q% Q%hsmS.@(A Q% Q% Q% Q%h*K *D * * * * !!%!)-	j6 j6SMj6#j6 c]j6 "#	j6
 49hsmXd^34j6 j6 j6 j6h )Y-..#    8(x} ( ( ( (D !!%!)-	
 
SM
#
 c]
 "#	

 49hsmXd^34
 
 
 
>!) !) !) !) !) !)H_ _c _C _D _ _ _ _Ay AXc] A A A A) c d    D Xc]    d t    
;3 
;3 
; 
; 
; 
;* #'    !" EI 8J.AB I I I')) z1D    (GC G G G G
 x}    

9
9

9 	
9
 c]
9 	
9 
9 
9 
9$$|$ #s(^$ 
	$ $ $ $zz|z z
 z 
z z z zzs uXd38n-ExPS}-T'U    ( ~` `t ` ` ` `F zl.Y l.4 l. l. l. l.t y t    < zY 4    6 }(6	 (6d (6 (6 (6 (6V	!8C= 	!S 	! 	! 	! 	!DC D D D DNJ$7 J$ J$ J$ J$h }M~'<  NA  B  B{  NF  G  GzMqrr7Dmnn6Dmnn;Dmnn7Etuu	  c  E T\]abegjbj]kTl    2 dV- - 4S>    ) ) tCH~   
    ONN    
 :99 
 " Nd38n N$sCx.9Q N N N Nbt    , 	 !!0 0 "!0<    9    	  ,L , , , ! ,  	      	       )    	&; & & &  &*" "t "QT " " " "JeT e e e eR ,0 (4. / / /$ $ $$ $ $ $ $ 	!""$ $ #"$N 	())/ / *)/    i   Ac3h ADcN A A A ApT#s(^ p p p pf 	%&&]7 ] ] ] '&] 
011W <S    21(      
;<<G 3    =< 
<==W C    >= 
;<<	G 	3 	 	 	 =<	 677	w 	c 	 	 	 87	) ) ) ) )i ) ) )
 	*++ 7N    ,+"    I    
9::=' = =DY = = = ;:=, 	<==T# T# T T T >=TnoY oY oYf      N M M M M M   ) * * * 		#
- 
 
 
 
 
 
$QS $QT $Q $Q $Q $QN(8
(8(8/3(8FI(8	(8 (8 (8 (8X l l
l
l l 	l
 l l l l l l ls   :F HAG-,H-HHH-] -]87]8d!Bd< d<BeeBeR0DS SDS"S!DS"S&DS8 S8DTTDT