@@ -11,6 +11,14 @@ const branchName = (name: string) => {
1111 return `agent/${ slug } -${ generateId ( ) . slice ( - 4 ) } `
1212}
1313
14+ const termColors = {
15+ reset : '\x1b[0m' ,
16+ red : '\x1b[31m' ,
17+ yellow : '\x1b[33m' ,
18+ cyan : '\x1b[36m' ,
19+ gray : '\x1b[90m' ,
20+ }
21+
1422export class Agent {
1523 state : AgentState
1624 private process ?: KimiProcess
@@ -54,11 +62,63 @@ export class Agent {
5462 }
5563 }
5664
65+ private termLog ( component : string , level : 'info' | 'warn' | 'error' , message : string ) {
66+ const c = level === 'error' ? termColors . red : level === 'warn' ? termColors . yellow : termColors . cyan
67+ const tag = level === 'info' ? '' : `${ c } ${ level . toUpperCase ( ) } ${ termColors . reset } `
68+ console . error ( `${ c } [${ component } ]${ termColors . reset } ${ tag } ${ message } ` )
69+ }
70+
71+ private isUserVisibleLog ( type : LogEntry [ 'type' ] , content : string ) : boolean {
72+ if ( type === 'input' || type === 'output' ) return true
73+ const patterns = [
74+ 'Agent 已恢复' ,
75+ '工作空间就绪' ,
76+ '启动失败' ,
77+ 'Token 预算已耗尽' ,
78+ 'Kimi CLI 未找到' ,
79+ '启动 Kimi CLI 失败' ,
80+ 'Agent 执行完毕' ,
81+ 'Agent 执行失败' ,
82+ 'Agent 已停止' ,
83+ '代码已推送' ,
84+ '推送失败' ,
85+ 'PR #' ,
86+ '已合并到 main' ,
87+ 'PR 被打回' ,
88+ '合并被拒绝' ,
89+ '审阅通过了此 PR' ,
90+ '审阅拒绝了此 PR' ,
91+ '全员审阅通过' ,
92+ '已指派' ,
93+ '自动修改已达最大轮次' ,
94+ ]
95+ return patterns . some ( ( p ) => content . includes ( p ) )
96+ }
97+
98+ private inferComponent ( content : string ) : string {
99+ if ( content . includes ( 'Kimi' ) || content . includes ( 'kimi' ) ) return 'Kimi'
100+ if ( content . includes ( 'GitHub' ) || content . includes ( 'github' ) ) return 'GitHub'
101+ if ( content . includes ( 'git ' ) || content . includes ( '仓库' ) || content . includes ( '分支' ) || content . includes ( '推送' ) || content . includes ( '代码' ) || content . includes ( '克隆' ) ) return 'Git'
102+ if ( content . includes ( '审阅' ) ) return 'Review'
103+ return 'Agent'
104+ }
105+
57106 private log ( type : LogEntry [ 'type' ] , content : string , tokens ?: number ) {
58107 const entry = this . makeLog ( type , content , tokens )
59108 this . state . logs . push ( entry )
60109 this . state . lastActivity = new Date ( ) . toISOString ( )
61- this . emit ( { type : 'log' , agentId : this . state . id , entry } )
110+
111+ const showInUi = this . isUserVisibleLog ( type , content )
112+ if ( showInUi ) {
113+ this . emit ( { type : 'log' , agentId : this . state . id , entry } )
114+ }
115+
116+ // system/error 都输出到终端(带颜色、组件前缀)
117+ if ( type === 'system' || type === 'error' ) {
118+ const level = type === 'error' ? 'error' : 'info'
119+ const component = this . inferComponent ( content )
120+ this . termLog ( component , level , content )
121+ }
62122 }
63123
64124 private setStatus ( status : TaskStatus ) {
@@ -159,16 +219,24 @@ export class Agent {
159219 }
160220 } ) ( )
161221
162- // stderr reader — filter out kimi CLI internal logging errors
222+ // stderr reader — filter out kimi CLI internal loguru errors (Windows known issue)
223+ let loguruBlockActive = false
163224 ; ( async ( ) => {
164225 try {
165226 for await ( const line of this . process ! . stderr ) {
166227 if ( ! this . running ) break
167- // Skip loguru internal rotation errors on Windows (not actionable)
168- if ( line . includes ( 'Loguru Handler' ) || line . includes ( 'PermissionError' ) ) {
169- this . log ( 'system' , `[kimi stderr filtered] ${ line . substring ( 0 , 120 ) } ` )
228+ // Detect start of loguru error block
229+ if ( line . includes ( '--- Logging error' ) && line . includes ( 'Loguru' ) ) {
230+ loguruBlockActive = true
231+ this . log ( 'system' , '[kimi stderr] loguru logging error filtered (see ~/.kimi/logs)' )
170232 continue
171233 }
234+ // End of loguru block: empty line or non-traceback line after PermissionError
235+ if ( loguruBlockActive && ( line . trim ( ) === '' || ( ! line . startsWith ( ' ' ) && ! line . includes ( 'Traceback' ) ) ) ) {
236+ loguruBlockActive = false
237+ continue
238+ }
239+ if ( loguruBlockActive ) continue
172240 this . log ( 'error' , line )
173241 this . emit ( { type : 'agent-output' , agentId : this . state . id , line, isStderr : true } )
174242 }
@@ -208,10 +276,19 @@ export class Agent {
208276 }
209277 }
210278
211- stop ( ) {
279+ async stop ( ) {
212280 if ( this . process ) {
213281 this . process . kill ( )
214282 this . running = false
283+ // 等待进程完全退出(kill() 内部 2s 后会 SIGKILL,这里最多等 3s)
284+ try {
285+ await Promise . race ( [
286+ this . process . wait ( ) ,
287+ new Promise < void > ( ( _ , reject ) => setTimeout ( ( ) => reject ( new Error ( 'timeout' ) ) , 3000 ) ) ,
288+ ] )
289+ } catch {
290+ // 超时或异常,忽略
291+ }
215292 }
216293 this . state . pid = undefined
217294 this . setStatus ( 'stopped' )
0 commit comments