§
    ¦¨îi31  ã                   ó$  — d Z ddlZddlZddlmZ ddlmZmZmZmZ ddl	m
Z
 ddlmZ  ej        e¦  «        Z e
¦   «         dz  Zded	efd
„Zdedeeef         d	efd„Zdeeef         d	ee         fd„Zdeeef         d	efd„Zdeeef         d	eeef         fd„Zd	eeeef                  fd„Zd	eeeef                  fd„Zded	eeeef                  fd„Zd	eeef         fd„Zdeded	ee         fd„Zdeded	ee         fd„Zd	efd„ZdS )a6  
Channel directory -- cached map of reachable channels/contacts per platform.

Built on gateway startup, refreshed periodically (every 5 min), and saved to
~/.hermes/channel_directory.json.  The send_message tool reads this file for
action="list" and for resolving human-friendly channel names to numeric IDs.
é    N)Údatetime)ÚAnyÚDictÚListÚOptional)Úget_hermes_home)Úatomic_json_writezchannel_directory.jsonÚvalueÚreturnc                 ót   — |                       d¦  «                             ¦   «                              ¦   «         S )Nú#)ÚlstripÚstripÚlower)r
   s    ú;/home/div/.hermes/hermes-agent/gateway/channel_directory.pyÚ_normalize_channel_queryr      s,   € Ø�<Š<˜ÑÔ×"Ò"Ñ$Ô$×*Ò*Ñ,Ô,Ð,ó    Úplatform_nameÚchannelc                 ó¨   — |d         }| dk    r|                      d¦  «        rd|› �S | dk    r#|                      d¦  «        r|› d|d         › d�S |S )zHReturn the human-facing target label shown to users for a channel entry.ÚnameÚdiscordÚguildr   Útypez (ú))Úget)r   r   r   s      r   Ú_channel_target_namer      so   € à�6Œ?€DØ˜	Ò!Ð! g§k¢k°'Ñ&:Ô&:Ð!Ø�4ˆzˆzÐØ˜	Ò!Ð! g§k¢k°&Ñ&9Ô&9Ð!ØÐ,Ð,˜' &œ/Ð,Ð,Ð,Ð,Ø€Kr   Úoriginc                 óŽ   — |                       d¦  «        }|sd S |                       d¦  «        }|r|› d|› �S t          |¦  «        S )NÚchat_idÚ	thread_idú:©r   Ústr)r   r    r!   s      r   Ú_session_entry_idr%   $   sY   € Ø�jŠj˜Ñ#Ô#€GØð ØˆtØ—
’
˜;Ñ'Ô'€IØð (ØÐ'Ð'˜IÐ'Ð'Ð'Ýˆw‰<Œ<Ðr   c                 ó  — |                       d¦  «        p6|                       d¦  «        p!t          |                       d¦  «        ¦  «        }|                       d¦  «        }|s|S |                       d¦  «        pd|› �}|› d|› �S )NÚ	chat_nameÚ	user_namer    r!   Ú
chat_topicztopic z / r#   )r   Ú	base_namer!   Útopic_labels       r   Ú_session_entry_namer,   .   s�   € Ø—
’
˜;Ñ'Ô'Ð`¨6¯:ª:°kÑ+BÔ+BÐ`ÅcÈ&Ï*Ê*ÐU^ÑJ_ÔJ_ÑF`ÔF`€IØ—
’
˜;Ñ'Ô'€IØð ØÐà—*’*˜\Ñ*Ô*ÐBÐ.B°yÐ.BÐ.B€KØÐ)Ð)˜KÐ)Ð)Ð)r   Úadaptersc              ƒ   ó”  K  — ddl m} i }|                      ¦   «         D ]\  }}	 ||j        k    rt	          |¦  «        |d<   n#||j        k    rt          |¦  «        ƒ d{V —†|d<   ŒH# t          $ r+}t           	                    d|j
        |¦  «         Y d}~Œxd}~ww xY wt          h d£¦  «        }|D ]$}|j
        }||v s||v rŒt          |¦  «        ||<   Œ%t          j        ¦   «                              ¦   «         |dœ}		 t!          t"          |	¦  «         n2# t          $ r%}t           	                    d	|¦  «         Y d}~nd}~ww xY w|	S )
z—
    Build a channel directory from connected platform adapters and session data.

    Returns the directory dict and writes it to DIRECTORY_PATH.
    r   )ÚPlatformr   NÚslackz)Channel directory: failed to build %s: %s>   ÚlocalÚwebhookÚ
api_server©Ú
updated_atÚ	platformsz&Channel directory: failed to write: %s)Úgateway.configr/   ÚitemsÚDISCORDÚ_build_discordÚSLACKÚ_build_slackÚ	ExceptionÚloggerÚwarningr
   Ú	frozensetÚ_build_from_sessionsr   ÚnowÚ	isoformatr	   ÚDIRECTORY_PATH)
r-   r/   r6   ÚplatformÚadapterÚeÚ_SKIP_SESSION_DISCOVERYÚplatÚ	plat_nameÚ	directorys
             r   Úbuild_channel_directoryrL   <   sÏ  è è € ð (Ð'Ð'Ð'Ð'Ð'à13€Ià%Ÿ^š^Ñ-Ô-ð [ð [Ñˆ�'ð	[Ø˜8Ô+Ò+Ð+Ý'5°gÑ'>Ô'>�	˜)Ñ$Ð$Ø˜Xœ^Ò+Ð+Ý+7¸Ñ+@Ô+@Ð%@Ð%@Ð%@Ð%@Ð%@Ð%@�	˜'Ñ"øøÝð 	[ð 	[ð 	[Ý�NŠNÐFÈÌÐXYÑZÔZÐZÐZÐZÐZÐZÐZøøøøð	[øøøõ (Ð(JÐ(JÐ(JÑKÔKÐØð ?ð ?ˆØ”Jˆ	ØÐ/Ð/Ð/°9À	Ð3IÐ3IØÝ3°IÑ>Ô>ˆ	�)ÑÐõ ”l‘n”n×.Ò.Ñ0Ô0Øðð €Ið
DÝ�.¨)Ñ4Ô4Ð4Ð4øÝð Dð Dð DÝ�ŠÐ?ÀÑCÔCÐCÐCÐCÐCÐCÐCøøøøðDøøøð Ðs0   ¥AA'Á'
BÁ1!BÂBÄ D Ä
EÄ E Å Ec           	      óØ  — g }t          | dd¦  «        }|s|S 	 ddl}n# t          $ r |cY S w xY w|j        D ]�}|j        D ]8}|                     t          |j        ¦  «        |j        |j        ddœ¦  «         Œ9t          |dd¦  «        pg }|D ]8}|                     t          |j        ¦  «        |j        |j        ddœ¦  «         Œ9Œ‘| 	                    t          d¦  «        ¦  «         |S )	zGEnumerate all text channels and forum channels the Discord bot can see.Ú_clientNr   r   )Úidr   r   r   Úforum_channelsÚforumr   )Úgetattrr   ÚImportErrorÚguildsÚtext_channelsÚappendr$   rO   r   ÚextendrA   )rF   ÚchannelsÚclientÚ_discordr   ÚchÚforumss          r   r:   r:   f   sK  € à€HÝ�W˜i¨Ñ.Ô.€FØð ØˆðØ"Ð"Ð"Ð"Ð"øÝð ð ð Øˆˆˆðøøøð ”ð ð ˆØÔ%ð 	ð 	ˆBØ�OŠOÝ˜"œ%‘j”jØœØœØ!ð	ð ñ ô ð ð õ ˜Ð 0°$Ñ7Ô7Ð=¸2ˆØð 	ð 	ˆBØ�OŠOÝ˜"œ%‘j”jØœØœØð	ð ñ ô ð ð ð	ð ‡O‚OÕ(¨Ñ3Ô3Ñ4Ô4Ð4Ø€Os   ™ ž-¬-c           
   ƒ   óV  K  — t          | dd¦  «        pi }|st          d¦  «        S g }t          ¦   «         }|                     ¦   «         D �]v\  }}	 d}t	          d¦  «        D �](}|                     ddd|¬¦  «        ƒ d{V —†}|                     d	¦  «        s2t                               d
||                     dd¦  «        ¦  «          nÁ|                     dg ¦  «        D ]z}	|	                     d¦  «        }
|	                     d¦  «        }|
r|r|
|v rŒ5| 	                    |
¦  «         | 
                    |
||	                     d¦  «        rdnddœ¦  «         Œ{|                     d¦  «        pi                      d¦  «        }|s n�Œ*�ŒD# t          $ r'}t                               d||¦  «         Y d}~�Œpd}~ww xY wt          d¦  «        D ]V}|                     d¦  «        |vr=| 
                    |¦  «         | 	                    |                     d¦  «        ¦  «         ŒW|S )a.  List Slack channels the bot has joined across all workspaces.

    Uses ``users.conversations`` against each workspace's web client. Pulls
    public + private channels the bot is a member of, then merges in DMs
    discovered from session history (IMs aren't useful to enumerate
    proactively).
    Ú_team_clientsNr0   é   zpublic_channel,private_channelTéÈ   )ÚtypesÚexclude_archivedÚlimitÚcursorÚokz=Channel directory: users.conversations not ok for team %s: %sÚerrorÚunknownrX   rO   r   Ú
is_privateÚprivater   )rO   r   r   Úresponse_metadataÚnext_cursorz@Channel directory: failed to list Slack channels for team %s: %s)rR   rA   Úsetr8   ÚrangeÚusers_conversationsr   r>   r?   ÚaddrV   r=   )rF   Úteam_clientsrX   Úseen_idsÚteam_idrY   rd   Ú_pageÚresponser[   Úcidr   rG   Úentrys                 r   r<   r<   ‹   sž  è è € õ ˜7 O°TÑ:Ô:Ð@¸b€LØð -Ý# GÑ,Ô,Ð,à%'€HÝ‘E”E€Hà'×-Ò-Ñ/Ô/ð $ñ $‰ˆ�ð#	Ø$(ˆFÝ˜r™œð ñ �Ø!'×!;Ò!;Ø:Ø%)ØØ!ð	 "<ñ "ô "ð ð ð ð ð ð �ð  —|’| DÑ)Ô)ð Ý—N’NØWØØ Ÿš W¨iÑ8Ô8ñô ð ð
 �EØ"Ÿ,š, z°2Ñ6Ô6ð 
ð 
�BØŸ&š& ™,œ,�CØŸ6š6 &™>œ>�DØð ! dð !¨c°X¨o¨oØ Ø—L’L Ñ%Ô%Ð%Ø—O’OØ!Ø $Ø-/¯VªV°LÑ-AÔ-AÐ P 	 	Àyð%ð %ñ ô ð ð ð
 #Ÿ,š,Ð':Ñ;Ô;ÐA¸r×FÒFÀ}ÑUÔU�Øð Ø�Eñùøåð 	ð 	ð 	Ý�NŠNØRØ˜ñô ð ð ˆHˆHˆH‰Høøøøð	øøøõ & gÑ.Ô.ð *ð *ˆØ�9Š9�T‰?Œ? (Ð*Ð*Ø�OŠO˜EÑ"Ô"Ð"Ø�LŠL˜Ÿš 4™œÑ)Ô)Ð)øà€Os   ÁD;FÆ
G ÆF;Æ;G c           	      óæ  — t          ¦   «         dz  dz  }|                     ¦   «         sg S g }	 t          |d¬¦  «        5 }t          j        |¦  «        }ddd¦  «         n# 1 swxY w Y   t          ¦   «         }|                     ¦   «         D ]¯\  }}|                     d¦  «        pi }|                     d¦  «        | k    rŒ6t          |¦  «        }	|	r|	|v rŒL| 	                    |	¦  «         | 
                    |	t          |¦  «        |                     dd	¦  «        |                     d
¦  «        dœ¦  «         Œ°n3# t          $ r&}
t                               d| |
¦  «         Y d}
~
nd}
~
ww xY w|S )z<Pull known channels/contacts from sessions.json origin data.Úsessionszsessions.jsonúutf-8©ÚencodingNr   rE   Ú	chat_typeÚdmr!   )rO   r   r   r!   z5Channel directory: failed to read sessions for %s: %s)r   ÚexistsÚopenÚjsonÚloadrl   r8   r   r%   ro   rV   r,   r=   r>   Údebug)r   Úsessions_pathÚentriesÚfÚdatarq   Ú_keyÚsessionr   Úentry_idrG   s              r   rA   rA   É   sâ  € å#Ñ%Ô%¨
Ñ2°_ÑD€MØ×ÒÑ!Ô!ð Øˆ	à€Gð`Ý�-¨'Ð2Ñ2Ô2ð 	 °aÝ”9˜Q‘<”<ˆDð	 ð 	 ð 	 ñ 	 ô 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 øøøð 	 ð 	 ð 	 ð 	 õ ‘5”5ˆØ!ŸZšZ™\œ\ð 	ð 	‰MˆD�'Ø—[’[ Ñ*Ô*Ð0¨bˆFØ�zŠz˜*Ñ%Ô%¨Ò6Ð6ØÝ(¨Ñ0Ô0ˆHØð ˜x¨8Ð3Ð3ØØ�LŠL˜Ñ"Ô"Ð"Ø�NŠNØÝ+¨FÑ3Ô3ØŸš K°Ñ6Ô6Ø#ŸZšZ¨Ñ4Ô4ð	ð ñ ô ð ð ð	øõ ð `ð `ð `Ý�ŠÐLÈmÐ]^Ñ_Ô_Ð_Ð_Ð_Ð_Ð_Ð_øøøøð`øøøð €Ns;   ®D> ¿A ÁD> Á A$Á$D> Á'A$Á(CD> Ä>
E.ÅE)Å)E.c                  óò   — t                                ¦   «         sdi dœS 	 t          t           d¬¦  «        5 } t          j        | ¦  «        cddd¦  «         S # 1 swxY w Y   dS # t
          $ r di dœcY S w xY w)z,Load the cached channel directory from disk.Nr4   ry   rz   )rD   r~   r   r€   r�   r=   )r…   s    r   Úload_directoryr‹   í   s×   € å× Ò Ñ"Ô"ð 5Ø"°Ð4Ð4Ð4ð5Ý•.¨7Ð3Ñ3Ô3ð 	 °qÝ”9˜Q‘<”<ð	 ð 	 ð 	 ð 	 ñ 	 ô 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 øøøð 	 ð 	 ð 	 ð 	 ð 	 ð 	 øåð 5ð 5ð 5Ø"°Ð4Ð4Ð4Ð4Ð4ð5øøøs4    A$ ¶AÁ
A$ ÁAÁA$ ÁAÁA$ Á$A6Á5A6r    c                 óÜ   — t          ¦   «         }|                     di ¦  «                             | g ¦  «        D ]2}|                     d¦  «        |k    r|                     d¦  «        c S Œ3dS )ziReturn the channel ``type`` string (e.g. ``"channel"``, ``"forum"``) for *chat_id*, or *None* if unknown.r6   rO   r   N)r‹   r   )r   r    rK   r[   s       r   Úlookup_channel_typer�   ø   so   € åÑ Ô €IØ�mŠm˜K¨Ñ,Ô,×0Ò0°ÀÑCÔCð "ð "ˆØ�6Š6�$‰<Œ<˜7Ò"Ð"Ø—6’6˜&‘>”>Ð!Ð!Ð!ð #àˆ4r   r   c                 ó$  ‡
— t          ¦   «         }|                     di ¦  «                             | g ¦  «        }|sdS |                     ¦   «         }|D ]%}|                     d¦  «        |k    r
|d         c S Œ&t          |¦  «        Š
|D ]P}t          |d         ¦  «        ‰
k    r
|d         c S t          t	          | |¦  «        ¦  «        ‰
k    r
|d         c S ŒQd‰
v r�‰
                     dd¦  «        \  }}|D ]e}|                     dd¦  «                             ¦   «                              ¦   «         }||k    r#t          |d         ¦  «        |k    r
|d         c S Œfˆ
fd	„|D ¦   «         }	t          |	¦  «        dk    r|	d
         d         S dS )a  
    Resolve a human-friendly channel name to a numeric ID.

    Matching strategy (case-insensitive, first match wins):
    - Discord: "bot-home", "#bot-home", "GuildName/bot-home"
    - Telegram: display name or group name
    - Slack: "engineering", "#engineering"
    r6   NrO   r   ú/é   r   Ú c                 ód   •— g | ],}t          |d          ¦  «                             ‰¦  «        ¯*|‘Œ-S )r   )r   Ú
startswith)Ú.0r[   Úquerys     €r   ú
<listcomp>z(resolve_channel_name.<locals>.<listcomp>)  s:   ø€ Ð_Ð_Ð_�bÕ(@ÀÀFÄÑ(LÔ(L×(WÒ(WÐX]Ñ(^Ô(^Ð_ˆrÐ_Ð_Ð_r   r   )r‹   r   r   r   r   Úrsplitr   Úlen)r   r   rK   rX   Úrawr[   Ú
guild_partÚch_partr   Úmatchesr•   s             @r   Úresolve_channel_namer�     sÉ  ø€ õ Ñ Ô €IØ�}Š}˜[¨"Ñ-Ô-×1Ò1°-ÀÑDÔD€HØð Øˆtð
 �*Š*‰,Œ,€CØð ð ˆØ�6Š6�$‰<Œ<˜3ÒÐØ�d”8ˆOˆOˆOð õ % TÑ*Ô*€Eð ð ð ˆÝ# B v¤JÑ/Ô/°5Ò8Ð8Ø�d”8ˆOˆOˆOÝ#Õ$8¸ÈÑ$KÔ$KÑLÔLÐPUÒUÐUØ�d”8ˆOˆOˆOð Vð ˆe€|€|Ø#Ÿlšl¨3°Ñ2Ô2Ñˆ
�GØð 	 ð 	 ˆBØ—F’F˜7 BÑ'Ô'×-Ò-Ñ/Ô/×5Ò5Ñ7Ô7ˆEØ˜
Ò"Ð"Õ'?ÀÀ6Ä
Ñ'KÔ'KÈwÒ'VÐ'VØ˜$”x���øð `Ð_Ð_Ð_˜HÐ_Ñ_Ô_€GÝ
ˆ7�|„|�qÒÐØ�qŒz˜$ÔÐàˆ4r   c                  óº  — t          ¦   «         } |                      di ¦  «        }t          |                     ¦   «         ¦  «        sdS dg}t	          |                     ¦   «         ¦  «        D �]°\  }}|sŒ	|dk    �r3i }g }|D ]X}|                     d¦  «        }|r*|                     |g ¦  «                             |¦  «         ŒC|                     |¦  «         ŒYt	          |                     ¦   «         ¦  «        D ]Y\  }	}
|                     d|	› d�¦  «         t	          |
d„ ¬	¦  «        D ](}|                     d
t          ||¦  «        › �¦  «         Œ)ŒZ|r@|                     d¦  «         |D ](}|                     d
t          ||¦  «        › �¦  «         Œ)|                     d¦  «         �ŒC|                     | 	                    ¦   «         › d�¦  «         |D ]+}|                     d|› dt          ||¦  «        › �¦  «         Œ,|                     d¦  «         �Œ²|                     d¦  «         |                     d¦  «         d 
                    |¦  «        S )zDFormat the channel directory as a human-readable list for the model.r6   z?No messaging platforms connected or no channels discovered yet.zAvailable messaging targets:
r   r   z	Discord (z):c                 ó   — | d         S )Nr   © )Úcs    r   ú<lambda>z.format_directory_for_display.<locals>.<lambda>K  s
   € ¸qÀ¼y€ r   )Úkeyz
  discord:zDiscord (DMs):r‘   r"   z  z1Use these as the "target" parameter when sending.z;Bare platform name (e.g. "telegram") sends to home channel.Ú
)r‹   r   ÚanyÚvaluesÚsortedr8   Ú
setdefaultrV   r   ÚtitleÚjoin)rK   r6   ÚlinesrJ   rX   rT   Údmsr[   r   Ú
guild_nameÚguild_channelss              r   Úformat_directory_for_displayr¯   0  s¥  € åÑ Ô €IØ—’˜k¨2Ñ.Ô.€Iåˆy×ÒÑ!Ô!Ñ"Ô"ð QØPÐPà-Ð.€Eå% i§o¢oÑ&7Ô&7Ñ8Ô8ð ñ Ñˆ	�8Øð 	Øð ˜	Ò!Ñ!Ø&(ˆFØˆCØð #ð #�ØŸš˜w™œ�Øð #Ø×%Ò% e¨RÑ0Ô0×7Ò7¸Ñ;Ô;Ð;Ð;à—J’J˜r‘N”N�N�Nå.4°V·\²\±^´^Ñ.DÔ.Dð Uð UÑ*�
˜NØ—’Ð7¨Ð7Ð7Ð7Ñ8Ô8Ð8Ý  Ð5HÐ5HÐIÑIÔIð Uð U�BØ—L’LÐ!SÕ.BÀ9ÈbÑ.QÔ.QÐ!SÐ!SÑTÔTÐTÐTðUàð UØ—’Ð-Ñ.Ô.Ð.Øð Uð U�BØ—L’LÐ!SÕ.BÀ9ÈbÑ.QÔ.QÐ!SÐ!SÑTÔTÐTÐTØ�LŠL˜ÑÔÐÑà�LŠL˜IŸOšOÑ-Ô-Ð0Ð0Ð0Ñ1Ô1Ð1Øð Uð U�Ø—’ÐS )ÐSÐSÕ.BÀ9ÈbÑ.QÔ.QÐSÐSÑTÔTÐTÐTØ�LŠL˜ÑÔÐÑà	‡L‚LÐDÑEÔEÐEØ	‡L‚LÐNÑOÔOÐOà�9Š9�UÑÔÐr   )Ú__doc__r€   Úloggingr   Útypingr   r   r   r   Úhermes_cli.configr   Úutilsr	   Ú	getLoggerÚ__name__r>   rD   r$   r   r   r%   r,   rL   r:   r<   rA   r‹   r�   r�   r¯   r    r   r   ú<module>r·      s¥  ððð ð €€€Ø €€€Ø Ð Ð Ð Ð Ð Ø ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,à -Ð -Ð -Ð -Ð -Ð -Ø #Ð #Ð #Ð #Ð #Ð #à	ˆÔ	˜8Ñ	$Ô	$€à �Ñ"Ô"Ð%=Ñ=€ð- Cð -¨Cð -ð -ð -ð -ð¨ð °d¸3À¸8´nð Èð ð ð ð ð˜d 3¨ 8œnð °¸#´ð ð ð ð ð*  S¨# X¤ð *°3ð *ð *ð *ð *ð'¨D°°c°¬Nð '¸tÀCÈÀH¼~ð 'ð 'ð 'ð 'ðT"˜t D¨¨c¨¤NÔ3ð "ð "ð "ð "ðJ; 4¨¨S°#¨X¬Ô#7ð ;ð ;ð ;ð ;ð|¨ð °°T¸#¸s¸(´^Ô0Dð ð ð ð ðH5˜˜S #˜Xœð 5ð 5ð 5ð 5ð sð °Sð ¸XÀc¼]ð ð ð ð ð,¨ð ,°3ð ,¸8ÀC¼=ð ,ð ,ð ,ð ,ð^+ cð +ð +ð +ð +ð +ð +r   