
    
Bj             
          U d 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 ddlmZ ddlmZ ddlmZ ddlmZmZmZmZmZ ddlmZmZ dd	l m!Z! dd
l"m#Z# dZ$dZ%dZ&dZ' e	jP                  d      Z)de*dede*fdZ+dZ,dedee-   fdZ.de-fdZ/de*de-de-fdZ0ddddedee-   dee-   de1fdZ2dZ3e4e*d f   e5d!<   d"e*d#ed$ee*ef   dee*ef   fd%Z6d&eeee*ef         defd'Z7dd(Z8d)e*de*fd*Z9d)e*de*fd+Z:de1fd,Z;d-ejx                  d.<    e8        ejz                  j}                  d e* ee?      j                  j                               dd/lAmBZB dd0lCmDZDmEZEmFZFmGZG  eB       ZHdd1lImJZJ dd2lKmLZL eHd3z  ZM eLeH ee?      j                         j                  d4   d3z  5       dd6ZP e	jP                  d7      ZQd8d9hZReHd:z  ZSeSj                         rz	 ddlUZV eWeSd;<      5 ZX eVj                  eX      xs i ZZddd       dd=l"m[Z[  e[eZ      ZZeZj                         D ]7  \  Z]Z^ e_e^e*e`e-e1f      se]ejx                  vs# e*e^      ejx                  e]<   9 eZj                  d>i       Zbebr e_ebec      ri d?d@dAdBdCdDdEdFdGdHdIdJdKdLdMdNdOdPdQdRdSdTdUdVdWdXdYdZd[d\d]d^d_d`dadbdcdddedfdgdhZdedj                         D ]  \  ZeZfeeebv sebee   Z^eedAk(  r e*e^      div r eedAk(  r$ e_e^e*      rejz                  j                  e^      Z^ e_e^ehecf      r  ej                  e^      ejx                  ef<   t e*e^      ejx                  ef<    eZj                  dji       ZjejrC e_ejec      r9dkdldmdndodpdqdrdsdodtdudvdwdodxZkekj                         D ]  \  ZlZmejj                  eli       Zn e_enec      s# e*enj                  dydz            j                         Zp e*enj                  d{dz            j                         Zq e*enj                  d|dz            j                         Zr e*enj                  d}dz            j                         Zseprepd~k7  repejx                  emdy   <   eqreqejx                  emd{   <   errerejx                  emd|   <   essesejx                  emd}   <    eZj                  di       Ztetr e_etec      rdetv r e*etd         ejx                  d<   detv r e*etd         ejx                  d<   detv r e*etd         ejx                  d<   detv r e*etd         ejx                  d<   detv r e*etd         ejx                  d<   detv r e*etd         ejx                  d<   eZj                  di       ZueurA e_euec      r8deuv r e*eud         ejx                  d<   deuv r e*eud         ejx                  d<   eZj                  ddz      Zvevr& e_eve*      revj                         ejx                  d<   eZj                  di       Zw e_ewec      r6ewj                  d      Zxex# e*ex      j                         ejx                  d<   	 ddlAmZ d e       v reZni j                  di       Z e_eec      rej                  d      r	 ed       	 ddl"mZ  e        	 ddl"mZ  e        d-ejx                  d<   d-ejx                  d<   ejx                  j                  dBdz      Zerediv r< ej                  d      xs  e* ej                               Zeejx                  dB<   ddlmZmZmZmZmZmZ ddlmZmZmZmZmZmZmZ ddlmZ ddlmZmZmZmZmZmZ ddlmZmZmZ ddlmZmZmZ  ejZ                  e~      Z e       ZdecfdZdecdz  fdZde*fdZde*dedz  fdZdZdZdZdZdZdZ eej                         ej                         ej                         ej                         ej                         ej                         h      Zdee*   de1fdZdede4e*dz  e*dz  f   fdZde*de*dz  fdZddde*fdZde1fdZdecfdZddecdz  de*fdĄZdeehe*      fdńZde*ddfdǄZdecddfdʄZddlZd˄ aeȐj                  e5d<   dd͜decde*de`de*fdфZdecde1fd҄ZdecdecdecfdՄZ G dք d׫      Zddej                  de`fdڄZddee   de1dee`   de1fd݄Zdބ Ze~dk(  r eҫ        yy# e$ r Y 	"w xY w# 1 sw Y   xY w# ez$ rIZ{ e|d e}e{      j                   de{ ej                          e|dej                         Y dZ{[{dZ{[{ww xY w# ez$ r"Z e|de ej                         Y dZ[dZ[ww xY w# ez$ r"Z e|de ej                         Y dZ[dZ[ww xY w# ez$ r"Z e|de ej                         Y dZ[/dZ[ww xY w)a<  
Gateway runner - entry point for messaging platform integrations.

This module provides:
- start_gateway(): Start all configured platform adapters
- GatewayRunner: Main class managing the gateway lifecycle

Usage:
    # Start the gateway
    python -m gateway.run
    
    # Or from CLI
    python cli.py --gateway
    N)OrderedDict)copy_contextPathdatetime)DictOptionalAnyListUnion)fetch_account_usagerender_account_usage_lines)t)cfg_get         @      >@      @z'(?<![\w:/])/([A-Za-z0-9][A-Za-z0-9_-]*)textplatformreturnc                     t        |d|      }|dk7  r| S ddlm dt        j                  t
           dt
        ffd}t        j                  ||       S )a%  Rewrite slash-command mentions to Telegram-valid command names.

    Telegram Bot API command names allow only lowercase letters, digits, and
    underscores.  Keep other platform renderings unchanged, but normalize
    Telegram help text so command mentions remain clickable/valid there.
    valuetelegramr   )_sanitize_telegram_namematchr   c                 b     | j                  d            }|rd| S | j                  d      S )N   /r   )group)r   	sanitizedr   s     0/home/ubuntu/.hermes/hermes-agent/gateway/run.py_replacez/_telegramize_command_mentions.<locals>._replaceQ   s0    +EKKN;	"+9+?Q?    )getattrhermes_cli.commandsr   reMatchstr_TELEGRAM_COMMAND_MENTION_REsub)r   r   platform_valuer$   r   s       @r#   _telegramize_command_mentionsr.   D   sV     Xw9N#;@ @# @ (++Hd;;r%     r   c                     | yt        | t              r| j                         S t        | t              ryt        | t        t
        f      r't        |       dkD  rt        |       dz  S t        |       S t        | t              r+| j                         }|sy	 t        |      }|dkD  r|dz  S |S y# t        $ r Y nw xY w	 t        j                  |j                  dd            j                         S # t        $ r Y yw xY w)a  Best-effort conversion of stored gateway timestamps to epoch seconds.

    Missing/unparseable timestamps return None so legacy transcripts keep the
    historical auto-continue behaviour instead of being silently dropped.
    Accepts: datetime, epoch seconds (int/float), epoch milliseconds (when
    the magnitude exceeds year-2286), ISO-8601 strings (with or without a
    trailing ``Z``), and numeric strings.
    Nl    d(	 g     @@Zz+00:00)
isinstancer   	timestampboolintfloatr*   strip
ValueErrorfromisoformatreplace)r   r   numerics      r#   _coerce_gateway_timestampr<   m   s     }%"  %%#u&(-e~(EuU|f$W5QV<W%{{}	DkG'.'?7V#LWL   			))$,,sH*EFPPRR 		s*   B. +B. .	B:9B:>2C1 1	C=<C=c                      t         j                  j                  d      } | | dk(  rt        t              S 	 t        |       S # t
        t        f$ r t        t              cY S w xY w)a  Return the configured auto-continue freshness window in seconds.

    Reads ``HERMES_AUTO_CONTINUE_FRESHNESS`` (bridged from
    ``config.yaml`` ``agent.gateway_auto_continue_freshness`` at gateway
    startup, same pattern as ``HERMES_AGENT_TIMEOUT``).  Falls back to the
    module default when unset or malformed.  Non-positive values disable
    the freshness gate (restores the pre-fix "always fresh" behaviour for
    users who want to opt out).
    HERMES_AUTO_CONTINUE_FRESHNESS )osenvirongetr6   %_AUTO_CONTINUE_FRESHNESS_SECS_DEFAULT	TypeErrorr8   )raws    r#   _auto_continue_freshness_windowrF      s]     **..9
:C
{cRi:;;<Szz" <:;;<s   
A A#"A#namedefaultc                     t         j                  j                  |       }||dk(  rt        |      S 	 t        |      S # t        t
        f$ r t        |      cY S w xY w)zRead an env var as float, falling back to ``default`` on typos/empty.

    A misconfigured env var (e.g. ``HERMES_AGENT_TIMEOUT=abc``) must not
    crash the gateway or an agent turn.  Unset/empty also falls back.
    r?   )r@   rA   rB   r6   rD   r8   )rG   rH   rE   s      r#   
_float_envrJ      sX     **..
C
{cRiW~Szz" W~s   
> AA)nowwindow_secsrK   rL   c                    |t        |      nt        t              }|dk  ryt        |       }|y|t        j                         n|}||z
  |k  S )a  Return True when an interruption marker is fresh enough to auto-continue.

    Unknown timestamps are treated as fresh for backward compatibility with
    legacy transcripts (pre-dating timestamp persistence) and with in-memory
    test scaffolding that constructs history entries without timestamps.

    A non-positive ``window_secs`` disables the gate (always fresh), which
    restores the pre-fix behaviour for users who opt out via config.
    r   T)r6   rC   r<   time)r   rK   rL   windowr3   currents         r#   _is_fresh_gateway_interruptionrQ      sb    " " 	k89 
 {)%0I [diikcGY&((r%   )	reasoningreasoning_contentreasoning_detailscodex_reasoning_itemscodex_message_itemsfinish_reason._ASSISTANT_REPLAY_FIELDSrolecontentmsgc                 |    | |d}| dk(  r1t         D ](  }||vr|j                  |      }|dk(  r|!|s$|||<   * |S )aW  Build a replay entry for a non-tool-calling message, preserving the
    assistant fields the agent's API builders rely on for multi-turn fidelity.

    Lifted out of the inline ``run_sync`` closure so the field whitelist can
    be unit-tested in isolation.  Mirrors the ``_ASSISTANT_REPLAY_FIELDS``
    contract above.

    Empty values: most fields are dropped when falsy (matching the original
    PR #2974 behaviour) since an empty list/string for those carries no
    information.  The exception is ``reasoning_content``: DeepSeek/Kimi
    thinking-mode replay treats an empty string as a meaningful sentinel
    that ``_copy_reasoning_content_for_api`` upgrades to a single space.
    Dropping it here would make the gateway send no ``reasoning_content`` at
    all on the next turn, which can cause HTTP 400 from strict thinking
    providers.
    rY   rZ   	assistantrS   )rX   rB   )rY   rZ   r[   entry_rkey_rvals         r#   _build_replay_entryrb      sd    " &*g>E{- 
	!ECGGENE++= E%L
	! Lr%   historyc                     | syt        |       D ]C  }t        |t              s|j                  d      }|r|dv r,|j                  d      }||c S  y y)uI  Return the ``timestamp`` of the last usable transcript row, if any.

    Skips metadata-only rows (``session_meta``, system injections) that are
    dropped before being handed to the agent.  Returns ``None`` when no
    usable row carries a timestamp — callers should treat that as "fresh"
    for backward compatibility.
    NrY   >   systemsession_metar3   )reversedr2   dictrB   )rc   r[   rY   tss       r#   _last_transcript_timestamprj     sh       #t$wwvt99WW[!>I  r%   c                     dt         j                  v ryddl} | j                         }|j                  |j
                  fD ]9  }|st         j                  j                  |      s&|t         j                  d<    y 	 ddl}|j                         t         j                  d<   y# t        $ r Y nw xY wdD ]6  }t         j                  j                  |      s#|t         j                  d<    y y)zBSet SSL_CERT_FILE if the system doesn't expose CA certs to Python.SSL_CERT_FILENr   )z"/etc/ssl/certs/ca-certificates.crtz /etc/pki/tls/certs/ca-bundle.crtz1/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pemz/etc/ssl/ca-bundle.pemz/etc/ssl/cert.pemz/etc/pki/tls/cert.pemz#/usr/local/etc/openssl@1.1/cert.pemz&/opt/homebrew/etc/openssl@1.1/cert.pem)r@   rA   sslget_default_verify_pathscafileopenssl_cafilepathexistscertifiwhereImportError)rm   paths	candidaters   s       r#   _ensure_ssl_certsrx   2  s    "**$ ((*EllE$8$89 		2*3BJJ'&-mmo

?# 	 	 77>>)$*3BJJ's   =%B# #	B/.B/platform_namec                 L    ddl m}  ||       }|r|S | j                          dS )a  Return the configured home-target env var for a platform.

    Consults built-in ``_HOME_TARGET_ENV_VARS`` first, then the plugin
    registry via ``cron.scheduler._resolve_home_env_var``, then falls back
    to ``<PLATFORM>_HOME_CHANNEL`` for unknown names.
    r   )_resolve_home_env_var_HOME_CHANNEL)cron.schedulerr{   upper)ry   r{   resolveds      r#   _home_target_env_varr   W  s0     5$]3H!!#$M22r%   c                     t        |        dS )zDReturn the optional thread/topic env var for a platform home target.
_THREAD_ID)r   )ry   s    r#   _home_thread_env_varr   f  s    "=12*==r%   c                  0    t         dz  j                         S )zIReturn True when a /restart completion marker is waiting to be delivered..restart_notify.json)_hermes_homerr    r%   r#   _restart_notification_pendingr   k  s    1199;;r%   1_HERMES_GATEWAY)get_hermes_home)atomic_json_writeatomic_yaml_writebase_url_host_matchesis_truthy_value)load_dotenv)load_hermes_dotenv.envr   hermes_homeproject_envc                     t        t        t        t              j	                         j
                  d   dz         t        dz  } | j                         sy	 ddl}t        | d      5 } |j                  |      xs i }ddd       dd	l
m}  |      }|j                  d
i       }t        |t              r%d|v r t!        |d         t"        j$                  d<   yyy# 1 sw Y   _xY w# t        $ r Y yw xY w)a~  Reload .env for fresh credentials without letting stale .env override config.

    Gateway processes are long-lived, so per-turn code reloads ~/.hermes/.env to
    pick up rotated API keys. config.yaml remains authoritative for agent budget
    settings such as agent.max_turns; otherwise a stale HERMES_MAX_ITERATIONS in
    .env can replace the startup bridge on later turns.
    r   r   r   config.yamlNr   utf-8encoding_expand_env_varsagent	max_turnsHERMES_MAX_ITERATIONS)r   r   r   __file__resolveparentsrr   yamlopen	safe_loadhermes_cli.configr   	ExceptionrB   r2   rh   r*   r@   rA   )config_path_yamlfcfgr   	agent_cfgs         r#   /_reload_runtime_env_preserving_config_authorityr     s      N**,44Q7&@
 .K+0 	+A!%//!$*C	+6s# $I)T"{i'?.1)K2H.I

*+ (@"	+ 	+  s*   C* )C C* C'#C* *	C65C6z=^(?P<host>.+):(?P<container>/[^:]+?)(?::(?P<options>[^:]+))?$z/outputz/outputsr   r   r   r   terminalbackendTERMINAL_ENVcwdTERMINAL_CWDtimeoutTERMINAL_TIMEOUTlifetime_secondsTERMINAL_LIFETIME_SECONDSdocker_imageTERMINAL_DOCKER_IMAGEdocker_forward_envTERMINAL_DOCKER_FORWARD_ENVsingularity_imageTERMINAL_SINGULARITY_IMAGEmodal_imageTERMINAL_MODAL_IMAGEdaytona_imageTERMINAL_DAYTONA_IMAGEvercel_runtimeTERMINAL_VERCEL_RUNTIMEssh_hostTERMINAL_SSH_HOSTssh_userTERMINAL_SSH_USERssh_portTERMINAL_SSH_PORTssh_keyTERMINAL_SSH_KEYcontainer_cpuTERMINAL_CONTAINER_CPUcontainer_memoryTERMINAL_CONTAINER_MEMORYcontainer_diskTERMINAL_CONTAINER_DISKTERMINAL_CONTAINER_PERSISTENTTERMINAL_DOCKER_VOLUMESTERMINAL_DOCKER_ENV&TERMINAL_DOCKER_MOUNT_CWD_TO_WORKSPACE TERMINAL_DOCKER_RUN_AS_HOST_USERTERMINAL_SANDBOX_DIRTERMINAL_PERSISTENT_SHELL)container_persistentdocker_volumes
docker_envdocker_mount_cwd_to_workspacedocker_run_as_host_usersandbox_dirpersistent_shell>   r   auto.	auxiliaryAUXILIARY_VISION_PROVIDERAUXILIARY_VISION_MODELAUXILIARY_VISION_BASE_URLAUXILIARY_VISION_API_KEY)providermodelbase_urlapi_keyAUXILIARY_WEB_EXTRACT_PROVIDERAUXILIARY_WEB_EXTRACT_MODELAUXILIARY_WEB_EXTRACT_BASE_URLAUXILIARY_WEB_EXTRACT_API_KEYAUXILIARY_APPROVAL_PROVIDERAUXILIARY_APPROVAL_MODELAUXILIARY_APPROVAL_BASE_URLAUXILIARY_APPROVAL_API_KEY)visionweb_extractapprovalr   r?   r   r   r   r   r   r   r   gateway_timeoutHERMES_AGENT_TIMEOUTgateway_timeout_warningHERMES_AGENT_TIMEOUT_WARNINGgateway_notify_intervalHERMES_AGENT_NOTIFY_INTERVALrestart_drain_timeoutHERMES_RESTART_DRAIN_TIMEOUTgateway_auto_continue_freshnessr>   displaybusy_input_modeHERMES_GATEWAY_BUSY_INPUT_MODEbusy_ack_enabledHERMES_GATEWAY_BUSY_ACK_ENABLEDtimezoneHERMES_TIMEZONEsecurityredact_secretsHERMES_REDACT_SECRETSu.     Warning: config.yaml → env bridge failed: : )filezz  Gateway will fall back to .env values, which may not match your current config.yaml. Run `hermes doctor` to investigate.)apply_ipv4_preference_cfgnetwork
force_ipv4Tforcez/  Warning: IPv4 preference application failed: )print_config_warningsz%  Warning: config validation failed: )warn_deprecated_cwd_env_varsz%  Warning: deprecation check failed: HERMES_QUIETHERMES_EXEC_ASKMESSAGING_CWD)Platform_BUILTIN_PLATFORM_VALUESGatewayConfigHomeChannelPlatformConfigload_gateway_config)SessionStoreSessionSourceSessionContextbuild_session_contextbuild_session_context_promptbuild_session_keyis_shared_multi_user_session)DeliveryRouter)BasePlatformAdapterEphemeralReplyMessageEventMessageType_reply_anchor_for_eventmerge_pending_message_event)%DEFAULT_GATEWAY_RESTART_DRAIN_TIMEOUT!GATEWAY_SERVICE_RESTART_EXIT_CODEparse_restart_drain_timeout)canonical_whatsapp_identifierexpand_whatsapp_aliasesnormalize_whatsapp_identifierc            
      $   ddl m} m} ddlm} 	  | t        j                  d            }|j                  d      |j                  d	      |j                  d
      |j                  d      |j                  d      t        |j                  d      xs g       |j                  d      dS # |$ r@}t        j                  d|       t               }||cY d}~S t         ||            |d}~wt        $ r}t         ||            |d}~ww xY w)zResolve provider credentials for gateway-created AIAgent instances.

    If the primary provider fails with an authentication error, attempt to
    resolve credentials using the fallback provider chain from config.yaml
    before giving up.
    r   )resolve_runtime_providerformat_runtime_provider_error)	AuthErrorHERMES_INFERENCE_PROVIDER)	requestedu4   Primary provider auth failed: %s — trying fallbackNr   r   r   api_modecommandargscredential_poolr   r   r   r5  r6  r7  r8  )hermes_cli.runtime_providerr0  r1  hermes_cli.authr2  r@   getenvloggerwarning_try_resolve_fallback_providerRuntimeErrorr   rB   list)r0  r1  r2  runtimeauth_exc	fb_configexcs          r#   _resolve_runtime_agent_kwargsrF    s     *H*ii ;<
 ;;y)KK
+KK
+KK
+;;y)W[[(.B/";;'89   R 	MxX24	 8BCQ H8=>CGHs/   B, ,D1#C,DC,,D8D

Dc                      ddl m}  	 ddl}t        dz  }|j	                         syt        |d      5 }|j                  |      xs i }ddd       j                  d      xs |j                  d      }|syt        |t              r|n|g}|D ]  }t        |t              s	  | |j                  d	      |j                  d
      |j                  d            }t        j                  d|j                  d	      |j                  d             |j                  d      |j                  d
      |j                  d	      |j                  d      |j                  d      t        |j                  d      xs g       |j                  d      |j                  d      dc S  	 y# 1 sw Y   `xY w# t        $ r1}	t        j                  d|j                  d	      |	       Y d}	~	Zd}	~	ww xY w# t        $ r Y yw xY w)zQAttempt to resolve credentials from the fallback_model/fallback_providers config.r   )r0  Nr   r   r   fallback_providersfallback_modelr   r   r   )r4  explicit_base_urlexplicit_api_keyz'Fallback provider resolved: %s model=%sr   r5  r6  r7  r8  )r   r   r   r5  r6  r7  r8  r   zFallback entry %s failed: %s)r:  r0  r   r   rr   r   r   rB   r2   rA  rh   r=  infor   debug)
r0  _ycfg_path_fr   fbfb_listr_   rB  fb_excs
             r#   r?  r?    s   D(-/ (W- 	),,r"(bC	)WW)*Gcgg6F.G"2t,"2$ 	EeT*2#ii
3&+ii
&;%*YYy%9
 =KK
+IIg&  '{{95 'J 7 'J 7 'J 7&{{95 V!4!:;'.{{3D'E"YYw/	 		: I	) 	)>  ;UYYz=RTZ[  sd   G1 G1 F'	.G1 8,G1 %C<F4!G1 $G1 'F1,G1 4	G.=&G)#G1 )G..G1 1	G=<G=c                    g }t        | dd      xs g }t        | dd      xs g }t        |      D ]  \  }}|t        |      k  r||   nd}|j                  d      st        | dd      t        j
                  k(  r|j                  d| d       `|j                  d	      r|j                  d
| d       |j                  d| d        dj                  |      S )ag  Build a text placeholder for media-only events so they aren't dropped.

    When a photo/document is queued during active processing and later
    dequeued, only .text is extracted.  If the event has no caption,
    the media would be silently lost.  This builds a placeholder that
    the vision enrichment pipeline will replace with a real description.
    
media_urlsNmedia_typesr?   image/message_typez[User sent an image: ]audio/z[User sent audio: z[User sent a file: 
)r&   	enumeratelen
startswithr&  PHOTOappendjoin)eventpartsrU  rV  iurlmtypes          r#   _build_media_placeholderrg    s     Ed39rJ%5;KJ' 73"#c+&6"6ABH%)MQ\QbQb)bLL0Q78h'LL-cU!45LL.se1567 99Ur%   session_keyc                 $    | j                  |      S )zConsume and return the full pending event for a session.

    Queued follow-ups must preserve their media metadata so they can re-enter
    the normal image/STT/document preprocessing path instead of being reduced
    to a placeholder string.
    )get_pending_message)adapterrh  s     r#   _dequeue_pending_eventrl    s     &&{33r%   zStop requestedzSession reset requestedz Execution timed out (inactivity)zSSE client disconnectedzGateway shutting downzGateway restartingmessagec                     | sydj                  t        |       j                         j                               j	                         }|t
        v S )z?Return True when an interrupt message is internal control flow.F )ra  r*   r7   splitlower_CONTROL_INTERRUPT_MESSAGES)rm  
normalizeds     r#   _is_control_interrupt_messagert  *  sA    #g,,,.4467==?J444r%   skill_mdc                    	 | j                  dd      }|j                  d      sy|j                  dd      }|dk  ryd	}|d| j	                         D ]}  }|j                         }|j                  d
      s%|j                  dd      d   j                         }t        |      dk\  r|d   |d   k(  r|d   dv r|dd }|j                         } n |sy|j                         j                  dd      j                  dd      }dd	l
}|j                  dd|      }|j                  dd|      j                  d      }|sd	|fS ||fS # t        $ r Y yw xY w)u  Derive the /command slug and declared frontmatter name from a SKILL.md.

    Matches the exact normalization used by
    :func:`agent.skill_commands.scan_skill_commands` so the slug here is the
    same string a user types after the leading ``/`` (e.g. a skill with
    frontmatter ``name: Stable Diffusion Image Generation`` resolves to
    ``stable-diffusion-image-generation`` — NOT the parent directory name,
    which is commonly shorter/different, e.g. ``stable-diffusion``).

    Using the directory name silently broke :func:`_check_unavailable_skill`
    for every skill whose directory name drifted from its frontmatter name
    (19 such skills on a standard install as of 2026-05), causing a generic
    "unknown command" response where a "disabled — enable with …" or
    "not installed — install with …" hint was expected.

    Returns ``(slug, declared_name)`` or ``(None, None)`` when the file
    can't be read or lacks a ``name:`` in its frontmatter.
    r   r:   )r   errorsNNz---z
---   r   Nzname::r      >   "'ro  -_z
[^a-z0-9-]r?   z-{2,})	read_textr   r^  find
splitlinesr7   rp  r]  rq  r:   r(   r,   )ru  rZ   enddeclared_namelinerE   slug_res           r#   _skill_slug_from_frontmatterr  2  sn   &$$gi$H e$
,,w
"C
Qw $M#))+ zz|??7#**S!$Q'--/C3x1}Q3r7!2s1v7K!BiIIKM  ((c2::3DD77="d+D778S$'--c2D]""5  s   E
 
	EEcommand_namec                    | j                         j                  dd      }	 ddlm} ddlm}  |       } |       D ]m  }|j                         s|j                  d      D ]F  }t        d |j                  D              r t        |      \  }}|r|s3||k(  s9||v s>d|  d	c c S  o dd
lm}	 t        t              j                         j                   j                   }
 |	|
dz        }|j                         r{|j                  d      D ]g  }t        |      \  }}|s||k(  s|j                   j#                  |      }t%        |j                        }ddj'                  |       }d|  d| dc S  y# t(        $ r Y yw xY w)u  Check if a command matches a known-but-inactive skill.

    Returns a helpful message if the skill exists but is disabled or only
    available as an optional install. Returns None if no match found.

    The slug for each on-disk skill is derived from its frontmatter ``name:``
    (via :func:`_skill_slug_from_frontmatter`), NOT from its containing
    directory name — because the two can differ (e.g. directory
    ``stable-diffusion`` + frontmatter ``Stable Diffusion Image Generation``
    yields slug ``stable-diffusion-image-generation``). Matching on
    directory name would miss that slug entirely and fall through to the
    generic "unknown command" path.
    r  r  r   )_get_disabled_skill_names)get_all_skills_dirszSKILL.mdc              3   $   K   | ]  }|d v  
 yw)>   .hub.github.archive.gitNr   .0parts     r#   	<genexpr>z+_check_unavailable_skill.<locals>.<genexpr>~  s     b4tFFb   The **zJ** skill is installed but disabled.
Enable it with: `hermes skills config`)get_optional_skills_dirzoptional-skillsz	official/r    zQ** skill is available but not installed.
Install it with: `hermes skills install `N)rq  r:   tools.skills_toolr  agent.skill_utilsr  rr   rglobanyrc  r  hermes_constantsr  r   r   r   parentrelative_torA  ra  r   )r  rs  r  r  disabled
skills_dirru  r  r  r  	repo_rootoptional_dir	_declaredrelrc  install_paths                   r#   _check_unavailable_skillr  d  s    ##%--c37J*?9,. ./ 	J$$&&,,Z8 bS[SaSabb&B8&L#m= :%-8*C  /A B	$ 	=N**,33::	.y;L/LM (..z: ">x"Hi:%"//55lCC OE%.sxx.?#@L  /CCO.PQS   s2   A7E: E: 	E: )A?E: )AE: 8E: :	FFr  c                 D    | t         j                  k(  rdS | j                  S )uN   Map a Platform enum to its config.yaml key (LOCAL→"cli", rest→enum value).cli)r  LOCALr   r   s    r#   _platform_config_keyr    s    .5BHNNBr%   c                  j    t               } t        | ddg       }t        |t              syd|v xs d|v S )zAReturn True when the standalone Teams pipeline plugin is enabled.pluginsenabledrH   Fteams_pipelinezteams-pipeline)_load_gateway_configr   r2   rA  )configr  s     r#   _teams_pipeline_plugin_enabledr    s=    !#FfiB?Ggt$w&E*:g*EEr%   c                  X   t         dz  } 	 ddlm}m} |  |       k(  r |       S 	 	 | j                         r1ddl}t        | dd      5 }|j                  |      xs i cddd       S 	 i S # t        $ r Y Pw xY w# 1 sw Y   i S xY w# t        $ r t        j                  d|        Y i S w xY w)	a  Load and parse ~/.hermes/config.yaml, returning {} on any error.

    Uses the module-level ``_hermes_home`` (so tests that monkeypatch it
    still see their fixture) and shares the mtime-keyed raw-yaml cache
    from ``hermes_cli.config.read_raw_config`` when the paths match.
    r   r   )get_config_pathread_raw_configNrr   r   z%Could not load gateway config from %s)r   r   r  r  r   rr   r   r   r   r=  rM  )r   r  r  r   r   s        r#   r  r    s     .K	F
 /++"$$ ,
Kk39 /Q~~a(.B/ /   I  / I  K<kJIKs@   A* "B A9	B *	A65A69B>B B B)(B)r  c                     | | n	t               }|j                  di       }t        |t              r|S t        |t              r(|j                  d      xs |j                  d      xs dS y)u   Read model from config.yaml — single source of truth.

    Without this, temporary AIAgent instances (e.g. /compress) fall
    back to the hardcoded default which fails when the active provider is
    openai-codex.
    r   rH   r?   )r  rB   r2   r*   rh   )r  r   	model_cfgs      r#   _resolve_gateway_modelr    sc     &&,@,BC$I)S!	It	$}}Y'G9==+AGRGr%   c                      ddl } | j                  d      }|r|gS 	 ddl}|j                  j	                  d      t
        j                  ddgS 	 y# t        $ r Y yw xY w)us  Resolve the Hermes update command as argv parts.

    Tries in order:
    1. ``shutil.which("hermes")`` — standard PATH lookup
    2. ``sys.executable -m hermes_cli.main`` — fallback when Hermes is running
       from a venv/module invocation and the ``hermes`` shim is not on PATH

    Returns argv parts ready for quoting/joining, or ``None`` if neither works.
    r   Nhermes
hermes_cliz-mzhermes_cli.main)shutilwhichimportlib.utilutil	find_specsys
executabler   )r  
hermes_bin	importlibs      r#   _resolve_hermes_binr    sp     h'J|>>##L1=NND*;<< >
   s   1A 	AAzdict | Nonec                     | j                  d      }t        |      dk\  r>|d   dk(  r6|d   dk(  r.|d   |d   |d	   d
}t        |      dkD  r|d   dv r|d   |d<   |S y)a@  Parse a session key into its component parts.

    Session keys follow the format
    ``agent:main:{platform}:{chat_type}:{chat_id}[:{extra}...]``.
    Returns a dict with ``platform``, ``chat_type``, ``chat_id``, and
    optionally ``thread_id`` keys, or None if the key doesn't match.

    The 6th element is only returned as ``thread_id`` for chat types where
    it is unambiguous (``dm`` and ``thread``).  For group/channel sessions
    the suffix may be a user_id (per-user isolation) rather than a
    thread_id, so we leave ``thread_id`` out to avoid mis-routing.
    rz     r   r   r   mainr{  ry     )r   	chat_typechat_id>   dmthread	thread_idN)rp  r]  )rh  rc  results      r#   _parse_session_keyr    s     c"E
5zQ58w.58v3EaqQx

 u:>eAh*::"'(F;r%   evtz
str | Nonec                 \   | j                  dd      }| j                  dd      }| j                  dd      }|dk(  rd| j                  dd	       d
S |dk(  rV| j                  dd      }| j                  dd	      }| j                  dd      }d| d| d| d| }|r	|d| dz  }|d
z  }|S y)zOFormat a watch pattern event from completion_queue into a [IMPORTANT:] message.type
completion
session_idunknownr6  watch_disabledz[IMPORTANT: rm  r?   rY  watch_matchpattern?output
suppressedr   [IMPORTANT: Background process z matched watch pattern "z".
Command: z
Matched output:
z
(z/ earlier matches were suppressed by rate limit)NrB   )r  evt_type_sid_cmd_pat_out_supr   s           r#   $_format_gateway_process_notificationr    s    wwv|,H77<+D779i(D##cggi45Q77= wwy#&wwx$ww|Q'-dV 4#f %v   $v' 	 c$NOODr%   c                       y Nr   r   r%   r#   <lambda>r  1      r%   _gateway_runner_refhistory_lenagent_resultresponser  c                   |r|S | j                  d      rb| j                  dd      }t        |      j                         t        fddD              xs dv xr |dkD  }|r	 yd	t        |      d
d  dS t	        | j                  dd      xs d      }|dkD  rH| j                  d      s7| j                  d      r$| j                  dd      }dt        |      d
d  dS 	 y|S )zNormalize empty/None agent responses into user-facing messages.

    Consolidates the existing ``failed`` handler and adds a catch-all for
    the case where the agent did work (api_calls > 0) but returned no text.
    Fix for #18765.
    failederrorunknown errorc              3   &   K   | ]  }|v  
 y wr  r   )r  p	error_strs     r#   r  z2_normalize_empty_agent_response.<locals>.<genexpr>F  s      !
 N!
   )contexttokenz	too largeztoo longexceedpayload4002   }   ⚠️ Session too large for the model's context window.
Use /compact to compress the conversation, or /reset to start fresh.zThe request failed: N,  z2
Try again or use /reset to start a fresh session.	api_callsr   interruptedpartialzprocessing incompleteu   ⚠️ Processing stopped:    z. Try again.u|   ⚠️ Processing completed but no response was generated. This may be a transient error — try sending your message again.)rB   r*   rq  r  r5   )r  r  r  error_detailis_context_failurer  errr  s          @r#   _normalize_empty_agent_responser  4  s(    !#''A%++-	  !
W!
 
 7 y 5[2%5 	 ) #3|#4Tc#:"; <@ @	

 L$$[!49:I1}\--m<I&""7,CDC0S$30@MMP	

 Or%   c                     t        | t              sy| j                  d      ry| j                  d      s"| j                  d      s| j                  d      ry| j                  d      du ryy)a  Return True only when a gateway turn really completed successfully.

    Restart recovery uses ``resume_pending`` as a durable marker for sessions
    interrupted during gateway drain.  A soft interrupt can still bubble out as
    a syntactically normal agent result with an empty final response; clearing
    the marker in that case loses the recovery signal and startup auto-resume
    has nothing to schedule.
    Fr  r  r  r  	completedT)r2   rh   rB   )r  s    r#   '_should_clear_resume_pending_after_turnr  b  sg     lD)&!\%5%5i%@LDTDTU\D]$-r%   current_resultfollowup_resultc                    t        |t              s|S t        | t              s|S | j                  d      }|j                  d      }t        |t              s|S t        |t              r||k  r|S t        |      }||d<   |S )ae  Carry the outer history offset through queued follow-up drains.

    ``_process_message_background()`` persists transcript rows only once, after the
    entire in-band queued-follow-up chain returns.  Each recursive ``_run_agent()``
    call advances ``history_offset`` to the history it received, so without
    correction the outermost persistence step sees only the *last* queued turn as
    "new" and silently drops earlier turns from the same drain chain.

    Preserve the earliest (outermost) history offset so the final transcript slice
    still includes every queued turn that ran during the chain.
    history_offset)r2   rh   rB   r5   )r  r  current_offsetfollowup_offsetmergeds        r#   (_preserve_queued_followup_history_offsetr  v  s     ot,nd+#''(89N%))*:;Onc*/3'O~,M/"F-FMr%   c                      e Zd ZU dZi Zeeef   ed<   dZ	eed<   e
Z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d<   dZeed<   dZeej.                     ed<   i Zeeeeef   f   ed<   i Zeeeeef   f   ed<   dKdee   fdZdLdZdLdZdefdZe dz  Z!de"dedefdZ#deeef   fdZ$dLdZ%dededdfdZ&dededdfd Z'dLd!Z(dLd"Z)defd#Z*defd$Z+defd%Z,e-defd&       Z.e-defd'       Z/e-dee   fd(       Z0e-dee   fd)       Z1d*e2defd+Z3d*e2defd,Z4 e5d-d.h      Z6d*e2defd/Z7d*e2defd0Z8d1Z9d*e2defd2Z:defd3Z;defd4Z<d*e2dee   fd5Z=d*e2ddfd6Z>dddd7d*ee2   d8ee   d9ee?   de@ee?f   fd:ZAd;ed<ed=e?de?fd>ZBd?eCddfd@ZDdAeddfdBZEdefdCZFdefdDZGdefdEZHdefdFZId8edGdHd?eddfdIZJd8ed?edJedH   dedH   fdKZKddLd8ed?edefdMZLeMdNedefdO       ZNd8ed?edefdPZOdQedefdRZPdMdSee   dTee   ddfdUZQddddVdedWee   dXee   dYee   ddf
dZZReMdeSeeef      fd[       ZTeMdefd\       ZUeMde?dz  fd]       ZVeMd^ede@eef   fd_       ZWddd`d*ee2   d8ee   de?dz  fdaZXd8edbee?   ddfdcZYeMdedz  fdd       ZZeMdefde       Z[eMdefdf       Z\eMdefdg       Z]eMdefdh       Z^eMde?fdi       Z_eMde`e?z  dz  fdj       Zadeeef   fdkZbd8edlecddfdmZddlecd8edefdnZedoede@eeef   ef   fdpZfdAeddfdqZgdLdrZhdseeef   ddfdtZidueddfdvZjdwZkdxZldyemddfdzZndefd{Zod8eddfd|ZpdLd}Zqddd~dededefdZr e5h d      ZsdefdZtdefdZudNdeddfdZvdeeef   ddfdZwdOdefdZxdefdZydPdeddfdZz	 dKde?dedee   ddfdZ{dKde?dee   ddfdZ|	 dKde?dededee   ddf
dZ}dLdZ~dLdZdddddedededdfdZdLdZde"dedeeC   fdZd*e2defdZdee"   defdZdeddfdZdlecdee   fdZdlecd*e2deSeeef      dee   fdZd8edeSe   fdZd8eddfdZd8efdZdedefdZdefdZdlecdeeef   fdZdlecdefdZd*e2dedee   fdZdlecdefdZdlecdefdZdlecdefdZdlecdefdZdlecdeeef   fdZdlecdeeef   fdZdlecdefdZdlecdefdZdlecdefdZdlecdee   fdZdlecdefdZdlecdefdZdlecdefdZdefdZdQdZdldHdefdZd*ededdfdZd*ededdfdZded*ededdfdńZdlecdefdƄZdlecdefdǄZeMdlecdee   fdȄ       ZdlecdefdɄZdlecdefdʄZdlecdefd˄Zdeddfd̄ZdedededefdЄZdededefdфZ	 dRdlecdede`dedef
dՄZdlecdeddfdׄZdedlecddfd؄ZdlecdefdلZdlecdefdڄZ	 dKded*ddedee   ddf
d߄ZdlecdefdZdlecdefdZdlecdeeef   fdZdlecdefdZdlecdefdZdlecdefdZd*e2de?fdZd*e2ddfdZd*e2ddfdZdedefdZd*e2dQededdfdZd*e2dQededdfdZdZd*e2defdZdefdZd*e2defdZÐdSdlecdedefdZd*e2defdZdlecdedefdZdlecdefdZdlecdefdZdlecdefdZdlecdefdZdlecdefdZdlecdee   fdZdlecdefdZdlecdefdZdlecdedededeed df   f
dZdlecdedededee   f
dZdeeef   fdZ	 dKdee   deeeef      fdZeMdlecdee   fd       ZdZdlecdee   fdZdlecdefdZ e5e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  e"j                  h      Zdlecdefd	Zdlecdefd
ZdLdZ	 	 	 dTdededoeddfdZdefdZdee@eeee   f      fdZdddeeme@eeee   f         deme@eeee   f      fdZdede`fdZde`ddfdZd ZdefdZdedeSe   defdZdedeSe   defdZde?fd Zd!ede?ddfd"Zd#e?ddfd$ZdwZd%Ze@ed&<   ed9e?dz  de?fd'       ZeM	 dKd<ed(e?d)e`d*ed+e?dz  defd,       Zd8ed<ed=e?de@fd-Z d8ed.edefd/Zdd0d8edee   defd1Zd8eddfd2Zd8edefd3Zd-d4d8edAedefd5Zd8ed6edefd7Zd?ed8ed6edz  ddfd8Zd9d:d8ed*e2d;ed<ed=eddfd>Zd8eddfd?Z	eMdued@eddfdA       Z
dueddfdBZdLdCZdefdDZdee   fdEZ	 	 	 dUdedFedeSeeef      d*ddQed8edee   dee   deeef   fdGZ	 	 	 	 	 dVdedFedeSeeef      d*e2dQed8edee   dHedee   dIee   deeef   fdJZy(W  GatewayRunnerz
    Main gateway controller.

    Manages the lifecycle of all platform adapters and routes
    messages to/from the agent.
    _running_agents_ts	interrupt_busy_input_mode_restart_drain_timeoutN
_exit_codeF	_draining_restart_requested_restart_task_started_restart_detached_restart_via_service
_stop_task_session_model_overrides_session_reasoning_overridesr  c                 
   |xs
 t               | _        i | _        | j                          t	        j
                  |       a| j                         | _        | j                         | _
        | j                         | _        | j                         | _        | j                         | _        | j#                         | _        | j'                         | _        | j+                         | _        | j/                         | _        ddlm t7        | j                  j8                  | j                  fd      | _        t=        | j                        | _        d| _         d | _!        tE        jF                         | _$        d| _%        d| _&        d | _'        d | _(        d| _)        d| _*        d| _+        d| _,        d| _-        d | _.        i | _/        i | _0        i | _1        i | _2        i | _3        i | _4        i | _5        tm               | _7        d| _8        dd l9}tm               | _:        |jw                         | _<        i | _=        i | _>        | j                         | _@        d | _A        d | _B        i | _C        i | _D        i | _E        dd lF}|j                  d      | _H        	 ddlImJ}  |d	       d | _L        	 dd
lMmN}  |       | _L        | j                  	 ddlQmR}  |       j                  d      xs i }|j                  dd      r~| j                  j                  t        |j                  dd            t        |j                  dd            t        |j                  dd            | j                  j8                         	 ddlQmR}  |       j                  d      xs i }
|
j                  dd      rvddlXmY}  |t        |
j                  dd            t        |
j                  dd            t        |
j                  dd            t        |
j                  dd                   ddlZm[}  |       | _\        dd l]m^}  |       | __        | j                         | _a        i | _b        t               | _d        y # t        $ r Y w xY w# t        $ r!}t        j                  d|       Y d }~d }~ww xY w# t        $ r!}	t        j                  d|	       Y d }	~	[d }	~	ww xY w# t        $ r }	t        j                  d|	       Y d }	~	d }	~	ww xY w)!Nr   process_registryc                 &    j                  |       S r  )has_active_for_session)keyr(  s    r#   r  z(GatewayRunner.__init__.<locals>.<lambda>  s    0@0W0WX[0\ r%   )has_active_processes_fnF   r   )ensure_installed)log_failures	SessionDBz&SQLite session store not available: %sload_configsessions
auto_pruneretention_daysZ   min_interval_hours   vacuum_after_pruneT)r6  r8  vacuumsessions_dirz%state.db auto-maintenance skipped: %scheckpoints)maybe_auto_prune_checkpoints   delete_orphansmax_total_size_mb  )r6  r8  r@  rA  z'checkpoint auto-maintenance skipped: %s)PairingStore)HookRegistry)er  r  adapters'_warn_if_docker_media_delivery_is_risky_weakrefrefr  _load_prefill_messages_prefill_messages_load_ephemeral_system_prompt_ephemeral_system_prompt_load_reasoning_config_reasoning_config_load_service_tier_service_tier_load_show_reasoning_show_reasoning_load_busy_input_moder  _load_restart_drain_timeoutr  _load_provider_routing_provider_routing_load_fallback_model_fallback_modeltools.process_registryr(  r  r<  session_storer"  delivery_router_running_gateway_loopasyncioEvent_shutdown_event_exit_cleanly_exit_with_failure_exit_reasonr  r  r  r   r!  r"  r#  _running_agentsr  _pending_messages_queued_events&_pending_native_image_paths_by_session_busy_ack_ts_session_run_generationr   _session_sources_session_sources_max	threading_agent_cacheLock_agent_cache_lockr$  r%  _active_profile_name_kanban_notifier_profile_teams_pipeline_runtime_teams_pipeline_runtime_error_pending_approvals_failed_platforms_update_prompt_pending	itertoolscount_slash_confirm_countertools.tirith_securityr.  r   _session_dbhermes_stater1  r=  r>  r   r3  rB   maybe_auto_prune_and_vacuumr5   r4   rM  tools.checkpoint_managerr>  gateway.pairingrC  pairing_storegateway.hooksrD  hooks_load_voice_modes_voice_mode_recent_voice_transcriptsset_background_tasks)selfr  
_threading
_itertoolsr.  r1  e_load_full_config	_sess_cfgrE  	_ckpt_cfgr>  rC  rD  r(  s                 @r#   __init__zGatewayRunner.__init__  s~   5 3 5=?446&ll40 "&!<!<!>(,(J(J(L%!%!<!<!>!446#88: $ : : <&*&F&F&H#!%!<!<!>#88: 	<)KK$$dkk$\
  .dkk:BF&}}""'+/)-"'%*"!&$)!26 024613 >@LN3.079$ DO=$'! 	'7B}!+!2 DF% HJ)(,(A(A(C%'+$<@* >@ BD 8:# 	'&0&6&6q&9#	>%0
  
	H.({D  'KN.044Z@FB	==u5$$@@'*9==9I2+N'O+.y}}=QSU/V+W#IMM2F$MN%)[[%=%=	 A 	IJ*,00?E2I}}\51Q,#&y}}5Eq'I#J'*9==9Mr+R'S#'	6F(M#N&))--8KS*Q&R	 	1)^ 	/!^
 ,0+A+A+C Z\& '*eM  		  	H NNCQGG	H4  KDcJJK"  	ILLBCHH	Is\   Q. &Q> B0R+ 6B(S .	Q;:Q;>	R(R##R(+	S4SS	T!S<<Tr   c                    t         j                  | j                  vryt               st        j                  d       y	 ddlm} 	  ||       }|rt        j                  d       y| j                  r!t        j                  d| j                         yy# t        $ r }t        j                  d|       Y d}~yd}~ww xY w# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)	u  Bind the Teams meeting pipeline runtime to Graph webhook ingress.

        No-op when the msgraph_webhook adapter isn't running or the
        teams_pipeline plugin isn't enabled — lets the gateway start cleanly
        whether or not the user has opted into the pipeline.
        Nz:Teams pipeline plugin is disabled; skipping runtime wiringr   )bind_gateway_runtimez(Teams pipeline runtime import failed: %sz(Teams pipeline runtime wiring failed: %sz7Teams pipeline runtime bound to msgraph webhook ingressz&Teams pipeline runtime unavailable: %s)r  MSGRAPH_WEBHOOKrE  r  r=  rM  plugins.teams_pipeline.runtimer  r   r>  rL  rs  )r  r  rE  bounds       r#   _wire_teams_pipeline_runtimez*GatewayRunner._wire_teams_pipeline_runtimen  s     ##4==8-/LLUV	K	(.E KKQR//NN822 0  	NNEsK	
  	NNEsK	s/   B C  	B=B88B= 	C)	C$$C)c                 &   t        j                  dd      j                         j                         dk7  ry| j                  j                         }|D cg c]6  }|t        j                  t        j                  t        j                  hvs5|8 }}|syt        j                  dd      j                         }g }|rO	 t        j                  |      }t        |t              r)|D cg c]  }t        |t              st        |        }}d	}|D ]7  }	t$        j'                  |	      }
|
s|
j)                  d
      }|t*        v s5d} n |ryt         j-                  d       yc c}w c c}w # t        $ r t         j#                  dd       Y w xY w)a  Warn when Docker-backed gateways lack an explicit export mount.

        MEDIA delivery happens in the gateway process, so paths emitted by the model
        must be readable from the host. A plain container-local path like
        `/workspace/report.txt` or `/output/report.txt` often exists only inside
        Docker, so users commonly need a dedicated export mount such as
        `host-dir:/output`.
        r   r?   dockerNr   zACould not parse TERMINAL_DOCKER_VOLUMES for gateway media warningTexc_infoF	containeraF  Docker backend is enabled for the messaging gateway but no explicit host-visible output mount (for example '/home/user/.hermes/cache/documents:/output') is configured. This is fine if the model already emits host-visible paths, but MEDIA file delivery can fail for container-local paths like '/workspace/...' or '/output/...'.)r@   r<  r7   rq  r  get_connected_platformsr  r  
API_SERVERWEBHOOKjsonloadsr2   rA  r*   r   r=  rM  _DOCKER_VOLUME_SPEC_REr   r!   $_DOCKER_MEDIA_OUTPUT_CONTAINER_PATHSr>  )r  	connectedr  messaging_platformsraw_volumesvolumesparsedvhas_explicit_output_mountspecr   container_paths               r#   rF  z5GatewayRunner._warn_if_docker_media_delivery_is_risky  sn    99^R(..0668HDKK779	*3xQqQYQdQdfnfvfv@w7wqxx"ii 92>DDFqK0fd+/5L!As9Ks1vLGL %*! 	D*006E"[[5N!EE,0)	 %P	
7 y M q`kopqs6   6E#E#>)E- 'E(=E(
E- (E- - FFc                 B    	 ddl m}  |d      duS # t        $ r Y yw xY w)z3Check if the hermes-agent-setup skill is installed.r   )_find_skillzhermes-agent-setupNF)tools.skill_manager_toolr  r   )r  r  s     r#   _has_setup_skillzGatewayRunner._has_setup_skill  s-    	<34D@@ 		s    	zgateway_voice_mode.jsonr   r  c                 $    |j                    d| S )z6Return a platform-namespaced key for voice mode state.rz  r   )r  r   r  s      r#   
_voice_keyzGatewayRunner._voice_key  s    ..!7),,r%   c                 n   	 t        j                  | j                  j                               }t        |t              si S h d}i }|j                         D ]5  \  }}||vrt        |      }d|vrt        j                  d|       1|||<   7 |S # t        t         j
                  t        f$ r i cY S w xY w)N>   alloff
voice_onlyrz  z}Skipping legacy unprefixed voice mode key %r during migration. Re-enable voice mode on that chat to rebuild the prefixed key.)r  r  _VOICE_MODE_PATHr  FileNotFoundErrorJSONDecodeErrorOSErrorr2   rh   itemsr*   r=  r>  )r  datavalid_modesr  r  moder+  s          r#   r  zGatewayRunner._load_voice_modes  s    	::d33==?@D $%I2!ZZ\ 	MGT;&g,C#~U
 F3K	 + "4#7#7A 	I	s   -B  B43B4c                     	 | j                   j                  j                  dd       | j                   j                  t	        j
                  | j                  d             y # t        $ r }t        j                  d|       Y d }~y d }~ww xY w)NT)r   exist_okr{  indentzFailed to save voice modes: %s)
r  r  mkdir
write_textr  dumpsr  r  r=  r>  )r  r  s     r#   _save_voice_modeszGatewayRunner._save_voice_modes  su    	@!!((..td.K!!,,

4++A6  	@NN;Q??	@s   A!A$ $	B-BBr  c                     t        |dd      }t        |t              sy|rA|j                  |       t        |dd      }t        |t              r|j	                  |       yy|j	                  |       y)zBUpdate an adapter's in-memory auto-TTS suppression set if present._auto_tts_disabled_chatsN_auto_tts_enabled_chatsr&   r2   r  adddiscard)r  rk  r  r  disabled_chatsenabled_chatss         r#   _set_adapter_auto_tts_disabledz,GatewayRunner._set_adapter_auto_tts_disabled  sk     *DdK.#.w'#G-FMM--%%g. . ""7+r%   r  c                     t        |dd      }t        |t              sy|rA|j                  |       t        |dd      }t        |t              r|j	                  |       yy|j	                  |       y)zUpdate an adapter's per-chat auto-TTS opt-in set if present.

        Used for ``/voice on``/``/voice tts`` where the user explicitly wants
        auto-TTS even when ``voice.auto_tts`` is False globally.
        r  Nr  r  )r  rk  r  r  r  r  s         r#   _set_adapter_auto_tts_enabledz+GatewayRunner._set_adapter_auto_tts_enabled  sm      )BDI--g&$W.H$ON.#.&&w/ / !!'*r%   c                    t        |dd      }t        |t              syt        |dd      }t        |dd      }t        |t              st        |t              sy	 ddlm}  |       }t        |j                  d      xs i j                  dd	            }t        |d
      r||_
        |j                   dt        |t              rB|j                          |j                  fd| j                  j                         D               t        |t              rC|j                          |j                  fd| j                  j                         D               yy# t        $ r d	}Y w xY w)ae  Restore persisted /voice state into a live platform adapter.

        Populates three fields from config + ``self._voice_mode``:
          - ``_auto_tts_default``: global default from ``voice.auto_tts``
          - ``_auto_tts_enabled_chats``: chats with mode ``voice_only``/``all``
          - ``_auto_tts_disabled_chats``: chats with mode ``off``
        r   Nr  r  r   r2  voiceauto_ttsF_auto_tts_defaultrz  c              3   l   K   | ]+  \  }}|d k(  r!|j                        r|t              d  - yw)r  Nr^  r]  r  r+  r  prefixs      r#   r  zBGatewayRunner._sync_voice_mode_state_to_adapter.<locals>.<genexpr>4  s:      "&/c45=S^^F%; CKL!"s   14c              3   j   K   | ]*  \  }}|d v r!|j                        r|t              d  , yw)>   r  r  Nr  r  s      r#   r  zBGatewayRunner._sync_voice_mode_state_to_adapter.<locals>.<genexpr>:  s<      !&/c400S^^F5K CKL!!s   03)r&   r2   r  r  r   r3  r4   rB   r   hasattrr  r   clearupdater  r  )	r  rk  r   r  r  r  	_full_cfgr  r  s	           @r#   !_sync_voice_mode_state_to_adapterz/GatewayRunner._sync_voice_mode_state_to_adapter  sR    7J5(H- *DdK)BDI.#.z-QT7U	&J)+I $w'-222:uE!
 7/0(9G%NN#1%nc*  "!! "373C3C3I3I3K"  mS)!   !373C3C3I3I3K!  *  	& %	&s   ;E E-,E-c                   K   | j                         }	 |dk  r|j                          d{    yt        j                  |j                         |       d{    y7 37 # t        j                  $ r( t
        j                  d|||j                  nd       Y yt        $ r/}t
        j                  d||j                  nd|       Y d}~yd}~ww xY ww)u  Call adapter.disconnect() defensively, swallowing any error.

        Used when adapter.connect() failed or raised — the adapter may
        have allocated partial resources (aiohttp.ClientSession, poll
        tasks, child subprocesses) that would otherwise leak and surface
        as "Unclosed client session" warnings at process exit.

        Must tolerate partial-init state and never raise, since callers
        use it inside error-handling blocks.
        r   Nr   zITimed out after %.1fs while disconnecting %s adapter; continuing shutdownrk  z7Defensive %s disconnect after failed connect raised: %s)
 _adapter_disconnect_timeout_secs
disconnectr^  wait_forTimeoutErrorr=  r>  r   r   rM  )r  rk  r   r   r  s        r#   _safe_adapter_disconnectz&GatewayRunner._safe_adapter_disconnect?  s      779	!|((***&&w'9'9';WMMM +M## 	NN["*"6I
  	LLI"*"6I 	sf   CA$ A A$ C(A$ A"A$ C A$ "A$ $8CCC&%CCCCc                     t        j                  dd      j                         }|r	 t        |      }t	        d|      S t        S # t
        $ r t        j                  d|       Y t        S w xY w)z?Return the per-adapter disconnect timeout used during shutdown.)HERMES_GATEWAY_ADAPTER_DISCONNECT_TIMEOUTr?           z=Ignoring invalid HERMES_GATEWAY_ADAPTER_DISCONNECT_TIMEOUT=%r)	r@   r<  r7   r6   maxr8   r=  r>  (_ADAPTER_DISCONNECT_TIMEOUT_SECS_DEFAULTr  rE   r   s      r#   r  z.GatewayRunner._adapter_disconnect_timeout_secs]  sm    iiCRHNNP)* 3((77  S 87   A A,+A,c                     t        j                  dd      j                         }|r	 t        |      }t	        d|      S t        S # t
        $ r t        j                  d|       Y t        S w xY w)zBReturn the per-platform connect timeout used during startup/retry.'HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUTr?   r  z;Ignoring invalid HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT=%r)	r@   r<  r7   r6   r  r8   r=  r>  &_PLATFORM_CONNECT_TIMEOUT_SECS_DEFAULTr  s      r#   _platform_connect_timeout_secsz,GatewayRunner._platform_connect_timeout_secsl  sm    iiA2FLLN)* 3((55  Q 65r  c                 8  K   | j                         }|dk  r|j                          d{   S 	 t        j                  |j                         |       d{   S 7 27 # t        j                  $ r"}t	        |j
                   d|dd      |d}~ww xY ww)zAConnect an adapter without allowing one platform to block others.r   Nr  z connect timed out after gs)r  connectr^  r  r  r   )r  rk  r   r   rE  s        r#   _connect_adapter_with_timeoutz+GatewayRunner._connect_adapter_with_timeout{  s     557a< ***	 ))'//*;WMMM +M## 	>>"";GA;aH	sD   )BAB(A" A A" B A" "B5BBBc                     | j                   S r  )ra  r  s    r#   should_exit_cleanlyz!GatewayRunner.should_exit_cleanly  s    !!!r%   c                     | j                   S r  )rb  r  s    r#   should_exit_with_failurez&GatewayRunner.should_exit_with_failure  s    &&&r%   c                     | j                   S r  )rc  r  s    r#   exit_reasonzGatewayRunner.exit_reason  s       r%   c                     | j                   S r  )r  r  s    r#   	exit_codezGatewayRunner.exit_code  s    r%   sourcec           	         t        | d      r<| j                  0	 | j                  j                  |      }t        |t              r|r|S t        | dd      }t        |t        |dd      t        |dd            S # t
        $ r Y <w xY w)	zUResolve the current session key for a source, honoring gateway config when available.rZ  Nr  group_sessions_per_userTthread_sessions_per_userFr  r  )r  rZ  _generate_session_keyr2   r*   r   r&   r   )r  r  rh  r  s       r#   _session_key_for_sourcez%GatewayRunner._session_key_for_source  s    4)d.@.@.L"00FFvNk3/K&& x. $+F4Mt$T%,V5OQV%W
 	
  s   .A: :	BBc                 D   |j                   t        j                  k7  s|j                  dk7  ryt	        | dd      }|y	 |j                  t        |j                        t        |j                              }|du S # t        $ r t        j                  dd       Y yw xY w)	z>Return whether Telegram DM topic mode is active for this chat.r  Fr{  Nr  user_idz(Failed to read Telegram topic mode stateTr  )r   r  TELEGRAMr  r&   is_telegram_topic_mode_enabledr*   r  r  r   r=  rM  )r  r  
session_dbrE   s       r#   _telegram_topic_mode_enabledz*GatewayRunner._telegram_topic_mode_enabled  s    ??h///63C3Ct3KT=$7
	;;FNN+FNN+ < C d{  	LLCdLS	s   9A< < BBr?   r   c                     |j                   t        j                  k7  s|j                  dk7  ry| j	                  |      syt        |j                  xs d      }|| j                  v S )zUTrue for the main Telegram DM (or General topic) when topic mode has made it a lobby.r  Fr?   r   r  r	  r  r  r*   r  _TELEGRAM_GENERAL_TOPIC_IDSr  r  tids      r#   _is_telegram_topic_root_lobbyz+GatewayRunner._is_telegram_topic_root_lobby  s[    ??h///63C3Ct3K008&""(b)d6666r%   c                     |j                   t        j                  k7  s|j                  dk7  ry| j	                  |      syt        |j                  xs d      }|r|| j                  v ryy)z9True for a user-created Telegram private-chat topic lane.r  Fr?   Tr  r  s      r#   _is_telegram_topic_lanez%GatewayRunner._is_telegram_topic_lane  s`    ??h///63C3Ct3K008&""(b)cT===r%   r   c                    t        | d      si | _        t        |j                  xs d      }|syddl}|j                         }| j                  j                  |d      }||z
  | j                  k  ry|| j                  |<   y)a+  Rate-limit root-DM lobby reminders to one message per cooldown window.

        A user who forgets multi-session mode is enabled and types several
        prompts in the root DM would otherwise get a reminder for every
        message. Cap it so the first one lands and the rest stay quiet.
        _telegram_lobby_reminder_tsr?   Tr   Nr  F)r  r  r*   r  rN   	monotonicrB   #_TELEGRAM_LOBBY_REMINDER_COOLDOWN_Sr  r  r  _timerK   lasts         r#   $_should_send_telegram_lobby_reminderz2GatewayRunner._should_send_telegram_lobby_reminder  s     t:;/1D,fnn*+oo//33GSA:@@@47((1r%   c                      	 y)Na  This main chat is reserved for system commands.

To start a new Hermes chat, open the All Messages topic at the top of this bot interface and send any message there. Telegram will create a new topic for that message; each topic works as an independent Hermes session.r   r  s    r#   "_telegram_topic_root_lobby_messagez0GatewayRunner._telegram_topic_root_lobby_message  s    *	
r%   c                      	 y)Na0  To start a new parallel Hermes chat, open the All Messages topic at the top of this bot interface and send any message there. Telegram will create a new topic for it.

Each topic is an independent Hermes session. Use /new inside an existing topic only if you want to replace that topic's current session.r   r  s    r#    _telegram_topic_root_new_messagez.GatewayRunner._telegram_topic_root_new_message  s    W	
r%   c                 *    | j                  |      sy 	 y)NzStarted a new Hermes session in this topic.

Tip: for parallel work, open All Messages and send a message there to create a separate topic instead of using /new here. /new replaces the session attached to the current topic.)r  )r  r  s     r#   _telegram_topic_new_headerz(GatewayRunner._telegram_topic_new_header  s    ++F39	
r%   c                 "   t        | dd      }||j                  r|j                  sy|j                  t	        |j                        t	        |j                        t	        |j
                  xs d      |j                  |j                         y)zEPersist the Telegram topic -> Hermes session binding for topic lanes.r{  Nr?   )r  r  r  rh  r  )r&   r  r  bind_telegram_topicr*   r  rh  r  )r  r  session_entryr  s       r#   _record_telegram_topic_bindingz,GatewayRunner._record_telegram_topic_binding  sy     T=$7
V^^6;K;K&&'&**+,"-%11$// 	' 	
r%   r  rh  user_configrh  r(  c          	         |}|s|	 | j                  |      }t        |      }|r| j                  j	                  |      nd}|r|j	                  d|      }|j	                  d      |j	                  d      |j	                  d      |j	                  d      d}|j	                  d      r0t
        j                  d|xs d	|||j	                  d             ||fS t
        j                  d
|xs d	||       nNt
        j                  d|xs d	|| j                  r&t        | j                  j                               dd nd       t               }	|	j                  dd      }
|
rt
        j                  d||
       |
}|r|r| j                  |||	      \  }}	|sC|	j	                  d      r2	 ddlm}  ||	d         }|rt
        j                  d||	d          ||	fS ||	fS # t        $ r d}Y w xY w# t        $ r Y ||	fS w xY w)a$  Resolve model/runtime for a session, honoring session-scoped /model overrides.

        If the session override already contains a complete provider bundle
        (provider/api_key/base_url/api_mode), prefer it directly instead of
        resolving fresh global runtime state first.
        Nr   r   r   r   r5  r   r   r   r5  zZSession model override (fast): session=%s config_model=%s -> override_model=%s provider=%sr?   z[Session model override (no api_key, fallback): session=%s config_model=%s override_model=%szFNo session model override: session=%s config_model=%s override_keys=%sr  z[]z;Runtime provider supplied explicit model override: %s -> %sr   )get_default_model_for_provideru8   No model configured — defaulting to %s for provider %s)r  r   r  r$  rB   r=  rM  rA  keysrF  poprL  _apply_session_model_overridehermes_cli.modelsr+  )r  r  rh  r(  resolved_session_keyr   overrideoverride_modeloverride_runtimeruntime_kwargsruntime_modelr+  s               r#   _resolve_session_agent_runtimez,GatewayRunner._resolve_session_agent_runtime  s3     +#(:,'+'C'CF'K$ '{3Nb400445IJhl%\\'59N$LL4#<<	2$LL4$LL4	   ##I.p(.B~$((4
 &'777 LLm$*E>
 LLX$*EBFB_B_T22779:2A>ei 78&**7D9KKM
 "E,$($F$F$e^%!E> ++J7	L6~j7QRKKR~j9 n$$un$$  ,'+$,x  n$$s#   G  +-G2  G/.G/2	H Huser_messager   r4  c                    ddl m} |j                  d      |j                  d      |j                  d      |j                  d      |j                  d      t        |j                  d      xs g       |j                  d	      d
}||||d   |d   |d   |d   t	        |d         fd}t        | dd      }|si |d<   |S 	  ||d         }|xs i |d<   |S # t        $ r d}Y w xY w)a2  Build the effective model/runtime config for a single turn.

        Always uses the session's primary model/provider.  If `/fast` is
        enabled and the model supports Priority Processing / Anthropic fast
        mode, attach `request_overrides` so the API call is marked
        accordingly.
        r   )resolve_fast_mode_overridesr   r   r   r5  r6  r7  r8  r9  )r   rB  	signaturerP  Nrequest_overridesr   )r/  r9  rB   rA  tupler&   r   )	r  r7  r   r4  r9  rB  routeservice_tier	overridess	            r#   _resolve_turn_agent_configz(GatewayRunner._resolve_turn_agent_configi  s#    	B &)))4&**:6&**:6&**:6%)))4++F39r:-112CD
 
#
#
#	"gfo&
 t_d;)+E%&L	3E'NCI &/_"!"  	I	s   C C&%C&rk  c                 n  K   t         j                  d|j                  j                  |j                  xs d|j
                  xs d       | j                  |j                  j                  |j                  rdnd|j                  |j
                         | j                  j                  |j                        }||u rZ	 |j                          d{    | j                  j                  |j                  d       | j                  | j                  _        |j                  r| j                  j                  j                  |j                        }|rt|j                  | j                  vr\|dt!        j"                         d	z   d
| j                  |j                  <   t         j%                  d|j                  j                         | j                  sx| j                  sl|j
                  xs d| _        |j                  rd| _        t         j                  d       nt         j                  d       | j+                          d{    y| j                  s| j                  r|j                  rJ|j
                  xs d| _        d| _        t         j                  d       | j+                          d{    yt         j-                  dt/        | j                               yyy7 # | j                  j                  |j                  d       | j                  | j                  _        w xY w7 7 |w)zReact to an adapter failure after startup.

        If the error is retryable (e.g. network blip, DNS failure), queue the
        platform for background reconnection instead of giving up permanently.
        zFatal %s adapter error (%s): %sr  r  retryingfatalplatform_state
error_codeerror_messageNr      r  attempts
next_retryz%%s queued for background reconnectionz#All messaging adapters disconnectedTzSNo connected messaging platforms remain. Shutting down gateway for service restart.zGNo connected messaging platforms remain. Shutting down gateway cleanly.z4All messaging platforms failed with retryable errorszuAll messaging platforms failed with retryable errors. Shutting down gateway for service restart (systemd will retry).zSNo connected messaging platforms remain, but %d platform(s) queued for reconnection)r=  r  r   r   fatal_error_codefatal_error_message_update_platform_runtime_statusfatal_error_retryablerE  rB   r  r-  r[  r  	platformsru  rN   r  rL  rc  rb  stopr>  r]  )r  rk  existingplatform_configs       r#   _handle_adapter_fatal_errorz)GatewayRunner._handle_adapter_fatal_error  s     	-""$$1	'':?		
 	,,"")0)F)F:G//!55	 	- 	
 ==$$W%5%56w>((***!!'"2"2D904$$- (("kk33778H8HIO7#3#34;Q;Q#Q- !"&.."2R"7<&&w'7'78
 ;$$**
 }}T%;%; ' ; ; d?dD,,*.'rsfg))+4#9#9 ,,$+$?$?$yCy!*.'V iik!!i../ $:7 +!!'"2"2D904$$-.  "sQ   B=L5 K+ K(K+ E0L5L1	A-L56L371L5(K+ +AL..L53L5reasonc                 T    d| _         || _        | j                  j                          y NT)ra  rc  r`  r  )r  rU  s     r#   _request_clean_exitz!GatewayRunner._request_clean_exit  s$    !"  "r%   c                 ,    t        | j                        S r  )r]  rd  r  s    r#   _running_agent_countz"GatewayRunner._running_agent_count  s    4''((r%   c                 "    | j                   rdS dS )Nrestartshutdownr  r  s    r#   _status_action_labelz"GatewayRunner._status_action_label  s     33yCCr%   c                 "    | j                   rdS dS )N
restartingshutting downr^  r  s    r#   _status_action_gerundz#GatewayRunner._status_action_gerund  s    #66|KOKr%   c                 :    | j                   xr | j                  dv S )N>   queuesteer)r  r  r  s    r#   _queue_during_drain_enabledz)GatewayRunner._queue_during_drain_enabled  s!     &&V4+@+@DV+VVr%   queued_eventr%  c                     |yt        |dd      }|yt        | dd      }|	i }|| _        ||v r"|j                  |g       j                  |       y|||<   y)z6Append a /queue event to the FIFO chain for a session.Nre  rf  )r&   rf  
setdefaultr`  )r  rh  rh  rk  pending_slotqueued_eventss         r#   _enqueue_fifozGatewayRunner._enqueue_fifo  sq    ?w(;TB&6= M"/D,&$$["5<<\J(4L%r%   pending_eventc                 &   t        | dd      }|s|S |j                  |      }|s|S |j                  d      }|s|j                  |d       ||S |t        |d      r||j                  |<   |S |j                  |g       j                  d|       |S )aY  Promote the next overflow item after the slot was drained.

        Called at the drain site after _dequeue_pending_event consumed
        (or failed to consume) the slot.  If there's an overflow item:
          - When pending_event is None (slot was empty), return the
            overflow head as the new pending_event.
          - When pending_event already exists (slot was populated by an
            interrupt follow-up or similar), stage the overflow head in
            the slot so the NEXT recursion picks it up.
        Returns the (possibly updated) pending_event for drain to use.
        rf  Nr   re  )r&   rB   r-  r  re  rj  insert)r  rh  rk  rn  rl  overflownext_queueds          r#   _promote_queued_eventz#GatewayRunner._promote_queued_event  s    "  &6=   $$[1  ll1ok40 774G#H5@G%%k2  $$["5<<QLr%   rk  c                    t        | dd      xs i }t        |j                  |g             }||t        |di       v r|dz  }|S )u=   Total pending /queue items for a session — slot + overflow.rf  Nre  r   )r&   r]  rB   )r  rh  rk  rl  depths        r#   _queue_depthzGatewayRunner._queue_depth*  sS    &6=CM%%k267;''CVXZ2[#[QJEr%   event_or_textc                 X    t        | d|       xs d}t        |      j                  d      S )zReturn True for synthetic /goal continuation turns.

        Goal continuations are normal queued user-role events, so pause/clear
        must distinguish them from real user /queue messages before removing or
        suppressing them.
        r   r?   z,[Continuing toward your standing goal]
Goal:)r&   r*   r^  )rx  r   s     r#   _is_goal_continuation_eventz)GatewayRunner._is_goal_continuation_event2  s-     }fm<B4y##$STTr%   c                    d}|t        |dd      nd}t        |t              r9|j                  |      }| j	                  |      r|j                  |d       |dz  }t        | dd      }t        |t              rc|j                  |      xs g }|rLg }|D ]*  }	| j	                  |	      r|dz  }|j                  |	       , |r|||<   |S |j                  |d       |S )a  Remove queued synthetic /goal continuations for one session.

        User-issued /goal pause/clear can race with a continuation already
        queued by the judge.  Remove only synthetic goal continuations while
        preserving normal /queue and user follow-up events.
        r   Nre  r   rf  )r&   r2   rh   rB   rz  r-  r`  )
r  rh  rk  removedrk  rn  rl  rq  keptrh  s
             r#   !_clear_goal_pending_continuationsz/GatewayRunner._clear_goal_pending_continuations=  s     FMFYww(;TB_clD)(,,[9M//>  d31&6=mT*$((5;H$, 2L77E1L1	2
 15M+.  "%%k48r%   r  c                     |sy	 ddl m}  ||      j                         S # t        $ r }t        j                  d|       Y d}~yd}~ww xY w)z@Best-effort fresh DB check before running a queued continuation.Fr   GoalManagerr  z2goal continuation: active-state recheck failed: %sN)hermes_cli.goalsr  	is_activer   r=  rM  )r  r  r  rE  s       r#   _goal_still_active_for_sessionz,GatewayRunner._goal_still_active_for_session\  sF    	4*5??AA 	LLMsS	s   " 	AAAgateway_stater  c                 x    	 ddl m}  |||| j                  | j                                y # t        $ r Y y w xY w)Nr   write_runtime_status)r  r  restart_requestedactive_agents)gateway.statusr  r  rZ  r   )r  r  r  r  s       r#   _update_runtime_statusz$GatewayRunner._update_runtime_statusg  sA    		; +'"&"9"9"779	  		s   *- 	99rD  rE  rF  rG  c                H    	 ddl m}  |||||       y # t        $ r Y y w xY w)Nr   r  )r   rE  rF  rG  )r  r  r   )r  r   rE  rF  rG  r  s         r#   rN  z-GatewayRunner._update_platform_runtime_statuss  s2    		; !-%+	  		s    	!!c                     t        j                  dd      } | s[	 ddl}t        dz  }|j	                         r=t        |d      5 }|j                  |      xs i }ddd       j                  dd      } | sg S t        |       j                         }|j                         s	t        |z  }|j	                         st        j                  d	|       g S 	 t        |d
d      5 }t        j                  |      }ddd       t!        t"              st        j                  d|       g S |S # 1 sw Y   xY w# t        $ r Y w xY w# 1 sw Y   NxY w# t        $ r#}t        j                  d||       g cY d}~S d}~ww xY w)a  Load ephemeral prefill messages from config or env var.
        
        Checks HERMES_PREFILL_MESSAGES_FILE env var first, then falls back to
        the prefill_messages_file key in ~/.hermes/config.yaml.
        Relative paths are resolved from ~/.hermes/.
        HERMES_PREFILL_MESSAGES_FILEr?   r   Nr   r   r   prefill_messages_filez#Prefill messages file not found: %sr  z3Prefill messages file must contain a JSON array: %sz+Failed to load prefill messages from %s: %s)r@   r<  r   r   rr   r   r   rB   r   r   
expanduseris_absoluter=  r>  r  loadr2   rA  )		file_pathrN  rO  rP  r   rq   r   r  r  s	            r#   rI  z$GatewayRunner._load_prefill_messages  s\    II<bA	!'-7??$h9 5R ll2.4"5 #(? DI II))+!$&D{{}NN@$GI		dC'2 $ayy|$dD)TVZ[	K'5 5  $ $  	NNH$PQRI	se   *D5 D)D5 E !E7/E 'E )D2.D5 5	E EE	E 	E<E71E<7E<c                  D   t        j                  dd      } | r| S 	 ddl}t        dz  }|j	                         rLt        |d      5 }|j                  |      xs i }ddd       t        dd	d
      xs dj                         S 	 y# 1 sw Y   ,xY w# t        $ r Y yw xY w)zLoad ephemeral system prompt from config or env var.
        
        Checks HERMES_EPHEMERAL_SYSTEM_PROMPT env var first, then falls back to
        agent.system_prompt in ~/.hermes/config.yaml.
        HERMES_EPHEMERAL_SYSTEM_PROMPTr?   r   Nr   r   r   r   system_promptr  )
r@   r<  r   r   rr   r   r   r   r7   r   )promptrN  rO  rP  r   s        r#   rK  z+GatewayRunner._load_ephemeral_system_prompt  s     ;R@M	#m3H (W5 1,,r*0bC1WorJPbWWYY ! 1 1  		s)   *B B(B BB 	BBc                     ddl m}  d}	 ddl}t        dz  }|j	                         rUt        |d      5 }|j                  |      xs i }ddd       t        t        dd	d
      xs d      j                         } | |      }|r(|j                         r|t        j                  d|       |S # 1 sw Y   gxY w# t        $ r Y Kw xY w)zLoad reasoning effort from config.yaml.

        Reads agent.reasoning_effort from config.yaml. Valid: "none",
        "minimal", "low", "medium", "high", "xhigh". Returns None to use
        default (medium).
        r   )parse_reasoning_effortr?   Nr   r   r   r   reasoning_effortr  z5Unknown reasoning_effort '%s', using default (medium))r  r  r   r   rr   r   r   r*   r   r7   r   r=  r>  )r  effortrN  rO  rP  r   r  s          r#   rM  z$GatewayRunner._load_reasoning_config  s     	<	#m3H (W5 1,,r*0bC1WS'3ErRXVXY__a (/fllnNNRTZ[1 1  		s(   *B< B0
2B< 0B95B< <	CCraw_argsc                 t   ddl }t        | xs d      j                         j                  dd      }|sy	  |j                  |      }d}g }|D ]  }|dk(  rd	}|j                  |        d
j                  |      j                         j                         |fS # t
        $ r |j	                         }Y nw xY w)zParse `/reasoning` args into `(value, persist_global)`.

        `/reasoning <level>` is session-scoped by default. `--global` may be
        supplied in any position to persist the change to config.yaml.
        r   Nr?   u   —z--)r?   FFz--globalTro  )	shlexr*   r7   r:   rp  r8   r`  ra  rq  )r  r  r   tokenspersist_globalvalue_tokensr  s          r#   _parse_reasoning_command_argsz+GatewayRunner._parse_reasoning_command_args  s     	8>r"((*225$?	" U[[&F  	+E
"!%##E*		+
 xx%++-335~EE  	"ZZ\F	"s   B B76B7r  rh  c                    |}|s|	 | j                  |      }t        | di       xs i }|r	||v r||   S | j                         S # t        $ r d}Y 9w xY w)zCResolve reasoning effort for a session, honoring session overrides.Nr%  )r  r   r&   rM  )r  r  rh  r0  r?  s        r#   !_resolve_session_reasoning_configz/GatewayRunner._resolve_session_reasoning_config  s{      +#(:,'+'C'CF'K$ D"@"EK	$8I$E122**,,  ,'+$,s   A AAreasoning_configc                     |syt        | d      si | _        || j                  j                  |d       yt        |      | j                  |<   y)z3Set or clear the session-scoped reasoning override.Nr%  )r  r%  r-  rh   )r  rh  r  s      r#   _set_session_reasoning_overridez-GatewayRunner._set_session_reasoning_override	  sO     t;<02D-#--11+tD=ABR=SD--k:r%   c                     d} 	 ddl }t        dz  }|j                         rUt        |d      5 }|j	                  |      xs i }ddd       t        t        ddd	      xs d      j                         } | j                         }|r|d
v ry|dv ryt        j                  d|        y# 1 sw Y   fxY w# t        $ r Y Jw xY w)a  Load Priority Processing setting from config.yaml.

        Reads agent.service_tier from config.yaml. Accepted values mirror the CLI:
        "fast"/"priority"/"on" => "priority", while "normal"/"off" disables it.
        Returns None when unset or unsupported.
        r?   r   Nr   r   r   r   r>  r  >   r  nonenormalrH   standard>   onfastpriorityr  z#Unknown service_tier '%s', ignoring)r   r   rr   r   r   r*   r   r7   r   rq  r=  r>  rE   rN  rO  rP  r   r   s         r#   rO  z GatewayRunner._load_service_tier	  s     	#m3H (W5 1,,r*0bC1'#wKQrRXXZ 		!QQ..<cB1 1  		s(   *B5 B)2B5 )B2.B5 5	C Cc                      	 ddl } t        dz  }|j                         rCt        |d      5 }| j	                  |      xs i }ddd       t        t        dd      d	      S 	 y# 1 sw Y   #xY w# t        $ r Y yw xY w)
z<Load show_reasoning toggle from config.yaml display section.r   Nr   r   r   r   show_reasoningFr  )r   r   rr   r   r   r   r   r   rN  rO  rP  r   s       r#   rQ  z"GatewayRunner._load_show_reasoning0	  s    	#m3H (W5 1,,r*0bC1&C,<=!  ! 1 1  		s(   *A0 A$A0 $A-)A0 0	A<;A<c                     t        j                  dd      j                         j                         } | s	 ddl}t
        dz  }|j                         rct        |d      5 }|j                  |      xs i }ddd       t        t        dd	d
      xs d      j                         j                         } | dk(  ry| dk(  ryy# 1 sw Y   NxY w# t        $ r Y $w xY w)z<Load gateway drain-time busy-input behavior from config/env.r   r?   r   Nr   r   r   r   r   r  re  rf  r  )r@   r<  r7   rq  r   r   rr   r   r   r*   r   r   )r  rN  rO  rP  r   s        r#   rS  z#GatewayRunner._load_busy_input_modeA	  s     yy92>DDFLLN!'-7??$h9 5R ll2.4"5wsI7HRTU[Y[\bbdjjlD 7?7?5 5  s*   *C  C6A C CC 	CCc                     t        j                  dd      j                         } | ss	 ddl}t        dz  }|j                         rUt        |d      5 }|j                  |      xs i }ddd       t        t        dd	d
      xs d      j                         } t        |       }| r|t        k(  r	 t        |        |S |S # 1 sw Y   YxY w# t        $ r Y =w xY w# t        t        f$ r t         j#                  d| t               Y |S w xY w)z<Load graceful gateway restart/stop drain timeout in seconds.r   r?   r   Nr   r   r   r   r   r  z7Invalid restart_drain_timeout '%s', using default %.0fs)r@   r<  r7   r   r   rr   r   r   r*   r   r   r+  r)  r6   rD   r8   r=  r>  r  s         r#   rT  z)GatewayRunner._load_restart_drain_timeoutU	  s	    ii6;AAC!'-7??$h9 5R ll2.4"5gc74KUWX^\^_eegC ,C05AAc
 u5 5   z* M9
 s;   *C C (2C 1C  C	C 	CC*D	D	c                     t        j                  dd      } | sl	 ddl}t        dz  }|j	                         rNt        |d      5 }|j                  |      xs i }ddd       t        dd	      }|d
u rd} n|dvrt        |      } | xs dj                         j                         } h d}| |vrt        j                  d|        y| S # 1 sw Y   oxY w# t        $ r Y Zw xY w)u  Load background process notification mode from config or env var.

        Modes:
          - ``all``    — push running-output updates *and* the final message (default)
          - ``result`` — only the final completion message (regardless of exit code)
          - ``error``  — only the final message when exit code is non-zero
          - ``off``    — no watcher messages at all
        HERMES_BACKGROUND_NOTIFICATIONSr?   r   Nr   r   r   r    background_process_notificationsFr     Nr?   r  >   r  r  r  r  zBUnknown background_process_notifications '%s', defaulting to 'all')r@   r<  r   r   rr   r   r   r   r*   r   r7   rq  r=  r>  )r  rN  rO  rP  r   rE   valids          r#   #_load_background_notifications_modez1GatewayRunner._load_background_notifications_modeo	  s     yy:B?!'-7??$h9 5R ll2.4"5!#y2TUCe|$J."3x $$&,,.1uNNT #5 5  s)   *C C+C CC 	C C c                      	 ddl } t        dz  }|j                         rAt        |d      5 }| j	                  |      xs i }ddd       j                  di       xs i S 	 i S # 1 sw Y   "xY w# t        $ r Y i S w xY w)z>Load OpenRouter provider routing preferences from config.yaml.r   Nr   r   r   provider_routingr   r   rr   r   r   rB   r   r  s       r#   rU  z$GatewayRunner._load_provider_routing	  s    	#m3H (W5 1,,r*0bC1ww126<"< ! 	1 1  			s(   *A/ A#A/ #A,(A/ /	A<;A<c                  $   	 ddl } t        dz  }|j                         rWt        |d      5 }| j	                  |      xs i }ddd       j                  d      xs |j                  d      xs d}|r|S y# 1 sw Y   6xY w# t        $ r Y yw xY w)a  Load fallback provider chain from config.yaml.

        Returns a list of provider dicts (``fallback_providers``), a single
        dict (legacy ``fallback_model``), or None if not configured.
        AIAgent.__init__ normalizes both formats into a chain.
        r   Nr   r   r   rH  rI  r  )rN  rO  rP  r   rQ  s        r#   rW  z"GatewayRunner._load_fallback_model	  s    
	#m3H (W5 1,,r*0bC1WW12Wcgg>N6OWSWI 1 1
  		s(   *B A73B 7B <B 	BBc                 z    | j                   j                         D ci c]  \  }}|t        ur|| c}}S c c}}w r  )rd  r  _AGENT_PENDING_SENTINEL)r  rh  r   s      r#   _snapshot_running_agentsz&GatewayRunner._snapshot_running_agents	  sE     '+&:&:&@&@&B
"U33 
 	
 
s   7rb  c                     | j                   j                  |j                  j                        }|sy t	        |j
                  ||       y r  )rE  rB   r  r   r(  re  )r  rh  rb  rk  s       r#   _queue_or_replace_pending_eventz-GatewayRunner._queue_or_replace_pending_event	  s8    --##ELL$9$9:#G$=$={ERr%   c                 v  K   | j                  |j                        s~t        j                  d|j                  j                  |j                  j
                  |j                  j                  r&|j                  j                  j                  |       yd|       y| j                  r}| j                  j                  |j                  j                        }|sy| j                  |      }| j                  |j                  |      }| j                         r'| j                  ||       d| j                          d}nd| j                          d}|j!                  |j                  j"                  ||j                  j                  t$        j&                  k(  r1|j                  j(                  dk(  r|j                  j*                  r|nJ|j                  j                  t$        j&                  k(  r|j                  j*                  rd n|j,                  |	       d {    y| j                  j                  |j                  j                        }|sy
| j.                  j                  |      }| j0                  }d
}	|dk(  r_|j2                  xs dj5                         }
|
xr |d uxr |t6        uxr t9        |d      }|r	 t;        |j=                  |
            }	|	sd}|	stA        |jB                  ||       |dk(  }|dk(  }|dk(  r&|r$|t6        ur	 |jE                  |j2                         tF        jH                  j                  dd      jK                         dk(  }|st        jM                  d|       yd}tO        jN                         }| jP                  j                  |d      }||z
  |k  ry|| jP                  |<   g }|r|t6        ur	 |jS                         }|j                  dd      }|j                  dd      }|j                  d      }| jT                  j                  |d      }|r*tW        ||z
  dz        }|dkD  r|jY                  | d       |r|jY                  d| d|        |r|jY                  d|        |rddj[                  |       dnd}|rd | d!}n|rd"| d#}nd$| d%}	 dd&l.m/}m0}m1}m2} tg               } |||      s)|rd} n|rd} nd} | d' ||        } |th        d(z  |       | j                  |      }| j                  |j                  |      }	 |j!                  |j                  j"                  ||j                  j                  t$        j&                  k(  r1|j                  j(                  dk(  r|j                  j*                  r|nJ|j                  j                  t$        j&                  k(  r|j                  j*                  rd n|j,                  |	       d {    y7 # t>        $ r$}t        j                  d||       d
}	Y d }~Ud }~ww xY w# t>        $ r Y w xY w# t>        $ r Y w xY w# t>        $ r!}!t        jM                  d)|!       Y d }!~!yd }!~!ww xY w7 # t>        $ r }"t        jM                  d*|"       Y d }"~"yd }"~"ww xY ww)+Nz`Dropping message from unauthorized user in active session: user=%s (%s), platform=%s, session=%sr  T   ⏳ Gateway 2    — queued for the next turn after it comes back.   ⏳ Gateway is - and is not accepting another turn right now.r  r  rZ   reply_tometadataFrf  r?   z'Gateway steer failed for session %s: %sre  r  r  truez"Busy ack suppressed for session %srH  r   api_call_countmax_iterationscurrent_tool<    min elapsed
iteration r    	running:  (, )u   ⏩ Steered into current runz0. Your message arrives after the next tool call.u   ⏳ Queued for the next turnz.. I'll respond once the current task finishes.u   ⚡ Interrupting current taskz'. I'll respond to your message shortly.)BUSY_INPUT_FLAGbusy_input_hint_gatewayis_seen	mark_seen

r   z.Failed to apply busy-input onboarding hint: %szFailed to send busy-ack: %s)5_is_user_authorizedr  r=  r>  r  	user_namer   r   r  rE  rB   r'  _thread_metadata_for_sourcerg  r  rc  _send_with_retryr  r  r	  r  r  
message_idrd  r  r   r7   r  r  r4   rf  r   r(  re  r  r@   rA   rq  rM  rN   rh  get_activity_summaryr  r5   r`  ra  agent.onboardingr  r  r  r  r  r   )#r  rb  rh  rk  reply_anchorthread_metarm  running_agenteffective_modesteered
steer_text	can_steerrE  is_queue_modeis_steer_moder  _BUSY_ACK_COOLDOWNrK   last_ackstatus_partssummary	iterationmax_iterr  start_tselapsed_minstatus_detailr  r  r  r  	_user_cfg
_hint_mode_onb_errr  s#                                      r#   #_handle_active_session_busy_messagez1GatewayRunner._handle_active_session_busy_message	  s     ''5NN8$$&&/4||/D/D%%++  KT  >>mm''(=(=>G77>L::5<<VK//144[%H()C)C)E(FFxy+D,F,F,H+IIvw**,, ||,,0A0AA..$6.. ! #(,,"7"78;L;L"LQVQ]Q]QgQg$mrm}m}$ +     --##ELL$9$9:,,00= ..W$***113J 4!-4!)@@4 M73	  $"=#6#6z#BCG !( '(A(A;PUV&'1&'1
 [(]}Tk?k''

3 ::>>*KVTZZ\`ffLL={K  iik$$((a8>..),+& ]2II'<<>#KK(8!<	";;'7;&{{>:2266{AF"%sX~&;"<K"Q$++{m<,HI ''*YKq
(KL '')L>(BC <H"TYY|45Q7R.}o >A B  .}o >? @  0 ?8 9 	U  -.I9o6 !(J"!(J!,Jit.z:;=  ,6H 33E:66u||\R	;**,, ||,,0A0AA..$6.. ! #(,,"7"78;L;L"LQVQ]Q]QgQg$mrm}m}$ +    IL ! $NN#Lk[^_#G$.  L  X  	ULLI8TT	U  	;LL6::	;s   HZ9 X!B%Z9X !6Z9X> 3B Z9B<Y 1Z9AY 
-Z98CZ ZZ 
Z9	X;X60Z96X;;Z9>	YZ9
YZ9	YZ9YZ9	Z'Z=Z9ZZ9Z 	Z6Z1,Z91Z66Z9r   c                 l   K    j                         } j                         dd
dt        dd f fd} j                  s |d       |dfS  |d       |dk  r|dfS t	        j
                         j                         |z   } j                  r{t	        j
                         j                         |k  rV |        t	        j                  d	       d {     j                  r&t	        j
                         j                         |k  rVt         j                        } |d       ||fS 7 Xw)Nr  Fr  r   c                     t        j                         j                         }j                         }| s|k7  s|z
  dk\  rj	                  d       ||y y )N      ?draining)r^  get_running_looprN   rZ  r  )r  rK   active_countlast_active_countlast_status_atr  s      r#   _maybe_update_statusz@GatewayRunner._drain_active_agents.<locals>._maybe_update_status
  s_    **,113C446L(99cN>RWZ=Z++J7$0!!$ >[r%   Tr  r   皙?F)r  rZ  r4   rd  r^  r   rN   sleep)r  r   snapshotr  deadline	timed_outr  r  s   `     @@r#   _drain_active_agentsz"GatewayRunner._drain_active_agents
  s    002 557	% 	% 	% ## t,U?"4(a<T>!++-224w>""w'?'?'A'F'F'H8'S "--$$$ ""w'?'?'A'F'F'H8'S --.	4("" %s   CD4D25D4#D4c                    t        | j                  j                               D ]6  \  }}|t        u r	 |j	                  |       t
        j                  d|       8 y # t        $ r }t
        j                  d|       Y d }~^d }~ww xY w)Nz8Interrupted running agent for session %s during shutdownz-Failed interrupting agent during shutdown: %s)rA  rd  r  r  r  r=  rM  r   )r  rU  rh  r   r  s        r#   _interrupt_running_agentsz'GatewayRunner._interrupt_running_agents
  s    "&t';';'A'A'C"D 	QK//Q'WYde	Q  QLaPPQs   'A	B'BBc                 	  K   | j                         }| j                  rdnd}| j                  rdnd}d| d| }t               }|D ]  }d}	 t        | dd      P| j                  j                          | j                  j                  j                  |      }|rt        |d	d      nd}|| j                  |      }|8|j                  j                  }
t        |j                        }|j                   }n)t#        |      }|s|d   }
|d   }|j                  d      }|
||rt        |      ndf}||v r	 t%        |
      }| j&                  j                  |      }|s| j(                  j*                  j                  |      }|$|j,                  st        j/                  d|
       g|rd|ind}|j1                  |||       d{   }|4t        |dd      du r%t        j                  d|
|t        |dd             |j3                  |       t        j/                  d|
|        t5        | j&                  j7                               D ]  \  }}| j(                  j9                  |      }|r|j                  s1| j(                  j*                  j                  |      }|-|j,                  s!t        j/                  d|j                         |j                  t        |j                        |j                   rt        |j                         ndf}||v r	 |j                   rd|j                   ind}|r0|j1                  t        |j                        ||       d{   }n-|j1                  t        |j                        |       d{   }|Ht        |dd      du r9t        j                  d|j                  |j                  t        |dd             |j3                  |       t        j/                  d|j                  |j                          y# t        $ r"}	t        j                  d
||	       Y d}	~	d}	~	ww xY w7 # t        $ r#}	t        j                  d|
||	       Y d}	~	:d}	~	ww xY w7 7 # t        $ r7}	t        j                  d|j                  |j                  |	       Y d}	~	sd}	~	ww xY ww)u4  Send shutdown/restart notifications to active chats and home channels.

        Called at the very start of stop() — adapters are still connected so
        messages can be delivered. Best-effort: individual send failures are
        logged and swallowed so they never block the shutdown sequence.
        ra  rb  zpYour current task will be interrupted. Send any message after restart and I'll try to resume where you left off.z&Your current task will be interrupted.u   ⚠️ Gateway     — NrZ  originz>Failed to load session origin for shutdown notification %s: %sr   r  r  z^Shutdown notification suppressed for active session: %s has gateway_restart_notification=falser  successTFz1Failed to send shutdown notification to %s:%s: %sr  send returned success=Falsez/Sent shutdown notification to active chat %s:%sz\Shutdown notification suppressed for home channel: %s has gateway_restart_notification=falsez>Failed to send shutdown notification to home channel %s:%s: %sz0Sent shutdown notification to home channel %s:%s)r  r  r  r&   rZ  _ensure_loaded_entriesrB   r   r=  rM  _get_cached_session_sourcer   r   r*   r  r  r  r  rE  r  rP  gateway_restart_notificationrL  sendr  rA  r  get_home_channel)r  activeactionhintr[   notifiedrh  r  r_   r  platform_strr  r  _parsed	dedup_keyr   rk  platform_cfgr  r  homes                        r#   #_notify_active_sessions_of_shutdownz1GatewayRunner._notify_active_sessions_of_shutdown
  s     ..0!%!8!8o &&X :	 	  xuTF38;! K	KF
4$7C&&557 ..77;;KHE?DWUHd;$F ~88E!%44fnn-",,	 -[9&z2!),#KK4	
 &w)IQUVIH$%#L1--++H5#{{4488B+L4]4]KKx$  8AK3d&||GS8|LL%'&)T*Je*SLLK$1NO	 Y'E 'GK	d "&dmm&9&9&;!< ,	Hg;;//9Dt||;;0044X>L'0Y0YrNN !T\\):SWSaSaC<OgklIH$<@NNK8PT#*<<DLL0A3QY<#ZZF#*<<DLL0A3#GGF%'&)T*Je*SLLX 1NO	 Y'FNNLLC,	W  T b M  G '1 > [G   TNNLL	 s   ASAP)+BS9(Q!S#A	Q,S.QQ8QS(Q0C3S$AR,R	--RRAR'S)<R%S)	Q2Q	SQSQ	R#R;SRS	RR	S,S	S	SSr  c           	          |j                         D ]0  }	 ddlm}  |dt        |dd       d       | j                  |       2 y # t        $ r Y w xY w)Nr   invoke_hookon_session_finalizer  gatewayr  r   )valueshermes_cli.pluginsr&  r&   r   _cleanup_agent_resources)r  r  r   _invoke_hooks       r#   _finalize_shutdown_agentsz'GatewayRunner._finalize_shutdown_agentsB  s`    "))+ 
	1EJ)&ulDA& ))%0
	1  s   A	AAr   c                 R   |y	 t        |d      r?t        |dd      }t        |t              r|j	                  |       n|j	                          	 t        |d      r|j                          	 ddlm}  |        y# t
        $ r Y 7w xY w# t
        $ r Y )w xY w# t
        $ r Y yw xY w)z<Best-effort cleanup for temporary or cached agent instances.Nshutdown_memory_provider_session_messagescloser   )cleanup_stale_async_clients)	r  r&   r2   rA  r0  r   r2  agent.auxiliary_clientr3  )r  r   session_messagesr3  s       r#   r,  z&GatewayRunner._cleanup_agent_resourcesO  s    =	u89 $+52Et#L .5223CD224	ug&	J')!  		  		  		s6   AA< B .B <	BB	BB	B&%B&ry  z.restart_failure_countsactive_session_keysc                 8   ddl }t        | j                  z  }	 |j                         r  |j                  |j                               ni }i }|D ]  }|j                  |d      dz   ||<    	 t        ||d       y# t        $ r i }Y >w xY w# t        $ r Y yw xY w)a  Increment restart-failure counters for sessions active at shutdown.

        Persists to a JSON file so counters survive across restarts.
        Sessions NOT in active_session_keys are removed (they completed
        successfully, so the loop is broken).
        r   Nr   r  )	r  r   _STUCK_LOOP_FILErr   r  r  r   rB   r   )r  r6  r  rq   counts
new_countsr+  s          r#   !_increment_restart_failure_countsz/GatewayRunner._increment_restart_failure_counts{  s     	d333	59[[]ZTZZ 01F
 
& 	5C$jja014JsO	5
	dJt<  	F	  		s#   2A< -B <B
	B
	BBc                    ddl }t        | j                  z  }|j                         sy	  |j                  |j                               }d}|j                         D cg c]  \  }}|| j                  k\  s| }}}|D ]\  }	 | j                  j                  j                  |      }	|	r2|	j                  s&d|	_        |dz  }t        j                  d|||          ^ |r	 | j                  j                          	 |j!                  d       |S # t        $ r Y yw xY wc c}}w # t        $ r Y w xY w# t        $ r Y Dw xY w# t        $ r Y |S w xY w)u,  Suspend sessions that have been active across too many restarts.

        Returns the number of sessions suspended.  Called on gateway startup
        AFTER suspend_recently_active() to catch the stuck-loop pattern:
        session loads → agent gets stuck → gateway restarts → repeat.
        r   NTr   u_   Auto-suspended stuck session %s (active across %d consecutive restarts — likely a stuck loop)
missing_ok)r  r   r8  rr   r  r  r   r  _STUCK_LOOP_THRESHOLDrZ  r  rB   	suspendedr=  r>  _saveunlink)
r  r  rq   r9  r@  kr  
stuck_keysrh  r_   s
             r#   _suspend_stuck_loop_sessionsz*GatewayRunner._suspend_stuck_loop_sessions  sg    	d333{{}	TZZ 01F 	$*LLNVDAqa4;U;U6UaV
V% 	K**3377D&*EONINNH#VK%8	 ""((*
	KK4K( A  		 W      		sT    D D 7D AD&"D5 =E 	DD&	D21D25	E E	EEc                 
   ddl }t        | j                  z  }|j                         sy	  |j                  |j                               }||v r'||= |rt        ||d       y|j                  d       yy# t        $ r Y yw xY w)zClear the restart-failure counter for a session that completed OK.

        Called after a successful agent turn to signal the loop is broken.
        r   Nr  Tr=  )	r  r   r8  rr   r  r  r   rB  r   )r  rh  r  rq   r9  s        r#   _clear_restart_failure_countz*GatewayRunner._clear_restart_failure_count  s    
 	d333{{}		TZZ 01Ff$;'%dF4@KK4K0 %  		s   7A6 "A6 6	BBc                   K   dd l }dd l}t               }|st        j	                  d       y t        j                         }t        j                  dk(  r~dd l	}ddl
m} g |dd}|j                  d      j                         } |j                  t        j                  d|t!        |      g|f|j"                  |j"                  d	 |        y d
j%                  d |D              }	d| d|	 d}
|j'                  d      }|r.|j                  |dd|
g|j"                  |j"                  d       y |j                  dd|
g|j"                  |j"                  d       y w)Nr   z4Could not locate hermes binary for detached /restartwin32windows_detach_popen_kwargsr(  r\  u  
                import os, subprocess, sys, time
                pid = int(sys.argv[1])
                cmd = sys.argv[2:]
                deadline = time.monotonic() + 120

                def _alive(p):
                    # On Windows, os.kill(pid, 0) is NOT a no-op — it maps to
                    # GenerateConsoleCtrlEvent(0, pid) (bpo-14484). Use the
                    # Win32 handle-based existence check instead.
                    if os.name == 'nt':
                        import ctypes
                        k32 = ctypes.windll.kernel32
                        k32.OpenProcess.restype = ctypes.c_void_p
                        k32.WaitForSingleObject.restype = ctypes.c_uint
                        k32.GetLastError.restype = ctypes.c_uint
                        h = k32.OpenProcess(0x1000 | 0x100000, False, int(p))
                        if not h:
                            return k32.GetLastError() != 87
                        try:
                            return k32.WaitForSingleObject(h, 0) == 0x102
                        finally:
                            k32.CloseHandle(h)
                    try:
                        os.kill(int(p), 0)
                        return True
                    except ProcessLookupError:
                        return False
                    except PermissionError:
                        return True
                    except OSError:
                        return False

                while time.monotonic() < deadline:
                    if not _alive(pid):
                        break
                    time.sleep(0.2)
                _CREATE_NEW_PROCESS_GROUP = 0x00000200
                _DETACHED_PROCESS = 0x00000008
                _CREATE_NO_WINDOW = 0x08000000
                subprocess.Popen(
                    cmd,
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                    creationflags=_CREATE_NEW_PROCESS_GROUP | _DETACHED_PROCESS | _CREATE_NO_WINDOW,
                )
                -cstdoutstderrro  c              3   F   K   | ]  }t        j                  |        y wr  r  quoter  s     r#   r  zAGatewayRunner._launch_detached_restart_command.<locals>.<genexpr>*  s     @Tu{{4(@   !zwhile kill -0 z" 2>/dev/null; do sleep 0.2; done; z gateway restartsetsidbashz-lcTrN  rO  start_new_session)r  
subprocessr  r=  r  r@   getpidr  r   textwraphermes_cli._subprocess_compatrK  dedentr7   Popenr  r*   DEVNULLra  r  )r  r  rX  
hermes_cmdcurrent_pidrZ  rK  cmd_argvwatchercmd	shell_cmd
setsid_bins               r#    _launch_detached_restart_commandz.GatewayRunner._launch_detached_restart_command  s|    (*
LLOPiik <<7"Q::Y:	:Hoo.0` ega b JwK0@L8L!))!)) ./	 hh@Z@@[M)Ke#% 	 \\(+
VUI6!))!))"&	   	*!))!))"&	  s   EE!detachedvia_servicerh  ri  c                      j                   ryd _         _         _        d _         d fd}t	        j
                   |             } j                  j                  |       |j                   j                  j                         y)NFTc                     K   t        j                  d       d {    j                  d        d {    y 7 !7 w)N皙?Tr\  detached_restartservice_restart)r^  r  rQ  )rh  r  ri  s   r#   _run_restartz3GatewayRunner.request_restart.<locals>._run_restartG  s;     --%%%))D8U`)aaa &as   A>AA A Ar   N)
r   r  r!  r"  r^  create_taskr  r  add_done_callbackr  )r  rh  ri  rp  tasks   ```  r#   request_restartzGatewayRunner.request_restart?  sz    %%"&!)$/!%)"	b ""<>2""4(t55==>r%   >   restart_timeoutshutdown_timeoutrestart_interruptedc           
         t               }	 | j                  j                  5  | j                  j                          | j                  j                  j                         D cg c]@  }|j                  r2|j                  s&|j                  |j                  | j                  v r|B }}ddd       t        j                         }d}D ]0  }|j                   xs |j"                  }|||z
  j%                         |kD  r7|j                  }| j&                  j)                  |j*                        }	|	At        j-                  d|j.                  t1        |j*                  d|j*                               t3        dt4        j6                  |d      }
t9        j:                  |	j=                  |
            }| j>                  jA                  |       |jC                  | j>                  jD                         |d	z  }3 |rt        jG                  d
|       |S c c}w # 1 sw Y   uxY w# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)u  Auto-continue fresh restart-interrupted sessions after startup.

        ``resume_pending`` already preserves the transcript AND the existing
        ``_is_resume_pending`` branch in ``_handle_message_with_agent``
        injects a reason-aware recovery system note on the next turn.  This
        method closes the UX gap by synthesizing that next turn once
        adapters are back online — the event text is empty so the existing
        injection path owns the wording and we never double up.

        Adapters that are not yet ready (adapter missing from
        ``self.adapters``) are skipped silently; their sessions stay
        ``resume_pending`` and will auto-resume on the next real user
        message, or on the next gateway startup.
        Nz/Failed to enumerate resume-pending sessions: %sr   z5Skipping auto-resume for %s: adapter not ready for %sr   r?   Tr   rX  r  internalr   z;Scheduled auto-resume for %d restart-interrupted session(s))$rF   rZ  _lock_ensure_loaded_lockedr  r*  resume_pendingr@  r  resume_reason_AUTO_RESUME_REASONSr   r=  r>  r   rK   last_resume_marked_at
updated_attotal_secondsrE  rB   r   rM  rh  r&   r%  r&  TEXTr^  rr  handle_messager  r  rs  r  rL  )r  rO   r_   
candidatesrE  rK   	scheduledmarkerr  rk  rb  rt  s               r#   !_schedule_resume_pending_sessionsz/GatewayRunner._schedule_resume_pending_sessionsY  s    12	##)) ""88:'+'9'9'B'B'I'I'K#++!OO0++t/H/HH	 
  lln	 	E00DE4D4DF!sV|&B&B&Dv&M\\Fmm''8GK%%FOOWfooF
 
 !(--	E &&w'='=e'DED""&&t,""4#9#9#A#ABNI7	: KKM _   	NNLcR	sB   H* AH#AH(H*H* HH'"H* *	I3IIc           
        K   t         j                  d       	 t        j                         | _        t         j                  d| j                  j                         	 ddlm	}  || j                        }|C|j                  d      r2t         j                  d|j                  dd	      |d
   |d   |d          	 t        t!        j"                  dd            }t         j                  d|       	 t!        j"                  dd      }|j%                         dv }|rt         j                  d       nt         j                  d|       	 ddlm}  |       }|r|dk7  rt         j                  d|       	 ddlm}	  |	dd       	 ddlm}
m}  |
       } ||      }|r+t         j                  d|       t         j                  d       d"}d#}d$}d$}	 dd%lm} t9        d& |j;                         D              }t9        d' |j;                         D              }t=        d( ||z   D              }t!        j"                  d)d*      j%                         d+v xs t=        d, ||z   D              }|s|st         j                  d-       	 dd.lm }  |        	 dd0l!m"} dd1l#m$}  | |       d23       | jJ                  jM                          	 dd5l'm(} |jS                         }|rt         j                  d6|       tT        d8z  }|jW                         r't         j                  d9       	 |jY                          n3	 | jZ                  j]                         }|rt         j                  d:|       	 | j_                         }|rt         j                  d<|       d}d}g } g }!| j                  j`                  jc                         D ](  \  }"}#|#jd                  s|d>z  }| jg                  |"|#      }$|$ss|"jh                  }%tj        jl                  jo                         D &ch c]  }&|&jh                   }'}&|%|'vrt         j                  d?|%       nt         j                  d@|%       |$jq                  | jr                         |$ju                  | jv                         |$jy                  | jZ                         |$j{                  | j|                         t         j                  dA|"jh                         | j                  |"jh                  dBddC       	 | j                  |$|"       d{   }(|(rf|$| j                  |"<   | j                  |$       |d>z  }| j                  |"jh                  dDddC       t         j                  dE|"jh                         n[t         j                  dF|"jh                         | j                  |$|"       d{    |$j                  r| j                  |"jh                  |$j                  rdGndH|$j                  |$j                  C       |$j                  r|!n| })|)j                  |"jh                   dI|$j                          |$j                  r|#d>t        j                         dJz   dK| j                  |"<   ne| j                  |"jh                  dGddLC       |!j                  |"jh                   dM       |#d>t        j                         dJz   dK| j                  |"<   + |dk(  r| rJdOj                  |       }*t         j                  dP|*       	 ddlm}	  |	dQ|*       | j                  |*       y |dkD  rR|!r9dOj                  |!      }*t         j                  dR|*       	 ddlm}	  |	dQ|*       y2t         j                  dS|       n*t         j                  dT       t         j                  dU       | j                  | j                  _A        | j                          d | _R        | j                  dV       t        | jJ                  j                        }+|+rt         j                  dW|+       | jJ                  j                  dXdY| j                  j                         D ,cg c]  },|,jh                   c},i       d{    |dkD  rt         j                  dZ|       	 dd[lXmY}-  |-| j                         d{   }.t        d\ |.j                  dYi       jo                         D              }/t         j                  d]|/       | j                          d{   }0|0s2t=        d_ tT        d`z  tT        daz  fD              r| j                          |dkD  rt        j                  db       d{    t               }1| j                          d{   }2|1s|2!|2r|2hnd}3| j                  |3c       d{    | j                          	 dd5l'm(} |j                  rq|j                  j                  d      }4t        j                  | j                  |4             t         j                  dd|4j                  de             |j                  rqt        j                  | j                                t        j                  | j                                t        j                  | j                                | j                  rJt         j                  dgt        | j                        dhj                  di | j                  D                     t        j                  | j                                t        j                  | j                                t         j                  dj       y # t
        $ r d| _        Y Rw xY w# t        $ r!}t         j                  d|       Y d}~
d}~ww xY w# t        $ r Y 
w xY w# t        $ r Y 
w xY w# t        $ r Y 
ow xY w# t        $ r Y 
nw xY w# t        $ r t         j                  dd !       Y 
Pw xY w# t        $ r Y 
w xY w# t        $ r t         j                  d/d !       Y 	w xY w# t        $ r t         j                  d4d !       Y 	w xY w# t        $ r!}t         j                  d7|       Y d}~	d}~ww xY w# t        $ r Y 	Kw xY w# t        $ r!}t         j                  d;|       Y d}~	td}~ww xY w# t        $ r!}t         j                  d=|       Y d}~	xd}~ww xY wc c}&w 7 7 Y# t        $ r}t         j                  dN|"jh                  |       | j                  |$|"       d{  7   | j                  |"jh                  dGdt        |      C       |!j                  |"jh                   dI|        |#d>t        j                         dJz   dK| j                  |"<   Y d}~
d}~ww xY w# t        $ r Y w xY w# t        $ r Y y2w xY wc c},w 7 }7 D# t        $ r!}t         j                  d^|       Y d}~!d}~ww xY w7 7 7 7 # t        $ r!}t         j                  df|       Y d}~d}~ww xY ww)kz
        Start the gateway and all configured platform adapters.
        
        Returns True if at least one adapter connected successfully.
        zStarting Hermes Gateway...NzSession storage: %sr   )check_systemd_timing_alignmentmismatchzStale systemd unit detected: %s has TimeoutStopSec=%.0fs but drain_timeout=%.0fs (expected >=%.0fs). systemd may SIGKILL the gateway mid-drain. Run `hermes gateway service install --replace` to regenerate the unit, or shorten agent.restart_drain_timeout.unitz	(unknown)timeout_stop_secdrain_timeoutexpected_minz)check_systemd_timing_alignment failed: %sr   90zuAgent budget: max_iterations=%d (agent.max_turns from config.yaml, or HERMES_MAX_ITERATIONS from .env, or default 90)r  r  >   r   r  yesr  z^Secret redaction: ENABLED (tool output, logs, and chat responses are scrubbed before delivery)zSecret redaction: DISABLED (HERMES_REDACT_SECRETS=%s). API keys and tokens may appear verbatim in chat output, session JSONs, and logs. Set security.redact_secrets: true in config.yaml to re-enable.get_active_profile_namerH   zActive profile: %sr  starting)r  r  )detect_compromisedgateway_log_messagez%szCRun `hermes doctor` on the gateway host for full remediation steps.z1security advisory check failed at gateway startupTr  )TELEGRAM_ALLOWED_USERSDISCORD_ALLOWED_USERSWHATSAPP_ALLOWED_USERSSLACK_ALLOWED_USERSSIGNAL_ALLOWED_USERSSIGNAL_GROUP_ALLOWED_USERSTELEGRAM_GROUP_ALLOWED_USERSTELEGRAM_GROUP_ALLOWED_CHATSEMAIL_ALLOWED_USERSSMS_ALLOWED_USERSMATTERMOST_ALLOWED_USERSMATRIX_ALLOWED_USERSDINGTALK_ALLOWED_USERSFEISHU_ALLOWED_USERSWECOM_ALLOWED_USERSWECOM_CALLBACK_ALLOWED_USERSWEIXIN_ALLOWED_USERSBLUEBUBBLES_ALLOWED_USERSQQ_ALLOWED_USERSYUANBAO_ALLOWED_USERSGATEWAY_ALLOWED_USERS)TELEGRAM_ALLOW_ALL_USERSDISCORD_ALLOW_ALL_USERSWHATSAPP_ALLOW_ALL_USERSSLACK_ALLOW_ALL_USERSSIGNAL_ALLOW_ALL_USERSEMAIL_ALLOW_ALL_USERSSMS_ALLOW_ALL_USERSMATTERMOST_ALLOW_ALL_USERSMATRIX_ALLOW_ALL_USERSDINGTALK_ALLOW_ALL_USERSFEISHU_ALLOW_ALL_USERSWECOM_ALLOW_ALL_USERSWECOM_CALLBACK_ALLOW_ALL_USERSWEIXIN_ALLOW_ALL_USERSBLUEBUBBLES_ALLOW_ALL_USERSQQ_ALLOW_ALL_USERSYUANBAO_ALLOW_ALL_USERSr   platform_registryc              3   L   K   | ]  }|j                   r|j                     y wr  )allowed_users_envr  r  s     r#   r  z&GatewayRunner.start.<locals>.<genexpr>/  s'      )()&& ##)   "$c              3   L   K   | ]  }|j                   r|j                     y wr  )allow_all_envr  s     r#   r  z&GatewayRunner.start.<locals>.<genexpr>3  s#      +$%?? +r  c              3   F   K   | ]  }t        j                  |        y wr  )r@   r<  r  r  s     r#   r  z&GatewayRunner.start.<locals>.<genexpr>9  s      
BIIaL
rS  GATEWAY_ALLOW_ALL_USERSr?   >   r   r  r  c              3   h   K   | ]*  }t        j                  |d       j                         dv  , yw)r?   >   r   r  r  N)r@   r<  rq  r  s     r#   r  z&GatewayRunner.start.<locals>.<genexpr><  s4      e
 IIa""$(<<e
s   02zNo user allowlists configured. All unauthorized users will be denied. Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access, or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id).)discover_pluginsz*plugin discovery failed at gateway startupr2  )register_from_configF)accept_hooksz1shell-hook registration failed at gateway startupr'  z5Recovered %s background process(es) from previous runzProcess checkpoint recovery: %s.clean_shutdownu?   Previous gateway exited cleanly — skipping session suspensionz=Marked %d in-flight session(s) as resumable from previous runz(Session suspension on startup failed: %sz'Auto-suspended %d stuck-loop session(s)zStuck-loop detection failed: %sr   uq   No adapter for '%s' — is the plugin installed? (platform is enabled in config.yaml but no plugin registered it)zNo adapter available for %szConnecting to %s...
connectingrD  r  u   ✓ %s connectedu   ✗ %s failed to connectrB  rC  r  rH  rI  zfailed to connectz: failed to connectu   ✗ %s error: %sz; z0Gateway hit a non-retryable startup conflict: %sstartup_failedz?Gateway failed to connect any configured messaging platform: %szNo adapter could be created for any of the %d configured platform(s). Check that required dependencies are installed and credentials are set. Gateway will continue for cron job execution.zNo messaging platforms enabled.z5Gateway will continue running for cron job execution.runningz%s hook(s) loadedzgateway:startuprP  z#Gateway running with %s platform(s)build_channel_directoryc              3   2   K   | ]  }t        |        y wr  r]  )r  chss     r#   r  z&GatewayRunner.start.<locals>.<genexpr>M  s     W3s8W   z%Channel directory built: %d target(s)z"Channel directory build failed: %sc              3   <   K   | ]  }|j                           y wr  )rr   )r  rq   s     r#   r  z&GatewayRunner.start.<locals>.<genexpr>U  s       
 KKM 
s   .update_pending.json.update_pending.claimed.jsonr  skip_targetsz(Resumed watcher for recovered process %sr  z!Recovered watcher setup error: %sz;Starting reconnection watcher for %d failed platform(s): %sr  c              3   4   K   | ]  }|j                     y wr  r  r  r  s     r#   r  z&GatewayRunner.start.<locals>.<genexpr>  s     Ba!''Bs   zPress Ctrl+C to stop)kr=  rL  r^  r   r]  r@  r  r<  gateway.shutdown_forensicsr  r  rB   r>  r   rM  r5   r@   r<  rq  hermes_cli.profilesr  r  r  hermes_cli.security_advisoriesr  r  gateway.platform_registryr  r<  plugin_entriesr  r+  r  r   r3  agent.shell_hooksr  r  discover_and_loadrY  r(  recover_from_checkpointr   rr   rB  rZ  suspend_recently_activerE  rP  r  r  _create_adapterr   r  __members__r*  set_message_handler_handle_messageset_fatal_error_handlerrT  set_session_storeset_busy_session_handlerr  rN  r  rE  r  r  has_fatal_errorrO  rL  rM  r`  rN   r  ru  r  r*   ra  rX  r[  r  r\  r  r]  loaded_hooksemitr,  gateway.channel_directoryr  sum_send_update_notification#_schedule_update_notification_watchr  r   _send_restart_notification(_send_home_channel_startup_notificationsr  pending_watchersr-  rr  _run_process_watcher_session_expiry_watcher_kanban_notifier_watcher_kanban_dispatcher_watcher_platform_reconnect_watcher_handoff_watcher)5r  r  
_alignment_e_effective_max_iter_redact_raw
_redact_onr  _profiler  r  r  	_adv_hits_adv_msg_builtin_allowed_vars_builtin_allow_all_vars_plugin_allowed_vars_plugin_allow_all_varsr  _any_allowlist
_allow_allr  r3  r  r(  	recoveredr  _clean_markerr@  stuckconnected_countenabled_platform_countstartup_nonretryable_errorsstartup_retryable_errorsr   rS  rk  _pvalm_builtin_namesr  targetrU  
hook_countr  r  	directorych_countr  restart_notification_pendingdelivered_restart_targetskip_home_targetsrb  s5                                                        r#   startzGatewayRunner.start  sq     	01	&!(!9!9!;D 	)4;;+C+CD	JQ78S8STJ%*..*DV NN6;712/~.		"%bii0G&N"OKKE#	))$;VDK$**,0JJJ>
 3  	C.0HH	10(;	; ztL	 +,I*95HtX.)!
$#
" ')(*	C#( )->-M-M-O) $  &+ +):)I)I)K+ &"  
"7:N"N
 
 YY8"=CCEI]] 
ad e
,/EEe
 b

 jNN[	;	5> UC 	

$$&	A?(@@BISU^_ %'88!KKYZ$$&N ..FFH	KK _ajk	?557EH%P !"13#.0  *.)>)>)D)D)F r	%Ho"**"a'"**8_EG 3;3G3G3N3N3P!Qa!''!Q!Q.NN[ NN#@%H ''(<(<=++D,L,LM%%d&8&89,,T-U-UV KK-x~~>00+"	 1 P $ B B7H UU.5DMM(+::7C#q(O88 '2#'&*	 9  KK 2HNNCNN#=x~~N 77JJJ..<<$NN9@9V9V:\c'.'?'?*1*E*E	 =   '<< 5!< 
 '~~.b1L1L0MN #88*9,-.2nn.>.C@D228< <<$NN+5'+*=	 =  177'~~..AB
 '6()*...*:R*?<..x8wr	h a*#>?OQWXC(7GU[\ ((0%)+ "YY'?@FLL!bdjkG,;KY_` !
 D +	 @AST )-%))+##I. 001
KK+Z8jjoo/4==+=+=+?@a!''@2
  	 	 QKK=O	DI5dmmDDIWy}}["/M/T/T/VWWHKK?J 7799C  
 55== 
 
 446
 Q--$$$ (E'F$)-)H)H)J#J  (+C+O.F)*D  ??. @    	..0	A?"33*;;??B##D$=$=g$FGFT`Hab #33 	D88:;
 	D99;< 	D;;=> !!KKMD**+		B4+A+ABB
 	D<<>? 	D1134*+}  	&!%D	&.  	JLLDbII	J  		.  		  		
  		*  	LLC  	l  		2  	LL<t  	$  	LLC  	   	ANN<a@@	A"    NI1MMN  	?LL:A>>	?$ "R4 V, KN  /C 33GXFFF44NN#-#"%a&	 5  )//8>>2B"QC0HI . !"&.."2R"74&&x06 !  %  : A	 E  	DNN?CC	D
 : % $K"  	ALL<a@@	Asg  y/m1 *y/An ;5n5 1Ao *o 3o% Ao5 y/Ap A+y/p, q ,y/.q: 5.y/$r' 4y/62r7 )(s$ By/t)Cy/	ttB!t tD%t&1y/w (A y/)w/ 9C/y/(w>;y/xy/'x	 xA
x	 y/ x6!Ay/6x97!y/x<$y/=x?>y/By Dy/1ny/ny/	n2n-'y/-n22y/5	o>y/oy/	oy/oy/	o"y/!o""y/%	o2.y/1o22y/5 py/py/	p)%y/(p))y/, qy/qy/ q73y/6q77y/:	r$ry/r$$y/'	r40y/3r44y/7	s! sy/s!!y/$	t-t	y/	ty/tt	w%6wuA5wy/wy/	w,(y/+w,,y//	w;8y/:w;;	y/x	 		x3x.(y/.x33y/9y/<y/?y/	y,y'!y/'y,,y/intervalc                 R  K   t        j                  d       d{    | j                  r	 | j                  t        j                  |       d{    7| j                  j	                         }|D ]g  }|j                  d      }|s| j                  j                  |      s3	 | j                  |       d{    | j                  j                  |       i 	 t        j                  |       d{    | j                  ryy7 7 7 R# t        $ rH}t        j                  d||d       | j                  j                  |t        |             Y d}~d}~ww xY w# t         j                  $ r  t        $ r"}t        j                  d|d       Y d}~d}~ww xY w7 w)u  Background task that processes pending CLI→gateway session handoffs.

        Polls ``state.db`` for sessions in ``handoff_state='pending'`` and,
        for each one:

        1. Atomically claims it (pending → running).
        2. Resolves the destination platform's configured home channel.
        3. Re-binds the gateway's session_key for that home channel to the
           CLI's existing session_id via ``session_store.switch_session`` so
           the full role-aware transcript replays on the next agent turn.
        4. Forges a synthetic ``MessageEvent`` (``internal=True``) with a
           handoff-notice text and dispatches through the normal gateway
           message pipeline so the agent runs and replies on the platform.
        5. Marks the row ``completed`` (or ``failed`` with ``handoff_error``).

        The CLI process is poll-blocked on the row's terminal state and
        prints the result to the user.
        r  Nidz!Handoff for session %s failed: %sTr  zHandoff watcher tick error: %s)r^  r  r\  r{  list_pending_handoffsrB   claim_handoff_process_handoffcomplete_handoffr   r=  r>  fail_handoffr*   CancelledErrorrM  )r  r  pendingrowr  rE  s         r#   r  zGatewayRunner._handoff_watcher  s|    * mmAmmS##+!--111**@@B" LC!$J% ++99*E L"33C888((99*EL( --)))5 mm 	 2 9$ L?&d '  ((55j#c(KKL ))  S=sTRRS)s   F'D
F'$E$ DE$ F'AE$ 'D;D<DE$ F'6F%7F'F'E$ D	E!>EE$ E!!E$ $F" FF'F""F'r(  c           	        K   ddl m} ddlm}m} ddlm} |d   }|j                  d      xs dj                         j                         }|st        d      	  ||      }| j                  j                  |      }	|	st        d| d      | j                  j                  |      }
|
r|
j                   st        d| d      |j                  d      xs |dd }d| }	 |	j#                  t%        |
j                         |       d{   }|xs# |
j,                  rt%        |
j,                        nd}|rd}nd} ||t%        |
j                         |
j.                  |dd|      }| j                  j0                  j                  |      }|r|j2                  ni } |||j                  dd      |j                  dd            }| j4                  j7                  |       | j4                  j9                  ||      }|t        d| d |       | j;                  |       | j=                  |       d!| d"} |||d#      }t(        j?                  d$|||
j                   ||       | jA                  |       d{   }|syi }|r||d%<   	 |	jC                  t%        |
j                         ||xs d&       d{   }tE        |d(d      stE        |d)d*      }t        d'|       y# t        t        f$ r t        d	| d
      w xY w7 # t&        $ r&}t(        j+                  d||d       d}Y d}~,d}~ww xY w7 7 # t&        $ r}t        d'|       |d}~ww xY ww)+zAExecute one handoff row. Raises on failure (caller marks failed).r   r  )r  r   )r%  r   handoff_platformr?   zhandoff_platform is emptyzunknown platform 'r~  z
platform 'z' is not active in this gatewayzno home channel configured for z(; run /sethome on the desired chat firsttitleN   u   Hermes — z/Handoff: create_handoff_thread raised on %s: %sTr  r  r  zsystem:handoffHandoff)r   r  	chat_namer  r  r  r  r  r  Fr  zcould not switch session key u    → z([Session was just handed off from CLI ("z") to this channel. The full prior conversation history is loaded above. Briefly confirm you're working here and summarize what we were working on, so the user can continue from this device.])r   r  r{  ub   Handoff: dispatching synthetic turn for CLI session %s → %s (home=%s, thread=%s, session_key=%s)r  r  rZ   r  zadapter.send failed: r  r  r  )#gateway.configr  gateway.sessionr  r   gateway.platforms.baser%  rB   r7   rq  r@  r8   KeyErrorrE  r  r  r  create_handoff_threadr*   r   r=  rM  r  rG   rP  extrarZ  get_or_create_sessionswitch_session_evict_cached_agent_release_running_agent_staterL  r  r  r&   )r  r(  r  r  r   r%  cli_session_idry   r   rk  r"  	cli_titlethread_namenew_thread_idrE  effective_thread_iddest_chat_typedest_sourcer!  r6  rh  switchedsynthetic_textsynthetic_eventresponse_textsend_metadatar  r
  s                               r#   r#  zGatewayRunner._process_handoff  s    +D7T!34:AACIIK:;;	F.H
 --##H-]O+JK 
 {{++H54<<1- A9 : 
 GGG$:r(:	 $I;/		!")"?"?DLL!;# M , 
#'>>Ct 	 %N "N#%ii$$)
 {{,,00:&2""'$)II.G$N%*YY/I5%Q
 	00= %%44[.Q/}E.AQR  	  - 	))+6 8	{ CF G 	 '
 	3M4<<9L		
 #22?CC 
 )+)<M+&	G"<<DLL)%&.$ (  F vy$/&'+HIC!6se<== 0m H% 	F!3M?!DEE	F8  	!LLAsT   !M	!D D
  	G!6se<=3F	Gs   AM*K/ #BM*%(L LL E/M*MM*.M
 MM
 )M*/LM*L 	ML>8M*>MM*M
 
	M'M""M''M*r  c           	        K   t        j                  d       d{    i }d}| j                  r	 | j                  j	                          g }t        | j                  j                  j                               D ]A  \  }}|j                  r| j                  j                  |      s/|j                  ||f       C |ri }|D ]C  \  }}	|j                  d      }
t        |
      dkD  r|
d   nd}|j                  |d      dz   ||<   E d	j                  d
 t        |j                               D              }t         j#                  dt        |      |       |D ]`  \  }}	 	 ddlm} |j                  d      }
t        |
      dkD  r|
d   nd} |d|j(                  |       d}t-        | dd      }|A|5  | j.                  j                  |      }t1        |t2              r|d   n|r|nd}ddd       || j4                  j                  |      }|r|t6        ur| j9                  |       | j;                  |       | j                  j<                  5  d|_        | j                  j?                          ddd       t         jA                  d|j(                         |jC                  |j(                  d       c |rPtG        d |D              }t        |      |z
  }|rt         j#                  d||       nt         j#                  d|       	 | jI                         }|rt         j#                  d|       t-        | dd      }d}tK        jJ                         |z
  |kD  rv	 tM        t-        | jN                  dd      xs d      }|dkD  r3| j                  jQ                  |      }|rt         j#                  d|       tK        jJ                         | _)        tU        |      D ]-  }| j                  s nt        j                  d       d{    / | j                  ryy7 
# t*        $ r Y w xY w# 1 sw Y   JxY w# 1 sw Y   xY w# t*        $ r}|j                  |j(                  d      dz   }|||j(                  <   ||k\  rt         jE                  d||j(                  |       | j                  j<                  5  d|_        | j                  j?                          ddd       n# 1 sw Y   nxY w|jC                  |j(                  d       n#t         jA                  d|||j(                  |       Y d}~d}~ww xY w# t*        $ r!}	t         jA                  d|	       Y d}	~	5d}	~	ww xY w# t*        $ r!}	t         jA                  d|	       Y d}	~	d}	~	ww xY w# t*        $ r!}t         jA                  d |       Y d}~d}~ww xY w7 ĭw)!a  Background task that finalizes expired sessions.

        Runs every ``interval`` seconds (default 5 min).  For each session
        whose reset policy has expired, invokes ``on_session_finalize``
        hooks, cleans up the cached AIAgent's tool resources, evicts the
        cache entry so it can be garbage-collected, and marks the session
        so it won't be finalized again.
        r  Nry  rz  r{  r  r   r   r  c              3   0   K   | ]  \  }}| d |   yw)rz  Nr   )r  r  cs      r#   r  z8GatewayRunner._session_expiry_watcher.<locals>.<genexpr>  s#      .'+q!1#Qqc
.s   z,Session expiry: %d sessions to finalize (%s)r%  r?   r'  r)  ro  TzSession expiry finalized for %szkSession finalize gave up after %d attempts for %s: %s. Marking as finalized to prevent infinite retry loop.z*Session finalize failed (%d/%d) for %s: %sc              3   @   K   | ]  \  }}|j                   sd   yw)r   N)expiry_finalized)r  r  r  s      r#   r  z8GatewayRunner._session_expiry_watcher.<locals>.<genexpr>  s!       "a!:L:L s   z3Session expiry done: %d finalized, %d pending retryz!Session expiry done: %d finalizedz+Agent cache idle sweep: evicted %d agent(s)zIdle agent sweep failed: %s_last_session_store_prune_tsr  r   session_store_max_age_daysz,SessionStore prune: dropped %d stale entrieszSessionStore prune failed: %sz Session expiry watcher error: %s)+r^  r  r\  rZ  r  rA  r  r  rK  _is_session_expiredr`  rp  r]  rB   ra  sortedr=  rL  r+  r&  r  r   r&   rm  r2   r<  rd  r  r,  r9  r|  rA  rM  r-  r>  r  _sweep_idle_cached_agentsrN   r5   r  prune_old_entriesrL  range)r  r  _finalize_failures_MAX_FINALIZE_RETRIES_expired_entriesr+  r_   
_platforms_kr  _parts_plat_plat_summaryr-  	_platform_cached_agent_cache_lock_cachedr  failures_done_failed_idle_evicted_last_prune_ts_prune_interval_max_age_prunedr  s                               r#   r  z%GatewayRunner._session_expiry_watcher  s     mmB-/ !mmND""113#% "&t'9'9'B'B'H'H'J"K :JC-- --AA%H $++S%L9: $ 24J"2 IB!##-0[1_q	),6NN5!,Dq,H
5)I %)II ./5j6F6F6H/I. %M KKF,-}
 #3 <JC;
!V%(YYs^F58[1_q	"I( 5+0+;+;)2 )-&-d4G&N&2!, {*.*;*;*?*?*D>HRW>X
ip^evz{
 )0,0,@,@,D,DS,IM(]BY-Y 99-H
 005 "//55 759E2 ..4467 =!,, +..u/?/?FU<| $  &6  E ""23e;GQ!7
 ?D$($B$B$DM$I) ")/Ms!S"(99;//AJ#&#DKK1MqQVUV$ $a<&*&8&8&J&J8&TG& &$R$+!" 9=		D5 8_ '}}mmA&&&'c mm 	 T  ) ! !{ { 7 7 % #5#9#9%:J:JA#NQR#R?G*5+;+;<#'<<"NN!W (%*:*:A
 "&!3!3!9!9 ;9= 6 $ 2 2 8 8 :; ; ; /2253C3CTJ"LL L (*?AQAQSTZ ! DLL!>CCD. % J%DbIIJ  D?CCD 's6  WP%WD1V% %AP(&Q97P80A'Q"Q9AQ=AV% (U ;)V% %AU8 V% 4WWW#W(	P51Q4P55Q8Q	=QQ	
Q	UA+U"S1(	U1S:6AU=V% UV% 	U5U0*V% 0U55V% 8	V"VV% V""V% %	W.W
W
WWc                 D    	 ddl m}  |       xs dS # t        $ r Y yw xY w)z0Return the profile name this gateway represents.r   r  rH   )r  r  r   )r  r  s     r#   rp  z"GatewayRunner._active_profile_name"  s*    	C*,9	9 		s    	c                    !"#K   ddl m} 	 ddlm" d!d}t         di       }| _        t         d	d      ##s j                         ## _
        t        j                  d
       d{     j                  rt	 !"# fd}t        j                  |       d{   }|D ]  }|d   }|d   }	|j                  d      }
|d   xs dj!                         }	  ||      } j&                  j                  |      }|Yt
        j)                  d||d          t        j                   j*                  ||d   |j                  dd      |
       d{    |	r|	j,                  n|d   dd }|d   D ]  }|j.                  }|	r|	j0                  r|	j0                  nd}|rd| dnd}|dk(  rd}d}|j2                  r3|j2                  j                  d      rt5        |j2                  d         }|r*|j7                         j9                         d   dd }d| }nA|	r?|	j:                  r3|	j:                  j7                         j9                         d   dd }d| }d| d|d    d | | }n |d!k(  rVd}|j2                  r9|j2                  j                  d"      rd#t5        |j2                  d"         dd  }d$| d|d    d%| }n|d&k(  rVd}|j2                  r9|j2                  j                  d'      rdt5        |j2                  d'         dd  }d(| d|d    d)| }nj|d*k(  rd(| d|d    d+}nX|d,k(  rQd}|j2                  r3|j2                  j                  d-      rt=        |j2                  d-         }d.| d|d    d/| d0}ni }|j                  d1      r|d1   |d1<   |d   |d   |d2   |j                  d1      xs df}	 |j?                  |d2   ||3       d{    t
        j)                  d4||d   ||d2   |
       |jA                  |d        t        j                   j$                  ||d   |
       d{    |	xr |	jD                  d8v }|st        j                   jB                  ||
       d{     	 tG        t=        tI        d5|                  D ]-  }  j                  s yt        j                  d5       d{    /  j                  rsyy# t        $ r t
        j                  d       Y yw xY w7 7 {# t"        $ r2 t        j                   j$                  ||d   |
       d{  7   Y w xY w7 7 s# t        $ r}|j                  |d      d5z   }|||<   t
        j                  d6|d   ||||       ||k\  rXt
        j                  d7|d   ||       t        j                   jB                  ||
       d{  7   |jA                  |d       n?t        j                   j*                  ||d   |j                  dd      |
       d{  7   Y d}~ d}~ww xY w7 7 # t        $ r!}t
        j                  d9|       Y d}~d}~ww xY w7 w):u  Poll ``kanban_notify_subs`` and deliver terminal events to users.

        For each subscription row, fetches ``task_events`` newer than the
        stored cursor with kind in the terminal set (``completed``,
        ``blocked``, ``gave_up``, ``crashed``, ``timed_out``). Sends one
        message per new event to ``(platform, chat_id, thread_id)``,
        then advances the cursor. When a task reaches a terminal state
        (``completed`` / ``archived``), the subscription is removed.

        Runs in the gateway event loop; all SQLite work is pushed to a
        thread via ``asyncio.to_thread`` so the loop never blocks on the
        WAL lock. Failures in one tick don't stop subsequent ticks.

        **Multi-board:** iterates every board discovered on disk per
        tick. Subscriptions live inside each board's own DB and cannot
        cross boards, so delivery semantics are unchanged — this is
        purely a fan-out of the single-DB poll.
        r   r*  	kanban_dbz<kanban notifier: kanban_db not importable; notifier disabledN)r  blockedgave_upcrashedr
  ry  _kanban_sub_fail_countsrq  r  c                     g } j                   j                         D ch c]&  }t        |dt        |            j	                         ( }}|st
        j                  d       | S 	 j                  d      }t               }|D ]5  }|j                  d      xs j                  }|j                  d      }	 |r0t        t        |      j                         j                               n't        j!                  |      j                               }||v rt
        j                  d||       |j#                  |       	 j%                  |	      }		 j'                  |	      }|st
        j                  d|       |D ]&  }|j                  d      xs d }|r-|k7  r(t
        j                  d|j                  d      |       H|j                  d      xs dj	                         }||vr+t
        j                  d|j                  d      |xs d       j)                  |	|d   |d   |d   |j                  d      xs d      \  }}}|sԉj+                  |	|d         }t
        j                  dt-        |      |d   |||       | j/                  ||||||d       ) 	 |	j1                          8 | S c c}w # t        $ r  j                  j                        g}Y tw xY w# t        $ r	 d| }Y w xY w# t        $ r"}
t
        j                  d
||
       Y d }
~
d }
~
ww xY w# |	j1                          w xY w)Nr   z5kanban notifier: no connected adapters; skipping tickFinclude_archivedr  db_pathzslug:z;kanban notifier: skipping duplicate board slug %s for DB %sboardz)kanban notifier: cannot open board %s: %sz.kanban notifier: board %s has no subscriptionsnotifier_profilezUkanban notifier: subscription for %s owned by profile %s; current profile %s skippingtask_idr   r?   zIkanban notifier: subscription for %s on %s skipped; adapter not connectedz	<missing>r  r  )rv  r   r  r  kindsuF   kanban notifier: claimed %d event(s) for %s on board %s cursor %s→%s)r,   
old_cursorcursoreventsrt  rt  )rE  r,  r&   r*   rq  r=  rM  list_boardsr   read_board_metadataDEFAULT_BOARDr  rB   r   r  r   kanban_db_pathr  r  list_notify_subsclaim_unseen_events_for_subget_taskr]  r`  r2  )
deliveriesr   active_platformsboardsseen_db_paths
board_metar  rr  resolved_db_pathconnrE  subsr,   owner_profilerx  ry  rz  rt  TERMINAL_KINDS_kbru  r  s                     r#   _collectz8GatewayRunner._kanban_notifier_watcher.<locals>._collectd  sp   -/J )-(:(:(<($  '3x=AGGI($ ( ,%\]))N!$%!H /2eM&, J)
)~~f5J9J9J",..";>\cs4=3K3K3M3U3U3W/Xilmpmm  AE  nF  nN  nN  nP  jQ, ,}<"LL ] $&6 %%))*:;%#&;;T;#:D7) $'#7#7#=D#' &-]_c d'+ %#038J0K0St#0]FV5V$*LL((+	(:MK[%& %-,/GGJ,?,E2+L+L+N#+3C#C$*LL(s(+	(:H<S%& %-=@=\=\$(,/	N-0_,/	N.1ggk.B.Hb*8 >] >" :
FF (.$,'*||D#i.'I &$l$'KYzSY!" !+ 1 1+.2<.4.4,0-13" !#=%#N !JJLUJ)V &%{( % N"%"9"9#:K:K"L!MN  ) >16tf~,>  ) %"LL)TVZ\_`$%t !JJLsO   +K 'K :AK1LEL4%K.-K.1LL	L1L,,L14Mr,   rt  rt  r   r?   ry  zPkanban notifier: adapter %s disconnected before delivery for %s; rewinding claimrv  rx  x   rz  @ro  r  r  r  r[     u   ✔ zKanban u
    done — rk  rU  r  u   ⏸ z blockedrl  r  u   ✖ z& gave up after repeated spawn failuresrm  z1 worker crashed (pid gone); dispatcher will retryr
  limit_secondsu   ⏱ z timed out (max_runtime=zs); will retryr  r  r  z?kanban notifier: delivered %s event for %s to %s/%s on board %sr   z=kanban notifier: send failed for %s on %s (attempt %d/%d): %szRkanban notifier: dropping subscription %s on %s after %d consecutive send failures>   donearchivedzkanban notifier tick failed: %s)%r1  r  r  rj  r   r=  r>  r&   rn  rp  rq  r^  r  r\  	to_threadrB   rq  r8   _kanban_advancerE  rM  _kanban_rewindr,  kindassigneer  r*   r7   r  r  r5   r  r-  _kanban_unsubstatusrR  r  )$r  r  	_PlatformMAX_SEND_FAILURESsub_fail_countsr  r  dr,   rt  
board_slugr  platrk  r,  evr  whotaghandoffpayload_summaryhr  r[   rU  r
  limitr  sub_keyrE  failstask_terminalr  r  r  ru  s$   `                                @@@r#   r  z&GatewayRunner._kanban_notifier_watcher*  s    & 	9	3
 U" ,3+R-
 (7$"4)CTJ#88:,<D) mmAmmyG_&B $+#4#4X#>>
# TAE(CV9D!"wJ$'
O$9r#@#@#BL!(6 #mm//5Gn(#i. &// //hKEE,2&   !+/TZZS^TcJEk w!ww 15t}}D,/#ajR;. ')G.2O!zzbjjnnY.G25bjj6K2L.$3$9$9$;$F$F$H$KDS$Q,.qc(!%$++$(KK$5$5$7$B$B$DQ$G$M,.qc("&se73y>2B C((-wwi!9   "Y.%'F!zzbjjnnX.F+-c"**X2F.G.M-N)O$(WS^4DHVH"UC!Y."$C!zzbjjnnW.E(*3rzz'/B+CDS+I*J&K"&se73y>2B C@@Cu!F   "Y."&se73y>2B CD !E   "[0$%E!zzbjjnn_.M(+BJJ,G(H"&se73y>2B C005wn!F  
 %3577;/474DH[1	NC
O	NCGGK,@,FB#&""),, #Ih #/ #   #LL a $c)nlC	NT^
 ,//>OwR &// 00#q{J   )-(T@T1T(")"3"3 $ 2 2C#  eTp 3s1h/01 '}}mmA&&&'y mmE  	NNYZ	@ 	J ? & ! &// 00#q{J   !!P  ) "$3$7$7$Ca$GE7<OG4"NN!6 #Ie 13	  %(99 &%R$'	NL%!"
 '.&7&78J8JCQ[&\ \ \ / 3 3GT B&-&7&7$($7$7$'$%hK$%EE,$:$.'" !" !" "9"B  G@#FFG 's;  Z S1 AZ *T+Z = Y T<Y T#A0Y UI:Y U)U*6U +Y Y
Y &$Y 
YY AZ Y=Z /Z 1TZ TZ Y 1UUUY UY U	Y(A;Y#W&
$AY5X8
6Y;Y YY Y 	Y:Y5/Z 5Y::Z r,   ry  rt  c           	          ddl m} |j                  |      }	 |j                  ||d   |d   |d   |j	                  d      xs d|	       |j                          y
# |j                          w xY w)zSync helper: advance a subscription's cursor. Runs in to_thread.

        ``board`` scopes the DB connection to the board that owns this
        subscription. Unsub cursors in one board can't touch another's.
        r   ri  rs  rv  r   r  r  r?   )rv  r   r  r  
new_cursorN)r  rj  r  advance_notify_cursorrB   r2  )r  r,   ry  rt  r  r  s         r#   r  zGatewayRunner._kanban_advancec  sp     	0{{{'
	%%IZI''+.4"! &  JJLDJJLs   3A A0c           	          ddl m} |j                  |      }	 |j                  ||d   |d   |d   |j	                  d      xs d	       |j                          y # |j                          w xY w)
Nr   ri  rs  rv  r   r  r  r?   )rv  r   r  r  )r  rj  r  remove_notify_subrB   r2  )r  r,   rt  r  r  s        r#   r  zGatewayRunner._kanban_unsuby  sk    /{{{'		!!IZI''+.4" "  JJLDJJLs   2A A/claimed_cursorrx  c           	          ddl m} |j                  |      }	 |j                  ||d   |d   |d   |j	                  d      xs d||	       |j                          y
# |j                          w xY w)zCSync helper: undo a claimed notification cursor after send failure.r   ri  rs  rv  r   r  r  r?   )rv  r   r  r  r  rx  N)r  rj  r  rewind_notify_cursorrB   r2  )r  r,   r  rx  rt  r  r  s          r#   r  zGatewayRunner._kanban_rewind  ss     	0{{{'	$$IZI''+.4"-% %  JJLDJJLs   4A A1c                 ~	  K   	 ddl m} t
        j                  j                  dd      j                         j                         }|dv rt        j                  d       y	  |       }t        |t              r|j                  d
i       ni }|j                  dd      st        j                  d       y	 ddlm t        |j                  dd      xs d      }t!        |d      }|j                  dd      t        j                  d        |j                  dj"                        }	 t%        |      dk  r-t        j	                  d|j"                         j"                  t+        j,                  d       d{    d}d}	d}
dt.        ddffdd+fd}dt0        ffd }t        j                  d!|       | j2                  r	 t+        j4                  |       d{   }d"}|xs g D ]  \  }}|	t7        |d#d      sd}t        j                  d$|t9        |j:                        |j<                  t?        |j@                  d%      rt9        |j@                        ndt?        |jB                  d%      rt9        |jB                        nd|jD                  t?        |jF                  d%      rt9        |jF                        nd        t+        j4                  |       d{   }|r|s|	dz  }	nd}	|	|k\  r=t%        tI        jH                               }||
z
  d&k\  rt        j	                  d'|	       |}
d*}||k  rM| j2                  rAt+        j,                  tQ        d||z
               d{    |dz  }||k  r| j2                  rA| j2                  ryy# t        $ r t        j	                  d       Y yw xY w# t        $ r }t        j	                  d	|       Y d}~yd}~ww xY w# t        $ r t        j	                  d       Y yw xY w# t&        t(        f$ r1 t        j	                  d|j"                         j"                  Y "w xY w7 7 u7 t# t*        jJ                  $ r t        jM                  d(        t        $ r t        jO                  d)       Y mw xY w7 9w),u  Embedded kanban dispatcher — one tick every `dispatch_interval_seconds`.

        Gated by `kanban.dispatch_in_gateway` in config.yaml (default True).
        When true, the gateway hosts the single dispatcher for this profile:
        no separate `hermes kanban daemon` process needed. When false, the
        loop exits immediately and an external daemon is expected.

        Each tick calls :func:`kanban_db.dispatch_once` inside
        ``asyncio.to_thread`` so the SQLite WAL lock never blocks the
        event loop. Failures in one tick don't stop subsequent ticks —
        same pattern as `_kanban_notifier_watcher`.

        Shutdown: the loop checks ``self._running`` between ticks; gateway
        stop() flips it to False and cancels pending tasks, and the
        in-flight ``to_thread`` returns on its own after the current
        ``dispatch_once`` call finishes (typically <1ms on an idle board).
        r   r2  z6kanban dispatcher: config loader unavailable; disabledN!HERMES_KANBAN_DISPATCH_IN_GATEWAYr?   >   0nor  falsezEkanban dispatcher: disabled via HERMES_KANBAN_DISPATCH_IN_GATEWAY envz4kanban dispatcher: cannot load config (%s); disabledkanbandispatch_in_gatewayTzGkanban dispatcher: disabled via config kanban.dispatch_in_gateway=falseri  z@kanban dispatcher: kanban_db not importable; dispatcher disableddispatch_interval_secondsr  r  	max_spawnzkanban dispatcher: max_spawn=failure_limitzDkanban dispatcher: invalid kanban.failure_limit=%r; using default %dr   zGkanban dispatcher: kanban.failure_limit=%r is below 1; using default %dr     r  r   zOptional[object]c                 ~   d}	 j                  |       }j                  ||       |	 |j                          S S # t        $ r Y S w xY w# t        $ r< t        j                  d|        Y |!	 |j                          y# t        $ r Y yw xY wyw xY w# |!	 |j                          w # t        $ r Y w w xY ww xY w)a  Run one dispatch_once for a specific board.

            Runs in a worker thread via `asyncio.to_thread`. `board=slug`
            is passed through `dispatch_once` so `resolve_workspace` and
            `_default_spawn` see the right paths. The per-board DB is
            opened explicitly so concurrent boards never share a
            connection handle or accidentally claim across each other.
            Nrs  )rt  r  r  z*kanban dispatcher: tick failed on board %s)r  dispatch_oncer2  r   r=  	exception)r  r  r  r  r  s     r#   _tick_once_for_boardzFGatewayRunner._kanban_dispatcher_watcher.<locals>._tick_once_for_board  s     D{{{. (('"/	 )  #

 $ %     !MtT#

$  $	 #

$  $sj   &A A  	AAB.B 2B 	BBBB B<B,+B<,	B85B<7B88B<c                     	 j                  d      } g }| D ]:  }|j	                  d      xs j                  }|j                  | |      f       < |S # t        $ r j                  j                        g} Y jw xY w)a  Run one dispatch_once per board. Returns (slug, result) pairs.

            Enumerating boards on every tick keeps the dispatcher honest
            when users create a new board mid-run: no restart required,
            the next tick picks it up automatically.
            Frp  r  )r{  r   r|  r}  rB   r`  )r  outbr  r  r  s       r#   
_tick_oncez<GatewayRunner._kanban_dispatcher_watcher.<locals>._tick_once  s    F%@ 9;C ?uuV}9(9(9

D"6t"<=>? J  F11#2C2CDEFs   A %B ?B c                  f   	 j                  d      } | D ]t  }|j	                  d      xs j                  }d}	 j                  |      }j                  |      r	 |	 |j                           y y	 |d	 |j                          v y# t        $ r j                  j                        g} Y w xY w# t        $ r Y  yw xY w# t        $ r& Y |	 |j                          # t        $ r Y w xY ww xY w# t        $ r Y w xY w# |!	 |j                          w # t        $ r Y w w xY ww xY w)a~  Cheap probe: is there at least one ready+assigned+unclaimed
            task on ANY board whose assignee maps to a real Hermes profile
            (i.e. one the dispatcher would actually spawn for)?

            Tasks assigned to control-plane lanes (e.g. ``orion-cc``,
            ``orion-research``) are pulled by terminals via
            ``claim_task`` directly and never spawnable, so a queue full
            of those is "correctly idle", not "stuck". Filtering them out
            here keeps the stuck-warn fire only on real failures (broken
            PATH, missing venv, credential loss for a real Hermes profile).
            Frp  r  Nrs  T)r{  r   r|  r}  rB   r  has_spawnable_readyr2  )r  r  r  r  r  s       r#   _ready_nonemptyzAGatewayRunner._kanban_dispatcher_watcher.<locals>._ready_nonempty)  sa   F%@  !uuV}9(9(9!;;T;2D..t4# '! JJL ( 5
 '! JJL! #  F11#2C2CDEF  ) ! ! ! '! JJL( ! !  ) ! ! '! JJL( ! ! (s   B #C
#B:<C<%B76B7:	CC
	C9DC))	C54C58C99D<	DDD0D D0 	D,	)D0+D,	,D0z7kanban dispatcher: embedded in gateway (interval=%.1fs)Fspawnedzckanban dispatcher [%s]: spawned=%d reclaimed=%d crashed=%d timed_out=%d promoted=%d auto_blocked=%d__len__r  zkanban dispatcher stuck: ready queue non-empty for %d consecutive ticks but 0 workers spawned. Check profile health (venv, PATH, credentials) and `hermes kanban list --status ready`.zkanban dispatcher: cancelledz+kanban dispatcher: unexpected watcher errorr  )r   z"list[tuple[str, Optional[object]]]))r   r3  r   r=  r>  r@   rA   rB   r7   rq  rL  r2   rh   r  rj  r6   r  DEFAULT_FAILURE_LIMITr5   rD   r8   r^  r  r*   r4   r\  r  r&   r]  r  	reclaimedr  rm  r
  promotedauto_blockedrN   r&  rM  r  min)r  _load_configenv_overrider   rE  
kanban_cfgr  raw_failure_limitHEALTH_WINDOW	bad_tickslast_warn_atr  r  resultsany_spawnedr  resready_pendingrK   sleptr  r  r  r  s                       @@@@r#   r  z(GatewayRunner._kanban_dispatcher_watcher  sK    ,	E zz~~&I2NTTV\\^66KK_`	.C /9d.CSWWXr*
~~3T:KKY 	3
 (CRHNBOx% NN;5	 KK7	{CD&NN?C<U<UV	6 12M 1NNY!))
  55M
 mmA
 	 	s  	/A  	D	"	 	B 	Ex	
 mm(P ' 1 1* ==#")-R ID#73	4+H&* R ,MM07Y0OC,UV29#--2SC.YZLL5<S=M=My5YC 0 01_`
" '.&7&7&H H NI !I-diik*C\)S0C & (+ E("t}}mmCX-=$>??? ("t}}] mmo  	NNST	  	NNQSVW	  	NN]^	 :& 	6NNV!))
  55M	6$ 	D >& !I  )) ;< P  !NOP @s  R=N- AR=%O ,AR=9O= ?A3R=3P! >A
R=Q$	AR=Q- 2Q'3Q- Q- C Q- 6Q*7AQ- 	8R=R:R=R=+R=-OR=OR=	O:O50R=5O::R==PR=PR=!<Q!R= Q!!R='Q- *Q- -AR73R=6R77R=c           
      	  K   d}d}t        j                  d       d{    | j                  r| j                  s<t	        d      D ]-  }| j                  s yt        j                  d       d{    / Ut        j                         }t        | j                  j                               D ]  }| j                  s y| j                  |   }||d   k  r*|d   |k\  r2t        j                  d	|j                  |d          | j                  |= d|d
   }|d   dz   }t        j                  d|j                  ||       	 | j                  ||      }	|	s.t        j                  d|j                         | j                  |= |	j                  | j                         |	j!                  | j"                         |	j%                  | j&                         |	j)                  | j*                         | j-                  |	|       d{   }
|
r|	| j.                  |<   | j1                  |	       | j.                  | j2                  _        | j                  |= | j5                  |j                  ddd       t        j                  d|j                         	 ddlm}  || j.                         d{    n|	j<                  rx|	j>                  sl| j5                  |j                  d|	j@                  |	jB                         t        j                  d|j                  |	jB                         | j                  |= n| j5                  |j                  d|	j@                  |	jB                  xs d       tE        dd|dz
  z  z  |      }||d<   t        j                         |z   |d<   t        j                  d|j                  |        t	        d      D ]-  }| j                  s yt        j                  d       d{    / | j                  ryy7 7 7 7 n# t:        $ r Y ew xY w# t:        $ r}| j5                  |j                  ddtG        |             tE        dd|dz
  z  z  |      }||d<   t        j                         |z   |d<   t        j                  d|j                  ||       Y d}~d}~ww xY w7 ƭw)u  Background task that periodically retries connecting failed platforms.

        Uses exponential backoff: 30s → 60s → 120s → 240s → 300s (cap).
        Stops retrying a platform after 20 failed attempts or if the error
        is non-retryable (e.g. bad auth token).
           r  
   NrH  r   rK  rJ  z+Giving up reconnecting %s after %d attemptsr  z"Reconnecting %s (attempt %d/%d)...zGReconnect %s: adapter creation returned None, removing from retry queuer  rD  u   ✓ %s reconnected successfullyr   r  rC  zAReconnect %s: non-retryable error (%s), removing from retry queuerB  zfailed to reconnectr{  z&Reconnect %s failed, next retry in %dsz)Reconnect %s error: %s, next retry in %ds)$r^  r  r\  ru  rR  rN   r  rA  r,  r=  r>  r   rL  r  r  r  r  rT  r  rZ  r  r  r  rE  r  r[  rN  r  r  r   r  rO  rL  rM  r  r*   )r  _MAX_ATTEMPTS_BACKOFF_CAPr  rK   r   rL  rS  attemptrk  r  r  backoffr  s                 r#   r  z)GatewayRunner._platform_reconnect_watcher  sj     mmBmm))r +A==!--***+ .."C !7!7!<!<!>? a}}--h7l++
#}4NNE Z(8 ..x8"&x.z*Q.8NNG]
K"228_MG"e$NN !228< //0D0DE33D4T4TU--d.@.@A44T5]5]^$($F$FwPX$YYG29h/>>wG8<,,5 228<<<$NN+6'+*.	 =  $Ex~~V!Y"9$--"HHH !009V9V<<$NN+2'.'?'?*1*E*E	 =  _$NNG,G,G !228<<<$NN+5'.'?'?*1*E*E*^I^	 =  #&bA'A+,>&?"N+2Z(-1^^-=-G\*D$NNGaaH 2Y '}}mmA&&&'] mm 	  +R Z" I( ! !: ! 88 '1#'&)!f	 9  ""gk(:";\JG'.D$)-)9G)CD&NNC 7 & 's   SPAS1P2CSAP1SBP1PBP1P"1P2P"6DP17S?S SSSP1P""	P.+P1-P..P11	S:A>R>8S>SSrm  r\  rn  ro  c                    K   |rd _         | _        | _         j                   j                   d{    yd fd}t	        j
                   |              _         j                   d{    y7 ?7 w)z-Stop the gateway and disconnect all adapters.TNc            	      H  K   dt         dd fd} t        j                  dj                  rdnd       t	        j
                         dt        ffd}d_        d	_        j                          d {    t        j                  d
 |              j                  }t	        j
                         }j                  |       d {   \  }}t        j                  d |       t	        j
                         |z
  |t        |      j                                |rt        j                  d|j                                j                  rdnd}t        j                   j#                               D ]+  \  }}|t$        u r	 j&                  j)                  ||       - j/                  j                  rt0        nt2               t5        j6                         j	                         dz   }
j                   rt5        j6                         j	                         |
k  r`j9                  d       t5        j:                  d       d {    j                   r&t5        j6                         j	                         |
k  r` | d       t        j                  d |              j                  r%j<                  r	 j?                          d {    jC                  |       tE        dd       }tE        dd       }|e|c|5  t        |jG                               }|jI                          d d d        D ]*  }tK        |tL              r|d   n|}jO                  |       , t        jP                  j#                               D ]  \  }}t	        j
                         }	 |jS                          d {    	 |jW                          d {    t        j                  d|jT                  t	        j
                         |z
          t        j                  d |              t        jX                        D ]!  }|jZ                  u r|j]                          # jX                  jI                          jP                  jI                          j                   jI                          j^                  jI                          j`                  jI                          jb                  jI                          te        d      rjf                  jI                          jh                  jk                           | d       t        j                  d |              	 dd l6m7}  |        tE        d"d       fD ]3  }|rtE        |d#d       nd }|te        |d$      s#	 |jq                          5 t        j                  d& |              dd'l9m:}m;}  |         |        |s	 tx        d(z  j{                          nt        j                  d)       |r(j}                  tk        |j                                      j                  r,j                  r t        _B        j                  xs d*_C        d_        j9                  d+j                         t        j                  d, |              y 7 B7 # t*        $ r"}	t        j-                  d||	       Y d }	~	ld }	~	ww xY w7 7 # t*        $ r!}t        jA                  d|       Y d }~@d }~ww xY w# 1 sw Y   xY w7 r# t*        $ r,}t        j-                  d|jT                  |       Y d }~d }~ww xY w7 # t*        $ rB}t        jA                  d|jT                  t	        j
                         |z
  |       Y d }~$d }~ww xY w# t*        $ r!}	t        j-                  d!|	       Y d }	~	ld }	~	ww xY w# t*        $ r!}	t        j-                  d%|	       Y d }	~	d }	~	ww xY w# t*        $ r Y w xY ww)-Nphaser   c                    	 ddl m} |j                         }|rt        j	                  d| |       	 ddlm}  |        	 ddl	m
}  |        y# t
        $ r!}t        j                  d| |       Y d}~Bd}~ww xY w# t
        $ r!}t        j                  d| |       Y d}~ad}~ww xY w# t
        $ r!}t        j                  d	| |       Y d}~yd}~ww xY w)
uw  Kill tool subprocesses + tear down terminal envs + browsers.

                Called twice in the shutdown path: once eagerly after a
                drain timeout forces agent interrupt (so we reclaim bash/
                sleep children before systemd TimeoutStopSec escalates to
                SIGKILL on the cgroup — #8202), and once as a final
                catch-all at the end of _stop_impl() for the graceful
                path or anything respawned mid-teardown.

                All steps are best-effort; exceptions are swallowed so
                one subsystem's failure doesn't block the rest.
                r   r'  z,Shutdown (%s): killed %d tool subprocess(es)z(process_registry.kill_all (%s) error: %sN)cleanup_all_environmentsz'cleanup_all_environments (%s) error: %s)cleanup_all_browsersz#cleanup_all_browsers (%s) error: %s)rY  r(  kill_allr=  rL  r   rM  tools.terminal_toolr  tools.browser_toolr  )r  r(  _killedr  r  r  s         r#   _kill_tool_subprocesseszGGatewayRunner.stop.<locals>._stop_impl.<locals>._kill_tool_subprocesses  s    	XG.779GJ!7WL,.SG(* ! XLL!KUTVWWX
 ! WLL!JESUVVW
 ! SLL!FrRRSsF   /A A;  B( 	A8A33A8;	B%B  B%(	C1CCzStopping gateway%s...z for restartr?   c                  2    t        j                          z
  S r  )rN   r  )_stop_started_ats   r#   _phase_elapsedz>GatewayRunner.stop.<locals>._stop_impl.<locals>._phase_elapsed6  s    ~~'*:::r%   FTz5Shutdown phase: notify_active_sessions done at +%.2fszhShutdown phase: drain done at +%.2fs (drain took %.2fs, timed_out=%s, active_at_start=%d, active_now=%d)zYGateway drain timed out after %.1fs with %d active agent(s); interrupting remaining work.rv  rw  z%mark_resume_pending failed for %s: %sr   r  r  zpost-interruptz7Shutdown phase: post-interrupt tool kill done at +%.2fsz-Failed to launch detached gateway restart: %sro  rm  r   u'   ✗ %s background-task cancel error: %su   ✓ %s disconnected (%.2fs)u'   ✗ %s disconnect error after %.2fs: %sz3Shutdown phase: all adapters disconnected at +%.2fsrh  zfinal-cleanupz6Shutdown phase: final-cleanup tool kill done at +%.2fs)shutdown_cached_clientsz!shutdown_cached_clients error: %srZ  _dbr2  zSessionDB close error: %sz.Shutdown phase: SessionDB close done at +%.2fs)remove_pid_filerelease_gateway_runtime_lockr  u   Skipping .clean_shutdown marker — drain timed out with interrupted agents; next startup will suspend recently active sessions.zGateway restart requestedstoppedz&Gateway stopped (total teardown %.2fs))Dr*   r=  rL  r  rN   r  r6   r\  r  r#  r  r  r]  rZ  r>  rA  rd  r  r  rZ  mark_resume_pendingr   rM  r  !_INTERRUPT_REASON_GATEWAY_RESTART"_INTERRUPT_REASON_GATEWAY_SHUTDOWNr^  r   r  r  r!  rf  r  r.  r&   r*  r  r2   r<  r,  rE  cancel_background_tasksr   r  r  r#  cancelr  re  rt  r  rh  r`  r  r4  r  r2  r  r  r  r   touchr;  r,  r"  r*  r  rc  )r  r  r   _drain_started_atr  r
  _resume_reason_sk_agentr  interrupt_deadliner  r]  _cache_idle_agents_entryr   rk  _adapter_started_at_taskr  
_db_holderr  r  r  r  r  s                            @r#   
_stop_implz&GatewayRunner.stop.<locals>._stop_impl  s     Ss  St  SD KK'"&"9"9r  $~~/;E ; "DM!DN ::<<<KKG 
 11G $ 0-1-F-Fw-O'O$M9KKC  #44M"))+ o--/6 *.)@)@%FX  $((<(<(B(B(D#E 	KC!88 **>>sNS		 ..9=9P9P5Vx &-%=%=%?%D%D%F%L"**w/G/G/I/N/N/PSe/e//
;!--,,, **w/G/G/I/N/N/PSe/e ((89M"$
 &&4+A+AU??AAA **=9 "$(;TBKT>48F&6+=  ##'#8LLLN# + :F%/%>q	F  11&9	: &*$--*=*=*?%@ !'&*nn&6#_!99;;;!,,...KK5 (+>>( KKE 
 d445 DOO+ ""((*MM!  &&(##))+""((*##))+t^,!!'')  $$& $O4KKH FJ')  $WT?D%IJ B
:Dgj%6$;gc7&;BIIKB KK@ 
 U(* !$55<<> ' 66s=;M;M;O7PQ&&4+D+D"C$($5$5$T9T!"DN''	43D3DEKK@.BRSG = (PZ % C  -$ B  ULL!PRSTTU# # <  _LL!JHNN\]^^_ / ! LLA (+>>	 d  F@"EEF ! BLL!<bAAB, ! s  A2`"6Z87A`"
Z;B<`"Z>$B(`"[,5`";`"?[2 [/[2 1`"*\2A4`"'\/:\,;\/ ]*]':]*E`"$^8 12`"$_%45`"*` B8`";`">	[)[$`"$[))`"/[2 2	\;\`"\`"\)$`",\//	]$8!]`"]$$`"']**	^537^0*`"0^55`"8	_"_`"_""`"%	`.`
`"
``"	``"``"rq  )r  r!  r"  r#  r^  rr  )r  r\  rn  ro  r  s   `    r#   rQ  zGatewayRunner.stop  sv      &*D#%5D"(7D%??&//!!T	Tl "--jl;oou "t 	s!   3A:A69A:0A81A:8A:c                 T   K   | j                   j                          d{    y7 w)zWait for shutdown signal.N)r`  waitr  s    r#   wait_for_shutdownzGatewayRunner.wait_for_shutdown&  s     ""'')))s   (&(c                    t        |d      r{t        |j                  t              ra|j                  j	                  d| j
                  j                         |j                  j	                  dt        | j
                  dd             	 ddlm	} |j                  |j                        rA|j                  |j                  |      }||S t        j                  d|j                         y	 |t"        j$                  k(  rdd
lm}m}  |       st        j-                  d       y ||      }t/        j0                  dd      }|sE	 t3               }	t5        |	dddd      }
|
dvr't7        |
      j9                         j;                         }|xs d}|dvrt        j-                  d|       d}||_        |S |t"        j>                  k(  r6ddl m!}m"}  |       st        j-                  d       y ||      }| |_#        |S |t"        jH                  k(  r-ddl%m&}m'}  |       st        j-                  d       y ||      S |t"        jP                  k(  r-ddl)m*}m+}  |       st        j-                  d       y ||      S |t"        jX                  k(  r-ddl-m.}m/}  |       st        j-                  d       y ||      S |t"        j`                  k(  r-ddl1m2}m3}  |       st        j-                  d       y ||      S |t"        jh                  k(  r-dd l5m6}m7}  |       st        j-                  d!       y ||      S |t"        jp                  k(  r-dd"l9m:}m;}  |       st        j-                  d#       y ||      S |t"        jx                  k(  r-dd$l=m>}m?}  |       st        j-                  d%       y ||      S |t"        j                  k(  r-dd&lAmB}mC}  |       st        j-                  d'       y ||      S |t"        j                  k(  r-dd(lEmF}mG}  |       st        j-                  d)       y ||      S |t"        j                  k(  r-dd*lImJ}mK}   |        st        j-                  d+       y ||      S |t"        j                  k(  r-dd,lMmN}!mO}"  |"       st        j-                  d-       y |!|      S |t"        j                  k(  r-dd.lQmR}#mS}$  |$       st        j-                  d/       y |#|      S |t"        j                  k(  r-dd0lUmV}%mW}&  |&       st        j-                  d1       y |%|      S |t"        j                  k(  r-dd2lYmZ}'m[}(  |(       st        j-                  d3       y |'|      S |t"        j                  k(  r6dd4l]m^})m_}*  |*       st        j-                  d5       y |)|      }| |_#        |S |t"        j                  k(  r-dd6lamb}+mc},  |,       st        j-                  d7       y |+|      S |t"        j                  k(  r-dd8lemf}-mg}.  |.       st        j-                  d9       y |-|      S |t"        j                  k(  r-dd:limj}/mk}0  |0       st        j-                  d;       y |/|      S |t"        j                  k(  r(dd<lmmn}1mo}2 |2st        j-                  d=       y |1|      S y# t        $ r,}t        j!                  d	|j                  |       Y d}~d}~ww xY w# t        $ r Y }w xY w)>zCreate the appropriate adapter for a platform.

        Checks the platform_registry first (plugin adapters), then falls
        through to the built-in if/elif chain for core platforms.
        r6  r  r  Fr   r  NzWPlatform '%s' is registered but adapter creation failed (check dependencies and config)z,Platform registry lookup for '%s' failed: %s)TelegramAdaptercheck_telegram_requirementsz+Telegram: python-telegram-bot not installedHERMES_TELEGRAM_NOTIFICATIONSr?   r   rP  r   notificationsr  	important>   r  r  z[Unknown telegram notifications mode '%s', defaulting to 'important' (valid: all, important))DiscordAdaptercheck_discord_requirementsz!Discord: discord.py not installed)WhatsAppAdaptercheck_whatsapp_requirementsz8WhatsApp: Node.js not installed or bridge not configured)SlackAdaptercheck_slack_requirementszGSlack: slack-bolt not installed. Run: pip install 'hermes-agent[slack]')SignalAdaptercheck_signal_requirementsz8Signal: SIGNAL_HTTP_URL or SIGNAL_ACCOUNT not configured)HomeAssistantAdaptercheck_ha_requirementsz:HomeAssistant: aiohttp not installed or HASS_TOKEN not set)EmailAdaptercheck_email_requirementszQEmail: EMAIL_ADDRESS, EMAIL_PASSWORD, EMAIL_IMAP_HOST, or EMAIL_SMTP_HOST not set)
SmsAdaptercheck_sms_requirementszJSMS: aiohttp not installed or TWILIO_ACCOUNT_SID/TWILIO_AUTH_TOKEN not set)DingTalkAdaptercheck_dingtalk_requirementszLDingTalk: dingtalk-stream not installed or DINGTALK_CLIENT_ID/SECRET not set)FeishuAdaptercheck_feishu_requirementsz?Feishu: lark-oapi not installed or FEISHU_APP_ID/SECRET not set)WecomCallbackAdapter!check_wecom_callback_requirementsz*WeComCallback: aiohttp/httpx not installed)WeComAdaptercheck_wecom_requirementsz;WeCom: aiohttp not installed or WECOM_BOT_ID/SECRET not set)WeixinAdaptercheck_weixin_requirementsz*Weixin: aiohttp/cryptography not installed)MattermostAdaptercheck_mattermost_requirementszJMattermost: MATTERMOST_TOKEN or MATTERMOST_URL not set, or aiohttp missing)MatrixAdaptercheck_matrix_requirementsz\Matrix: mautrix not installed or credentials not set. Run: pip install 'mautrix[encryption]')APIServerAdaptercheck_api_server_requirementsz!API Server: aiohttp not installed)WebhookAdaptercheck_webhook_requirementszWebhook: aiohttp not installed)MSGraphWebhookAdapter"check_msgraph_webhook_requirementsz&MSGraph webhook: aiohttp not installed)BlueBubblesAdaptercheck_bluebubbles_requirementsz`BlueBubbles: aiohttp/httpx missing or BLUEBUBBLES_SERVER_URL/BLUEBUBBLES_PASSWORD not configured)	QQAdaptercheck_qq_requirementszIQQBot: aiohttp/httpx missing or QQ_APP_ID/QQ_CLIENT_SECRET not configured)YuanbaoAdapterWEBSOCKETS_AVAILABLEz>Yuanbao: websockets not installed. Run: pip install websockets)pr  r2   r6  rh   rj  r  r  r&   r  r  is_registeredr   create_adapterr=  r  r   rM  r  r	  gateway.platforms.telegramr  r  r>  r@   r<  r  r   r*   r7   rq  _notifications_modeDISCORDgateway.platforms.discordr  r  gateway_runnerWHATSAPPgateway.platforms.whatsappr  r  SLACKgateway.platforms.slackr  r  SIGNALgateway.platforms.signalr  r  HOMEASSISTANTgateway.platforms.homeassistantr  r  EMAILgateway.platforms.emailr  r  SMSgateway.platforms.smsr  r  DINGTALKgateway.platforms.dingtalkr  r   FEISHUgateway.platforms.feishur!  r"  WECOM_CALLBACK gateway.platforms.wecom_callbackr#  r$  WECOMgateway.platforms.wecomr%  r&  WEIXINgateway.platforms.weixinr'  r(  
MATTERMOSTgateway.platforms.mattermostr)  r*  MATRIXgateway.platforms.matrixr+  r,  r  gateway.platforms.api_serverr-  r.  r  gateway.platforms.webhookr/  r0  r  !gateway.platforms.msgraph_webhookr1  r2  BLUEBUBBLESgateway.platforms.bluebubblesr3  r4  QQBOTgateway.platforms.qqbotr5  r6  YUANBAOgateway.platforms.yuanbaor7  r8  )3r  r   r  r  rk  r  r  r  _notify_mode_gw_cfg_rawr  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  r1  r2  r3  r4  r5  r6  r7  r8  s3                                                      r#   r  zGatewayRunner._create_adapter*  s    67#
6<<(FLL##)33 LL##*%?G	\C ..x~~>+::8>>6R&"N 6NN
  ?  x(((_.0LM%f-G 99%DbIL24G"7I{JP_`D:-'*4y'8'>'>'@ (6;L#77H 
  +*6G'N)))\-/BC$V,G%)G"N***_.0YZ"6**'V+-hi''(Y,.YZ ((///c(*[\'//'V+-rs''%P)+klf%%***_.0mn"6**(Y,.`a ((000 56KL'//'V+-\]''(Y,.KL ((,,,e02kl$V,,(Y,.}~ ((,,,d02BC#F++)))\-/?@$V,G%)G"N111 67GH(00---h13   B  C%f--'P(*jkV$$)))V'_`!&))o  	\LLGYZ[[	\( ! s2   	A [ 
 [ A[: 	[7![22[7:	\\c                    |j                   t        j                  t        j                  hv ry|j                  }|syi t        j
                  dt        j                  dt        j                  dt        j                  dt        j                  dt        j                  dt        j                  d	t        j                  d
t        j                  dt        j                  dt        j                  dt        j                   dt        j"                  dt        j$                  dt        j&                  dt        j(                  dt        j*                  d}t        j
                  di}t        j
                  dt        j(                  di}i t        j
                  dt        j                  dt        j                  dt        j                  dt        j                  dt        j                  dt        j                  dt        j                  dt        j                  dt        j                  d t        j                  d!t        j                   d"t        j"                  d#t        j$                  d$t        j&                  d%t        j(                  d&t        j*                  d'}t        j                  d(t        j                  d)i}|j                   |vrx	 d*d+lm} |j1                  |j                   j2                        }	|	rJ|	j4                  r|	j4                  ||j                   <   |	j6                  r|	j6                  ||j                   <   |j1                  |j                   d,      }
|
r't;        j<                  |
d,      j?                         d-v rytA        |d.d      rR|j1                  |j                         }|r5t;        j<                  |d/      j?                         jC                         d0v ry|j                   t        j                  k(  r%t;        j<                  d1d,      jC                         ry|j                   r|j                   j2                  nd,}| jD                  jG                  ||      ryt;        j<                  |j1                  |j                   d,      d,      jC                         }d,}d,}|jH                  d2v r|t;        j<                  |j1                  |j                   d,      d,      jC                         }t;        j<                  |j1                  |j                   d,      d,      jC                         }t;        j<                  d3d,      jC                         }|s,|s*|s(|s&t;        j<                  d4d,      j?                         d-v S |rj|jH                  d2v r\|jJ                  rP|jM                  d5      D ch c]#  }|jC                         s|jC                         % }}d6|v s|jJ                  |v ry|j                   t        j
                  k(  r|r|jH                  d2v r|jJ                  r|jM                  d5      D ch c]1  }|jC                         jO                  d7      r|jC                         3 }}|rQtA        | d8d      s5tP        jS                  d9d5jU                  tW        |                   d| _,        |jJ                  |v ryt[               }|r'|j]                  d: |jM                  d5      D               |r'|j]                  d; |jM                  d5      D               |r'|j]                  d< |jM                  d5      D               d6|v ry|h}d=|v r#|j_                  |jM                  d=      d*          |j                   t        j                  k(  rgt[               }|D ]  }|j]                  ta        |              |r|}|j]                  ta        |             tc        |      }|r|j_                  |       te        ||z        S # t8        $ r Y w xY wc c}w c c}w )>ao  
        Check if a user is authorized to use the bot.
        
        Checks in order:
        1. Per-platform allow-all flag (e.g., DISCORD_ALLOW_ALL_USERS=true)
        2. Environment variable allowlists (TELEGRAM_ALLOWED_USERS, etc.)
        3. DM pairing approved list
        4. Global allow-all (GATEWAY_ALLOW_ALL_USERS=true)
        5. Default: deny
        TFr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  QQ_GROUP_ALLOWED_USERSr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  DISCORD_ALLOW_BOTSFEISHU_ALLOW_BOTSr   r  r?   >   r   r  r  is_botr  >   r  mentionsDISCORD_ALLOWED_ROLES>   forumr!   r  r  ,*r  #_warned_telegram_group_users_legacyu   TELEGRAM_GROUP_ALLOWED_USERS contains chat-ID-shaped values (%s). Treating them as chat IDs for backward compatibility. Move chat IDs to TELEGRAM_GROUP_ALLOWED_CHATS — the _USERS var is now for sender user IDs.c              3   ^   K   | ]%  }|j                         s|j                          ' y wr  r7   r  uids     r#   r  z4GatewayRunner._is_user_authorized.<locals>.<genexpr>  s"     csWZW`W`Wbsyy{c   --c              3   ^   K   | ]%  }|j                         s|j                          ' y wr  rr  rs  s     r#   r  z4GatewayRunner._is_user_authorized.<locals>.<genexpr>  s"     esY\YbYbYdsyy{eru  c              3   ^   K   | ]%  }|j                         s|j                          ' y wr  rr  rs  s     r#   r  z4GatewayRunner._is_user_authorized.<locals>.<genexpr>  s"     asUXU^U^U`syy{aru  r  )3r   r  rF  r  r  r	  r=  r@  rB  rD  rH  rJ  rV  rX  rL  rN  rR  rP  rT  r]  r_  ra  r  r  rB   r   r  r  r   r@   r<  rq  r&   r7   r  is_approvedr  r  rp  r^  r=  r>  ra  rO  rp  r  r  r  _expand_whatsapp_auth_aliases_normalize_whatsapp_identifierr4   )r  r  r  platform_env_mapplatform_group_user_env_mapplatform_group_chat_env_mapplatform_allow_all_mapplatform_allow_bots_mapr  r_   platform_allow_all_varallow_bots_varry   platform_allowlistgroup_user_allowlistgroup_chat_allowlistglobal_allowlistr  allowed_group_idsr  legacy_chat_idsallowed_ids	check_idsnormalized_allowed_ids
allowed_idnormalized_user_ids                             r#   r  z!GatewayRunner._is_user_authorized  s     ??x55x7G7GHH..
7
5
 7
 NN1	

 OO3
 NN1
 LL-
 !;
 OO3
 7
 OO3
 NN1
 ##%C
 OO3
   "=
  NN.!
" 5#
( ='
# =NN4'
#"
9"
7"
 9"
 NN3	"

 OO5"
 NN3"
 LL/"
 !="
 OO5"
 9"
 OO5"
 NN3"
 ##%E"
 OO5"
   "?"
  NN0!"
" 7#"
* 2OO0#
 ??"22	G)--foo.C.CD..<A<S<S(9**BGBUBU.v?
 "8!;!;FOOR!P!bii0F&K&Q&Q&SWk&k68U+488IN"))NF"C"I"I"K"Q"Q"SWj"j OOx///		126<<> 28--b))-A  YY'7';';FOOR'PRTU[[]!!11#%99-H-L-LV__^`-ace#f#l#l#n #%99-H-L-LV__^`-ace#f#l#l#n 99%<bAGGI!*>G[dt996;AACG[[[
  F$4$48J$Jv~~/C/I/I#/N!$+RYR_R_Ra! ! ''6>>=N+N OOx000$  $66 .33C8779'', 	O 
 t%JERNN6 !89 @DD<>>_4 ec6H6N6Ns6Scce6J6P6PQT6Ueea6F6L6LS6Qaa +I	'>MM'--,Q/0 ??h///%(U") Y
&--.KJ.WXY%4:7CD!?!H!01I+,,k  ^!$s%   (A7`- `=)`=6a-	`:9`:c                    t        | dd      }|rYt        |d      rM|rKt        |d      r|j                  j                  |      nd}|r dt        |di       v r|j	                  |      S |r't        |d      r|j
                  dk7  r|j
                  S |ri t        j                  dt        j                  d	t        j                  d
t        j                  dt        j                  dt        j                  dt        j                  dt        j                  dt        j                  dt        j                   dt        j"                  dt        j$                  dt        j&                  dt        j(                  dt        j*                  dt        j,                  d}t        j                  dt        j,                  di}t/        j0                  |j                  |d      d      j3                         ry|j                  |d      D ](  }t/        j0                  |d      j3                         s( y t/        j0                  dd      j3                         ryy)u  Return how unauthorized DMs should be handled for a platform.

        Resolution order:
        1. Explicit per-platform ``unauthorized_dm_behavior`` in config — always wins.
        2. Explicit global ``unauthorized_dm_behavior`` in config — wins when no per-platform.
        3. When an allowlist (``PLATFORM_ALLOWED_USERS``,
           ``PLATFORM_GROUP_ALLOWED_USERS`` / ``PLATFORM_GROUP_ALLOWED_CHATS``,
           or ``GATEWAY_ALLOWED_USERS``) is configured, default to ``"ignore"`` —
           the allowlist signals that the owner has deliberately restricted
           access; spamming unknown contacts with pairing codes is both noisy
           and a potential info-leak. (#9337)
        4. No allowlist and no explicit config → ``"pair"`` (open-gateway default).
        r  Nget_unauthorized_dm_behaviorrP  unauthorized_dm_behaviorr6  pairr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  )r  r  )rg  r?   ignorer   r  )r&   r  rP  rB   r  r  r  r	  r=  r@  rB  rD  rH  rJ  rV  rX  rL  rN  rR  rP  rT  r]  r_  r@   r<  r7   )r  r   r  r!  r{  platform_group_env_mapenv_keys          r#   _get_unauthorized_dm_behaviorz+GatewayRunner._get_unauthorized_dm_behavior  s`    x. gf&DE(=DV[=Y6++//9_cL :glT[]_>` `::8DD gf&@A..&8666
  !!#;   #:  !!#;  #8	 
 #9  #8  #6  ##%?  #9  !!#;  #9  #8  '')G  #9  $$&A   #5! & !! $  ;&" yy)--h;R@FFH155hC $99Wb)//1#$ 99,b1779r%   rZ   c           	      L  K   | j                   j                  |j                        }|syt        | dd      }d}|r't	        |d      r|j                  |j                        }| j                  |      }|dk(  rNt        |dd      rA	 |j                  |j                  |j                  ||       d{   }t        |dd	      ry	 |j                  |j                  ||       d{    y7 :# t        $ r& t        j                  d
t        |dd      d       Y Ww xY w7 9w)zIDeliver a setup/operational notice using platform-specific privacy rules.Nr  publicget_notice_deliveryprivater  r  r  Fz7[%s] send_private_notice failed, falling back to publicr   r  Tr  )rE  rB   r   r&   r  r  r  send_private_noticer  r  r   r=  rM  r  )r  r  rZ   rk  r  notice_deliveryr  r  s           r#   _deliver_platform_noticez&GatewayRunner._deliver_platform_notice  s!    --##FOO4x."gf&;<$88IO33F;i'GFIt,L&::NNNN%	  ;    69e4 5 ll6>>7XlFFF  MFJ4!   	GsH   BD$,C0 3C.4C0 #D$(D")D$.C0 0,DD$DD$c                 ~F   K   j                   }t        t        dd            }|s	 ddlm}  |d  j
                        }|D ]  }t        |t              s|j                  d	      }|d
k(  rYt        j                  d|j                  d      |j                  r|j                  j                  nd|j                  xs d        y|dk(  rF|j                  d      }	t        |	t               r#t#        j$                  |	      j                   } n	|dk(  s n |rn|j&                  +t        j)                  d|j                  j                         y j+                  |      st        j                  d|j&                  |j,                  |j                  j                         |j.                  dk(  rh j1                  |j                        dk(  rI|j                  r|j                  j                  nd}
 j2                  j5                  |
|j&                        ry j2                  j7                  |
|j&                  |j,                  xs d      }|rV j8                  j                  |j                        }|r.|j;                  |j                  d| d|
 d| d       d{    y j8                  j                  |j                        }|r$|j;                  |j                  d       d{     j2                  j=                  |
|j&                         y j?                  |      }t         di       }|j                  |      rvj@                  xs djC                         }jE                         }|dv rd}n4|dv rd }n-d}|r"	 dd!l#m$} |	  ||      }|r|jJ                  nd}|rd}n|}|rtL        d"z  }tL        d#z  }	 |jO                  d$      }|jQ                  |       |j%                  |       |jS                  d%&       |jW                  |d       tY        |      d)k  r|n|dd) d*z   }d+| d,S rtL        d"z  }tL        d#z  }	 |jO                  d$      }|jQ                  d       |j%                  |       |jS                  d%&       t        j                  d-||       |jW                  |d       	 dd/l-m.} |j_                  |      }|qj@                  xs djC                         }|rQ|ja                  d0      s@jc                  |jd                  |      }|r"t        j                  d1||jd                         ydd2l-m3} |ji                  |      }d} 	 dd3l5m6}!  |!|      } |r| sƉj@                  xs djC                         }"jE                         }#d}$|#d4v rd5}$nL|#d6v rd7}$nE|#d8v rd9}$n>|"jo                         d:v rd5}$n)|"jo                         d;v rd7}$n|"jo                         d<v rd9}$|$0|jq                  ||j                  d=      |$       d{   }|xs dS |js                  |       tu        d>d?      }% jv                  j                  |d      }&| jx                  v r7|&r4t{        jz                         |&z
  }' jx                  j                  |      }(t}        d@      })d}*|(rwt        |(dA      rk	 |(j                         }+|+j                  dBt}        d@            })dC|+j                  dDd       dE|)dFdG|+j                  dHd       d0|+j                  dId       }*|%dkD  rt        |%dJz  dK      n
t}        d@      },|(t        uxr |%dkD  xr |)|%k\  xs |'|,kD  }-|-r>t        j                  dL||'|)|%|*        j                  |dMN        j                  |       | jx                  v 	rԉjE                         dOk(  r j                         d{   S ddPl#mF}.m$}/ jE                         }0|0r |/|0      nd}1|0r"|1  j                  ||1jJ                        }2|2|2S |1r(|1jJ                  dQk(  r j                         d{   S |1rZ|1jJ                  dRk(  rK j                  ||t        dST       d{    t        j                  dU|       t        t        dV            S |1rI|1jJ                  dWk(  r: j                  ||t        dXT       d{     j                         d{   S jE                         dYv rىj                         jC                         }3|3syZ j8                  j                  |j                        }|rOt        |3t        j                  j                   j                  j                  [      }4 j                  ||4|        j                  | j8                  j                  |j                        \      }5|5d]k  ry^d_|5 d`S |1r{|1jJ                  dak(  rkj                         jC                         }6|6syb jx                  j                  |      }7|7t        u rs j8                  j                  |j                        }|rKt        |6t        j                  j                   j                  j                  [      }4|4|j                  |<   yc|7r?t        |7da      r3	 |7j                  |6      }8|8r|6ddf tY        |6      dfkD  rdgndz   }:dh|: diS yj j8                  j                  |j                        }|rKt        |6t        j                  j                   j                  j                  [      }4|4|j                  |<   yk|1r|1jJ                  dlk(  rym|1r|1jJ                  dnk(  ryo|1rO|1jJ                  dpv rA|1jJ                  dqk(  r j                         d{   S  j                         d{   S |1r(|1jJ                  drk(  r j                         d{   S |1r(|1jJ                  dsk(  r j                         d{   S |1r(|1jJ                  dtk(  r j                         d{   S |1r_|1jJ                  duk(  rPj                         xs djC                         jo                         };|;r|;dvv r j                         d{   S yw|1r|1jJ                  dxv rx|1jJ                  dyk(  r j                         d{   S |1jJ                  dzk(  r j                         d{   S |1jJ                  d{k(  r j                         d{   S |1r|1jJ                  |.v r|1jJ                  d|k(  r j                         d{   S |1jJ                  d}k(  r j                         d{   S |1jJ                  d~k(  r j                         d{   S |1jJ                  dk(  r j                         d{   S |1rd|1jJ                   dS j                  t        j                  k(  rUt        j)                  d|        j8                  j                  |j                        }|rt        |j                  |       yt}        t        j                  dd            }< jv                  j                  |d      }=|j                  t        j                  k(  rj                  t        j                  k(  r|<dkD  r|=rt{        jz                         |=z
  |<k  rmt        j)                  dt{        jz                         |=z
  |        j8                  j                  |j                        }|rt        |j                  |d%       y jx                  j                  |      }7|7t        u rjE                         dRk(  r2 j                  |       t        j                  d|       t        d      S  j8                  j                  |j                        }|rt        |j                  |d%       y j                  rZ j                         r j                  |        j                         rd j                          dS d j                          dS  j                  dk(  r)t        j)                  d|        j                  |       y j                  dak(  rj@                  xs djC                         }6d}>|6r't        |7da      r	 t        |7j                  |6            }>|>rt        j)                  d|       yt        j)                  d|        j                  |       yt        j)                  d|       |7j                  j@                         yjE                         }?ddl#ms}@mt}Am$}B |?r B|?      nd}|r|jJ                  n|?}C|?r<|9t         j                  t              r! j                  j                  di       xs i }Dnt         j                  di       xs i }Dt        Dt              r|?Dv rD|?   }E|Ej                  d      dk(  rEj                  dd      jC                         }F|FrFja                  d0      rFnd0F }F|Fj                  d0      }Gj                         jC                         }H|F d|H jC                         _         |GrGj                         d   nG}?|?r B|?      nd}|r|jJ                  n|?}C|?r Cr AC      r j                  |C      }2|2|2S |?r AC      rj                         jC                         }I|j                  r|j                  j                  nd|j&                  C|?I|Id}J	  j                  j                  dC J       d{   }KKD ]h  }Mt        |Mt              st!        Mj                  dd            jC                         jo                         }N|NrNdk(  rTNdk(  r/Mj                  d      }Ot        |Ot               rOrOc S d|? dc S Ndk(  r+Mj                  d      }Ot        |Ot               rOrOc S dc S Ndk(  st!        Mj                  dd            jC                         j                  d0      }P|Pst!        Mj                  dd            jC                         }Qd0P d|Q jC                         _         jE                         }?|?r B|?      nd}|r|jJ                  n|?}C n CdWk(  rE j                  |      r j                         S  fd}R j                  dWdd|R       d{   S Cdk(  r j                         d{   S Cd|k(  r j                         d{   S Cd}k(  r j                         d{   S Cd~k(  r j                         d{   S Cdk(  r j                         d{   S CdOk(  r j                         d{   S Cdrk(  r j                         d{   S CdQk(  r j                         d{   S CdRk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r j                         d{   S Cdzk(  r j                         d{   S Cd{k(  r j                         d{   S Cdyk(  r j                         d{   S Cdlk(  r j                         d{   S Cdnk(  r j                         d{   S Cdk(  r j	                         d{   S Cdtk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r$ fd}S j                  ddd|S       d{   S Cdk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r j                         d{   S Cdqk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r j                         d{   S Cdk(  r j!                         d{   S Cdsk(  r j                         d{   S Cdak(  r)j                         jC                         }T|Tsy	 T_         Cduk(  r j                         d{   S Cdk(  r j#                         d{   S  j                  rd j                          dS |?rNt         j                  t              r! j                  j                  di       xs i }Dnt         j                  di       xs i }Dt        Dt              si }D|?Dv rD|?   }E|Ej                  d      dk(  rEj                  dd      }U|Ur	 ddlm}V  |Vt        j(                  j+                               }Wt-        j.                  Ut,        j0                  j2                  t,        j0                  j2                  |W«       d{   }Xt-        j4                  |Xj7                         dìī       d{   \  }Y}Z|Yxs Zj9                         jC                         }[|[rddlm}\  |\[      }[[r[S dS d|? dʝS Ej                  d      dk(  rEj                  dd      jC                         }F|FryFja                  d0      rFnd0F }F|Fj                  d0      }Gj                         jC                         }H|F d|H jC                         _         |GrGj                         d   nG}?nd|? d˝S d|? d̝S |?rx	 ddlm}]  |]|?j%                  ddϫ            }^|^rVj                         jC                         }H ^|H      }_t-        jB                  |_      r
_ d{   }__rt!        _      S dS 	 |?r		 ddlm}`m}am}b  |`       }c |b|?      }d|dcd   j                  dd      }e|j                  r|j                  j                  nd}f|frerddlm}g e |gfԫ      v r	de df dםS j                         jC                         }h ad|h|ث      }i|irki_         nctQ        |?      }j|jrjS |?j%                  ddϫ      @vr?t        j                  d|?|j                  r|j                  j                  ndګ       d|? dܝS  j                  |      r$ jS                  |      r jU                         S yt         jx                  |<   t{        jz                          jv                  |<    jW                  |      }k	  jY                  ||k       d{   }l	 d}mt        lt              rt!        lj                  dޫ      xs d      }mnt        lt               rl}mmjC                         r<	  j
                  j[                  |      }nn j]                  n|m߫       d{    l jx                  j                  |      t        u r j                  |       S  jv                  jW                  |d       t         d      r j^                  jW                  |d       S S # t        $ r#}t        j                  d|       g }Y d}~d}~ww xY w7 -7 # t        $ r d}Y Rw xY w# t        $ r d}Y Iw xY w# tT        $ r%}t        j                  d'|       d(| cY d}~S d}~ww xY w# tT        $ r!}t        j                  d.|       Y d}~d}~ww xY w# t        $ r d}Y w xY w# t        $ r d} Y w xY w7 Z# t        $ r Y [w xY w7 7 >7 7 7 # t        $ r&}9t        j                  dd||9       de|9 cY d}9~9S d}9~9ww xY w7 7 7 7 7 7 (7 7 7 7 s7 N7 )7 # t        $ r$}9t        j                  d||9       d}>Y d}9~9d}9~9ww xY w7 # t        $ r$}Lt        j)                  dCL       g }KY d}L~Ld}L~Lww xY w7 7 7 7 7 7 s7 X7 =7 "7 7 
7 
7 
7 
7 
~7 
b7 
F7 
*7 
7 	7 	7 	7 	7 	y7 	]7 	A7 	%7 	
7 7 7 7 7 7 d7 H7 -# t        $ r Y 	w xY w7 7 7 7 j# t,        j>                  $ r Y yt        $ r}d| cY d}~S d}~ww xY w7 7# t        $ r!}t        j)                  d|       Y d}~Jd}~ww xY w# t        $ r!}t        j)                  d|       Y d}~kd}~ww xY w7 # t        $ r d}nY zw xY w7 d# t        $ r!}ot        j)                  do       Y d}o~od}o~oww xY w#  jx                  j                  |      t        u r j                  |       w  jv                  jW                  |d       t         d      r j^                  jW                  |d       w w xY ww)a  
        Handle an incoming message from any platform.
        
        This is the core message processing pipeline:
        1. Check user authorization
        2. Check for commands (/new, /reset, etc.)
        3. Check for running agent and interrupt if needed
        4. Get or create session
        5. Build context for agent
        6. Run agent conversation
        7. Return response
        r{  Fr   r%  pre_gateway_dispatch)rb  r(  rZ  z*pre_gateway_dispatch invocation failed: %sNr  skipz8pre_gateway_dispatch skip: reason=%s platform=%s chat=%srU  r  rewriter   r   allowz(Ignoring message with no user_id from %sz Unauthorized user: %s (%s) on %sr  r  r?   z;Hi~ I don't recognize you yet!

Here's your pairing code: `z5`

Ask the bot owner to run:
`hermes pairing approve ro  r  z<Too many pairing requests right now~ Please try again later!rv  >   r  approvey>   r  denynresolve_command.update_response.update_prompt.json.tmpTr=  z#Failed to write update response: %su/   ✗ Failed to send response to update process: r     …u
   ✓ Sent `z` to the update process.ziRecognized /%s during pending update prompt for %s; cancelled prompt with default and dispatching commandz=Failed to write cancel response for pending update prompt: %sclarify_gatewayr    z=Gateway intercepted clarify text response (session=%s, id=%s)slash_confirm)has_blocking_approval>   okr  r  confirmonce>   alwaysrememberr  >   r  r  r  	nevermindr  >   approve oncer  r  >   always approver  >   r  r  r  
confirm_idr     infr  seconds_since_activityz | last_activity=last_activity_descr  .0fzs ago) | iteration=r  r  r  i   zWEvicting stale _running_agents entry for %s (age: %.0fs, idle: %.0fs, timeout: %.0fs)%sstale_running_agent_evictionrU  r  )ACTIVE_SESSION_BYPASS_COMMANDSr  r\  rQ  stop_commandinterrupt_reasoninvalidation_reasonu@   STOP for session %s — agent interrupted, session lock releasedgateway.stop.stoppednewnew_command>   qre  zUsage: /queue <prompt>r   rX  r  r  channel_promptrt  r   zQueued for the next turn.zQueued for the next turn. (z queued)rf  zUsage: /steer <prompt>u9   Agent still starting — /steer queued for the next turn.zSteer failed for session %s: %su   ⚠️ Steer failed: r  ...u8   ⏩ Steer queued — arrives after the next tool call: 'r~  zSteer rejected (empty payload).u4   No active agent — /steer queued for the next turn.r   u=   Agent is running — wait or /stop first, then switch models.zcodex-runtimeu>   Agent is running — wait or /stop first, then change runtime.>   r  r  r  agents
backgroundr  goal>   r  rQ  r  pauseresumer  ub   Agent is running — use /goal status / pause / clear mid-run, or /stop before setting a new goal.>   yoloverboser  r  footerhelpcommandsprofiler  u   ⏳ Agent is running — `/zE` can't run mid-turn. Wait for the current response or `/stop` first.uF   PRIORITY photo follow-up for session %s — queueing without interrupt&HERMES_TELEGRAM_FOLLOWUP_GRACE_SECONDSz3.0uV   Telegram follow-up arrived %.2fs after run start for %s — queueing without interrupt)
merge_textu7   HARD STOP (pending) for session %s — sentinel cleareduE   ⚡ Force-stopped. The agent was still starting — session unlocked.r  r  r  r  re  z'PRIORITY queue follow-up for session %sz(PRIORITY steer failed for session %s: %szPRIORITY steer for session %sz/PRIORITY steer-fallback-to-queue for session %sz!PRIORITY interrupt for session %s)GATEWAY_KNOWN_COMMANDSis_gateway_known_commandr  quick_commandsr  aliasr  )r   r  r6  raw_commandr7  r  zcommand:z/command:%s hook dispatch failed (non-fatal): %sdecisionr  rm  z
Command `/z` was blocked by a hook.handledr  r  c                  B   K   j                          d {   S 7 wr  )_handle_reset_commandrb  r  s   r#   	_do_resetz0GatewayRunner._handle_message.<locals>._do_reset  s     !77>>>>   z/newzJThis starts a fresh session and discards the current conversation history.)rb  r6  r,  detailexecutetopicwhoamirR   r  personalityretryundoc                  B   K   j                          d {   S 7 wr  )_handle_undo_commandr  s   r#   _do_undoz/GatewayRunner._handle_message.<locals>._do_undoT  s     !66u====r  z/undoz;This removes the last user/assistant exchange from history.sethomecompressusageinsights
reload-mcpzreload-skillsrM  r,  r  branchrollbackzJUsage: /steer <prompt>  (no agent is running; sending as a normal message)r  z) and is not accepting new work right now.execr6  )_sanitize_subprocess_env)rN  rO  envrH  r  )redact_sensitive_textzCommand returned no output.zQuick command timed out (30s).zQuick command error: zQuick command '/z' has no command defined.z' has no target defined.z4' has unsupported type (supported: 'exec', 'alias').)get_plugin_command_handlerr  r  z.Plugin command dispatch failed (non-fatal): %s)get_skill_commandsbuild_skill_invocation_messageresolve_skill_command_keyrG   )get_disabled_skill_namesr  r  z** skill is disabled for z(.
Enable it with: `hermes skills config`rv  uO   Unrecognized slash command /%s from %s — replying with unknown-command noticer  zUnknown command `/zl`. Type /commands to see what's available, or resend without the leading slash to send as a regular message.z*Skill command check failed (non-fatal): %sfinal_response)r%  r  r  z!goal continuation hook failed: %srh  )r  r4   r&   r+  r&  rZ  r   r=  r>  r2   rh   rB   rL  r   r   r  r*   dataclassesr:   r  rM  r  r  r  r  r  _is_rate_limitedgenerate_coderE  r  _record_rate_limitr  r   r7   get_commandr'   r  rG   r   with_suffixr  rB  r  r-  r]  toolsr  get_pending_for_sessionr^  resolve_gateway_clarify
clarify_idr  get_pendingtools.approvalr  rq  r   clear_if_stalerJ   r  rd  rN   r6   r  r  r  r  "_invalidate_session_run_generationr:  _handle_status_commandr  _check_slash_access_handle_restart_command_interrupt_and_clear_session_INTERRUPT_REASON_STOPr$  r   _INTERRUPT_REASON_RESETr  get_command_argsr%  r&  r  r  r  rm  rw  re  rf  _handle_approve_command_handle_deny_command_handle_agents_command_handle_background_command_handle_kanban_command_handle_goal_command_handle_yolo_command_handle_verbose_command_handle_footer_command_handle_help_command_handle_commands_command_handle_profile_command_handle_update_commandrX  r_  r(  r@   r<  r  r	  r  rg  r  rc  r  r  r  r  r  lstriprp  r  emit_collectr  r    _maybe_confirm_destructive_slash_handle_topic_command_handle_whoami_command_handle_stop_command_handle_reasoning_command_handle_fast_command_handle_model_command_handle_codex_runtime_command_handle_personality_command_handle_retry_command_handle_set_home_command_handle_compress_command_handle_usage_command_handle_insights_command_handle_reload_mcp_command_handle_reload_skills_command_handle_debug_command_handle_title_command_handle_resume_command_handle_branch_command_handle_rollback_command_handle_voice_commandtools.environments.localr  rA   copyr^  create_subprocess_shellrX  PIPEr  communicatedecodeagent.redactr  r  r  iscoroutineagent.skill_commandsr  r  r  r  r  r  r  r  _begin_session_run_generation_handle_message_with_agentr7  _post_turn_goal_continuationrh  )pr  rb  r  is_internalr-  _hook_results	_hook_exc_result_action	_new_textry   coderk  
_quick_key_update_promptsrE   rc  rE  _recognized_cmd_resolve_update_cmd_cmd_defresponse_pathprompt_pathtmpr  label_clarify_mod_pending_clarify_raw_clarify_reply	_resolved_slash_confirm_mod_pending_confirm_tool_approval_liver  
_raw_reply
_cmd_reply_confirm_choice_raw_stale_timeout	_stale_ts
_stale_age_stale_agent_stale_idle_stale_detail_sa	_wall_ttl_should_evict_DEDICATED_HANDLERS_resolve_cmd_inner_evt_cmd_cmd_def_inner_deniedqueued_textrh  rv  r  r  acceptedrE  preview	_goal_arg_telegram_followup_grace_started_atr  r6  r  r  _resolve_cmd	canonicalr  qcmdr  target_command	user_argsr  hook_ctxhook_results	_hook_errhook_resultr  rm  r  new_argsr  r  steer_payloadexec_cmdr  sanitized_envprocrN  rO  r  r  r  plugin_handlerr  r  r  r  
skill_cmdscmd_key_skill_namerY  _get_plat_disableduser_instructionr[   _unavail_msg_run_generation_agent_result_final_textr%  	_goal_excsp   ``                                                                                                              r#   r  zGatewayRunner._handle_message3  s"      75*e<= 
#J ,* "&"4"4	! ) !'40!++h/f$KKRH-17--i3)	  i' 'F 3I!)S1 + 3 3E	 J!&g%'* ^^#
 LLCV__EZEZ[))&1NN=v~~vO_O_agapapavavw4'D,N,Nv,_ci,i9? 5 5i %%66}fnnU))77!6>>63C3C3Ir "mm//@G%ll"NN::> @77DoQtfAO  "  #mm//@G%ll"NN6   &&99-X 11&9
!$(@"Ez*::#**,C##%C(( #& #"&3^ +63':3'?H?GhmmTO #$&M$'M ,/A A*-BBQ'33F;CNN=1KK.&&$&7  ##J5),]);r)A}UXVXGY\aGa#E7*BCC  ,/A A*-BB'33F;CNN2&KK.&&$&7KKP'"	  ##J5	$=+CCJO '"'**"2!9!9!;
 "*<*G*G*L(@@$//1C	 KKW"$4$?$?  	>-99*E#	(<"7
"C $7***113J**,J"O@@"(55"*DD"*!!#'JJ"(!!#'CC"*!!#'DD"**"4"<"< 0 4 4\ BO# 	 !B& --j9 ((>E++//
A>	---)y0J//33J?L  ,KM6L M	&;;=C"%''*BE%L"QK+CGG4H),T+U V', -''*ww/?'C&DAcggN^`aFbEce " ?QST>T.3T:Z_`eZfI$;; '!+Q?Q0Q .!I-	  B
K&	 779 8  11*=---  "h.!88??? ((*H=E/94N N6226>;N;NO&"N."5"5"B!99%@@@ ."5"5"?77%;(6	 8    ^`jk%a(>&?@@ ."5"5">77%<(5	 8    "77>>>   "n4#446<<>"3--++FOO<#/(%0%5%5$||#(#3#3',';';$L &&z<I))*dmm>O>OPVP_P_>`)aA:64UG8DD ."5"5"@"335;;=
!3 $ 4 4 8 8 D $;;"mm//@G'3!+)4)9)9#(<<','7'7+0+?+?( AM11*=V W]G%D=#0#6#6z#B  ",Sb/c*oPR>RUXZ"[!YZaYbbcdd<--++FOO<#/'%0%5%5$||#(#3#3',';';$L =IG--j9M ."5"5"@V ."5"5"H* ."5"59L"L!&&)3!%!=!=e!DDD!66u=== ."5"5"A!88??? ."5"5"E!<<UCCC ."5"5"A!88??? ."5"5"?"335;BBDJJL	 I1g$g!%!:!:5!AAA{ ."5"59L"L!&&&0!%!:!:5!AAA!&&)3!%!=!=e!DDD!&&(2!%!<!<U!CCC ."5"59L"L!&&&0!%!:!:5!AAA!&&*4!%!>!>u!EEE!&&)3!%!=!=e!DDD!&&(2!%!<!<U!CCC 1.2E2E1F GP Q
 !![%6%66egqr--++FOO</0I0I:W\]',		BEJ($ 1155j!DK8#4#44&&+*:*::,q0YY[;.3KKlIIK+-
 --++FOO</11"#'	  0044Z@M 77$$&&055jAKK Y[ef)*qrr --++FOO</11"#'	 ~~33588UK 779 #4#=#=#?"@@rs +4+E+E+G*HHuv
 $$/F
S44ZG$$/ $jj.B557
'-"A("&}':'::'F"G LL!@*MNPZ[44ZGLL<jI##EJJ/  ##%	
 	
 -4<(%-HMM7	 x'$++t,!%1A2!F!L"!(6F!K!Qr.$/G~4M%g.88F#w.!XXh399;F+1+<+<S+A6(|)/s);$)$:$:$<$B$B$D	(.xq%<%B%B%D
?M."6"6"8";Sa<C<#85=HMM7	 y%=i%H..vyAG" /	:--/557H5;__FOO11"!>>$& $H	"%)ZZ%<%<yk*H&    , !+t4{z2>?EEGMMO8w#6v%)ooi8G!'3/G&'y0HIIy()ooi8G&0#&>77TPTTy("%#;#egffSk   ' ";??:r#BCIIKH#$[M8*!=!C!C!EEJ#//1G8?|G4TH19wI36 11&9<<>>?>>, " ? 	 	 	 33E:::225999
"66u===	!55e<<< 44U;;; 44U;;; 44U;;;	!55e<<<225999#77>>>225999	!55e<<< 44U;;;22599933E:::';;EBBB%99%@@@ 44U;;;33E:::>>>T  ?    	!66u===
"66u===33E:::
"66u===$88???';;EBBB	!55e<<<225999 44U;;;33E:::33E::: 44U;;; 44U;;;
"66u===$88??? "224::<M c*
 22599933E:::>>$T%?%?%A$BBkll $++t,!%1A2!F!L"!(6F!K!Qrnd3!#.(%g.88F#v-#xx	26H? Z,DRZZ__EV,WM)0)H)H ('.'9'9'>'>'.'9'9'>'>$1	* $D 4;3C3CDDTDTDV`b3c-cNFF&,&6%>%>%@%F%F%HF% N)>v)F-36V9VV "2':STTXXf%0!XXh399;F+1+<+<S+A6(|)/s);$)$:$:$<$B$B$D	(.xq%<%B%B%D
?M."6"6"8";Sa "2':RSS-gY6jkk RI "<GOOCQT<U!V! % 6 6 8 > > @I+I6F**62'-*03v;:d: " 9N 
 01
3G<&
 #-W"5"9"9&""EK5;__FOO11$Ed&*<e*LL"(5Nug VI !J (-'='='?'E'E'G$8!1:C %(

 $<G#DL#++ sC08NNC#5;__FOO11#	 1	 :4 5 --f5 88@>>@@ ,CZ(.2iik
+<<ZH+	<"&"A"A%Q[]l"mmMM mT2"%m&7&78H&I&OR"PKs3"/K $$&-(,(:(:(P(PQW(X %0"??*7#)+6 @    ! ##''
37NN11*= ''++J=40%%))*d; 1e   #KYW "#d@ % 3.2+3  ) 3.2O3  QNN#H!LLQCPPQ4  NNW    	$#	$L  	("'	(&V ! 8 @, A$ ?f % ='H*VYZ!6se<<=H E= @ D @ B BDC BEDCt % ('QS]_bc"'(b   "Ey  ""N	 ; : > = < < < = : ? : = < : ; C A < ;
 > > ; > @ C = : < ; ; < < > @   : ;0$ .d  '33 D#C( ?%:1##>>?< ". RMqQQR@  NI1MMN4 n& % -(,-
  M@)LLM ##''
37NN11*= ''++J=40%%))*d; 1s	  %BL=A~+ CBL=FBL=-A.ABL=9A:BBL=A  BL=A2 7BL=AB@ ABL=AB@5 9BL=BA" #BBL=0BA4 >B.BL=,BB-B"BL=A*BB	 :B2BL=,BB-A1BL=BB1BL=BBABL=,BB"-BL=BB%GBL=BB( C+BL=
BCBL=#BC$)BL=BC )BL=7BC#8)BL=!BC&"ABL=BC)8BL=:BC,;'BL="BC/#'BL=
BC27BL=BC5'BL=*BC8+'BL=BC;'BL=:BC>;LBL=DBD D%I*BL=N"BD4 N2BD1N3BD4 N7B?BL=Q7C3BL=U*BE$U+BL=VBE'V	BL=V&BE*V'BL=WBE-WBL=W"BE0W#BL=X BE3XBL=XBE6XBL=X<BE9X=BL=YBE<YBL=Y8BE?Y9BL=ZBFZBL=Z6BFZ7BL=[BF[BL=[2BF[3BL=\BF\BL=\/BF\0BL=]BF]BL=]-BF].BL=^BF^BL=^*BF^+(BL=_BF _BL=_2BF#_3BL=`BF&`BL=`0BF)`1BL=aBF,aBL=a.BF/a/BL=bBF2bBL=b+BF5b,BL=c	BF8c
BL=c'BF;c(BL=dBF>dBL=d%BGd&BL=eBGeBL=e#BGe$BL=fBG
fBL=f BGf!*BL=gBG gBL=g,BG g-BL=hBG#hCBL=kB BG, mBG&m/BG, m?BG)n =BG, n=BL=n>BG, n?CBL=rA"BH  s&BHs'BH  s7BL=s8BH  s9BL=s?A1BI u0BL=u1ABI v3BL=v4ABI xA<BL=zBJ? zBI:zBJ? z!ABJ {5BI= |BJ |+BJ|,BJ |0BJ? |1A:BL=~+	A~4ABL=ABL=BL= A/+BL=.A//BL=2B@=BL=@ B@@BL=@	B@2@B@-@'B@2@(BL=@-B@2@2BL=@5	BA@>BAABL=ABAABL=A"BA1A-BL=A0BA1A1BL=A4BBA?BL=BBBBBL=B		BBBBL=BBBBBL=BBL=BBL=B"BL=B%BL=B(	BCB1BCCBCCBL=CBCCBL=CBL=C BL=C#BL=C&BL=C)BL=C,BL=C/BL=C2BL=C5BL=C8BL=C;BL=C>BL=D	BD.D
BD)D#BL=D)BD.D.BL=D1BD4 D4	BE!D=BEEBL=EBE!E!BL=E'BL=E*BL=E-BL=E0BL=E3BL=E6BL=E9BL=E<BL=E?BL=FBL=FBL=FBL=FBL=FBL=FBL=FBL=FBL=FBL=FBL=F BL=F#BL=F&BL=F)BL=F,BL=F/BL=F2BL=F5BL=F8BL=F;BL=F>BL=GBL=GBL=GBL=G
BL=GBL=G	BGGBL=GBGGBL=G#BL=G&BG, G)BG, G,BHHBL=HBHHBHHBHHBL=HBHHBL=HBH  H 	BI
H)BIH?BL=IBI
I
BL=I	BI7IBI2I,BL=I2BI7I7BL=I:BJ? I=BJJBJ JBJJBJ J	BJ<JBJ7J1BJ? J7BJ<J<BJ? J?A;BL:L:BL=rc   c                  +K   |xs g }|j                   xs d+t        | j                  dd      }t        | j                  dd      }| j                  |      }| j	                  |       t        |||      }|r|j                  rd|j                   d+ +|j                  r'g }g }	t        |j                        D ]  \  }
}|
t        |j                        k  r|j                  |
   nd}|j                  d	      s|j                  t        j                  k(  r|j                  |       |j                  d
      s-|j                  t        j                   t        j"                  hv s|	j                  |        |r| j%                         }|dk(  rFt        | dd      }|	i }|| _        t)        |      ||<   t*        j-                  dt        |             n:t*        j-                  d|t        |             | j/                  +|       d{   +|	r| j1                  +|	       d{   +d}t3        +fd|D              r| j4                  j7                  |j8                        }| j;                  || j=                  |            }|r>	 d}| j?                         r|dz  }|jA                  |jB                  ||       d{    |j                  ri|j                  t        jF                  k(  rKddl$}ddl%m&} h d}t        |j                        D ]$  \  }
}|
t        |j                        k  r|j                  |
   nd}|dv rOtN        jP                  jS                  |      d   jU                         }||v rd}n|jW                  |      \  }}|r|}|j                  d      stN        jP                  jY                  |      }|j[                  dd      }t        |      dk\  r|d   n|}t]        j^                  dd|      } ||      }|j                  d       r
d!| d"| d#}n	d$| d%| d&}| d'+ +' t        |d(d      r#|j`                  r|jb                  dd) }d*| d++ +d,+v r	 dd-l2m3} dd.l4m5}  tN        jl                  j7                  d/tN        jP                  jo                  d0            }!tq               }"d}#	 ts               }$|$j7                  d1i       }%tu        |%tv              r|%j7                  d2      }&|&ty        |&      }# | | jz                  | j|                  xs |"j7                  d3      xs d|"j7                  d4      xs d|#5      }' |+|!|'|!6       d{   }(|(j~                  ri| j4                  j7                  |j8                        })|)rA|)jA                  |jB                  d7j                  |(j                        xs d8       d{    y|(j                  r|(j                  ++S +S 7 7 7 F# tD        $ r Y Pw xY w# tD        $ r Y w xY w7 7 L# tD        $ r!}*t*        j                  d9|*       Y d}*~*+S d}*~*ww xY ww):a  Prepare inbound event text for the agent.

        Keep the normal inbound path and the queued follow-up path on the same
        preprocessing pipeline so sender attribution, image enrichment, STT,
        document notes, reply context, and @ references all behave the same.

        Side effect: buffers per-session native image paths when the active
        model supports native vision AND the user has images attached. The
        caller consumes and clears that session-scoped buffer at the
        ``run_conversation`` site to build a multimodal user turn. When the
        list is empty, the ``_enrich_message_with_vision`` text path has
        already run and images are represented in-text.
        r?   r  Tr  Fr  [] rW  rZ  nativerg  NzSImage routing: native (model supports vision). %d image(s) will be attached inline.zLImage routing: text (mode=%s). Pre-analyzing %d image(s) via vision_analyze.)No STT providerzSTT is disabledzcan't listenVOICE_TOOLS_OPENAI_KEYc              3   &   K   | ]  }|v  
 y wr  r   )r  r  message_texts     r#   r  z>GatewayRunner._prepare_inbound_message_text.<locals>.<genexpr>  s     N&v-Nr  u
  🎤 I received your voice message but can't transcribe it — no speech-to-text provider is configured.

To enable voice: install faster-whisper (`pip install faster-whisper` in the Hermes venv) and set `stt.enabled: true` in config.yaml, then /restart the gateway.z@

For full setup instructions, type: `/skill hermes-agent-setup`r  r   )to_agent_visible_cache_path>   .md.cfg.csv.ini.log.txt.xml.yml.json.toml.yaml>   application/octet-streamr?   r   z
text/plain)zapplication/text/r  r{  ry  z	[^\w.\- ]r  z![The user sent a text document: 'zC'. Its content has been included below. The file is also saved at: rY  z[The user sent a document: 'z'. The file is saved at: z3. Ask the user what they'd like you to do with it.]r  reply_to_textrB  z[Replying to: "z"]

r  )#preprocess_context_references_async)get_model_context_lengthr   ~r   context_lengthr   r   )r   r   config_context_length)r   r  allowed_rootr[  zContext injection refused.z(@ context reference expansion failed: %s)Er   r&   r  r  #_consume_pending_native_image_pathsr!  r  rU  r\  r]  rV  r^  rX  r&  r_  r`  VOICEAUDIO_decide_image_input_moderg  rA  r=  rL  _enrich_message_with_vision"_enrich_message_with_transcriptionr  rE  rB   r   r  r'  r  r  r  r   DOCUMENT	mimetypestools.credential_filesr  r@   rq   splitextrq  
guess_typebasenamerp  r(   r,   reply_to_message_idr  agent.context_referencesr  agent.model_metadatar  rA   r  rF  r  r2   rh   r5   _model	_base_urlrk  ra  warningsexpandedrm  rM  ),r  rb  r  rc   _group_sessions_per_user_thread_sessions_per_userrh  _is_shared_multi_userimage_pathsaudio_pathsrd  rq   rf  	_img_modepending_native_stt_fail_markers_stt_adapter	_stt_meta_stt_msg
_mimetypesr  _TEXT_EXTENSIONS_extguessedr  r  rc  display_name
agent_pathcontext_notereply_snippetr  r  _msg_cwd_msg_runtime_msg_config_ctx_msg_cfg_msg_model_cfg_msg_raw_ctx_msg_ctx_len_ctx_result_adapterrE  r  s,                                              @r#   _prepare_inbound_message_textz+GatewayRunner._prepare_inbound_message_textk  sw    ( -Rzz'R#*4;;8QSW#X $+DKK9SUZ$[! 226: 	00= <$<%>!

 !V%5%5v//0<.ALKK$U%5%56 -401C8I8I4J0J))!,PR##H-1C1C{GXGX1X&&t,##H-1C1CHYHY[f[l[lGm1m&&t,-  !99;	(%,T3[]a%bN%-)+FTC26{2CN;/KKmK(
 KKf!3{#3 *.)I)I$#* $L
 %)%L%L &  %! N<MNN#'==#4#4V__#EL $ @ @IeIefkIl mI#!!= %  $446 (,p p"."3"3 & ()2 #4 #    2 2k6J6J J*Jy$U%5%56 #C401C8I8I4J0J))!,PR<<77++D1!4::<D// ,%/%:%:4%@
"$+E''(AB77++D1 sA.+.u:?uQx!vvlCF
 9>
##G,;L> J66@\D ! 7|n E11; =LM !
 #/tL>BG#CJ 5/40U5N5N "//5M,]O6,PL,'NXI::>>."'':L:LS:QR<>"&35H%-\\'2%>N!.$7'5'9'9:J'K'3.1,.?O  8KK!^^Q|/?/?
/KQr(,,Y7=2*9	  %H  #/!)	%  &&#}}00AH&mm"NN IIk&:&:;[?[    ''#.#6#6L |K$ 2
  ) ! !L !   NGMMNs   E&Y!*B%Y!XY!+X
,A"Y!8X XX F+Y!8AX4 A
X  AX4 1X02A3X4 %X2&X4 *Y!+X4 Y!
Y!X 	XY!XY! 	X-)X4 ,X--X4 2X4 4	Y=YY!YY!c                 b    t        | dd       }|sg S t        |j                  |g       xs g       S )Nrg  )r&   rA  r-  )r  rh  r  s      r#   r  z1GatewayRunner._consume_pending_native_image_paths8  s7     'OQUVIN&&{B7=2>>r%   c                    |r|y t        | dd       }|t               }|| _        	 t        j                  |      ||<   	 |j                  |       t        | dd      }t        |      |kD  r"|j                  d       t        |      |kD  r!y y # t
        $ r t        j                  d|d       Y y w xY w# t
        $ r Y y w xY w)	Nrj  z*Failed to cache live session source for %sTr  rk  r-  F)r  )r&   r   rj  r  r:   r   r=  rM  move_to_endr]  popitem)r  rh  r  cached_sourcesmax_sizes        r#   _cache_session_sourcez#GatewayRunner._cache_session_source>  s    fn '94@!(]N$2D!	*5*=*=f*EN;'
	&&{3t%;SAHn%0&&E&2 n%0  	LLE{]aLb	  		s$   B  AB6 !B32B36	CCc                     |sy t        | dd       }|sy |j                  |      }|	 |j                  |       |S |S # t        $ r Y |S w xY w)Nrj  )r&   rB   r  r   )r  rh  r  r  s       r#   r  z(GatewayRunner._get_cached_session_sourceS  sj     '94@##K0**;7 v  s   = 	A
	A
rO  run_generationc                 ;  K   t        j                          }t        |j                  d      r|j                  j                  nt	        |j                        }|j
                  xs ddd j                  dd      }t        j                  d||j                  xs |j                  xs d|j                  xs d|       | j                  j                  |      }|j                  }	| j                  |	|       | j!                  |      r	 | j"                  rC| j"                  j%                  t	        |j                        t	        |j&                        	      nd}
|
rPt	        |
j-                  d      xs d      }|rC||j.                  k7  r4| j                  j1                  |	|      }||}n	 | j3                  ||       t5        |dd      rV| j6                  j9                  |	d       | j;                  |	d       t        | d      r| j<                  j9                  |	d       |j>                  |j@                  k(  xs t5        |dd      xs t5        |dd      }t5        |dd      rd|_!        |r_| jD                  jG                  d|j                  r|j                  j                  nd|j                  |j.                  |	d       d{    tI        || jJ                  |      }| jM                  |      }d}	 tO               }tQ        |j-                  d      xs i j-                  dd            }tS        ||      }t5        |dd      rt5        |dd      xs d}|dk(  rd}n
|dk(  rd}nd}|dz   |z   }	 | j                  jJ                  jU                  |j                  t5        |d d!      "      }|j                  r|j                  j                  nd}t5        |d#d      }|dk(  xs  |jV                  xr |xr ||jX                  v}|r| jZ                  j-                  |j                        }|r|dk(  rd$}nO|dk(  rd%|j\                   d&}n9|j^                  d'z  }|j^                  d'z  }|s| d(n|r| d)| d*n| d*}d+| }d,| d-}	 | ja                         }|r| d| }|jc                  |j                  || je                  |      .       d{    d|_3        d|_4        t5        |d0d      }!|r|!rtk        |!t              r|!gn
tm        |!      }"	 d1d2l7m8}#m9}$ g }%g }&|"D ]`  }' |#|'|3      }(|(r<|(\  })}*}+d4|+ d5}, |$|)|*|,      }-|-s(|%ju                  |-       |&ju                  |'       Kt        jw                  d6|'       b |%rH|%ju                  |j
                         djy                  |%      |_        t        j                  d7|&|	       | j                  j{                  |j.                        }.|.rOt}        |.      d9k\  r@d1d:l?m@}/mA}0 d;}1d<}2d}3d=}4d}5d}6d}7d}8i }9	 tO               }9|9r|9j-                  d>i       }:tk        |:t              r|:}1ntk        |:t              rq|:j-                  d?      xs |:j-                  d>      xs |1}1|:j-                  d@      };|;	 t        |;      }5|:j-                  dA      xs d}6|:j-                  dB      xs d}7|9j-                  dCi       }<tk        |<t              rQt	        |<j-                  dDd            j                         dEv }3|<j-                  dF      }=|=	 t        |=      }>|>d1kD  r|>}4	 | j                  ||	tk        |9t              r|9ndG      \  }1}?|?j-                  dA      xs |6}6|?j-                  dB      xs |7}7|?j-                  dH      xs |8}8|5|7r	 	 d1dIlHmI}@  |@|9      }AAD ]  }Btk        |Bt              sBj-                  dB      xs dj                  dK      }C|Cs;C|7j                  dK      k(  sPBj-                  dLi       }Dtk        |Dt              r@Dj-                  |1i       }Etk        |Et              rEj-                  d@      }F|Ft        F      }5 n |3r |0|1|7xs d|8xs d|5|6xs dM      }Gt        |G|2z        }Ht        |GdNz        }It}        |.      }J|j                  }K|Kd1kD  rKdO}Ln
 |/|.      dP}L|4}MHk\  xs JMk\  }N|NrWt        j                  dQJdRLt        |2dSz        GdRHdR       | je                  || j                  |            }O	 d1dTlMmN}P | j                  ||	tk        |9t              r|9ndG      \  }1}?|?j-                  dH      r|.D Qcg c]I  }Q|Qj-                  dU      dVv r4Qj-                  dW      r#Qj-                  dU      |Qj-                  dW      dXK c}Qt}              d9k\  rc Pdi |?|1d9dddYg|j.                  dZ	 d[ _O        t        j                         }R|Rj                  dfd\       d{   \  }S}Tj.                  }U|U|j.                  k7  r!U|_        | j                  j                          | j                  j                  |j.                  S       d1|_K        |S}.t}        |S      }V |/|S      }Wt        j                  d]J|VdR|WdR       |WIk\  rt        jw                  d^WdR       t5        d_d      }X|Xt5        Xd`d      rt5        Xdad1      }Yt5        |Xdbd      xs dc}Zdd|Z deY df}[	 | jZ                  j-                  |j                        }\|\r2|j                  r&\jc                  |j                  [O.       d{    nXt5        Xdhd      rt5        Xdhd      }^t5        |Xdid      xs dc}_dj^ dk|_ dl}`	 | jZ                  j-                  |j                        }\|\r2|j                  r&\jc                  |j                  `O.       d{    | j                  |	       | j                         |.s| j                  j                         s|doz  }|.s|j                  r|j                  t        j                  k7  r|j                  t        j                  k7  r|j                  j                  }t        |      }at        j                  |a      sR|j                  t        j                  k(  rdpndq}bdr|j                          ds|b dt}| j                  ||       d{    |j                  t        j                  k(  re| jZ                  j-                  t        j                        }| j                  |      }c|cr)|r't        |du      r|j                  c      }d|dr|dd z  }| j                  |||.v       d{   }e|ey| j                  | jZ                  j-                  |j                        |	|       	 |j                  r|j                  j                  nd|j                  |j                  xs d|j.                  eddw dx}f| jD                  jG                  dy|f       d{    | j                  e||.||j.                  |	|| j                  |      |j                  z	       d{   }g	 | jZ                  j-                  |j                        }h|hr/t        hd{      r#hj                  |j                         d{    | j                  ||      st        j                  d||xs d}|       | jZ                  j-                  |j                        }it5        t        |i      d~d      ij                  ||       n*ir(t        id      rij                  j9                  |d       	 | j                  |       ygj-                  d      xs d}j|jdk(  rd}jgj-                  dg       }kt        j                          |z
  }l|gj-                  dd1      }mt}        j      }nt        j                  d||j                  xs dlmn       |	r8t        g      r-| j                  |	       	 | j                  j                  |	       t        gjt}        |.            }j|gj-                  d      rgd   |j.                  k7  r
gd   |_        	 d1dlrms}p  |ptO               t        |j                        dt5        | dd            }qqrjrgj-                  d      }r|rrnrj                         j                         }st}        |s      dkD  r*djy                  sdd       }t|tdt}        |s      dz
   dz  }tnrj                         }tdt dj }jd}u	 d1dlwmx}v  |vtO               t        |j                        gj-                  d>      |gj-                  dd1      xs d1gj-                  d@      xs dt        j                  j-                  dd            }uurjrgj-                  d      sj du }j| jD                  jG                  di fdjxs dddw i       d{    	 d1dlzm{}x xj                  rLxj                  j9                  d1      }yt        j                  | j                  |y             |xj                  rL	 d1dlzm{}z g }{zj                   j                         s`zj                   j                         }|||j-                  dd      }}|}dv r{ju                  |       zj                   j                         s`{D ]-  }|t        ||      }~|~s	 | j	                  ~|       d{    / 	 tQ        gj-                  d            }t	        |gj-                  dd            j                         |xrG tQ        gj-                  d            xs+ t        fddD              xs dv xr t}        |.      dkD  }|r!t        j                  d|j.                         n"r t        j                  d|j.                         gj-                  d      r|r|	rt        j                  d|j.                         | j                  j                  |	       | j                  |	       | j6                  j9                  |	d       | j;                  |	d       t        | d      r| j<                  j9                  |	d       jxs ddz   }jt        j                         j                         }rnp|.sngj-                  dg       }| j                  j                  |j.                  d|xs g t               |j                  r|j                  j                  ndd       rnr,| j                  j                  |j.                  ded       ngj-                  dt}        |.            }t}        k      |kD  rkd ng }|sY| j                  j                  |j.                  ded       jr| j                  j                  |j.                  djd       n[| j"                  du}D ]H  }|j-                  dU      dk(  ri di}| j                  j                  |j.                  |       J | j                  j                  |j                  gj-                  dd1             tQ        |gj-                  d            }| j                  |jk|      r| j                  |j       d{    gj-                  d      rgj-                  d      sjrC| jZ                  j-                  |j                        }|r| j                  j|       d{    urm	 | jZ                  j-                  |j                        }|rEjc                  |j                  u| je                  || j                  |            .       d{    	 | j                  |       yj| j                  |       S # t(        $ r t        j+                  d
d       d}
Y w xY w# t(        $ r t        j+                  dd       Y w xY w7 # t(        $ r Y w xY w# t(        $ r Y w xY w7 # t(        $ r!} t        j+                  d/|        Y d} ~ d} ~ ww xY w# t(        $ r"} t        jw                  d8|"|        Y d} ~ Cd} ~ ww xY w# t        t        f$ r Y w xY w# t        t        f$ r Y w xY w# t(        $ r Y w xY w# t(        $ r' |9j-                  dJ      }Atk        |Atl              sg }AY w xY w# t        t        f$ r Y  w xY w# t(        $ r Y 0w xY wc c}Qw 7 ~7 )# t(        $ r!}]t        jw                  dg]       Y d}]~]d}]~]ww xY w7 # t(        $ r!}]t        jw                  dm]       Y d}]~]d}]~]ww xY w# | j                  |	       | j                         w xY w# t(        $ r!} t        jw                  dn|        Y d} ~ d} ~ ww xY w7 97 7 7 7 ^# t(        $ r Y hw xY w# t(        $ r"}ot        j+                  d|	o       Y d}o~o
d}o~oww xY w# t(        $ r t5        | dd      }qY 	w xY w# t(        $ r#}wt        j+                  dw       d}uY d}w~wd}w~www xY w7 # t(        $ r!} t        j                  d|        Y d} ~ ^d} ~ ww xY w7 # t(        $ r!}t        j                  d       Y d}~d}~ww xY w# t(        $ r!} t        j+                  d|        Y d} ~ d} ~ ww xY w7 I7 7 y# t(        $ r!}ot        j+                  do       Y d}o~od}o~oww xY w# t(        $ r} 	 | jZ                  j-                  |j                        }|r0t        d{      r$j                  |j                         d{  7   n# t(        $ r Y nw xY wt        j!                  d|	       t        |       j"                  }t	        |       rt	        |       dd nd}d}t5        | dd      }dt%               v rt}        |.      nd1}dk(  rd}ndk(  rd}ndk(  rt5        | dd      }i }	 !j'                         j-                  di       }n# t(        $ r Y nw xY wj-                  d      dk(  r;j-                  dī      }|r%d1kD  r d1dl}|j+                  dz        }d| dǝ}n5d}n2d}n/dk(  rd}n'dv r#dkD  r	 Y d} ~ | j                  |       yd=k(  rd}d d d dѝcY d} ~ | j                  |       S d} ~ ww xY w# | j                  |       w xY ww)zAInner handler that runs under the _running_agents sentinel guard.r   r?   NP   r[  ro  z3inbound message: platform=%s user=%s chat=%s msg=%rr  r  r  %Failed to read Telegram topic bindingTr  r  z'Failed to record Telegram topic bindingwas_auto_resetF_pending_model_notesis_fresh_resetzsession:start)r   r  r  rh  privacy
redact_pii)r  auto_reset_reasonidler@  zy[System note: The user's previous session was stopped and suspended. This is a fresh conversation with no prior context.]dailyz[System note: The user's session was automatically reset by the daily schedule. This is a fresh conversation with no prior context.]zy[System note: The user's previous session expired due to inactivity. This is a fresh conversation with no prior context.]r  r  r  )r   session_typereset_had_activityz+previous session was stopped or interruptedzdaily schedule at z:00r  r  zh r  zinactive for u!   ◐ Session automatically reset (z). Conversation history cleared.
Use /resume to browse and restore a previous session.
Adjust reset timing in config.yaml under session_reset.r  z.Auto-reset notification failed (non-fatal): %s
auto_skillr   )_load_skill_payload_build_skill_messager   z[IMPORTANT: The "zB" skill is auto-loaded. Follow its instructions for this session.]z#[Gateway] Auto-skill '%s' not foundz0[Gateway] Auto-loaded skill(s) %s for session %sz-[Gateway] Failed to auto-load skill(s) %s: %sr  )estimate_messages_tokens_roughr  zanthropic/claude-sonnet-4.6g333333?  r   rH   r  r   r   compressionr  >   r   r  r  hygiene_hard_message_limitr'  r   get_compatible_custom_providerscustom_providersr    models)r   r   r  r   ffffff?actual	estimateduf   Session hygiene: %s messages, ~%s tokens (%s) — auto-compressing (threshold: %s%% of %s = %s tokens)rn  d   AIAgentrY   >   userr^   rZ   r]   memoryr   r  
quiet_modeskip_memoryenabled_toolsetsr  c                       y r  r   akws     r#   r  z:GatewayRunner._handle_message_with_agent.<locals>.<lambda>  r  r%   c                  ,    j                  d       S )Nr?   )approx_tokens_compress_context)_approx_tokens
_hyg_agent	_hyg_msgss   r#   r  z:GatewayRunner._handle_message_with_agent.<locals>.<lambda>  s     
0L0L,5r:H 1M 1* r%   u>   Session hygiene: compressed %s → %s msgs, ~%s → ~%s tokensz3Session hygiene: still ~%s tokens after compressioncontext_compressor_last_summary_fallback_used_last_summary_dropped_count_last_summary_errorr  u+   ⚠️ Context compression summary failed (z). z historical message(s) were removed and replaced with a placeholder. Earlier context is no longer recoverable. Consider /reset for a clean session, or check your auxiliary.compression model configuration.z9Failed to deliver compression-failure warning to user: %s_last_aux_model_failure_model_last_aux_model_failure_erroru%   ℹ️ Configured compression model `z
` failed (u   ). Recovered using your main model — context is intact — but you may want to check `auxiliary.compression.model` in config.yaml.z7Failed to deliver aux-model-fallback notice to user: %sz(Session hygiene auto-compress failed: %sz

[System note: This is the user's very first message ever. Briefly introduce yourself and mention that /help shows available commands. Keep the introduction concise -- one or two sentences max.]z/hermes sethomez/sethomeu    📬 No home channel is set for z^. A home channel is where Hermes delivers cron job results and cross-platform messages.

Type z8 to make this chat your home channel, or ignore to skip.get_voice_channel_contextrb  r  rc   rB  )r   r  r  r  rm  zagent:start)	rm  context_promptrc   r  r  rh  r  event_message_idr  stop_typinguK   Discarding stale agent result for %s — generation %d is no longer currentr  pop_post_delivery_callback
generation_post_delivery_callbacksr  (empty)u   ⚠️ The model returned no response after processing tool results. This can happen with some models — try again or rephrase your question.messagesr  zMresponse ready: platform=%s chat=%s time=%.1fs api_calls=%d response=%d charsz&clear_resume_pending failed for %s: %sr  resolve_display_settingr  rR  last_reasoning   z
_... (z more lines)_u   💭 **Reasoning:**
```
z
```

)build_footer_linelast_prompt_tokensr   )r(  platform_keyr   context_tokensr  r   zruntime_footer build failed: %salready_sentz	agent:endr  r'  zProcess watcher setup error: %sr  r  >   r  r  &Watch notification injection error: %szWatch queue drain error: %sr  r  compression_exhaustedc              3   &   K   | ]  }|v  
 y wr  r   )r  r  _err_str_for_classifys     r#   r  z;GatewayRunner._handle_message_with_agent.<locals>.<genexpr>r  s      aq11 r  )zcontext lengthzcontext sizezcontext windowzmaximum contextztoken limitztoo many tokenszreduce the lengthzexceeds the limitzrequest entity too largezprompt is too longzpayload too largezinput is too longr   r  zjSkipping transcript persistence for context-overflow failure in session %s to prevent session growth loop.up   Transient agent failure in session %s — persisting user message so conversation context is preserved on retry.z7Auto-resetting session %s after compression exhaustion.u   

🔄 Session auto-reset — the conversation exceeded the maximum context size and could not be compressed further. Your next message will start a fresh session.r  rf   )rY   r  r   r   r3   r  )rY   rZ   r3   r  r^   re   r3   )skip_dbr+  )r.  ztrailing footer send failed: %szAgent error in session %sr  zno details availablestatus_coderc   i  zH Check your API key or run `claude /login` to refresh OAuth credentials.i  zG Your API balance or quota is exhausted. Check your provider dashboard.i  usage_limit_reachedresets_in_secondsr/   z9 Your plan's usage limit has been reached. It resets in ~zh.zG Your plan's usage limit has been reached. Please wait until it resets.z@ You are being rate-limited. Please wait a moment and try again.i  z= The API is temporarily overloaded. Please try again shortly.>   r  rB  r  z% The request was rejected by the API.zSorry, I encountered an error (z).
z1Try again or use /reset to start a fresh session.r   )rN   r  r   r   r*   r   r:   r=  rL  r  r  r  rZ  r7  rh  r  r  r{  get_telegram_topic_bindingr  r   rM  rB   r  r8  r&  r&   r$  r-  r  r  
created_atr  r  r  r  r  r  _set_session_envr  r4   r  get_reset_policynotifynotify_exclude_platformsrE  at_houridle_minutes_format_session_infor  r  r  r  r2   rA  rD  r  r  r`  r>  ra  load_transcriptr]  r  r  r  rh   r5   rD   r8   rq  r6  r   r  rstripr+  r'  	run_agentr  	_print_fnr^  r   run_in_executorrA  rewrite_transcriptr9  r,  has_any_sessionsr  r  r  r   r@   r<  rB  r,  r  r=  _get_guild_idr  r  _bind_adapter_run_generation
_run_agentr  r  _is_session_run_currentr  r   r#  _clear_session_envr  rG  clear_resume_pendingr  gateway.display_configr'  r  r7   r  gateway.runtime_footerr*  rA   rY  r(  r  rr  r  r  completion_queueempty
get_nowaitr  _inject_watch_notificationr  reset_sessionr   rK   	isoformatappend_to_transcriptr  update_session_should_send_voice_reply_send_voice_reply_deliver_media_from_responser  __name__localsr  mathceil)r  rb  r  rO  r  _msg_start_time_platform_name_msg_previewr%  rh  bindingbound_session_idrB  _is_new_sessionr  _session_env_tokens_redact_pii_pcfgr  reset_reasonr  policyry   had_activityshould_notifyrk  reason_texthoursminsdurationnoticesession_infor  _auto_skill_namesr  r  _combined_parts_loaded_names_sname_loaded_loaded_skill
_skill_dir_display_name_note_partrc   r  r  
_hyg_model_hyg_threshold_pct_hyg_compression_enabled_hyg_hard_msg_limit_hyg_config_context_length_hyg_provider_hyg_base_url_hyg_api_key	_hyg_data
_model_cfg_raw_ctx	_comp_cfg_raw_hard_limitr  _hyg_runtime_gw_gcp_hyg_custom_providers_cp_cp_url
_cp_models_cp_model_cfg_cp_ctx_hyg_context_length_compress_token_threshold_warn_token_threshold
_msg_count_stored_tokens_token_source_HARD_MSG_LIMIT_needs_compress	_hyg_metar  r  loop_compressedr  _hyg_new_sid
_new_count_new_tokens_comp_dropped_err	_warn_msgr  _werr
_aux_model_aux_err_aux_msgr  sethome_cmdguild_id
vc_contextr  r{  r  _typing_adapter_stale_adapterr  agent_messages_response_time
_api_calls	_resp_lenr  _rds_show_reasoning_effectiver(  linesdisplay_reasoning_footer_line_bfl_footer_errr(  rb  _pr_watch_eventsr  r  
synth_texte2agent_failed_earlyis_context_overflow_failureri   	tool_defsr  new_messagesagent_persistedr[   r_   _already_sent_media_adapter_foot_adapter_err_adapter
error_typer  status_hintr5  	_hist_len	_err_body	_err_json
_resets_inr]  _hoursr  r2  r  r  s                                                                                                                                                          @@@@r#   rF  z(GatewayRunner._handle_message_with_agenta  sK    ))+29&//72S..Y\]c]l]lYm

(b#2.66tSAAF,,KK)NN'i	
 **@@H#//"";7''/ %% **EE/!&"2"23 F  ,0  #&w{{<'@'FB#G #(8M<T<T(T  $11@@N^_H+(0[77N ="2E:
 ))--k4@00dCt34))--k4@ $$(@(@@ ?}&6>?}&6> 	 ="2E:+0M(**///5;__FOO11"!>>+66*	4    (]K #33G< 	(*E		) 4 :??eTUK
 6g+V ="2E:"=2EtLVPVL{*  [(  f  [)F2^CN+R++22CC#__!(d!C D  :@ 5 5b&}6JER !- ; !MM M$M%V-L-LL 
 !"mm//@G';6*WK)W4,>v~~>Nc*RK$*$7$72$=E#)#6#6#;D:>%{[`ugRPTvUVDWimhnnofpH,9(*DK?} MV W !+/+D+D+FL+,284~)F &ll"NNF%)%E%Ef%M +    ,1M(.2M+ |T2u&0&<E7$u+LaZ-/+-* VF1&*MGCJ@z=/ ?I J  !5]JPU V +2259)008'LfUV ##**5::6!'_!=EJKKJ%{ $$44]5M5MN" s7|q( 7J!%'+$"%)-& M MLIN02	!*w!;J!*c2%/
#J5%/^^I%>%g*..QXBY%g]g
 $.>>2B#C#/%=@] : )3z(B(Jd(2z(B(Jd
 !*mR @I!)T236%MM)T:4%'%94:0 +4--8T*U*6%*-o*>#*Q;:A$7
/3/R/R%$/1;It1LIRV 0S 0,J
 %1$4$4Z$@$QMM$0$4$4Z$@$QMM#/#3#3I#>#N,L .5-;d4;I4F1
 $9 &C#-c4#8 ('*wwz':'@b&H&H&MG&7m6J6J36O+O-0WWXr-B
#-j$#?4>NN:r4RM'1-'F2?2C2CDT2U+2+>ILW,F %&$ (&>*0b(.B*D*0b'# -0'*<<-) ),,?$,F(G% \
 "/!A!A!A%%3N$,M%CG%LN$/M$ #6"&?? 5!_4  
 #KK>"~a&8=.45.q14Q7 !% @ @IeIefkIl mIE5373V3V#)(35?	45P	VZ 4W 40
L
 (++I6 *1)$%#$55=4I#I$%EE)$4 *+v155CS T)I  #9~2-4 ."&2."*434/3046>Z/</G/G."
f!N;PJ$8+2+C+C+ED;?;O;O(,)*<& 6&NK 4>3H3HL'3}7O7O'OCO(@(,(:(:(@(@(B$($6$6$I$I(5(@(@+%& HIM$D.9G14[1AJ2P(33&K %+KK)=(2J+9!*<+a	%& (36K'K(.-:/:1o)* -4J@TVZ,[E','8WULikp=q3:5B_ab3c/6u>SUY/Z/m^m-004vS
 C^-^ )2).7;}}7H7H7YH/7FNN6>mmFNNT]hqm6r0r0r */):wuNmos?t5<UDceg5h
3:5Bacg3h3{l{.ST^S_ `77?j Ab-b )1).7;}}7H7H7YH/7FNN6>mmFNNT\gpm6q0q0q %)$<$<[$I$($A$A*$M t11BBDNN 6??v(../PU[UdUdhphxhxUx"OO11M*=9G99W% (..8 &#  7}7J7J7L6M N (= ))*  33FFCCC ??h...mm''(8(89G))%0HG9T(U$>>xH
"ZL&99N "?? @ 
 

 
 	))MMfoo.	
o	9 6<__FOO11"!>>!>>/R+66'-H **//-::: "&$-(33'-!%!=!=e!D$33 "1 
" 
L"&--"3"3FOO"D"w'N)55fnnEEE //
NKa%#"
 "&!2!26??!C4/1MtT`"=="#1 >  $@Z([";;??
DQB ##$78 $''(89?RH 9$. 
 *--j"=N!YY[?:N%))+q9JHIKK_ ;)
I F|T11+>&&;;KH 7hCLH -,|2LP]PhPh2h+7+E(	TR,0(*(9$D"3U;	-) )X!-!1!12B!C!*002==?E5zB,0IIeCRj,A))xE
R7H-VV),:,@,@,B)!;<M;NiX`WabH L"L# 4 6!5foo!F&**73#/#3#34H!#L#QPQ#/#3#34D#E#M

~r:  1A1A.1Q&ZtL>: **//+ 00X^Tc20   CC&77.??CCAFG''(A(A'(JK '77?J "..446..99;C"wwv|<H#DD%,,S1	 ..446 ) WC!Ec!JJ!W"&"A"A*c"RRR	W@ "&l&6&6x&@!A$'(8(8"(E$F$L$L$N!
 += 
+\%%&=>? J  <  J 22Hs7|b7H ( +L!,,
 $M!,,  78]{M!,, ""00=((5--11+tD44[$G4!78--11+tD$ND ))+B
 +(,,Wb9	""77!,, .!*b!7!9=C__FOO$9$9RT%'	 +#
 ""77!,,#2N
 +../?WN?B>?RU`?`~kl;fh $&&;;%00!'LrR  **??)44%0XTVW '+&6&6d&BO+ 	776?h6$ 83 8R 8**??)44e$3 @ 	 --))#/#3#34H!#L .  !!1!1.!ABM,,UHn[h,i,,UH=== /8H8H8R%)]]%6%6v%GN%"??$e^    	L(,(9(9&//(J("/"4"4 & ,)-)I)I&RVRnRnotRu)v #5 #   ~ ##$78{ z ##$78o  DtT" ! [LL!JUYLZ[.$  		t  ) ! !  RMqQQRN  aNP\^_``av %.z#: % $%( %.z#: % $% !   ) ;4=MMBT4U1#-.CT#J8: 5;" &z2  N)*6&~ 1s/8 ).,2NN0k05-. -.).. 1r/8 ).,2NN0i05-. -.). %)$<$<[$I$($A$A*$M$ F D D6
4 ;
  F l ! LL@#R 4  T,3D:KU,S)T:  ">L!"  C>BBC( S( W"LL)QSUVVW ?:A>>?t >"
 % L%FKKL  8	#}}00AGL-$H&226>>BBB 8+Fa))J+.q63q6$3<7MLK!!]D9K(1VX(=G1Ic!h#g##Az48		 ,$-NN$4$8$8"$E	  ==(,AA!*/B!CJ!j1n#!%:+<!=(abhaiik&l&o"dK#]
* r>1 ##$78 !C'"IK1*T.-CD ##$78w8	v ##$78s  DAwAAa AAw1Ab D
AwAb(.Aw=8Ab+ 5AAw<DAc Ab; 00Ac  Ac!Ac %=Aw#5Ac; BAc; AAw0A=Af. .Ad) 9B
Af. Ad? A(Ae ?Af. Ae% <Af Af %A%Af 
CAwAAi AAf> .Ai 6Ah& AgDAh& 	AAg	 AgAg	 "7Ah& AAg9 .Ag6/Ag9 3"Ai C,AwAi:BAwAi= =AwA.Ao Aj AAo AjAo AAj	 (Aj)Aj	 -B&Ao Aw&B(Ao BAj B*AAo C/7Ak D&BAo F.BAk$ H1AAo I9AlI:Ao I?AAl KBAm3 M"Am3 M7AmNAmNAmNAm3 NOAo ]An ]A&Ao ^>An#^?Ao _A'An) `-An&`.An) `3AwaAo aAwa"Aa>a:Awa=Aa>a>Awb Ab%b!Awb$Ab%b%Awb+	Ab8b4Awb7Ab8b8Awb;	AccAc cAccAc c	Ac8cAc3c-Awc3Ac8c8Awc;	Ad&dAd!dAwd!Ad&d&Awd)Ad<d8Af. d;Ad<d<Af. d?AeeAf. eAeeAf. e	Ae"eAf. e!Ae"e"Af. e%,AffAf fAffAf fAf+f'Af. f*Af+f+Af. f.	Af;f7Awf:Af;f;Awf>Ai gAh& gAg	 g		Ag3gAg.g(Ah& g.Ag3g3Ah& g6Ag9 g9	Ah#hAhhAh& hAh#h#Ah& h&$Ai
i
Ai i	Ai7iAi2i,Awi2Ai7i7Awi=Awj Ao jAo jAj	 j		AjjAo jAjjAo j	Akj"Aj?j9Ao j?AkkAo kAk!kAo k Ak!k!Ao k$	Alk-AllAo lAllAo l	Am lAl;l5Ao l;Am m Ao mAmm	Am0mAm+m%Am3 m+Am0m0Am3 m3	Anm<AnnAo nAnnAo n#Ao n&An) n)	Aon2AooAo oAooAo o
Av9o!AAp9p2Ap5p3Ap9p8Av4p9	AqqAv4qAqqBAv4s#AttAv4t	AttAv4tAttA'Av4u5Av< u9AwvAv4vAv9vAv< v"Awv4Av9v9Av< v<AwwAwc                    ddl m}m} t               }d}d}d}d}d}d}		 t	               }	|	rz|	j                  di       }
t        |
t              rI|
j                  d      }|	 t        |      }|
j                  d      xs d}|
j                  d      xs d}	 ddlm}  ||	      }||	r	 |	j                  d	g       }|r|D ]  }t        |t              s|j                  d      xs d
}|j                  d      xs i }|r&||k(  r!|j                  d      }|	 t        |      } not        |t              sw|j                  |      }t        |t              r|j                  d      }n|}|t        |t        t        f      s	 t        |      } n 	 t               }|xs |j                  d      }|xs |j                  d      }|j                  d      } |||xs d
|xs d
||xs d
|      }|d}n
||k(  rd}nd}|dk\  r
|dz  dd}n|dk\  r	|dz   d}nt!        |      }d| dd|xs d d| d| dg}|r d|v sd|v sd|v r|j#                  d |        d!j%                  |      S # t        t        f$ r Y w xY w# t        $ r |	j                  d	      }Y w xY w# t        $ r Y w xY w# t        t        f$ r Y w xY w# t        t        f$ r Y %w xY w# t        $ r Y `w xY w# t        $ r Y *w xY w)"a  Resolve current model config and return a formatted info block.

        Surfaces model, provider, context length, and endpoint so gateway
        users can immediately see if context detection went wrong (e.g.
        local models falling to the 128K default).
        r   )r  DEFAULT_FALLBACK_CONTEXTNr   r  r   r   r  r  r?   r  r   )r   r   r  r   r  r  u:   default — set model.context_length in config to overridedetectedi@B z.1fMi  Ku   ◆ Model: `r  u   ◆ Provider: 
openrouteru   ◆ Context: z	 tokens (r  	localhostz	127.0.0.1z0.0.0.0u   ◆ Endpoint: r[  )r  r  r  r  r  rB   r2   rh   r5   rD   r8   r   r  r   r6   rF  r*   r`  ra  )r  r  r  r   r  r   r   r   custom_provsr  r  raw_ctxr  r  cpcp_model	cp_models
raw_cp_ctxmodel_entry	model_ctxrB  r  
ctx_sourcectx_displayr  s                            r#   r@  z"GatewayRunner._format_session_infoO  s    	\&( $	')D HHWb1	i.'mm,<=G*!47L1  )}}Z8@DH(}}Z8@DH@Q#B4#HL !(T#'88,>#C #. ))"d3$#%66'?#8b$&FF8$4$:	#E(9)+0@)AJ)5!)<?
O$9$) &i6*3--*>K)+t<,7OO<L,M	,7	(4IPSUZ|9\!)<?	N$9$)1)>	35G:7;;z#:H:7;;z#:Hkk),G 2^Mr"7^)
 !,!J77UJ#J Y&+i7<A>Ku$+u45Q7Kn-K 5'#X567K=	*Q?
 0K84Ky\dOdLL>(45yyw !*:6 ! ! ! @#'88,>#?L@ 		( )2:'> !)$(!) )2:'> !)$(!)   		s   AJ. "I7 -*J. J +A.K* J>%K* 87K* 0K* KK* AK: 7J
J. 	J

J. J+'J. *J++J. .	J;:J;>KK* KK* K'#K* &K''K* *	K76K7:	LLc                 D
  K   |j                   }| j                  |      }| j                  |d       | j                  j                  j                  |      }t        | dd      }|T|5  | j                  j                  |      }t        |t              r|d   n|r|nd}ddd       | j                  |       | j                  |       t        | dd      }||j                  |d       	 ddlm}	  |	        	 ddlm}
  |
        | j                  j%                  |      }| j&                  j                  |d       | j)                  |d       t+        | d	      r| j,                  j                  |d       | j/                  |       	 dd
lm} |r|j4                  nd} |d||j6                  r|j6                  j8                  nd       | j:                  j=                  d|j6                  r|j6                  j8                  nd|j>                  |d       d{    | j:                  j=                  d|j6                  r|j6                  j8                  nd|j>                  |d       d{    	 | jA                         }|r| jC                  |      xs tE        d      }n;| j                  jG                  |d      }| jC                  |      xs tE        d      }|jI                         jK                         }d}|rj| jL                  r^|r\ddl'm(} 	 |jS                  |      }|r5	 | jL                  jY                  |j4                  |       tE        d|      }n|stE        d      }||z   }| j[                  |      r|	 | j]                  ||       	 dd
lm} |r|j4                  nd} |d||j6                  r|j6                  j8                  nd       	 ddl1m2} tE        d  |       !      }|rtg        | d"| |       S tg        | |       S # 1 sw Y   }xY w# t        $ r Y 9w xY w# t        $ r Y ;w xY w# t        $ r Y w xY w7 :7 # t        $ r d}Y w xY w# tT        $ r#}d}tE        dtW        |            }Y d}~kd}~ww xY w# tT        $ r!}tE        dtW        |            }Y d}~Td}~wt        $ r Y cw xY w# t        $ r t^        ja                  dd       Y _w xY w# t        $ r Y +w xY w# t        $ r d}Y $w xY ww)#zHandle /new or /reset command.session_resetr  ro  Nr   rf  )clear_env_passthrough)clear_credential_filesr  r%  r'  r?   r)  zsession:end)r   r  rh  zsession:resetzgateway.reset.header_defaultT)	force_newzgateway.reset.header_newr0  zgateway.reset.title_rejectedr  zgateway.reset.header_titledr,  z"gateway.reset.title_error_untitledz"gateway.reset.title_empty_untitledz*Failed to rebind Telegram topic after /newr  on_session_reset)get_random_tipzgateway.reset.tip)tipr  )4r  r  r  rZ  r  rB   r&   rm  r2   r<  r,  r9  r-  tools.env_passthroughr  r   r  r  rT  r$  r  r  r  &_clear_session_boundary_security_stater+  r&  r  r   r   r  r  r  r@  r"  r   r7  r  r7   r{  r|  r1  sanitize_titler8   r*   set_session_titler  r&  r=  rM  hermes_cli.tipsr  r$  )r  rb  r  rh  	old_entryr]  r^  
_old_agent_qer  r  	new_entryr-  _old_sidrq  header
_title_arg_title_noter1  r"   r  _new_sidr  	_tip_lines                           r#   r  z#GatewayRunner._handle_reset_command  s     226://O/T &&//33K@	
 d$7>" h++//<+5gu+EWQZV]7cg
h %--j9  -
 d,d3?GGK&	C!#	E"$
 &&44[A	 	%%))+t<,,[$?4/0%%))+t<
 	33K@	F/8y++dH.8;A??&//"7"7PRT jjoom17--b~~&.
  	 	 jjooo17--b~~&0
  	 		446L 44V<aB`@aF **@@SW@XI44V<]B\@]F ++-335
$**y.N%44Z@	 $$66y7K7KYW<INF
 ! DE+% ''/I4IZ33FIF
	F/8y++dH+;A??&//"7"7PRT	6->3CDI !VHDyk"JKK455Ch h"  		  		0  				  	L	"  N 	 >c!fMN " X"#$HPSTUPV"WK    ZITXYZ  		  	I	s  A'T )7P AT .P( <P8 	BT AQ AT QAT 2Q3T 8Q BT Q0 ,T /3R "&T 	S AS>  T 8#T P% T (	P51T 4P55T 8	QT QT 	QT QT T Q-)T ,Q--T 0	R9RT RT 	S(S>T ST ST  S;7T :S;;T >	TT 
TT TT TT c                    K   ddl m} ddlm}  |       } |       }t	        d|      t	        d|      g}dj                  |      S w)	u@   Handle /profile — show active profile name and home directory.r   display_hermes_homer  zgateway.profile.header)r  zgateway.profile.home)r"  r[  )r  r  r  r  r   ra  )r  rb  r  r  r   profile_namer  s          r#   r"  z%GatewayRunner._handle_profile_commandZ   sL     8?%'.0 &=$73

 yys   AAcanonical_cmdc                    ddl m} |sy || j                  |      }|j                  r|j	                  |j
                  |      ryt        j                  d||j                  r|j                  j                  nd|j
                         t        |j                        }|r5ddj                  d |dd	 D              z   t        |      d	kD  rd
ndz   dz   }nd}d| d| S )u  Return a denial message if ``source`` cannot run ``canonical_cmd``,
        else None. Used by both the cold and running-agent dispatch paths
        in ``_handle_message`` so admin/user gating can't be bypassed by
        an in-flight agent.

        Backward-compat semantics live in
        :func:`gateway.slash_access.policy_for_source` — when the operator
        hasn't set ``allow_admin_from`` for the scope, the policy returns
        ``enabled=False`` and this method always returns None.
        r   policy_for_sourceNzLSlash command /%s denied for %s:%s (not admin, not in user_allowed_commands)r  zYou can run: r  c              3   &   K   | ]	  }d |   ywr    Nr   r  rI  s     r#   r  z4GatewayRunner._check_slash_access.<locals>.<genexpr>   s     BasGB      r  r?   z . Use /whoami for the full list.zNo slash commands are enabled for non-admins on this platform. Ask an admin to add you to allow_admin_from or to set user_allowed_commands.u   ⛔ /z is admin-only here. )gateway.slash_accessr  r  r  can_runr  r=  rL  r   r   rO  user_allowed_commandsra  r]  )r  r  r  _policy_for_sourceri  allowed_previewsuffixs          r#   r  z!GatewayRunner._check_slash_accessj   s     	Q#DKK8~~!NZ%+__FOO!!#NN		
 !!=!=>))B_Sb-ABBC02552? 55 3 
 }o%:6(CCr%   c                   K   ddl m} |j                  } || j                  |      }|r"|j                  r|j                  j
                  nd}|r|j                  ndxs d}|j                         dv rdnd}|r|j                  nd	xs d}|j                  sd
| d| d| dS |j                  |      rd
| d| d| dS ddg}	t        |j                        }
t               }g }|	|
z   D ])  }||vs|j                  |       |j                  |       + |rdj!                  d |D              nd}d
| d| d| d| S w)uD  Handle /whoami — show the user's slash command access on this scope.

        Always works (it's in the always-allowed floor of slash_access).
        Reports: platform, scope (DM vs group), the user's tier
        (admin / user / unrestricted), and the slash commands they can
        actually run on this scope.
        r   r  r  r?   r  >   r?   r  directr  DMzgroup/channelNu   **You** — r  z)
User ID: `z\`
Tier: unrestricted (no admin list configured for this scope)
Slash commands: all availablez/`
Tier: **admin**
Slash commands: all availabler  r  r  c              3   &   K   | ]	  }d |   ywr  r   r 	  s     r#   r  z7GatewayRunner._handle_whoami_command.<locals>.<genexpr>   s      ;Q1QC ;r	  z(none)z)`
Tier: user
Slash commands you can run: )r	  r  r  r  r   r   r  rq  r  r  is_adminrO  r	  r  r  r`  ra  )r  rb  r	  r  ri  r   r  scoper  floor
configuredseenrunnablerI  runnable_strs                  r#   r(  z$GatewayRunner._handle_whoami_command   s}     	Q#DKK8,2v6??((C)/V%%R@D	!)-LLRa%+6>>=#~~xj5' 2$I &01 ??7#xj5' 2$I &01 "F889
 # 	#A}"	# @Htyy ;( ;;X8*Bug . 	 "++7.:	
s   C0E3AEc                    K   ddl }ddl}ddl}ddlm} |j
                  xs dj                         }|j                  d      r|j                  d      }|j                  d      r|t        d      d j                         }|r |j                  |      ng }dd}d}	|	t        |      k  rY||	   }
|
dk(  r |	dz   t        |      k\  rn=||	dz      |	d	z  }	8|
j                  d
      r|
j                  dd      d   |	dz  }	d|
}	 |dk(  }	  |j                  ||       d{   }|r|r |j                  d|      }|r|j                  d      	 |j                   }t#        |dd      }t%        |d      r|j&                  nt)        |xs d      j+                         t)        t#        |dd      xs d      t)        t#        |dd      xs d      t)        t#        |dd      xs d      xs drHrF fd} |j                  |       d{    |j-                         dz   t        d      z   }t        |      dkD  r|dd dz   t        d      z   }|xs t        d      S 7 M# t        $ r}t        d|      cY d}~S d}~ww xY w7 |# t        $ r }t.        j1                  d|       Y d}~}d}~ww xY ww)u  Handle /kanban — delegate to the shared kanban CLI.

        Run the potentially-blocking DB work in a thread pool so the
        gateway event loop stays responsive.  Read operations (list,
        show, context, tail) are permitted while an agent is running;
        mutations are allowed too because the board is profile-agnostic
        and does not touch the running agent's state.

        For ``/kanban create`` invocations we also auto-subscribe the
        originating gateway source (platform + chat + thread) to the new
        task's terminal events, so the user hears back when the worker
        completes / blocks / auto-blocks / crashes without having to poll.
        r   N)	run_slashr?   r    r  z--boardr   r{  z--board==createzgateway.kanban.error_prefixr  zCreated\s+(t_[0-9a-f]+)\br   r   r  r  r  c                      ddl m}  | j                        }	 | j                  |xs d t	        dd       xs j                                |j                          y # |j                          w xY w)Nr   ri  rs  rq  )rv  r   r  r  r  ru  )r  rj  r  add_notify_subr&   rp  r2  )	r  r  r  r  requested_boardr  rv  r  r  s	     r#   _subz2GatewayRunner._handle_kanban_command.<locals>._sub!  s    C#&;;_;#ED	- # 2 2$('-97.7.?4,35<TC]_c5d  6Dhl  iB  iB  iD !3 !" !%



s   9A% %A7r[  z gateway.kanban.subscribed_suffixr   z'kanban create auto-subscribe failed: %si  zgateway.kanban.truncated_suffixzgateway.kanban.no_output)r^  r(   r  hermes_cli.kanbanr	  r   r7   r^  r$  r]  rp  r  r   r   searchr!   r  r&   r  r   r*   rq  rB  r=  r>  )r  rb  r^  r(   r  r	  r   r  r  rd  tok	is_creater  rE  r  r  r   r	  r  r  r	  rv  r  r  s   `                 @@@@@@r#   r  z$GatewayRunner._handle_kanban_command   s     	/

 b'')??3;;s#D??8$H'..0D&*T"#f+o)Ciq5CK'"(Q-Q~~j)"%))C"3A"6QFh&		?,7,,Y==F 		6?A''!*S"\\F&vz4@H*1(G*D#hnZ\J]eg ! "'&)R"@"FBGG #GFK$D$J KI!'&)R"@"FBGO4G#- - 0g//555"MMO"# BGTU  v;ET]T)A.O,PPF6566a > 	?2#>>	?D 6 ! SNN#LcRRSs   DK	I8 (I5)I8 -,K	CJ J%J 1K	5I8 8	JJJK	JK	J 	K&K<K	KK	c                   K   |j                   }| j                  j                  |      }| j                  j	                         D cg c]  }|j
                   }}|j                  }|| j                  v }|r%| j                  j                  |j                        nd}| j                  ||      }	d}
d}| j                  r	 | j                  j                  |j                        }
	 | j                  j                  |j                        }|rm|j                  d      xs d|j                  d      xs dz   |j                  d      xs dz   |j                  d      xs dz   |j                  d      xs dz   }t!        d	      d
t!        d|j                        g}|
r|j#                  t!        d|
             |j%                  t!        d|j&                  j)                  d            t!        d|j*                  j)                  d            t!        d|d      t!        d|rt!        d      n
t!        d            g       |	r|j#                  t!        d|	             |j%                  d
t!        ddj-                  |            g       dj-                  |      S c c}w # t        $ r d}
Y w xY w# t        $ r d}Y Mw xY ww) zHandle /status command.Nrt  r   input_tokensoutput_tokenscache_read_tokenscache_write_tokensreasoning_tokenszgateway.status.headerr?   zgateway.status.session_idr  zgateway.status.titler  zgateway.status.createdz%Y-%m-%d %H:%M)r3   zgateway.status.last_activityzgateway.status.tokensrn  r  zgateway.status.agent_runningzgateway.status.state_yeszgateway.status.state_nostatezgateway.status.queuedrx  zgateway.status.platformsr  )rP  r[  )r  rZ  r7  rE  r,  r   rh  rd  rB   r   rw  r{  get_session_titler  r   get_sessionr   r`  extendr9  strftimer  ra  )r  rb  r  r%  r  connected_platformsrh  
is_runningrk  queue_depthr,  db_total_tokensr(  r  s                 r#   r  z$GatewayRunner._handle_status_command+!  s    **@@H040B0B0DE1qwwEE $// D$8$88
 9?$--##FOO4D''W'E ((::=;S;ST$&&22=3K3KL05A77?38q:77#67<1> 77#78=A? 77#56;!	= $ %&)m6N6NO

 LL1?@&-2J2J2S2STd2ef,8P8P8Y8YZj8kl%0CE,U_A6P4Qef  hA  fB  C	
 	 LL2+FG(DII>Q4RS
 	
 yyq F*    $"#$sW   AKJ$A(K%J) (BJ; <D-K)J84K7J88K;K
K	K

Kc                   K   ddl m}m} t        j                         }| j	                  |j
                        }t        | di       xs i }t        | di       xs i }g }|j                         D ]  \  }	}
t        |j                  |	|            }t        dt        ||z
              }|
t        u }|j                  |	||rt        d      n
t        d      |rdnt        t        |
dd      xs d      |rdnt        t        |
d	d      xs d      d
        |j!                  d d       g }	 |j#                         D cg c]  }|j                  d      dk(  r| }}t        | dt'                     xs
 t'               D cg c]!  }t)        |d      r |j*                         s|# }}t        d      dt        dt-        |            g}|rt/        |dd d      D ]k  \  }}|d   |k(  rt        d      nd}|d   r	d|d    dnd}|d	   r	d|d	    dnd}|j                  | d|d    d|d    d ||d           | | | 
       m t-        |      dkD  r(|j                  t        d!t-        |      dz
               |j1                  dt        d"t-        |            g       |r|dd D ]  }d#j3                  t        |j                  d$d            j5                               }t-        |      d%kD  r|dd& d'z   }|j                  d(|j                  dd)       d |t        |j                  d*d                   d| d        t-        |      dkD  r(|j                  t        d!t-        |      dz
               |j1                  dt        d+t-        |            g       |s/|s-|s+|j                  d       |j                  t        d,             d-j3                  |      S c c}w # t$        $ r g }Y w xY wc c}w w).z>Handle /agents command - list active agents and running tasks.r   )format_uptime_shortr(  rd  r  zgateway.agents.state_startingzgateway.agents.state_runningr?   r  r   )rh  elapsedr(	  r  r   c                     | d   S )Nr4	  r   )r(  s    r#   r  z6GatewayRunner._handle_agents_command.<locals>.<lambda>!  s
    I r%   T)r+  reverser  r  r  r  zgateway.agents.headerzgateway.agents.active_agentsr)	  Nr	  r   rh  zgateway.agents.this_chatu    · `r  z. `u   ` · r(	  u    · r4	  zgateway.agents.morez gateway.agents.running_processesro  r6  r7  W   r  z- `r  uptime_secondszgateway.agents.async_jobszgateway.agents.noner[  )rY  r3	  r(  rN   r  r  r&   r  r6   rB   r  r5   r  r`  r   r*   sortlist_sessionsr   r  r  r  r]  r\  r,	  ra  rp  )r  rb  r3	  r(  rK   current_session_keyrunning_agentsrunning_started
agent_rowsrh  r   startedr4	  
is_pendingrunning_processesr  r   background_tasksr  idxr(  rP   sidr   r  rc  s                             r#   r  z$GatewayRunner._handle_agents_commandj!  s8    Piik"::5<<H&t->CIr '.BB G M2!#
"0"6"6"8 	KO//SABG!Sw/0G"99J#.&CMQ>?STUsSt(2"GE<Y[<\<b`b8c#-R3wugr7R7XVX3Y		 	6E(*	#+99;!55?i/ ! !  &935AJSU
q&!&!&&( 
 
 %&,C
OD
 %j"oq9 S;>};MQd;d!67jl69,6Gc,/02R36w<%G~Q/Re3s=12%G~T*3y>:;C5yR	 :#Q4C
Ob<PQR4C@Q<RS	
 )#2. hhs488Ir#:;AACDs8b=cr(U*C$((<56e*3txx8H!/L+MNOuUXTYYZ\	 $%*Q4C@Q<RUW<WXY-S9I5JK	
 "3<LLLLL012yyw!  	# "	#
sI   D*P-O/ ?O*O/ #P&P'IP*O/ /O>:P=O>>Pc                   K   |j                   }| j                  j                  |      }|j                  }| j                  j                  |      }|t        u rK| j                  ||t        d       d{    t        j                  d|       t        t        d            S |r5| j                  ||t        d       d{    t        t        d            S t        d      S 7 p7 %w)	a  Handle /stop command - interrupt a running agent.

        When an agent is truly hung (blocked thread that never checks
        _interrupt_requested), the early intercept in _handle_message()
        handles /stop before this method is reached.  This handler fires
        only through normal command dispatch (no running agent) or as a
        fallback.  Force-clean the session lock in all cases for safety.

        The session is preserved so the user can continue the conversation.
        stop_command_pendingr  Nu2   STOP (pending) for session %s — sentinel clearedzgateway.stop.stopped_pendingstop_command_handlerr  zgateway.stop.no_active)r  rZ  r7  rh  rd  rB   r  r  r  r=  rL  r$  r   )r  rb  r  r%  rh  r   s         r#   r)  z"GatewayRunner._handle_stop_command!  s      **@@H#//$$((5++33!7$:	 4    KKLkZ!!$B"CDD 33!7$:	 4    "!$:";<<-..'s%   A3C*5C&6AC*C($C*(C*c                 h  K   | j                  |      rdt        j                  d|j                  r6|j                  j                  r |j                  j                  j
                  nd|j                         y| j                  s| j                  r3| j                         }|rt        d|      S t        t        d            S 	 |j                  j                  r |j                  j                  j
                  nd|j                  j                  d}|j                  j                  r|j                  j                  |d	<   t        t        d
z  |d       	 |j                  j                  r |j                  j                  j
                  ndt%        j$                         d}|j                  |j                  |d<   t        t        dz  |d       | j                         }t'        t(        j*                  j-                  d            }|r| j/                  dd       n| j/                  dd       |rt        d|      S t        t        d            S # t         $ r!}t        j#                  d|       Y d}~'d}~ww xY w# t         $ r }t        j#                  d|       Y d}~d}~ww xY ww)zFHandle /restart command - drain active work, then restart the gateway.uo   Ignoring redelivered /restart (platform=%s, update_id=%s) — already processed by a previous gateway instance.r  r?   zgateway.drainingr)	  zgateway.restart.in_progressN)r   r  r  r   r  z'Failed to write restart notify file: %s)r   requested_at	update_id.restart_last_processed.jsonz(Failed to write restart dedup marker: %sINVOCATION_IDFTrg  zgateway.restart.restarting)_is_stale_restart_redeliveryr=  rL  r  r   r   platform_update_idr  r  rZ  r   r$  r  r  r   r   r   rM  rN   r4   r@   rA   rB   ru  )r  rb  rx  notify_datar  
dedup_datar  _under_services           r#   r  z%GatewayRunner._handle_restart_command!  s?     ,,U3KKD/4||@U@U%%++[^((	 ""dnn--/E+599!!$A"BCC	G;@<<;P;PELL1177VZ <<//K ||%%+0<<+A+AK(55	H;@<<;P;PELL1177VZ $		J ''3*/*B*B
;'== 113 bjjnn_=>  %T B  $E B'}==a <=>>I  	GLLBAFF	G(  	HLLCQGG	HsW   CJ2BI A=J BJ2	J"I>8J2>JJ2	J/J*%J2*J//J2c                 ~   ||j                   y|j                  y|j                   j                  y	 |j                   j                  j                  }|dk7  ry	 t
        dz  }|j                         syt        j                  |j                               }|j                  d      |k7  ry|j                  d      }t        |t              sy|j                  d      }t        |t        t        f      rt        j                         |z
  dkD  ry|j                  |k  S # t        $ r Y yw xY w# t        $ r Y yw xY w)aB  Return True if this /restart is a Telegram re-delivery we already handled.

        The previous gateway wrote ``.restart_last_processed.json`` with the
        triggering platform + update_id when it processed the /restart.  If
        we now see a /restart on the same platform with an update_id <= that
        recorded value AND the marker is recent (< 5 minutes), it's a
        redelivery and should be ignored.

        Only applies to Telegram today (the only platform that exposes a
        numeric cross-session update ordering); other platforms return False.
        Fr   rK	  r   rJ	  rI	  r  )r  rN	  r   r   r   r   rr   r  r  r  rB   r2   r5   r6   rN   )r  rb  r-   marker_pathr  recorded_uidrI	  s          r#   rM	  z*GatewayRunner._is_stale_restart_redelivery;"  s1    =ELL0##+<<  (	"\\2288N Z'	&)GGK%%'::k3356D 88J>1xx,,, xx/lS%L1yy{\)C/''<773  		  		s)    D! D0 6#D0 !	D-,D-0	D<;D<c           
        K   ddl m} t        d      g |       }	 ddlm}  |       }|r|j                  t        dt        |                   t        |      }|dd D ]  }|j                  d	| d
||   d           ! t        |      dkD  r(|j                  t        dt        |      dz
               t        dj                  |      t        t        |dd      dd            S # t        $ r Y <w xY ww)z/Handle /help command - list available commands.r   gateway_help_lineszgateway.help.headerr  zgateway.help.skill_headerr)	  Nr  r     ` — descriptionzgateway.help.more_use_commandsr[  r  r   )r'   rW	  r   rD  r  r`  r]  rO  r   r.   ra  r&   )r  rb  rW	  r  r  r  sorted_cmdsrc  s           r#   r   z"GatewayRunner._handle_help_commandm"  s    :#$
!
	?+-JQ:#j/RS$Z0&s+ RCLL1SE
30N/O!PQR{#b(LL#C3{K[^`K`!ab -IIeGE8T2JE
 	
  		s)   C9BC* 91C9*	C63C95C66C9c           
        K   ddl m} |j                         j                         }|r	 t	        |      }nd}t         |             }	 ddlm	}  |       }|r|j                  d       |j                  t        d             t        |      D ]I  }||   j                  dd      j                         xs t        d	      }	|j                  d
| d|	        K |st        d      S ddlm}
 |j                   j"                  |
j$                  k(  rdnd}t'        dt)        |      |z   dz
  |z        }t'        dt+        ||            }|dz
  |z  }||||z    }t        dt)        |      ||      dg|}|dkD  rlg }|dkD  r|j                  t        d|dz
               ||k  r|j                  t        d|dz                |j-                  ddj/                  |      g       ||k7  r|j                  t        d||             t1        dj/                  |      t3        t3        |dd      dd            S # t
        $ r t        d      cY S w xY w# t        $ r Y w xY ww)zDHandle /commands [page] - paginated list of all commands and skills.r   rV	  zgateway.commands.usager   rX	  r?   zgateway.commands.skill_headerrZ	  zgateway.commands.default_descr  rY	  zgateway.commands.noner*  r)  r  zgateway.commands.header)totalpagetotal_pageszgateway.commands.nav_prev)r^	  zgateway.commands.nav_nextz | zgateway.commands.out_of_range)r4  r^	  r[  r  Nr   )r'   rW	  r  r7   r5   r8   r   rA  rD  r  r`  rO  rB   r   r1  r  r  r   r	  r  r]  r  r,	  ra  r.   r&   )r  rb  rW	  r  requested_pageentriesr  r  rc  descr  	page_sizer_	  r^	  r  page_entriesr  	nav_partss                     r#   r!  z&GatewayRunner._handle_commands_command"  se    :))+1133!$X N )+,
	?+-Jr"q!@AB!*- :C%c?..}bAGGIoQOnMoDNNQse6$#89: ,--+,,//83D3DDB"	!c'lY6:yHI1c.+67Y&uUY%67 's7|$T_`
 

 ?Iax  #>TAX!NOk!  #>TAX!NOLL"ejj345>!LL:n[_`a,IIeGE8T2JE
 	
W  31223   		sM   'I"H8 I"	BI EI"8II"II"	II"II"c                 T  /0123456789K   ddl }ddlm5m}m}m} ddlm} |j                         j                         } ||      \  }}	}
d}d}d7d6d9d8t        dz  }	 t               }|rx|j                  di       }t        |t              r6|j                  d	d      }|j                  d
|      }|j                  dd      7|j                  d      9	 ddlm}  ||      8|j$                  }| j'                  |      }| j(                  j                  |i       }|rH|j                  d|      }|j                  d
|      }|j                  d7      7|j                  d6      6|sP|	sM| j*                  j                  |j,                        }|duxr t/        t1        |      dd      du}|r	  ||7|98d      }|r| 3|4|1|2706/dt2        dt2        dt2        dt2        f/0123456789fd}| j5                  || j7                  |            }|j9                  |j:                  ||||||       d{   }|j<                  ry ||      }t?        d|xs d|      dg}	  ||7|98d      }|D ]  }|d   rt?        d      nd}|jA                  d|d     d!|d"    d#| d$       |d%   rgd&jC                  d' |d%   D              }|d(   tE        |d%         kD  rt?        d)|d(   tE        |d%         z
  *      nd}|jA                  d+| |        n)|j                  d,      r|jA                  d-|d,    d#       |jA                  d        	 |jA                  t?        d.             |jA                  t?        d/             |jA                  t?        d0             d1jC                  |      S  5|||76|
|	982	      }|j<                  st?        d3|jF                  4      S d} t/        | d5d      }!t/        | d6d      }"|!r|"|!5  |"j                  |      } ddd       | rQ| d   L	 | d   j                  |jH                  |jJ                  |jL                  |jN                  |jP                  7       tW        | d9      si | _,        d:| d;|jH                   d<|jZ                  xs |jJ                   d=| jX                  |<   |jH                  |jJ                  |jL                  |jN                  |jP                  d>| j(                  |<   | j]                  |       |
r	 |j_                         r,ta        |d?@      5 }$|jc                  |$      xs i }ddd       ni }je                  di       }|jH                  |d	<   |jJ                  |d
<   |jN                  r|jN                  |d<   ddAlm3}%  |%|       |jZ                  xs |jJ                  }t?        dC|jH                  D      g}|jA                  t?        dE|F             |jh                  }'ddGlm5}( d})	 t               }*|*j                  di       }+t        |+t              r|+j                  dH      },|,tm        |,      }) |(|jH                  |jJ                  |jN                  xs 7xs d|jL                  xs 6xs d|'8|)I      }-|-r|jA                  t?        dJ|-dKL             |'r|'jn                  r(|jA                  t?        dM|'jn                  dKL             |'jq                         r*|jA                  t?        dN|'js                         O             |jA                  t?        dP|'ju                         Q             tw        |jN                  xs ddR      xr dS|jH                  jy                         v xs |jP                  dTk(  }.|.r|jA                  t?        dU             |jz                  r&|jA                  t?        dV|jz                  W             |
r|jA                  t?        dX             n|jA                  t?        dY             d1jC                  |      S # t"        $ r |j                  d      8Y w xY w# t"        $ r Y w xY w# t"        $ r g }Y w xY w7 P# t"        $ r Y Aw xY w# 1 sw Y   xY w# t"        $ r!}#tR        jU                  d8|#       Y d}#~#Ud}#~#ww xY w# 1 sw Y   xY w# t"        $ r!}&tR        jU                  dB|&       Y d}&~&Od}&~&ww xY w# t"        $ r Y w xY ww)Zu  Handle /model command — switch model for this session.

        Supports:
          /model                              — interactive picker (Telegram/Discord) or text list
          /model <name>                       — switch for this session only
          /model <name> --global              — switch and persist to config.yaml
          /model <name> --provider <provider> — switch provider + model
          /model --provider <provider>        — switch to provider, auto-detect model
        r   N)switch_modelparse_model_flagslist_authenticated_providerslist_picker_providers)	get_labelr?   r  r   r   rH   r   r   	providersr  r  r   send_model_pickerr  )current_providercurrent_base_urlcurrent_modeluser_providersr  
max_models_chat_idmodel_idprovider_slugr   c                 $  K    |d|	      }|j                   st        d|j                        S d}t        dd      }t        dd      }|r||5  |j	                        }ddd       |rQ|d   L	 |d   j                  |j                  |j                  |j                  |j                  |j                  	       t        d      si _        d d|j                   d|j                   xs |j                   dj                  <   |j                  |j                  |j                  |j                  |j                  dj"                  <   j%                         |j                   xs |j                  }t        d|j                        g}	|	j'                  t        d|             |j(                  }
ddlm} d}	 t/               }|j	                  di       }t1        |t2              r|j	                  d      }|t5        |      } ||j                  |j                  |j                  xs xs d|j                  xs xs d|
|      }|r|	j'                  t        d|d             |
r|
j6                  r(|	j'                  t        d|
j6                  d             |
j9                         r*|	j'                  t        d|
j;                                      |	j'                  t        d |
j=                         !             |	j'                  t        d"             d#j?                  |	      S # 1 sw Y   xY w# t        $ r!}t        j                  d
|       Y d}~d}~ww xY w# t        $ r Y rw xY ww)$z6Perform the model switch and return confirmation text.F		raw_inputrn	  rp	  ro	  current_api_key	is_globalexplicit_providerrq	  r  gateway.model.error_prefixr  Nro  rm  r   	new_modelnew_providerr   r   r5  z/Picker model switch failed for cached agent: %sr  $[Note: model was just switched from  to  via /. Adjust your self-identification accordingly.]r   r   r   r   r5  gateway.model.switchedr   gateway.model.provider_labelr   resolve_display_context_lengthr   r  r?   r   r   
model_infor  r  gateway.model.context_labelrn  r&	  gateway.model.max_output_labelgateway.model.cost_labelcost gateway.model.capabilities_labelcapabilitiesgateway.model.session_only_hintr[  ) r  r   rG  r&   rB   rg	  r~	  target_providerr   r   r5  r   r=  r>  r  r  provider_labelr$  r9  r`  r	  hermes_cli.model_switchr	  r  r2   rh   r5   
max_outputhas_cost_dataformat_costformat_capabilitiesra  )rs	  rt	  ru	  r  cached_entryr]  r  rE  plabelr  mir	  _sw_config_ctx_sw_cfg_sw_model_cfg_sw_rawctx_cur_api_key_cur_base_url
_cur_model_cur_provider_self_session_key_switch_modelry	  ro	  r  
user_provss                    r#   _on_model_selectedz?GatewayRunner._handle_model_command.<locals>._on_model_selected#  sv     "/&.-:*4-:,8&+.;+5-9
"  &~~#$%AI]I]#^^ (,&-e5H$&O!(!E&6+=!, H/5zz,/GH'LO,G	g ,Q < <.4.>.>171G1G,2NN-3__-3__ != !"  'u.DE9;E6B:,dSYScScRd e##)#8#8#RF<R<R"S TLM 22<@ &,%5%5(.(>(>'-~~(.(.H66|D 11,? "(!6!6!P&:P:P!"#;6CSCS!T UQ'EPV%WX#..Z)-!&:&<G,3KK,DM)->*7*;*;<L*M#*#658\N =",,"22%+__%N8H%NB$*NN$Ko$K')-92@ !LL+HTWXYSZ)\]!}} %Q/O[][h[hijZk-m n!//1 %Q/IPRP^P^P`-a b!LL+M\^\r\r\t)uvQ'H%IJ#yy//KH H $- g &/`be f fgL  ) ! !sp   ANM,N<AM DNA
N  D,NMN	M=M82N8M==N 	N	NNN)r  rl	  rp	  rn	  rh  on_model_selectedr  zgateway.model.current_labelr  )r   r   r  
is_currentzgateway.model.current_tagz**rG   z** `--provider r  r  rz  r  r  c              3   (   K   | ]
  }d | d   ywr  Nr   )r  r  s     r#   r  z6GatewayRunner._handle_model_command.<locals>.<genexpr>#  s     .MA1#Qx.M   total_modelsz gateway.model.more_models_suffixr)	  z  api_urlz  `z gateway.model.usage_switch_modelz#gateway.model.usage_switch_providerzgateway.model.usage_persistr[  rw	  r|	  r  ro  rm  r}	  z1In-place model switch failed for cached agent: %sr  r	  r	  r	  r	  r	  r   r   )save_configz"Failed to persist model switch: %sr	  r	  r	  r	  r	  r  r	  r	  rn  r&	  r	  r	  r	  r	  r	  zopenrouter.aiclaudeanthropic_messagesz$gateway.model.prompt_caching_enabledzgateway.model.warning_prefix)r>  zgateway.model.saved_globalr	  )>r   r	  rg	  rh	  ri	  rj	  hermes_cli.providersrk	  r  r7   r   r  rB   r2   rh   r   r  r   r  r  r$  rE  r   r&   r  r*   r  r'  rm	  r  r  r   r`  ra  r]  rG  r~	  r	  r   r   r5  r=  r>  r  r  r	  r9  rr   r   r   rj  r	  r	  r	  r5   r	  r	  r	  r	  r   rq  warning_message):r  rb  r   rh	  ri	  rj	  rk	  r  model_inputr{	  r  rp	  rn	  r   r   r  r  r  rh  r1  rk  
has_pickerrl	  r	  r  r  r	  r  r  r  
model_strsr6  r	  r]  r  rE  r   r	  r  r	  r	  _sw2_config_ctx_sw2_cfg_sw2_model_cfg_sw2_rawr	  cache_enabledr	  r	  r	  r	  r	  r	  r	  ry	  ro	  r  r	  s:                                                  @@@@@@@@@@@r#   r,  z#GatewayRunner._handle_model_command"  s\	     		
 	

 	3))+113 :K89T6& '
"]2	&(CGGGR0	i.$-MM)R$@M'0}}ZAQ'R$'0}}Z'D$ WW[1
?Q#B3#GL 226:0044["E$LL-@M'||J8HI'||J8HI&ll9oFO #4mm''8Gt# RDM+>ETQ 
 
# 5)9)9&3'1)5#%!I  !E#.L!.J$4M$4M#2L\0"%\014\0EH\0\0 \0|  $??HdHdejHklH#*#<#< &"+&3)9$/*<!) $= $ F ~~# ''78N4M<VYaoprtuE8%5%5"/#-%1 	 # 	%A<=lO!78QSCLL2ai[&	{!C5PQ!RS{%)YY.M8.M%M
uv  xF  vG  JM  NO  PX  NY  JZ  vZ"DAnL]`cdefndo`pLp q  `br*eW%=>y)s1Y<.%:;LL$	% LL=>?LL@ABLL89:99U## !-'-+$/%)

 ~~19M9MNN d$7>~t46- 7%zz+67 LO7	YQ,,$..!'!7!7"NN#__#__ -  t34(*D%2=/fFVFVEW X((BF,B,BC D<= 	!!+. %%..~~6
%%k2 	  - H%%'kG< 6"nnQ/526 6 CNN7B7	'-'7'7	)$(.(>(>	*%??,2OOIj)9C 
  ..H&2H2H+63C3CDEQ5OP J	+-H%\\'26N.$/)--.>?'&)(mO -""__>(8>BNN;o;)"1
 LL8C7LM}}Q?2==YZJ[]^!Q9@PQRLL=BLbLbLdef #6??#8b/Jsx[a[k[k[q[q[sOs 7"66 	 LLABC!!LL96CYCYZ[LL789LL<=>yyw	 ! ?#&77+=#>L? 		@ ! # "I#XF  :7 7  YRTWXXY<6 6  HCQGGH(  		sO  Ad($A5a/ a (Cd(7a? A<d(b-d(0C.b B6d(b$&d(6Ab1 B(d(*c+ cA$c+ A"d($A
d .G d(a,(a/ +a,,a/ /	a<8d(;a<<d(?b
d(bd(	b!d( b!!d($b.)d(1	c:cd(cd(c(#c+ +	d4d
d(dd(	d%!d($d%%d(c                 P  K   ddl m} |r|j                         j                         nd}|j	                  |      \  }}|rddj                  |      z   S 	 ddlm}m}  |       }	|j                  |	|||nd	      }
|
j                  r;|9|
j                  r-	 | j                  |j                        }| j                  |       |
j                  rdnd}| d|
j$                   S # t        $ r}d| cY d}~S d}~ww xY w# t        $ r t         j#                  d
d       Y ^w xY ww)u9  Handle /codex-runtime command in the gateway.

        Same surface as the CLI handler in cli.py:
            /codex-runtime                  — show current state
            /codex-runtime auto             — Hermes default runtime
            /codex-runtime codex_app_server — codex subprocess runtime
            /codex-runtime on / off         — synonyms

        On change, the cached agent for this session is evicted so the next
        message creates a fresh AIAgent with the new api_mode wired in
        (avoids prompt-cache invalidation mid-session).r   )codex_runtime_switchr?   u   ❌ u   
❌ )r3  r	  u   ❌ Could not load config: N)persist_callbackz7could not evict cached agent after codex-runtime changeTr  u   ✓u   ✗ro  )r  r	  r  r7   
parse_argsra  r   r3  r	  r   applyr  requires_new_sessionr  r  r9  r=  rM  rm  )r  rb  crsr  	new_valuerw  r3  r	  rE  r   r  rh  r  s                r#   r-  z+GatewayRunner._handle_codex_runtime_command&$  s+     	;7<5))+113"NN84	6HMM&111	7B m-6-Bk  
 >>i38S8S,"::5<<H((5
 !..e6>>*+++  	7066	7   ,V&*  ,,sZ   AD&C% 9D&,D  !D&%	C=.C82C=3D&8C==D&  D# D&"D##D&c                 $  K   ddl m} |j                         j                         j	                         }t
        dz  }	 t               }t        |ddi       }|st        d |             S |st        d	      g}|j                  t        d
             |j                         D ]s  \  }}	t        |	t              r)|	j                  d      xs |	j                  dd      dd }
nt        |	      dkD  r|	dd dz   n|	}
|j                  t        d||
             u |j                  t        d             dj!                  |      S d }|dv rO	 d|vst        |j                  d      t              si |d<   d|d   d<   t#        ||       d| _        t        d      S ||v r\ |||         }	 d|vst        |j                  d      t              si |d<   ||d   d<   t#        ||       || _        t        d|      S ddj!                  d |D              z   }t        d||       S # t        $ r i }i }Y w xY w# t        $ r }t        dt%        |            cY d}~S d}~ww xY w# t        $ r }t        dt%        |            cY d}~S d}~ww xY ww)!z8Handle /personality command - list or set a personality.r   r  r   r   personalitiesr  z#gateway.personality.none_configured)rq   zgateway.personality.headerzgateway.personality.none_optionrZ	  r  r?   Nr  r  zgateway.personality.item)rG   rr  zgateway.personality.usager[  c                 .   t        | t              r{| j                  dd      g}| j                  d      r|j                  d| d           | j                  d      r|j                  d| d           dj	                  d |D              S t        |       S )	Nr  r?   tonezTone: stylezStyle: r[  c              3   &   K   | ]	  }|s|  y wr  r   r  s     r#   r  zUGatewayRunner._handle_personality_command.<locals>._resolve_prompt.<locals>.<genexpr>w$  s      7qQ 7s   )r2   rh   rB   r`  ra  r*   )r   rc  s     r#   _resolve_promptzBGatewayRunner._handle_personality_command.<locals>._resolve_promptp$  s    %&?B7899V$LL6%-!9:99W%LL75>*:!;<yy 7E 777u:r%   >   r  rH   neutralzgateway.personality.save_failedr  zgateway.personality.clearedzgateway.personality.set_torG   z`none`, r  c              3   (   K   | ]
  }d | d   ywr	  r   )r  r  s     r#   r  z<GatewayRunner._handle_personality_command.<locals>.<genexpr>$  s     *KQqc8*Kr	  zgateway.personality.unknown)rG   	available)r  r  r  r7   rq  r   r  r   r   r   r`  r  r2   rh   rB   r]  ra  r   r*   rL  )r  rb  r  r7  r   r  r	  r  rG   r  rr  r	  r  
new_promptr	  s                  r#   r.  z)GatewayRunner._handle_personality_commandS$  s    8%%'--/557"]2	)+F#FG_bQM
 :ATAVWW345ELL<=> - 3 3 5 Xffd+$jj7_6::oWY;Z[^\^;_G58[25EfSbkE16GQ9gVWX LL67899U##	 11J&(
6::g;NPT0U&(F7O35w0!+v6 -/D)233]"(t)<=JJ&(
6::g;NPT0U&(F7O3=w0!+v6
 -7D)1==*K]*K!KK	.TYOOs  	FM	F  J:#a&IIJ  J:#a&IIJs   <JH$ C4J<H8 	!J+<I$ '=J$H51J4H55J8	I!II!JI!!J$	J-JJJJJc                 ^  K   |j                   }| j                  j                  |      }| j                  j                  |j                        }d}d}t        t        |      dz
  dd      D ]2  }||   j                  d      dk(  s||   j                  dd      }|} n |st        d      S |d| }| j                  j                  |j                  |       d	|_
        t        |t        j                  ||j                  |j                  
      }	| j!                  |	       d{   S 7 w)z6Handle /retry command - re-send the last user message.Nr   r|  rY   r  rZ   r?   zgateway.retry.no_previousr   )r   rX  r  raw_messager  )r  rZ  r7  rA  r  rR  r]  rB   r   rF  r+  r%  r&  r  r	  r  r  )
r  rb  r  r%  rc   last_user_msglast_user_idxrd  	truncatedretry_events
             r#   r/  z#GatewayRunner._handle_retry_command$  s(    **@@H$$44]5M5MN s7|a'R0 	Aqz~~f%/ '
y" = !		 011 N]+	--m.F.F	R+,( #$)))) //
 ))+6666s   BD-BD-&D+'D-c                 V   	 t        | j                  t              r | j                  xs i j                  di       nt	        | j                  di       xs i }|s$ddlm}  |       xs i j                  d      xs i }t        |j                  dd      xs d      S # t        $ r Y yw xY w)a%  Resolve the configured /goal turn budget for gateway sessions.

        GatewayRunner.config is a GatewayConfig dataclass, not the full
        user config mapping. Top-level config blocks such as ``goals`` are
        therefore only available through hermes_cli.config.load_config().
        goalsr   r2  r   r  )	r2   r  rh   rB   r&   r   r3  r5   r   )r  	goals_cfgr3  s      r#   _goal_max_turns_from_configz)GatewayRunner._goal_max_turns_from_config$  s    	 dkk40 "''4T[['26<" 
 9(]0b55g>D"	y}}["5;<< 		s   BB 	B('B(c                 l   	 ddl m} 	 | j
                  j                  |j                        }t        |dd      xs d}|sy| j                         } |||	      |fS # t        $ r }t        j	                  d|       Y d}~yd}~ww xY w# t        $ r }t        j	                  d|       Y d}~yd}~ww xY w)
zReturn a GoalManager bound to the session for this gateway event.

        Returns ``(manager, session_entry)`` or ``(None, None)`` if the
        goals module can't be loaded.
        r   r  zgoal manager unavailable: %sNrx  z'goal manager: session lookup failed: %sr  r?   r  default_max_turns)
r  r  r   r=  rM  rZ  r7  r  r&   r	  )r  rb  r  rE  r%  rD	  r   s          r#   _get_goal_manager_for_eventz)GatewayRunner._get_goal_manager_for_event$  s    	4	 ..DDU\\RM m\48>B446	cYGVV  	LL7=	
  	LLBCH	s.   A %B
 	B'BB
	B3B..B3c                   K   |j                         xs dj                         }|j                         }| j                  |      \  }}|t	        d      S |r|dk(  r|j                         S |dk(  r|j                  d      }|t	        d      S 	 |j                  r/| j                  j                  |j                  j                        nd}|j                  r| j                  |j                        nd}|r|r| j                  ||       t	        d
|j                         S |dk(  r4|j#                         }|t	        d      S t	        d|j                         S |dv r|j%                         }
|j'                          	 |j                  r/| j                  j                  |j                  j                        nd}|j                  r| j                  |j                        nd}|r|r| j                  ||       |
rt	        d      S t	        d      S 	 |j)                  |      }|j                  r/| j                  j                  |j                  j                        nd}|j                  r| j                  |j                        nd}|r\|rZ	 t/        |j                   t0        j2                  |j                  |j4                  |j6                        }| j9                  |||       t	        d|j:                  |j                         S # t        $ r!}	t        j                  d	|	       Y d}	~	&d}	~	ww xY w# t        $ r!}	t        j                  d|	       Y d}	~	bd}	~	ww xY w# t*        $ r }	t	        dt-        |	            cY d}	~	S d}	~	ww xY w# t        $ r }	t        j                  d|	       Y d}	~	d}	~	ww xY ww)u  Handle /goal for gateway platforms.

        Subcommands: ``/goal`` / ``/goal status`` / ``/goal pause`` /
        ``/goal resume`` / ``/goal clear``. Any other text becomes the
        new goal.

        Setting a new goal queues the goal text as the next turn so the
        agent starts working on it immediately — the post-turn
        continuation hook then takes over from there.
        r?   Nzgateway.goal.unavailabler  r  zuser-pausedr  zgateway.goal.no_goal_setz3goal pause: pending continuation cleanup failed: %szgateway.goal.paused)r  r  zgateway.goal.no_resumezgateway.goal.resumed>   r  rQ  r  z3goal clear: pending continuation cleanup failed: %szgateway.goal_clearedzgateway.no_active_goalzgateway.goal.invalidr  r  zgoal kickoff enqueue failed: %szgateway.goal.set)budgetr  )r  r7   rq  r	  r   status_liner  r  rE  rB   r   r  r~  r   r=  rM  r  r  has_goalr  r  r8   r*   r%  r&  r  r  r  rm  r   )r  rb  r7  rq  mgrr%  r(	  rk  rO  rE  hadkickoff_events               r#   r  z"GatewayRunner._handle_goal_command$  s/     &&(.B557

!==eD];/00u(??$$GII]I3E}344YFKll$--++ELL,A,ABX\KP<<T99%,,G]a
z:::wO *<<HJJLE}122+%**==--,,.CIIKYFKll$--++ELL,A,ABX\KP<<T99%,,G]a
z:::wO 141+,T;S9TT	=GGDME ?Dll$--##ELL$9$9:PTCH<<T11%,,?UY
z
E ,!,!1!1 <<$//#(#7#7! "":}gF #EOO%**MM[  YRTWXXY$  YRTWXXY  	=+3s8<<	="  E>DDEs   BOA<L A4OA<L< ?OM) )A*OAN -"O	L9L4.O4L99O<	M&M!O!M&&O)	N2NNONO	N>N94O9N>>Orm  c                   K   | j                   j                  |j                        }|s"t        j	                  dt        |dd             y	 | j                  |      }|j                  |j                  ||       d{   }|0t        |dd      s"t        j                  dt        |dd	             yyy# t        $ r d}Y fw xY w7 Hw)
zCSend a /goal judge status line back to the originating chat/thread.$goal continuation: no adapter for %sr   Nr  r  T)goal continuation: status send failed: %sr  r  )rE  rB   r   r=  rM  r&   r  r   r  r  r>  )r  r  rm  rk  r  r  s         r#   _send_goal_status_noticez&GatewayRunner._send_goal_status_notice7%  s     --##FOO4LL?Q[]aAbc	77?H ||FNNGh|OOgfi&FNN;9 'G	  	H	 Ps6   A
C
B7 !C
?C 7C
7CC
CC
c                    K    j                   j                  j                        }|s"t        j	                  dt        dd             yd
 fd}	  j                        }|rOt        |d      rC	 d}t        |di       j                  |      }|t        |dd      }|j                  |||       y |        d{    y# t        $ r d}Y nw xY w# t        $ r }t        j	                  d	|       Y d}~Ed}~ww xY w7 Bw)a  Send a /goal status line after the main response is delivered.

        The gateway message handler returns the agent response to the platform
        adapter, which sends it after this method's caller has returned.  For a
        natural Discord/Telegram reading order, goal status belongs after that
        send.  Platform adapters provide a one-shot post-delivery callback for
        exactly this boundary; when unavailable, fall back to direct awaited
        delivery rather than silently dropping the notice.
        r	  r   Nc                     K   	 j                         d {    y 7 # t        $ r"} t        j                  d| d       Y d } ~ y d } ~ ww xY ww)Nr	  Tr  )r	  r   r=  r>  )rE  rm  r  r  s    r#   _deliverzHGatewayRunner._defer_goal_status_notice_after_delivery.<locals>._deliverY%  sK     `33FGDDD `JCZ^__`s6   A"  " A" 	AAAAAregister_post_delivery_callback_active_sessions_hermes_run_generationr!  zAgoal continuation: post-delivery callback registration failed: %srq  )
rE  rB   r   r=  rM  r&   r  r   r  r	  )	r  r  rm  rk  r	  rh  r"  r  rE  s	   ```      r#   (_defer_goal_status_notice_after_deliveryz6GatewayRunner._defer_goal_status_notice_after_deliveryJ%  s     --##FOO4LL?Q[]aAbc	`	66v>K 77,MNg!
 *<bAEEkR%!(1I4!PJ77) 8 
  j%  	K	  g`beffg 	sa   AD	C
 )D	8AC 9D	DD	
CD	CD		D$C?:D	?DD	r%  r  c                  K   	 ddl m} t        |dd      xs d}|sy| j                         } |||      }|j                         sy|j                  |xs dd	      }	|	j                  d
      xs d}
|
r|| j                  ||
       d{    |	j                  d      sy|	j                  d      xs d}|r|y	 | j                  j                  |j                        }| j                  |      }|r5|r2t        |t        j                   |dd      }| j#                  |||       yyy# t        $ r }t        j	                  d|       Y d}~yd}~ww xY w7 # t        $ r }t        j	                  d|       Y d}~yd}~ww xY ww)a  Run the goal judge after a gateway turn and, if still active,
        enqueue a continuation prompt for the same session.

        Called from ``_handle_message_with_agent`` at turn boundary, AFTER
        the response has been delivered. Safe when no goal is set.

        We use the adapter's pending-message / FIFO machinery so any real
        user message that arrives simultaneously is handled by the same
        queue and takes priority naturally.
        r   r  z/goal continuation: goals module unavailable: %sNr  r?   r	  T)user_initiatedrm  should_continuecontinuation_promptr  z%goal continuation: enqueue failed: %s)r  r  r   r=  rM  r&   r	  r  evaluate_after_turnrB   r	  rE  r   r  r%  r&  r  rm  )r  r%  r  r  r  rE  rD	  r   r	  r  r[   r  rk  rO  
cont_events                  r#   rG  z*GatewayRunner._post_turn_goal_continuationu%  s    "	4
 m\48>B446	SIF}}**>+?RPT*Ull9%+ 6%??LLL||-.34:	Gmm''8G55f=J:)!,!1!1!##'
 "":z7C &wM  	LLJCP	0 M.  	GLL@#FF	Gse   FD. BFE0F A+E +F.	E7EFEF	F%F ;F FFc                 $  K   |j                   }| j                  j                  |      }| j                  j                  |j                        }d}t        t        |      dz
  dd      D ]  }||   j                  d      dk(  s|} n |t        d      S ||   j                  dd      }t        |      |z
  }| j                  j                  |j                  |d|        d	|_
        t        |      d
kD  r|dd
 dz   n|}	t        d||	      S w)z?Handle /undo command - remove the last user/assistant exchange.Nr   r|  rY   r  zgateway.undo.nothingrZ   r?   r   (   r  zgateway.undo.removed)rx  rr  )r  rZ  r7  rA  r  rR  r]  rB   r   rF  r+  )
r  rb  r  r%  rc   r	  rd  removed_msgremoved_countrr  s
             r#   r  z"GatewayRunner._handle_undo_command%  s    **@@H$$44]5M5MN s7|a'R0 	Aqz~~f%/ !	
  +,,m,00B?G}4--m.F.FP^Q^H_`+,(.1+.>.C+cr"U*'}gNNs   BDBDc                   K   |j                   }|j                  r|j                  j                  nd}|j                  }|j                  xs |}t        |      }t        |      }|j                  }	 ddlm	}	  |	|t        |              |	|t        |xs d             |j                  rn| j                  j                  j                  |j                  t!        d	            }t#        |j                  t        |      ||rt        |      nd
      |_        t        d||      S # t        $ r}
t        d|
      cY d}
~
S d}
~
ww xY ww)zOHandle /sethome command -- set the current chat as the platform's home channel.r  r   )save_env_valuer?   zgateway.set_home.save_failedr  NTr  )r   r  rG   r  zgateway.set_home.success)rG   r  )r  r   r   r  r/  r   r   r  r   r
  r*   r   r   r  rP  rj  r  r  home_channel)r  rb  r  ry   r  r/  r  thread_env_keyr  r
  r  rS  s               r#   r0  z&GatewayRunner._handle_set_home_command%  s    17--i..$$/	&}5-m<$$		>87CL1 >3yB+?@ ??"kk33>>t,O ,7G,5#i.4	,O( +)WMM#  	>31==	>s7   A/E2.D(  BE(	E1E=E>EEEc                     t        | dd      }|yt        |d      r!|j                  rt        |j                        S t        |d      r"|j                  r|j                  j
                  S y)z5Extract Discord guild_id from the raw message object.r	  Nr  guild)r&   r  r  r5   r
  r   )rb  rE   s     r#   rH  zGatewayRunner._get_guild_id%  sZ     e]D1;3
#s||$$3 SYY99<<r%   c                   K   |j                         j                         j                         }|j                  j                  }|j                  j
                  }| j                  ||      }| j                  j                  |      }|dv r@d| j                  |<   | j                          |r| j                  ||d       t        d      S |dv r@d| j                  |<   | j                          |r| j                  ||d       t        d	      S |d
k(  r@d| j                  |<   | j                          |r| j                  ||d       t        d      S |dv r| j                  |       d{   S |dk(  r| j                  |       d{   S |dk(  rP| j                  j                  |d      }t        d      t        d      t        d      d}| j                  j                  |j                  j
                        }| j!                  |      }	|	rt#        |d      r|j%                  |	      }
|
rt        d|j                  ||            t        d|
d         t        d|
d         g}|
d   D ]@  }|j                  d      rt        d       nd!}|j'                  t        d"|d#   |$             B d%j)                  |      S t        d|j                  ||            S | j                  j                  |d      }|dk(  r@d| j                  |<   | j                          |r| j                  ||d       t        d&      S d| j                  |<   | j                          |r| j                  ||d       t        d'      S 7 7 w)(z8Handle /voice [on|off|tts|channel|leave|status] command.>   r  enabler  Tr
  z gateway.voice.enabled_voice_only>   r  disabler  r  zgateway.voice.disabled_textttsr  zgateway.voice.tts_enabled>   ra  channelNleaver  zgateway.voice.label_offzgateway.voice.label_voice_onlyzgateway.voice.label_all)r  r  r  get_voice_channel_infozgateway.voice.status_moderW  zgateway.voice.status_channelchannel_name)r
  z!gateway.voice.status_participantsmember_countr)	  membersis_speakingzgateway.voice.speakingr?   zgateway.voice.status_memberr  )rG   r  r[  zgateway.voice.enabled_shortzgateway.voice.disabled_short)r  r7   rq  r  r  r   r  rE  rB   r  r  r  r   r  _handle_voice_channel_join_handle_voice_channel_leaverH  r  r
  r`  ra  )r  rb  r7  r  r   	voice_keyrk  r  labelsr  rL  r  r  r  rP   s                  r#   r;  z#GatewayRunner._handle_voice_command&  sm    %%'--/557,,&&<<((OOHg6	--##H-##*6DY'""$227GT2R788''*/DY'""$33GWt3T233U]*/DY'""$227GT2R011((88???W_99%@@@X##''	59D23 @A23F mm''(=(=>G))%0HGG-EF55h?5VZZd=ST8$~BVW=T.EYZE
 ")_ n@Am@T#;!<Z\Q'D1^K\ek%lmn  99U++0

48NOO &&**9e<G%.:  +&&(66wQU6V677.3  +&&(77SW7X788Q @@s%   E+N-N.NN
G<N
Nc                   K   | j                   j                  |j                  j                        }t	        |d      sy| j                  |      }|sy|j                  ||j                  j                         d{   }|syt	        |d      r| j                  |_	        t	        |d      r| j                  |_        	 |j                  |       d{   }|rt)        |j                  j*                        |j,                  |<   t	        |d      r'|j                  j/                         |j0                  |<   d| j2                  | j5                  |j                  j                  |j                  j*                        <   | j7                          | j9                  ||j                  j*                  d       d|j:                   dS d|_	        y7 T7 # t        $ rj}t        j                  d|       d|_	        t!        |      j#                         }d	|v sd
|v sd|v rdt$        j&                   dcY d}~S d| cY d}~S d}~ww xY ww)z.Join the user's current Discord voice channel.join_voice_channelz2Voice channels are not supported on this platform.z,This command only works in a Discord server.Nz(You need to be in a voice channel first._voice_input_callback_on_voice_disconnectz Failed to join voice channel: %spynaclnacldaveyz@Voice dependencies are missing (PyNaCl / davey). Install with: `z -m pip install PyNaCl`zFailed to join voice channel: _voice_sourcesr  Tr
  zJoined voice channel **zL**.
I'll speak my replies and listen to you. Use /voice leave to disconnect.zFFailed to join voice channel. Check bot permissions (Connect + Speak).)rE  rB   r  r   r  rH  get_user_voice_channelr  _handle_voice_channel_inputr
  _handle_voice_timeout_cleanupr
  r
  r   r=  r>  r*   rq  r  r  r5   r  _voice_text_channelsto_dictr
  r  r  r  r  rG   )r  rb  rk  r  voice_channelr  r  	err_lowers           r#   r
  z(GatewayRunner._handle_voice_channel_joinL&  s    --##ELL$9$9:w 45G%%e,A%<<ell**
 
 = 734,0,L,LG)723+/+M+MG(	8#66}EEG 589M9M5NG((2w 0138<<3G3G3I&&x0]bDT__U\\-B-BELLDXDXYZ""$..w8L8LVZ.[)-*<*<)= >[ \
 )-%WO
 F 		8NN=qA,0G)AI9$)(;w)?S&&)nn%55LN 4A377		8si   A:I<G=AI?G GG C9IG 	IAI3I4I9I=I>IIIc                   K   | j                   j                  |j                  j                        }| j	                  |      }|rt        |d      syt        |d      r|j                  |      sy	 |j                  |       d{    d| j                  | j                  |j                  j                  |j                  j                        <   | j                          | j                  ||j                  j                  d       t        |d	      rd|_        y
7 # t        $ r }t        j                  d|       Y d}~d}~ww xY ww)z Leave the Discord voice channel.leave_voice_channelzNot in a voice channel.is_in_voice_channelNzError leaving voice channel: %sr  Tr

  r
  zLeft voice channel.)rE  rB   r  r   rH  r  r)
  r(
  r   r=  r>  r  r  r  r  r  r
  )r  rb  rk  r  r  s        r#   r
  z)GatewayRunner._handle_voice_channel_leave&  s    --##ELL$9$9:%%e,ww0EF,w 56g>Y>YZb>c,	A--h777 Z_)>)>@T@TUV ++GU\\5I5ITX+Y734,0G)$ 8 	ANN<a@@	AsC   A.E1D DD 
BED 	E(E>EEEc                     d| j                   | j                  t        j                  |      <   | j	                          | j
                  j                  t        j                        }| j                  ||d       y)zCalled by the adapter when a voice channel times out.

        Cleans up runner-side voice_mode state that the adapter cannot reach.
        r  Tr

  N)r  r  r  r=  r  rE  rB   r  )r  r  rk  s      r#   r"
  z+GatewayRunner._handle_voice_timeout_cleanup&  sb    
 HM)9)97CD --##H$4$45++GWt+Lr%   r  r  
transcriptc                 t   ddl m} t        j                  dd|      j	                         j                         }t        j                  dd|      }|syt        j                         }d}||f}t        | d	d
      }	t        |	t              s	i }	|	| _        |	j                  |g       D 
cg c]  \  }
}||
z
  |k  r|
|f }}
}|D ]Q  \  }}||k(  r||	|<    yt        |      dk\  s!t        |      dk\  s0 |d
||      j                         dk\  sL||	|<    y |j                  ||f       |dd
 |	|<   yc c}}
w )ar  Suppress repeated STT outputs for the same recent utterance.

        Voice capture can occasionally emit the same utterance twice a few
        seconds apart, which creates a second queued agent run and overlapping
        spoken replies. Dedup exact and near-exact repeats per guild/user over a
        short window while allowing genuinely new turns through.
        r   )SequenceMatcher\s+ro  z[^\w\s]r?   Fg      (@r  NT   r  )difflibr-
  r(   r,   r7   rq  rN   r  r&   r2   rh   r  rB   r]  ratior`  )r  r  r  r+
  r-
  rs  rK   window_secondsr+  recent_storeri   txtrecentr  priors                  r#   _is_duplicate_voice_transcriptz,GatewayRunner._is_duplicate_voice_transcript&  sW    	,VVFC4::<BBD
VVJJ7
nn!t%@$G,-L-9D* (++C4
CRx>) I
 
  	 HAu
"$*S!5zRC
Or$9"4
;AACtK(.L%	  	sJ'("23KS#
s   )D4c           	        K   | j                   j                  t        j                        }|sy|j                  j                  |      }|syt        |di       j                  |      }|r6t        j                  |      }t        |      |_	        t        |      |_
        n9t        t        j                  t        |      t        |      t        |      d      }| j                  |      st        j                  d|       y| j                  |||      rt        j                  d|||dd        y	 |j                   j#                  |      }|rD|dd j%                  d	d
      j%                  dd      }	|j'                  d| d|	        d{    ddlm}
 t/        ||t0        j2                   |
|d            }|j5                  |       d{    y7 I# t(        $ r Y Rw xY w7 w)zHandle transcribed voice from a user in a voice channel.

        Creates a synthetic MessageEvent and processes it through the
        adapter's full message pipeline (session, typing, agent, TTS reply).
        Nr
  r
  )r   r  r  r  r  z/Unauthorized voice input from user %d, ignoringz?Suppressing duplicate voice transcript for guild=%s user=%s: %sr  i  z	@everyoneu   @​everyonez@hereu   @​herez**[Voice]** <@z>: r   )SimpleNamespace)r  r
  )r  r   rX  r	  )rE  rB   r  r=  r#
  r&   r  	from_dictr*   r  r  r  r=  rM  r8
  rL  _clientget_channelr:   r  r   typesr:
  r%  r&  r  r  )r  r  r  r+
  rk  
text_ch_idsource_datar  r
  	safe_textr:
  rb  s               r#   r!
  z)GatewayRunner._handle_voice_channel_input&  s     --##H$4$451155h?
 g'7<@@J",,[9F \FN"7|F"!))JGg,#F ''/LLJGT..x*MKKQ4C 	 	oo11*=G&u-55kCTU]]^egtu	ll^G9C	{#KLLL 	*$**'F	
 $$U+++ M 		 	,sI   D/G+2AG GG ?G+G)G+G 	G&#G+%G&&G+r  r  r.  c                 l   |r|j                  d      ry|j                  j                  }| j                  j	                  | j                  |j                  j                  |      d      }|j                  t        j                  k(  }|dk(  xs	 |dk(  xr |}|syt        d |D              }	|	ry|r|syy)a  Decide whether the runner should send a TTS voice reply.

        Returns False when:
        - voice_mode is off for this chat
        - response is empty or an error
        - agent already called text_to_speech tool (dedup)
        - voice input and base adapter auto-TTS already handled it (skip_double)
          UNLESS streaming already consumed the response (already_sent=True),
          in which case the base adapter won't have text for auto-TTS so the
          runner must handle it.
        zError:Fr  r  r  c              3      K   | ]?  }|j                  d       dk(  xr% t        d |j                  d      xs g D               A yw)rY   r^   c              3   d   K   | ](  }|j                  d i       j                  d      dk(   * yw)functionrG   text_to_speechNr  )r  tcs     r#   r  zCGatewayRunner._should_send_voice_reply.<locals>.<genexpr>.<genexpr>0'  s4       z2&**626FFs   .0
tool_callsN)rB   r  )r  r[   s     r#   r  z9GatewayRunner._should_send_voice_reply.<locals>.<genexpr>.'  sT      
  GGFO{*  77<06B 
s   AAT)r^  r  r  r  rB   r  r   rX  r&  r  r  )
r  rb  r  r  r.  r  
voice_modeis_voice_inputshouldhas_agent_ttss
             r#   rX  z&GatewayRunner._should_send_voice_reply'  s    $ 8..x8,,&&%%))$//%,,:O:OQX*Y[`a
,,0A0AA 5  ?l*=~ 	   
 &
 
  ,r%   r   c                   K   ddl }d}d}	 ddlm}m}  ||dd       }|s%	 ||hdhz
  D ]  }		 t	        j
                  |	        yt        j                  j                  t        j                         dd|j                         j                  dd  d      }t	        j                  t        j                  j                  |      d	
       t        j                   |||       d{   }
t#        j$                  |
      }|j'                  d|      }|j'                  d      rt        j                  j)                  |      sJt*        j-                  d|j'                  d             	 ||hdhz
  D ]  }		 t	        j
                  |	        y| j.                  j'                  |j0                  j2                        }| j5                  |      }|rDt7        |d      r8t7        |d      r,|j9                  |      r|j;                  ||       d{    nv|rtt7        |d      rh| j=                  |      }| j?                  |j0                  |      }|j0                  j@                  ||d}|r||d<    |jB                  di | d{    ||hdhz
  D ]  }		 t	        j
                  |	        y# t        $ r Y w xY w7 # t        $ r Y [w xY w7 7 M# tD        $ r"}t*        j-                  d|d	       Y d}~qd}~ww xY w# t        $ r Y |w xY w# ||hdhz
  D ]'  }		 t	        j
                  |	       # t        $ r Y %w xY w w xY ww)zEGenerate TTS audio and send as a voice message before the text reply.r   N)text_to_speech_tool_strip_markdown_for_ttsi  hermes_voice
tts_reply_r	  z.mp3T)r  )r   output_pathr  r  zAuto voice reply TTS failed: %sr  play_in_voice_channelr)
  
send_voice)r  
audio_pathr  r  zAuto voice reply failed: %sr  r   )#uuidtools.tts_toolrN
  rO
  r@   rB  r  rq   ra  tempfile
gettempdiruuid4hexmakedirsdirnamer^  r  r  r  rB   isfiler=  r>  rE  r  r   rH  r  r)
  rS
  r'  r  r  rT
  r   )r  rb  r   _uuidrU
  actual_pathrN
  rO
  tts_textr  result_jsonr  rk  r  r  r  send_kwargsr  s                     r#   rY  zGatewayRunner._send_voice_replyC'  s%    
5	S.tET{;HX !+.$7 IIaLQ ##%~U[[]..s34D9J KK
3dC ' 1 1#(
! K ZZ,F !**[*=K::i({0K@&**WBUV2 !+.$7 IIaL/ mm''(=(=>G ))%0H)@A)>?33H=33HkJJJWWl;#;;EB">>u||\Z$||33"- ,/
 .9K
+(g((7;777 !+.$7 IIaL  KJ  % K 8 	LNN8!dNKK	L   !+.$7 IIaL s  	MK MJ-MB!K 'J=(B K )M5K 
MB K KA6K KK 	ML*M-	J:6M9J::M=K  	K	MKMK K 	K?K:5L :K??L 	LMLMML43M4	M 	=M?M 	 MMc                   K   ddl m} ddlm} 	 d|v }|j	                  |      \  }}|j                  |      \  }}	|j                  |	      \  }
}| j                  |j                  | j                  |            }ddl
m} h d}h d}g }g }|D ]R  \  }} ||      j                  j                         }||v r|s|s|j                  |       @|j                  ||f       T g }|
D ]I  } ||      j                  j                         |v r|s|j                  |       9|j                  |       K |rK	 |D cg c]  }d ||       d	f }}|j                  |j                  j                   ||
       d{    |D ]  \  }}	  ||      j                  j                         } ||j                  j*                  ||      r1|j-                  |j                  j                   ||       d{    ne||v r1|j/                  |j                  j                   ||       d{    n0|j1                  |j                  j                   ||       d{     |D ]  }	  ||      j                  j                         }||v r1|j/                  |j                  j                   ||       d{    n0|j1                  |j                  j                   ||       d{     yc c}w 7 x# t"        $ r,}t$        j'                  d|j(                  |       Y d}~d}~ww xY w7 ;7 	7 # t"        $ r,}t$        j'                  d|j(                  |       Y d}~d}~ww xY w7 7 # t"        $ r,}t$        j'                  d|j(                  |       Y d}~@d}~ww xY w# t"        $ r }t$        j'                  d|       Y d}~yd}~ww xY ww)u=  Extract MEDIA: tags and local file paths from a response and deliver them.

        Called after streaming has already sent the text to the user, so the
        text itself is already delivered — this only handles file attachments
        that the normal _process_message_background path would have caught.
        r   r   )rR  z[[as_document]])should_send_media_as_audio>   .3gp.avi.mkv.mov.mp4.webm>   .gif.jpg.png.jpeg.webpzfile://r?   )r  imagesr  Nz0[%s] Post-stream image batch delivery failed: %s)is_voice)r  rU
  r  )r  
video_pathr  r  r  r  z*[%s] Post-stream media delivery failed: %sz)[%s] Post-stream file delivery failed: %sz'Post-stream media extraction failed: %s)pathlibr   urllib.parserR  extract_mediaextract_imagesextract_local_filesr  r  r'  r3  re
  r	  rq  r`  send_multiple_imagesr  r   r=  r>  rG   r   rT
  
send_videosend_document)r  r  rb  rk  r   _quoteforce_document_attachmentsmedia_filesr  cleanedlocal_files_thread_metare
  _VIDEO_EXTS_IMAGE_EXTSr  non_image_media
media_pathrr
  extnon_image_localr  r  rq
  r  s                            r#   rZ  z*GatewayRunner._deliver_media_from_response'  s     	!0_	I
 *;h)F&$228<NK //9JAw$88ANK;;ELL$JfJfglJmnLIKKDK !#K$&O(3 C$
H:&--335;& ( :&&z2#**J+ABC %'O( 6	O**002kA :&&y1#**956 hCNOa4b9OFO!66 % 4 4%!- 7    )8 b$
Hbz*11779C1%,,2G2GW_`%00$)LL$8$8'1%1 1   
 +%00$)LL$8$8'1%1 1    &33$)LL$8$8&0%1 4   !b0 - a	ay/00668Ck)%00$)LL$8$8'0%1 1    &33$)LL$8$8&/%1 4   aC P
 ! hNN#UW^WcWcefggh
 ! bNN#OQXQ]Q]_`aab
 ! aNN#NPWP\P\^_``a  	INNDaHH	Is'  OD&N+ 7K7 ;K/-K7 <K4=K7 N+ 
A*L74L/54L7)L2*0L7L5L7N+ 'AM36M/70M3'M1(M3,N+ .O/K7 7	L, !L'!N+ 'L,,N+ /L72L75L77	M, !M'!N+ 'M,,N+ /M31M33	N(<!N#N+ #N((N+ +	O4O
OOOc           	      &  K   ddl m}m} i }	 ddl}t        dz  }|j                         rQt        |d      5 }|j                  |      xs i }ddd       j                  di       }t        |t              rd|i}|j                  dd	      st        d
      S  |d|j                  dd      |j                  dd      |j                  dd            }	t        j                  dt        t!        j"                                     }
|j%                         j'                         }|s|	j)                  |
      } |||
      S |	j)                  |
      }|st        d|
      S d}	 t+        |      dz
  }d|cxk  rt-        |      k  rn n	||   d   }nt        dt-        |            S 	 |	j1                  |
|      }|d   rt        d|d   |d         S t        d|d    !      S # 1 sw Y   xY w# t        $ r Y w xY w# t.        $ r |}Y ew xY ww)"uD   Handle /rollback command — list or restore filesystem checkpoints.r   )CheckpointManagerformat_checkpoint_listNr   r   r   r=  r  Fzgateway.rollback.not_enabledTmax_snapshotsr  rA  rB  max_file_size_mbr  )r  r
  rA  r
  r   zgateway.rollback.none_found)r   r   hashzgateway.rollback.invalid_number)r  r  zgateway.rollback.restoredrestored_torU  )r
  rU  zgateway.rollback.restore_failedr  r  )r~  r
  r
  r   r   rr   r   r   rB   r2   r4   r   r   r@   r<  r*   r   r"  r  r7   list_checkpointsr5   r]  r8   restore)r  rb  r
  r
  cp_cfgrN  	_cfg_pathrP  _datar	  r   argr=  target_hashrC	  r  s                   r#   r:  z&GatewayRunner._handle_rollback_command'  s    V 
	$}4I!)g6 3"LL,2E3="5fd+'0F zz)U+344 **_b9$jj)<cB#ZZ(:B?	
 iiDIIK(89$$&,,...s3K)+s;; **3/2<<	c(Q,CC*#k**)#.v6:K@PQQ  S+.)+M*h' 
 2&/JJ_3 3
  		@  	K	se   H*G0 G#.G0 <C&H#AH  &=H#G-(G0 0	G=9H<G==H HHHHc                 J  K   |j                         j                         }|st        d      S |j                  }dt	        j
                         j                  d       dt        j                  d      j                          }| j                  |      }t        j                  | j                  ||||            }| j                  j                  |       |j!                  | j                  j"                         |dd t%        |      dkD  rd	nd
z   }t        d||      S w)u)  Handle /background <prompt> — run a prompt in a separate background session.

        Spawns a new AIAgent in a background thread with its own session.
        When it completes, sends the result back to the same chat without
        modifying the active session's conversation history.
        zgateway.background.usagebg_z%H%M%Sr  ry  )r  Nr  r  r?   zgateway.background.started)rr  rv  )r  r7   r   r  r   rK   r-	  r@   urandomr[
  r'  r^  rr  _run_background_taskr  r  rs  r  r]  )r  rb  r  r  rv  r  r  rr  s           r#   r  z(GatewayRunner._handle_background_command*(  s     '')//1/00//9:!BJJqM<M<M<O;PQ77> ##%%!1	 & 
 	""5) 6 6 > >?"+#f+*:C-wPPs   D!D#r  r  rv  r  c                     K   ddl m  j                  j                  j                        }|s"t
        j                  dj                         y j                  |      }	 t               } j                  |      \  }}	|	j                  d      s+|j                  j                  d d|	       d{    yt        j                        dd
lm}
 t         |
|            |j                  d      xs i }|j                  d      xs d j                   t#        t%        j&                  dd             j)                         _         j-                          _         j1                  ||	         fd} j3                  |       d{   }|r|j                  dd      nd}|s|r|j                  d      rd|d    }|r|j5                  |      \  }}|j7                  |      \  }}dd t9              dkD  rdndz   }d| d}|r*|j                  j                  ||z   |       d{    n-|s+|s)|j                  j                  |dz   |       d{    |xs g D ]-  \  }}	 |j;                  j                  |||       d{    / |xs g D ],  \  }}	 |j?                  j                  ||       d{    . ydd t9              dkD  rdndz   }|j                  j                  d| d|       d{    y7 v7 7 7 7 # t<        $ r Y w xY w7 e# t<        $ r Y w xY w7 1# t<        $ rc}t
        jA                  d       	 |j                  j                  d d| |       d{  7   n# t<        $ r Y nw xY wY d}~yY d}~yd}~ww xY ww) zCExecute a background agent task and deliver the result to the chat.r   r  z0No adapter for platform %s in background task %sN)r  r(  r   u   ❌ Background task z, failed: no provider credentials configured.r  _get_platform_toolsr   disabled_toolsetsr   r  )r  c            
          d$dd   id   i ddddddd	d
d	j                   dj                  d      dj                  d      dj                  d      dj                  d      dj                  d      dj                  dd      dj                  d      ddd
j                  d
j                  d
j                  d
j
                  d
j                  d 
j                  d!	j                  d"	j                  } 	 | j                  #      	j                  |        S # 	j                  |        w xY w)%Nr   rB  r  r  Tverbose_loggingFr	  r
  r  r>  r;  providers_allowedonlyproviders_ignoredr  providers_orderorderprovider_sortr9	  provider_require_parametersrequire_parametersprovider_data_collectiondata_collectionr  r   r  r  r  r/  r  r  r  rI  )r7  rv  r   )rP  rB   r  r  r  r/  r  r  r{  rX  run_conversationr,  )r   r  r
  r	  r  r,  prr  r  r  r  rv  
turn_routes    r#   run_syncz4GatewayRunner._run_background_task.<locals>.run_syncv(  s    $W- + $2  $	
 %* &6 '8 &6 "&!3!3 '1nn5H&I ')ffVn ')ffX&6 %'FF7O #%&&. 137KU0S  .0VV4E-F!"  '#$ *%& #NN'( %..)* #NN+, %..-. %../0 %..12  $//34 $(#7#7589 11%+ ' 2 
 11%8D11%8s   !E Er  r?   r  zError: r  r  u&   ✅ Background task complete
Prompt: "z"

r0  z(No response generated))r  	image_urlcaptionr  rt
  z"

(No response generated)zBackground task %s failedz	 failed: )!rC  r  rE  rB   r   r=  r>  r  r  r6  r  r  r  hermes_cli.tools_configr
  rO  rV  r5   r@   r<  r  rN  rO  rP  r@  _run_in_executor_with_contextrw
  rx
  r]  
send_imager   r|
  r  )!r  r  r  rv  r  rk  _thread_metadatar(  r   r4  r
  r   r
  r  r  r
  rq
  text_contentrr  r  r
  alt_textr
  	_is_voicer  r  r
  r	  r  r,  r
  r  r
  s!   ````                     @@@@@@@@r#   r
  z"GatewayRunner._run_background_taskI(  s?     	&--##FOO4NNMv`gh;;FDTUC	.0K$($G$G' %H %!E> "%%i0llNN*7)3_`- #   
 /@LC%&9+|&TU#06BI ).A B Jd''B +BD!IJN#EEVET%5D"!%!8!8!:D88WJ#9 #9J  ==hGGF;Avzz"2B7rH6::g+>$VG_$56 (/(=(=h(G%X'.'='=h'G$ "+#f+2BKB7)5Q!,, & & 5!1 '   
  !,, & &)B B!1 '    -3Lb 	'Ix%00$*NN&/$,%5	 1   	 /:.?R )J	%33$*NN&0%5 4    !"+#f+2BKll"NNEgYNjk- #   [t H % 
 %   		8'Bll"NN27)9QCH- #   
  		s;  A"O1AM+ L=M+ OC'M+ 4M 5B&M+ M-M+ 	M
M+ "M	=M>M	M+ !M2M3M7M+ 9O:=M+ 7M)8M+ <O=M+  M+ M+ M+ M			MM+ MM+ M	M&#M+ %M&&M+ +	O4O'N92N53N98O9	OOOO
OOOc                   K   ddl |j                         j                         }| j                  |      \  }}t        dz  | j                  |j                        }| j                         | _        | j                  |j                  |      | _
        dt        ffd}|s| j                  }|t        d      }n1|j                  d      d	u rt        d
      }n|j                  dd      }| j                  rt        d      n
t        d      }	|t        | di       xs i v }
|
rt        d      n
t        d      }t        d|||	      S t        |j                  j                         }|dv r!d| _         |d| dd       t        d|      S |dv r!d	| _         |d| dd	       t        d|      S |j                         }|dk(  rP|rt        d      S | j#                  |d       | j%                         | _
        | j'                  |       t        d      S |dk(  rdd	i}n)|d v rd|d!}nt        d"|xs |j)                         #      S || _
        |ri |d$|      r0| j#                  |d       | j'                  |       t        d%|&      S | j#                  ||       | j'                  |       t        d'|&      S | j#                  ||       | j'                  |       t        d(|&      S w))u]  Handle /reasoning command — manage reasoning effort and display toggle.

        Usage:
            /reasoning                       Show current effort level and display state
            /reasoning <level>               Set reasoning effort for this session only
            /reasoning <level> --global      Persist reasoning effort to config.yaml
            /reasoning reset                 Clear this session's reasoning override
            /reasoning show|on               Show model reasoning in responses
            /reasoning hide|off              Hide model reasoning from responses
        r   Nr   r  key_pathc                    	 i }j                         r+t        d      5 }	j                  |      xs i }ddd       | j                  d      }|}|dd D ]#  }||vst	        ||   t
              si ||<   ||   }% |||d   <   t        |       y# 1 sw Y   \xY w# t        $ r!}t        j                  d| |       Y d}~yd}~ww xY w	z(Save a dot-separated key to config.yaml.r   r   Nr   r|  Tz Failed to save config key %s: %sF
rr   r   r   rp  r2   rh   r   r   r=  r  
r
  r   r(  r   r,  rP   rC  r  r   r   s
           r#   _save_config_keyzAGatewayRunner._handle_reasoning_command.<locals>._save_config_key(       %%'kG< >&*nnQ&7&=2>~~c*%cr )A'z'!*d/K%'
%ajG) %*R!!+{;> >  ?1M.   B BAB BB 	C	(CC	zgateway.reasoning.level_defaultr  Fz gateway.reasoning.level_disabledr  mediumzgateway.reasoning.display_onzgateway.reasoning.display_offr%  zgateway.reasoning.scope_sessionzgateway.reasoning.scope_globalzgateway.reasoning.status)levelr	  r   >   r  showTzdisplay.platforms.z.show_reasoningz gateway.reasoning.display_set_onr  >   r  hidez!gateway.reasoning.display_set_offresetz*gateway.reasoning.reset_global_unsupportedzgateway.reasoning.reset_doner  >   lowhighxhighr
  minimal)r  r  zgateway.reasoning.unknown_argr
  zagent.reasoning_effortzgateway.reasoning.set_global)r  z(gateway.reasoning.set_global_save_failedzgateway.reasoning.set_session)r   r  r7   r  r   r  r  rQ  rR  r  rN  r*   r   rB   r&   r  r   r  rM  r9  rq  )r  rb  r  r7  r  rh  r
  rcr
  display_statehas_session_overrider	  r,  r  r  r   r   s                  @@r#   r*  z'GatewayRunner._handle_reasoning_command(  s     	))+113#AA(Kn"]2225<<@#88:!%!G!G<<# "H "

	s 	( ''Bz;<	"e+<=x2 '' 0167 
 $/74A_ac3d3jhj#k  ( 3478 
 *%	  ,ELL,A,AB>!#'D 1,OQUV7,OO?"#(D 1,OQVW8<PP WEFF00dC%)%@%@%BD"$$[1344V'FDD!%8F/.hnn. 
 "( 8&A44[$G((57GG00fE$$[1?OO,,[&A  -0@@s   K+K/c                 V  
K   ddl ddlm} |j                         j	                         j                         }t        dz  
| j                         | _        t               }t        |      } ||      st        d      S dt        f
fd}|r|dk(  r2| j                  d	k(  rt        d
      n
t        d      }t        d|      S |dv rd	| _        d}t        d      }	n&|dv rd| _        d}t        d      }	nt        d|      S  |d|      rt        d|	      S t        d|	      S w)uL   Handle /fast — mirror the CLI Priority Processing toggle in gateway chats.r   N)model_supports_fast_moder   zgateway.fast.not_supportedr
  c                    	 i }j                         r+t        d      5 }	j                  |      xs i }ddd       | j                  d      }|}|dd D ]#  }||vst	        ||   t
              si ||<   ||   }% |||d   <   t        |       y# 1 sw Y   \xY w# t        $ r!}t        j                  d| |       Y d}~yd}~ww xY wr
  r
  r
  s
           r#   r
  z<GatewayRunner._handle_fast_command.<locals>._save_config_key`)  r
  r
  r  r  zgateway.fast.status_fastzgateway.fast.status_normalzgateway.fast.status)r  >   r  r  r  zgateway.fast.label_fast>   r  r  r  zgateway.fast.label_normalzgateway.fast.unknown_argr
  zagent.service_tierzgateway.fast.savedr
  zgateway.fast.session_only)r   r/  r
  r  r7   rq  r   rO  rP  r  r  r   r*   )r  rb  r
  r7  r(  r   r
  r  saved_valuerW  r   r   s             @@r#   r+  z"GatewayRunner._handle_fast_commandR)  s+    >%%'--/557"]2!446*,&{3'.122	s 	( tx'6:6H6HJ6VQ12\]^z\{F*88>!!+D K/0E&&!%D"K12E/T::0+>)77,E::s   D%D)c                    K   ddl m}m}m} | j	                  |j
                        } ||      }|r ||       t        t        d            S  ||       t        t        d            S w)uP   Handle /yolo — toggle dangerous command approval bypass for this session only.r   )disable_session_yoloenable_session_yolois_session_yolo_enabledzgateway.yolo.disabledzgateway.yolo.enabled)r  r
  r
  r
  r  r  r$  r   )r  rb  r
  r
  r
  rh  rP   s          r#   r  z"GatewayRunner._handle_yolo_command)  se     	
 	
 225<<@)+6 -!!$;"<==,!!$:";<<s   A(A*c                   K   t         dz  }t        |j                  j                        }	 t	               }t        t        |dd      d      }|st        d      S g d}t        d      t        d	      t        d
      t        d      d}ddl	m
}  ||dd      }	|	|vrd}	|j                  |	      dz   t        |      z  }
||
   }	 d|vst        |j                  d      t              si |d<   |d   }d|vst        |j                  d      t              si |d<   ||d   vs"t        |d   j                  |      t              si |d   |<   ||d   |   d<   t!        ||       ||    dt        d|      z   S # t        $ r d}Y :w xY w# t        $ r6}t"        j%                  d|       ||    dt        d|      z   cY d}~S d}~ww xY ww)u  Handle /verbose command — cycle tool progress display mode.

        Gated by ``display.tool_progress_command`` in config.yaml (default off).
        When enabled, cycles the tool progress mode through off → new → all →
        verbose → off for the *current platform*.  The setting is saved to
        ``display.platforms.<platform>.tool_progress`` so each channel can
        have its own verbosity level independently.
        r   r   tool_progress_commandFr  zgateway.verbose.not_enabled)r  r  r  r  zgateway.verbose.mode_offzgateway.verbose.mode_newzgateway.verbose.mode_allzgateway.verbose.mode_verboser   r&  tool_progressr  r   rP  r[  zgateway.verbose.saved_suffixr  z%Failed to save tool_progress mode: %szgateway.verbose.save_failedr  N)r   r  r  r   r  r   r   r   r   rN  r'  indexr]  r2   rB   rh   r   r=  r>  )r  rb  r   r,  r(  gate_enabledcycledescriptionsr'  rP   rC	  new_moder   r  s                 r#   r  z%GatewayRunner._handle_verbose_command)  s	     #]2+ELL,A,AB	!.0K*Y0GHL 233 1/0/0/078	
 	C)+|_V[\%G{{7#a'3u:5:	]+:kooi>XZ^3_)+I&!),G')GKK<TVZ1[')$7;#77z'R]J^JbJbcoJprv?w57$\2BJGK .?k;7)*"-2\JKE  	! L	!L  	]NNBAF"8,-R015RZ[3\\\	]sZ   )G"E: A8GB2F 9G:F	GF		G	G+G GGGGc                   K   ddl m} t        dz  }t        |j                  j
                        }d}	 t        |dd      xs dj                         }|j                  d      rA|j                  dd      }t        |      dkD  r!|d   j                         j                         }	 t               } |||      }
|dv rN|
d   rt        d      n
t        d      }dj                  |
j!                  d      xs g       }t        d|||      S |dv rd}n|dv rd}n|dk(  r|
d    }nt        d      S 	 t#        |j!                  d      t$              si |d<   |d   }t#        |j!                  d      t$              si |d<   ||d   d<   t'        ||       |rt        d      n
t        d      }d}|rCddl m}  |t/        |      xs ddd|
j!                  d      xs g d      }|rt        d|       }t        d!||"      S # t        $ r d}Y hw xY w# t        $ r}	t        d	|	
      cY d}	~	S d}	~	ww xY w# t        $ r-}	t(        j+                  d|	       t        d|	
      cY d}	~	S d}	~	ww xY ww)#u8  Handle /footer command — toggle the runtime-metadata footer.

        Usage:
            /footer           → toggle on/off
            /footer on        → enable globally
            /footer off       → disable globally
            /footer status    → show current state + fields

        The footer is saved to ``display.runtime_footer.enabled`` (global).
        Per-platform overrides under ``display.platforms.<platform>.runtime_footer``
        are respected but not modified here — edit config.yaml directly for
        per-platform control.
        r   )resolve_footer_configr   r?   rm  Nr    r   zgateway.config_read_failedr  >   r  r  r  zgateway.footer.state_onzgateway.footer.state_offr  fieldszgateway.footer.status)r(	  r
  r   >   r   r  r  r
  T>   r  r  r  r	
  Fzgateway.footer.usager   runtime_footerz)Failed to save runtime_footer.enabled: %szgateway.config_save_failed)format_runtime_footer)r   context_pctr   )r   r-  r  r
  zgateway.footer.example_linerr  zgateway.footer.saved)r(	  example)rO  r
  r   r  r  r   r&   r7   r^  rp  r]  rq  r   r  r   ra  rB   r2   rh   r   r=  r>  r
  r  )r  rb  r
  r   r,  r
  r   rc  r(  r  	effectiver(	  r
  	new_stater   r
  r
  rr  s                     r#   r  z$GatewayRunner._handle_footer_command)  s~     	A"]2+ELL,A,AB 	E9d39r@@BDs#

4+u:>(..*002C
	< 4 6K *+|D	/!4=i4HA/0aPjNkEYYy}}X6<"=F'%	  //I44IBY%i00I+,,
	<kooi8$?)+I&!),Ggkk*:;TB,.()3<G$%i0k;7
 1:+,qA[?\D+,[9AT # }}X.Q2Q	G 97K'ugFFq  	C	  	<1;;	<B  	<NNFJ1;;	<s   1I-A1G? &
H 0B I-1A!H4 A-I-?H
I-HI-	H1H,&H1'I-,H11I-4	I*="I%I* I-%I**I-c                 l  K   |j                   }| j                  j                  |      }| j                  j                  |j                        }|rt        |      dk  rt        d      S |j                         xs dj                         xs d	 ddl	m
} ddlm} ddlm} | j                  |      }| j!                  ||	      \  }	}
|
j#                  d
      st        d      S |D cg c]I  }|j#                  d      dv r4|j#                  d      r#|j#                  d      |j#                  d      dK c} |d/i |
|	ddddg|j                  d	 d _        t'        dd      xs d}t'        dd      xs d} |||      j(                  }|j+                        s-t        d      | j-                  |       | j/                         S t1        j2                         }|j5                  dfd       d{   \  }}j                  }||j                  k7  r!||_        | j                  j7                          | j                  j9                  ||       | j                  j;                  |j<                  d        ||||      } |||      }t?        t'        |dd            }tA        t'        |dd      xs d      }t'        |dd      }t'        |dd      }t'        |dd      }| j-                  |       | j/                         d |d!    g}r|jC                  t        d"#             |jC                  |d$          |d%   r|jC                  |d%          |r"|jC                  t        d&|xs d'|(             n#|r!|jC                  t        d)||xs d'*             d+jE                  |      S c c}w 7 # | j-                  |       | j/                         w xY w# tF        $ r-}tH        jK                  d,|       t        d-|.      cY d}~S d}~ww xY ww)0a  Handle /compress command -- manually compress conversation context.

        Accepts an optional focus topic: ``/compress <focus>`` guides the
        summariser to preserve information related to *focus* while being
        more aggressive about discarding everything else.
        r  zgateway.compress.not_enoughr?   Nr   r  )summarize_manual_compression)estimate_request_tokens_roughr  r   zgateway.compress.no_providerrY   >   r  r^   rZ   r]   Tr  r  c                       y r  r   r  s     r#   r  z8GatewayRunner._handle_compress_command.<locals>.<lambda>Y*  r  r%   _cached_system_promptr  )r  r  zgateway.compress.nothing_to_doc                  .    j                  d       S )Nr?   )r  focus_topicr  )r  r
  msgs	tmp_agents   r#   r  z8GatewayRunner._handle_compress_command.<locals>.<lambda>l*  s    I77bP]kv7w r%   r4  r  Fr  r  r  r  u   🗜️ headlinezgateway.compress.focus_line)r  
token_linenotezgateway.compress.summary_failedr  )r  rx  zgateway.compress.aux_failed)r   r  r[  zManual compress failed: %szgateway.compress.failedr  r   )&r  rZ  r7  rA  r  r]  r   r  r7   rC  r  !agent.manual_compression_feedbackr
  r  r
  r  r6  rB   rD  r&   r  has_content_to_compressr9  r,  r^  r   rE  rA  rF  rW  rh  r4   r5   r`  ra  r   r=  r>  ) r  rb  r  r%  rc   r  r
  r
  rh  r   r4  r  _sys_prompt_tools
compressorr  
compressedr  new_session_id
new_tokensr  _summary_failed_dropped_count_summary_err_aux_fail_model_aux_fail_errr  r  r  r
  r
  r
  s                                @@@@r#   r1  z&GatewayRunner._handle_compress_command+*  s=     **@@H$$44]5M5MN#g,*233 --/52<<>F$s	9)VJ66v>K$($G$G' %H %!E> "%%i0788 !55=$99aeeI>N v1553CDD      "*(33I=9&;	# &i1H"MSQS GT:Bd =6! '99
!99$?=>Z ((5--i8Y //1&*&:&:w' !
A "+!5!5!]%=%==/=M,&&,,.""55njQ""11!--! 2  ;k
 7!	 #'wz;XZ_'`"a!$WZ9VXY%Z%_^_!`&z3H$O #**6UW["\ '
4SUY Z ((5--i8
 3456EQ<KPQLL./vWV_-9+>, !5-,? 99U##GB!T ((5--i80  	9NN7;.a88	9s   BP4AO; )P4*O; .AO<O; AO 8"O; P4/O 
OC3O >CO; P4O; O $O88O; ;	P1"P,&P1'P4,P11P4c                 x  K   t        | dd      r%| j                  j                  |j                        nd}t        |dd      }|t	        |d      sddiS 	 |j                          d{   d
t        ffd}d |d       |d      dS 7 "# t        $ r t        j                  dd	       ddicY S w xY ww)z?Read Telegram private-topic capability flags via Bot API getMe.rE  N_botget_mecheckedFz1Failed to fetch Telegram getMe topic capabilitiesTr  rG   c                     t        |       rt        |       S t        dd       }t        |t              r| |v r|j	                  |       S t        t              rj	                  |       S y )N
api_kwargs)r  r&   r2   rh   rB   )rG   r  mes     r#   _fieldz>GatewayRunner._get_telegram_topic_capabilities.<locals>._field*  sc    r4 r4(( \48J*d+
0B!~~d++"d#vvd|#r%   has_topics_enabledallows_users_to_create_topics)r  r	  r
  )
r&   rE  rB   r   r  r  r   r=  rM  r*   )r  r  rk  botr  r  s        @r#    _get_telegram_topic_capabilitiesz.GatewayRunner._get_telegram_topic_capabilities*  s     8?jRV8W$--##FOO4]agvt,;gc84u%%	&zz|#B
	 	 "()=">-34S-T
 	
 $ 	&LLLW[L\u%%	&s<   AB:B +B,B 0B:B $B74B:6B77B:c                 8  K   t        | dd      r%| j                  j                  |j                        nd}||j                  syd}t        |dd      }t        |      r%	  |t        |j                        d       d{   }|syd}	 |j                  |j                  dd	t        |      i
       d{   }t        |dd      }|syt        |dd      }|t        |d      sy	 |j                  t        |j                        t        |      d       d{    y7 # t        $ r t        j                  dd       Y w xY w7 # t        $ r t        j                  dd       Y w xY w7 U# t        $ r t        j                  dd       Y yw xY ww)zJCreate/pin the managed System topic after /topic activation when possible.rE  N_create_dm_topicSystemz&Failed to create Telegram System topicTr  z,System topic for Hermes commands and status.r  r  r  z*Failed to send Telegram System topic intror  pin_chat_message)r  r  disable_notificationz)Failed to pin Telegram System topic intro)r&   rE  rB   r   r  callabler5   r   r=  rM  r  r*   r  r  )r  r  rk  r  create_topicr  send_resultr  s           r#   _ensure_telegram_system_topicz+GatewayRunner._ensure_telegram_system_topic*  s    8?jRV8W$--##FOO4]a?&..	w(:DAL!V".s6>>/BH"MM	 
	V '>%s9~6 !- ! K
 !lDAJ gvt,;gc+=>	U&&FNN+z?%) '   / N VEPTUV  	VLLEPTLU	V
  	ULLDtLT	Us   AF!D$  D"D$ F,E 7E
8E 	F)3E4 E2E4 !F"D$ $ EFEF
E  E/,F.E//F2E4 4 FFFFc           	        K   t        | dd      r%| j                  j                  |j                        nd}||j                  rt        |d      syt        t              j                         j                  dz  dz  }|j                         sy	 |j                  |j                  t        |      d|j                  rdt        |j                        ind       d{    y7 # t        $ r t        j!                  d	d
       Y yw xY ww)zFSend the bundled BotFather Threads Settings screenshot when available.rE  Nsend_image_fileassetsz'telegram-botfather-threads-settings.jpgu/   BotFather → Bot Settings → Threads Settingsr  )r  
image_pathr
  r  z)Failed to send Telegram topic setup imageTr  )r&   rE  rB   r   r  r  r   r   r   r  rr   r  r*   r  r   r=  rM  )r  r  rk  r  s       r#    _send_telegram_topic_setup_imagez.GatewayRunner._send_telegram_topic_setup_image*  s     8?jRV8W$--##FOO4]a?&..IZ8[(^++-44x?Bkk
  "	U))z?IAGAQAQ+s6+;+;'<=W[	 *     	ULLDtLT	Us=   BDAC' C% C' $D%C' ' D
D	D

Dr,  c                     t        j                  ddt        |xs d            j                         }|syt	        |      dkD  r|dd j                         dz   }|S )	zFReturn a Bot API-safe forum topic name from a generated session title.r.
  ro  r?   zHermes Chatr  Nu   r  )r(   r,   r*   r7   r]  rB  )r  r,  r
  s      r#   _sanitize_telegram_topic_titlez,GatewayRunner._sanitize_telegram_topic_title+  sX    &&c%+2&67==?  w<#dsm**,u4Gr%   c                 T  K   | j                  |      r|j                  r|j                  syt        | dd      r%| j                  j                  |j                        nd}|ct        t        |      dd      }t        |      rB	  ||t        |j                        t        |j                              }t        |t              ryt        | dd      }|g	 |j                  t        |j                        t        |j                              }|r+t        |j                  d      xs d      t        |      k7  ry|y| j!                  |      }		 t        |dd      }
|
: |
t        |j                        t        |j                        |	       d{    yt        |dd      }|t        |dd      nd}||t        |dd      nd}|y	  |t#        |j                        t#        |j                        |	       d{    y# t        $ r d}Y bw xY w# t        $ r t        j                  dd	
       Y yw xY w7 7 ?# t$        t&        f$ r+  ||j                  |j                  |	       d{  7   Y yw xY w# t        $ r t        j                  dd	
       Y yw xY ww)zLBest-effort rename of a Telegram DM topic when Hermes auto-titles a session.NrE  _get_dm_topic_infor{  r  r  r?   z5Failed to verify Telegram topic binding before renameTr  rename_dm_topic)r  r  rG   r  edit_forum_topiceditForumTopic)r  message_thread_idrG   z8Failed to rename Telegram topic for auto-generated title)r  r  r  r&   rE  rB   r   r  r  r*   r   r2   rh   r8  r=  rM  r  r5   rD   r8   )r  r  r  r,  rk  get_infooperator_topicr  rb  
topic_namerename_topicr  r!  s                r#   (_rename_telegram_topic_for_session_titlez6GatewayRunner._rename_telegram_topic_for_session_title+  s     ++F36>>QWQaQa 9@jRV8W$--##FOO4]atG}.BDIH!*%-gs6>>7JCPVP`P`La%bN
 nd3T=$7
!	$??/!&"2"23 @  s7;;|#<#BCs:V
 ?88?
	d"7,=tDL'"/!&"2"23#  
 '640CILws,>E^b'KN?730@$#G`d '&/&)&*:*:&;#  Q ! *%)N*   T_cd
 z* &"NN&,&6&6#    	dLLS^bLc	ds   BJ(0H	 5 J(A%H ;J(AJ IJ J(3J J(4I II J(	HJ(HJ( H>;J(=H>>J(J I 1I?6I97I?<J =J(>I??J  J%"J($J%%J(c                 z   |r| j                  |      sy	 t        j                         }||j                         ry	 t        j                  |      }t        j                  | j                  |||      |      }dd}|j                  |       y# t        $ r t	        | dd      }Y ~w xY w# t        $ r |}Y fw xY w)z>Schedule a topic rename from the auto-title background thread.Nr]  c                 r    	 | j                          y # t        $ r t        j                  dd       Y y w xY w)Nz"Telegram topic title rename failedTr  )r  r   r=  rM  )futs    r#   _log_rename_failurezPGatewayRunner._schedule_telegram_topic_title_rename.<locals>._log_rename_failurex+  s3    R

 RADQRs     66rq  )r  r^  r   r@  r&   	is_closedr  r:   r   run_coroutine_threadsafer(  rs  )r  r  r  r,  r  copied_sourcefuturer,  s           r#   %_schedule_telegram_topic_title_renamez3GatewayRunner._schedule_telegram_topic_title_renamea+  s     D88@	8++-D <4>>+	#'//7M 1199-UZ[
	R 	  !45%  	84$7D	8  	#"M	#s"   B B, B)(B),B:9B:g     r@c                    t        | d      si | _        t        |j                  xs d      }|syddl}|j                         }| j                  j                  |d      }||z
  | j                  k  ry|| j                  |<   y)zRate-limit the BotFather Threads Settings screenshot.

        If a user sends /topic repeatedly while Threads Settings are still
        off, we shouldn't keep re-uploading the screenshot every time.
        _telegram_capability_hint_tsr?   Tr   Nr  F)r  r3  r*   r  rN   r  rB   $_TELEGRAM_CAPABILITY_HINT_COOLDOWN_Sr  s         r#   %_should_send_telegram_capability_hintz3GatewayRunner._should_send_telegram_capability_hint+  s     t;<02D-fnn*+oo0044WcB:AAA58))'2r%   c                      	 y)Nuz  /topic — enable multi-session DM mode (one bot, many parallel chats)

Usage:
  /topic             Enable topic mode, or show status if already on
  /topic help        Show this message
  /topic off         Disable topic mode and clear topic bindings
  /topic <id>        Inside a topic: restore a previous session by ID

How it works:
1. Run /topic once in this DM — Hermes checks BotFather Threads
   Settings are enabled and flips on multi-session mode.
2. Tap All Messages at the top of the bot and send any message.
   Telegram creates a new topic for that message; each topic is
   an independent Hermes session (fresh history, fresh context).
3. The root DM becomes a system lobby — send /topic, /status,
   /help, /usage there. Normal prompts go in a topic.
4. /new inside a topic resets just that topic's session.
5. /topic <id> inside a topic restores an old session into it.r   r  s    r#   _telegram_topic_help_textz'GatewayRunner._telegram_topic_help_text+  s    M	
r%   c                     | j                   sddlm}  |t        d            S t	        |j
                  xs d      }|sy	 | j                   j                  |t	        |j                  xs d            }|sy		 | j                   j                  |
       dD ]2  }t        | |d      }t        |t              s!|j                  |d       4 	 y# t        $ r d}Y fw xY w# t        $ r$}t        j                  d       d| cY d}~S d}~ww xY w)z5Cleanly disable topic mode for a chat via /topic off.r   format_session_db_unavailable,gateway.shared.session_db_unavailable_prefixr  r?   zCould not determine chat ID.r  Fz@Multi-session topic mode is not currently enabled for this chat.)r  z%Failed to disable Telegram topic modezFailed to disable topic mode: N)r  r3  u   Multi-session topic mode is now OFF for this chat.

Existing topics in Telegram aren't removed — they'll just stop being gated as independent sessions. The root DM works as a normal Hermes chat again. Run /topic to re-enable later.)r{  r|  r:  r   r*   r  r
  r  r   disable_telegram_topic_moder=  r  r&   r2   rh   r-  )r  r  r:  r  currently_enabledrE  attrstores           r#   %_disable_telegram_topic_mode_for_chatz3GatewayRunner._disable_telegram_topic_mode_for_chat+  s   B0:h8ijjfnn*+1	& $ 0 0 O OFNN0b1 !P ! !U	:888I T 	)DD$-E%&		'4(	)
G	
  	& %	&  	:DE3C599	:s0   4C :C  CC 	D)DDDr7  c                   K   |j                   }|j                  t        j                  k7  s|j                  dk7  rt        d      S | j                  sddlm}  |t        d            S t        | dd      }t        |      r	  ||      st        d	      S 	 |j                         j                         }|j!                         dv r| j#                         S |j!                         dv r| j%                  |      S |r1|j&                  st        d      S | j)                  ||       d{   S | j+                  |       d{   }|j-                  d      r|j-                  d      du r5| j/                  |      r| j1                  |       d{    t        d      S |j-                  d      du r5| j/                  |      r| j1                  |       d{    t        d      S 	 | j                  j3                  t5        |j6                        t5        |j8                        |j-                  d      |j-                  d             |j&                  s| j=                  |       d{    |j&                  r	 | j                  j?                  t5        |j6                        t5        |j&                              }|rYt5        |j-                  d      xs d      }	d}
	 | j                  jA                  |	      }
|
xs t        d      }t        d||	       S t        d!      S | jC                  |      S # t        $ r t        j                  d
d       Y w xY w7 67  7 7 # t        $ r,}t        j;                  d       t        d|      cY d}~S d}~ww xY w7 7# t        $ r t        j                  dd       d}Y 
w xY w# t        $ r d}
Y w xY ww)"z:Handle /topic for Telegram DM user-managed topic sessions.r  zgateway.topic.not_telegram_dmr   r9  r;  r<  r  Nzgateway.topic.unauthorizedzTopic auth check failedTr  >   -h--helpr  r  >   r  rQ  r	
  z!gateway.topic.restore_needs_topicr  r	  Fzgateway.topic.topics_disabledr
  z$gateway.topic.topics_user_disallowed)r  r  r	  r
  z$Failed to enable Telegram topic modezgateway.topic.enable_failedr  r  r  r  r?   zgateway.topic.untitled_sessionzgateway.topic.bound_status)rW  r  zgateway.topic.thread_ready)"r  r   r  r	  r  r   r{  r|  r:  r&   r  r   r=  rM  r  r7   rq  r7  rA  r  _restore_telegram_topic_sessionr  rB   r5  r  enable_telegram_topic_moder*   r  r  r  r  r8  r*	  #_telegram_topic_root_status_message)r  rb  r7  r  r:  auth_fnr	  rE  rb  r  r,  session_labels               r#   r'  z#GatewayRunner._handle_topic_command+  s    ??h///63C3Ct3K455B0:h8ijj $ 5t<GGv9:: '
 %%'--/ ::<881133 ::<55==fEE##<====eTJJJ!BB6JJI& 45> ==fE??GGG899 ?@EI==fE??GGG?@@		?77FNN+FNN+#/#3#34H#I.:.>.>?^._	 8  44V<<<**EE/!&"2"23 F   \!:!@bA
! ,,>>zJE !& L+K)L0') 
 12277??K  G6FG  KJ H H  	?CD2#>>	?
 =  DtT ! ! E!s   B OL7 BO(M)OM!AOM$AOM'O(A#M*  O+N",O=AN%  "O#O >9O7 MOMO!O$O'O*	N3!NNONO%"OO
OOOOOOc           
         g d}	 | j                   j                  t        |j                        t        |j                        d      }|r|j                  d       |D ]  }t        |j                  d      xs d	      }t        |j                  d
      xs d      }t        |j                  d      xs d	      j                         }d| d| d}|r|d| z  }|j                  |        |j                  d	dddd|d   j                  d       dg       n|j                  g d       dj                  |      S # t
        $ r t        j                  dd       g }Y w xY w)N)z*Telegram multi-session topics are enabled.r?   zTo create a new Hermes chat, open All Messages at the top of this bot interface and send any message there. Telegram will create a new topic for it.r?   r  )r  r  r  z)Failed to list unlinked Telegram sessionsTr  zPrevious unlinked sessions:r   r?   r,  zUntitled sessionrr  z- u    — `r  r  zTo restore one:]1. Create or open a topic. To create a new one, open All Messages and send any message there..2. Send /topic <session-id> inside that topic.zExample: Send /topic r   z inside a topic.)z-No previous unlinked Telegram sessions found.r?   z$To restore a previous session later:rK  rL  r[  )r{  (list_unlinked_telegram_sessions_for_userr*   r  r  r   r=  rM  r`  rB   r7   r,	  ra  )	r  r  r  r4  sessionr  r,  rr  r  s	            r#   rG  z1GatewayRunner._telegram_topic_root_status_message(,  sq   
	''PPFNN+FNN+ Q H LL67# # T!2!8b9
GKK0F4FGgkk)4:;AACE7&A6eG9--DT"# LL!o@'(='>>NO  LL   yy;  	LLDtLTH	s   AE "E'&E'raw_session_idc                   K   |j                   }| j                  j                  |j                               }|sd|j                          S | j                  j	                  |      }|sd|j                          S t        |j                  d      xs d      dk7  ryt        |j                  d      xs d      t        |j                        k7  ry| j                  j                  |      }| j                  j                  t        |j                        t        |j                        	      }|r|r|j                  d
      |k7  ry| j                  |      }	 | j                  j                  t        |j                        t        |j                        t        |j                        ||d       | j                  j                  |      xs |}
d}	 t!        | j                  j#                  |            D ]D  }|j                  d      dk(  s|j                  d      s*t        |j                  d            } n d|
 }|r|d| z  }|S # t        $ r}	dt        |	      v rY d}	~	y d}	~	ww xY w# t$        $ r d}Y Bw xY ww)zBRestore an existing Telegram-owned Hermes session into this topic.zSession not found: r  r?   r   zNThat session is not a Telegram session and cannot be restored into this topic.r  z3That session does not belong to this Telegram user.r  r  r  z9That session is already linked to another Telegram topic.restored)r  r  r  rh  r  managed_modezalready linkedNrY   r^   rZ   zSession restored: z

Last Hermes message:
)r  r{  resolve_session_idr7   r+	  r*   rB   r  #is_telegram_session_linked_to_topicr8  r  r  r  r$  r8   r*	  rg   get_messagesr   )r  rb  rO  r  r  rN  linkedcurrent_bindingrh  rE  r,  last_assistantrm  r  s                 r#   rE  z-GatewayRunner._restore_telegram_topic_sessionV,  sf    %%889M9M9OP
()=)=)?(@AA""..z:()=)=)?(@AAw{{8$*+z9cw{{9%+,FNN0CCH!!EEQ[E\**EE'&**+ F 
 "o&9&9,&G:&UR226:	00FNN+f../FNN+'%' 1    22:>L*	"#D$4$4$A$A*$MN ;;v&+5'++i:P%(Y)?%@N (w/4^4DEEH%  	3s8+R	  	"!N	"sg   EJAI 8!J;I: I: (I: J	I7I2,J1I22I77J:JJJJc                   K   |j                   }| j                  j                  |      }|j                  }| j                  sddlm}  |t        d            S | j                  j                  |      }|K	 | j                  j                  ||j                  r|j                  j                  nd|j                         |j                         j                         }|r^	 | j                  j!                  |      }|st        d
      S 	 | j                  j%                  ||      rt        d|      S t        d      S | j                  j                  |      }
|
rt        d||
      S t        d|      S # t        $ r Y w xY w# t"        $ r}	t        d|		      cY d}	~	S d}	~	ww xY w# t"        $ r}	t        d|		      cY d}	~	S d}	~	ww xY ww)uB   Handle /title command — set or show the current session's title.r   r9  r;  r<  Nr  )r  r  r  zgateway.shared.warn_passthroughr  zgateway.title.empty_after_cleanzgateway.title.set_tor  zgateway.title.not_foundz gateway.title.current_with_title)r  r,  zgateway.title.current_no_titler  )r  rZ  r7  r  r{  r|  r:  r   r*	  create_sessionr   r   r  r   r  r7   r  r8   r  )r  rb  r  r%  r  r:  existing_title	title_argr"   r  r,  s              r#   r7  z#GatewayRunner._handle_title_command,  s    **@@H"--
B0:h8ijj ));;JG!  //)4:OO6??00"NN 0  **,224	E ,,;;IF	 :;;E##55j)L39EE677
 $$66zBE;
Z_``9jQQ5    E:!DDE  E:!DDEs   A5G8A
E8  G#F >G(F* 4G5
F* ?9G8	FGFG	F'F"F'G"F''G*	G
3G?G
 GG

Gc                   K   | j                   sddlm}  |t        d            S |j                  }| j                  |      }|j                         j                         }|s	 |j                  r|j                  j                  nd}| j                   j                  |d      }|D cg c]  }|j                  d      s| }	}|	st        d	      S t        d
      g}
|	dd D ]J  }|d   }|j                  dd      dd }|rt        d|      nd}|
j                  t        d||             L |
j                  t        d             dj                  |
      S | j                   j#                  |      }|st        d|      S 	 | j                   j%                  |      }| j&                  j)                  |      }|j*                  |k(  rt        d|      S | j-                  |       | j&                  j/                  ||      }|st        d      S | j1                  |       | j3                  |       | j                   j5                  |      xs |}| j&                  j7                  |      }|r.t9        |D cg c]  }|j                  d      dk(  s| c}      nd}|st        d|      S |d k(  rt        d!||"      S t        d#||"      S c c}w # t        $ r-}t        j!                  d|       t        d|      cY d}~S d}~ww xY w# t        $ r"}t        j!                  d||       Y d}~d}~ww xY wc c}w w)$u@   Handle /resume command — switch to a previously-named session.r   r9  r;  r<  Nr  )r  r  r,  z gateway.resume.no_named_sessionszgateway.resume.list_headerrr  r?   r	  z"gateway.resume.list_preview_suffixr
  zgateway.resume.list_item)r,  preview_partzgateway.resume.list_footerr[  z"Failed to list titled sessions: %szgateway.resume.list_failedr  zgateway.resume.not_foundr	  z0Failed to resolve resume continuation for %s: %szgateway.resume.already_onzgateway.resume.switch_failedrY   r  zgateway.resume.resumed_no_countr  r   zgateway.resume.resumed_one)r,  rx  zgateway.resume.resumed_many)r{  r|  r:  r   r  r  r  r7   r   r   list_sessions_richrB   r`  ra  r   r=  rM  resolve_session_by_titleresolve_resume_session_idrZ  r7  r  r:  r8  r  r9  r*	  rA  r]  )r  rb  r:  r  rh  rG   user_sourcer4  r  titledr  r,  rr  r^  r  	target_idcurrent_entryr  rc   r  	msg_counts                        r#   r8  z$GatewayRunner._handle_resume_command,  s    B0:h8ijj226:%%'--/@7=foo33D++>>&b ?  &.@w!@@?@@789 hAgJEeeIr23B7G_f1%ISZ#[lnLLL#=UYe!fg	h
 Q;<=yy'' $$==dC	/d;;	[((BB9MI
 **@@H##y00t<< 	))+6 &&55k9M	34433K@ 	  -   229=E $$44Y?LSCGGqquuV}/FGHYZ	6eDD>1iPP.e9MMs A  @A1E5Q??@  	[LLKYXYZZ	[: Hs   A"M%AK *KKK MBK +ML #CM;L>L>9MK 	L "LLMLM	L;L60M6L;;Mc                   K   ddl }| j                  sddlm}  |t	        d            S |j
                  }| j                  |      }| j                  j                  |      }| j                  j                  |j                        }|st	        d      S |j                         j                         }ddlm}	 |	j                         }
|
j                  d      }|j!                         j"                  dd	 }| d
| }|r|}nF| j                  j%                  |j                        }|xs d}| j                  j'                  |      }|j                  }	 | j                  j)                  ||j*                  r|j*                  j,                  ndt/        | j0                  t2              r/| j0                  j5                  di       xs i j5                  d      nd|       |D ]  }	 | j                  j=                  ||j5                  dd      |j5                  d      |j5                  d      xs |j5                  d      |j5                  d      |j5                  d      |j5                  d      |j5                  d      |j5                  d      |j5                  d      |j5                  d      |j5                  d               	 | j                  j?                  ||       | j                  jA                  ||      }|st	        d!      S | jC                  |       | jE                  |       tG        |D cg c]  }|j5                  d      dk(  s| c}      }|d"k(  rd#nd$}t	        |||||%      S # t6        $ r-}t8        j;                  d|       t	        d|      cY d}~S d}~ww xY w# t6        $ r Y w xY w# t6        $ r Y w xY wc c}w w)&u  Handle /branch [name] — fork the current session into a new independent copy.

        Copies conversation history to a new session so the user can explore
        a different approach without losing the original.
        Inspired by Claude Code's /branch command.
        r   Nr9  r;  r<  zgateway.branch.no_conversationr   z%Y%m%d_%H%M%Sr  r  r  r(  r   rH   )r  r  r   parent_session_idz#Failed to create branch session: %szgateway.branch.create_failedr  rY   r  rZ   	tool_namerG   rH
  tool_call_idrW   rR   rS   rT   rU   rV   )r  rY   rZ   ri  rH
  rj  rW   rR   rS   rT   rU   rV   zgateway.branch.switch_failedr   zgateway.branch.branched_onezgateway.branch.branched_many)r,  rx  r  r  )$rV
  r{  r|  r:  r   r  r  rZ  r7  rA  r  r  r7   r   rK   r-	  rZ
  r[
  r*	  get_next_title_in_lineagerZ  r   r   r2   r  rh   rB   r   r=  r  append_messager  r8  r  r9  r]  )r  rb  r_
  r:  r  rh  re  rc   branch_name_dtrK   timestamp_str
short_uuidr
  branch_titlecurrent_titlebaserh  r  r[   r  r  rf  r+  s                           r#   r9  z$GatewayRunner._handle_branch_command	-  sp     	B0:h8ijj226: **@@H$$44]5M5MN566,,.446 	-ggi_5[[]&&r*
)?!J<8 &L ,,>>}?W?WXM ,HD++EEdKL)44		>++)06v,,YMWX\XcXceiMjt{{w39r>>yIpt"3	 ,   	C  //-0GGI.!ggk2Ecggfo"ww|4!$!8"%''/":!ggk2&)gg.A&B&)gg.A&B*-''2I*J(+0E(F 0 	(	..~|L
 &&55k>R	34433K@ 	  -GGqquuV}/FGH	/8A~+CaL	BSYghhS  	>LL>B31==	>*    		 Hs   EOB
M7  O&C N0O	O  %AO9OO O7	N- "N("N-#O(N--O0	N=9O<N==O 	O	OOOc           
        K   |j                   }| j                  |      }| j                  j                  |      }|r|t        u rAt        | dd      }t        | dd      }|r%|#|5  |j                  |      }|r|d   }ddd       |r|t        urt        |dd      nd}|r|t        urt        |dd      nd}	|r|t        urt        |dd      nd}
|s|t        | dd      o	 | j                  j                  |      }| j                  j                  |j                        xs i }|xs |j                  d	      }|	xs |j                  d
      }	g }|r5	 t        j                  t        ||	|
       d{   }|rt        |d      }|rit!        |d      r\|j"                  dkD  rLg }|j%                         }|rE|j&                  r9ddlm} |j-                  t/        d ||                   |j-                  d       t        |dd      xs d}t        |dd      xs d}t        |dd      xs d}t        |dd      xs d}|j-                  t/        d             |j-                  t/        d|j0                               |j-                  t/        d|d             |r|j-                  t/        d|d             |r|j-                  t/        d|d             |j-                  t/        d|d             |j-                  t/        d |j2                  d             |j-                  t/        d!|j"                               	 dd"lm}m}  ||j0                   |||||#      t        |dd      t        |dd      $      }|j:                  F|j<                  d%k(  rd&nd}|j-                  t/        d'|t?        |j:                        d()             n)|j<                  d*k(  r|j-                  t/        d+             |j@                  }|jB                  rl|jD                  r&tG        d,|jB                  |jD                  z  d,z        nd}|j-                  t/        d-|jB                  d|jD                  d|d./             |jH                  r&|j-                  t/        d0|jH                               |r"|j-                  d       |jK                  |       d1jM                  |      S | j                  j                  |      }| j                  jO                  |j                        }|rdd2l(m)} |D cg c]*  }|j                  d3      d4v s|j                  d5      s)|, } } ||       }!t/        d6      t/        d7tU        |             t/        d8|!d      t/        d9      g}|r"|j-                  d       |jK                  |       d1jM                  |      S |rd1jM                  |      S t/        d:      S # 1 sw Y   xY w# t        $ r i }Y w xY w7 # t        $ r d}Y w xY w# t        $ r Y <w xY wc c}w w);a:  Handle /usage command -- show token usage for the current session.

        Checks both _running_agents (mid-turn) and _agent_cache (between turns)
        so that rate limits, cost estimates, and detailed token breakdowns are
        available whenever the user asks, not only while the agent is running.
        ro  Nrm  r   r   r   r   r{  billing_providerbilling_base_url)r   r   T)markdownsession_total_tokens)format_rate_limit_compactzgateway.usage.rate_limitsr'	  r?   session_input_tokenssession_output_tokenssession_cache_read_tokenssession_cache_write_tokenszgateway.usage.header_sessionzgateway.usage.label_modelr	  z gateway.usage.label_input_tokensrn  r)	  zgateway.usage.label_cache_readzgateway.usage.label_cache_writez!gateway.usage.label_output_tokenszgateway.usage.label_totalzgateway.usage.label_api_calls)CanonicalUsageestimate_usage_cost)r!	  r"	  r#	  r$	  )r   r   r   r  zgateway.usage.label_costz.4f)r  amountincludedz!gateway.usage.label_cost_includedr  zgateway.usage.label_contextr  )usedr]	  pctz gateway.usage.label_compressionsr[  )r  rY   >   r  r^   rZ   z!gateway.usage.header_session_infozgateway.usage.label_messagesz%gateway.usage.label_estimated_contextz"gateway.usage.detailed_after_firstzgateway.usage.no_data)+r  r  rd  rB   r  r&   rZ  r7  r{  r+	  r  r   r^  r  r   r   r  session_api_callsget_rate_limit_statehas_dataagent.rate_limit_trackerry  r`  r   r   rx  agent.usage_pricingr~  r  
amount_usdr  r6   r  r+  r  r  compression_countr,	  ra  rA  r  r  r]  )"r  rb  r  rh  r   r]  r  cachedr   r   r   _entry_for_billing	persistedaccount_linesaccount_snapshotr  rl_statery  r!	  r"	  
cache_readcache_writer~  r  cost_resultr  r	  r  r%  rc   r  r  r
  approxs"                                     r#   r2  z#GatewayRunner._handle_usage_commande-  s     226: $$((5!88!$(;TBKT>48Fv1  *#ZZ4F &q	* 8=NeAe75*d3ko7<NeAe75*d3ko5:uLc?c'%D1imGD->J%)%7%7%M%Mf%U" ,,889K9V9VW][]	  D9==1C#DHD9==1C#DH $&()0):):'%#	* $    :;KVZ [WU$:;@W@WZ[@[E 113HH--NQ:B[\dBefgR  #5*@!DIL#E+BAFK!M (CQGL1J!%)EqINQKLL9:;LL6ekkJKLL=UVGWYZQ?*UVYZQ@;WX/[\LL>WXHY[\LL6A[A[\]@^`aLL:%BYBYZ[S1KK"%1&3*4+6	 %UJ=$UJ=
 ))5$/$6$6+$ES2FLL#=fX]^i^t^tXuvyWz!|} '':5LL#F!GH
 **C%%UXUgUgc#s558J8JJSPQmnQ<cF\F\]^E_knk}k}~  kA  KN  OR  JS  U  V$$QAI^I^_`R ]+99U## **@@H$$44]5M5MNK&f!!%%-;P*PUVUZUZ[dUeAfDf3D9F560D	B9F1:O67	E R ]+99U##99]++())o* *   	$  (#' (f  , gs   A#Y%X>A"Y!AX %.Y X' 4X$5X' 9GYB9X9 	D7Y Y	Y	+Y	/BYX
YX!Y X!!Y$X' 'X62Y5X66Y9	YYYYc                 2  	
K   |j                         j                         }t        j                  dd|      }d
d|r|j	                         }d}|t        |      k  r||   dk(  r)|dz   t        |      k  r	 t        ||dz            
|d
z  }nS||   dk(  r|dz   t        |      k  r||dz      |d
z  }n,||   j                         rt        ||         
|dz  }n|dz  }|t        |      k  r	 ddl
m	 ddlm t        j                         }	
fd}|j!                  d|       d{   S # t        $ r t        d||dz      	      cY S w xY w7 &# t"        $ r/}t$        j'                  d|d       t        d|      cY d}~S d}~ww xY ww)z>Handle /insights command -- show usage insights and analytics.z'[\u2012\u2013\u2014\u2015](days|source)z--\1rH  Nr   z--daysr   zgateway.insights.invalid_daysr  r{  z--sourcer0  )InsightsEnginec                              }  |       }|j                        }|j                  |      }| j                          |S )N)daysr  )generateformat_gatewayr2  )dbenginereportr  r  r1  r  r  s       r#   _run_insightsz=GatewayRunner._handle_insights_command.<locals>._run_insights.  sD    ['+d6B..v6
r%   zInsights command error: %sTr  zgateway.insights.errorr  )r  r7   r(   r,   rp  r]  r5   r8   r   isdigitr|  r1  agent.insightsr  r^  r   rE  r   r=  r  )r  rb  r7  rc  rd  r  r  r  r  r1  r  r  s           @@@@r#   r3  z&GatewayRunner._handle_insights_command-  s    %%'--/ vv@'4P JJLEAc%j.8x'AECJ,>V"5Q<0 FA1X+AE
0B"1q5\FFA1X%%'uQx=DFAFA c%j. 	8.5++-D --dMBBB5 & V !@aRSeUUV4 C 	8LL5q4LH-Q77	8sm   A5F<D8 A'F6=E 3E4E 7F8EFEFE 	F%$F	F
FFFc                    K   j                   } j                  |       j                         }t        |t              r|j                  d      nd}d}t        |t              rt        |j                  dd            }|s j                         d{   S dt        dt        t           f fd}t        d      } j                  d	d
||       d{   S 7 H7 w)u  Handle /reload-mcp — reconnect MCP servers and rebuild the cached agent.

        Reloading MCP tools invalidates the provider prompt cache for the
        active session (tool schemas are baked into the system prompt).  The
        next message re-sends full input tokens, which is expensive on
        long-context or high-reasoning models.

        To surface that cost, the command routes through the slash-confirm
        primitive: users get an Approve Once / Always Approve / Cancel
        prompt before the reload actually runs.  "Always Approve" persists
        ``approvals.mcp_reload_confirm: false`` so the prompt is silenced
        for subsequent reloads in any session.

        Users can also skip the confirm by flipping the config key directly.
        	approvalsNTmcp_reload_confirmchoicer   c                 @  K   | dk(  rt        d      S | dk(  r&	 ddlm}  |dd       t        j	                  d       j                         d {   }| dk(  r| d
t        d      z   S |S # t
        $ r }t        j                  d	|       Y d }~Ud }~ww xY w7 Hw)Nr  zgateway.reload_mcp.cancelledr  r   save_config_valuezapprovals.mcp_reload_confirmFz7User opted out of /reload-mcp confirmation (session=%s)z.Failed to persist mcp_reload_confirm=false: %sr  z"gateway.reload_mcp.always_followup)r   r  r  r=  rL  r   r>  _execute_mcp_reload)r  r  rE  r  rb  r  rh  s       r#   _on_confirmz=GatewayRunner._handle_reload_mcp_command.<locals>._on_confirmA.  s     !788!Z5%&DeLKKQ#  33E::F! +O)PPPM ! ZNN#SUXYYZ ;s9   B%A0 BBB0	B9BBBBz!gateway.reload_mcp.confirm_promptr  z/reload-mcprb  r6  r,  rm  handler)r  r  _read_user_configr2   rh   rB   r4   r  r*   r
   r   _request_slash_confirm)	r  rb  r  r(  r  confirm_requiredr  prompt_messagerh  s	   ``      @r#   r4  z(GatewayRunner._handle_reload_mcp_command.  s       226: ,,.4>{D4QKOOK0W[	i&#IMM2F$MN11%888	c 	hsm 	( >?00 " 1 
 
 	
7 96
s%   BC&C"AC&C$C&$C&c           
      b  K   t        j                         }	 ddlm}m}m}m} |5  t        |j                               }ddd       |j                  d|       d{    |j                  d|       d{   }|5  t        |j                               }	ddd       	z
  }
||	z
  }|	|z  }t        d      g}|r4|j                  t        ddj                  t        |                         |
r4|j                  t        ddj                  t        |
                         |r4|j                  t        d	dj                  t        |                         |	s|j                  t        d
             n/|j                  t        dt        |      t        |	                   g }|
r,|j                  ddj                  t        |
                    |r,|j                  ddj                  t        |                    |r,|j                  ddj                  t        |                    |rt        |       dnd}|rdj                  |      dz   nd}dd| | dd}	 | j                  j!                  |j"                        }| j                  j%                  |j&                  |       dj                  |      S # 1 sw Y   xY w7 w7 `# 1 sw Y   DxY w# t(        $ r Y <w xY w# t(        $ r-}t*        j-                  d|       t        d|      cY d}~S d}~ww xY ww)a  Actually disconnect, reconnect, and notify MCP tool changes.

        Split out from ``_handle_reload_mcp_command`` so the confirmation
        wrapper can invoke the same path whether the user confirmed via
        button, text reply, or has the confirm gate disabled.
        r   )shutdown_mcp_serversdiscover_mcp_tools_serversr|  Nzgateway.reload_mcp.headerzgateway.reload_mcp.reconnectedr  )nameszgateway.reload_mcp.addedzgateway.reload_mcp.removedz!gateway.reload_mcp.none_connectedz"gateway.reload_mcp.tools_available)r  serverszAdded servers: zRemoved servers: zReconnected servers: z MCP tool(s) now availablezNo MCP tools availablez. r?   r  z,[IMPORTANT: MCP servers have been reloaded. zD. The tool list for this conversation has been updated accordingly.]r]   r[  zMCP reload failed: %szgateway.reload_mcp.failedr  )r^  r   tools.mcp_toolr  r  r  r|  r  r,  rE  r   r`  ra  rO  r]  rZ  r7  r  rV  r  r   r=  r>  )r  rb  r  r  r  r  r|  old_servers	new_toolsconnected_serversaddedr|  reconnectedr  change_partstool_summarychange_detail
reload_msgr%  r  s                       r#   r  z!GatewayRunner._execute_mcp_reload^.  s     '')>	;``  3!(--/23
 &&t-ABBB #2249KLLI  9$'$8!9 &3E!$55G+k9K234EQ?tyyQWXcQdGefgQ96RW=AYZ[Q;499VT[_C]^_$QBCDQC3y>cfgxcyz{
 L##odiiu6N5O$PQ##&7		&/8R7S$TU##&;DIIf[FY<Z;[$\]LUc)n--GH[sL>JDIIl3d:PRMI-YeXf  gk  lJ $ 2 2 H H V""77!,,j 99U##m3 3
 C M9 9P  
  	;NN2A60::	;s   L/K6 K K6 KK6 7K8K6 >KGK6 +AK' 6K6 L/K	K6 K6 K$K6 '	K30K6 2K33K6 6	L,?"L'!L,"L/'L,,L/c           
        K   t        j                         }	 ddlm} |j	                  d|       d{   }|j                  dg       }|j                  dg       }|j                  dd      }t        | j                  j                               D ]B  }t        |dd      }	t        |	      s	  |	       }
t        j                  |
      r
|
 d{    D t!        d
      g}|sI|sG|j#                  t!        d             |j#                  t!        d|             dj%                  |      S dt&        dt(        fd}|r8|j#                  t!        d             |D ]  }|j#                   ||              |r8|j#                  t!        d             |D ]  }|j#                   ||              |j#                  t!        d|             dg}|r@|j#                  d       |j#                  d       |D ]  }|j#                   ||              |r@|j#                  d       |j#                  d       |D ]  }|j#                   ||              |j#                  d       |j#                  d       dj%                  |      }| j+                  |j,                        }t/        | d      si | _        |r|| j0                  |<   dj%                  |      S 7 7 # t        $ r-}t        j                  dt        |d	|      |       Y d}~}d}~ww xY w# t        $ r-}t        j                  d|       t!        d|      cY d}~S d}~ww xY ww)u  Handle /reload-skills — rescan skills dir, queue a note for next turn.

        Skills don't need to be in the system prompt for the model to use
        them (they're invoked via ``/skill-name``, ``skills_list``, or
        ``skill_view`` at runtime), so this does NOT clear the prompt cache
        — prefix caching stays intact.

        If any skills were added or removed, a one-shot note is queued on
        ``self._pending_skills_reload_notes[session_key]``. The gateway
        prepends it to the NEXT user message in this session (see the
        consumer at ~L11025 in ``_run_agent_turn``), then clears it. Nothing
        is written to the session transcript out-of-band, so message
        alternation is preserved.
        r   )reload_skillsNr  r|  r]	  refresh_skill_groupz)Adapter %s refresh_skill_group raised: %srG   zgateway.reload_skills.headerzgateway.reload_skills.no_newzgateway.reload_skills.totalr)	  r[  itemr   c                     | j                  dd      }| j                  dd      }|rt        d||      S t        d|      S )NrG   r?   rZ	  z$gateway.reload_skills.item_with_desc)rG   rb	  z"gateway.reload_skills.item_no_descr	  )rB   r   )r  nmrb	  s      r#   	_fmt_linez>GatewayRunner._handle_reload_skills_command.<locals>._fmt_line.  sD    XXfb)xxr2C"SWXX=BGGr%   z"gateway.reload_skills.added_headerz$gateway.reload_skills.removed_headerz[USER INITIATED SKILLS RELOAD:r?   zAdded Skills:zRemoved Skills:z,Use skills_list to see the updated catalog.]_pending_skills_reload_noteszSkills reload failed: %szgateway.reload_skills.failedr  )r^  r   rD  r  rE  rB   rA  rE  r*  r&   r  inspectisawaitabler   r=  r>  r   r`  ra  rh   r*   r  r  r  r  )r  rb  r  r  r  r  r|  r]	  rk  refreshmayberE  r  r  r  sectionsr
  rh  r  s                      r#   r5  z+GatewayRunner._handle_reload_skills_command.  s     '')R	>://mDDFJJw+EjjB/GJJw*E   4 4 67 !'+@$G(#IE**51# 567EQ=>?Q<EJKyy''H H H QCDE! 2DLL412QEFG# 2DLL412LL8FG 99H#0! 5DOOIdO45# 12# 5DOOIdO45OOBOOJK99X&D66u||DK4!?@461AE11+>99U##W E( $  NNC93 p  	>NN5q931==	>s   ML KA9L .!KKKAL ,M-F)L ML K	L&"LL LL 	M"MMMMMr6  r  r$  c                  K   d}	 | j                         }t        |t              r|j                  d      nd}t        |t              rt	        |j                  dd            }|s         d{   S | j                  |j                        dt        ffd}	d d| d	}
| j                  |||
|	
       d{   S # t
        $ r Y lw xY w7 c7 w)u  Gate a destructive session slash command (/new, /reset, /undo).

        ``execute`` is an async callable ``execute() -> str | EphemeralReply``
        that performs the destructive action.  If the
        ``approvals.destructive_slash_confirm`` config gate is off, ``execute``
        runs immediately (returning its result).  Otherwise this routes
        through ``_request_slash_confirm`` — native yes/no buttons on
        Telegram/Discord/Slack, text fallback elsewhere.

        Three-option resolution:

          - ``once``  — run ``execute`` and return its result
          - ``always`` — persist ``approvals.destructive_slash_confirm: false``,
                        then run ``execute``
          - ``cancel`` — return a "cancelled" message; do not run ``execute``
        Tr  Ndestructive_slash_confirmr  c                 2  K   | dk(  rd dS | dk(  r&	 ddl m}  |dd       t        j                  d	                d {   }| dk(  rd}t        |t              r||z   S |S |S # t        $ r }t        j                  d
|       Y d }~Sd }~ww xY w7 Pw)Nr  u   🟡 /z# cancelled. Conversation unchanged.r  r   r  z#approvals.destructive_slash_confirmFz8User opted out of destructive slash confirm (session=%s)z5Failed to persist destructive_slash_confirm=false: %su   

ℹ️ Future /clear, /new, /reset, and /undo will run without confirmation. Re-enable via `approvals.destructive_slash_confirm: true` in config.yaml.)r  r  r=  rL  r   r>  r2   r*   )r  r  rE  r  r
  r6  r  rh  s        r#   r  zCGatewayRunner._maybe_confirm_destructive_slash.<locals>._on_confirmB/  s     !y(KLL!
5%&KUSKKR# #9_F!R 
 fc*!D=( M# ! NNOQT  %s9   B%A) 
BB$B)	B2BBBBu   ⚠️ **Confirm /z**

u   

Choose:
• **Approve Once** — proceed this time only
• **Always Approve** — proceed and silence this prompt permanently
• **Cancel** — keep current conversation

_Text fallback: reply `/approve`, `/always`, or `/cancel`._r  )
r  r2   rh   rB   r4   r   r  r  r*   r  )r  rb  r6  r,  r  r  r  r   r  r  r  rh  s     `  `     @r#   r&  z.GatewayRunner._maybe_confirm_destructive_slash/  s     4  	((*C0:30E,4I)T*#'	6QSW(X#Y    ?"225<<@	c 	> !	h JJ 	 00" 1 
 
 	
_  		 #V
sH   CAC 'C3C4ACCC	CCCCCc                n  K   ddl m} |j                  }| j                  |      }t	        | dd      }	|	ddl}
|
j                  d      }	|	| _        t        |	       }|j                  ||||       | j                  j                  |j                        }| j                  || j                  |            }d}|;	 |j                  |j                   |||||       d{   }|rt	        |dd      rd	}|ry|S 7 # t"        $ r,}t$        j'                  d
||j                  |       Y d}~7d}~ww xY ww)aR  Ask the user to confirm an expensive slash command.

        ``handler`` is an async callable ``handler(choice: str) -> str``
        where ``choice`` is ``"once"``, ``"always"``, or ``"cancel"``.
        The handler runs on the event loop when the user responds; its
        return value is sent back as a gateway message.

        Returns a short acknowledgment string to send immediately (before
        the user's response).  If buttons rendered successfully the ack
        is ``None`` (buttons are self-explanatory); if we fell back to
        text the message itself IS the ack.
        r   r  ry  Nr   F)r  r,  rm  rh  r  r  r  Tz*send_slash_confirm failed for %s on %s: %s)r  r  r  r  r&   rw  rx  ry  nextregisterrE  rB   r   r  r'  send_slash_confirmr  r   r=  rM  )r  rb  r6  r,  rm  r  r\  r  rh  counterr  r  rk  r  used_buttonsbutton_resultrE  s                    r#   r  z$GatewayRunner._request_slash_confirmq/  sD    * 	>226:
 $ 8$??* &&q)G*1D'W
 	##KWgN--##FOO433FD<X<XY^<_`&-&@&@"NN# +)% 'A ' ! !W]Iu%M#'L )!  @V__c sB   B9D5<$C=  C;!C= 6D5;C= =	D2"D-(D5-D22D5c                 h    	 ddl m}  |       }t        |t              r|S i S # t        $ r i cY S w xY w)zRead the user's raw config.yaml (cached) for gate lookups.

        Used by slash-confirm gates that must reflect on-disk state changes
        (e.g. a prior "Always Approve" click) without a gateway restart.
        r   r2  )r   r3  r2   rh   r   )r  r3  r   s      r#   r  zGatewayRunner._read_user_config/  s9    	5-C$S$/37R7 	I	s   # # 11r  c                     t        |dd      }|yd|i}t        |dd      t        j                  k(  r6t        |dd      dk(  r&d|d<   |xs t        |dd      }|t        |      |d	<   |S )
z@Build the metadata dict platforms need for thread-aware replies.r  Nr   r  r  T telegram_dm_topic_reply_fallbackr  telegram_reply_to_message_id)r&   r  r	  r*   )r  r  r  r  r  anchors         r#   r  z)GatewayRunner._thread_metadata_for_source/  s     FK6	$/#;FJ-1B1BBT2d:;?H78(OGFL$,OF!;>v;78r%   c                     t        |       S )zBReturn the platform-specific reply anchor for GatewayRunner sends.)r'  )rb  s    r#   r'  z%GatewayRunner._reply_anchor_for_event/  s     'u--r%   c                   K   |j                   }| j                  |      }ddlm}m}  ||      s?|| j
                  v r&| j
                  j                  |       t        d      S t        d      S |j                         j                         j                         j                         }d|v }|D cg c]
  }|dk7  s	| }	}t        d |	D              rd}
nt        d |	D              rd	}
nd
}
 |||
|      }|st        d      S | j                  j                  |j                        }|r|j!                  |j"                         t$        j'                  d||
       |dkD  rdnd}t        d|
 d| |      S c c}w w)u  Handle /approve command — unblock waiting agent thread(s).

        The agent thread(s) are blocked inside tools/approval.py waiting for
        the user to respond.  This handler signals the event so the agent
        resumes and the terminal_tool executes the command inline — the same
        flow as the CLI's synchronous input() approval.

        Supports multiple concurrent approvals (parallel subagents,
        execute_code).  ``/approve`` resolves the oldest pending command;
        ``/approve all`` resolves every pending command at once.

        Usage:
            /approve              — approve oldest pending command once
            /approve all          — approve ALL pending commands at once
            /approve session      — approve oldest + remember for session
            /approve all session  — approve all + remember for session
            /approve always       — approve oldest + remember permanently
            /approve all always   — approve all + remember permanently
        r   resolve_gateway_approvalr  zgateway.approval_expiredzgateway.approve.no_pendingr  c              3   $   K   | ]  }|d v  
 yw)>   r  	permanentpermanentlyNr   r  r  s     r#   r  z8GatewayRunner._handle_approve_command.<locals>.<genexpr>0  s     Nqq::Nr  r  c              3   $   K   | ]  }|d v  
 yw)>   sesrN  Nr   r  s     r#   r  z8GatewayRunner._handle_approve_command.<locals>.<genexpr>
0  s     <Q((<r  rN  r  resolve_allz7User approved %d dangerous command(s) via /approve (%s)r   pluralsingularzgateway.approve.r  r)	  )r  r  r  r  r  rt  r-  r   r  r7   rq  rp  r  rE  rB   r   resume_typing_for_chatr  r=  rL  )r  rb  r  rh  r  r  r7  r  r  	remainingr  rx  r  r  s                 r#   r  z%GatewayRunner._handle_approve_command/  sm    ( 226:	
 %[1d555''++K8344122 %%'--/557==?tm $31U
Q3	3NINNF<)<<FF(f+V122 ==$$V__5++FNN;MuV\]"QYJ#F81VH5UCC) 4s   B/F 1
E;<E; C F c                 \  K   |j                   }| j                  |      }ddlm}m}  ||      s?|| j
                  v r&| j
                  j                  |       t        d      S t        d      S |j                         j                         j                         }d|v } ||d|      }|st        d      S | j                  j                  |j                        }	|	r|	j                  |j                         t         j#                  d|       |d	kD  rt        d
|      S t        d      S w)u  Handle /deny command — reject pending dangerous command(s).

        Signals blocked agent thread(s) with a 'deny' result so they receive
        a definitive BLOCKED message, same as the CLI deny flow.

        ``/deny`` denies the oldest; ``/deny all`` denies everything.
        r   r  zgateway.deny.stalezgateway.deny.no_pendingr  r  r  z-User denied %d dangerous command(s) via /denyr   zgateway.deny.denied_pluralr)	  zgateway.deny.denied_singular)r  r  r  r  r  rt  r-  r   r  r7   rq  rE  rB   r   r  r  r=  rL  )
r  rb  r  rh  r  r  r7  r  rx  r  s
             r#   r  z"GatewayRunner._handle_deny_command0  s     226:	
 %[1d555''++K8-...//%%'--/557tm(f+V.// ==$$V__5++FNN;CUK191??/00s   D*D,c                    	
K   ddl }ddlmm	m
mmm  |j                         }	
fd}|j                  d|       d{   S 7 w)u6  Handle /debug — upload debug report (summary only) and return paste URLs.

        Gateway uploads ONLY the summary report (system info + log tails),
        NOT full log files, to protect conversation privacy.  Users who need
        full log uploads should use ``hermes debug share`` from the CLI.
        r   N)_capture_dumpcollect_debug_reportupload_to_pastebin_schedule_auto_delete_GATEWAY_PRIVACY_NOTICE!_best_effort_sweep_expired_pastesc                  ^    	         
       }  d|       }i }	  |      |d<    t        |j                                      dt        d      dg}t	        d |D              }|j                         D ]!  \  }}|j                  d	|d
| dd|        # |j                  d       |j                  t        d             |j                  t        d             |j                  t        d             dj                  |      S # t         $ r}t        d|      cY d }~S d }~ww xY w)Nr  )	log_lines	dump_textReportzgateway.debug.upload_failedr  r?   zgateway.debug.headerc              3   2   K   | ]  }t        |        y wr  r  )r  rC  s     r#   r  zSGatewayRunner._handle_debug_command.<locals>._collect_and_upload.<locals>.<genexpr>k0  s     3c!f3r  r  <z`  zgateway.debug.auto_deletezgateway.debug.full_logs_hintzgateway.debug.share_hintr[  )r   r   rA  r*  r  r  r`  ra  )r  r  urlsrE  r  label_widthrW  re  r  r  r  r  r  r  s           r#   _collect_and_uploadz@GatewayRunner._handle_debug_command.<locals>._collect_and_upload\0  s"   -/%I)C9MFDC!3F!;X
 "$t{{}"56,b!4J2KRPE3d33K"jjl A
sqqPo 6c#?@A LLLL678LL9:;LL56799U##  C6cBBCs   D 	D,D'!D,'D,)
r^  hermes_cli.debugr  r  r  r  r  r  r   rE  )r  rb  r^  r  r  r  r  r  r  r  r  s        @@@@@@r#   r6  z#GatewayRunner._handle_debug_commandK0  sN      		
 	
 (w'')	$ 	$2 ))$0CDDDDs   AAAAc           
        K   ddl }ddl}ddl}ddlm} ddlm}m} |j                  j                  }| j                  }	||	vr<	 ddl
m}
 |
j                  |j                        }|r|j                  st        d      S 	  |       rd |d       S t#        t$              j&                  j&                  j)                         }|d	z  }|j+                         st        d
      S t-               }|st        d      S t.        dz  }t.        dz  }t.        dz  }| j1                  |j                        }|j                  j                  j                  |j                  j2                  |j                  j4                  | |j6                         j9                         d}|j                  j:                  r|j                  j:                  |d<   |j=                  d      }|j?                   |j@                  |             |jC                  |       |jE                  d       	 tF        j                  dk(  rddl$}ddl%m&} |jO                  d      jQ                         } |jR                  tF        jT                  d|tW        |      tW        |      g|ddf|jX                  |jX                  d |        ndj[                  d |D              }d| dt]        j^                  tW        |             dt]        j^                  tW        |             }|ja                  d       }|r.|jS                  |d!d|g|jX                  |jX                  d"       n,|jS                  d!d|g|jX                  |jX                  d"       | jc                          t        d%      S # t         $ r t        d      cY S w xY w# t         $ r;}|jE                  d       |jE                  d       t        d#|$      cY d}~S d}~ww xY ww)&us  Handle /update command — update Hermes Agent to the latest version.

        Spawns ``hermes update`` in a detached session (via ``setsid``) so it
        survives the gateway restart that ``hermes update`` may trigger. Marker
        files are written so either the current gateway process or the next one
        can notify the user when the update finishes.
        r   Nr   )
is_managedformat_managed_messager  z%gateway.update.platform_not_messagingu   ✗ zupdate Hermes Agentr  zgateway.update.not_git_repoz#gateway.update.hermes_cmd_not_foundr  .update_output.txt.update_exit_code)r   r  r  rh  r3   r  r  Tr=  rI  rJ  aK  
                    import os, subprocess, sys
                    output_path = sys.argv[1]
                    exit_code_path = sys.argv[2]
                    cmd = sys.argv[3:]
                    env = dict(os.environ)
                    env["PYTHONUNBUFFERED"] = "1"
                    with open(output_path, "wb") as f:
                        proc = subprocess.Popen(cmd, stdout=f, stderr=subprocess.STDOUT, env=env)
                        rc = proc.wait()
                    with open(exit_code_path, "w") as f:
                        f.write(str(rc))
                    rL  r  z	--gatewayrM  ro  c              3   F   K   | ]  }t        j                  |        y wr  rQ  r  s     r#   r  z7GatewayRunner._handle_update_command.<locals>.<genexpr>0  s     )S%++d*;)SrS  zPYTHONUNBUFFERED=1 z update --gateway > z* 2>&1; status=$?; printf '%s' "$status" > rT  rU  rV  zgateway.update.start_failedr  zgateway.update.starting)2r  r  rX  r   r   r  r   r  r   _UPDATE_ALLOWED_PLATFORMSr  r  rB   r   allow_update_commandr   r   r   r   r  r   rr   r  r   r  r  r  rK   rU  r  r  r  r  r:   rB  r  rZ  r[  rK  r\  r7   r]  r  r*   r^  ra  r  rR  r  r  )r  rb  r  r  rX  r   r  r   r   _allowedr  r_   project_rootgit_dirr_  pending_pathrR
  exit_code_pathrh  r'  _tmp_pendingrZ  rK  helperhermes_cmd_str
update_cmdre  r  s                               r#   r#  z$GatewayRunner._handle_update_commandw0  s     	%H <<((118#BG)--hnn=E$>$>DEE %?
 <01FGHIIH~,,33;;='~~233(*
:;;#&<<"%99%(;;225<<@--33||++||++&%113
 <<!!#(<<#9#9GK #//7


7 34\*.4;	=||w&U " %'  !
  fK(#n*= $ &. 0;	 &--%--	 23	 "%)S
)S!S).)9 :++c+&678 9<<AKKNH[<\;]_ 
 $\\(3
$$#VT:>)11)11*.	 %  $$z2)11)11*.	 %  	002*++q  B@AABd  	=40!!T!22!<<	=s\   AO:9N =F!O:EN3 >O:N0-O:/N00O:3	O7<0O2,O7-O:2O77O:c                     t        | dd      }|r|j                         sy	 t        j                  | j	                               | _        y# t        $ r t        j                  d       Y yw xY w)z;Ensure a background task is watching for update completion._update_notification_taskNz;Skipping update notification watcher: no running event loop)	r&   r  r^  rr  _watch_update_progressr  r@  r=  rM  )r  existing_tasks     r#   r  z1GatewayRunner._schedule_update_notification_watch	1  sg    &A4H!3!3!5	X-4-@-@++-.D*  	XLLVW	Xs   (A A,+A,poll_intervalstream_intervalc                    !"#K   t         dz  }t         dz  }t         dz  }t         dz  }t         dz  }t        j                         ""j                         |z   }	dd d}
d#||fD ]  }|j	                         s	 t        j                  |j                               }|j                  d      }|j                  d       |j                  d	      }
|j                  d
      }|rd
|ind#|r1 r/t        |      }| j                  j                  |      |
s| d  }
 n r st        j                  d       |j	                         s|j	                         r"j                         |	k  rz|j	                         r| j                          d{    yt        j                  |       d{    |j	                         s|j	                         r"j                         |	k  rz|j	                         s|j	                         r9|j	                         s)|j!                  d       | j                          d{    ydt"        dt"        fdd}"j                         !dd, !"#fd}"j                         |	k  r	|j	                         r7|j	                         r2	 |j                         }t%        |      |kD  r||d z  t%        |      } |        d{    	 |j                         j)                         xs d}t+        |      }|dk(  rj-                   d#       d{    n+j-                   dj/                  |      #       d{    t        j1                  d||
       |||||fD ]  }|j3                  d        t         dz  j3                  d       | j4                  j7                  |
d       y|j	                         r2	 |j                         }t%        |      |kD  r||d z  t%        |      }j)                         r%"j                         !z
  |k\  r |        d{    |j	                         r|
r| j4                  j                  |
      s	 t        j                  |j                               }|j                  dd      }|j                  dd      }|r |        d{    d}t9        t;              d d      !	 j=                   |||
#!       d{    d}|s,|rd#| d$nd}j-                   d%| | d&#       d{    d| j4                  |
<   t        j1                  d'|
|dd(        t        j                  |       d{    "j                         |	k  r	|j	                         st        j                  d*|       |j!                  d        |        d{    	 j-                   d+#       d{    |||||fD ]  }|j3                  d        t         dz  j3                  d       | j4                  j7                  |
d       yy# t        $ r Y w xY w7 7 7 !# t&        $ r Y w xY w7 7 A7 # t        $ r!}t        j                  d|       Y d}~$d}~ww xY w# t&        $ r Y w xY w7 v7 7 # t        $ r!}t        j?                  d"|       Y d}~d}~ww xY w7 # t
        j@                  t&        f$ r!}t        j?                  d)|       Y d}~d}~ww xY w7 7 _7 E# t        $ r Y Ow xY ww)-a  Watch ``hermes update --gateway``, streaming output + forwarding prompts.

        Polls ``.update_output.txt`` for new content and sends chunks to the
        user periodically.  Detects ``.update_prompt.json`` (written by the
        update process when it needs user input) and forwards the prompt to
        the messenger.  The user's next message is intercepted by
        ``_handle_message`` and written to ``.update_response``.
        r  r  r  r  r  Nr   r  rh  r  rz  zOUpdate watcher: cannot resolve adapter/chat_id, falling back to completion-only124r   r   c                 0    t        j                  dd|       S )Nz\x1b\[[0-9;]*[A-Za-z]r?   )r(   r,   r  s    r#   _strip_ansiz9GatewayRunner._watch_update_progress.<locals>._strip_ansiR1  s    662B==r%   r   r?   c                    K   j                         sdy       j                         } dj                         
| syd}t        dt        |       |      D cg c]
  }| |||z     }}|D ]#  }	 j	                  	d| d       d{    % yc c}w 7 # t
        $ r }t        j                  d|       Y d}~Rd}~ww xY ww)	z!Send buffered output to the user.r?   N  r   z```

```r  zUpdate stream send failed: %s)r7   rN   rR  r]  r  r   r=  rM  )clean	max_chunkrd  chunkschunkr  r  rk  bufferr  last_stream_timer  r  s         r#   _flush_bufferz;GatewayRunner._watch_update_progress.<locals>._flush_bufferY1  s      <<>'--/EF#yy{I6;As5z96UVeAa)m,VFV EE!,,w%we0Dx,XXXE W Y  ELL!@!DDEsN   AC	B+C	3BBBC	B	C&C<C	CC	r   u   ✅ Hermes update finished.r  u(   ❌ Hermes update failed (exit code {}).z&Update finished (exit=%s), notified %sz$Update final notification failed: %sTr=  r  r  rH   Fsend_update_prompt)r  r  rH   rh  r  z%Button-based update prompt failed: %sz (default: r  u"   ⚕ **Update needs your input:**

zG

Reply `/approve` (yes) or `/deny` (no), or type your answer directly.z!Forwarded update prompt to %s: %sr  z Failed to read update prompt: %sz$Update watcher timed out after %.0fsu-   ❌ Hermes update timed out after 30 minutes.rq  )!r   r^  r   rN   rr   r  r  r  rB   r  rE  r   r=  r>  r  r  r  r*   r]  r  r7   r5   r  formatrL  rB  rv  r-  r&   r  r#  rM  r  )$r  r  r  r   r	  claimed_pathrR
  r
  rU  r	  rh  rq   r'  r  r  r   
bytes_sentr"  rZ   exit_code_rawr  r  r  prompt_dataprompt_textrH   sent_buttonsbtn_errdefault_hintr  rk  r   r  r!  r  r  s$                                @@@@@@@r#   r  z$GatewayRunner._watch_update_progress1  sH     $&<<#&DD"%99%(;;"%::'')99;( !<0 	D{{}"jj)9:G#*;;z#:L%kk)4G")++m"<K 'K 8I;DY7$H##+L#9"&--"3"3H"=*-9N!G9*EK	& gNNlm&&(L,?,?,Atyy{U]G]!((*88:::mmM222	  &&(L,?,?,Atyy{U]G]
 ##%)<)<)>H]H]H_))%044666	>c 	>c 	> 
99;	E 	E* iikH$$$&%%'"-"7"7"9w<*4"gjk&::F),WJ $o%%N$2$<$<$>$D$D$F$M#M #M 2I A~%ll74Q\dleee%ll#FMMiX%- +   
 KK H)U`a
 'k(+7 .AHHH-.  22::d:K++//TB !!#)335G7|j0'*+"66%(\

 ||~499;1A#Ao"U#o%% ""$ 77;;KH)H"&**[-B-B-D"EK"-//(B"?K)ooi<G" ,o--',"4=2FMY
_&-&@&@,3+6,30;-5 'A '" !" !" 04  ,GN[	+CTVL"),, '"F#.-~ >@!A *2 #/ #   DH33K@$GVabeceVfg --...K iikH$P $$&NNA7K%%e,/!!llC% #    #L+$k3 *D)* ..66$6G''++K>! 'i !  ;2 7R # % f ! NNN#I1MMN$  
 & .
!" $- _ &-TV] ^ ^_  ,,g6 HLL!CQGGH / "
  s  A4]?B"Y
!A3]Y]2Y37]+A]?Y  A*]+1Y# 
]&Y3'],A	Y< 5Y66+Y< !Y9"Y< =A$]"1Z) 0]Z93]8A[2 Z<[2 )[ Z?[ 	)[2 2[/3-[2  ]8\/9]A]\2]\8 0\51\8 5A]
	Y]Y]] ]#	Y0,]/Y00]6Y< 9Y< <	Z&Z!]!Z&&])	Z62]5Z66]<[2 ?[ 	[,['![2 '[,,[2 2\,\'!]'\,,]2]5\8 8	]]]]c                   K   t         dz  }t         dz  }t         dz  }t         dz  }|j                         s|j                         syd}|}	 |j                         r	 |j                  |       n]|j                         sM	 |rI|j	                  d       |j	                  d       |j	                  d       |j	                  d       yyt        j                  |j                               }|j                  d      }|j                  d	      }	|j                  d
      }
|j                         swt        j                  d       d}|}|j                  |       	 |rI|j	                  d       |j	                  d       |j	                  d       |j	                  d       yy|j                         j                         xs d}t        |      }d}|j                         r|j                         }t        |      }| j                  j                  |      }|r|	r|
rd
|
ind}t        j                   dd|      j                         }|r)t#        |      dkD  rd|dd z   }|dk(  rd| d}nd| d}n
|dk(  rd}nd}|j%                  |	||       d{    t        j                  d||	|       |rH|j	                  d       |j	                  d       |j	                  d       |j	                  d       y# t        $ ra |j                         sMY |rI|j	                  d       |j	                  d       |j	                  d       |j	                  d       yyY w xY w7 # t&        $ r }t        j)                  d|       Y d}~d}~ww xY w# |rI|j	                  d       |j	                  d       |j	                  d       |j	                  d       w w xY ww)a  If an update finished, notify the user.

        Returns False when the update is still running so a caller can retry
        later. Returns True after a definitive send/skip decision.

        This is the legacy notification path used when the streaming watcher
        cannot resolve the adapter (e.g. after a gateway restart where the
        platform hasn't reconnected yet).
        r  r  r  r  FTr=  r   r  r  z2Update notification deferred: update still runningr   r?   Nz\x1b\[[0-9;]*mr  r  iTr   u!   ✅ Hermes update finished.

```
r  u   ❌ Hermes update failed.

```
u(   ✅ Hermes update finished successfully.u]   ❌ Hermes update failed. Check the gateway logs or run `hermes update` manually for details.r  z0Sent post-update notification to %s:%s (exit=%s)z#Post-update notification failed: %s)r   rr   r:   r  rB  r  r  r  rB   r=  rL  r7   r5   r  rE  r(   r,   r]  r  r   r>  )r  r	  r%  rR
  r
  cleanupactive_pending_pathr'  r  r  r  r'  r  r  r   rk  r  r[   r  s                      r#   r  z'GatewayRunner._send_update_notification1  s     $&<<#&DD"%99%(;;""$\-@-@-B*?	7""$$ ((6 "((*f #**d*;##t#4""d"3%%%6	 c jj!7!7!9:G";;z2Lkk),GK0I!((*PQ&2#$$\2N #**d*;##t#4""d"3%%%6	 K +446<<>E#MM*I F!!#$..0  -Hmm''1G77@K3d 12v>DDF6{T)!&!7 A~ DVHER B6(%P!^DCyCll7C(lCCCF 	 #**d*;##t#4""d"3%%%6{ ) $'..0#j #**d*;##t#4""d"3%%%6	 m 1$Z D  	ENN@!DD	E #**d*;##t#4""d"3%%%6	 s   A
O/M3 L /M3 AO/BM3 AO/*C2M3 M1M3 9AO/M.M3 AO/*M3 -M..M3 3	N<NN NN AO,,O/c           
        K   t         dz  }|j                         sy	 t        j                  |j	                               }|j                  d      }|j                  d      }|j                  d      }|r|s	 |j                  d       yt        |      }| j                  j                  |      }|s*t        j                  d|       	 |j                  d       y| j                  j                  j                  |      }|6|j                  s*t        j                  d	|       	 |j                  d       y|rd|ind}	|j                  t!        |      d
|	       d{   }
|
Ft#        |
dd      du r7t        j%                  d||t#        |
dd             	 |j                  d       yt        j                  d||       t!        |      t!        |      |rt!        |      ndf|j                  d       S 7 # t&        $ r2}t        j%                  d|       Y d}~|j                  d       yd}~ww xY w# |j                  d       w xY ww)zANotify the chat that initiated /restart that the gateway is back.r   Nr   r  r  Tr=  z6Restart notification skipped: %s adapter not connectedzJRestart notification suppressed: %s has gateway_restart_notification=falseu;   ♻ Gateway restarted successfully. Your session continues.r  r  Fz3Restart notification to %s:%s was not delivered: %sr  r  z"Sent restart notification to %s:%szRestart notification failed: %s)r   rr   r  r  r  rB   rB  r  rE  r=  rM  r  rP  r  rL  r  r*   r&   r>  r   )r  notify_pathr  r  r  r  r   rk  r!  r  r  r  s               r#   r  z(GatewayRunner._send_restart_notification?2  sB    "%;;!!#7	0::k3356D88J/Lhhy)G-Iw` $/]  -Hmm''1GL  N $/K  ;;0044X>L'0Y0Y`  > $/; 4=Y/$H"<<GM! (  F !gfi&F%&OI FG-JK	  $/ KK4
 |$c'liC	NUYY
 $/90  	NN<a@$/		 $/s   IAG? 9I>G? IA	G? (I;(G? #G=$8G? I0:G? *I=G? ?	H:H5H= "I5H::H= =IIr  r  c                  K   t               }|xs
 t               }d}| j                  j                         D ]  \  }}| j                  j	                  |      }|r|j
                  s1| j                  j                  j                  |      }|-|j                  s!t        j                  d|j                         |j                  t        |j
                        |j                  rt        |j                        ndf}	|	|v s|	|v r	 |j                  rd|j                  ind}
|
r0|j                  t        |j
                        ||
       d{   }n-|j                  t        |j
                        |       d{   }|Ht        |dd      du r9t        j!                  d	|j                  |j
                  t        |d
d             |j#                  |	       t        j                  d|j                  |j
                          |S 7 7 # t$        $ r7}t        j!                  d	|j                  |j
                  |       Y d}~d}~ww xY ww)a3  Notify configured home channels that the gateway is back online.

        The notification is best-effort and sent once per connected platform
        home channel. ``skip_targets`` lets startup avoid duplicate messages
        when a more specific restart notification is queued for the same chat.
        u3   ♻️ Gateway online — Hermes is back and ready.NzWHome-channel startup notification suppressed: %s has gateway_restart_notification=falser  r  r  TFz6Home-channel startup notification failed for %s:%s: %sr  r  z/Sent home-channel startup notification to %s:%s)r  rE  r  r  r  r  rP  rB   r  r=  rL  r   r*   r  r  r&   r>  r  r   )r  r  	deliveredskippedrm  r   rk  r"  r!  r  r  r  rE  s                r#   r  z6GatewayRunner._send_home_channel_startup_notifications~2  s     :=	'#%G!%!4!4!6 ,	Hg;;//9Dt||;;0044X>L'0Y0YmNN nnc$,,&7PTP^P^T^^9LdhiF Fi$7<@NNK8PT#*<<DLL0A7U]<#^^F#*<<DLL0A7#KKF%'&)T*Je*SNNP 1NO	 f%ENNLLC,	\ 5 _K   LNNLL	 sb   DI	AHH-H?H AHI<H
IHH	I,IIIIr  c           
         ddl m}  ||j                  j                  j                  |j                  j
                  |j                  j                  xs d|j                  j                  rt        |j                  j                        nd|j                  j                  rt        |j                  j                        nd|j                  j                  rt        |j                  j                        nd|j                        S )a@  Set session context variables for the current async task.

        Uses ``contextvars`` instead of ``os.environ`` so that concurrent
        gateway messages cannot overwrite each other's session state.

        Returns a list of reset tokens; pass them to ``_clear_session_env``
        in a ``finally`` block.
        r   )set_session_varsr?   )r   r  r/  r  r  r  rh  )gateway.session_contextr6  r  r   r   r  r/  r  r*   r  r  rh  )r  r  r6  s      r#   r:  zGatewayRunner._set_session_env2  s     	=^^,,22NN**nn..4"7>~~7O7Oc'..223UW3:>>3I3IC../r7>~~7O7Oc'..223UW++
 	
r%   r  c                      ddl m}  ||       y)z>Restore session context variables to their pre-handler values.r   )clear_session_varsN)r7  r9  )r  r  r9  s      r#   rL  z GatewayRunner._clear_session_env2  s    >6"r%   c                    K   t        j                         }t               } |j                  d|j                  |g|  d{   S 7 w)zJRun blocking work in the thread pool while preserving session contextvars.N)r^  r   r   rE  run)r  funcr7  r  r	  s        r#   r
  z+GatewayRunner._run_in_executor_with_context2  sA     '')n)T))$EEEEEs   AA
AA
c                     	 ddl m} ddlm}m} ddlm}  |       } |       } |       } ||||      S # t        $ r }t        j                  d|       Y d}~yd}~ww xY w)a  Resolve the image-input routing for the currently active model.

        Returns ``"native"`` (attach pixels on the user turn) or ``"text"``
        (pre-analyze with vision_analyze and prepend the description). See
        agent/image_routing.py for the full decision table.

        The active provider/model are read from config.yaml so the decision
        tracks ``/model`` switches automatically on the next message.
        r   )decide_image_input_mode)_read_main_model_read_main_providerr2  u;   image_routing: decision failed, falling back to text — %sNr   )
agent.image_routingr>  r4  r?  r@  r   r3  r   r=  rM  )	r  r>  r?  r@  r3  r   r   r   rE  s	            r#   r  z&GatewayRunner._decide_image_input_mode2  sY    	CT5-C*,H$&E*8UC@@ 	LLVX[\	s   25 	AAA	user_textr  c                 ,  K   ddl m} ddlm} d}g }|D ]  }	 t        j                  d|        |||       d{   }t        j                  |      }	|	j                  d      r3|	j                  d	d
      }
 ||
      }
|j                  d|
 d| d       n|j                  d| d        |rdj                  |      }|r| d| S |S |S 7 # t        $ r5}t        j                  d|       |j                  d| d       Y d}~d}~ww xY ww)a  
        Auto-analyze user-attached images with the vision tool and prepend
        the descriptions to the message text.

        Each image is analyzed with a general-purpose prompt.  The resulting
        description *and* the local cache path are injected so the model can:
          1. Immediately understand what the user sent (no extra tool call).
          2. Re-examine the image with vision_analyze if it needs more detail.

        Args:
            user_text:   The user's original caption / message text.
            image_paths: List of local file paths to cached images.

        Returns:
            The enriched message string with vision descriptions prepended.
        r   )vision_analyze_tool)sanitize_contextzDescribe everything visible in this image in thorough detail. Include any text, code, data, objects, people, layout, colors, and any other notable visual information.zAuto-analyzing user image: %s)r
  user_promptNr  analysisr?   z0[The user sent an image~ Here's what I can see:
zA]
[If you need a closer look, use vision_analyze with image_url: z ~]z[The user sent an image but I couldn't quite see it this time (>_<) You can try looking at it yourself with vision_analyze using image_url: rY  zVision auto-analysis error: %sz[The user sent an image but something went wrong when I tried to look at it~ You can try examining it yourself with vision_analyze using image_url: r  )tools.vision_toolsrD  agent.memory_managerrE  r=  rM  r  r  rB   r`  r   r  ra  )r  rB  r  rD  rE  analysis_promptenriched_partsrq   rb
  r  rZ	  r  r  s                r#   r  z)GatewayRunner._enrich_message_with_vision2  sU    * 	;98 	  	D<dC$7" /%  K0::i("(**Z"<K"2;"?K"))KK= Y&&*V30 #))@@DvQH#	> [[0F i[11MC&  =qA%%<<@6D s?   D#CCA2C/"DC	D+DDDDr  c                   K   t        | j                  dd      s'd}| j                         r|dz  }|dz  }|r| d| S |S ddlm} g }|D ]  }	 t
        j                  d	|       t        j                  ||       d
{   }|d   r|d   }|j                  d| d       nj|j                  dd      }	d|	v s|	j                  d      r.d}
| j                         r|
dz  }
|
dz  }
|j                  |
       n|j                  d|	 d        |r5dj                  |      }d}|r|j                         |k(  r|S |r| d| S |S |S 7 # t        $ r2}t
        j                  d|       |j                  d       Y d
}~5d
}~ww xY ww)a  
        Auto-transcribe user voice/audio messages using the configured STT provider
        and prepend the transcript to the message text.

        Args:
            user_text:   The user's original caption / message text.
            audio_paths: List of local file paths to cached audio files.

        Returns:
            The enriched message string with transcriptions prepended.
        stt_enabledTzI[The user sent voice message(s), but transcription is disabled in config.z{ You have a skill called hermes-agent-setup that can help users configure Hermes features including voice, tools, and more.rY  r  r   )transcribe_audiozTranscribing user voice: %sNr  r+
  z8[The user sent a voice message~ Here's what they said: "z"]r  r  r  z8Neither VOICE_TOOLS_OPENAI_KEY nor OPENAI_API_KEY is setu   [The user sent a voice message but I can't listen to it right now — no STT provider is configured. A direct message has already been sent to the user with setup instructions.zC[The user sent a voice message but I had trouble transcribing it~ (z)]zTranscription error: %sze[The user sent a voice message but something went wrong when I tried to listen to it~ Let them know!]z.(The user sent a message with no text content))r&   r  r  tools.transcription_toolsrN  r=  rM  r^  r  r`  rB   r^  r   r  ra  r7   )r  rB  r  disabled_noterN  rK  rq   r  r+
  r  _no_stt_noter  r  _placeholders                 r#   r  z0GatewayRunner._enrich_message_with_transcription83  s      t{{M48gM$$&X S M'YK88  > (	D':DA&001A4HH)$!'!5J"))44><rC
 #JJw@E)U2 ++,fg7 %  002(!DL
 %+&--l;&--116r;?(	T [[0F LLY__.,> i[11Mc I@  6:%%D sC   AF/E
>E?BE
;FE

	F'F :F FFr  c                    ddl m} t        |j                  d      xs d      j	                         }d}d}d}|r	 | j
                  j                          | j
                  j                  j                  |      }|rt        |dd      r|j                  S | j                  |      }	|	|	S t        |      }
|
r|
d   }|
d	   }|
d
   }t        |j                  d      xs |xs d      j	                         j                         }t        |j                  d	      xs |xs d      j	                         j                         }t        |j                  d
      xs |xs d      j	                         }|r|r|sy	 t!        |      }|j"                  t$        vr.	 ddlm} |j+                  |j"                        st-        |      	  ||||t        |j                  d      xs d      j	                         xs dt        |j                  d      xs d      j	                         xs dt        |j                  d      xs d      j	                         xs d      S # t        $ r"}t        j                  d||       Y d}~d}~ww xY w# t        $ r t-        |      w xY w# t        $ r t        j/                  d|       Y yw xY w)a  Resolve the canonical source for a synthetic background-process event.

        Prefer the persisted session-store origin for the event's session key.
        Falling back to the currently active foreground event is what causes
        cross-topic bleed, so don't do that.
        r   )r  rh  r?   r  Nz>Synthetic process-event session-store lookup failed for %s: %sr   r  r  r  z9Synthetic process event has invalid platform metadata: %rr  r  r  )r   r  r  r  r  r  )r2  r  r*   rB   r7   rZ  r  r  r&   r  r   r=  rM  r  r  rq  r  r   r  r  r  r9  r8   r>  )r  r  r  rh  derived_platformderived_chat_typederived_chat_idr_   rE  cached_sourcer  ry   r  r  r   r  s                   r#   _build_process_event_sourcez)GatewayRunner._build_process_event_source3  s    	2#''-06B7==?
""113**3377DWUHd; <<' !;;KHM($$(5G#*:#6 $+K$8!"))"4CGGJ/I3CIrJPPRXXZ,G0AGRHNNPVVX	cggi(AOArBHHJIW	.H ~~%==4K,::8>>J(77 K #''+.4"5;;=E	*0b1779AT#''+.4"5;;=E
 	
U  T B ! 4$]334 	NNK 	sB   AI ;J$ ,J J$ 	J	'JJ	J!!J$ $KKr  c                   K   | j                  |      }|s't        j                  d|j                  dd             yt	        |j
                  d      r|j
                  j                  nt        |j
                        }d}| j                  j                         D ]  \  }}|j                  |k(  s|} n |sy	 t        |t        j                  |d      }t        j                  d||j                  |j                         |j!                  |       d{    y7 # t"        $ r }	t        j%                  d	|	       Y d}	~	yd}	~	ww xY ww)
zInject a watch-pattern notification as a synthetic message event.

        Routing must come from the queued watch event itself, not from whatever
        foreground message happened to be active when the queue was drained.
        zCDropping watch notification with no routing metadata for process %sr  r  Nr   Trz  uA   Watch pattern notification — injecting for %s chat=%s thread=%sr/  )rX  r=  r>  rB   r  r   r   r*   rE  r  r%  r&  r  rL  r  r  r  r   r  )
r  r  r  r  ry   rk  r  r  synth_eventr  s
             r#   rS  z(GatewayRunner._inject_watch_notification3  s(     11#6NNUi0 18'1R--X[\b\k\kXlMM'') 	DAqww-'	 	F&(--	K KKS  	 ((555 	FLLA1EE	FsI   B-E
0E
9AD DD E
D 	E'E=E
EE
rb  c           
      P	  K   ddl m} |d   }|d   }|j                  dd      }|j                  dd      }|j                  dd      }|j                  d	d      }|j                  d
d      }	|j                  dd      }
|j                  dd      }| j                         }t        j                  d||||       |dk(  rX|sV	 t        j                  |       d{    |j                  |      }||j                  rn>t        j                  d|       yd}	 t        j                  |       d{    |j                  |      }|nt        |j                        }||kD  }|}|j                  rddl m} |r<|j                  |      s*ddlm} |j                  r ||j                  dd       nd}d| d|j                   d|j                   d| d	}| j!                  ||||||	|
d      }|st        j#                  d|       nd}| j$                  j'                         D ]  \  }}||j(                  k(  s|} n |rp|j*                  rd	 t-        |t.        j0                  |d      }t        j3                  d|||j*                  |j4                         |j7                  |       d{    nB|dv xs |d k(  xr |j                  d!v}|r|j                  r|j                  d"d nd}d#| d$|j                   d%| d}d}| j$                  j'                         D ]  \  }}|j<                  |k(  s|} n |r'|r%	 |rd	|ind}|j?                  |||&       d{    n|r|d(k(  r|s|j                  r|j                  d)d nd}d#| d*| d}d}| j$                  j'                         D ]  \  }}|j<                  |k(  s|} n |r'|r%	 |rd	|ind}|j?                  |||&       d{    t        j                  d+|       y7 97 7 f# t8        $ r!}t        j;                  d|       Y d}~d}~ww xY w7 # t8        $ r!}t        j;                  d'|       Y d}~d}~ww xY w7 # t8        $ r }t        j;                  d'|       Y d}~d}~ww xY ww),u  
        Periodically check a background process and push updates to the user.

        Runs as an asyncio task. Stays silent when nothing changed.
        Auto-removes when the process exits or is killed.

        Notification mode (from ``display.background_process_notifications``):
          - ``all``    — running-output updates + final message
          - ``result`` — final completion message only
          - ``error``  — final message only when exit code != 0
          - ``off``    — no messages at all
        r   r'  r  check_intervalrh  r?   r   r  r  r  r  notify_on_completeFzCProcess watcher started: %s (every %ss, notify=%s, agent_notify=%s)r  TNz"Process watcher ended (silent): %s)
strip_ansii0r  z completed (exit code z).
Command: z	
Output:
rY  )r  rh  r   r  r  r  r  zHDropping completion notification with no routing metadata for process %srz  uU   Process %s finished — injecting agent notification for session %s chat=%s thread=%sz Agent notify injection error: %s>   r  r  r  >   Nr   iz[Background process z finished with exit code z~ Here's the final output:
r  zWatcher delivery error: %sr  iz is still running~ New output:
zProcess watcher ended: %s) rY  r(  rB   r  r=  rM  r^  r  exitedr]  output_bufferis_completion_consumedtools.ansi_stripr^  r  r6  rX  r>  rE  r  r   r  r%  r&  r  rL  r  r  r   r  r   r  )r  rb  r(  r  r  rh  ry   r  r  r  r  agent_notifynotify_moderN  last_output_lencurrent_output_lenhas_new_output	_pr_checkr^  r  r  r  rk  r  r  rZ  r  rk  
new_outputr  	send_metas                                  r#   r  z"GatewayRunner._run_process_watcher3  s      	<\*
+,kk-4J3++i,KKR0	++i,KKR0	{{#7?>>@Z (K	G % mmH---*..z:?gnn	 
 LL=zJ--)))&**:6G!$W%:%:!;//AN0O~~ Q	(H(H(T;HOH]H]:g&;&;EF&CDceD9* F&&-&7&7%8 9$$+OO#4 5$$(6,  "==&0'2$1#*%.#*%.? F "f& "G $ 3 3 5 "1/&'G!" 6>>P*6%/-8-=-='-)-	+K #KK w * + & & 0 0 #*"8"8"EEE 
  #44 W#w.U73D3DI3U  !BIBWBW!6!6uv!>]_J.zl:ST[TeTeSf g55?LC ! #G $ 3 3 5 "177m3&'G!" 7JDMi(@SWI"),,wy,"YYY K5$8 >E=R=RW22459XZ
*:, 7$$.<q2   MM//1 DAqww-/"# wF@I[)$<t	%ll7L9lUUUU \ 	0*=m . *v F( P"LL)KQOOP0 Z( J"LL)EqIIJ( V$ F%A1EEFs   CR&PAR&2P3DR&R&AP 3P4P 8BR&<	R&Q %Q	&Q *A!R&	R&Q: 5Q86Q: :R&R&P 	Q%Q;R&QR&	Q 	Q5Q0*R&0Q55R&8Q: :	R#RR&R##R&))r   r  )r   
max_tokens)r  r  )r  	threshold)r  target_ratio)r  protect_last_n)r   r
  _CACHE_BUSTING_CONFIG_KEYSc                 <   i }t        |t              r|ni }| j                  D ]J  \  }}|j                  |      }t        |t              r|j                  |      || d| <   Ad|| d| <   L 	 ddlm} t        |dd      |d<   |S # t        $ r	 d|d<   Y |S w xY w)al  Pull values that must bust the cached agent.

        Returns a flat dict keyed by 'section.key'.  Missing config keys and
        non-dict sections yield None values, which still contribute to the
        signature (so 'absent' vs 'present-and-null' differ).

        The live tool registry generation is included too.  MCP reloads and
        dynamic MCP tool-list changes mutate the registry without necessarily
        changing config.yaml.  Cached AIAgent instances freeze their tool
        schemas at construction time, so a registry generation change must
        rebuild the agent before the next turn.
        r   Nr   )registry_generationztools.registry_generation)r2   rh   ro  rB   tools.registryrq  r&   r   )clsr(  r  r   sectionr+  section_valrq  s           r#   _extract_cache_busting_configz+GatewayRunner._extract_cache_busting_config4  s     !'T:k:: 	/LGS'''*K+t,*5//#*>wiq&'*.wiq&'	/	4//6xPT/UC+, 
  	4/3C+,
	4s   1B	 	BBrB  r	  ephemeral_prompt
cache_keysc           
         ddl }ddl}t        |j                  dd      xs d      }|r-|j	                  |j                               j                         nd}t        |xs i j                               }	|j                  | ||j                  dd      |j                  dd      |j                  dd      |rt        |      ng |xs d|	gdt        	      }
|j	                  |
j                               j                         dd
 S )u  Compute a stable string key from agent config values.

        When this signature changes between messages, the cached AIAgent is
        discarded and rebuilt.  When it stays the same, the cached agent is
        reused — preserving the frozen system prompt and tool schemas for
        prompt cache hits.

        ``cache_keys`` is an optional flat dict of additional config values
        that should invalidate the cache when they change.  Callers pass
        the output of ``_extract_cache_busting_config(user_config)`` so
        edits to model.context_length / compression.* in config.yaml are
        picked up on the next gateway message without a manual restart.
        r   Nr   r?   r   r   r5  T)	sort_keysrH   r/
  )
hashlibr  r*   rB   sha256encode	hexdigestrO  r  r  )r   rB  r	  rx  ry  r|  _j_api_key_api_key_fingerprint_cache_keys_sortedblobs              r#   _agent_config_signaturez%GatewayRunner._agent_config_signature4  s    * 	# w{{9b17R8PXw~~hoo.?@JJL^`#Z%52$<$<$>?xx$J+J+J+,<'(" !&B"   
  ~~dkkm,668"==r%   c                     | j                   j                  |      }|s||fS |j                  d|      }dD ]  }|j                  |      }||||<    ||fS )a  Apply /model session overrides if present, returning (model, runtime_kwargs).

        The gateway /model command stores per-session overrides in
        ``_session_model_overrides``.  These must take precedence over
        config.yaml defaults so the switched model is actually used for
        subsequent messages.  Fields with ``None`` values are skipped so
        partial overrides don't clobber valid config defaults.
        r   r*  r$  rB   )r  rh  r   r4  r1  r+  vals          r#   r.  z+GatewayRunner._apply_session_model_override4  sq     0044[A.((We,B 	*C,,s#C&)s#	* n$$r%   agent_modelc                 l    | j                   j                  |      }|duxr |j                  d      |k(  S )zGReturn True if *agent_model* matches an active /model session override.Nr   r  )r  rh  r  r1  s       r#   _is_intentional_model_switchz*GatewayRunner._is_intentional_model_switch5  s6    0044[At#LW(=(LLr%   r  c                    |sy|| j                  ||      sy| j                  j                  |d       | j                  j                  |d       t	        | d      r| j
                  j                  |d       y)u  Pop ALL per-running-agent state entries for ``session_key``.

        Replaces ad-hoc ``del self._running_agents[key]`` calls scattered
        across the gateway.  Those sites had drifted: some popped only
        ``_running_agents``; some also ``_running_agents_ts``; only one
        path also cleared ``_busy_ack_ts``.  Each missed entry was a
        small, persistent leak — a (str_key → float) tuple per session
        per gateway lifetime.

        Use this at every site that ends a running turn, regardless of
        cause (normal completion, /stop, /reset, /resume, sentinel
        cleanup, stale-eviction).  Per-session state that PERSISTS
        across turns (``_session_model_overrides``, ``_voice_mode``,
        ``_pending_approvals``, ``_update_prompt_pending``) is NOT
        touched here — those have their own lifecycles.

        When ``run_generation`` is provided, only clear the slot if that
        generation is still current for the session.  This prevents an
        older async run whose generation was bumped by /stop or /new from
        clobbering a newer run's state during its own unwind.  Returns
        True when the slot was cleared, False when an ownership guard
        blocked it.
        FNrh  T)rK  rd  r-  r  r  rh  )r  rh  r  s      r#   r:  z*GatewayRunner._release_running_agent_state5  sv    : %d.J.J/
   d3##K64(!!+t4r%   c                 n   |syt        | dd      }t        |t              r|j                  |d       t        | dd      }t        |t              r|j                  |d       t        | dd      }t        |t              r|j                  |d       	 ddlm} |	 |j                  |       	 ddl
m} 	  ||       y# t        $ r d}Y 2w xY w# t        $ r!}t        j                  d||       Y d}~Gd}~ww xY w# t        $ r Y yw xY w# t        $ r!}t        j                  d	||       Y d}~yd}~ww xY w)
zHClear per-session control state that must not survive a boundary switch.Nr  rt  rv  r   r  z?Failed to clear slash-confirm state for session boundary %s: %sclear_sessionz:Failed to clear approval state for session boundary %s: %s)r&   r2   rh   r-  r  r  r   r  r=  rM  r  r  )r  rh  pending_skills_reload_notespending_approvalsupdate_prompt_pendingr\  r  _clear_approval_sessions           r#   r  z4GatewayRunner._clear_session_boundary_security_state:5  sF   &-0$'
# 148'++K>#D*>E'.!!+t4 '.F M+T2!%%k48	&A )"((5	O	#K0%  	&!%	&
  U   		
  	LLL 	sT   B= C -C; 4D
 =C
C	C8C33C8;	DD
	D4D//D4c                     |sy| j                   j                  d      }|	i }|| _        t        |j                  |d            dz   }|||<   |S )ae  Claim a fresh run generation token for ``session_key``.

        Every top-level gateway turn gets a monotonically increasing token.
        If a later command like /stop or /new invalidates that token while the
        old worker is still unwinding, the late result can be recognized and
        dropped instead of bleeding into the fresh session.
        r   ri  r   )__dict__rB   ri  r5   )r  rh  generationsnext_generations       r#   rE  z+GatewayRunner._begin_session_run_generationi5  s\     mm''(ABK+6D(kook1=>B#2K r%   r  c                \    | j                  |      }|rt        j                  d|||       |S )z7Invalidate any in-flight run token for ``session_key``.u-   Invalidated run generation for %s → %d (%s))rE  r=  rL  )r  rh  rU  r"  s       r#   r  z0GatewayRunner._invalidate_session_run_generation{5  s5    77D
KK?	 r%   r"  c                     |sy| j                   j                  d      xs i }t        |j                  |d            t        |      k(  S )zEReturn True when ``generation`` is still current for ``session_key``.Tri  r   )r  rB   r5   )r  rh  r"  r  s       r#   rK  z%GatewayRunner._is_session_run_current5  sA    mm''(ABHb;??;23s:FFr%   c                     |r|r|y	 t        |di       j                  |      }|t        |dt        |             yy# t        $ r Y yw xY w)zDBind a gateway run generation to the adapter's active-session event.Nr	  r	  )r&   rB   setattrr5   r   )r  rk  rh  r"  interrupt_events        r#   rI  z*GatewayRunner._bind_adapter_run_generation5  s^     kZ-?	%g/A2FJJ;WO*)A3z?S + 		s   4? 	A
AT)release_running_stater  r  r  c                  K   |sy| j                   j                  |      }|r|t        ur|j                  |       | j	                  ||       | j
                  j                  |j                        }|r0t        |d      r$|j                  ||j                         d{    |rt        |d      r|j                  |       | j                  j                  |d       |r| j                  |       yy7 Tw)zFInterrupt the current run and clear queued session state consistently.Nr  interrupt_session_activityrj  )rd  rB   r  r  r  rE  r   r  r  r  rj  re  r-  r:  )r  rh  r  r  r  r  r  rk  s           r#   r  z*GatewayRunner._interrupt_and_clear_session5  s      ,,00=]2II##$45//DW/X--##FOO4ww(DE44[&..QQQww(=>''4"";5 --k: !	 Rs   BC8!C6"AC8c                     t        | dd      }|r(|5  | j                  j                  |d       ddd       yy# 1 sw Y   yxY w)zBRemove a cached agent for a session (called on /new, /model, etc).ro  N)r&   rm  r-  )r  rh  r|  s      r#   r9  z!GatewayRunner._evict_cached_agent5  sL    148 9!!%%k489 9 9 9s	   9Ainterrupt_depthc                 \    |dk(  r t        j                          | _        d| _        d| _        y)u  Reset per-turn state on a cached agent before a new turn starts.

        Both _last_activity_ts and _last_activity_desc are only reset for
        fresh external turns (depth 0); they are semantically paired —
        desc describes the activity *at* ts, so updating one without the
        other would make get_activity_summary() misleading.
        For interrupt-recursive turns both are preserved so the inactivity
        watchdog can accumulate stuck-turn idle time and fire the 30-min
        timeout (#15654).  The depth-0 reset is still needed: a session
        idle for 29 min would otherwise trip the watchdog before the new
        turn makes its first API call (#9051).
        r   zstarting new turn (cached)N)rN   _last_activity_ts_last_activity_desc_api_call_count)r   r  s     r#   _init_cached_agent_for_turnz)GatewayRunner._init_cached_agent_for_turn5  s*     a&*iikE#(DE% !r%   c                     |y	 t        |d      r|j                          y| j                  |       y# t        $ r Y yw xY w)u  Soft cleanup for cache-evicted agents — preserves session tool state.

        Called from _enforce_agent_cache_cap and _sweep_idle_cached_agents.
        Distinct from _cleanup_agent_resources (full teardown) because a
        cache-evicted session may resume at any time — its terminal
        sandbox, browser daemon, and tracked bg processes must outlive
        the Python AIAgent instance so the next agent built for the
        same task_id inherits them.
        Nrelease_clients)r  r  r,  r   )r  r   s     r#   _release_evicted_agent_softz)GatewayRunner._release_evicted_agent_soft5  sJ     =	u/0%%' --e4 		s   4 4 	A A c           
      ^   t        | dd      }|yt        |d      syt        | di       j                         D ch c]  }||t        urt	        |       }}t        dt        |      t        z
        }g }|dkD  rpt        |j                               }|d| D ]O  }|j                  |      }t        |t              r|r|d   nd}	|	t	        |	      |v r=|j                  ||	f       Q |D ]  \  }}
|j                  |d        t        |      t        z
  }|dkD  r%t        j!                  dt        |      t        |       |D ]`  \  }}	t        j#                  d|t        |             |	)t%        j&                  | j(                  |	fdd	|dd
        j+                          b yc c}w )u  Evict oldest cached agents when cache exceeds _AGENT_CACHE_MAX_SIZE.

        Must be called with _agent_cache_lock held.  Resource cleanup
        (memory provider shutdown, tool resource close) is scheduled
        on a daemon thread so the caller doesn't block on slow teardown
        while holding the cache lock.

        Agents currently in _running_agents are SKIPPED — their clients,
        terminal sandboxes, background processes, and child subagents
        are all in active use by the running turn.  Evicting them would
        tear down those resources mid-turn and crash the request.  If
        every candidate in the LRU order is active, we simply leave the
        cache over the cap; it will be re-checked on the next insert.
        rm  Nr  rd  r   uk   Agent cache over cap (%d > %d); %d excess slot(s) held by mid-turn agents — will re-check on next insert.z;Agent cache at cap; evicting LRU session=%s (cache_size=%d)Tzagent-cache-evict-r9  r  r7  daemonrG   )r&   r  r*  r  r   r  r]  _AGENT_CACHE_MAX_SIZErA  r,  rB   r2   r<  r`  r-  r=  r>  rL  rl  Threadr  r  )r  r  r  running_idsexcess
evict_planordered_keysr+  r_   r   r  remaining_over_caps               r#   _enforce_agent_cache_capz&GatewayRunner._enforce_agent_cache_cap5  s    ~t4> v}- T#4b9@@B
}*A!A qE
 
 QF&;;<"$
A:.L#GV, 0

3$.ue$<aD$Ek)A!!3,/0 ! 	"FCJJsD!	" ![+@@!NNDF24F % 	JCKKMS[    ;;-c#2hZ8	
 %'	K
s   F*c                 .   t        | dd      }t        | dd      }||yt        j                         }g }t        | di       j                         D ch c]  }||t        urt	        |       }}|5  t        |j                               D ]_  \  }}t        |t              r|r|d   nd}	|	"t	        |	      |v r0t        |	dd      }
|
@||
z
  t        kD  sM|j                  ||	f       a |D ]  \  }}|j                  |d        	 ddd       |D ]b  \  }}	t        j                  d||t        |	d|      z
         t        j                  | j                   |	fdd	|dd
        j#                          d t%        |      S c c}w # 1 sw Y   xY w)u  Evict cached agents whose AIAgent has been idle > _AGENT_CACHE_IDLE_TTL_SECS.

        Safe to call from the session expiry watcher without holding the
        cache lock — acquires it internally.  Returns the number of entries
        evicted.  Resource cleanup is scheduled on daemon threads.

        Agents currently in _running_agents are SKIPPED for the same reason
        as _enforce_agent_cache_cap: tearing down an active turn's clients
        mid-flight would crash the request.
        rm  Nro  r   rd  r  z3Agent cache idle-TTL evict: session=%s (idle=%.0fs)Tzagent-cache-idle-r9  r  )r&   rN   r*  r  r   rA  r  r2   r<  _AGENT_CACHE_IDLE_TTL_SECSr`  r-  r=  rL  rl  r  r  r  r]  )r  r  r|  rK   to_evictr  r  r+  r_   r   last_activityr  s               r#   rP  z'GatewayRunner._sweep_idle_cached_agents36  s    ~t4148>U]iik " T#4b9@@B
}*A!A qE
 

  	&"6<<>2 
2
U$.ue$<aD=e9+ '/BD I (-'+EEOOS%L1
2 # &Q

3%&	& # 
	JCKKES75*=sCC 77X(Sb
3	
 eg
	 8}=

	& 	&s   F3A&F1FFc                    t        j                  dd      j                         }|r|j                  d      S t	               }|j                  d      xs i j                  dd      j                         }|r|j                  d      S y)zReturn the proxy URL if proxy mode is configured, else None.

        Checks GATEWAY_PROXY_URL env var first (convenient for Docker),
        then ``gateway.proxy_url`` in config.yaml.
        GATEWAY_PROXY_URLr?   r    r(  	proxy_urlN)r@   r<  r7   rB  r  rB   )r  re  r   s      r#   _get_proxy_urlzGatewayRunner._get_proxy_urlh6  st     ii+R0668::c?""$wwy!'R,,["=CCE::c?"r%   r  c	                 l   K   	 ddl m}	m}
  j	                         }|sdg dg dS t        j                  dd      j                         }dt        f fd	}g }|r|j                  d
|d       |D ]@  }|j                  d      }|j                  d      }|dv s*|s-|j                  ||d       B |j                  d|d       ddi}|rd| |d<   |r||d<   d|dd}d}t        t         dd      dd      }|ddlm}  |       }t        |j                        }t!               }ddlm}  |||d      }||j&                  xr |j(                  dk7  n
t        |      } j+                  ||      }|r 	 ddlm}m}  j2                  j                  |j                        }|rt        |dd      } | r|j4                  nd}!d }"|j                  t6        j8                  k(  rd}!d}"|j                  t6        j:                  k(  rt=        t        |d!d"      xs d"      nd"}# ||j>                  |j@                  |!|"|#|j(                  xs d#t        |d$d      xs d%      }$ |||jB                  |$||&      }d}&|r#tK        jL                  |jO                               }& j2                  j                  |j                        }|r&	 |jQ                  |jB                  |(       d{    d}'tS        jR                         }(	  |
dd)*      }) |	|)+      4 d{   }*|*jU                  | d,||-      4 d{   }+|+jV                  d.k7  r|+jY                          d{   },tF        j[                  d/|+jV                  ||,dd0        d1|+jV                   d2|,dd3  g dg dcddd      d{    cddd      d{    |r|j]                          |&r!	 tK        j^                  |&d4+       d{    S S d}-|+jf                  ji                         2 3 d{   }. |       stF        jk                  d5xs d6xs d       dg dg tm        |      |d d7c cddd      d{    cddd      d{    |r|j]                          |&r!	 tK        j^                  |&d4+       d{    S S |.jo                  d8d9:      }/|-|/z  }-d;|-v s|-jq                  d;d<      \  }0}-|0j                         }0|0s-|0js                  d=      r|0d>d }1|1j                         d?k(  r	 tu        jv                  |1      }2|2j                  d@g       }3|3rA|3d   j                  dAi       }4|4j                  dd      }|r|'|z  }'|r|jy                  |       d;|-v rÐ}# t        $ r
 dg dg dcY S w xY w# tD        $ r!}%tF        jI                  d'|%       Y d}%~%Xd}%~%ww xY w7 # tD        $ r Y w xY w7 7 7 7 D7 67 # tJ        j`                  tJ        jb                  f$ r |&je                          Y S w xY w7 7 7 7 # tJ        j`                  tJ        jb                  f$ r |&je                          Y S w xY w# tt        jz                  $ r Y w xY w6 ddd      d{  7   n# 1 d{  7  sw Y   nxY wddd      d{  7   n# 1 d{  7  sw Y   nxY wn# tJ        jb                  $ r  tD        $ r}5tF        j}                  dB||5       |'s~dC|5 g dg dcY d}5~5|r|j]                          |&r[	 tK        j^                  |&d4+       d{  7   S # tJ        j`                  tJ        jb                  f$ r |&je                          Y S w xY wS Y d}5~5nd}5~5ww xY w|r|j]                          |&r	 tK        j^                  |&d4+       d{  7   n# tJ        j`                  tJ        jb                  f$ r |&je                          Y nxw xY w# |r|j]                          |&r[	 tK        j^                  |&d4+       d{  7   w # tJ        j`                  tJ        jb                  f$ r |&je                          Y w w xY ww xY wtS        jR                         |(z
  }6 |       s2tF        jk                  dDxs d6xs d       dg dg tm        |      |d d7S tF        jk                  dE||xs dddF |6tm        |'             |'xs dGd|ddH|'dgd<g tm        |      ||duxr t        |'      d7S w)IaT  Forward the message to a remote Hermes API server instead of
        running a local AIAgent.

        When ``GATEWAY_PROXY_URL`` (or ``gateway.proxy_url`` in config.yaml)
        is set, the gateway becomes a thin relay: it handles platform I/O
        (encryption, threading, media) and delegates all agent work to the
        remote server via ``POST /v1/chat/completions`` with SSE streaming.

        This lets a Docker container handle Matrix E2EE while the actual
        agent runs on the host with full access to local files, memory,
        skills, and a unified session store.
        r   )ClientSessionClientTimeoutuE   ⚠️ Proxy mode requires aiohttp. Install with: pip install aiohttpr  r%  r  r  uH   ⚠️ Proxy URL not configured (GATEWAY_PROXY_URL or gateway.proxy_url)GATEWAY_PROXY_KEYr?   r   c                  2     syj                         S rW  rK  r  r  rh  s   r#   _run_still_currentz>GatewayRunner._run_agent_via_proxy.<locals>._run_still_current6  !    %[//^LLr%   re   r]   rY   rZ   >   r  r^   r  zContent-Typezapplication/jsonzBearer AuthorizationzX-Hermes-Session-Idzhermes-agentT)r   r%  streamNr  	streamingStreamingConfigr&  r  GatewayStreamConsumerStreamConsumerConfigSUPPORTS_MESSAGE_EDITINGFfresh_final_after_secondsr  r   r  edit_intervalbuffer_thresholdry  buffer_onlyr  	transportr  )rk  r  r  r  initial_reply_to_idz+Proxy: could not set up stream consumer: %sr  r  )r]	  	sock_readr  z/v1/chat/completions)r  headersr  zProxy error (%d) from %s: %srB  u   ⚠️ Proxy error (z): r  r   uK   Discarding stale proxy stream for %s — generation %d is no longer currentr  )r  r%  r  r  r  r  response_previewedr   r:   )rw  r[  r   zdata: r  z[DONE]choicesdeltaz Proxy connection error to %s: %su   ⚠️ Proxy connection error: uK   Discarding stale proxy result for %s — generation %d is no longer currentz>proxy response: url=%s session=%s time=%.1fs response=%d charsr  z(No response from remote agent)r^   )?aiohttpr  r  ru   r  r@   r<  r7   r4   r`  rB   r&   r1  r  r  r   r  rN  r'  r  r  r  gateway.stream_consumerr  r  rE  ry  r  rX  r	  r6   r  r  r  r   r=  rM  r^  rr  r;  send_typingrN   postr  r   r>  finishr  r  r&  r  rZ   iter_anyrL  r]  rA  rp  r^  r  r  on_deltar  r  )7r  rm  r  rc   r  r  rh  r  r  _AioClientSessionr  r  	proxy_keyr  api_messagesr[   rY   rZ   r  body_stream_consumer_scfgr  r,  r(  r'  _plat_streaming_streaming_enabledr
  r  r  r  _adapter_supports_edit_effective_cursor_buffer_only_fresh_final_secs_consumer_cfg_sc_errstream_taskfull_response_start_timeoutrN  resp
error_textr   r  r   r  r  objr  r  r  _elapseds7   `     ``                                               r#   _run_agent_via_proxyz"GatewayRunner._run_agent_via_proxyw6  s	    .	Q '')	"l	  II126<<>		MD 	M  .0n MN 	HC776?Dggi(G,,##Tg$FG		H 	V@A $23E"F)0'<GO$-7G)* $$
  h5{DI=6#%E+FOO<*,B1{

 & MM6eoo6o& 	 6:5U5UV\^n5o$U_==,,V__=-4X?Y[_-`*8NTV%#(L(//9,.)'+ "??h.?.?? ge-H#NURUV  &
 %9&+&9&9).)?)?0$02C"'//";V")&+r"B"Hb%M (= ( &,!1,<($ !--.>.B.B.DEK ==$$V__5**6>>DT*UUU
 U	)$1=H(: =) =)g"<< k!56# (  <) <) {{c)+/99;%6
: KKJt4D
 1ET[[MQTU_`dadUeTf.g(*)*%'	 <) <) <)=) =) =)Z   ''))!**;DDD w  F'+||'<'<'> () ()e13"KK m + 2s . 3! 35,.-.)+25g,.86;$ 7<) <) <)=) =) =)Z   ''))!**;DDD U  %||GI|F$ #fn+1<<a+@LD&#'::<D#' (#x8'+ABx#'::<8#;$)!)*.**T*:C.1ggi.DG'.07
w0K27))Ir2J+2,9W,DM/?0@0I0I'0R% #fnk  	"i	 	l  UJGTTU V =)<) &7<)=)b E,,g.D.DE )&&(){())<)=)b E,,g.D.DE )&&()- (,';'; !)$(!)O (?)<) <) <) <) <)=) =) =) =) =)~ %% 	 	LL;YJ (Gs&K "!"	    ''))!**;DDD,,g.D.DE )&&()  !	   ''))!**;DDD,,g.D.DE )&&()   ''))!**;DDD,,g.D.DE )&&()  99;'!#KK]"s#! #%"%g,(&+  	L
(b#2.#m:L	
 ,P/PG4$? !'l$"2$">"V4CV
 	
sA  d4V Bd4d4!Cd4>C?V0 =Ad4 W  ,W-W  1d4[* W0 [* #[?W3 [#Z-&W6'AZ-)[5W96[:[* W<[* d4 X:W?;X?d4Z-Z!X;
"Z%;Z- [,X>-[1[* =Y>[* d4Y1Y2Y6d48Z-AZ-)A*Z Z-Z-V-*d4,V--d40	W9Wd4Wd4W   	W-)d4,W--d40[* 3[6Z-9[<[* ?X3X85d47X88d4;Z>[[* Y3Y=:d4<Y==d4 Z	Z-Z	Z-[&Z)'[-Z?	3Z64Z?	;[[* [[* [&[[&"[* )` *^'"^"(^')` -d4]#]]#"d4#3^d4^d4` "^''` *d4?_  __  d4 3`d4`d4b
/a	a
ab
3bb
bb

B*d4_interrupt_depthr  c           !      3   	
ghijklmnopqrstuvwxyz{|}~K    j                         r! j                  	       d{   S ddlmg ddldt
        f fdwt               t        j                        }ddl	m
} t         ||            j                  d      xs i }|j                  d	      xs d~j                  d
i       }t        |t              si }ddlm 	 ddlm}  |dd      } ||rt'        |      nd        |d      }t+        j,                  d      }t        |t              r|ni }|j                  d      xs i }|j                  |      xs i }|j                  d      xs i }d|v xs. t        |t              xr d|v xs t        |t              xr ||v }|r|s|n	|xs |xs dddlmh dk7  xr j                  hj2                  k7  j                  hj2                  k7  xr t5        |j                  d      d      rj7                         nddgdgdgt         |d            nnr% j8                  j                  j                        nd}|)t;        |      j<                  t>        j<                  u rdnd}g mdgdiddt@        dt@        dt@        dt        fiw}f	dj                  hjB                  k(  rjD                  xs 	}njD                  }|r%|jD                  k(  r jG                  	      nd |induj                  hjH                  k(  rjD                  r	r	ndvmnuvw} fd!}dg}dgdgdgtK        jL                         r jN                  od"t&        d#tP        ddforwfd$| j8                  j                  j                        xjR                  zj                  hjH                  k(  rjD                  r	r|	d%{n|r jG                  	      nd{dt@        d&t@        ddfmnrwxz{fd'yghorwxyz{|}
~	 f!d(}d}rtK        jT                   |             }d}fd)}tK        jT                   |             }} fd*}tK        jT                   |             } tK        jV                         qq} fd+}!tK        jT                   |!             }"tY        d,d-      }#|#dkD  r|#ndjt[        jZ                         tjmnt{} fd.}$tK        jT                   |$             }%	 tY        d/d0      }&|&dkD  r|&nd}'tY        d1d2      }(|(dkD  r|(nd})d}*tK        j\                   j_                  |            }+d},d3}-|'d}.	 tK        j`                  |+h|-4       d{   \  }/}0|/r|+jc                         }.nqje                         sr j8                  j                  j                        }1}d   }2|1r|2rtg        |1d5      r|1ji                        ru|1jj                  j                        }3|3r|3jl                  nd}4tn        jq                  d6|"js                         rd7nd8       |2ju                  |4       qjw                          d}.	 tK        j`                  |+h|-4       d{   \  }/}0|/r|+jc                         }.n}d   }5d9}6|5r/tg        |5d:      r#	 |5jy                         }7|7j                  d;d9      }6|*s|)|6|)k\  r~d}* j8                  j                  j                        }8|8rUt'        |)d<z        xs d=}9t'        |'|)z
  d<z        xs d=}:	 |8j{                  jR                  d>|9 d?|: d@{A       d{    |6|'k\  rd},nԉqje                         sr j8                  j                  j                        }1}d   }2|1r|2rtg        |1d5      r|1ji                        ru|1jj                  j                        }3|3r|3jl                  nd}4tn        jq                  d6|"js                         rd7nd8       |2ju                  |4       qjw                          |,rf}d   }<i }=|<rtg        |<d:      r	 |<jy                         }=|=j                  dCdD      }>|=j                  d;d      }?|=j                  dE      }@|=j                  dFd      }A|=j                  dGd      }Btn        j                  dH|?|'|>|A|B|@xs dI       |<r!tg        |<dJ      r|<ju                  t               t'        |'d<z        xs d=}CdK|C dLg}D@r Dj                  dM@ dN|?dOdPA dQB dR	       nDj                  dS|> dT|?dOdUA dQB dV	       Dj                  dW       dXj                  |D      d   rd   j                  dYg       ng Ad   xs g dddZ}.}d   }Ed   }F|FrFj                  d[      nd}GETtg        Ed\      rHGsFt               }HEj                  |Hk7  r- j                  Ej                        s j                         d   }I j8                  j                  j                        }Jd}Kd}L|IrJrrt        J      }K j                  |J|K      }KIj                  d]      rNKsLIj                  d^      r;Ij                  d^      }Mt        |M      rtn        jq                  d_xs d`M       n7M}Ln4Kr2Kjl                  xs t        K      }Ltn        j}                  da|Lddb        Ir2Ls0Ks.Ij                  dc      }N|NrN}Ltn        j}                  dd|Lddb        LrLj                         j                  dQ      reLj                         j                  dd=      }O|OrOd   d=d j                         nde}P|Pr)	 ddflOmP}Q  |QP      rtn        jq                  dgP       d}Kd}L j                  r1KsLr-tn        jq                  dhxs d` j                                d}Kd}LKsLrotn        j}                  diLddb        Jr9tg        Jdj      r-r+Jj                  v rJj                     j                           j                  k\  r4tn        j                  dk        j8                  j                  j                        }J|JrKrt        Jjj                  K       n Jrtg        Jdl      rJj                  L       d   xs |.dm	 |r|j                          |"j                          |%j                          |r 	 tK        j                  |d34       d{    | j                          r j                  n        j                  r j                  do       ||"| |%fD ]  }R|Rs	 R d{     S Ij                  d]      }S|Ssd   }T|Tr"|r 	 tK        j                  |d34       d{    t        Ij                  dq            }Vt        Txr t        Tdrd      xs V      }WIj                  dsde      }X|XrDWsB	 tn        jq                  dtxs d`       Jj{                  jR                  X{A       d{    nXrtn        jq                  dvxs d`       t        t;        J      dwd      FJj                  x      }Yt        |Y      r	  Y       }Zt        j                  |Z      r
Z d{    n\JrZtg        Jdy      rNJj                  j                  d      }Yt        |Y      r'	  Y       }Zt        j                  |Z      r
Z d{    Ij                  dY      }[}\L}]d}^d}_Kt        Kdzd      xs }\ j                  K      r܉ j                        stn        jq                  d{xs d`       I|r|j                          |"j                          |%j                          |r 	 tK        j                  |d34       d{    | j                          r j                  n        j                  r j                  do       ||"| |%fD ]  }R|Rs	 R d{     S  j                  K\[|       d{   }]|]I|r|j                          |"j                          |%j                          |r 	 tK        j                  |d34       d{    | j                          r j                  n        j                  r j                  do       ||"| |%fD ]  }R|Rs	 R d{     S  j                  K      }^t        |Kd}d      }_ j8                  j                  j                        }`|`r&	 `j                  jR                  {A       d{     j                  ][\d=z   ^_~
       d{   }at        I|a      |r|j                          |"j                          |%j                          |r 	 tK        j                  |d34       d{    | j                          r j                  n        j                  r j                  do       ||"| |%fD ]  }R|Rs	 R d{     S 	 |r|j                          |"j                          |%j                          |r 	 tK        j                  |d34       d{    | j                          r j                  n        j                  r j                  do       ||"| |%fD ]  }R|Rs	 R d{     	 d   }Tt        |.t              r|.j                  d[      sz|.j                  ds      xs de}b|b xs bdk(  }ct        Txr t        Tdrd            }dt        |.j                  dq            }Vcs%dsVr!tn        jq                  dxs d`dV       d|.d<   nr|~mr|rzt        |.t              rj|.j                  d[      sYtg        |d      rMtQ        m      pjR                  l|ktK        jL                         sdklpsfd}e	 |j                  ex       |.S |.S 7 K# t(        $ r Y w xY w7 7 # t(        $ r Y w xY w7 # t(        $ r!};tn        j}                  dB|;       Y d};~;1d};~;ww xY w# t(        $ r Y @w xY w# t(        $ r Y w xY w7 # tJ        j                  tJ        j                  f$ r: |j                          	 | d{  7   n# tJ        j                  $ r Y nw xY wY w xY w7 # tJ        j                  $ r Y w xY w7 m# tJ        j                  tJ        j                  f$ r: |j                          	 | d{  7   n# tJ        j                  $ r Y nw xY wY t(        $ r!}Utn        j}                  dpU       Y d}U~Ud}U~Uww xY w7 i# t(        $ r!}Utn        j                  duU       Y d}U~Uod}U~Uww xY w7 !# t(        $ r Y w xY w7 # t(        $ r Y w xY w7 +# tJ        j                  tJ        j                  f$ r: |j                          	 | d{  7   n# tJ        j                  $ r Y nw xY wY w xY w7 5# tJ        j                  $ r Y Zw xY w7 37 # tJ        j                  tJ        j                  f$ r: |j                          	 | d{  7   n# tJ        j                  $ r Y nw xY wY 9w xY w7 # tJ        j                  $ r Y w xY w7 # t(        $ r Y w xY w7 7 )# tJ        j                  tJ        j                  f$ r: |j                          	 | d{  7   n# tJ        j                  $ r Y nw xY wY w xY w7 3# tJ        j                  $ r Y Xw xY w7 # tJ        j                  tJ        j                  f$ r: |j                          	 | d{  7   n# tJ        j                  $ r Y nw xY wY Rw xY w7 # tJ        j                  $ r Y 'w xY w# |r|j                          |"j                          |%j                          |r	 tK        j                  |d34       d{  7   n_# tJ        j                  tJ        j                  f$ r9 |j                          	 | d{  7   n# tJ        j                  $ r Y nw xY wY nw xY w| j                          r j                  n        j                  r j                  do       ||"| |%fD ]*  }R|Rs	 R d{  7   # tJ        j                  $ r Y (w xY w w xY w# t(        $ r!}ftn        j}                  df       Y d}f~f|.S d}f~fww xY ww)a  
        Run the agent with the given message and context.
        
        Returns the full result dict from run_conversation, including:
          - "final_response": str (the text to send back)
          - "messages": list (full conversation including tool calls)
          - "api_calls": int
          - "completed": bool
        
        This is run in a thread pool to not block the event loop.
        Supports interruption via new messages.
        )rm  r  rc   r  r  rh  r  r  Nr   r  r   c                  2     syj                         S rW  r  r  s   r#   r  z4GatewayRunner._run_agent.<locals>._run_still_current7  r  r%   r
  r   r
  r   r&  )set_tool_preview_max_lentool_preview_lengthr
  HERMES_TOOL_PROGRESS_MODErP  tool_progress_overridesr  r*  r  interim_assistant_messagesTr  cleanup_progressFr   
event_typeri  rr  r7  c                   	 r        sy| dk(  rd   s	 |j                  d      xs d}|k\  ridk(  rdddlm}m}m}m}	 t               }
t        t        |
dd      d	
      }|r4 ||
|      s+dd<   j                   |	               |t        dz  |       y| dvry	 rd   nd}|t        |dd	      rydk(  r	|d   k(  ry|d<   ddlm}  ||d
      }dk(  r|rlddlm}  |       }t%        j&                  |d	t(              }|dkD  rt+        |      |kD  r|d|dz
   dz   }| d| dt-        |j/                                d| }n|r| d| d| d}n| d| d}j                  |       y|r;ddlm}  |       }|dkD  r|nd}t+        |      |kD  r|d|dz
   dz   }| d| d| d}n| d| d}|d   k(  r%dxx   dz  cc<   j                  d|d   f       y|d<   dd<   j                  |       y# t        $ r }t        j                  d|       Y d}~yd}~ww xY w# t        $ r Y w xY w) z3Callback invoked by agent on tool lifecycle events.Nztool.completedr   ro  r  )TOOL_PROGRESS_FLAGr  r  tool_progress_hint_gatewayr   r
  Fr  Tr   z(tool-progress onboarding hint failed: %s>   tool.startedis_interruptedr  )get_tool_emojiu   ⚙️r  )get_tool_preview_max_len)ensure_asciirH   ry  r  ro  (z)
z: "r}  r	  r   	__dedup__)rB   r  r  r  r  r  r  r   r   putr   r   r=  rM  r&   agent.displayr
  r  r  r  r*   r]  rA  r,  )r  ri  rr  r7  kwargsro  r  r  r  r  r  gate_on	_hint_err_agent_for_interruptr
  emojir  _plargs_strr[   _cap_LONG_TOOL_THRESHOLD_Sr  agent_holderlast_progress_msg	last_toollong_tool_hint_firedprogress_modeprogress_queuerepeat_counts                        r#   progress_callbackz3GatewayRunner._run_agent.<locals>.progress_callback8  s   !);)= --6J16MX%zz*5:H#99mu>T   45"1#D)5LM$)# #749K+L6:03*../I/KL%l]&BDVW  !22:F|AD$'3(*:E9 
 %)y|*C$IaL 5"9h?E 	)F24C#zz$UCPH Qw3x=3#6#+HS1W#5#="G1YKqdiik1B0C3xjQC"G1YKtG9B?C"G1YKs3C""3'
 B.0!Ags2w<$&%itax058Gq4y;q3/
 '**Q1$ ""Kl1o#FG#&a LOs#] ! XLL!KYWWX,  s*   BH !H2 	H/H**H/2	H?>H?r  c                  P  K   sy j                   j                  j                        } | sy t        |       j                  t
        j                  u r3j                         s"	 j                          j                         s"y g }d }d}d}d}	 	         s3j                         s"	 j                          j                         s"y j                         }	 rd   nd }|+t        |dd      rt        j                  d       d {    t        |t              r6t        |      dk(  r(|d   dk(  r |\  }}	}
|r|	 d	|
d
z    d|d<   |r|d   n|	}nHt        |t              r%t        |      d
k\  r|d   dk(  rd }g }d d<   dd<   |}|j                  |       t!        j"                         }|||z
  z
  }|dkD  rt        j                  |       d {    Q        sy |r|dj%                  |      }| j	                  j&                  ||       d {   }|j(                  s]t        |dd      xs dj+                         }d|v sd|v r t,        j/                  d| j0                         d}| j3                  j&                  |       d {   }rt        |dd      rt        |dd       rщj                  t5        |j6                               n|r9dj%                  |      }| j3                  j&                  |       d {   }n'| j3                  j&                  |       d {   }|j(                  r>|j6                  r2|j6                  }r$j                  t5        |j6                               t!        j"                         }t        j                  d       d {            r%| j9                  j&                         d {    \# t        $ r Y y w xY w# t        $ r Y y w xY w7 # t        $ r Y w xY w7 D7 7 7 7 7 q7 G# j:                  $ r! t        j                  d       d {  7   Y tt        j<                  $ rj j                         s	 j                         }t        |t              r-t        |      dk(  r|d   dk(  r|\  }}	}
|r|	 d	|
d
z    d|d<   nt        |t              rtt        |      d
k\  rf|d   dk(  r^|rM|rK|rIdj%                  |      }	 | j	                  j&                  ||       d {  7   n# t        $ r Y nw xY wd }g }d d<   dd<   n|j                  |       n# t        $ r Y nw xY wj                         s|rO|rM|rKdj%                  |      }	 | j	                  j&                  ||       d {  7   Y y # t        $ r Y Y y w xY wY y t        $ r?}t,        j?                  d|       t        j                  d
       d {  7   Y d }~2d }~ww xY ww)NTr  g      ?r   r	  Fry  r  u    (×r   r  r|  	__reset__r[  )r  r  rZ   r  r?   floodzretry afterz1[%s] Progress edits disabled due to flood controlr  r  r  g333333?r  zProgress message error: %s) rE  rB   r   r  edit_messager#  rQ  rR  r   r&   r^  r  r2   r<  r]  r`  rN   r  ra  r  r  rq  r=  rL  rG   r  r*   r  r  Emptyr&  r  ) rk  progress_linesprogress_msg_idcan_edit_last_edit_ts_PROGRESS_EDIT_INTERVALrE   r  r  base_msgrx  r[   _now
_remaining	full_textr  r  _flood_result_pending_textr  _cleanup_msg_ids_cleanup_progress_progress_metadata_progress_reply_tor  r  r  r  re  r   r  r  s                        r#   send_progress_messagesz8GatewayRunner._run_agent.<locals>.send_progress_messages8  sD    !mm''8G
 G}))-@-M-MM(..0&113 )..0
 N"OHM&)#o+-/"0"6"6"8& . 9 9 ; #1"6"6"8
 (335C	BN|ATX,/;02BEA #*--"222$
 "#u-#c(a-CFkDY-0*8U)4<:T%!)A1NN2.4BnR0#C/CHMc!fP[F[ +/)+/3)!,*+Q !&--c2  >>+D!8D=<P!QJ!A~ &mmJ777 -/O$?$(IIn$=	'.';';$*NN'6$- (< ( "
  &~~$+FGR$@$FB#M#M#OD&$-42G !'$W$+LL!" (-H29,,(.(+););	 3? 3 -M !2$+M9e$L$+M<$N 0 7 7M<T<T8U V#(,		.(AI+2<<(.(1););	 ,8 , &F ,3<<(.(+););	 ,8 , &F ">>f.?.?.4.?.?O0 0 7 7F<M<M8N O$(NN$4M "--,,,)+%11&..K]1^^^  %   $- & %&" 3$ J 8" - && -^{{ -!--,,,-- *,224""0";";"=C)#u5#c(a-CPQFVaLa58 28U#1<D:T%RS)TU9VN2$6!+C!7CHMcRSfXcNc $,?48IIn4MM%-.5.B.B4:NN7F4A /C /* )* )*
 ,5 %-(,%-26137; 1! 423Q . 5 5c :( "!"7 -224<  N$(IIn$=	!")"6"6(.+:(1 #7 #     ) ! !  +LL!=qA!--***+s.  A!X&%O. 5X&X&P1 +O= ;P1 X&P1 0P PP X&A:P1 X&AP1 PP1  X&"P1 )X&*6P1  P"!A<P1 P%A:P1 P('P1  P+A:P1 ;P-<+P1 'P/(P1 ,X&.	O:7X&9O::X&=	P	P1 X&P		P1 P 	PP1 PP1 "P1 %P1 (P1 +P1 -P1 /P1 1'X#QX#X& $X#B
U(!T81T42T87U(8	UU(U#U('X#(	U41X#3U44X#	X#!!W
WW
X&
	WX#X&WX#X&X#$.XXXX&X##X&r  
prev_toolsc                            sy 	 g }|xs g D ]Q  }t        |t              r%|j                  |j                  d      xs d       8|j                  t	        |             S t        j                  j                  d	j                  r	j                  j                  nd	j                  | ||d             y # t        $ r }t        j                  d|       Y d }~y d }~ww xY w)NrG   r?   z
agent:step)r   r  r  r  
tool_namesr  zagent:step hook error: %s)r2   rh   r`  rB   r*   r^  r.  r  r   r   r  r   r=  rM  )
r  r7  _names_tr  
_hooks_ref_loop_for_stepr  r  r  s
        r#   _step_callback_syncz5GatewayRunner._run_agent.<locals>._step_callback_syncz9  s    %'> %'%+ /B!"d+bffVn&:;c"g.	/
 00OOL=C__FOO$9$9RT#)>>&0%.&,!+3  #
  >8"==>s   B5C 	C*
C%%C*)r  r  rm  c                     	r        sy 	 t        j                  	j                  
|            }rdfd}|j                  |       y y # t        $ r!}t
        j                  d| |       Y d }~y d }~ww xY w)Nr  c                     	 | j                         }t        |dd       }t        |dd      r|rj                  t	        |             y y y # t        $ r Y y w xY w)Nr  r  F)r  r   r&   r`  r*   )r+  r  midr2  s      r#   _track_status_idzQGatewayRunner._run_agent.<locals>._status_callback_sync.<locals>._track_status_id9  s`    #"%**,C &c<>"3	59c,33CH= ?B9  ) #"#s   A 	AAzstatus_callback error (%s): %srq  )r^  r.  r  rs  r   r=  rM  )r  rm  _futrB  r  r2  r3  r=  r  _status_adapter_status_chat_id_status_thread_metadatas        r#   _status_callback_syncz7GatewayRunner._run_agent.<locals>._status_callback_sync9  s    "*<*>O77#(('!8 ) 
 # %> **+;< %  O=z2NNOs   AA 	A:A55A:c                  X   !cdefghi xs dt         j                  d<   t        t        j                  dd            } j                  kj
                  k(  rdnj                  j                  }wxs d}vxs dj                         }|r|dz   |z   j                         }j                  r |dz   j                  z   j                         }t                	 j                        \  }}t        j                  d||j                  d	      xs d       j                  }j!                        }|_        j%                         _        d hd }	t)        t)        dd       dd       }
|
ddlm}  |       }
 |d      }||
j.                  xr |
j0                  dk7  n
t3        |      }|}|}|}|s|r	 ddlm}m} j:                  j                  j                        }|rt)        |dd      }|st=        d      |
j>                  }d}j                  kj@                  k(  rd}d}j                  kjB                  k(  rtE        t)        |
dd      xs d      nd} ||
jF                  |
jH                  ||||
j0                  xs dt)        dd      xs d      } ||jJ                  |sfdnd z      h|rdtL        dd fohfd }	hd<   dd"dtL        d#t2        dd fnoprshfd$}jO                  }||      }jQ                  |d%   |d&   y|jS                        '      }d }t)        d(d       }t)        d)d       }|rs|q|5  |j                        } | rS| d*   |k(  rK| d   }tU        |d+      r	 |jW                         j[                  |m       t        j                  d,       d d d        |v jdd%|d%   i|d&   i d-| d.dd/dd0yd1xd2|xs d d3j\                  xs d d4|d5j&                  d6|j                  d6      d7|j                  d8      d9|j                  d:      d;|j                  d<      d=|j                  d>      d?|j                  d@d      dA|j                  dB      dCdD|dEj^                  dFj`                  dGjJ                  dHjb                  djd                  dIjf                  dJdKjh                  dLjj                  }|r$|"|5  ||f|<   jm                          d d d        t        j                  dM|       r~nd |_7        ljp                  rtnd |_9        |	|_:        |r|nd |_;        q|_<        ||_=        j&                  |_>        |j                  d6      xs i |_?        t        j                         fg dt        j                         edNtL        dd fnoprsfdOgddefgfdP}!dNtL        dd fdefgopfdQ}"|"|_C        prAr?t)        t        p      dRd       pj                  |!S       nt)        pdTd       }#|#|!|#<   dUtL        dtL        fnprsfdV}$|$|_F        |ud<   tU        |dW      r|j                  nd d<   g }%{D ]  }&|&j                  dX      }'|'s|'dYv r|'dZk(  r"d[|&v }(d\|&v })|'d]k(  }*|(s|)s|*r;|&j                         D +,ci c]  \  }+},|+d^k7  s|+|, }-}+},|%j                  |-       p|&j                  d_      }.|.s|&j                  d`      r|&j                  dadb      }/dc|/ dd|. }.t        |'|.|&      }0|%j                  |0        t               }1|%D ]  }2|2j                  dX      dev s|2j                  d_d      }3df|3v s.t        j                  dg|3      D ]D  }4|4j                  d*      j                         j                  dh      }5|5s4|1j                  |5       F  ddilQmR}6mS}7mT}8mU}9 djt        dd fcnprsfdk}:t)        dli       };r|;j                  d       nd }<|<r|<dz   }z   }t               }=t        t        {      |=m      }>d }?r&	 j                  j                  j                        }?t3        |?d uxr t)        |?dnd      xr |>      }@t3        |%xr |%do   j                  dX      d]k(  xr |>      }A@r+t)        |?dpd       xs dq}B|Bdqk(  rdrnBdsk(  rdtndu}Cdv|C dw}z   }nArdx}z   }t)        dyd       };|;r"r |;v r|;j                  d       }D|DrDdz   }z   }xs dc |8c      }E |6c|:       	 j                        }F|FrM	 ddzl^m_}G  |G}F      \  }H}I|Ir t        j                  d{t        I      |I       t        d| HD              rH}Jn}}Jn}}J|j                  J|%~      }L |9c       	 ddldme}M  |Mc        |7E       Ld<   hhj                          Lj                  d      }Nd}Od}Pd}Qd}Rud   }S|SrXtU        Sd      rLt)        Sj                  dd      }Ot)        |Sdd      }Pt)        |Sdd      }Qt)        |Sj                  dd      xs d}RSrt)        Sd%d       nd }TNsLj                  d      rdLd    nd}Ui d|UdLj                  dg       d|Lj                  dd      d|Lj                  dd      d|Lj                  dd      d|Lj                  d      d|Lj                  dd      d|Lj                  d      d|Lj                  d      d|Lj                  dd      dWd   xs g dt        |%      dOdPdQd%TdRS dfNvrg }Vd}WLj                  dg       D ]  }&|&j                  dX      dev s|&j                  d_d      }.df|.v s.t        j                  dg|.      D ]L  }X|Xj                  d*      j                         j                  dh      }Y|Ys4Y|1vs9Vj                  dfY        N d|.v sd}W Vret               }Zg }[VD ])  }\|\ZvsZj                  \       [j                  |\       + Wr[j                  dd       Ndz   dj                  [      z   }Nud   }d}]|rrtU        |dC      r|j                  k7  rud}]t        j                  d|j                         j                  j                  j                        }0|0r+|j                  |0_j        j                  j                          |rt)        |dC      ni]rdn
t        |%      }^Nrjh                  r	 ddlmmn}_ d   rd   j                  dg       ng }`t)        |dd       }a|a|r?t)        |d%d       t)        |d	d       t)        |dd       t)        |dd       t)        |dd       dnd d}bj                        r
ifdbd<    _jh                  i}N`fi b i dNdLj                  d      dd   rd   j                  dg       ng dd   rd   j                  dd      nddd   rd   j                  d      nd dd   rd   j                  dd      nddd   rd   j                  dd      nddd   rd   j                  d      nd dd   rd   j                  d      nd dWd   xs g d^dOdPdQd%TdRdCidLj                  dd      iS # t        $ r}d
| g dg dcY d }~S d }~ww xY w# t        $ r!}t        j                  d!|       Y d }~d }~ww xY w# tX        $ r Y w xY w# 1 sw Y   xY w# 1 sw Y   
bxY wc c},}+w # t        $ r d }?Y w xY w# t        $ r#}Kt        j                  d}K       }}JY d }K~Kd }K~Kww xY w# t        $ r Y w xY w#  |9c       	 ddldme}M  |Mc       n# t        $ r Y nw xY w |7E       w xY w# t        $ r Y  w xY w)Nr?   HERMES_SESSION_KEYr   r  r  r  r'  z3run_agent resolved: model=%s provider=%s session=%sr   u'   ⚠️ Provider authentication failed: r   r  r  r  r  r  r  r  r  Tz(skip streaming for non-editable platformFr  r  r   r  r  c                  &     j                  d      S )N)r#  )r  )r  s   r#   r  z<GatewayRunner._run_agent.<locals>.run_sync.<locals>.<lambda><:  s    ););N)K r%   )rk  r  r  r  on_new_messager  r   r   c                 8            rj                  |        y y r  )r  )r   r  r  s    r#   _stream_delta_cbzDGatewayRunner._run_agent.<locals>.run_sync.<locals>._stream_delta_cbC:  s    #5#7$4$=$=d$C $8r%   z$Could not set up stream consumer: %s)already_streamedrN  c                R           sy %|rj                          y j                  |        y |srt        | xs d      j                         sy 	 t	        j
                  j                  |              y # t        $ r }t        j                  d|       Y d }~y d }~ww xY w)Nr?   r  z$interim_assistant_callback error: %s)
on_segment_breakon_commentaryr*   r7   r^  r.  r  r   r=  rM  )	r   rN  r  r=  r  rD  rE  rF  r  s	      r#   _interim_assistant_cbzIGatewayRunner._run_agent.<locals>.run_sync.<locals>._interim_assistant_cbJ:  s    )+#/'(99;  )66t<#?#djb/BWBWBY
M44',,+ %< - 
 ' ! MLL!GLLMs   (A= =	B&B!!B&r   rB  )ry  ro  rm  r   r  z#Reusing cached agent for session %sr  r  r
  r	  r
  ephemeral_system_promptprefill_messagesr  r>  r;  r
  r
  r
  r  r
  r
  r
  r9	  r
  r
  r
  r
  r  r   r  r  r  r/  r  gateway_session_keyr  rI  z)Created new agent for session %s (sig=%s)rm  c                     r        sy 	 t        j                  j                  |              y # t        $ r }t        j                  d|       Y d }~y d }~ww xY w)Nr  z$background_review_callback error: %s)r^  r.  r  r   r=  rM  )rm  r  r=  r  rD  rE  rF  s     r#   _deliver_bg_review_messagezNGatewayRunner._run_agent.<locals>.run_sync.<locals>._deliver_bg_review_message:  sj    &.@.B
M44',,+#%< - 
 ' ! MLL!GLLMs   (6 	AAAc                      j                          5  t              } j                          d d d         D ]
  } |        y # 1 sw Y   xY wr  )r  rA  r  )r'  queued_bg_review_pending_bg_review_pending_lock_bg_review_releaserW  s     r#   _release_bg_review_messageszOGatewayRunner._run_agent.<locals>.run_sync.<locals>._release_bg_review_messages:  sW    "&&(, /"#56G&,,./ & 7F.v67/ /s   AAc                     r        sy j                         s75  j                         sj                  |        	 d d d        y 	 d d d         |        y # 1 sw Y   xY wr  )is_setr`  )rm  rZ  r[  r\  rW  r  rD  s    r#   _bg_review_sendzCGatewayRunner._run_agent.<locals>.run_sync.<locals>._bg_review_send:  sg    &.@.B)0020 #188:.55g>"# #:# +73	# #s   #AA%r	  r!  r#  questionc           
         ddl m} dd l}sy|j                         j                  d d }|j                  |xs d| |rt        |      nd        	 j                         d}	 t        j                  j                  | |rt        |      nd |xs d            }|j                  d	      }t        t        |d
d            }|s|j#                  xs d       y|j%                         }	|j'                  |t)        |	      	      }
|
|
dk(  rdt+        |	dz         dS |
S # t        $ r Y w xY w# t        $ r"}t        j!                  d|       d}Y d }~d }~ww xY w)Nr   r  r?   r  )r  rh  ra  r  F)r  ra  r  r  rh  r  r)  r  r  zClarify send failed: %sz'[clarify prompt could not be delivered]z[user did not respond within r  zm])r  r  rV
  rZ
  r[
  r  rA  pause_typing_for_chatr   r^  r.  send_clarifyr  r4   r&   r=  r>  r  get_clarify_timeoutwait_for_responser6   r5   )ra  r  rX  r_
  r  send_okr+  r  rE  r   r  r=  rD  rE  rF  rh  s              r#   _clarify_callback_synczJGatewayRunner._run_agent.<locals>.run_sync.<locals>._clarify_callback_sync:  s   A$&"[[]..s3
%%) + 1r%-4DM$	 & #99/J  $!::'44$3%-5<DM$'1(3(9r%< 5  '
C !ZZZ3F"769e#DEG
  !..{/@bAD&::<'99*eT[n9]#x2~:3w|;L:MRPPE ! $ ! $NN#<cB#G$s+   D* 'A$D9 *	D65D69	E$EE$r  rY   >   rf   re   rH
  rj  toolr3   rZ   mirrormirror_sourcezanother sessionz[Delivered from r  >   ri  rE
  zMEDIA:zMEDIA:(\S+)z",})register_gateway_notifyreset_current_session_keyset_current_session_keyunregister_gateway_notifyapproval_datac           	         	j                  
       | j                  dd      }| j                  dd      }t        t        	      dd      h	 t	        j
                  	j                  
||            j                  d	      }|j                  ryt        j                  d
|j                         t        |      dkD  r|dd dz   n|}d| d| d}	 t	        j
                  	j                  
|            j                  d	       y# t        $ r }t        j                  d|       Y d}~d}~ww xY w# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)aY  Send the approval request to the user from the agent thread.

                If the adapter supports interactive button-based approvals
                (e.g. Discord's ``send_exec_approval``), use that for a richer
                UX.  Otherwise fall back to a plain text message with
                ``/approve`` instructions.
                r6  r?   rZ	  zdangerous commandsend_exec_approvalN)r  r6  rh  rZ	  r  r)  r  zLButton-based approval failed (send returned error), falling back to text: %sz6Button-based approval failed, falling back to text: %sr  r  u4   ⚠️ **Dangerous command requires approval:**
```
z
```
Reason: z

Reply `/approve` to execute, `/approve session` to approve this pattern for the session, `/approve always` to approve permanently, or `/deny` to cancel.r  z#Failed to send approval request: %s)rc  rB   r&   r  r^  r.  rr  r  r  r=  r>  r  r   r]  r  )rp  rc  rb	  _approval_resultr  cmd_previewr[   _approval_session_keyr=  rD  rE  rF  s          r#   _approval_notify_synczIGatewayRunner._run_agent.<locals>.run_sync.<locals>._approval_notify_syncy;  s     55oF#''	26$((8KL
 402FMY+2+K+K+>>(7(+,A,0)@ ?  +	, !&&, ) ,33"j,22 47s8c>c$3i%/s'= )#f %gh 
L44',,+%< - 
 ' fRf(- % TVX . ! LLL!FKKLs7   AD  D 8D< 	D9D44D9<	E%E  E%r  )rL   r~  r|  r  rv  za gateway restartrw  za gateway shutdownza gateway interruptionzD[System note: Your previous turn in this session was interrupted by z. The conversation history below is intact. If it contains unfinished tool result(s), process them first and summarize what was accomplished, then address the user's new message below.]

a)  [System note: Your previous turn was interrupted before you could process the last tool result(s). The conversation history contains tool outputs you haven't responded to yet. Please finish processing those results and summarize what was accomplished, then address the user's new message below.]

r  )build_native_content_partsz:Native image attachment: skipped %d unreadable path(s): %sc              3   D   K   | ]  }|j                  d       dk(    yw)r  r
  Nr  r  s     r#   r  z=GatewayRunner._run_agent.<locals>.run_sync.<locals>.<genexpr>*<  s     LquuV};Ls    z8Native image attachment failed, falling back to text: %s)conversation_historyrv  r  r  r  r+  session_prompt_tokenssession_completion_tokensr  r  u   ⚠️ r%  r  r  r  r  r  interrupt_messager0  r  r!	  r"	  z[[audio_as_voice]]r[  u/   Session split detected: %s → %s (compression))maybe_auto_title_emit_auxiliary_failurer   r   r5  )r   r   r   r   r5  )failure_callbackmain_runtimec                 *    j                  |       S r  )r1  )r,  effective_session_idr  r  s    r#   r  z<GatewayRunner._run_agent.<locals>.run_sync.<locals>.<lambda><  s    RVR|R|"0!S r%   title_callbackr(  r  r   rq  )pr@   rA   r5   r<  r   r  r   r7   rL  r   r6  r=  rM  rB   r   rV  r  rN  rO  rP  r&   r1  r  r  r  r4   r  r  r  rE  r@  ry  rX  r	  r6   r  r  r  r*   r@  r  rw  r  r  r4  r  rJ  r  r  r/  r  r  r{  rX  r  tool_progress_callbackr  step_callbackstream_delta_callbackinterim_assistant_callbackstatus_callbackr  r>  r;  rl  r_  rn  background_review_callbackr  r	  clarify_callbackr  r  r`  rb   r  r(   finditerr!   rB  r  r  rl  rm  rn  ro  rh   r-  rF   rQ   rj   rZ  r  r  rA  rw  r>  r]  r  r
  tools.clarify_gatewayr  r  r  rp  ra  r  rL  rA  agent.title_generatorr}  r  )r  r,  combined_ephemeralevent_channel_promptr   r4  rE  r
  r  rM  r  r  r  r  _want_stream_deltas_want_interim_messages_want_interim_consumerr  r  r  r  r  r  r  r  r  rR  r
  _sigr   r]  r  r  r]  r`  _pdcrh  agent_historyr[   rY   has_tool_callshas_tool_call_idis_tool_messagerC  r  	clean_msgrZ   
mirror_srcr_   _history_media_paths_hm_hc_match_prl  rm  rn  ro  rv  _pending_notes_msn_freshness_window_interruption_is_fresh_resume_entry_is_resume_pending_has_fresh_tool_tail_reason_reason_phrase_srn_approval_session_token_native_imgsrw  rX  _skipped_run_message_img_excr  _clear_clarify_sessionr  _last_prompt_toks_input_toks_output_toks_context_lengthr  _resolved_model	error_msg
media_tagshas_voice_directiver   rq   r	  unique_tagsr  _session_was_split_effective_history_offsetr}  all_msgs_title_failure_cbmaybe_auto_title_kwargsru  rZ  r[  r\  rW  r  r  r  r  r<  r  r=  r  rD  rG  rE  rF  r>  r  r  r  r
  r	  r  rc   "interim_assistant_messages_enabledrm  r!  r  r'  result_holderr  r  r  rh  r  stream_consumer_holdertool_progress_enabledtools_holderr(  s                                                                                                      @@@@@@@r#   r
  z*GatewayRunner._run_agent.<locals>.run_sync9  s    0;/@bBJJ+, !+BD!IJN %+OOx~~$E56??K`K`L "0!52$2$8b#?#?#A #&86&ADX&X%_%_%a",,&86&ADDaDa&a%h%h%j"
 <=(,(K(K! + + )L )%~
 I>--j9;;L" ''B#EE'  F   &6D"!%!8!8!:D##GD(D9;ME}:')
 6\;O #* :%//U":/* 
 #5%G"%;""&<8Rc#}}00AH 29C]_c1d.5"./Y"ZZ,1LL) (-!??hoo=02-+/L  &(2C2CC "'%1Lc"R"YVYZ!$ *
 )=*/*=*=-2-C-C#4(46G&+oo&?&-fk2&F&L") ,A$,$*NN#0%< $2#= "L%)0@,( /Ds Dt D 5E.q1 NS MC Md MW[ M M. 88%XJ
 //7#9% "==kJ 0 D E!$(;TBKT>48Fv1  Y#ZZ4F&)t"3 &q	 #6=9% & 2 2; ? 88@PQ%JKXY } $W- + $2  $	
 %* &6 '8 -?,F$ &*%;%;%Ct &6 "&!3!3 '1nn5H&I ')ffVn ')ffX&6 %'FF7O  #%&&.!" 137KU0S#$ .0VV4E-F%&  *'( *)* #NN+, %..-. #NN/0 %..12 %..34 %..56 )478  $//9: $(#7#7;> 6#5$ 8/4dm{+5578 H+W[\ AV+<[_E(9C9P9P"5VZE*:E'H^/DdhE,$9E!%5E"!%!3!3E&0nn5H&I&ORE#!*!2,.&/nn&6#MC MD M M7 74 4 4 4 0?E, ;402SUYZf#CC#3#1 D  #?4NPTUD',G[)7  7 # 7  7 r &<E" $LO-4UG-Dekk$LO M %4wwv ,, 8# ".!4#1S#8 "&&.!%525))+ R$!QkAQA RI R!((3 "ggi0G778,),BS)TJ(8Bwi&PG !4D'3 G%,,U3K%4T ), $ =776?&::'')R0C3&(kk.#&F =F!'a!6!6!8!?!?!FB! 4 8 8 <=	= ALT ALd AL ALH %T+A2FN<G>%%k48TD-'10 !@ A%C*73-&"
 !M)$($6$6$?$?$C$CK$PM "&T) +M+;UC+*"
 $( +!"%))&1V;+*$  "!-$G\K\ "33 ( "44 .1 () **+
   &5
   %T+I4PN++2O%))+t<"Vmg5G$/$52!&=>S&T##$9;PQ+C
  $GGT/R+E#(,( $"NN \ #Hx LVLL06L ,3L $+L//S`jt/u)*?@]*+@A **AB%M!  + '') $ZZ(89N !"KLO!!_F'&*>?$+F,E,EG[]^$_!%f.EqI&v/JAN")&*C*CEUWX"Y"^]^@Fgfgt<DO!;A::g;NgfWo%67TV	$i

:r :  K!; fjj59	
 vzz)U;  K!8 "6::mU#C (4G)H VZZ0 ,VZZ8OQV-W \!_2 %c-&8 )*; #K $\  _!" %o# < ~-
&+#!::j"5 	;Cwwv*>>"%'')R"8#w.)+^W)M G',{{1~';';'='D'DU'K#'D8L,L$.$5$5tfo$FG  4w>6: 3	; 5D"$K) 4d? HHSM'..s34 +#**1.BC%3d%:TYY{=S%SN !OE!&)E%JZJZ^hJh%)"E 0 0 **3377D','7'7E$&&,,.OT75,
#KZd  .@SEW% $"2"2#FGTUVGW}Q/33JC]_H
 )08$)% -> # &-UGT%B(/z4(H(/z4(H'.ui'F(/z4(H) )-	/+ 33F;E/0@A
 %((,&  2 . &**-="> MRSDTM!,00R@Z\ ]STEU]1-11+qA[\	
 -PQBR]1-11+>X\ ][\M]}Q/33M5Ich ]STEU=+//	5A[` -:Jq)--g6PT $R_`aRb]1%5%9%9:M%Nhl a.B !"; %&7      !/!" 2#$ %fjj1Eu&M% W  (OPSu&U "!"	 | ! RLL!GQQRf $, % $%Y Y`8 8\ !Sf ! )$(M)Z % /V$ (//$ !  **?@]*+@A  )*AB\ ! s#  A| D|: -}7	}'(}7~~)~+%~ ( )A~) 4(  !B'A@ 	|7#	|2,|72|7:	}$}}$'	}40}73}44}77~~~&%~&)	2
( ( 	%$%(	A@2A@@ A@@	A@@
A@@A@@A@@	A@)@(A@)c                     K   t        d      D ]A  } d   d   j                          d{     yt        j                  d       d{    C y7 &7 	w)z8Wait for the stream consumer to be created, then run it.r  r   Nrl  )rR  r;  r^  r  )r  r  s    r#   _start_stream_consumerz8GatewayRunner._run_agent.<locals>._start_stream_consumer<  sX     3Z *)!,80377999mmD)))	*9)s!   *AAAAAAc                  (  K    d   #t        j                  d       d {     d   #sy .j                        st        j	                  dxs d       y  d   j
                  <   j                  rj                  d       y y 7 nw)Nr   rl  uL   Skipping stale agent promotion for %s — generation %s is no longer currentr?   r  )r^  r  rK  r=  rL  rd  r  r  )r  r  r  rh  s   r#   track_agentz-GatewayRunner._run_agent.<locals>.track_agent<  s     q/)mmD))) q/)
 )$2N2N^3 b%2"
 0<QD  -~~++J7 # *s   BB	BA%Bc                  0  K   sy 	 t        j                  d       d {    	 j                  j                  	j                        } | sGt        | d      rz| j                        rid   }|rb| j                  j                        }|r|j                  nd }t        j                  d       |j                  |       j                          y 7 # t         j                  $ r  t        $ r }t        j                  d|       Y d }~:d }~ww xY ww)Ng?has_pending_interruptr   z3Interrupt detected from adapter, signaling agent...z,monitor_for_interrupt error (will retry): %s)r^  r  rE  rB   r   r  r  re  r   r=  rM  r  r  r&  r   )
r  r   _peek_eventpending_text_mon_err_interrupt_detectedr  r  rh  r  s
        r#   monitor_for_interruptz7GatewayRunner._run_agent.<locals>.monitor_for_interrupt=  s     mmC((([  $}}00AH# 
 x)@AhFdFdepFq ,Q  +3*D*D*H*H*UK?J;+;+;PTL"LL)^_!OOL9/335!9 (8 --   [LL!OQYZZ[sE   DCD'C DBC DD3D	DDDr      c                  j  K   y j                   j                  j                        } | sy 	 t        j                         d {    t        t        j                         z
  dz        }d   }d}|rt        |d      r~	 |j                         }d|d    d|d    g}|j                  d	      r|j                  d
|d	           n!|j                  |j                  dd             ddj                  |      z   }	 | j                  j                  d| d| d       d {   }
r>t        |dd      r1t        |dd       r$	j                  t        |j                                C7 +# t        $ r Y ~w xY w7 X# t        $ r }t"        j%                  d|       Y d }~:d }~ww xY ww)Nr  r   r?   r  r  r  r    r  r  r  r  r  r  u   ⏳ Still working... (r  r  r  r  Fr  z#Long-running notification error: %s)rE  rB   r   r^  r  r5   rN   r  r  r`  ra  r   r  r  r&   r*   r  r=  rM  )_notify_adapter_elapsed_mins
_agent_ref_status_detail_arX  _notify_res_ne_NOTIFY_INTERVALr2  r3  _notify_startrF  r  r  r  s           r#   _notify_long_runningz6GatewayRunner._run_agent.<locals>._notify_long_runningP=  s    '"mm//@O"mm$4555 #TYY[=%@R$G H)!_
!#'*6L"M	'<<>$.r2B/C.DAbIYFZE["\!]66.1"MMIb6H5I*JK"MM"&&1Er*JK)0499V3D)DM(7(<(<0|NK[[\]!8 )= ) #K *#KEB#KtD(//K4J4J0KL9 5 % # ! MLL!FLLMsn   AF3E3	<F3A=E6 (F ,F-AF 1F36	F?F3FF3F 	F0F+&F3+F00F3r   r  r   i  r   r  r  zABackup interrupt detected for session %s (monitor task state: %s)r  r  r  r  r  r  r   u   ⚠️ No activity for zB min. If the agent does not respond soon, it will be timed out in z- min. You can continue waiting or use /reset.r  z!Inactivity warning send error: %sr  r  r  r  r  zaAgent idle for %.0fs (timeout %.0fs) in session %s | last_activity=%s | iteration=%s/%s | tool=%sr  r  u   ⏱️ Agent inactive for u(    min — no tool calls or API responses.z!The agent appears stuck on tool `z` (r  z!s since last activity, iteration r    z).zLast activity: r  zs ago, iteration z6). The agent may have been waiting on an API response.zTo increase the limit, set agent.gateway_timeout in config.yaml (value in seconds, 0 = no limit) and restart the gateway.
Try again, or use /reset to start fresh.r[  r%  )r  r%  r  r  r  r  r  r   r  r|  z5Ignoring control interrupt message for session %s: %sr  z9Processing queued message after agent completion: '%s...'r	  pending_steerz0Delivering leftover /steer as next turn: '%s...'r?   r  uZ   Discarding command '/%s' from pending queue — commands must not be passed as agent inputz=Discarding pending follow-up for session %s during gateway %sz#Processing pending message: '%s...'r	  u^   Interrupt recursion depth %d reached for session %s — queueing message instead of recursing.queue_message)r  r%  r  r  z5Stream consumer wait before queued message failed: %sr  final_response_sentr  zoQueued follow-up for session %s: final stream delivery not confirmed; sending first response before continuing.z7Failed to send first response before queued message: %sz_Queued follow-up for session %s: skipping resend because final streamed delivery was confirmed.r   r!  r#  r  uN   Discarding stale goal continuation for session %s — goal is no longer activer  r  )
rm  r  rc   r  r  rh  r  r  r  r  r$  zjSuppressing normal final send for session %s: final delivery already confirmed (streamed=%s previewed=%s).r.  r	  c                  l    dfd} 	 t        j                   |               y # t        $ r Y y w xY w)Nc                  t   K   D ]  } 	 j                  |        d {     y 7 # t        $ r Y .w xY wwr  )delete_messager   )_mid_adapter_snapshot_chat_id_snapshot_ids_snapshots    r#   _delete_allzLGatewayRunner._run_agent.<locals>._cleanup_temp_bubbles.<locals>._delete_alll?  sP      - !!"3"B"B 14#  !  ) ! !s(   8)')8)	5858rq  )r^  r.  r   )r  r  r  r  _loop_snapshots    r#   _cleanup_temp_bubblesz7GatewayRunner._run_agent.<locals>._cleanup_temp_bubblesk?  s1    !44[]NS  s   ' 	33z-Post-delivery cleanup registration failed: %sNNNrq  )nr  r  rC  r  re  r4   r  r  r   r
  r
  rO  rB   r2   rh   rN  r'  r  r  r5   r   r@   r<  r1  r  r  r   QueuerE  r  r  r#  r*   rB  r  r  rN  r^  r   r  rA  r  rr  r_  rJ   rN   ensure_futurer
  r	  r  r_  r  r  re  r   r=  rL  r  r  r  r  r  rM  r  _INTERRUPT_REASON_TIMEOUTr`  ra  r  r   r  r9  rl  rs  rt  rg  r7   r^  rp  rq  r'   r  r  r_  r	  r  _MAX_INTERRUPT_DEPTHr>  r(  r  r  r  r  r&  r:  r  r&   r   r  r  r  r#  r-  rz  r  r  r'  r  rJ  r  r	  )r  rm  r  rc   r  r  rh  r  r  r  r  r,  r
  agent_cfg_localdisplay_configr  _tpl_resolved_tp_env_tp_display_cfg_platforms_cfg_platform_cfg_legacy_tp_overrides_tool_progress_configured_cleanup_adapter_progress_thread_idr6  r
  progress_taskr  r  r  tracking_taskr  interrupt_monitor_NOTIFY_INTERVAL_RAWr  _notify_task_agent_timeout_raw_agent_timeout_agent_warning_raw_agent_warning_warning_fired_executor_task_inactivity_timeout_POLL_INTERVALr  r  r  _backup_adapter_backup_agent	_bp_event_bp_textr  
_idle_secs_act_warn_adapter_elapsed_warn_remaining_mins	_warn_err_timed_out_agent	_activity
_last_desc	_secs_ago	_cur_tool_iter_n	_iter_max_timeout_mins_diag_linesr  _result_for_fb_run_failed
_cfg_modelr  rk  rn  r'  r|  _leftover_steer_pending_parts_pending_cmd_word_rc_pendingrt  was_interrupted_scr  
_previewed_already_streamedfirst_response_bg_cb
_bg_resultupdated_historynext_sourcenext_messagenext_message_idnext_channel_prompt_followup_adapterr  _final_is_empty_sentinel	_streamedr  _rper  r  r  r  r  r  r2  r3  r<  r  r  r=  r  r  r4  r5  r  rD  rG  rE  rF  r>  r  r
  r	  r  r  r  r  r!  r  r  re  r   r'  r  r  r  r  r(  s   ```````````                                                                                            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@r#   rJ  zGatewayRunner._run_agent7  s    4  22-%'-!1 3 	 	 	 	&	MD 	M
 +,+FOO<?!"5k<"PQ%//'28b+//0CDL$B7.$/N
 	C	>*;F[]^_D$$SYA>
 /{L/Z))78)3ND)I~r%))+6<"&**<8>B+//0IJPb|+ =$/ 5#}4 /6 9 $88 	" 8 2'2U 	 	, - 6 ^6??hN^N^;^
 OOx/// ""#?@ 	+ +@TF	!Fs !#K?QR
 BS4==,,V__=X\'!"115H5W5WW !&#&( !&w!%l	$# l	$# l	$s l	$ae l	$ l	$p ??hnn,"("2"2"F6F"("2"2
 ! #f&6&66 ,,V5EF23&*	 	 (//1f6F6FK[  	I	+ I	+X vv"& !113ZZ
	>3 	>D 	>T 	> 	>8 --++FOO< ..??hoo-&2B2BGW 1'7A#
 exd&F&FvO_&`  ~B#	Oc 	OC 	OD 	O 	O2j	 j	 j	 j	 j	Z  #//0F0HIM 	* ))*@*BC	8.  ++KM: &mmo$	[ $	[L $//0E0GH  **H#N3G!3K/QU		$	M $	ML **+?+AB~	 ",,BD!I3E3I/tN!+,JC!P3E3I/tN"N$22228<N #( N%  $+LL'(.% GD! #1#8#8#: /557K*.--*;*;FOO*L(4Q+$+O=T$U$3$I$I+$V(7(I(I(M(Mk(ZI9By~~H"KK!; +*;*@*@*B		 *33H=/3351 :  $+LL'(.% GD! #1#8#8#:!-aJ!$J!gj:P&Q!#-#B#B#DD)-2JC)PJ +~/I *n <)-(,(9(9&//(J(,/"0D,E,JM.1>N3RWY2Y.Z._^_O
]&3&8&8$*NN&=m_ M77F6G HN%O .E '9 '" !" !" "^3.2+.557K*.--*;*;FOO*L(4Q+$+O=T$U$3$I$I+$V(7(I(I(M(Mk(ZI9By~~H"KK!; +*;*@*@*B		 *33H=/335m p ##/? 	#0@BX(Y$4$I$I$K	 ']]+?K
%MM*BAF	%MM.9	#--(8!<%MM*:A>	E~{' $0@+(N$../HI #Nb$8 9 >Q 1 @( ) &&;I; G%c? +%%,IQyk=  &&)*R	# G%%,IQyk :NN
 ""? '+ii&<HUVWHXa 0 4 4Z D^`!()!_2&'"" "!_F*1-N:H.,,X6eK!gfg&>{35
<<:-d6W6WXcekeqeq6r ,,[9 #1%Fmm''8G !MG'k 6w L !% : :;Q^ _::m,]vzzReGf(.

3F(G%45FGS'.3- #4"+00[4L]4[GLL!\^efigi^jk gm"(**_"="-GLL!SU\]`^`Uab 7==?55c:!(!6!6tQ!?ESN1$5ab$9$?$?$AY[!$V&'89"KK!M 1
 -1M&*G ~~=GS&3--/
 !%BGCRLQ
 ww0BCXcgnggX,,[9??A $t'@'@@NNA(+
 #mm//@G=3G4M4M{\ij WWo%F--k7C(+`(X_/``v $$&$$&! !**;DDD   " 11 2  ~~++J7 '(9=,W "

q #)**]";& 13C{	e")"2"2;"LLL "&fjj1E&F!GJ(,K.CU!K &%)% &,ZZ0@"%EN%.?i"KK !R + 2s #*,, & .)@ #/ #   (}'.3 tG}.JDQ]!(!C!C''5 "D " $F+%-3X
#*#6#6z#B*4$4$4 !WW6P%Q!(!A!A!E!EkSW!X#F+%-3X
#*#6#6z#B*4$4$4 #)**Z"A$&"&&*# ,")-4"H"RFK77FtOrOrs}O~l'.3  &N $$&$$&! !**;DDD   " 11 2  ~~++J7 '(9=,W "

K *.)K)K+* / *L * $L
 $+%@ $$&$$&! !**;DDD   " 11 2  ~~++J7 '(9=,W "

} '+&B&B=&QO*1-AQSW*X'
 %)MM$5$5foo$F!$/;;"NN%< <    )-(#1+&) +#1%5%9%4#6 )8 ) # @X $$&$$&! !**;DDD   " 11 2  ~~++J7 '(9=,W "

a !(b $$&$$&! !**;DDD   " 11 2  ~~++J7 '(9=,W "

( %Q'h%hll8.D\\"239rF%+!Bv/BB%:EBI
 hll+?@AJ%9
 A&3	 ,0(  , 8T*LL*(*KL !12M & 0$557N T @@)- A  xe>	R  		t-<  ) ! !!" $- ] &-PR[ \ \]< % L % J E,,g.D.DE &&()))"11 	2 #"11 i M ' 4 4g6L6LM %'..0%&1 1 1#*#9#9 % $%( e"LL)`bcdde
  ) i"NN+dfghhi( %5#, % $% %5#, % $%B E,,g.D.DE &&()))"11 	2 #"11 S$\ E,,g.D.DE &&()))"11 	2 #"11 s % #. E,,g.D.DE &&()))"11 	2 #"11 7 E,,g.D.DE &&()))"11 	2 #"11 G $$&$$&! !**;DDD,,g.D.DE &&()))"11 	   " 11 2  ~~++J7 '(9=,W "

"11 	\  TLdSSTs  -Ag<"AR-#B'Ag<&AR0 1P=Ag</A5Ab  $AS %DAb  2AS30Ab  $"AS AAb   (AS AS	AS C4Ab  AT L,Ab  ?(AT 'D)Ab  4Ag<AT) AT&!AT)%AAg<4AV9AV	:AV>Ag<Ab  AV) 9AV&:AV) >AAb  ;AX4 AX1	AX4 AAb  !AY$  AY!AY$ 6Ab  <!AY7 AY4AY7 "A+Ab  @4Ag<AAZ
AAZAAZ
A!AAg<B0A[-B5A[*B6A[-B:Ag<B=Ab  CA\CAb  C4Ag<DA\D+A\
D,A\D0AAg<E?A]0FA]-FA]0F	Ag<FAAb  G A^ G2A^
G3A^ G7!Ab  HA^HAb  H(4Ag<IA^#I7A^ I8A^#I<AAg<KA`KA`KA`KAg<K4Ag<LA`# L(A` L)A`# L-AAg<M<AbNAbNAbNDAg<RAg R)Ag<R0	AR=R9Ag<R<AR=R=Ag<S Ab  SAb  S	ASSAb  SASSAb  SAS S	ATS"AS>S8Ab  S>ATTAb  T	ATTAb  TATTAb  T	AT#TAb  T"AT#T#Ab  T&AT)T)3AVUAU)U"AU%U#AU)U(AVU)AU?U<AVU>AU?U?AVVAg<VAVVAg<V	AVVAV#VAg<V"AV#V#Ag<V&AV) V)3AX.WAW)W"AW%W#AW)W(AX.W)AW?W<AX.W>AW?W?AX.XAb  XAX.XAX)X#Ab  X)AX.X.Ab  X1AX4 X4	AYX=AYYAb  YAYYAb  Y!AY$ Y$	AY1Y-Ab  Y0AY1Y1Ab  Y4AY7 Y7	AZZ Ab  ZAZZAb  ZAZ
Z
3A['Z>A[
[A[[A[
[	A['[
A[ [A['[A[ [ A['[#Ag<[&A['['Ag<[*A[-[-A\\ Ag<\A\\Ag<\Ab  \
A\\3A]*]A]]A]	]A]]A]*]A]#] A]*]"A]#]#A]*]&Ag<])A]*]*Ag<]-A]0]0A^^Ag<^A^^Ag<^
A^ ^	A^^Ab  ^A^^Ab  ^ A^#^#3A` _A_#_A__A_#_"A` _#A_9_6A` _8A_9_9A` _<Ag<_?A` ` Ag<`A``A``Ag<`A``Ag<` A`# `#3Ab aAa#aAaaAa#a"Ab a#Aa9a6Ab a8Aa9a9Ab a<Ag<a?Ab b Ag<bAbbAbbAg<bAbbAg<b 5AgcAc7c0Ac3c1Ac7c6Agc73Aed+Ad7d0Ad3
d1Ad7d6Aed7Ae	e
AeeAe	eAeeAgeAeeAAgf%Af1f*Af-
f+Af1f0Agf1Ag	gAggAg	gAggAg<g	Ag9gAg4g.Ag<g4Ag9g9Ag<r  rq  rx  )       @)r  )r   )rb  r%  r  )r?   )r0  g      @g      @r  )NNr   NN(  r[  
__module____qualname____doc__r  r	   r*   r6   __annotations__r  r)  r  r  r
   r5   r  r4   r  r   r!  r"  r#  r^  Taskr$  r%  r   r  r  r  rF  r  r   r  r  r  r  r  r  r  r  r  r  r  r  propertyr  r  r  r  r  r  r  	frozensetr  r  r  r  r  r  r   r"  r&  rh   r<  r6  r@  r#  rT  rX  rZ  r_  rc  rg  rm  rs  rw  staticmethodrz  r~  r  r  rN  r   rI  rK  rM  r  r  r  rO  rQ  rS  rT  r  rU  rA  rW  r  r%  r  r  r  r  r#  r.  r,  r?  r8  r  r;  rE  rG  rf  ru  r  r  r  r  r#  r  rp  r  r  r  r  r  r  rQ  r
  r  r  r  r  r  r  r  r  r  rF  r@  r   r$  r  r"  r  r(  r  r  r  r)  r  rM	  r   r!  r,  r-  r.  r/  r	  r	  r  r	  r	  rG  r  r0  rH  r;  r
  r
  r"
  r8
  r!
  rX  rY  rZ  r:  r  r
  r*  r+  r  r  r  r1  r  r  r  r  r(  r1  r4  r5  r7  rA  r'  rG  rE  r7  r8  r9  r2  r3  r4  r  r5  r&  r  r  r  r'  _APPROVAL_TIMEOUT_SECONDSr  r  r	  r=  rB  r@  rD  rV  rX  rF  rH  rJ  rL  rN  rR  rP  rT  r]  r_  r  r  r6  r#  r  r  r  r  r  r  r:  rL  r
  r  r  r  rX  rS  r  r  ro  classmethodrw  r  r.  r  r:  r  rE  r  rK  rI  r  r9  r  r  r  rP  r  r  rJ  r   r%   r#   r  r    s,    ,.S%Z(-'c'$IEI $J$It$$"'4'#t#!&$&)-J&-:<d3S#X#67<>@ $sDcN':";@~,x6 ~,B>-
f$  $&??-8 -c -c -4S> 4@,s ,d ,W[ ,+c +D +UY +$*X<8% 86 6
 
 "T " " '$ ' ' !Xc] ! ! 8C=  
m 
 
 = T . #,RI"67M 7d 7	m 	 	 +/'= T (
C 

# 

 
8C= 


 
	
( +/%)&*P% 'P% c]	P%
 d^P% 
sDy	P%d*s *3 *X\ *ae *XA9L AQU AF## #$ #
)c )Dc DLs LWT W"5 5N 5UX 5]a 5 !! !  /	!
 
.	!!F @D     U3 U4 U US 3 SV >	 	 	
HSM 
W_`cWd 
pt 
  )-$('+ !	
 SM  } 
& #Dc3h$8 # #J 3  ( D4K  . F Fc4i8H F F8 +/%)	- '- c]	-
 
-&TT #4.T 
	T d
  4 $    3  &   2      D D   $+"4  (
$sCx. 
S3 S| SX\ SC| CZ] Cbf CJ#% #E$sCx.RVBV<W #<Q Q QQf1tCH~ 1$ 1'c 'd 'R 0S T 6/c /b  *aF 38U 4 d W[ , %FB3 BHGT GR0*u 0*t 0*df>$sCx. f>T f>P`'c `'Dc w'u w't w't	 >B!$-5c]	, hsm t &  $  	
 } 
._B|'B !& %g g 	g
 g 
gR	*ZZ Z 
%	&	ZvI-- I-D I-VBhx6H BS BHGc Gd G>v<< v<HSM v<p!K K 	K
 d38n%K 
#KZ?s ?tCy ?  *c l9# l9_b l9\u c u nR6 R6%^H[B\ R6h <  C   (D#(D47(D	#(DV1
, 1
3 1
h`7, `73 `7D= , = 3 = ~X , X 3 X t%/ %/sNGZA[ %/NN?< N?E#~J]D^ N?`/8, /84 /8d
 
 
26
L 6
S 6
pf  f (3- f P+, +,# +,ZCP| CP CPJ"7 "7# "7NS *W,KN KN3 KNZS 3 4 &)S )SV )[_ )VCG CG 	CG
 CG 
CGJO O O2#NL #NS #NJ \ hsm  D9 D9# D9L1Xl 1Xs 1Xf%| % %.MS MT M's 'S 'VY '^b 'RB,B,&)B,7:B,R #44 4 	4
 4 
4l:\ : : :xnInI nI
 
nI`9KL 9KS 9KvQl Qs QH +/TT  T 	T
 #3-T 
TlqA\ qAc qAf3; 3; 3;j= =sNGZA[ ="<]< <]C <]|SG, SG3 SGjD9L D9S D9L
] 
t 
8'U- 'UD 'URU] Ut U$	C 	C 	LdLd Ld 	Ld
 
Ld\66 6 	6
 
6> ,1(M d &
3 
,"
M "
c "
HW@ W@S W@RU W@r, - , C , \4< 4Y\ 4ad 4l/R /R# /RbJN, JN3 JNXZi, Zi3 ZixG* G*# G*R/8L /8S /8b=
l =
xPS} =
~F;| F; F;Pb> b># b>hU
 U
 	U

 U
 U
 
s$d*	+U
nB B 	B
 B B 
#BH4S>   .2 &c] 
$sCx.	!	( .| . . . !$8D< 8DHSM 8Dt$1 $1 $1P !*8++X^^X=N=N,,hooh>O>O)@)@(//S[SgSgiqiwiw  zB  zH  zH	+ !*E *E# *EXP,, P,3 P,dX  #!$	P?P? P? 	P?
 
P?dU Un=0(5c8TW=AX;Y2Z =0D GK= s5c8C=)@#ABC= 
U3Xc]*+	,	=~
 
4 
(# #$ #
F# .CC #YC 
	CJSS #YS 
	SjE
t E
N$F3 $FT $Fd $FLW>$ W>4 W>r )  t   :  #'/>/>/> /> 	/>
 4K/> 
/> />b%%'*%<@%	%*M M# MRV M )-	'' !	'
 
'R-# -$ -^  $ UW 
c 
c 
[^ 
G3 GC GD G  $J	
 
. '+;; ;
 ; !;  $; 
;29s 9t 9 "3 " " " "$  ,JX/3 /j ,  (,*.Z
Z
 Z
 d38n%	Z

  Z
 Z
 Z
 !Z
 #3-Z
 
c3hZ
J	  (, !*.(,mm m d38n%	m
 m m m !m m #3-m !m 
c3hmr%   r  
stop_eventr  c                    ddl m} ddlm}m} ddlm} d}d}	d}
d}t        j                  d|       d}| j                         s	  |d||	       |dz  }||	z  dk(  r9|r7	 ddlm} |.t        j                   ||      |      }|j!                  d       ||z  dk(  rD	  |d      }|rt        j                  d|       	  |d      }|rt        j                  d|       ||
z  dk(  r$	  |       \  }}|rt        j                  d||       ||z  dk(  r	 ddlm}  |t'        d      d        | j)                  |       | j                         st        j                  d       y# t        $ r!}t        j                  d
|       Y d}~@d}~ww xY w# t        $ r!}t        j                  d|       Y d}~'d}~ww xY w# t        $ r!}t        j                  d|       Y d}~*d}~ww xY w# t        $ r!}t        j                  d|       Y d}~5d}~ww xY w# t        $ r!}t        j                  d|       Y d}~6d}~ww xY w# t        $ r!}t        j                  d|       Y d}~@d}~ww xY w)a  
    Background thread that ticks the cron scheduler at a regular interval.
    
    Runs inside the gateway process so cronjobs fire automatically without
    needing a separate `hermes cron daemon` or system cron entry.

    When ``adapters`` and ``loop`` are provided, passes them through to the
    cron delivery path so live adapters can be used for E2EE rooms.

    Also refreshes the channel directory every 5 minutes and prunes the
    image/audio/document cache + expired ``hermes debug share`` pastes
    once per hour.
    r   )tick)cleanup_image_cachecleanup_document_cache)_sweep_expired_pastesr  r  z"Cron ticker started (interval=%ds)F)r  rE  r  zCron tick error: %sNr   r  rH  r  z#Channel directory refresh error: %sr9  )max_age_hoursz-Image cache cleanup: removed %d stale file(s)zImage cache cleanup error: %sz0Document cache cleanup: removed %d stale file(s)z Document cache cleanup error: %sz4Paste sweep: deleted %d expired paste(s), %d pendingzPaste sweep error: %s)maybe_run_curatorr  c                 .    t         j                  d|       S )Nzcurator: %s)r=  rL  )r[   s    r#   r  z$_start_cron_ticker.<locals>.<lambda>?  s    6;;}c+J r%   )idle_for_seconds
on_summaryzCurator tick error: %szCron ticker stopped)r}   r=  r3  r>  r?  r  r@  r=  rL  r_  r   rM  r  r  r^  r.  r  agent.curatorrB  r6   r	  )r;  rE  r  r  	cron_tickr>  r?  r@  IMAGE_CACHE_EVERYCHANNEL_DIR_EVERYPASTE_SWEEP_EVERYCURATOR_EVERY
tick_countr  r  r+  r|  deletedr  rB  s                       r#   _start_cron_tickerrN  ?  sl    1R6M
KK4h?J!	3ehTB 	a
))Q.8GM#
 "::/94C JJrJ* ))Q.A-B?KK OQXYD0rBKK RT[\ ))Q.9%:%<"KKN %*:;!%*5\J 	) !@ KK%&{  	3LL.22	3"  GBAFFG  A<a@@A  D?CCD  94a889  :5q99:s   E- "6F !!G !G4 -#H! I -	F6FF	G#F??G	G1G,,G14	H=HH!	I*II	I8I33I8r:   	verbosityc                   %&K   ddl m}m}m}m}m}m}  |       }	|	U|	t        j                         k7  r=|r  ||	      }
t        j                  d|	       	 ddl m}  ||	       	  ||	d       ddl m} t)        d      D ]!  } ||	      s nMt+        j,                  d       # t        j/                  d|	       	  ||	d       t+        j,                  d        |        	 t1               dz  j3                  d       	 dd
l m}  |        	 ddl m}  ||	|
      }|rt        j                  d|       n:t7        t1                     }t        j#                  d|	|       t9        d|	 d       y	 ddlm}  |d       ddlm }  |tB        d       |ddl"m#} tH        jJ                  tH        jL                  djO                  |tH        jP                        }tI        jR                         }|jU                  |       |jW                   |d             tI        jX                         j[                  |       |tI        jX                         j\                  k  r#tI        jX                         jU                  |       t_        |       &d%d3%&fd 	}&fd!}ta        jb                         }te        jf                         te        jh                         u rktj        jl                  tj        jn                  fD ]  }	 |jq                  |||        tu        tj        d"      r7	 |jq                  tj        jv                  |       nt        j                  d#       ddl<}dd$l m=}m}m}  |       }|.|t        j                         k7  rt        j#                  d%|       y |       st        j#                  d&       y	  |        |j                  |       |j                  |       	 dd(l@mA} ta        jb                         } | j                  d|       d{    &j                          d{   }!|!sy&j                  r-&j                  r t        j#                  d*&j                         yte        j                         }"te        j                  t        |"f&j                  ta        jb                         d+dd,-      }#|#j                          &j                          d{    &j                  r-&j                  r t        j#                  d.&j                         y|"j                          |#j                  d/0       	 dd1l@mN}$  |$        &j                  t        &j                        %r"&j                  st        j                  d2       yy# t        $ r!}t        j                  d|       Y d}~Sd}~ww xY w# t        $ r Y \t        t         f$ r7 t        j#                  d	|	       	 dd
l m}  |        Y y# t        $ r Y Y yw xY ww xY w# t        t        t         f$ r Y Pw xY w# t        $ r Y ;w xY w# t        $ r Y =w xY w# t        $ r Y w xY w# t        $ r Y w xY w# tr        $ r Y w xY w# tr        $ r Y 6w xY w# t|        $ r  |        t        j#                  d'       Y yw xY w7 # t        $ r!}t        j                  d)|       Y d}~d}~ww xY w7 7 # t        $ r Y w xY ww)4a7  
    Start the gateway and run until interrupted.
    
    This is the main entry point for running the gateway.
    Returns True if the gateway ran successfully, False if it failed to start.
    A False return causes a non-zero exit code so systemd can auto-restart.
    
    Args:
        config: Optional gateway configuration override.
        replace: If True, kill any existing gateway instance before starting.
                 Useful for systemd services to avoid restart-loop deadlocks
                 when the previous process hasn't fully exited yet.
    r   )acquire_gateway_runtime_lockget_running_pidget_process_start_timer  r  terminate_pidNz<Replacing existing gateway instance (PID %d) with --replace.)write_takeover_markerz#Could not write takeover marker: %sFr  z1Permission denied killing PID %d. Cannot replace.)clear_takeover_marker)_pid_existsr  g      ?zAOld gateway (PID %d) did not exit after SIGTERM, sending SIGKILL.Tzgateway.pidr=  )release_all_scoped_locks)	owner_pidowner_start_timez2Released %d stale scoped lock(s) from old gateway.zAnother gateway instance is already running (PID %d, HERMES_HOME=%s). Use 'hermes gateway restart' to replace it, or 'hermes gateway stop' first.u"   
❌ Gateway already running (PID z).
   Use 'hermes gateway restart' to replace it,
   or 'hermes gateway stop' to kill it first.
   Or use 'hermes gateway run --replace' to auto-replace.
)sync_skills)quiet)setup_loggingr(  )r   r  )RedactingFormatter)r   r   z#%(levelname)s %(name)s: %(message)sc                    d}	 ddl m}  |       }d}| t
        j                  k(  rd}n|s	 ddl m}  |       }	 ddlm	}m
}m}  ||       }	|rt        j                  d
|	r|	d   nd       n?|rt        j                  d|	r|	d   nd       ndt        j                  d|	r|	d   nd       |	8	 t        j                  d |	             	 t        dz  dz  } ||	d   d       t        j                   j#                                y # t        $ r!}t        j	                  d|       Y d }~d }~ww xY w# t        $ r!}t        j	                  d|       Y d }~"d }~ww xY w# t        $ r#}
d }	t        j	                  d	|
       Y d }
~
>d }
~
ww xY w# t        $ r }
t        j	                  d|
       Y d }
~
d }
~
ww xY w# t        $ r }
t        j	                  d|
       Y d }
~
d }
~
ww xY w)NFr   ) consume_takeover_marker_for_selfz Takeover marker check failed: %sT)$consume_planned_stop_marker_for_selfz$Planned stop marker check failed: %s)format_context_for_logsnapshot_shutdown_contextspawn_async_diagnosticz$snapshot_shutdown_context failed: %su?   Received %s as a planned --replace takeover — exiting cleanlysignalSIGTERMu9   Received %s as a planned gateway stop — exiting cleanlyzSIGTERM/SIGINTu#   Received %s — initiating shutdownzShutdown context: %sz!format_context_for_log failed: %slogszgateway-shutdown-diag.logr   )timeout_secondsz!spawn_async_diagnostic failed: %s)r  r`  r   r=  rM  re  SIGINTra  r  rb  rc  rd  rL  r>  r   r^  rr  rQ  )received_signalplanned_takeoverr`  r  planned_stopra  rb  rc  rd  _shutdown_ctxr  	_diag_log_signal_initiated_shutdownrunners               r#   shutdown_signal_handlerz.start_gateway.<locals>.shutdown_signal_handler@  s    !	@G?A fmm+L!HOCE		E 
 6oFM
 KKQ+8h'i KKK+8h'>N
 *.&KK5+8h'>N $F*,B=,QF(614OO	&}X6
 	FKKM*S  	@LL;Q??	@  HCQGGH   	E MLL?DD	E:  F@"EEF  F@"EEFsu   D
 D7 E$ /F F? 
	D4D//D47	E! EE!$	F-FF	F<F77F<?	G(G##G(c                  ,     j                  dd       y )NFTrg  )ru  )rp  s   r#   restart_signal_handlerz-start_gateway.<locals>.restart_signal_handler@  s    4@r%   SIGUSR1z6Skipping signal handlers (not running in main thread).)write_pid_filer  rR  z^Another gateway instance (PID %d) started during our startup. Exiting to avoid double-running.zBGateway runtime lock is already held by another instance. Exiting.z8PID file race lost to another gateway instance. Exiting.)r  zMCP tool discovery failed: %szGateway exiting cleanly: %s)rE  r  zcron-ticker)r  r7  r  r  rG   z Gateway exiting with failure: %sr  r  )r  z}Exiting with code 1 (signal-initiated shutdown without restart request) so systemd Restart=on-failure can revive the gateway.r  )Rr  rQ  rR  rS  r  r  rT  r@   rY  r=  rL  rU  r   rM  ProcessLookupErrorPermissionErrorr  r  rV  rW  rR  rN   r  r>  r   rB  rX  r*   printtools.skills_syncr[  hermes_loggingr]  r   rB  r^  loggingWARNINGINFOrB   DEBUGStreamHandlersetLevelsetFormatter	getLogger
addHandlerr
  r  r^  r   rl  current_threadmain_threadre  ri  rf  add_signal_handlerNotImplementedErrorr  rt  atexitru  FileExistsErrorr  r  r  rE  r  r  r  r_  r  rN  rE  r
  r  r  ra  r  r  
SystemExitr  )'r  r:   rO  rQ  rR  rS  r  r  rT  existing_pidexisting_start_timerU  r  rV  rW  r  rX  	_releasedr   r[  r]  r^  _stderr_level_stderr_handlerrq  rs  r  sigr  ru  _current_pidr  _loopr  	cron_stopcron_threadr  ro  rp  s'                                        @@r#   start_gatewayr  ?  sR    &  #$LLBIIK$?"8"FKKNG@%l3l%8& 32Y "<0

3 W !,d;JJsO  "]2::d:K
@%'	C4*%8	 KK TV_` o/0KLL^k
 5l^ DN O 1$ -l; 3#OO=AA)W]][!//1  /$$%78]%^_&&77,,.444((76"F "'V+pA ##%D!Y%:%:%<<MM6>>2 	C''-DcJ	
 69%''8NO 	LM OO"$LLBIIK$?/0<	
 ')P	
  OOO$
OO0195((*##D*<===
 LLN"G!!LL68J8JK !I""!\"OOW5M5M5OPK  
"
"
$$$&&LL;V=O=OP MMOQ7 #))** "&*C*CM	
 O
  GBAFFG & #W- G D)+  ! < +OWE       *  F ' 
 ' 6  $&F	
 $ 	> 94a889 #* %  s  A\V (
W 2A\>X \%X1 Y (Y 9;\5Y! E6\;Y1\! Z A8\:Z "\$/Z? Z<Z? \+[,,B5\![/"A\[2 A\	V?V:4\:V??\	X\$X3X \	XX\XX\X.*\-X..\1	X>:\=X>>\	Y
\Y\	Y\Y\!	Y.*\-Y..\1	Y>:\=Y>>\	Z
\Z\%Z96\8Z99\<Z? ?	[)[$\$[))\/\2	[?;\>[??\c                     	 ddl m}   |         ddl}|j	                  d      }|j                  ddd	       |j                  d
ddd       |j                         }d}|j                  rNddl}t        |j                  d      5 }|j                  |      xs i }t        j                  |      }ddd       t        j                  t        |            }|st!        j"                  d       yy# t        $ r Y w xY w# 1 sw Y   OxY w)z CLI entry point for the gateway.r   )configure_windows_stdioNz)Hermes Gateway - Multi-platform messaging)rZ	  z--configrL  zPath to gateway config file)r  z	--verbosez-v
store_truezVerbose output)r  r  r   r   r   )hermes_cli.stdior  r   argparseArgumentParseradd_argumentr	  r  r   r   r   r  r;
  r^  r;  r  r  exit)	r  r  parserr7  r  r   r   r  r  s	            r#   r  r  VA  s    <! $$1\$]F

D/LM
T,EUVDF{{$++0 	3A>>!$*D",,T2F	3 kk-/0G +  	3 	3s   C1 +D 1	C=<C= D	__main__rq  r  )NNr  )NFr   )r3  hermes_bootstrapModuleNotFoundErrorr^  r  r  r  r{  r@   r(   r  r  re  rX
  rl  rN   collectionsr   contextvarsr   ru
  r   r   typingr	   r
   r   r   r   agent.account_usager   r   
agent.i18nr   r   r   r  r  r  r  compiler+   r*   r.   rC   r6   r<   rF   rJ   r4   rQ   rX   r<  r4  rb   rj   rx   r   r   r   rA   rq   rp  r   r  r  r   utilsr   r   r   r   r   dotenvr   hermes_cli.env_loaderr   	_env_pathr   r   r   r  r  _config_pathrr   r   r   r   rP  r   r  r   r  _key_valr2   r5   rB   _terminal_cfgrh   _terminal_env_map_cfg_key_env_varr  rA  r  _auxiliary_cfg_aux_task_env	_task_key_env_map	_task_cfgr7   _provr  r  r  
_agent_cfgr  _tz_cfg_security_cfg_redactrq  r   _bridge_errrx  r  r[  rO  r
  dir_network_cfg_bootstrap_excr  r  _configured_cwdr<  r"  	_fallbackr1  r  r  r  r  r  r  r2  r  r  r  r  r  r   r!  gateway.deliveryr"  r3  r#  r$  r%  r&  r'  r(  gateway.restartr)  r*  r+  gateway.whatsapp_identityr,  _canonical_whatsapp_identifierr-  ry  r.  rz  r  r=  objectr  rF  r?  rg  rl  r  r  r   _INTERRUPT_REASON_SSE_DISCONNECTr  r  r7  rr  rt  r  r  r  r  r  r  r  r  r  weakrefrG  r  rH  r  r  r  r  r_  rN  r  r  r   r%   r#   <module>r     s  "	      	 	  
     # $   3 3 P  %  # )- &+. ()rzz*TU < <s <s <L )0 %S Xe_ D< <&S 5 U $  #'	)) 
%) %	)
 
)r- %S/ c C d38n cSVh Bd38n1E(F 3 :#J3 3 3> > >
<t < !$

    3tH~,,334 5 - ^ ^   46!	 |h9O9O9Q9Y9YZ[9\_e9e fJ< $$de (1:'> $ m+[
,1 	-R"5??2&,"D	- 	7%**, 	-JD$$c5$ 78T=S#&t9

4 	-
 R0Zt<!>!~! -! #$?	!
  7! %&C! $%A! 5!  !9! !";! /! /! /! -!  !9!  #$?!!" !";#!$ )H";31Y+M5$?1!4 '8&=&=&? 9"(},(2D
  5(SY:N-N   5(Zc-B!ww11$7!$t5/9tzz$/?

8,/24y

8,!9* +r2j> !<5 ;9	 !A: @>	  !>7 =;	M( (5':':'< ?#	8*..y"=	!)T2IMM*b9:@@BY]]7B78>>@	j" =>DDF	y}}Y;<BBDUf_7<BJJx
344:BJJx017@BJJx
346>BJJx	23?, XXgr*
*Z6j(69*[:Q6R

23 J.58DU9V5W

12(J6=@LeAf=g

9:(J6=@LeAf=g

9:&*4=@LcAd=e

9:0J>?B@A@

;< xx	2.J|T: L0?B<PaCb?c

;<!\1@CLQcDd@e

<=((:r*z'3/,3MMOBJJ()R0mT*#''(89G"69'l6H6H6J

23*_6"ceOD88BGL,%,*:*:<*HD)
U7
U> "
 !

>  !$

  **..4/-AA		/*>c)$))+.>I!*BJJ~    ,    
		8	$ !( $t $N,t ,^s ,4 49L 4 * 3 > #<  %< "$8 !'$$&%%'!'')(..0*002)//1	 58C= 5T 5/4 /E#*cDj:P4Q /d;3 ;3: ;|C: C# C
F Fd :4$; #  Xd3i0 6C M 4d | < $0 X\\ 0 	+++ 	+
 	+\$ 4 ( 
@lk lk^WY'9?? Y'X[ Y'xr 7 r rbjknbo rx| rj@ zF EK  	
 		r	- 	-P  
 	<K ))*"[M;	

 	L	
 	

2  _	;N;K
LSVS]S]^^_  U	1.1A
BTTU  U	1.1A
BTTUs   d# 8d< d/9d< d< &Bd< 4Gd< 7F:d< 2Af 9f7 g! #d,+d,/d94d< <f
>ff
f4f//f47g<gg!h&hh