
    sjr8                        U d Z ddlmZ ddlZddlZddlmZ dZdZ ej	                    Z
i Zded<   d*dZd+dZd,dZd-dZd,dZd.dZd/dZd0dZd+dZd1ddd2d Zd!Zd"Zd1d3d#Zd1d3d$Zd1d4d&Zd1d4d'Zd5d)ZdS )6u  Bot Mode roster probe — canonical Bot Chat system prompt section.

When the desktop's Bot Mode manages this install (any profile carries a
``ui_meta['hermes-bots']`` block in its profile.yaml), a bot's canonical
"Bot Chat" session — and ONLY that session — gets a short "Messaging other
agents" section so the bot can receive teammate DMs, reply with attribution,
and hand off @mentions.  Regular sessions never carry the section; the
desktop's composer middleware owns the @mention send path there.

The caller (agent/system_prompt.py) enforces the session-title gate against
``BOT_CHAT_TITLE``; this module answers "is this install Bot-Mode-managed,
and what should the section say for this profile".

This replaces the plugin-side SOUL.md backfill: the protocol is injected by
the core at prompt-build time instead of appended to user-authored SOUL
files.  If the profile's SOUL.md already carries the section (created by an
older plugin version), the probe stays silent so the text never doubles up.

Silent (returns ``""``) when:
- no profile on this install is Bot-Mode-managed (the dominant case),
- the current profile's SOUL.md already contains the protocol heading,
- anything at all goes wrong (never crash a prompt build).

Deterministic within a process: the result is computed once and cached, so
compression-triggered prompt rebuilds produce identical bytes.

Toggle via ``agent.bot_mode_protocol`` in config.yaml (default True).
    )annotationsN)Pathz## Messaging other agentszBot Chatzdict[str, str]_cachedhomer   returnc                >    | j         j        dk    r| j         j         S | S )z?Root ~/.hermes for both the default profile and named profiles.profilesparentnamer   s    8/home/agent/.hermes/hermes-agent/tools/bot_mode_probe.py_hermes_rootr   /   s"    {:%%{!!K    strc                4    | j         j        dk    r| j        S dS )Nr	   defaultr
   r   s    r   _profile_namer   6   s    {:%%y9r   profile_dirboolc                   | dz  }	 |                                 sdS |                    dd          }d|vrdS ddl}|                    |          }t	          |t
                    r|                    d	          nd}t	          |t
                    o't	          |                    d          t
                    S # t          $ r Y dS w xY w)
zTrue when profile.yaml carries a ui_meta['hermes-bots'] block.

    Cheap substring check before the YAML parse keeps the silent path fast.
    zprofile.yamlFutf-8replaceencodingerrorszhermes-botsr   Nui_meta)is_file	read_textyaml	safe_load
isinstancedictget	Exception)r   metarawr    datar   s         r   _is_bot_managedr)   <   s    
 'D||~~ 	5nngin@@##5~~c"")3D$)?)?I$((9%%%T'4((YZM8R8RTX-Y-YY   uus   B< B< BB< <
C
	C
rootlist[tuple[str, Path]]c                   d| fg}	 | dz  }|                                 rTt          |                                          D ]2}|                                 r|                    |j        |f           3n# t
          $ r Y nw xY w|S )z:(name, dir) for the default profile + every named profile.r   r	   )is_dirsortediterdirappendr   r%   )r*   entriesr	   childs       r   _rosterr3   Q   s    (14'8&9G*$?? 	8 0 0 2 233 8 8<<>> 8NNEJ#6777   Ns   A-A5 5
BBc                    	 | dz  }|                                 ot          |                    dd          v S # t          $ r Y dS w xY w)NSOUL.mdr   r   r   F)r   _PROTOCOL_HEADINGr   r%   )r   souls     r   _soul_has_protocolr8   _   s]    Y&||~~i"3t~~w_h~7i7i"ii   uus   69 
AAr   c                    | dk    rdn| S )Nr   hermes )r   s    r   _handler<   g   s    y((88d2r   	list[str]c                   	 | dz  }|                                 sg S |                    dd          }d|vrg S ddl}|                    |          }t	          |t
                    r|                    d          nd}t	          |t
                    sg S t          d |D                       S # t          $ r g cY S w xY w)	u   Registered peer gateway names (``hermes peer``), for the protocol text.

    Reads config.yaml directly (cheap, no config-loader import) — the section
    is optional and absent on most installs. Never raises.
    zconfig.yamlr   r   r   	bot_peersr   Nc              3  v   K   | ]4}t          |                                          #t          |          V  5d S N)r   strip).0r   s     r   	<genexpr>z_peers.<locals>.<genexpr>   s=      GGDSYY__5F5FGc$iiGGGGGGr   )	r   r   r    r!   r"   r#   r$   r.   r%   )r*   cfg_pathr'   r    r(   peerss         r   _peersrG   l   s    -'!! 	I  ') DDc!!I~~c"")3D$)?)?I%%%T%&& 	IGGEGGGGGG   			s"   B/ B/ AB/ B/ /B>=B>c                r    t          |           }|sdS d                    d |D                       }d| dS )uB   Protocol addendum for cross-machine DMs — only when peers exist. , c              3  "   K   | ]
}d | d V  dS `Nr;   )rC   ps     r   rD   z"_peer_paragraph.<locals>.<genexpr>   s*      //Ax1xxx//////r   zO

Teammates on OTHER machines: this install also has peer gateways registered (ux  ). Message an agent on a peer the same way — write the message to a temp file first, then pipe it on stdin (same terminal-tool pattern: background=true, notify_on_complete=true; the reply prints on stdout when it completes):
```
hermes peer dm <peer>/<agent-name> < /tmp/dm.txt
```
Use `<peer>` alone for the peer's main agent. Run `hermes peer list` for the live peer list.)rG   join)r*   rF   listeds      r   _peer_paragraphrQ      sV    4LLE rYY///////F		"		" 		" 		"r   c                   t          |           }t          |           t          |          }t          d |D                       sdS dk    r| n|dz  z  }t	          |          rdS t                    }d                    fd|D                       pd}t           d| d	| d
| dt          |          z   S )Nc              3  :   K   | ]\  }}t          |          V  d S rA   r)   )rC   _nds      r   rD   z!_build_section.<locals>.<genexpr>   s.      66eb!q!!666666r   rI   r   r	   rJ   c              3  6   K   | ]\  }}|k    d | d V  dS rL   r;   )rC   n_dmes      r   rD   z!_build_section.<locals>.<genexpr>   s3      BBuq"!r''(a(((''''BBr   z
(none yet)u9  
This install runs Bot Mode: each Hermes profile is an agent teammate with one canonical "Bot Chat" conversation. To message a teammate: write the message to a temp file with the file tool FIRST (never inline it into the command — quotes truncate it and $( ) would execute), then run on the terminal tool (background=true, notify_on_complete=true) and finish your turn — the reply arrives later as a new message:
```
hermes -p <agent-name> chat --in ~ -c "Bot Chat" --create-if-missing -Q --query-file /tmp/dm.txt
```
The file must open with the "Message from 🤖 z (@u  ):" prefix so they know who is talking. When YOU receive a message with that prefix, you are being messaged by a teammate agent — address them (not the user) and reply concisely. When the user says "ask <name>" or "tell <name> ...", that is a handoff: message that agent, wait for the reply, and report back, saying which agent it came from. Run `hermes profile list` for the LIVE teammate list before a handoff. Teammates at session start: .)	r   r   r3   anyr8   r<   rO   r6   rQ   )r   r*   rostermy_dirhandle	teammatesrZ   s         @r   _build_sectionra      s   D	t		BT]]F66v66666 r 9__TT$*;b*@F&!! rR[[F		BBBBBBBBBRlI  	K 	K ;A	K 	K FL	K 	K  ?H!	K 	K 	K" $

#	 r   F)force_refreshstr | os.PathLike | Nonerb   c               |   | rt          |           n2t          j        d          pt          j                            d          }t
          5  |s	|t          vr@	 t          t          |                    t          |<   n# t          $ r dt          |<   Y nw xY wt          |         cddd           S # 1 swxY w Y   dS )u   Cached probe entry point — one filesystem pass per (process, home).

    ``home`` should be the AGENT'S OWN resolved home (session-db derived),
    not the ambient HERMES_HOME — build threads can lose the ContextVar
    override and the env var would then name the wrong profile.
    HERMES_HOME	~/.hermesrI   N)
r   osgetenvpath
expanduser_lockr   ra   r   r%   )r   rb   resolveds      r   get_bot_mode_protocol_sectionrm      s    !cs4yyyry'?'?'b27CUCUVaCbCbH	 ! ! 	'HG33'$24>>$B$B!! ' ' '$&!!!'x ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !s6   B1$A>=B1>BB1BB11B58B5zCapability epoch: z Capability epoch: ([0-9a-f]{12})c                   ddl }ddl}t          | rt          |           n2t	          j        d          pt          j                            d                    }i }	 ddlm	} ddl
m}m}  |t          |                    }	  |            pi }	 ||           n#  ||           w xY wt          |	                    d          t                    r|	                    d          ni }
t          |	                    d          t                    r|	                    d          ni }|
pi }
|pi }t!          d	 |
                    d
          pg D                       |d<   t!          d |                    d          pg D                       |d<   |	                    d          }t          |t                    r|                    |dt                    nd|d<   n# t$          $ r Y nw xY w	 |dz  }|                                r9|                    |                                                                          nd|d<   n# t$          $ r d|d<   Y nw xY w	 g }|dz  }|                                rR|                    d          D ]<}|                    t          |j                            |                               =t!          |          |d<   n# t$          $ r g |d<   Y nw xY w	 t9          |          }t!          d t;          |          D                       |d<   n# t$          $ r g |d<   Y nw xY w	 t=          t9          |                    |d<   n# t$          $ r g |d<   Y nw xY w	 |                    |d                              d          }|                    |                                          dd         S # t$          $ r Y dS w xY w)u  12-hex digest of the capability surface for ``home``'s profile.

    Sources: the profile's disabled skills + enabled toolsets + MCP server
    config (config.yaml), SOUL.md bytes, installed skill names, and the
    Bot-Mode roster (managed profile names). Deliberately NOT cached — the
    whole point is detecting on-disk drift; callers compare it against the
    epoch embedded in a stored prompt. Never raises.
    r   Nre   rf   )load_config_readonly)reset_hermes_home_overrideset_hermes_home_overrideskillstoolsc              3  X   K   | ]%}t          |                                          V  &d S rA   )r   lower)rC   ss     r   rD   z)capability_fingerprint.<locals>.<genexpr>   s0      +g+gqCFFLLNN+g+g+g+g+g+gr   disableddisabled_skillsc              3  4   K   | ]}t          |          V  d S rA   )r   )rC   ts     r   rD   z)capability_fingerprint.<locals>.<genexpr>   s(      ,g,gSVV,g,g,g,g,g,gr   enabled_toolsetsmcp_serversT)	sort_keysr   rI   mcpr5   r7   z**/SKILL.mdc              3  >   K   | ]\  }}t          |          |V  d S rA   rT   )rC   rX   rV   s      r   rD   z)capability_fingerprint.<locals>.<genexpr>  s4      "U"UA/RSBTBT"U1"U"U"U"U"U"Ur   r]   rF   )r}   r      unavailable) hashlibjsonr   r   rg   rh   ri   rj   hermes_cli.configro   hermes_constantsrp   rq   r"   r$   r#   r.   dumpsr%   r   sha256
read_bytes	hexdigestr-   globr0   r   relative_tor   r3   rG   encode)r   r   r   rl   surfacero   rp   rq   tokencfg
skills_cfg	tools_cfgr~   r7   namesskills_rootskill_mdr*   blobs                      r   capability_fingerprintr      sy    NNNKKKhCIIIBIm,D,D,gHZHZ[fHgHgiiHG 	;:::::YYYYYYYY((X77	.&&((.BC&&u----&&u----*4SWWX5F5F*M*MUSWWX&&&SU
(237773C3CT(J(JRCGGG$$$PR	%2
O	%++g+gZ^^T^E_E_Eece+g+g+g%g%g!"&,,g,gimmL^>_>_>ece,g,g,g&g&g"#ggm$$ISTWY]I^I^fC4EEEdf   )#KO<<>>a'..):):;;EEGGG_a   ) 	L',,];; L LS!<!<[!I!IJJKKKK"5MM   H%%""U"U"U"U"UUU    ",x"8"899   zz'Tz2299'BB~~d##--//44   }}s   &G! B G! B))D7G! !
G.-G.2AI
 
II A?K   K21K268L/ /M MM% %M76M7;AO 
O"!O"c                2    t            t          |            S )z.The epoch stamp appended to a Bot Chat prompt.)_EPOCH_PREFIXr   r   s    r   
epoch_liner   &  s    ;3D99;;;r   stored_promptc                    ddl }	 |                    t          | pd          }|sdS t          |          }|dk    rdS |                    d          |k    S # t
          $ r Y dS w xY w)u<  True when ``stored_prompt`` is a Bot Chat prompt whose embedded
    capability epoch no longer matches the current disk state.

    Non-Bot-Chat prompts (no epoch stamp) are never stale by this check.
    Fails closed to "not stale" — a broken probe must never turn into a
    rebuild-every-turn cache burner.
    r   NrI   Fr      )research_EPOCH_RE_TEXTr   groupr%   )r   r   r   mcurrents        r   stored_prompt_capability_staler   +  s     III	IInm&9r:: 	5(..m##5wwqzzW$$   uus   A A A 
A%$A%c                    	 t           | pdv rdS t          | pdv rdS t          t          |                    S # t          $ r Y dS w xY w)u  True when a Bot Chat session's stored prompt PREDATES this feature.

    Legacy Bot Chats (created before bundling / this epoch mechanism)
    persisted prompts with no protocol section and no epoch stamp; without
    an explicit upgrade they would be stranded forever — the staleness check
    above only fires on stamped prompts. This is a one-time migration per
    legacy session: the caller must only invoke it for sessions titled
    "Bot Chat", and only rebuilds when the probe would actually emit a
    section (a profile whose SOUL.md already carries the legacy plugin-side
    append keeps its protocol-free prompt — rebuilding those would loop,
    since the probe stays silent and the rebuilt prompt would be unstamped
    again). Fails closed to "no upgrade".
    rI   F)r   r6   r   rm   r%   )r   r   s     r   $stored_bot_chat_prompt_needs_upgrader   A  sl    
]0b115!4"555 1$77888   uus   8 8 8 
AANonec                 x    t           5  t                                           d d d            d S # 1 swxY w Y   d S rA   )rk   r   clearr;   r   r   _reset_cache_for_testsr   \  sy    	                   s   /33)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   rA   )r   rc   rb   r   r   r   )r   rc   r   r   )r   r   r   rc   r   r   )r   r   )__doc__
__future__r   rg   	threadingpathlibr   r6   BOT_CHAT_TITLELockrk   r   __annotations__r   r   r)   r3   r8   r<   rG   rQ   ra   rm   r   r   r   r   r   r   r   r;   r   r   <module>r      s    : # " " " " " 				          / 
 	             *      3 3 3 3
   0   ($ $ $ $N!bg ! ! ! ! ! !> %4A A A A AH< < < < <
    ,    6     r   