@@ -28,6 +28,49 @@ import { TaskExecutor } from '../agents/core/task-executor';
2828import { ChatRequestDto , ChatResponseDto } from './dto/chat.dto' ;
2929import { ToolsService } from '../tools/tools.service' ;
3030
31+ /** 与 SecurityReActAgent 中 THINK/EXEC 事件的 step 关联键一致,避免并行子任务共用 iteration 导致前端时间线串台 */
32+ function sseStepKey ( data : Record < string , unknown > , iteration : number ) : string {
33+ const todoId = data [ 'todoId' ] ;
34+ if ( todoId !== undefined && todoId !== null && String ( todoId ) . length > 0 ) {
35+ return `todo-${ todoId } ` ;
36+ }
37+ return `iter-${ iteration } ` ;
38+ }
39+
40+ /** CLI 流式输出用:结构化精炼报告 + 较短最终回复,避免刷屏 */
41+ function buildStreamSummaryPayload ( summary : InteractionSummary ) : {
42+ report : string ;
43+ response : string ;
44+ } {
45+ const findings = summary . keyFindings
46+ . slice ( 0 , 6 )
47+ . map ( ( l ) => `- ${ l } ` )
48+ . join ( '\n' ) ;
49+ const recs = summary . recommendations
50+ . slice ( 0 , 6 )
51+ . map ( ( l ) => `- ${ l } ` )
52+ . join ( '\n' ) ;
53+ const parts : string [ ] = [ ] ;
54+ const head = summary . taskSummary ?. trim ( ) ;
55+ if ( head ) parts . push ( `## 摘要\n${ head } ` ) ;
56+ if ( findings ) parts . push ( `## 关键发现\n${ findings } ` ) ;
57+ if ( recs ) parts . push ( `## 修复建议\n${ recs } ` ) ;
58+ const tail = summary . overallConclusion ?. trim ( ) ;
59+ if ( tail ) parts . push ( `## 总结\n${ tail } ` ) ;
60+ let report = parts . join ( '\n\n' ) ;
61+ if ( ! report . trim ( ) ) {
62+ report = summary . rawReport ?. trim ( ) ? summary . rawReport . slice ( 0 , 12_000 ) : '(未能生成摘要,请查看服务端日志。)' ;
63+ }
64+ /** 短收尾:详细章节已在「安全报告」块中展示,避免 TUI 再打一屏重复摘要 */
65+ const response =
66+ tail ||
67+ ( summary . keyFindings [ 0 ] ?. trim ( )
68+ ? `首要发现:${ summary . keyFindings [ 0 ] . trim ( ) } `
69+ : '' ) ||
70+ '详情见上方「安全报告」。' ;
71+ return { report, response } ;
72+ }
73+
3174@Injectable ( )
3275export class ChatService {
3376 private eventBus = new EventBus ( ) ;
@@ -120,34 +163,44 @@ export class ChatService {
120163 const t = event . type ;
121164 const d = event . data ;
122165 if ( t === EventType . THINK_START ) {
123- emit ( 'thought_start' , { iteration : d [ 'iteration' ] ?? 1 } ) ;
166+ const iteration = Number ( d [ 'iteration' ] ?? 1 ) ;
167+ emit ( 'thought_start' , {
168+ iteration,
169+ step_key : sseStepKey ( d , iteration ) ,
170+ task : d [ 'task' ] as string | undefined ,
171+ } ) ;
124172 } else if ( t === EventType . THINK_END ) {
173+ const iteration = Number ( d [ 'iteration' ] ?? 1 ) ;
125174 emit ( 'thought' , {
126175 content : d [ 'thought' ] ?? '' ,
127- iteration : d [ 'iteration' ] ?? 1 ,
176+ iteration,
177+ step_key : sseStepKey ( d , iteration ) ,
128178 } ) ;
129179 } else if ( t === EventType . EXEC_START ) {
180+ const iteration = Number ( d [ 'iteration' ] ?? 1 ) ;
130181 emit ( 'action_start' , {
131182 tool : d [ 'tool' ] ?? '' ,
132183 params : d [ 'params' ] ?? { } ,
133- iteration : d [ 'iteration' ] ?? 1 ,
184+ iteration,
185+ step_key : sseStepKey ( d , iteration ) ,
134186 } ) ;
135187 } else if ( t === EventType . EXEC_RESULT ) {
188+ const iteration = Number ( d [ 'iteration' ] ?? 1 ) ;
136189 emit ( 'action_result' , {
137190 tool : d [ 'tool' ] ?? '' ,
138191 success : d [ 'success' ] ?? true ,
139192 result : d [ 'observation' ] ?? '' ,
140- iteration : d [ 'iteration' ] ?? 1 ,
193+ iteration,
194+ step_key : sseStepKey ( d , iteration ) ,
141195 } ) ;
142196 }
143197 } ;
144198
145- let response : string ;
146199 if ( planResult . todos . length > 1 ) {
147200 const executor = new TaskExecutor ( planResult , selectedAgent , this . eventBus ) ;
148- response = await executor . run ( message , onAgentEvent ) ;
201+ await executor . run ( message , onAgentEvent ) ;
149202 } else {
150- response = await selectedAgent . process ( message , { onEvent : onAgentEvent } ) ;
203+ await selectedAgent . process ( message , { onEvent : onAgentEvent } ) ;
151204 }
152205
153206 emit ( 'phase' , { phase : 'summarizing' , detail : '正在生成报告...' } ) ;
@@ -162,13 +215,12 @@ export class ChatService {
162215 mode : planResult . todos . length <= 1 ? 'brief' : 'full' ,
163216 } ) ;
164217
165- if ( summary . rawReport ) {
166- emit ( 'report' , { content : summary . rawReport } ) ;
167- }
218+ const streamPayload = buildStreamSummaryPayload ( summary ) ;
219+ emit ( 'report' , { content : streamPayload . report } ) ;
168220
169- emit ( 'response' , { content : response , agent : agentType } ) ;
221+ emit ( 'response' , { content : streamPayload . response , agent : agentType } ) ;
170222 emit ( 'done' , { } ) ;
171- return response ;
223+ return streamPayload . response ;
172224 }
173225
174226 async chatSync ( body : ChatRequestDto ) : Promise < ChatResponseDto > {
0 commit comments