
    )Gj                   @   d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlZddlmZmZ ddlmZ ddlmZmZmZmZmZ erddlZdZn	 ddlZdZn# e$ r dZdZY nw xY wdd	lmZmZ dd
lm Z m!Z!m"Z"m#Z#m$Z$ ddl%m&Z& ddl'm(Z(  ej)        e*          Z+dZ,dZ-dZ.dZ/dZ0 ee1          j2        dz  Z3dZ4dZ5ddgZ6dIdZ7dJdZ8dKd"Z9dKd#Z:dLd%Z;dJd&Z< G d' d(e           Z=dMd,Z>d-d.d/d0d-d-d-d1Z?d2d2d3d4d2d5d5d6Z@dd7dNd=ZAdddd>dOdFZBdPdHZCdS )Qu2  
Photon Spectrum (iMessage) platform adapter for Hermes Agent.

Both directions of traffic flow through a small supervised Node sidecar
(see ``sidecar/index.mjs``) that runs the ``spectrum-ts`` SDK — the SDK is
TypeScript-only and there is no public HTTP message API, so a sidecar is
unavoidable.

Inbound:
    The SDK's ``app.messages`` is a long-lived **gRPC** stream. The sidecar
    serializes each message to a normalized JSON event and streams it to this
    adapter over a loopback ``GET /inbound`` (NDJSON). A background task here
    consumes that stream, dedupes on ``messageId``, and dispatches a
    ``MessageEvent`` to the gateway via ``BasePlatformAdapter.handle_message``.
    No webhook, no public URL, no signing secret.

Outbound:
    ``send`` / ``send_typing`` are loopback POSTs to the sidecar's control
    endpoints, authenticated with a shared bearer token.  Outbound media
    (images, voice notes, video, documents) goes through spectrum-ts'
    ``attachment()`` / ``voice()`` content builders via the sidecar's
    ``/send-attachment`` endpoint.
    )annotationsN)datetimetimezone)Path)TYPE_CHECKINGAnyDictListOptionalTF)PlatformPlatformConfig)BasePlatformAdapterMessageEventMessageTypeProcessingOutcome
SendResult)strip_markdown   )load_project_credentialsiU"  z	127.0.0.1i@  i  i  sidecar)zinternal sidecar errorzupstream connect errorzupstream unavailablezconnection droppedzreset reason: overflowupstream_overflowupstream_unavailable      @z#(?<![\w@])@?hermes\s+agent\b[,:\-]?z(?<![\w@])@?hermes\b[,:\-]?valuer   defaultintreturnc                T    	 t          |           S # t          t          f$ r |cY S w xY wN)r   	TypeError
ValueError)r   r   s     F/home/rurouni/.hermes/hermes-agent/plugins/platforms/photon/adapter.py_coerce_portr#   w   s<    5zzz"   s    ''boolc                     t           sdS t          j        t          j        d          pd          sdS t
          dz                                  sdS dS )zEReturn True when both Python deps and the Node sidecar are available.FPHOTON_NODE_BINnodenode_modulesT)HTTPX_AVAILABLEshutilwhichosgetenv_SIDECAR_DIRexists     r"   check_requirementsr2   ~   s[     u<	"344>?? u>)1133  u4r1   cfgr   c                   | j         pi }|                    d          pt          j        d          }|                    d          pt          j        d          }|r|s"t	                      \  }}t          |o|          S dS )N
project_idPHOTON_PROJECT_IDproject_secretPHOTON_PROJECT_SECRETT)extragetr,   r-   r   r$   )r3   r9   r5   r7   	stored_id
stored_secs         r"   validate_configr=      s    IOE<((JBI6I,J,JJYY/00VBI>U4V4VN .^ . 8 : :	:I,*---4r1   c                     t          |           S r   )r=   r3   s    r"   is_connectedr@      s    3r1   Optional[dict]c                     t                      \  } }| r|sdS | |d}t          j        dd                                          }|r|t          j        dd          d|d<   |S )	zSeed PlatformConfig.extra from env so env-only setups appear in status.

    The special ``home_channel`` key is handled by the core plugin hook and
    becomes a proper ``HomeChannel`` on ``PlatformConfig``.
    N)r5   r7   PHOTON_HOME_CHANNEL PHOTON_HOME_CHANNEL_NAMEHome)chat_idnamehome_channel)r   r,   r-   strip)r5   r7   seedhomes       r"   _env_enablementrM      s     ":!;!;J > t *nMMD9*B//5577D 
I8&AA 
  
^ Kr1   c                 x    t          j        dd                                                                          dvS )a:  Send agent replies as markdown (spectrum-ts ``markdown()`` builder).

    iMessage renders it natively; other Spectrum platforms degrade to
    readable plain text. On-device rendering can't be unit-tested, so
    ``PHOTON_MARKDOWN=false`` is the kill-switch back to stripped plain
    text without a release.
    PHOTON_MARKDOWNtrue>   0nofalser,   r-   rJ   lowerr0   r1   r"   _markdown_enabledrV      s<     9&//5577==?? H  r1   c                  j    e Zd ZdZeZdk fdZedld	            ZdmdZ	dndZ
dddodZdpdZdpdZdpdZdqdZdrdZdsdZedtd!            Zedud#            Zedud$            Zdpd%Zdpd&Zdvd)Zdpd*Z	 	 dwdxd3Z	 	 	 dydz fd6Z	 	 	 dyd{d8Z	 	 	 dyd|d:Z	 	 	 dyd}d<Z	 	 	 	 d~dd?Z	 	 	 dyddAZdddBZ ddCZ!dDZ"dEZ#ddGZ$ e%j&        dH          Z'e(ddI            Z)ddJZ*ddKZ+ddMZ,ddNZ-	 dddOZ.	 dddPZ/ddRZ0ddUZ1ddVZ2ddWZ3eddY            Z4	 	 	 	 ddd_Z5ddaZ6d+d+d+dbdcddhZ7ddjZ8 xZ9S )PhotonAdapterzBidirectional bridge to Photon Spectrum via the Node spectrum-ts sidecar.

    Inbound: consume the sidecar's ``/inbound`` gRPC stream.
    Outbound: loopback POSTs to the sidecar's control channel.
    configr   c                $   t                                          |t          d                     |j        pi }t	                      \  }}t          j        d          p|                    d          p|pd| _        t          j        d          p|                    d          p|pd| _	        t          |                    d          pt          j        d          t                    | _        t          | _        t          j        d	          pt          j        d
          | _        t%          t          j        dd                                                    dv| _        t          j        d          pt+          j        d          pd| _        t1                      | _        d | _        d | _        d | _        d | _        d| _        d | _        d| _         i | _!        i | _"        i | _#        i | _$        |                    d          }|t          j        d          }t%          |          %                                                                dv | _&        | '                    d|v r|d         nt          j        d                    | _(        d S )Nphotonr6   r5   rD   r8   r7   sidecar_portPHOTON_SIDECAR_PORTPHOTON_SIDECAR_TOKEN   PHOTON_SIDECAR_AUTOSTARTrP   )rQ   rS   rR   r&   r'   F      .@require_mentionPHOTON_REQUIRE_MENTION>   1onyesrP   mention_patternsPHOTON_MENTION_PATTERNS))super__init__r   r9   r   r,   r-   r:   _project_id_project_secretr#   _DEFAULT_SIDECAR_PORT_sidecar_port_DEFAULT_SIDECAR_BIND_sidecar_bindsecrets	token_hex_sidecar_tokenstrrU   _autostart_sidecarr*   r+   	_node_binrV   supports_code_blocks_sidecar_proc_sidecar_supervisor_task_inbound_task_sidecar_health_task_inbound_running_http_client_sidecar_health_interval_seen_messages_sent_message_ids_last_inbound_by_chat_typing_last_sentrJ   rb   _compile_mention_patterns_mention_patterns)selfrY   r9   r;   r<   _require_mention	__class__s         r"   rj   zPhotonAdapter.__init__   s   (!3!3444"
 !9 : :	:I)** yy&& 	 	 I-.. yy)** 	 	 *IIn%%I3H)I)I!
 
 3I,--F1B21F1F 	 #&I0&99#
 #

%''-#. #455Wf9M9MWQW %6$7$7! :>@D%59<@! %;?(,% 13 46 68"35
 !99%677#!y)ABB"#344::<<BBDD I
  
 "&!?!?!U** $%%455"
 "
r1   rawr   r   'list[re.Pattern]'c                   | t          t                    }nt          | t                    rv|                                 }	 |rt          j        |          ng }n# t          $ r d}Y nw xY wt          |t                     r|nd |                                D             }nt          | t                     r| }n| g}g }|D ]}t          |                                          }|s&	 |	                    t          j        |t          j                             Z# t          j        $ r&}t                              d||           Y d}~d}~ww xY w|S )aD  Compile group-mention wake words from config/env.

        ``raw`` is a list (config or env JSON), a string (env var: JSON
        list, or comma/newline-separated), or None (use Hermes defaults).
        Mirrors the BlueBubbles implementation so both iMessage channels
        accept the same configuration shapes.
        Nc                f    g | ].}|                     d           D ]}|                                /S ),)splitrJ   ).0lineparts      r"   
<listcomp>z;PhotonAdapter._compile_mention_patterns.<locals>.<listcomp>&  sZ     @ @ @ JJsOO@ @  

@ @ @ @r1   z'[photon] Invalid mention pattern %r: %s)list_DEFAULT_MENTION_PATTERNS
isinstancert   rJ   jsonloads	Exception
splitlinesappendrecompile
IGNORECASEerrorloggerwarning)r   patternstextloadedcompiledpatternexcs          r"   r   z'PhotonAdapter._compile_mention_patterns  s    ;566HHS!! 	99;;D-19D)))r   !+FD!9!9 vv @ @ OO--@ @ @HH
 T"" 	HHuH') 	U 	UGw<<%%''D U
4 ? ?@@@@8 U U UH$PSTTTTTTTTUs*   A A*)A*)2DE+EEr   rt   r$   c                Z    r| j         sdS t          fd| j         D                       S )NFc              3  B   K   | ]}|                               V  d S r   )search)r   r   r   s     r"   	<genexpr>zBPhotonAdapter._message_matches_mention_patterns.<locals>.<genexpr>>  s/      NNG7>>$''NNNNNNr1   )r   any)r   r   s    `r"   !_message_matches_mention_patternsz/PhotonAdapter._message_matches_mention_patterns;  s@     	41 	5NNNNt7MNNNNNNr1   c                   |s|S | j         D ]r}|                    |                                          }|rG|                                |                                d                             d          }|p|c S s|S )zStrip a leading wake word before dispatch.

        Custom mention patterns are regexes, so we only strip a leading
        match to avoid deleting ordinary words later in the prompt.
        Nz ,:-)r   matchlstripend)r   r   r   r   cleaneds        r"   _clean_mention_textz!PhotonAdapter._clean_mention_text@  s      	K- 	' 	'GMM$++--00E '++--		5<<VDD$&&&' r1   F)is_reconnectr   c               l  K   t           s|                     ddd           dS | j        r| j        s|                     ddd           dS t	          j        d          }|| _        | j        rp	 |                                  d {V  nn# t          $ rG}|                     d	d
| d           |
                                 d {V  d | _        Y d }~dS d }~ww xY wt                              d           d| _        t          j                                        |                                           | _        t          j                                        |                                           | _        |                                  t                              d| j        | j                   dS )NMISSING_DEPhttpx not installedF	retryableMISSING_CREDENTIALSzRPHOTON_PROJECT_ID and PHOTON_PROJECT_SECRET are required. Run: hermes photon setup      >@timeoutSIDECAR_FAILEDz failed to start Photon sidecar: TuD   [photon] sidecar autostart disabled — inbound + outbound will failuD   [photon] connected — sidecar on %s:%d, streaming inbound over gRPC)r)   _set_fatal_errorrk   rl   httpxAsyncClientr}   ru   _start_sidecarr   acloser   r   r|   asyncioget_event_loopcreate_task_inbound_looprz   _monitor_sidecar_healthr{   _mark_connectedinforp   rn   )r   r   clientes       r"   connectzPhotonAdapter.connectQ  s      	!!4 "    5 	t'; 	!!%+	 "    5"4000" " 	
))++++++++++   %%$:q::" &   
 mmoo%%%%%%%$(!uuuuu NNV  
 !%$355AA  
 
 %,$:$<$<$H$H((**%
 %
! 	R 2	
 	
 	
 ts   0B 
C<CCNonec                n  K   d| _         | j        a| j        }d | _        |                                 |t          j                    ur*	 | d {V  n # t          j        $ r Y nt          $ r Y nw xY w| j        O| j                                         	 | j         d {V  n # t          j        $ r Y nt          $ r Y nw xY wd | _        |                                  d {V  | j	        8	 | j	        
                                 d {V  n# t          $ r Y nw xY wd | _	        |                                  d S )NF)r|   r{   cancelr   current_taskCancelledErrorr   rz   _stop_sidecarr}   r   _mark_disconnected)r   tasks     r"   
disconnectzPhotonAdapter.disconnect  s      %$0,D(,D%KKMMM7/1111JJJJJJJJ-   D    D)%%'''((((((((()      !%D  """""""""('..0000000000    $D!!!!!sB   	A A/#	A/.A/B! !B>2	B>=B>*D
 

DDc                  K   | j         }|dS d| j         d| j         d}d| j        i}d}| j        rI	 |                    d||d          4 d{V 	 }|j        d	k    rt          d
|j                   d}|                                2 3 d{V }| j        s n4|	                                }|s'| 
                    |           d{V  C6 	 ddd          d{V  n# 1 d{V swxY w Y   n|# t          j        $ r  t          $ r`}| j        sY d}~dS t                              d||           t          j        |           d{V  t#          |dz  d          }Y d}~nd}~ww xY w| j        GdS dS )zConsume the sidecar's ``/inbound`` NDJSON stream, with reconnect.

        The sidecar owns the gRPC reconnect/heartbeat to Photon; this loop
        only has to re-open the loopback HTTP stream if it drops (e.g. the
        sidecar restarts).
        Nhttp://:z/inboundX-Hermes-Sidecar-Tokeng      ?GETheadersr      z/inbound returned z;[photon] inbound stream dropped (%s); reconnecting in %.1fs   r   )r}   rp   rn   rs   r|   streamstatus_codeRuntimeErroraiter_linesrJ   _on_inbound_liner   r   r   r   r   sleepmin)r   r   urlr   backoffrespr   r   s           r"   r   zPhotonAdapter._inbound_loop  s      ">FI*IIT-?III+T-@A# 	11!==3 )   : : : : : : : :'3..*+R@P+R+RSSS!G&*&6&6&8&8 : : : : : : :d#4 "!E#zz||# %$"33D9999999999 '9&8: : : : : : : : : : : : : : : : : : : : : : : : : : : )    1 1 1, EEEEEQw   mG,,,,,,,,,gk4001# # 	1 	1 	1 	1 	1sN   C7 9C%C>C%C7 %
C//C7 2C/3C7 7E0E+A	E++E0c                  K   | j         rt          j        | j                   d{V  | j         sdS 	 |                     di            d{V }nA# t          j        $ r  t          $ r%}t                              d|           Y d}~d}~ww xY wt          |t                    r|                    d          nd}t          |t                    r|                    d          durt          |                    d          pd          }|                    d	          }t          |                    d
          pd          }d| d| d| }t                              d|           |                     d|d           	 |                                  d{V  n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wdS dS )a  Promote degraded upstream Photon stream health into reconnect.

        The sidecar HTTP process can stay alive while spectrum-ts repeatedly
        fails to maintain the upstream inbound gRPC stream. Polling `/healthz`
        keeps that from becoming a silent inbound outage.
        N/healthzz([photon] sidecar health check failed: %sr   okFstateunknowndegradedForMs	lastIssuezunknown stream issuez'Photon upstream stream degraded (state=z, degradedForMs=z): [photon] %sUPSTREAM_STREAM_DEGRADEDTr   ,[photon] fatal-error notification failed: %s)r|   r   r   r~   _sidecar_callr   r   r   debugr   dictr:   rt   r   r   _notify_fatal_errorr   )r   datar   r   r   degraded_for_ms
last_issuemessages           r"   r   z%PhotonAdapter._monitor_sidecar_health  s^      # "	- =>>>>>>>>>( !//
B????????)      GMMM ,6dD+A+AKTXXh'''tFfd++ vzz$/?/?u/L/L

7++8y99E$jj99OVZZ44N8NOOJ     2A       
 LL000!!* "   
T..0000000000 T T TMsSSSSSSSSTEE"	 "	s/   A B*B

BF" "
G,GGr   c                  K   	 t          j        |          }n0# t           j        $ r t                              d           Y d S w xY w|                    d          }|r|                     |          rd S 	 |                     |           d {V  d S # t          $ r t          	                    d           Y d S w xY w)Nz'[photon] skipping non-JSON inbound line	messageIdz [photon] inbound dispatch failed)
r   r   JSONDecodeErrorr   r   r:   _is_duplicate_dispatch_inboundr   	exception)r   r   eventmsg_ids       r"   r   zPhotonAdapter._on_inbound_line  s      	Jt$$EE# 	 	 	LLBCCCFF	 ;'' 	d((00 	F	A((/////////// 	A 	A 	A?@@@@@@	As!    )AA8B $B=<B=r  c                X   t          j                     }| j        }|                    |          }|||z
  t          k     rdS ||v r||= |||<   t	          |          t
          k    rDt          |                                          d t	          |          t
          z
           D ]}||= dS )NTF)timer   r:   _DEDUP_WINDOW_SECONDSlen_DEDUP_MAX_SIZEr   keys)r   r  nowseentolds         r"   r   zPhotonAdapter._is_duplicate  s    ikk"HHV=S1W'<<<4 T>>VVt99&&DIIKK(()F3t99+F)FG  IIur1   r   Dict[str, Any]c                n
  K   |                     d          pi }|                     d          pi }|                     d          pi }|                     d          pd}|st                              d           dS |                     d          d	k    rd	nd
}|                     d          p|                     d          p|}|                     d          pd}	 |r(t          j        |                    dd                    nt          j        t          j                  }	n/# t          $ r" t          j        t          j                  }	Y nw xY wg }
g }d,d}|                     d          }|dk    r|                     d          }|                     d          dk    p
|o|| j
        v }|st                              d           dS |                     d          pd}|                     |||||pd          }|                     t          d| t          j        ||                     d          ||                     d          pdd||	 	  	                   d{V  dS |                     ||                     d                     |d!k    r%|                     d!          pd}t          j        }n|d"v r ||          \  }}}
}n|d	k    rg }t          j        }|                     d#          pg D ]}t%          |t&                    s|                     d          pi }t%          |t&                    sF|                     d          }|d!k    r/|                     d!          pd}|r|                    |           |d"v rd ||          \  }}}}|t          j        k    r|}|
                    |           |                    |           |s|                    |           |r|                    d$| d%           |
r|t          j        k    rt          j        }d&                    d' |D                                                       }|s|
rd(nd)}nd$| d%}t          j        }|d	k    rM| j        rF|                     |          st                              d*           dS |                     |          }|                     |||||pd          }t          ||||                     d          ||	|
|+          }|                     |           d{V  dS )-uq  Normalize a sidecar inbound event and dispatch it to the gateway.

        Event shape (from ``sidecar/index.mjs``)::

            {
              "messageId": "...",
              "platform": "iMessage",
              "space": {"id": "...", "type": "dm"|"group", "phone": "+E164"},
              "sender": {"id": "+E164"},
              "content": {"type": "text", "text": "..."}
                       | {"type": "attachment"|"voice", "id", "name",
                          "mimeType", "size", "duration"?, "data"?,
                          "encoding"?}
                       | {"type": "reaction", "emoji": "❤️",
                          "targetMessageId": "..." | null,
                          "targetDirection": "inbound"|"outbound" | null,
                          "targetText": "..." | null},
              "timestamp": "2026-05-14T19:06:32.000Z"

        Attachment and voice content carry the bytes inline as base64 ``data``
        (with ``encoding == "base64"``) when the sidecar could read them
        within its size cap; otherwise only metadata is present and we surface
        a marker.
            }
        spacesendercontentidrD   z![photon] inbound missing space.idNtypegroupdmphone	timestampZz+00:00)tzpayloadr  r   -tuple[str, MessageType, List[str], List[str]]c                   |                      d          dk    }|                      d          p|rdnd}|                      d          pd}|rt          j        nt          |          }t	          | |||          }|r|rdnd	||g|p|rd
ndgfS |rdnd}|                      d          }t          |t          t          f          rd| dnd}d| d| d|pd | d|g g fS )Nr  voicerH   z	(unnamed)mimeTyperD   force_audioz(voice)(attachment)	audio/mp4zapplication/octet-stream
attachmentdurationz, duration: sz[Photon z received: z (zunknown MIMEz)])r:   r   VOICE_attachment_message_type_cache_inbound_attachmentr   r   float)	r  is_voicerH   mimemtypecachedlabelr#  duration_texts	            r"   _normalize_binary_payloadzBPhotonAdapter._dispatch_inbound.<locals>._normalize_binary_payloadO  sf    {{6**g5H;;v&&Ph+O77KD;;z**0bD)1UK%%7OPT7U7UE.t  F  !)=II~HVXUkk;UW	   (9GG\E{{:..H he55*x**** >5 > >T > >*N>,9> > > r1   reactiontargetMessageIdtargetDirectionoutboundz6[photon] ignoring reaction on a message we didn't sendemoji)rG   	chat_name	chat_typeuser_id	user_namezreaction:added:r   
targetTextT)	r   message_typesource
message_idreply_to_message_idreply_to_textreply_to_is_own_messageraw_messager  r   >   r  r"  itemsz"[Photon content type not handled: ]
c              3     K   | ]}||V  	d S r   r0   )r   r   s     r"   r   z2PhotonAdapter._dispatch_inbound.<locals>.<genexpr>  s'      AAdDATAAAAAAr1   r   z[Photon empty group received]zR[photon] ignoring group message (require_mention=true, no mention pattern matched))r   r:  r;  r<  r@  r  
media_urlsmedia_types)r  r  r   r  )r:   r   r   r   fromisoformatreplacer  r   utcr!   r   r   build_sourcehandle_messager   r   TEXT_record_last_inboundr   r   r   extendDOCUMENTjoinrJ   rb   r   r   )r   r   r  r  r  space_idr6  	sender_idts_strr  rE  rF  r/  ctype	target_idis_oursr4  r;  r   r+  
text_partsitemitem_content	item_type	item_textmarker
item_mtype	item_urls
item_typesmessage_events                                 r"   r   zPhotonAdapter._dispatch_inbound  sA     4 		'""(b8$$*))I&&,"99T??(b 	NN>???F  %yy00G;;GG	JJt$$F		'(:(:Fh	;''-2	6 3&v~~c8'D'DEEE\X\222 I
  	6 	6 	6 555III	6
 !#
!#	 	 	 	@ F##J
  $566Ikk"344
B Ai4+AA   L   KK((.BE&& "#!#+t '  F %%2522!,!1!$yy55(1")++l";";"Ct,0 %'
 
 
         F
 	!!(EIIk,B,BCCCF??;;v&&,"D$EE---3L3LW3U3U0D%[[g$&J$EG,,2 Y Y!$-- #xx	228b!,55 (,,V44	&& , 0 0 8 8 >BI  5")))444 777@Y@Y$A A=FJ	:  000 *%%i000&&z222$ 2"))&111 Y%%&W9&W&W&WXXX -e{'777#,99AAjAAAAAGGIID Y)3X~~9X@@@@D$E D$899$?? I   ++D11D""'4 # 
 
 %yy--!#	
 	
 	
 !!-00000000000s   A	D( ()EEportr   	List[int]c                    	 t          j        ddd|  dgdddd          }n# t          t           j        f$ r g cY S w xY wd	 |j                                        D             S )
zBPIDs listening on a local TCP port (empty if none/undeterminable).lsofz-tiztcp:z-sTCP:LISTENTr   Fcapture_outputr   r   checkc                x    g | ]7}|                                                                 (t          |          8S r0   )rJ   isdigitr   )r   toks     r"   r   z5PhotonAdapter._find_listener_pids.<locals>.<listcomp>  s7    PPPS#))++:M:M:O:OPCPPPr1   )
subprocessrunOSErrorTimeoutExpiredstdoutr   )ra  outs     r"   _find_listener_pidsz!PhotonAdapter._find_listener_pids  s    	.t~>#$5  CC 23 	 	 	III	PPCJ$4$4$6$6PPPPs    # >>pidc                    	 t          j        ddt          |           ddgdddd          }n# t          t           j        f$ r Y dS w xY wd	|j        v S )
z;True if ``pid``'s command line is a Photon sidecar process.psz-pz-ozcommand=Tr   Fre  zphoton/sidecar/index.mjs)rk  rl  rt   rm  rn  ro  )rr  rp  s     r"   _pid_is_sidecarzPhotonAdapter._pid_is_sidecar  sw    	.tSXXtZ8#$5  CC 23 	 	 	55	 *SZ77s   +. AAc                T    	 t          j        | d           dS # t          $ r Y dS w xY w)Nr   TF)r,   killrm  )rr  s    r"   
_pid_alivezPhotonAdapter._pid_alive  s=    	GCOOO4 	 	 	55	s    
''c                   K   t           j        dk    rdS 	 t          j        d          4 d{V }|                    d j         d j         dd j        i	           d{V  ddd          d{V  n# 1 d{V swxY w Y   n# t          j        $ r Y dS w xY w 	                     j                  } fd
|D             fd|D             }st          d j         d|pd d          D ]S}t                              d| j                   	 t          j        |t          j                   D# t"          $ r Y Pw xY wt%          j                    dz   }t%          j                    |k     rgt'           fdD                       rLt)          j        d           d{V  t%          j                    |k     rt'           fdD                       LD ]H}                     |          r1	 t          j        |t          j                   8# t"          $ r Y Dw xY wIt)          j        d           d{V  |rt          d j         d| d          dS )u:  Kill an orphaned sidecar squatting our port before spawning ours.

        A hard gateway exit (crash, SIGKILL, supervisor restart) used to leave
        the detached sidecar running with a token the new gateway doesn't
        know, so it can't be told to ``/shutdown`` — and every replacement
        spawn died on EADDRINUSE, failing each reconnect attempt. The
        stdin-EOF watch prevents new orphans; this reclaims the port from
        orphans that predate it (or survived it). Listeners are verified by
        command line before being signalled.
        win32N       @r   r   r   r   r   r   c                >    g | ]}                     |          |S r0   )ru  )r   rr  r   s     r"   r   z5PhotonAdapter._reap_stale_sidecar.<locals>.<listcomp>#  s,    BBB(<(<S(A(ABBBBr1   c                    g | ]}|v|	S r0   r0   )r   rr  stales     r"   r   z5PhotonAdapter._reap_stale_sidecar.<locals>.<listcomp>$  s#    ;;;3#U*:*:3*:*:*:r1   zport z% is in use by another process (pids: r   uR   , not a Photon sidecar) — free it or set PHOTON_SIDECAR_PORT to a different portz5[photon] reaping orphaned sidecar (pid %d) on port %d      @c              3  B   K   | ]}                     |          V  d S r   )rx  )r   pr   s     r"   r   z4PhotonAdapter._reap_stale_sidecar.<locals>.<genexpr>5  s/      ,O,OAT__Q-?-?,O,O,O,O,O,Or1   g?皙?z. is also held by non-sidecar processes (pids: u<   ) — free it or set PHOTON_SIDECAR_PORT to a different port)sysplatformr   r   postrp   rn   rs   RequestErrorrq  r   r   r   r,   rw  signalSIGTERMrm  r  r   r   r   rx  SIGKILL)r   r   pidsforeignrr  deadliner  s   `     @r"   _reap_stale_sidecarz!PhotonAdapter._reap_stale_sidecar  s      <7""F	(555       kkOd0OO43EOOO5t7JK "                                   
 ! 	 	 	FF	''(:;;BBBBBBB;;;;$;;; 	J* J J!.YJ J J  
  	 	CNNGT'  V^,,,,   9;;$ikkH$$,O,O,O,O,O,O,O)O)O$-$$$$$$$$$ ikkH$$,O,O,O,O,O,O,O)O)O$ 	 	Cs## GC0000   D mC          	;* ; ;$+; ; ;  	 	sX   B 6A;)B ;
BB B	B B B D>>
E
E<H
H)(H)c           	     x  K   t           dz                                  st          dt            d          |                                  d {V  t          j                                        }| j        |d<   | j        |d<   t          | j
                  |d<   | j        |d<   | j        |d<   d	|d
<   	 t          j        | j        t          t           dz            t          t                     gdddd          }|j        dk    r/t          |j        p|j        pd                                          |j                                        r2t(                              d|j                                                   n2# t,          $ r%}t(                              d|           Y d }~nd }~ww xY wt          j        | j        t          t           dz            gt          j        t          j        t          j        |t6          j        dk              | _        t=          j                    }|                     | !                    | j                            | _"        tG          j#                    dz   }d }tI          j%        d          4 d {V }tG          j#                    |k     r| j        &                                t          d| j        j         d          	 |'                    d| j         d| j
         dd| j        i            d {V }|j(        d!k    r	 d d d           d {V  d S n# tH          j)        $ r}	|	}Y d }	~	nd }	~	ww xY wt=          j*        d"           d {V  tG          j#                    |k     	 d d d           d {V  n# 1 d {V swxY w Y   t          d#|           )$Nr(   z+Photon sidecar deps not installed. Run: cd z, && npm install   (or `hermes photon setup`)r6   r8   r]   PHOTON_SIDECAR_BINDr^   rd   PHOTON_SIDECAR_WATCH_STDINz$patch-spectrum-mixed-attachments.mjsT
   Fre  r   rD   r   z<[photon] failed to apply Spectrum mixed attachment patch: %sz	index.mjsrz  )stdinro  stderrenvstart_new_sessionra   r{  r   z Photon sidecar exited with code z before becoming readyr   r   r   r   r|  r   r  z0Photon sidecar did not become ready within 15s: )+r.   r/   r   r  r,   environcopyrk   rl   rt   rn   rp   rs   rk  rl  rv   
returncoder  ro  rJ   r   r   r   r   PopenPIPESTDOUTr  r  rx   r   r   r   _supervise_sidecarry   r  r   r   pollr  r   r  r   )
r   r  patchr   loopr  last_errr   r   r   s
             r"   r   zPhotonAdapter._start_sidecarF  s     ~-5577 	Q"Q Q Q   &&(((((((((joo#'#3 '+';#$%();%<%<!"%)%7!"&*&9"# -0()	NN'MMNN%%
  $
 
 
E 1$$"EL$FEL$FB#M#M#O#OPPP|!!## B]EL,>,>,@,@AAA 	 	 	NNN       	 (-^S!;<<=/?$"|w6
 
 
 %''(,(8(8##D$677)
 )
%
 9;;%(,$S111 	) 	) 	) 	) 	) 	) 	)V)++((%**,,8&Q-8Q Q Q  !!'S$"4SSt7ISSS!94;N O "- " "      D '3..	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) /) ! ! ! HHHHHH!mC((((((((( )++(((	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	)" IxII
 
 	
sW   4CF 
F1F,,F1AN"A L76N7MMNM4N
N#&N#procsubprocess.Popenc                  K   |j         dS |j         }t          j                    }	 	 |                    d|j                   d{V }|snBt
                              d|                    dd                                                     fn2# t          $ r%}t
          
                    d|           Y d}~nd}~ww xY w| j        r|                                }t
                              d|           |                     dd	| d
d           	 |                                  d{V  dS # t          $ r&}t
          
                    d|           Y d}~dS d}~ww xY wdS )z1Pump the sidecar's stdout/stderr into our logger.NTz[photon-sidecar] %szutf-8rH  z&[photon-sidecar] supervisor exited: %suG   [photon] sidecar exited unexpectedly (code %s) — triggering reconnectSIDECAR_CRASHEDz)Photon sidecar exited unexpectedly (code )r   r   )ro  r   r   run_in_executorreadliner   r   decoderstripr   r   r|   r  r   r   r   )r   r  ro  r  r   r   	exit_coder   s           r"   r  z PhotonAdapter._supervise_sidecar  s     ;F%''	H]!11$HHHHHHHH 14;;w	3R3R3Y3Y3[3[\\\	]  	H 	H 	HNNCQGGGGGGGG	H  	T		ILLY   !!!HIHHH "   
T..00000000000 T T TMsSSSSSSSSST	T 	Ts0   A'B 
B>B99B>D0 0
E :EE c                  K   | j         }|d S 	 |j        +	 |j                                         n# t          $ r Y nw xY w| j        M	 | j                            d| j         d| j         dd| j        id           d {V  n# t          $ r Y nw xY w	 |	                    d           n# t          j        $ r t          j        d	k    rc	 t          j        t          j        |j                  t$          j                   n?# t(          t*          f$ r |                                 Y nw xY w|                                 	 |	                    d           n)# t          j        $ r |                                 Y nw xY wY nw xY wd | _         | j        "| j                                         d | _        d S d S # d | _         | j         | j                                         d | _        w xY w)
Nr   r   z	/shutdownr   r{  r   r  r   rz  )rx   r  closer   r}   r  rp   rn   rs   waitrk  rn  r  r  r,   killpggetpgidrr  r  r  ProcessLookupErrorPermissionError	terminaterw  ry   r   )r   r  s     r"   r   zPhotonAdapter._stop_sidecar  s     !<F%	5 z%J$$&&&&    D  ,+00T$"4TTt7ITTT!94;N O # 1          
 !   D 		#	&&&&,      <7**)	"*TX"6"6GGGG.@ ) ) )((((() NN$$$ IIcI****!0      IIKKKKK   "&D,8-4466604--- 98 "&D,8-4466604-4444s   F< 1 F< 
>F< >
F< 	;B F< 
BF< BF< B- ,F< -F6DF%D,)F+D,,FEF#F>F FFF< FF< <0G,NrG   r  reply_toOptional[str]metadataOptional[Dict[str, Any]]r   c                d   K   |                      ||                     |                     d {V S r   )_sidecar_sendformat_message)r   rG   r  r  r  s        r"   sendzPhotonAdapter.send  s>       ''1D1DW1M1MNNNNNNNNNr1   	image_urlcaptionc                   K   	 ddl m}  ||           d {V }n:# t          $ r- t                                          ||||           d {V cY S w xY w|                     |||           d {V S )Nr   )cache_image_from_urlr  )gateway.platforms.baser  r   ri   
send_image_sidecar_send_attachment)	r   rG   r  r  r  r  r  
local_pathr   s	           r"   r  zPhotonAdapter.send_image  s      	SCCCCCC33I>>>>>>>>JJ 	S 	S 	S++GYRRRRRRRRRRR	S 22Z 3 
 
 
 
 
 
 
 
 	
s    4AA
image_pathc                B   K   |                      |||           d {V S Nr  r  )r   rG   r  r  r  r  kwargss          r"   send_image_filezPhotonAdapter.send_image_file  J       22Z 3 
 
 
 
 
 
 
 
 	
r1   
audio_pathc                D   K   |                      |||d           d {V S )Nr  )r  kindr  )r   rG   r  r  r  r  r  s          r"   
send_voicezPhotonAdapter.send_voice  sL       22Zw 3 
 
 
 
 
 
 
 
 	
r1   
video_pathc                B   K   |                      |||           d {V S r  r  )r   rG   r  r  r  r  r  s          r"   
send_videozPhotonAdapter.send_video  r  r1   	file_path	file_namec                D   K   |                      ||||           d {V S )N)rH   r  r  )r   rG   r  r  r  r  r  r  s           r"   send_documentzPhotonAdapter.send_document,  sL       22YY 3 
 
 
 
 
 
 
 
 	
r1   animation_urlc                D   K   |                      |||||           d {V S r   )r  )r   rG   r  r  r  r  s         r"   send_animationzPhotonAdapter.send_animation:  sG       __]GXx
 
 
 
 
 
 
 
 	
r1   c                <  K   t          j                     }|| j                            |d          z
  t          k     rd S || j        |<   	 |                     d|dd           d {V  d S # t
          $ r&}t                              d|           Y d }~d S d }~ww xY w)Ng        /typingstartspaceIdr   z[photon] send_typing failed: %s)r  r   r:   _TYPING_COOLDOWN_SECONDSr   r   r   r   )r   rG   r  r  r   s        r"   send_typingzPhotonAdapter.send_typingG  s      ikk'++GS999<TTTF*-w'	?$$wAA            	? 	? 	?LL:A>>>>>>>>>	?s   
A+ +
B5BBc                   K   | j                             |d            	 |                     d|dd           d {V  d S # t          $ r&}t                              d|           Y d }~d S d }~ww xY w)Nr  stopr  z[photon] stop_typing failed: %s)r   popr   r   r   r   )r   rG   r   s      r"   stop_typingzPhotonAdapter.stop_typingS  s      ""7D111	?$$w@@            	? 	? 	?LL:A>>>>>>>>>	?s   A   
A0
A++A0i  r   r<  c                   |sd S | j         }||v r||= t          j                    ||<   t          |          | j        k    rDt	          |                                          d t          |          | j        z
           D ]}||= d S d S r   )r   r  r  _SENT_IDS_MAXr   r  )r   r<  sentr  s       r"   _record_sent_messagez"PhotonAdapter._record_sent_messagee  s     	F%Z 9;;Zt99t)))DIIKK(()I3t99t7I+I)IJ  II *) r1   z^any;-;(\+\d{6,})$c                h    | j                             |          }|r|                    d          n|S )Nr   )_DM_CHAT_GUID_REr   r  )clsrG   r   s      r"   _normalize_chat_keyz!PhotonAdapter._normalize_chat_keyw  s1    $**733!&3u{{1~~~G3r1   c                   |r|sd S |                      |          }| j        }||v r||= |||<   t          |          | j        k    rDt	          |                                          d t          |          | j        z
           D ]}||= d S d S r   )r  r   r  _LAST_INBOUND_CHATS_MAXr   r  )r   rG   r<  keylastr  s         r"   rM  z"PhotonAdapter._record_last_inbound|  s      	j 	F&&w//)$;;S	S	t99t333DIIKK((:#d))d:::   II	 43 r1   c                x    t          j        dd                                                                          dv S )NPHOTON_REACTIONSrS   >   rd   re   rf   rP   rT   )r   s    r"   _reactions_enabledz PhotonAdapter._reactions_enabled  s:    y+W55;;==CCEE J
 
 	
r1   r4  c                   K   	 |                      d|||d           d{V  dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)zCTapback ``emoji`` onto a message. Soft-fails (False), never raises.z/react)r  r   r4  NTz [photon] add_reaction failed: %sFr   r   r   r   )r   rG   r<  r4  r   s        r"   _add_reactionzPhotonAdapter._add_reaction  s      	$$#*uMM         4 	 	 	LL;Q???55555	s    & 
AAAc                   K   	 |                      d||d           d{V  dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)a+  Retract our tapback from a message. Soft-fails (False), never raises.

        The sidecar tracks one reaction handle per target message; after a
        sidecar restart the handle is gone and removal is best-effort (the
        stale tapback self-heals when the next reaction replaces it).
        z/unreact)r  r   NTz#[photon] remove_reaction failed: %sFr  )r   rG   r<  r   s       r"   _remove_reactionzPhotonAdapter._remove_reaction  s      	$$jII         4 	 	 	LL>BBB55555	s   % 
AAAc                   K   |p,| j                             |                     |                    }|sdddS |                     |||           d{V }|sdddS d|dS )uA  Tapback ``emoji`` onto a message in ``chat_id``.

        Without ``message_id``, targets the chat's most recent inbound
        message (typically the one the agent is responding to). iMessage
        maps ❤️👍👎😂‼️❓ to native tapbacks; anything else uses Apple's
        custom-emoji reaction.
        Fuk   no message to react to — pass message_id (no inbound message seen in this chat since the gateway started)successr   Nz'reaction failed (see gateway debug log)Tr  r<  )r   r:   r  r  )r   rG   r4  r<  targetr   s         r"   add_reactionzPhotonAdapter.add_reaction  s        
t9==$$W-- 
  
  	 O  
 %%gvu======== 	 B    v666r1   c                   K   |p,| j                             |                     |                    }|sdddS |                     ||           d{V }|sdddS d|dS )z1Retract our tapback from a message (best-effort).Fu)   no message to unreact — pass message_idr  Nz&unreact failed (see gateway debug log)Tr  )r   r:   r  r  )r   rG   r<  r  r   s        r"   remove_reactionzPhotonAdapter.remove_reaction  s        
t9==$$W-- 
  
  	 D   ((&99999999 	 A    v666r1   r   c                   K   |                                  sdS t          |j        dd          }t          |dd          }|r!|r!|                     ||d           d{V  dS dS dS )u=   Tapback 👀 on the triggering message while the agent works.NrG   r<  u   👀)r  getattrr;  r  )r   r   rG   r<  s       r"   on_processing_startz!PhotonAdapter.on_processing_start  s      &&(( 	F%,	488UL$77
 	Hz 	H$$Wj,GGGGGGGGGGG	H 	H 	H 	Hr1   outcomer   c                  K   |                                  sdS t          |j        dd          }t          |dd          }|r|sdS |                     ||           d{V  |t          j        k    r|                     ||d           d{V  dS |t          j        k    r|                     ||d           d{V  dS dS )u  Swap the 👀 progress tapback for a 👍/👎 result.

        Remove-then-add rather than a bare replace: deterministic whether the
        platform replaces a sender's previous tapback or stacks them, and it
        keeps the sidecar's reaction-handle slot coherent.
        NrG   r<  u   👍u   👎)r  r  r;  r  r   SUCCESSr  FAILURE)r   r   r  rG   r<  s        r"   on_processing_completez$PhotonAdapter.on_processing_complete  s      &&(( 	F%,	488UL$77
 	j 	F##GZ888888888'///$$Wj,GGGGGGGGGGG)111$$Wj,GGGGGGGGGGG 21r1   c                   K   |d|dS )zReturn whatever we know about a Spectrum space id.

        Photon's ``space.id`` is opaque; the inbound event also carries the
        DM/group type, but here we only have the id, so infer conservatively.
        r  )rH   r  r  r0   )r   rG   s     r"   get_chat_infozPhotonAdapter.get_chat_info  s        W===r1   c                @    t                      r|S t          |          S r   )rV   r   )r   r  s     r"   r  zPhotonAdapter.format_message  s%      	Ng&&&r1   r   c                    t          j        |           rdS | sdS |                                 t          fdt          D                       S )NTFc              3      K   | ]}|v V  	d S r   r0   )r   patlowereds     r"   r   z4PhotonAdapter._is_retryable_error.<locals>.<genexpr>  s'      HHc3'>HHHHHHr1   )r   _is_retryable_errorrU   r   _PHOTON_RETRYABLE_PATTERNS)r   r  s    @r"   r  z!PhotonAdapter._is_retryable_error  sY    2599 	4 	5++--HHHH-GHHHHHHr1   r   r{  max_retries
base_delayr(  c                p  K   |                      |          }|                     ||||           d{V }|j        r|S |j        pd}	|j        p|                     |	          }
|
s|                     |	          r|S |
rt          d|dz             D ]}|d|dz
  z  z  }t          	                    d||||	           t          j        |           d{V  |                     ||||           d{V }|j        r|c S |j        pd}	|j        s|                     |	          s nt                              d||	           |S t          	                    d|	           |                     ||d| j                 ||           d{V }|j        s t                              d	|j                   |S )
u   Retry sends without the generic Markdown banner.

        Photon replies are markdown (rendered by iMessage) or stripped plain
        text under ``PHOTON_MARKDOWN=false`` — either way the gateway's
        generic banner never applies.
        )rG   r  r  r  NrD   r   r   z;[photon] Send failed (attempt %d/%d, retrying in %.1fs): %sz8[photon] Failed to deliver response after %d retries: %sz6[photon] Send failed: %s - retrying plain-text messagez)[photon] Plain-text retry also failed: %s)r  r  r  r   r   r  _is_timeout_errorranger   r   r   r   MAX_MESSAGE_LENGTH)r   rG   r  r  r  r  r  r   result	error_str
is_networkattemptdelayfallback_results                 r"   _send_with_retryzPhotonAdapter._send_with_retry  s}      ""7++yy	 ! 
 
 
 
 
 
 
 
 > 	ML&B	%L)A)A))L)L
 	d44Y?? 	M 	 K!O44  "aGaK&89Q[%   mE*********#yy# %%	  )           > "!MMM"L.B	( D,D,DY,O,O EN   D	
 	
 	
 !%		24223	 !* !
 !
 
 
 
 
 
 
 & 	]LLDoF[\\\r1   rQ  c                  K   t          |          | j        k    r=t                              dt          |          | j                   |d | j                 }||d}t	                      rd|d<   	 |                     d|           d {V }n5# t          $ r(}t          dt          |                    cY d }~S d }~ww xY w| 	                    |
                    d                     t          d	|
                    d          
          S )Nz0[photon] truncating outbound from %d to %d charsr  r   markdownformat/sendFr  r   Tr  )r  r  r   r   rV   r   r   r   rt   r  r:   )r   rQ  r   bodyr   r   s         r"   r  zPhotonAdapter._sidecar_sende  s0     t99t...NNBD		42   1$112D+3TBB  	('DN	;++GT::::::::DD 	; 	; 	;e3q66:::::::::	;!!$((;"7"7888$488K3H3HIIIIs   1B 
C B;5C ;C r"  )rH   	mime_typer  r  pathrH   r%  r  c               >  K   |                      t          |                    }|st          dd|           S |s ddl}|                    |          \  }	}
|	pd}|||dk    rdndd}|r||d	<   |r||d
<   |r||d<   	 |                     d|           d{V }n5# t          $ r(}t          dt          |                    cY d}~S d}~ww xY w|                     |                    d                     t          d|                    d                    S )a  POST a local file to the sidecar's ``/send-attachment`` endpoint.

        ``kind`` is ``"voice"`` for audio sent as a voice note (downgrades
        to a plain audio attachment on platforms without voice notes),
        otherwise ``"attachment"``. spectrum-ts infers ``name`` and
        ``mimeType`` from the file extension; we only pass overrides when
        Hermes supplied them.
        Fz#unsafe or missing attachment path: r  r   Nr  r"  r  r&  r  rH   r  r  /send-attachmentr   Tr  )	validate_media_delivery_pathrt   r   	mimetypes
guess_typer   r   r  r:   )r   rQ  r&  rH   r%  r  r  	safe_pathr+  guessed_r$  r   r   s                 r"   r  z&PhotonAdapter._sidecar_send_attachmentx  s     * 55c$ii@@	 	%Q4%Q%Q     	("--i88JGQ4I#wGGL 
  

  	 DL 	)(D 	&%DO	;++,>EEEEEEEEDD 	; 	; 	;e3q66:::::::::	;!!$((;"7"7888$488K3H3HIIIIs   B 
C(CCCr$  c           
     :  K   | j         t          d          d| j         d| j         | }d| j        i}t          j        d          4 d {V }|                    |||           d {V }d d d           d {V  n# 1 d {V swxY w Y   |j        dk    r*t          d	| d
|j         d|j	        d d                    |
                                pi }|                    d          s(t          d	| d|                    d                     |S )NzPhoton adapter not connectedr   r   r   r   r   r   r   r   zPhoton sidecar z
 returned : r   z reported error: r   )r}   r   rp   rn   rs   r   r   r  r   r   r   r:   )r   r&  r$  r   r   r   r   r   s           r"   r   zPhotonAdapter._sidecar_call  s     $=>>> H*GGT-?GGG+T-@A$T222 	F 	F 	F 	F 	F 	F 	FfStWEEEEEEEED	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	Fs""W$WW$2BWWdiPTQTPToWW   yy{{ bxx~~ 	L$LL'9J9JLL   s   B
BB)rY   r   )r   r   r   r   )r   rt   r   r$   )r   rt   r   rt   )r   r$   r   r$   r   r   )r   rt   r   r   )r  rt   r   r$   )r   r  r   r   )ra  r   r   rb  )rr  r   r   r$   )r  r  r   r   )NN)
rG   rt   r  rt   r  r  r  r  r   r   )NNN)rG   rt   r  rt   r  r  r  r  r  r  r   r   )rG   rt   r  rt   r  r  r  r  r  r  r   r   )rG   rt   r  rt   r  r  r  r  r  r  r   r   )rG   rt   r  rt   r  r  r  r  r  r  r   r   )NNNN)rG   rt   r  rt   r  r  r  r  r  r  r  r  r   r   )rG   rt   r  rt   r  r  r  r  r  r  r   r   r   )rG   rt   r   r   )r<  r  r   r   )rG   rt   r   rt   )rG   r  r<  r  r   r   r   r$   )rG   rt   r<  rt   r4  rt   r   r$   )rG   rt   r<  rt   r   r$   )rG   rt   r4  rt   r<  r  r   r  )rG   rt   r<  r  r   r  )r   r   r   r   )r   r   r  r   r   r   )rG   rt   r   r  )r  rt   r   rt   )r   r  r   r$   )NNr   r{  )rG   rt   r  rt   r  r  r  r   r  r   r  r(  r   r   )rQ  rt   r   rt   r   r   )rQ  rt   r&  rt   rH   r  r%  r  r  r  r  rt   r   r   )r&  rt   r$  r  r   r  ):__name__
__module____qualname____doc___MAX_MESSAGE_LENGTHr  rj   staticmethodr   r   r   r   r   r   r   r   r   r   rq  ru  rx  r  r   r  r   r  r  r  r  r  r  r  r  r  r  r  r  r   r   r  classmethodr  rM  r  r  r  r  r   r  r  r
  r  r  r  r  r  r   __classcell__)r   s   @r"   rX   rX      s*         -J
 J
 J
 J
 J
 J
\ # # # \#JO O O O
   " 5: 3 3 3 3 3 3j" " " "B&1 &1 &1 &1P) ) ) )VA A A A   "O1 O1 O1 O1f 	Q 	Q 	Q \	Q 
8 
8 
8 \
8    \7 7 7 7rN
 N
 N
 N
`T T T T<)5 )5 )5 )5b #'-1O O O O O* "&"&-1
 
 
 
 
 
 
. "&"&-1
 
 
 
 
" "&"&-1
 
 
 
 
" "&"&-1
 
 
 
 
" "&#'"&-1
 
 
 
 
$ "&"&-1
 
 
 
 

? 
? 
? 
? 
?? ? ? ? M!	 	 	 	  "rz"7884 4 4 [4    
 
 
 

      4 %)	7 7 7 7 7> 9=7 7 7 7 7(H H H HH H H H,> > > >' ' ' ' I I I \I #'D D D D DLJ J J J0 ##'!% /J /J /J /J /J /Jb       r1   rX   r*  rt   r   c                N   | pd                                 } |                     d          rt          j        S |                     d          rt          j        S |                     d          rt          j        S |                     d          rt          j        S t          j        S )NrD   image/zvideo/audio/zapplication/)rU   
startswithr   PHOTOVIDEOAUDIOrO  )r*  s    r"   r&  r&    s    JBDx   !  x   !  x   !  ~&& $##r1   .jpgz.pngz.gifz.webp)z
image/jpegz	image/pngz	image/gifz
image/webpz
image/heicz
image/heifz
image/tiff.mp3z.oggz.wav.m4a)z	audio/mp3z
audio/mpegz	audio/oggz	audio/wavzaudio/x-cafr!  z	audio/aacr  r  r  rH   r  r  c                  |                      d          }|sdS 	 t          j        |          }n:# t          t          f$ r&}t
                              d|           Y d}~dS d}~ww xY wddlm}m	}m
}	 |pd                                }|rt          |          j        nd}
	 |                    d          rF|
pt                               |d          }	  |	||          S # t          $ r  |||          cY S w xY w|s|                    d	          r-|
pt                                ||rd
nd          } |||          S  |||          S # t"          $ r'}t
                              d||           Y d}~dS d}~ww xY w)a  Decode a base64-inlined inbound attachment and cache it locally.

    The sidecar inlines the attachment bytes as ``content["data"]`` (base64).
    We decode them and route to the shared media cache by MIME type, returning
    the cached absolute path so the caller can populate ``media_urls`` (which
    the gateway then hands to the model). Returns ``None`` when there are no
    bytes (over the sidecar's inline cap or a failed read) or when caching
    fails, so the caller can fall back to a text marker.
    r   Nz6[photon] failed to decode inbound attachment bytes: %sr   )cache_audio_from_bytescache_document_from_bytescache_image_from_bytesrD   r>  rD  r?  rF  rE  z2[photon] failed to cache inbound attachment %s: %s)r:   base64	b64decoder!   r    r   r   r  rH  rI  rJ  rU   r   suffixr@  _IMAGE_EXT_BY_MIME_AUDIO_EXT_BY_MIMEr   )r  rH   r*  r  data_b64r   r   rH  rI  rJ  rM  exts               r"   r'  r'    s     {{6""H tx((	"   OQTUUUttttt          JBD"&.T$ZZBF??8$$ 	<@.224@@C<--c3777 < < < 10d;;;;;<  	4$//(33 	4 .227ff C *)#s333((d333   KTSVWWWtttttsR   0 A'A""A'#2E C" "C;8E :C;;AE E 
E?E::E?)	thread_idmedia_filesforce_documentpconfigrG   r   rR  rS  Optional[list]rT  c          	        K   t           sddiS t          | j        pi                     d          pt	          j        d          t                    }t	          j        d          }|sddiS dt           d| }d	|i}	d }
	 t          j	        d
          4 d {V }|r||d t                   d}t                      rd|d<   |                    | d||	           d {V }|j        dk    r.dd|j         d|j        d d          icd d d           d {V  S |                                pi }|                    d          s+d|                    d          pdicd d d           d {V  S |                    d          }
dd l}|pg D ]8\  }}t#          j        t'          |                    }|st(                              d           D|                    |          \  }}|||rdndd}|r||d<   |                    | d||	           d {V }|j        dk    r0dd|j         d|j        d d          ic cd d d           d {V  S |                                pi }|                    d          s-d|                    d          pdic cd d d           d {V  S |                    d          p|
}
:	 d d d           d {V  n# 1 d {V swxY w Y   d|
dS # t.          $ r}dd | icY d }~S d }~ww xY w)!Nr   r   r\   r]   r^   zPhoton standalone send requires a running sidecar with PHOTON_SIDECAR_TOKEN set in the environment. Cron processes cannot spawn the sidecar themselves.r   r   r   r   r   r   r!  r"  r#  r1  r   zsidecar returned r2  r   zsidecar reported failurer   r   z-[photon] standalone send skipping unsafe pathr  r"  r(  r  r)  Tr  zPhoton standalone send failed: )r)   r#   r9   r:   r,   r-   rm   ro   r   r   r9  rV   r  r   r   r   r+  r   r*  rt   r   r   r,  r   )rU  rG   r   rR  rS  rT  ra  tokenbaser   last_message_idr   	send_bodyr   r   r+  
media_pathr)  r-  r.  r/  att_bodyr   s                          r"   _standalone_sendr^  $  s       0.//		"!!.11URY?T5U5U D I,--E 
7
 	
 4*33T33D'/G%)O2@$T222 -	K -	K -	K -	K -	K -	K -	Kf 8&#$8%8$89- -	 %&& 5*4Ih'#[[NNNG )         #s**#%^9I%^%^TYW[X[W[_%^%^_-	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K yy{{(bxx~~ V#TXXg%6%6%T:TU!-	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K" #'((;"7"7
 (3(9r K K$
H/LSQ[__]]	  NN#RSSS&11)<<
&%'/AGG\, ,
  3+2HZ(#[[---Hg )         #s**#%^9I%^%^TYW[X[W[_%^%^___S-	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	KT yy{{(bxx~~ V#TXXg%6%6%T:TUUUY-	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	KZ #'((;"7"7"J?+K1-	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K -	K^  ??? @ @ @>1>>???????@sv   ?K2 A/K	K2 AKK2 2CKK2 AKK2 0K
K2 
K&&K2 )K&*K2 2
L<LLLr   c                    ddl m} |                     ddd t          t          t
          ddgd|j        t          d	t          d
dt          dddd           | 
                    dd|j        |j                   dS )z.Called by the Hermes plugin loader at startup.r   )clir[   ziMessage via Photonc                     t          |           S r   )rX   r?   s    r"   <lambda>zregister.<locals>.<lambda>  s    M#$6$6 r1   r6   r8   zRun: hermes photon setup  (logs in via device flow, creates a Spectrum project, links your phone number, installs the spectrum-ts sidecar).rC   PHOTON_ALLOWED_USERSPHOTON_ALLOW_ALL_USERSu   📱Tua  You are communicating via Photon Spectrum (iMessage). Treat replies like regular text messages — short and friendly. Markdown is rendered (bold, italics, lists, code), but keep formatting light and conversational. Recipient identifiers are E.164 phone numbers; never expose them in responses unless the user asked. Attachments arrive as metadata only.)rH   r-  adapter_factorycheck_fnr=   r@   required_envinstall_hintsetup_fnenv_enablement_fncron_deliver_env_varstandalone_sender_fnallowed_users_envallow_all_envmax_message_lengthr4  pii_safeallow_update_commandplatform_hintz1Set up and manage the Photon iMessage integration)rH   helpri  
handler_fnN)rD   r`  register_platformr2   r=   r@   gateway_setuprM   r^  r9  register_cli_commandregister_clidispatch)ctx_clis     r"   registerr|  w  s     #66#'!)+BC$ #)2-0.. !?;  $ $ $N @"=	      r1   )r   r   r   r   r   r   r4  )r3   r   r   r$   )r   rA   )r*  rt   r   r   )
r  r  rH   rt   r*  rt   r  r$   r   r  )rU  r   rG   rt   r   rt   rR  r  rS  rV  rT  r$   r   r  r3  )Dr8  
__future__r   r   rK  r   loggingr,   r   rq   r*   r  rk  r  r  r   r   pathlibr   typingr   r   r	   r
   r   r   r)   ImportErrorgateway.configr   r   r  r   r   r   r   r   gateway.platforms.helpersr   authr   	getLoggerr5  r   rm   ro   r9  r  r  __file__parentr.   r  r  r   r#   r2   r=   r@   rM   rV   rX   r&  rN  rO  r'  r^  r|  r0   r1   r"   <module>r     s   . # " " " " "     				 				        



  ' ' ' ' ' ' ' '       ; ; ; ; ; ; ; ; ; ; ; ; ; ;  LLLOO    4 3 3 3 3 3 3 3              5 4 4 4 4 4 * * * * * *		8	$	$
  # 
   ! tH~~$y0
    +"                    &
 
 
 
 A A A A A' A A AN(
  
  
  
          4 4 4 4 4 4B  $"& M@ M@ M@ M@ M@ M@f2 2 2 2 2 2s   A& &	A21A2