
    5VjK&                       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m	Z	 ddl
mZ  e	e      j                         j                  j                  dz  dz  Zej                   j#                  dd	      Z eh d
      ZdZ ej,                  dej.                        Z ej,                  dej.                        ZddZdddZddZddZddZddZ	 	 	 	 ddZ ddZ!y)u0  Hard enforcement of the NEVER rules in memory/show-on-mention.md.

The show-on-mention rules used to live only in the prompt, and a prompt rule is a
suggestion: "NEVER open email" is one line arguing against a hundred lines that
describe, concretely, how to drive Gmail in the browser. The model rationalises
("I'm not *showing* the inbox, I'm *reading* it") and opens the page anyway.

So the NEVER rules are enforced HERE, in code, as a PreToolUse gate. If a NEVER
rule blocks a surface, the tool call that would open it is DENIED before it runs.
The model cannot talk its way past this; the worst it can do is answer without
the page, which is exactly what Ahmed asked for.

Only SURFACE-OPENING tools are gated (browser navigation, app launches, shell
`open`). Data tools are never touched — a NEVER rule means "don't put it on my
screen", not "don't answer me", so mcp__gmail__gmail_search still works fine and
Jarvis can read the inbox out loud without Chrome ever coming up.

Rule format (memory/show-on-mention.md):
    - NEVER · "email" (say: emails, check email) → block: mail.google.com, Mail
The `→ block:` clause lists what must not be opened: URL fragments and/or app
names, comma-separated. A NEVER rule with no block clause can't be enforced (it
stays prompt-only), so Jarvis is told to always write one.
    )annotationsN)Path)Anymemoryzshow-on-mention.mdJARVIS_CHROME_CDPzhttp://127.0.0.1:9222>   appbashexecgotoopenshelllaunchscrapeactivateapp_toolnavigateopen_app	osascript
launch_app
run_script
shell_toolapplescriptrun_commandbrowser_tabsexecute_scriptbrowser_tab_newexecute_commandbrowser_evaluatebrowser_navigatebrowser_run_code_unsafe)r   r   r   r   r   r   u   NEVER\s*·\s*"([^"]+)"zblock\s*:\s*(.+)$c                 .   	 t         j                  d      } g }| j                         D ]  }|j	                         }|j                  d      s%t        j                  |      }t        j                  |      }|r|sT|j                  d      j                  d      D cg c]1  }|j	                         s|j	                         j                         3 }}|s|j                  |j                  d      |f        |S # t        $ r g cY S w xY wc c}w )u   Live-read the enforceable NEVER rules → [(topic, [blocked patterns])].

    Re-read on every call so a rule Ahmed just set ("never open gmail") is armed
    on his very next utterance, with no restart.
    zutf-8)encodingz-    ,)_RULES	read_textOSError
splitlinesstrip
startswith	_TOPIC_REsearch	_BLOCK_REgroupsplitlowerappend)bodyoutlinetopicblockppatss          =/Users/ahmed/devFolder/Ultron/claude-voice/voice/show_gate.pynever_rulesr:   @   s    1 (*C!zz|t$  &  &E+0;;q>+?+?+DR+Da		!+DRJJA-. " J  	 Ss   D  D6 DDDc                "   dkD  ryt        | t              r| j                         S t        | t              r(dj	                  fd| j                         D              S t        | t        t        f      rdj	                  fd| D              S y)u  EVERY string in an opener's payload, as one lowercase blob.

    It used to read only a whitelist of keys ("url", "command", "script", …).
    That is a list you have to keep in sync with every MCP server's schema, and
    it was already out of date: macos-automator names its parameter
    `script_content`, not `script`, so an AppleScript that did
    `open location "https://mail.google.com"` produced an EMPTY blob and sailed
    straight through the gate.

    We only ever build this for a tool that can OPEN something (see
    `_OPENER_NAMES`), so there is no innocent field to protect: if a shell /
    AppleScript / navigate call mentions the blocked surface anywhere in its
    payload, it is about to open it.     c              3  <   K   | ]  }t        |d z           ywr#   N_blob.0vdepths     r9   	<genexpr>z_blob.<locals>.<genexpr>l   s     I5Ha+5H   c              3  <   K   | ]  }t        |d z           ywr@   rA   rC   s     r9   rG   z_blob.<locals>.<genexpr>n   s     @Za+ZrH   )
isinstancestrr0   dictjoinvalueslisttuple)
tool_inputrF   s    `r9   rB   rB   Y   sv     qy*c"!!*d#xxIZ5F5F5HIII*tUm,xx@Z@@@    c                    | xs dj                         j                  dd      d   }|t        v xs t        fdt        D              S )zCan this tool put something on screen? Bare name (after the MCP prefix)
    against the known-opener set, plus a substring fallback.r=   __r#   c              3  &   K   | ]  }|v  
 y wN )rD   tnames     r9   rG   z_is_opener.<locals>.<genexpr>w   s     'J>aT	>s   )r0   rsplit_OPENER_NAMESany_OPENER_SUBSTR)	tool_namebarerZ   s     @r9   
_is_openerra   r   sG     O""$D;;tQ#D= JC'J>'J$JJrR   c                p    d| v r| |v S t        j                  dt        j                  |        d|      duS )u  Does `pat` name the surface `blob` opens?

    A DOMAIN pattern (has a dot: mail.google.com) matches as a plain substring.
    A bare word (an app name: Mail) matches only on WORD BOUNDARIES — a naive
    substring would make "mail" match ".../email-accounts" and silently break
    the open-smartlead workflow. Boundaries keep "mail" off "email"/"mailchimp"
    while still catching "open -a Mail"..z\bN)rer,   escape)patblobs     r9   _hitrh   z   s=     czd{99299S>*"-t4D@@rR   c                    t        |       syt        |      }|syt               D ]   \  }}|D ]  }t        ||      s||fc c S  " y)zF(topic, pattern) if this call would open a NEVER'd surface, else None.N)ra   rB   r:   rh   )r_   rQ   rg   r5   r8   rf   s         r9   checkrj      sO    i D"}tCCcz!  % rR   c                    t               D  cg c]  \  } }|D ]  }|  }}} }|sg S 	 t        j                  j                  t         dd      5 }t        j                  |      }ddd       g }D ]  }|j                  d      dk7  r|j                  d      xs dj                         t        fd	|D              sP	 t        j                  j                  t         d
|d    d      5  |j                  |j                  dd             ddd        |S c c}}} w # 1 sw Y   xY w# t        $ r g cY S w xY w# 1 sw Y   1xY w# t        $ r Y w xY w)u(  Close any tab in the Jarvis Chrome sitting on a NEVER'd surface.

    Denying browser_navigate is not enough on its own. If a Gmail tab is ALREADY
    open — left over from an earlier session, or resurrected by Chrome's session
    restore — Jarvis can put it on Ahmed's screen without navigating anywhere:
    select the tab, front the window, snapshot it. Worse, a tab-select tool call
    carries only an index, no URL, so the gate can't judge it.

    So we make the tab not exist. Swept before any browser action, there is
    nothing to select, nothing to front, nothing to show. Plain CDP HTTP (no
    Playwright dependency, no await): GET /json/list → GET /json/close/<id>.

    Only touches the dedicated ~/.jarvis-chrome automation profile — never the
    real Chrome Ahmed browses in.
    z
/json/listg      ?)timeoutNtypepageurlr=   c              3  6   K   | ]  }t        |        y wrW   )rh   )rD   r7   ro   s     r9   rG   zsweep_tabs.<locals>.<genexpr>   s     .A43<s   z/json/close/id)r:   urllibrequesturlopen_CDPjsonload	Exceptiongetr0   r]   r1   )	_psr7   r8   rtabsclosedrY   ro   s	           @r9   
sweep_tabsr      sO     '=5=%!R"QA"A=D5	^^##tfJ$7#E99Q<D F F55=F"uuU|!r((*...	''fL4	2C ( AaeeE2./A  M+ 6 FE 	A A  		sX   D"(D5 D)*D5 -E4"EE)D2.D5 5EEE	E	EEc                p  K   | j                  d      xs dj                         }d|v sd|v sd|v st        |      r t               D ]  }t	        d|dd          t        | j                  dd      | j                  d	i       xs i       }|si S |\  }}t	        d
| d|        dddd| d| ddiS w)u   PreToolUse hook — deny anything that would open a NEVER'd surface, and
    sweep away any already-open one before Jarvis touches the browser.r_   r=   browserdesktopmacz)       [show-gate] closed a NEVER'd tab: NF   rQ   z$       [show-gate] BLOCKED opening 'u   ' — NEVER rule: hookSpecificOutput
PreToolUsedenyz,BLOCKED by Ahmed's standing NEVER rule for "z)" (memory/show-on-mention.md): never put uT   on his screen. This is not a soft preference — he has told you repeatedly and gets angry when you do it anyway. Do NOT retry, do NOT reach for another tool to open it, do NOT mention that you were blocked. Get the information from a DATA tool instead (for email: the mcp__gmail__* / mcp__email__* API tools) and just answer him out loud.)hookEventNamepermissionDecisionpermissionDecisionReason)ry   r0   ra   r   printrj   )
input_datatool_use_idcontexttoolro   hitr5   rf   s           r9   gater      s      NN;'-2446DDI-$$<C=c#2hZHI   
{B/|R06B8C	JE3	05Gw
OP)"(?w G::= ?''	
 s   B4B6c                 H    t         j                  j                  dd      dk7  S )N	SHOW_GATE10)osenvironry   rX   rR   r9   enabledr      s    ::>>+s+s22rR   )returnzlist[tuple[str, list[str]]])r   )rQ   r   rF   intr   rK   )r_   rK   r   bool)rf   rK   rg   rK   r   r   )r_   rK   rQ   dict[str, Any]r   ztuple[str, str] | None)r   z	list[str])r   r   r   z
str | Noner   r   r   r   )r   r   )"__doc__
__future__r   rv   r   rd   urllib.requestrr   pathlibr   typingr   __file__resolveparentr%   r   ry   ru   	frozensetr\   r^   compileIr+   r-   r:   rB   ra   rh   rj   r   r   r   rX   rR   r9   <module>r      s   . #  	 	   	h			!	(	(	/	/(	:=Q	Q	zz~~)+BC  
 
9 BJJ0"$$7	BJJ+RTT2	22K
A%P .D3rR   