
    eKGj1                         d 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 ddlm	Z	 ddl
mZ ddlZddlZddlmZ  ej                   e      Zdedefd	Zd
edefdZ G d d      Zy)z
Brain module for Kimi Copilot.

Maintains session context, decides when to invoke the side model,
calls the side model, and writes valuable corrections to RAG.
    N)datetime)Path)Any)	HintPanelvaluereturnc                 z    t        | t              s| S t        j                  d      }d }|j	                  ||       S )z5Replace ${VAR:default} placeholders in config values.z\$\{([^}:]+)(?::([^}]*))?\}c                     | j                  d      }| j                  d      xs d}t        j                  j                  ||      S )N       )grouposenvironget)matchvar_namedefaults      0/Users/admin/workspace/jyq/kimi-copilot/brain.pyreplz_resolve_env.<locals>.repl"   s6    ;;q>++a.&Bzz~~h00    )
isinstancestrrecompilesub)r   patternr   s      r   _resolve_envr      s8    eS!jj78G1
 ;;tU##r   objc                     t        | t              r-| j                         D ci c]  \  }}|t        |       c}}S t        | t              r| D cg c]  }t        |       c}S t        |       S c c}}w c c}w N)r   dictitems_deep_resolve_envlistr   )r   kvitems       r   r$   r$   *   sg    #t47IIK@DAq$Q''@@#t478D!$'88 A8s   A2A8c            
          e Zd Z	 	 	 dPdedefdZdedefdZd Zdefd	Z	e
d
edefd       Ze
d
edee   defd       Ze
d
edee   defd       ZdQdedefdZdRdedefdZd
efdZd
efdZd
efdZd Zd
edefdZdee   fdZdee   fdZd Zdedz  fdZd Zd Zd
efdZd
ededz  fdZd
edee   fd Z d
efd!Z!d" Z"d# Z#dee   fd$Z$d%efd&Z%d' Z&d(efd)Z'd%efd*Z(d
ededz  fd+Z)d,ed-efd.Z*d,ed/ee   fd0Z+d(efd1Z,d2ee   dee   fd3Z-e
dSd4ed5ed6edefd7       Z.dTd8ed9e/defd:Z0d8efd;Z1d4ed5edefd<Z2d=edefd>Z3d?edee   fd@Z4e
dAe5dee   fdB       Z6de7e   fdCZ8dDee   dee   fdEZ9dFedee   fdGZ:d=edHee   dIee   dee   fdJZ;d=edHee   dedz  fdKZ<d=edLee   fdMZ=d,edNefdOZ>y)UBrainNconfig_pathpanelc                     | j                  |      | _        g | _        d| _        d| _        d| _        d| _        |xs
 t               | _        |xs d | _	        | j                         | _        | j                  r t        j                  d| j                         g | _        t               | _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        | j5                          y )Nr   r   c                     t        | d      S )Nr   )end)print)msgs    r   <lambda>z Brain.__init__.<locals>.<lambda>B   s    uSb?Q r   zDetected project: %s        F)_load_configconfighistorypending_inputpending_outputround_countlast_trigger_roundr   r,   notify_callback_detect_projectprojectloggerinforecent_issuessetstuck_alerts_shownlast_trigger_timelast_scan_timefrustration_scorelast_frustration_timepending_frustration_summaryfrustration_trigger_timelast_frustration_summary_timelast_frustration_check_timefrustration_notified_setup_logging)selfr+   r,   r;   s       r   __init__zBrain.__init__3   s     ''4#% "#)ik
  /R3Q ++-<<KK.= *,,/E!$! "#%("+0((+%-0*+.($)!r   pathr   c                     t        |dd      5 }t        j                  |      }d d d        t        |      S # 1 sw Y   t              S xY w)Nrutf-8encoding)openyaml	safe_loadr$   )rM   rO   fraws       r   r4   zBrain._load_config]   sD    $g. 	$!..#C	$ %%	$ %%s	   8Ac                    | j                   j                  di       j                  dd      }| j                   j                  di       j                  dd      }t        j                         }|j                  ry t        j
                  |d      }|j                  t        j                  d             |j                  t        t        |j                         t        j                               |j                  |       y )	NlogginglevelINFOfilezkimi-copilot.logrR   rS   z'%(asctime)s [%(levelname)s] %(message)s)r5   r   r[   	getLoggerhandlersFileHandlersetFormatter	FormattersetLevelgetattrupperr]   
addHandler)rM   r\   log_pathroothandlers        r   rL   zBrain._setup_loggingb   s    	2.227FC;;??9b155f>PQ  "==%%hAGH	
 	ggu{{}gllCD r   c                 f   t         j                  j                  dd      j                         }|r|S | j                  j                  dd      j                         }|r|S t        j
                         j                  dd      }|j                  d      D cg c]  }|s|	 }}|r|d   S yc c}w )zDetermine the current project for scoping knowledge.

        Priority:
        1. COPILOT_PROJECT environment variable
        2. config.yaml ``project`` field
        3. The current working directory name
        COPILOT_PROJECTr   r=   \/)r   r   r   stripr5   getcwdreplacesplit)rM   r=   cwdppartss        r   r<   zBrain._detect_projectq   s     **..!2B7==?N++//)R0668Niik!!$,IIcN0qa009 1s   B. B.textc                 P    t        j                  d      }|j                  d|       S )zERemove ANSI escape sequences including CSI and OSC (e.g. hyperlinks).zC\x1B(?:\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])r   )r   r   r   )rw   ansi_escapes     r   
strip_ansizBrain.strip_ansi   s*     jj
 r4((r   patternsc                 Z    | j                         }|D ]  }|j                         |v s y y)NTFlower)rw   r{   loweredr   s       r   _matches_anyzBrain._matches_any   s2    **, 	G}}')	 r   c                     | j                         }|D ]^  }|j                         }d}	 |j                  ||      }|dk(  r.|dkD  r+||dz
     }|j                         s|dv r|t        |      z   }I y y)u   Match patterns at a word boundary on the left side.

        This avoids firing on embedded substrings like "可能性" while still
        allowing Chinese phrases such as "我可能错了" to trigger.
        r   Tro   r   z_-F)r~   findisalnumlen)rw   r{   r   r   needlestartidxprevs           r   _matches_word_boundaryzBrain._matches_word_boundary   s     **, 	G]]_FEll651"9 7"37+D||~ #c&k 1 	 r   cooldown_secondsc                 |    t        j                         j                         }|| j                  z
  |k  ry|| _        y)z+Throttle side-model triggers to avoid spam.FT)r   now	timestamprC   rM   r   r   s      r   _should_triggerzBrain._should_trigger   s8    lln&&('''*::!$r   c                 |    t        j                         j                         }|| j                  z
  |k  ry|| _        y)z:Throttle real-time scans to avoid spamming the side model.FT)r   r   r   rD   r   s      r   _should_scanzBrain._should_scan   s8    lln&&($$$'77!r   c                    | j                  |      }| xj                  |z  c_        d| j                  v sd| j                  v r| j                  j                         }|rht        j	                  d|       | j
                  j                  d|d       | j                          | j                  |       | j                  |d       d| _        yy)	z*Feed a chunk of user input into the brain.
zUser input: %ruserrolecontentsourcer   N)
rz   r7   rp   r>   debugr6   append_prune_history_check_frustration_check_trigger)rM   rw   cleanlines       r   feed_user_inputzBrain.feed_user_input   s    %e#4%%%1C1C)C%%++-D-t4##V$EF##%''-##D#8!#D *Dr   c                    | j                  |      }| xj                  |z  c_        d| j                  v sd| j                  v rt        j                  d| j                        }|d   | _        |dd D ]}  }|j	                         }|st
        j                  d|       | j                  j                  d|d       | j                          | j                  |       | j                  |d	        yy)
z+Feed a chunk of Kimi output into the brain.r   r   z[\r\n]+ro   NzKimi output: %rkimir   r   )rz   r8   r   rs   rp   r>   r   r6   r   r   _check_resolutionr   )rM   rw   r   linesr   s        r   feed_kimi_outputzBrain.feed_kimi_output   s    %u$4&&&$$2E2E*EHHZ)<)<=E"')Dcr
 =zz|LL!2D9LL''D(IJ'')**40''V'<= +Fr   c                     | j                  |      j                         }|rXt        j                  d|       | j                  j                  d|d       | j                          | j                  |d       yy)z:Feed explicit tool/command output (e.g. failure messages).zTool output: %rtoolr   r   N)rz   rp   r>   r   r6   r   r   r   )rM   rw   r   s      r   feed_tool_outputzBrain.feed_tool_output   sf    %++-LL*E2LLE BC!f5	 r   c                 R   | j                   j                  di       j                  dd      }t        d | j                  D              }||kD  rZ| j                  rM| j                  j	                  d      }|t        |j                  dd            z  }||kD  r| j                  rKyyyy)	z?Keep history bounded so memory and request size do not explode.copilotmax_history_charsi@ c              3   R   K   | ]  }t        |j                  d d             ! ywr   r   Nr   r   .0ms     r   	<genexpr>z'Brain._prune_history.<locals>.<genexpr>   s      D!Ci,-D   %'r   r   r   N)r5   r   sumr6   popr   )rM   	max_charstotalremoveds       r   r   zBrain._prune_history   s    KKOOIr2667JFS	Dt||DDiDLLll&&q)GSY344E iDLLLr   r   c                 >   | j                   j                  di       j                  di       }| j                   j                  di       j                  dd      }|j                  dd      r{|dk(  rv| j                   j                  di       j                  dg       }| j                  ||      r8| j                  |      r&t        j                  d	|       | j                          y
|j                  dd      rz|dv rv| j                   j                  di       j                  dg       }| j                  ||      r8| j                  |      r&t        j                  d|       | j                          y
|j                  dd      r{|dk(  rv| j                   j                  di       j                  dg       }| j                  ||      r8| j                  |      r&t        j                  d|       | j                          y
|j                  dd      }|dkD  rx|dk(  rs| xj                  dz  c_        | j                  | j                  z
  |k\  rB| j                  |      r0t        j                  d| j                         | j                          y
| j                          y
)z(Decide whether to invoke the side model.r   triggerstrigger_cooldown_seconds      @user_invokeTr   invoke_keywordszTriggered by user invoke: %rNcommand_failure)r   r   failure_patternsz Triggered by failure pattern: %runcertainty_keywordsr   uncertainty_patternsz$Triggered by uncertainty keyword: %rperiodic_roundsr   r   z"Triggered periodically at round %d)r5   r   r   r   r>   r?   _invoke_copilotr   r9   r:   _scan_and_trigger)	rM   rw   r   r   cooldownr   r   r   periodics	            r   r   zBrain._check_trigger   s:   ;;??9b155j"E;;??9b1556PRUV <<t,61A"kkooi<@@ARTVWO  7''1KK >E((* <<)40V?O5O#{{y"=AABTVXY  '78''1KK BDI((* <<.5&F:J#';;??9b#A#E#EF\^`#a **41EF''1KK FM((* << 115a<Ff,!$"9"99XE''1KK DdFVFVW((* 	 r   c                 8   | j                   j                  di       j                  dd      }t        j                  j	                  |      r(t        |dd      5 }|j                         }d d d        nd}ddg}| j                   j                  d	i       j                  d
d      }t        | j                        |kD  r| j                  | d  n| j                  }|D ]-  }|d   }|dk(  rd}n|dk(  rd}|j                  ||d   d       / |S # 1 sw Y   xY w)Npromptssystemzprompts/copilot_system.txtrQ   rR   rS   #You are a helpful coding assistant.r   r   max_history_linesd   r   r   	assistantr   r   r   
r5   r   r   rO   existsrU   readr   r6   r   )	rM   system_pathrX   system_contentmessagesmax_historyrecent_historyr1   r   s	            r   _build_messageszBrain._build_messages!  s   kkooi488C_`77>>+&k39 *Q!"* * CN%.ABkkooi4889LcR8;DLL8IK8W{lm4]a]i]i! 	GCv;Dv~"OOTc)nEF	G * *s   DDc                 \   | j                   j                  di       j                  dd      }t        j                  j	                  |      r(t        |dd      5 }|j                         }ddd       nd}d	d
g}| j                   j                  di       j                  di       }|j                  dd      }t        | j                        |kD  r| j                  | d n| j                  }|D ]-  }|d   }	|	dk(  rd}	n|	dk(  rd}	|j                  |	|d   d
       / |S # 1 sw Y   xY w)z9Build a short context for the lightweight real-time scan.r   scanzprompts/copilot_scan.txtrQ   rR   rS   NzqYou are a helpful coding assistant. Review the conversation and output JSON with has_issue and risk_score (0-10).r   r   r   real_time_scanr      r   r   r   r   r   r   r   )
rM   	scan_pathrX   r   r   scan_cfgr   r   r1   r   s
             r   _build_scan_messageszBrain._build_scan_messages5  s-   KKOOIr266v?YZ	77>>)$iw7 *1!"* *` 
 &.AB;;??9b1556FKll#6;8;DLL8IK8W{lm4]a]i]i! 	GCv;Dv~"OOTc)nEF	G '* *s   D""D+c           	         | j                   sy| j                  | _        | j                         }| j                  j                  di       j                  dd      }| j                  j                  di       j                  dd      }t        d |D              }t        |      |kD  r!t        j                  dt        |      |       y||kD  rt        j                  d	||       y| j                  j                  d
i       }|j                  d      || j                  j                  di       j                  dd      | j                  j                  di       j                  dd      d}t        j                  dt        |             	 t        j                  |j                  d      dd|j                  dd       d|| j                  j                  di       j                  dd            }|j                  s:t        j                  d|j                  |j                   |j"                  dd        y|j%                          	 |j)                         }	|	d   d   d   d    }
| j1                  |
       y# t        j&                  $ r }t        j                  d|       Y d}~yd}~ww xY w# t*        t,        t(        j.                  f$ r }t        j                  d!|       Y d}~yd}~ww xY w)"z*Call the side model and handle the result.Nr   max_messages_hardx   max_request_chars c              3   R   K   | ]  }t        |j                  d d             ! ywr   r   r   s     r   r   z(Brain._invoke_copilot.<locals>.<genexpr>Z        G3quuY34Gr   z5Skipping side model call: too many messages (%d > %d)z;Skipping side model call: request too large (%d > %d chars)modelnametemperature皙?
max_tokens   r   r   r   r   z#Calling side model with %d messagesbase_urlapplication/jsonBearer api_keyr   zContent-TypeAuthorizationtimeoutheadersjsonr   z%Side model request failed: %s %s - %s  zSide model request failed: %schoicesr   messager   z'Failed to parse side model response: %s)r6   r9   r:   r   r5   r   r   r   r>   warningr?   requestspostokerrorstatus_codereasonrw   raise_for_statusRequestExceptionr   KeyError
IndexErrorJSONDecodeError_handle_copilot_resultrM   r   max_messagesr   request_sizecfgpayloadrespedatar   s              r   r   zBrain._invoke_copilotN  s   ||"&"2"2'') {{y"599:MsS KKOOIr:>>?RTZ[GhGGx=<'NNGH
 ++NNM!
 kkoogr*WWV_ ;;??9b9==mSQ++//)R8<<\4P	
 	93x=I	==
#$6'.swwy"/E.F%G 	26::9cJD 77;$$KKIIdsO	 !!#
	99;D9oa(3I>G
 	##G, (( 	LL8!<	 *d&:&:; 	LLBAF	s7   B-I: 9I: 
J0 :J-J((J-0K.K))K.c           	         | j                   sy| j                         }| j                  j                  di       j                  dd      }| j                  j                  di       j                  dd      }t	        d |D              }t        |      |kD  r!t        j                  dt        |      |       y||kD  rt        j                  d	||       y| j                  j                  d
i       }| j                  j                  di       j                  di       }|j                  d      || j                  j                  di       j                  dd      |j                  dd      d}t        j                  dt        |             	 t        j                  |j                  d      dd|j                  dd       d||j                  dd            }|j                  s:t        j                  d|j                  |j                  |j                  dd        y|j!                          	 |j%                         }
|
d   d    d!   d"   }| j-                  |      }|st        j                  d$|dd%        y|S # t        j"                  $ r }	t        j                  d|	       Y d}	~	yd}	~	ww xY w# t&        t(        t$        j*                  f$ r }	t        j                  d#|	       Y d}	~	yd}	~	ww xY w)&z0Call the side model for a lightweight risk scan.Nr   r   r   r   r   c              3   R   K   | ]  }t        |j                  d d             ! ywr   r   r   s     r   r   z%Brain._invoke_scan.<locals>.<genexpr>  r   r   z4Skipping real-time scan: too many messages (%d > %d)z:Skipping real-time scan: request too large (%d > %d chars)r   r   r   r   r   r   i   r   z6Calling side model for real-time scan with %d messagesr   r   r   r   r   r   r      r   z)Real-time scan request failed: %s %s - %sr   z!Real-time scan request failed: %sr   r   r   r   z!Failed to parse scan response: %sz"Scan did not return valid JSON: %r   )r6   r   r5   r   r   r   r>   r   r?   r   r   r   r   r  r  rw   r  r  r   r  r  r  _extract_json)rM   r   r
  r   r  r  r   r  r  r  r  r   results                r   _invoke_scanzBrain._invoke_scan  s   ||,,. {{y"599:MsS KKOOIr:>>?RTZ[GhGGx=<'NNFH
 ++NNL!
 kkoogr*;;??9b1556FKWWV_ ;;??9b9==mSQ",,|S9	
 	LcRZm\	==
#$6'.swwy"/E.F%G  Y3D 77?$$KKIIdsO	 !!#
	99;D9oa(3I>G
 ##G,NN?#O! (( 	LL<a@	 *d&:&:; 	LL<a@	s7   BI>  I> 1J4 >J1J,,J14K2K--K2c                    | j                   j                  di       j                  di       }|j                  dd      sy|j                  dd      }| j                  |      sy| j                         }|sy|j                  dd      }|j                  d	d
      }|j                  dd      }t        j                  d|||       |s||k  ryt        j                  d       | j                          y)zGLightweight qwen scan after each turn; triggers deep analysis if risky.r   r   enabledFNr         @	has_issue
risk_scorer   	threshold   z6Scan result: has_issue=%s, risk_score=%s, threshold=%szScan triggered deep analysis)r5   r   r   r  r>   r?   r   )rM   r   r   r  r  r  r  s          r   r   zBrain._scan_and_trigger  s    ;;??9b1556FK||Iu-<< 2C8  *""$JJ{E2	ZZa0
LLa0	D		
 Z)323r   c                 d   | j                   j                  di       j                  di       }|j                  dd      }t        j                         j	                         }|| j
                  z
  |kD  r>| j                  dkD  r t        j                  d| j                         d| _        d| _	        yy)	zHReset frustration score if too much time has passed without new signals.r   frustration_detectiondecay_secondsi,  r   z&Frustration score decayed from %d to 0FN)
r5   r   r   r   r   rF   rE   r>   r   rK   )rM   r  decayr   s       r   _decay_frustrationzBrain._decay_frustration  s    kkooi,001H"M-lln&&(+++e3%%)EtG]G]^%&D"(-D%	 4r   c                    | j                   j                  di       j                  di       }|j                  dd      sy| j                          t        j                         j                         }|j                  dd      }|| j                  z
  |k  ry|| _        | j                  |      }|sy|j                  dd	      }t        |t        t        f      r|d	k  ry| xj                  t        |      z  c_        || _        t        j                  d
|| j                         |j                  dd      }| j                  |k\  r3d| _        || _        | j"                  sd| _        | j%                          yyy)zRUse qwen to semantically detect user frustration and mark for later summarization.r   r   r  FNr   g       @scorer   z*Frustration check score=%s, accumulated=%dr     T)r5   r   r#  r   r   r   rJ   _invoke_frustration_checkr   intfloatrE   rF   r>   r?   rG   rH   rK   _notify_frustration_detected)rM   rw   r  r   r   r  r%  r  s           r   r   zBrain._check_frustration  sP   kkooi,001H"Mwwy%(!lln&&(77-s3111H<+.(//5

7A&%#u.%1*#e*,%("8""	
 GGK+	!!Y./3D,,/D),,,0)113 - /r   c           	      $   | j                  |      }| j                  j                  di       j                  dd      }| j                  j                  di       j                  dd      }t        d |D              }t	        |      |kD  s||kD  ry| j                  j                  di       j                  di       }| j                  j                  d	i       }|j                  d
      || j                  j                  di       j                  dd      |j                  dd      d}	 t        j                  |j                  d      dd|j                  dd       d||j                  dd            }	|	j                  s:t        j                  d|	j                  |	j                  |	j                  dd        y|	j                          	 |	j                         }|d   d   d   d   }| j'                  |      }|st        j)                  d!|dd"        y|S # t
        j                  $ r }
t        j                  d|
       Y d}
~
yd}
~
ww xY w# t         t"        t        j$                  f$ r }
t        j                  d |
       Y d}
~
yd}
~
ww xY w)#z6Call qwen to judge whether the user sounds frustrated.r   r   r   r   r   c              3   R   K   | ]  }t        |j                  d d             ! ywr   r   r   s     r   r   z2Brain._invoke_frustration_check.<locals>.<genexpr>2  r   r   Nr   r   r   r   r   r      r   r   r   r   r   r   r   r      r   z,Frustration check request failed: %s %s - %sr   z$Frustration check request failed: %sr   r   r   r   z.Failed to parse frustration check response: %sz/Frustration check did not return valid JSON: %rr  )!_build_frustration_check_messagesr5   r   r   r   r   r   r   r>   r   r  r  rw   r  r  r   r  r  r  r  r   )rM   rw   r   r
  r   r  r  	model_cfgr  r  r  r  r   r  s                 r   r'  zBrain._invoke_frustration_check,  sD   99$?{{y"599:MsS KKOOIr:>>?RTZ[GhGGx=<'<:K+Kkkooi,001H"MKKOOGR0	]]6* ;;??9b9==mSQ'',4	
	==j)$6'.y}}Y/K.L%M 	2.D 77B$$KKIIdsO	 !!#
	99;D9oa(3I>G
 ##G,NNLgVZWZm\ (( 	LL?C	 *d&:&:; 	LLI1M	s7   )BH =H I I.I		IJ/J

Jc                    | j                   j                  di       j                  dd      }t        j                  j	                  |      r(t        |dd      5 }|j                         }ddd       nd}d	d
g}t        | j                        dkD  r| j                  dd n| j                  }|D ]-  }|d   }|dk(  rd}n|dk(  rd}|j                  ||d   d
       / |j                  dd| d
       |S # 1 sw Y   xY w)zBuild a short prompt asking qwen to judge user frustration.

        Includes the most recent conversation turns so qwen can understand
        context and distinguish a normal correction from real frustration.
        r   frustrationzprompts/copilot_frustration.txtrQ   rR   rS   NzzJudge whether the user input shows frustration or dissatisfaction. Output JSON with is_frustrated (bool) and score (0-10).r   r   r&  ir   r   r   r   r   r   u   【当前输入】r   )	rM   rw   prompt_pathrX   r   r   r   r1   r   s	            r   r/  z'Brain._build_frustration_check_messagesc  s    kkooi488<
 77>>+&k39 *Q!"* *J 
 &.AB.1$,,.?!.Cbc*! 	GCv;Dv~"OOTc)nEF	G 	6H4OPQ'* *s   C::Dc                 X   | j                   sy| j                  j                  di       j                  di       }|j                  dd      syt        j                         j                         }|j                  dd      }|| j                  z
  |kD  r&t        j                  d       | j                          y|j                  d	d
      }|| j                  z
  |k  ry|j                  dg       }|sy| j                  ||      r&t        j                  d       | j                          yy)zRIf the user was frustrated and Kimi now seems to have solved it, summarize to RAG.Nr   r   r  Fsummary_window_secondsiX  z-Frustration summary window expired, resettingsummary_cooldown_seconds<   resolution_patternszDResolution signal detected after frustration, triggering RAG summary)rG   r5   r   r   r   r   rH   r>   r?   _reset_frustration_staterI   r   _invoke_frustration_rag_summary)rM   rw   r  r   windowr   r{   s          r   r   zBrain._check_resolution  s   //kkooi,001H"Mwwy%(lln&&(137...7KKGH))+775r:333h>770"5T8,KK^_002 -r   c                 <    d| _         d| _        d| _        d| _        y )Nr   Fr3   )rE   rG   rH   rK   )rM   s    r   r9  zBrain._reset_frustration_state  s"    !"+0((+%$)!r   c           	      F   | j                         }| j                  j                  di       j                  dd      }| j                  j                  di       j                  dd      }t        d |D              }t	        |      |kD  r1t
        j                  dt	        |      |       | j                          y||kD  r(t
        j                  d	||       | j                          y| j                  j                  d
i       }|j                  d      || j                  j                  di       j                  dd      | j                  j                  di       j                  dd      d}t
        j                  dt	        |             	 t        j                  |j                  d      dd|j                  dd       d|| j                  j                  di       j                  dd            }|j                  sJt
        j                  d|j                  |j                  |j                  dd        | j                          y|j!                          	 |j%                         }	|	d   d   d   d    }
| j-                  |
       y# t        j"                  $ r0}t
        j                  d|       | j                          Y d}~yd}~ww xY w# t&        t(        t$        j*                  f$ r0}t
        j                  d!|       | j                          Y d}~yd}~ww xY w)"zGCall the side model to summarize a frustration-resolution arc into RAG.r   r   r   r   r   c              3   R   K   | ]  }t        |j                  d d             ! ywr   r   r   s     r   r   z8Brain._invoke_frustration_rag_summary.<locals>.<genexpr>  r   r   z=Skipping frustration RAG summary: too many messages (%d > %d)NzCSkipping frustration RAG summary: request too large (%d > %d chars)r   r   r   r   r   r   r   z?Calling side model for frustration RAG summary with %d messagesr   r   r   r   r   r   r   r   z2Frustration RAG summary request failed: %s %s - %sr   z*Frustration RAG summary request failed: %sr   r   r   r   z4Failed to parse frustration RAG summary response: %s)_build_frustration_rag_messagesr5   r   r   r   r>   r   r9  r?   r   r   r   r   r  r  rw   r  r  r   r  r  r  _handle_frustration_rag_resultr	  s              r   r:  z%Brain._invoke_frustration_rag_summary  s   779{{y"599:MsS KKOOIr:>>?RTZ[GhGGx=<'NNOH
 ))+++NNU!
 ))+kkoogr*WWV_ ;;??9b9==mSQ++//)R8<<\4P	
 	UWZ[cWde	==
#$6'.swwy"/E.F%G 	26::9cJD 77H$$KKIIdsO	 --/!!#	99;D9oa(3I>G 	++G4 (( 	LLEqI))+	 *d&:&:; 	LLOQRS))+	s7   B=J J K K&K

KL 0&LL c                    | j                   j                  di       j                  dd      }t        j                  j	                  |      r(t        |dd      5 }|j                         }ddd       nd}d	d
g}| j                  D ]-  }|d   }|dk(  rd}n|dk(  rd}|j                  ||d   d
       / |S # 1 sw Y   MxY w)z4Build messages summarizing the full frustration arc.r   frustration_ragz#prompts/copilot_frustration_rag.txtrQ   rR   rS   NzySummarize the following coding conversation into a structured RAG entry. Output JSON with worth_rag and rag_entry fields.r   r   r   r   r   r   r   r   )	r5   r   r   rO   r   rU   r   r6   r   )rM   r3  rX   r   r   r1   r   s          r   r?  z%Brain._build_frustration_rag_messages  s    kkooi488D
 77>>+&k39 *Q!"* *C 
 &.AB<< 	GCv;Dv~"OOTc)nEF	G !* *s   B::Cr   c                    t        j                         j                         | _        | j	                          | j                  |      }|st        j                  d|dd        y|j                  d      st        j                  d       y|j                  di       }|st        j                  d       y|j                  dd	      }t        j                  d
|       | j                  |       | j                  |       y)z9Parse the side model output and write to RAG if valuable.z5Frustration RAG summary did not return valid JSON: %rNr  	worth_ragz1Frustration RAG summary deemed not worth storing.	rag_entryz@Frustration RAG summary returned worth_rag=true but no rag_entrytitle   未命名知识z*Frustration RAG summary produced entry: %s)r   r   r   rI   r9  r  r>   r   r   r?   
_write_rag_notify_frustration_rag_write)rM   r   r  rE  rF  s        r   r@  z$Brain._handle_frustration_rag_result   s    -5\\^-E-E-G*%%'##G,NNRT[\`]`Tabzz+&KKKLJJ{B/	NN]^g'89@%H	"**51r   c                 L    g d}| j                  dj                  |             y)zBLet the user know we'll auto-summarize once the issue is resolved.)r   u<   [Copilot] 😤 检测到你似乎对当前问题不太满意ub   Kimi 解决后，我会自动把这段经验提炼成知识库条目，避免下次再走弯路。	[Copilot]r   r   N)r;   join)rM   r   s     r   r*  z"Brain._notify_frustration_detected  s"    
 	TYYu-.r   rF  c                     t        j                         j                  d      }ddd| d| ddg}| j                  dj	                  |             y)	z=Confirm that a frustration-resolution arc was written to RAG.%Y-%m-%d %H:%M:%Sr   u6   [Copilot] ✅ 已自动总结踩坑经验并写入 RAG   时间:    标题: rK  r   Nr   r   strftimer;   rL  rM   rF  r   r   s       r   rI  z#Brain._notify_frustration_rag_write#  sY    lln%%&9:Dseug
 	TYYu-.r   c                    | j                  |      }|st        j                  d|dd        y|j                  dd      }|j                  dd      }|j                  dd      }|j                  d	d
      }|j                  d      r"|j                  di       j                  dg       ng }|st        j	                  d       yt        j	                  d||       ||||d}| j
                  j                  |       | j                  j                  ||||       | j                  j                  ||      \  }	}
|	r(t        j	                  d|       | j                  ||
       | j                  |      r| j                  ||
       | j                  ||       |j                  d      r'|j                  di       }|r| j                  |       yyy)z*Parse the side model output and act on it.z(Side model did not return valid JSON: %rNr  r  Fsummaryr   
suggestionseveritymediumrE  tagszSide model found no issue.zSide model found issue [%s]: %s)rU  rV  rY  rW  zRepeated mistake detected: %srD  )r  r>   r   r   r?   r@   r   r,   	add_issueis_repeated_notify_repeated_detect_stuck_state_handle_stuck_state_notify_userrH  )rM   r   r  r  rU  rV  rW  rY  issue_recordr[  relatedrE  s               r   r  zBrain._handle_copilot_result0  s   ##G,NNEwtPS}UJJ{E2	**Y+ZZb1
::j(3>Djj>Uvzz+r*..vr:[]KK455xI $ 	
 	!!,/ 	

Wj$A#zz55dGDWKK7A!!'73 ##L1$$\7; 	':.::k"

;3I	*  #r   c                    |j                         }|j                  d      rC|j                  d      }|j                         j                  d      r|dd j                         }	 t        j                  |      S # t        j
                  $ r Y nw xY wt        j                  d|t        j                        }|r>	 t        j                  |j                  d            S # t        j
                  $ r Y yw xY wy)z.Best-effort JSON extraction from model output.z````r      Nz\{.*\}r   )
rp   
startswithr~   r   loadsr  r   searchDOTALLr   )rM   rw   r   s      r   r  zBrain._extract_json^  s    zz|??5!::c?Dzz|&&v.ABx~~'	::d#### 		 		)T2995zz%++a.11'' s$   &A; ;BB<#C   C65C6rU  rV  c                     ddd| g}|r|j                  d|        |j                  ddg       | j                  dj                  |             y)z2Emit a notice to the user in the wrapped terminal.r   u/   [Copilot] ⚠️ 旁路模型发现潜在问题u   摘要: u   建议: rK  r   Nr   extendr;   rL  )rM   rU  rV  r   s       r   r_  zBrain._notify_users  sV    F(SZR[H\]LL8J<01k2&'TYYu-.r   ra  c                 v    ddd| dt        |       ddddg}| j                  dj                  |             y	)
z8Warn the user when a similar issue has been seen before.r   u$   [Copilot] 🔁 检测到重复踩坑   当前问题: u   历史相关提示: u    条u9   运行 python3 copilot_panel.py --open 打开提示面板rK  r   N)r   r;   rL  )rM   rU  ra  r   s       r   r\  zBrain._notify_repeated{  sM     2WI&"3w<.5G
 	TYYu-.r   c                     t        j                         j                  d      }ddd| d| ddg}| j                  dj	                  |             y)	z7Emit a confirmation when a new entry is written to RAG.rN  r   u!   [Copilot] ✅ 知识已写入 RAGrO  rP  rK  r   NrQ  rS  s       r   _notify_rag_writezBrain._notify_rag_write  sY    lln%%&9:/seug
 	TYYu-.r   rY  c                    t        |xs g       }| j                  r$d| j                   }||vr|j                  |       | j                  j	                  di       j	                  dg       D ]  }||vs|j                  |        |S )z2Add project and default scope tags to a RAG entry.project:ragdefault_scopes)r%   r=   r   r5   r   )rM   rY  enrichedproject_tagscopes        r   _enrich_tagszBrain._enrich_tags  s    
#<<$T\\N3K(*,[[__UB/334DbI 	'EH$&	' r   abr  c                    | j                  d      xs dj                         }|j                  d      xs dj                         }|r-|r+t        j                  d||      j	                         }||k\  ry| j                  d      xs dj                         }|j                  d      xs dj                         }|r-|r+t        j                  d||      j	                         }||k\  ryy)z4Return True if two RAG entries look like duplicates.rF  r   NTquestionF)r   r~   difflibSequenceMatcherratio)rx  ry  r  a_titleb_titler~  
a_question
b_questions           r   _is_similar_ragzBrain._is_similar_rag  s     55>'R..055>'R..0w++D'7CIIKE	!eeJ'-2446
eeJ'-2446
*++D*jIOOQE	!r   rE  lookbackc                     | j                   j                  | d D ]+  }|j                  d      dk7  r| j                  ||      s+ y y)zDCheck whether a similar RAG entry already exists in the local panel.Ntyperr  TF)r,   recordsr   r  )rM   rE  r  records       r   _has_duplicate_in_panelzBrain._has_duplicate_in_panel  sP    jj(((4 	Fzz&!U*##Iv6		
 r   c           
         | j                   j                  di       }|j                  d      }|st        j                  d       y|j                  dd      }|j                  dd      }|j                  d	d      }|j                  d
d      }| j	                  |j                  dg             }| j                  |      rt        j                  d|       yd| d| d| ddj                  |       }	||	d}
t        j                  d|       	 t        j                  ||
d      }|j                          t        j                  d|j                                | j                  j                  |||||       | j                  |       y# t        j                  $ r }t        j!                  d|       Y d}~yd}~ww xY w)z3Write a structured entry to the RAG knowledge base.rr  urlz'RAG URL not configured, skipping write.NrF  rG  r{  r   wrong_approachcorrect_approachrY  z Skipping duplicate RAG entry: %su	   问题：u   
错误方案：   
正确方案：u
   
标签：, )rF  r   zWriting to RAG: %sr  r   r   zRAG write OK: %s)rF  r{  wrongcorrectrY  zRAG write failed: %s)r5   r   r>   r   rw  r  r?   rL  r   r   r  r   r,   add_ragro  r  r   )rM   rE  rag_cfgr  rF  r{  r  r  rY  r   r  r  r  s                r   rH  zBrain._write_rag  s   ++//%,kk% NNDEg'89==R0.3-- 2B7  vr!:; ''	2KK:EB z "#W %%Y '		$(* 	 "g6(%0	4==7B?D!!#KK*DIIK8JJ!   ""5)(( 	4LL/33	4s   A=F G!F<<Gc                 :   t        d |j                  dg       D              }t        d |j                  dg       D              }|r|r||z  ry|j                  dd      j                         }|j                  dd      j                         }|r|r	||v s||v ryy)z@Return True if two issues look like the same underlying problem.c              3   B   K   | ]  }|s|j                           y wr!   r}   r   ts     r   r   z*Brain._is_similar_issue.<locals>.<genexpr>       ?1QQWWY?   rY  c              3   B   K   | ]  }|s|j                           y wr!   r}   r  s     r   r   z*Brain._is_similar_issue.<locals>.<genexpr>  r  r  TrU  r   F)rA   r   r~   )rM   rx  ry  a_tagsb_tags	a_summary	b_summarys          r   _is_similar_issuezBrain._is_similar_issue  s    ?fb(9???fb(9??f&6/EE)R(..0	EE)R(..0		Y(>)yBXr   current_issuec                 &   | j                   j                  di       j                  di       }|j                  dd      sy|j                  dd      }d}t        | j                  d	d
       D ]  }| j	                  ||      r|dz  } ||k\  S  ||k\  S )z?Check whether Kimi has failed multiple times on similar issues.r   stuck_detectionr  TFconsecutive_similar_failuresr   r   Nro   )r5   r   reversedr@   r  )rM   r  	stuck_cfgr  similar_countpasts         r   r]  zBrain._detect_stuck_state  s    KKOOIr2667H"M	}}Y-MM"@!D	 T//45 	D%%mT:" 	))	 	))r   queryc                    | j                   j                  di       j                  dd      }|sg S d|id|id|ig}|D ]M  }	 t        j                  ||d      }|j	                          |j                         }| j                  |      c S  g S # t        j                  $ r }t        j                  d	|       Y d
}~d
}~wt        $ r }t        j                  d|       Y d
}~d
}~ww xY w)z@Query the RAG knowledge base. Tries a few common payload shapes.rr  	query_urlr   qr  r{  r.  r  zRAG query attempt failed: %sNzRAG query parse failed: %s)r5   r   r   r   r  r   _normalize_rag_resultsr  r>   r   	Exception)rM   r  r  
candidatesr  r  r  r  s           r   
_query_ragzBrain._query_rag  s    KKOOE2.22;C	I %Le


 " 	G
}}YWbI%%'yy{22488	 	 ,, ;Q? 91=s%   ABC*#B>>C*
C%%C*r  c                    t        | t              r| gS t        | t              rng }| D ]e  }t        |t              r|j                  |       %t        |t              s6dD ]+  }||v s||   s|j                  t        ||                 e g |S t        | t              r1dD ]   }|| v st
        j                  | |         c S  t        |       gS t        |       gS )zIBest-effort extraction of text snippets from various RAG response shapes.)r   rw   answersnippetdocument)resultsr  	documentschunksr  responser   )r   r   r%   r   r"   r*   r  )r  snippetsr(   keys       r   r  zBrain._normalize_rag_results   s     dC 6MdD!H "dC(OOD)d+S "$;49$OOCS	N;!"	" OdD!b C$; 77S	BBC I;D	{r   c                     t               }| j                  r|j                  d| j                          | j                  j	                  di       j	                  dg       D ]  }|j                  |        |S )z6Tags that are allowed for the current project context.rq  rr  rs  )rA   r=   addr5   r   )rM   scopesrv  s      r   _scope_tagszBrain._scope_tags8  sd    <<JJ$,,01[[__UB/334DbI 	EJJu	r   r  c                     | j                         }|s|S g }|D ]L  }t        t        j                  d|            }|s|j	                  |       6||z  s<|j	                  |       N |S )zHDrop RAG snippets that belong to a different project and are not common.z(?:project|scope):[\w-]+)r  rA   r   findallr   )rM   r  allowedfilteredr  tags_in_snippets         r   _filter_rag_snippets_by_scopez#Brain._filter_rag_snippets_by_scopeA  st    ""$O 		)G!

6@O #(((		) r   issuec                    | j                   j                  |j                  dg       |j                  dd      d| j                               }g }|D ]  }|j                  d      dk(  rK|j	                  d|j                  d	d       d
|j                  dd       d|j                  dd              b|j                  d      dk(  sw|j	                  d|j                  dd       d|j                  dd               |S )z2Use the local panel history as fallback knowledge.rY  rU  r   r  )limitallowed_scopesr  rr  u	   标题：rF  u
   
问题：r{  r  r  r  	   摘要：u
   
建议：rV  )r,   find_relatedr   r  r   )rM   r  ra  r  rQ   s        r   _query_local_knowledgezBrain._query_local_knowledgeT  s   **))IIfb!IIi$++-	 * 
  	AuuV}%gr 23 4  !j" 56 7&&'ee,>&C%DF
 v')i 45 6  !lB 78:	 r   related_issuesknowledge_snippetsc           
         | j                   j                  di       j                  dd      }t        j                  j	                  |      r(t        |dd      5 }|j                         }d d d        nd}dd	g}| j                  D ]-  }|d
   }	|	dk(  rd}	n|	dk(  rd}	|j                  |	|d   d	       / dd|j                  dd       d|j                  dd       ddj                  |j                  dg              ddg}
t        |dd  d      D ]a  \  }}|
j                  | d|j                  dd              |j                  d      s>|
j                  d|j                  dd              c |r@|
j                  ddg       t        |d d d      D ]  \  }}|
j                  | d|         |j                  dd j                  |
      d	       |S # 1 sw Y   vxY w)!Nr   stuckzprompts/copilot_stuck.txtrQ   rR   rS   r   r   r   r   r   r   r   r   r   u#   === 当前反复出现的问题 ===r  rU  r   u	   建议：rV  u	   标签：r  rY  u,   === 本次会话中相似的历史问题 ===r   z. u      建议：u   === 相关知识 ===r  r   )r5   r   r   rO   r   rU   r   r6   r   rL  	enumeraterk  )rM   r  r  r  r   rX   r   r   r1   r   rv   r   r  r  s                 r   _build_stuck_messageszBrain._build_stuck_messagesk  s    kkooi4880
 77>>+&k39 *Q!"* * CN%.AB << 	GCv;Dv~"OOTc)nEF	G 2)))R89:)),;<=		-"3"3FB"?@AB:
 $N23$7; 	KJCLLC5599Y#;"<=>yy&|EIIlB,G+HIJ	K
 LL"456 )*<Ra*@! D 2WuBwi012 	DIIe4DEFI* *s   G""G,c           	         |j                  dd      }| j                  r| j                   d| }| j                  |      }| j                  |      }| j	                  |      }||z   }| j                  |||      }| j                  j                  di       }|j                  d      || j                  j                  di       j                  dd      | j                  j                  di       j                  d	d
      d}	t        j                  dt        |             	 t        j                  |j                  d      dd|j                  dd       d|	| j                  j                  di       j                  dd            }
|
j                  s:t        j                  d|
j                  |
j                  |
j                   dd        y|
j#                          	 |
j'                         }|d   d   d   d   }| j/                  |      S # t        j$                  $ r }t        j                  d|       Y d}~yd}~ww xY w# t(        t*        t&        j,                  f$ r }t        j                  d|       Y d}~yd}~ww xY w)z@Call the side model with RAG/local knowledge for stuck analysis.rU  r   u
    相关：r   r   r   r   r   r   r   r   z6Calling side model for stuck analysis with %d messagesr   r   r   r   r   r   r   r   z)Stuck analysis request failed: %s %s - %sNr   z!Stuck analysis request failed: %sr   r   r   r   z+Failed to parse stuck analysis response: %s)r   r=   r  r  r  r  r5   r>   r?   r   r   r   r   r   r  r  rw   r  r  r   r  r  r  r  )rM   r  r  r  rag_snippetslocal_snippetsr  r   r  r  r  r  r  r   s                 r   _invoke_stuck_analysiszBrain._invoke_stuck_analysis  s8    !!)R0<<||nJug6Eu-99,G44]C)N:--m^M_`kkoogr*WWV_ ;;??9b9==mSQ++//)R8<<\4P	
 	LcRZm\	==
#$6'.swwy"/E.F%G 	26::9cJD 77?$$KKIIdsO	 !!#
	99;D9oa(3I>G
 !!'** (( 	LL<a@	 *d&:&:; 	LLFJ	s7   !B-H H  I I"H==IJ#I>>Jrelated_panel_issuesc           
         |j                  dd      }|j                         dd }|| j                  v ry| j                  j                  |       | j                  dd D cg c]  }| j                  ||      r| }}| j                  ||      }|sy|j                  d      st        j                  d       y|j                  dd      }|j                  d	d      }	|j                  d
g       }
|	st        j                  d       y| j                  j                  |||	|
|j                  dg              | j                  ||       yc c}w )z:Produce an intervention hint when Kimi is stuck in a loop.rU  r   NP   ro   is_stuckz4Side model does not consider the issue a stuck loop.
root_causeintervention_promptsourcesz2Stuck analysis returned empty intervention prompt.rY  )rU  r  r  r  rY  )r   r~   rB   r  r@   r  r  r>   r?   r   r,   add_intervention_notify_stuck)rM   r  r  rU  	alert_keyr  session_relatedr  r  r  r  s              r   r^  zBrain._handle_stuck_state  sL   ##Ir2MMOCR(	///##I.  $11#26
%%mU; 
 

 ,,]OLzz*%KKNOZZb1
$jj)>C**Y+"NNOP

##! 3""62. 	$ 	
 	7J/7
s   !Er  c                     ddd| g}|r|j                  d|        |j                  g d       | j                  dj                  |             y)zNWarn the user that Kimi appears to be stuck and an intervention hint is ready.r   u)   [Copilot] 🚨 Kimi 似乎陷入了循环rm  u   根因: )u9   运行 python3 copilot_panel.py --open 查看干预建议rK  r   r   Nrj  )rM   rU  r  r   s       r   r  zBrain._notify_stuck  sW     7WI&

 LL8J<01cdTYYu-.r   )zconfig.yamlNN)r   )r  )g      ?)2   )?__name__
__module____qualname__r   r   rN   r"   r4   rL   r<   staticmethodrz   r%   boolr   r   r)  r   r   r   r   r   r   r   r   r   r   r  r   r#  r   r'  r/  r   r9  r:  r?  r@  r*  rI  r  r  r_  r\  ro  rw  r  r(  r  rH  r  r]  r  r   r  rA   r  r  r  r  r  r^  r   r   r   r*   r*   2   s    )	(( (T& & &
! , 
) 
) 
) 
) 3 $s)    S DI $  0  U T $C $=S = 6S 65+!3 +! +!Zd (d4j 2C-JGdTk GR>	.$4s $4L5c 5dTk 5nc d4j >3c 38*B5Hd 02c 20	//3 /,+c ,+\# $+ */C /S // /d4j //s /
c 
tCy 
 4 D U d  $  d )4D )4V
4 
D 
T 
* *$ *& S	 2 S T#Y  .SX d3i DI &D T#Y ... T
. !I	.
 
d.`6+6+ T
6+ 
	6+p$0 $0TRVZ $0L
/S 
/c 
/r   r*   )__doc__r|  r   r[   r   r   stringr   pathlibr   typingr   r   rV   copilot_panelr   r_   r  r>   r   r$   r*   r  r   r   <module>r     sv       	 	       # 
		8	$$ $ $3 3 Q/ Q/r   