
    ,j                    8   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dgZ4dGdZ5dHdZ6dId Z7dId!Z8dJd#Z9dHd$Z: G d% d&e           Z;dKd*Z<d+d,d-d.d+d+d+d/Z=d0d0d1d2d0d3d3d4Z>dd5dLd;Z?dddd<dMdDZ@dNdFZAdS )Ou2  
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  sidecarz#(?<![\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     E/home/ubuntu/.hermes/hermes-agent/plugins/platforms/photon/adapter.py_coerce_portr    e   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_requirementsr/   l   s[     u<	"344>?? u>)1133  u4r.   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!   )r0   r6   r2   r4   	stored_id
stored_secs         r   validate_configr:   z   s    IOE<((JBI6I,J,JJYY/00VBI>U4V4VN .^ . 8 : :	:I,*---4r.   c                     t          |           S r   )r:   r0   s    r   is_connectedr=      s    3r.   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)r2   r4   PHOTON_HOME_CHANNEL PHOTON_HOME_CHANNEL_NAMEHome)chat_idnamehome_channel)r   r)   r*   strip)r2   r4   seedhomes       r   _env_enablementrJ      s     ":!;!;J > t *nMMD9*B//5577D 
I8&AA 
  
^ Kr.   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*   rG   lowerr-   r.   r   _markdown_enabledrS      s<     9&//5577==?? H  r.   c                  D    e Zd ZdZeZde fdZedfd	            ZdgdZ	dhdZ
didZdjdZdjdZdkdZdldZdmdZednd            Zedod            Zedod             Zdjd!Zdjd"Zdpd%Zdjd&Z	 	 dqdrd/Z	 	 	 dsdt fd2Z	 	 	 dsdud4Z	 	 	 dsdvd6Z	 	 	 dsdwd8Z	 	 	 	 dxdyd;Z	 	 	 dsdzd=Zd{d|d>Zd|d?Z d@Z!dAZ"d}dCZ# e$j%        dD          Z&e'd~dE            Z(ddFZ)didGZ*ddIZ+ddJZ,	 d{ddKZ-	 d{ddLZ.ddNZ/ddQZ0ddRZ1ddSZ2	 	 	 	 dddYZ3dd[Z4d'd'd'd\d]ddbZ5dddZ6 xZ7S )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 | _        i | _        i | _         i | _!        |                    d          }|t          j        d          }t%          |          "                                                                dv | _#        | $                    d|v r|d         nt          j        d                    | _%        d S )Nphotonr3   r2   rA   r5   r4   sidecar_portPHOTON_SIDECAR_PORTPHOTON_SIDECAR_TOKEN   PHOTON_SIDECAR_AUTOSTARTrM   )rN   rP   rO   r#   r$   Frequire_mentionPHOTON_REQUIRE_MENTION>   1onyesrM   mention_patternsPHOTON_MENTION_PATTERNS)&super__init__r   r6   r   r)   r*   r7   _project_id_project_secretr    _DEFAULT_SIDECAR_PORT_sidecar_port_DEFAULT_SIDECAR_BIND_sidecar_bindsecrets	token_hex_sidecar_tokenstrrR   _autostart_sidecarr'   r(   	_node_binrS   supports_code_blocks_sidecar_proc_sidecar_supervisor_task_inbound_task_inbound_running_http_client_seen_messages_sent_message_ids_last_inbound_by_chatrG   r^   _compile_mention_patterns_mention_patterns)selfrV   r6   r8   r9   _require_mention	__class__s         r   rf   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"
 !99%677#!y)ABB"#344::<<BBDD I
  
 "&!?!?!U** $%%455"
 "
r.   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 ),)splitrG   ).0lineparts      r   
<listcomp>z;PhotonAdapter._compile_mention_patterns.<locals>.<listcomp>  sZ     @ @ @ JJsOO@ @  

@ @ @ @r.   z'[photon] Invalid mention pattern %r: %s)list_DEFAULT_MENTION_PATTERNS
isinstancerp   rG   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   rp   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Nr.   )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Nr.   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$&&&' r.   c                  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                              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_errorrg   rh   httpxAsyncClientrx   rq   _start_sidecarr   acloser   r   rw   asyncioget_event_loopcreate_task_inbound_looprv   _mark_connectedinforl   rj   )r~   clientes      r   connectzPhotonAdapter.connect;  s      	!!4 "    5 	t'; 	!!%+	 "    5"4000" " 	
))++++++++++   %%$:q::" &   
 mmoo%%%%%%%$(!uuuuu NNV  
 !%$355AA  
 
 	R 2	
 	
 	
 ts   0B 
C<CCNonec                  K   d| _         | 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)
rw   rv   cancelr   CancelledErrorr   _stop_sidecarrx   r   _mark_disconnectedr~   s    r   
disconnectzPhotonAdapter.disconnectm  s-      %)%%'''((((((((()      !%D  """""""""('..0000000000    $D!!!!!s'   9 A
	AAB" "
B/.B/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   )rx   rl   rj   ro   rw   streamstatus_codeRuntimeErroraiter_linesrG   _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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   debugr7   _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)timery   r7   _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ur.   r   Dict[str, Any]c           
       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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{V  dS |                     ||                     d                     |dk    r%|                     d          pd}t          j        }n|dv r|dk    }|                     d          p|rdnd}|                     d          pd}|rt          j        nt'          |          }t)          ||||           }|r7|
                    |           |                    |p|rd!nd"           |rd#nd$}nb|rdnd%}|                     d&          }t-          |t.          t0          f          rd'| d(nd}d)| d*| d+|pd, | d-}nd.| d/}t          j        }|d	k    rM| j        rF|                     |          st                              d0           dS |                     |          }|                     |||||pd          }t          ||||                     d          ||	|
|1          }|                     |           d{V  dS )2u;  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},
              "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idrA   z![photon] inbound missing space.idNtypegroupdmphone	timestampZz+00:00)tzreactiontargetMessageIdtargetDirectionoutboundz6[photon] ignoring reaction on a message we didn't sendemoji)rD   	chat_name	chat_typeuser_id	user_namezreaction:added:r   )r   message_typesource
message_idraw_messager   r   >   voice
attachmentr
  rE   z	(unnamed)mimeTypeforce_audio	audio/mp4zapplication/octet-streamz(voice)z(attachment)r  durationz, duration: sz[Photon z received: z (zunknown MIMEz)]z"[Photon content type not handled: ]zR[photon] ignoring group message (require_mention=true, no mention pattern matched))r   r  r  r  r	  r   
media_urlsmedia_types)r7   r   r   r   fromisoformatreplacer   r   utcr   rz   r   build_sourcehandle_messager   r   TEXT_record_last_inboundVOICE_attachment_message_type_cache_inbound_attachmentr   r   r   floatr^   r   r   )r~   r   r   r   r   space_idr  	sender_idts_strr   r  r  ctype	target_idis_oursr  r  r   mtypeis_voicerE   mimecachedlabelr  duration_textmessage_events                              r   r   zPhotonAdapter._dispatch_inbound  s     2 		'""(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 %'  	 	 	 	 	 	 	 	 	 F
 	!!(EIIk,B,BCCCF??;;v&&,"D$EE---'H;;v&&Ph+O77KD;;z**0bD)1UK%%7OPT7U7UE.t  F  !!&)))""UHT[[:T   %-@yy.
 $,=";;z22 "(S%L99.8.... Bu B B B B.B0=B B B 
 A@@@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:LISTENT      @Fcapture_outputr   r   checkc                x    g | ]7}|                                                                 (t          |          8S r-   )rG   isdigitr   )r   toks     r   r   z5PhotonAdapter._find_listener_pids.<locals>.<listcomp>y  s7    PPPS#))++:M:M:O:OPCPPPr.   )
subprocessrunOSErrorTimeoutExpiredstdoutr   )r-  outs     r   _find_listener_pidsz!PhotonAdapter._find_listener_pidso  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=Tr1  Fr2  zphoton/sidecar/index.mjs)r8  r9  rp   r:  r;  r<  )r?  r=  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)   killr:  )r?  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   /healthzr   r   c                >    g | ]}                     |          |S r-   )rB  )r   r?  r~   s     r   r   z5PhotonAdapter._reap_stale_sidecar.<locals>.<listcomp>  s,    BBB(<(<S(A(ABBBBr.   c                    g | ]}|v|	S r-   r-   )r   r?  stales     r   r   z5PhotonAdapter._reap_stale_sidecar.<locals>.<listcomp>  s#    ;;;3#U*:*:3*:*:*:r.   zport z% is in use by another process (pids: unknownuR   , 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   )rE  )r   pr~   s     r   r   z4PhotonAdapter._reap_stale_sidecar.<locals>.<genexpr>  s/      ,O,OAT__Q-?-?,O,O,O,O,O,Or.   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   postrl   rj   ro   RequestErrorr>  r   r   r   r)   rD  signalSIGTERMr:  r   r   r   r   rE  SIGKILL)r~   r   pidsforeignr?  deadlinerM  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           	     v  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            gt          j        t          j        t          j        |t$          j        dk              | _        t+          j                    }|                    |                     | j                            | _        t5          j                    dz   }d }t7          j        d          4 d {V }t5          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# t6          j!        $ r}|}Y d }~nd }~ww xY wt+          j"        d           d {V  t5          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`)r3   r5   rZ   PHOTON_SIDECAR_BINDr[   r`   PHOTON_SIDECAR_WATCH_STDINz	index.mjsrG  )stdinr<  stderrenvstart_new_sessiong      .@rH  r   z Photon sidecar exited with code z before becoming readyr   r   rI  r   rJ  r   rR  z0Photon sidecar did not become ready within 15s: )#r+   r,   r   r]  r)   environcopyrg   rh   rp   rj   rl   ro   r8  Popenrr   PIPESTDOUTrS  rT  rt   r   r   r   _supervise_sidecarru   r   r   r   poll
returncoderU  r   rV  r   )r~   rc  loopr\  last_errr   r   r   s           r   r   zPhotonAdapter._start_sidecar  s     ~-5577 	Q"Q Q Q   &&(((((((((joo#'#3 '+';#$%();%<%<!"%)%7!"&*&9"# -0()'-^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
 
 	
s>   AJ!A H65J6IIJI4J
J"%J"procsubprocess.Popenc                  K   |j         dS |j         }t          j                    }	 	 |                    d|j                   d{V }|sdS t
                              d|                    dd                                                     g# t          $ r&}t
          
                    d|           Y d}~dS d}~ww xY w)z1Pump the sidecar's stdout/stderr into our logger.NTz[photon-sidecar] %szutf-8r  z&[photon-sidecar] supervisor exited: %s)r<  r   r   run_in_executorreadliner   r   decoderstripr   r   )r~   ro  r<  rm  r   r   s         r   rj  z PhotonAdapter._supervise_sidecar  s      ;F%''	H]!11$HHHHHHHH E14;;w	3R3R3Y3Y3[3[\\\	]
  	H 	H 	HNNCQGGGGGGGGG	Hs   $B AB 
B?B::B?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   rH  r   rO  r   rG  )rt   ra  closer   rx   rU  rl   rj   ro   waitr8  r;  rS  rT  r)   killpggetpgidr?  rW  rX  ProcessLookupErrorPermissionError	terminaterD  ru   r   )r~   ro  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,NrD   r   reply_toOptional[str]metadataOptional[Dict[str, Any]]r   c                d   K   |                      ||                     |                     d {V S r   )_sidecar_sendformat_message)r~   rD   r   r~  r  s        r   sendzPhotonAdapter.send?  s>       ''1D1DW1M1MNNNNNNNNNr.   	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   re   
send_image_sidecar_send_attachment)	r~   rD   r  r  r~  r  r  
local_pathr   s	           r   r  zPhotonAdapter.send_imageP  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~   rD   r  r  r~  r  kwargss          r   send_image_filezPhotonAdapter.send_image_filec  J       22Z 3 
 
 
 
 
 
 
 
 	
r.   
audio_pathc                D   K   |                      |||d           d {V S )Nr
  )r  kindr  )r~   rD   r  r  r~  r  r  s          r   
send_voicezPhotonAdapter.send_voicep  sL       22Zw 3 
 
 
 
 
 
 
 
 	
r.   
video_pathc                B   K   |                      |||           d {V S r  r  )r~   rD   r  r  r~  r  r  s          r   
send_videozPhotonAdapter.send_video}  r  r.   	file_path	file_namec                D   K   |                      ||||           d {V S )N)rE   r  r  )r~   rD   r  r  r  r~  r  r  s           r   send_documentzPhotonAdapter.send_document  sL       22YY 3 
 
 
 
 
 
 
 
 	
r.   animation_urlc                D   K   |                      |||||           d {V S r   )r  )r~   rD   r  r  r~  r  s         r   send_animationzPhotonAdapter.send_animation  sG       __]GXx
 
 
 
 
 
 
 
 	
r.   c                   K   	 |                      d|dd           d {V  d S # t          $ r&}t                              d|           Y d }~d S d }~ww xY w)N/typingstartspaceIdstatez[photon] send_typing failed: %s_sidecar_callr   r   r   )r~   rD   r  r   s       r   send_typingzPhotonAdapter.send_typing  s      	?$$wAA            	? 	? 	?LL:A>>>>>>>>>	?   % 
AAAc                   K   	 |                      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: %sr  )r~   rD   r   s      r   stop_typingzPhotonAdapter.stop_typing  s      	?$$w@@            	? 	? 	?LL:A>>>>>>>>>	?r  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   )rz   r   r   _SENT_IDS_MAXr   r   )r~   r  sentr   s       r   _record_sent_messagez"PhotonAdapter._record_sent_message  s     	F%Z 9;;Zt99t)))DIIKK(()I3t99t7I+I)IJ  II *) r.   z^any;-;(\+\d{6,})$c                h    | j                             |          }|r|                    d          n|S )Nr   )_DM_CHAT_GUID_REr   r   )clsrD   r   s      r   _normalize_chat_keyz!PhotonAdapter._normalize_chat_key  s1    $**733!&3u{{1~~~G3r.   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~   rD   r  keylastr   s         r   r  z"PhotonAdapter._record_last_inbound  s      	j 	F&&w//)$;;S	S	t99t333DIIKK((:#d))d:::   II	 43 r.   c                x    t          j        dd                                                                          dv S )NPHOTON_REACTIONSrP   >   r`   ra   rb   rM   rQ   r   s    r   _reactions_enabledz PhotonAdapter._reactions_enabled  s:    y+W55;;==CCEE J
 
 	
r.   r  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   r  NTz [photon] add_reaction failed: %sFr  )r~   rD   r  r  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~   rD   r  r   s       r   _remove_reactionzPhotonAdapter._remove_reaction  s      	$$jII         4 	 	 	LL>BBB55555	r  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{   r7   r  r  )r~   rD   r  r  targetoks         r   add_reactionzPhotonAdapter.add_reaction  s        
t9==$$W-- 
  
  	 O  
 %%gvu======== 	 B    v666r.   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{   r7   r  r  )r~   rD   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6r.   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.NrD   r  u   👀)r  getattrr  r  )r~   r   rD   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r.   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.
        NrD   r  u   👍u   👎)r  r  r  r  r   SUCCESSr  FAILURE)r~   r   r  rD   r  s        r   on_processing_completez$PhotonAdapter.on_processing_completeI  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1r.   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   )rE   r   r   r-   )r~   rD   s     r   get_chat_infozPhotonAdapter.get_chat_info_  s        W===r.   c                @    t                      r|S t          |          S r   )rS   r   )r~   r   s     r   r  zPhotonAdapter.format_messageg  s%      	Ng&&&r.   r   rH  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.
        )rD   r   r~  r  NrA   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   _is_retryable_error_is_timeout_errorranger   r   r   r   MAX_MESSAGE_LENGTH)r~   rD   r   r~  r  r  r  r   result	error_str
is_networkattemptdelayfallback_results                 r   _send_with_retryzPhotonAdapter._send_with_retryo  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[\\\r.   r   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   rS   r  r   r   rp   r  r7   )r~   r   r   bodydatar   s         r   r  zPhotonAdapter._sidecar_send  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  )rE   	mime_typer  r  pathrE   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  rE   r  r  /send-attachmentr   Tr  )	validate_media_delivery_pathrp   r   	mimetypes
guess_typer  r   r  r7   )r~   r   r  rE   r  r  r  	safe_pathr  guessed_r  r  r   s                 r   r  z&PhotonAdapter._sidecar_send_attachment  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   )rx   r   rl   rj   ro   r   r   rU  r   r   r   r7   )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)rV   r   )r   r   r   r   )r   rp   r   r!   )r   rp   r   rp   r   r!   r   r   )r   rp   r   r   )r   rp   r   r!   )r   r   r   r   )r-  r   r   r.  )r?  r   r   r!   )ro  rp  r   r   )NN)
rD   rp   r   rp   r~  r  r  r  r   r   )NNN)rD   rp   r  rp   r  r  r~  r  r  r  r   r   )rD   rp   r  rp   r  r  r~  r  r  r  r   r   )rD   rp   r  rp   r  r  r~  r  r  r  r   r   )rD   rp   r  rp   r  r  r~  r  r  r  r   r   )NNNN)rD   rp   r  rp   r  r  r  r  r~  r  r  r  r   r   )rD   rp   r  rp   r  r  r~  r  r  r  r   r   r   )rD   rp   r   r   )r  r  r   r   )rD   rp   r   rp   )rD   r  r  r  r   r   )rD   rp   r  rp   r  rp   r   r!   )rD   rp   r  rp   r   r!   )rD   rp   r  rp   r  r  r   r   )rD   rp   r  r  r   r   )r   r   r   r   )r   r   r  r   r   r   )rD   rp   r   r   )r   rp   r   rp   )NNr   rH  )rD   rp   r   rp   r~  r  r  r   r  r   r  r  r   r   )r   rp   r   rp   r   r   )r   rp   r  rp   rE   r  r  r  r  r  r  rp   r   r   )r  rp   r  r   r   r   )8__name__
__module____qualname____doc___MAX_MESSAGE_LENGTHr  rf   staticmethodr|   r   r   r   r   r   r   r   r   r>  rB  rE  r]  r   rj  r   r  r  r  r  r  r  r  r  r  r  r  r  r   r   r  classmethodr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  __classcell__)r   s   @r   rU   rU      s         -F
 F
 F
 F
 F
 F
T # # # \#JO O O O
   "0 0 0 0d" " " ",&1 &1 &1 &1PA A A A   "a1 a1 a1 a1J 	Q 	Q 	Q \	Q 
8 
8 
8 \
8    \7 7 7 7r8
 8
 8
 8
tH H H H)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,> > > >' ' ' ' #'D D D D DLJ J J J0 ##'!% /J /J /J /J /J /Jb       r.   rU   r(  rp   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 )NrA   image/zvideo/audio/zapplication/)rR   
startswithr   PHOTOVIDEOAUDIODOCUMENT)r(  s    r   r  r    s    JBDx   !  x   !  x   !  ~&& $##r.   .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   rE   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_bytesrA   r  r  r  r  r  z2[photon] failed to cache inbound attachment %s: %s)r7   base64	b64decoder   r   r   r   r  r  r  r  rR   r   suffixr  _IMAGE_EXT_BY_MIME_AUDIO_EXT_BY_MIMEr   )r   rE   r(  r  data_b64r   r   r  r  r  r  exts               r   r  r  8  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pconfigrD   messager  r  Optional[list]r  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   rY   rZ   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  r  r   zsidecar returned r  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    r6   r7   r)   r*   ri   rk   r   r   r  rS   rU  r   r   r   r  r   r  rp   r   r   r  r   )r  rD   r  r  r  r  r-  tokenbaser   last_message_idr   	send_bodyr   r  r  
media_pathr'  r  r  r  att_bodyr   s                          r   _standalone_sendr(  t  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   )clirX   ziMessage via Photonc                     t          |           S r   )rU   r<   s    r   <lambda>zregister.<locals>.<lambda>  s    M#$6$6 r.   r3   r5   zRun: hermes photon setup  (logs in via device flow, creates a Spectrum project, links your phone number, installs the spectrum-ts sidecar).r@   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.)rE   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_lengthr  pii_safeallow_update_commandplatform_hintz1Set up and manage the Photon iMessage integration)rE   helpr3  
handler_fnN)rA   r*  register_platformr/   r:   r=   gateway_setuprJ   r(  r  register_cli_commandregister_clidispatch)ctx_clis     r   registerrF    s     #66#'!)+BC$ #)2-0.. !?;  $ $ $N @"=	      r.   )r   r   r   r   r   r   r  )r0   r   r   r!   )r   r>   )r(  rp   r   r   )
r   r   rE   rp   r(  rp   r  r!   r   r  )r  r   rD   rp   r  rp   r  r  r  r   r  r!   r   r   r  )Br   
__future__r   r   r  r   loggingr)   r   rm   r'   rW  r8  rS  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   	getLoggerr  r   ri   rk   r  r   r   __file__parentr+   r   r    r/   r:   r=   rJ   rS   rU   r  r  r  r  r(  rF  r-   r.   r   <module>rR     s   . # " " " " "     				 				        



  ' ' ' ' ' ' ' '       ; ; ; ; ; ; ; ; ; ; ; ; ; ;  LLLOO    4 3 3 3 3 3 3 3              5 4 4 4 4 4 * * * * * *		8	$	$
  # 
   ! tH~~$y0 +"                    &
 
 
 
 c c c c c' c c cR#
  
  
  
          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