@@ -14,6 +14,7 @@ import { useCallback, useEffect, useMemo, useRef } from "react";
1414import type { SessionEvent } from "../stores/sessionStore" ;
1515import { useSessionViewStore } from "../stores/sessionViewStore" ;
1616import { AgentMessage } from "./AgentMessage" ;
17+ import { ConsoleMessage } from "./ConsoleMessage" ;
1718import { formatDuration , GeneratingIndicator } from "./GeneratingIndicator" ;
1819import { MessageEditor } from "./MessageEditor" ;
1920import { ToolCallBlock } from "./ToolCallBlock" ;
@@ -79,11 +80,18 @@ interface ToolData {
7980 result ?: unknown ;
8081}
8182
83+ interface ConsoleData {
84+ level : "info" | "debug" | "warn" | "error" ;
85+ message : string ;
86+ timestamp ?: string ;
87+ }
88+
8289interface ParsedMessage {
8390 id : string ;
84- type : "user" | "agent" | "tool" ;
91+ type : "user" | "agent" | "tool" | "console" ;
8592 content : string ;
8693 toolData ?: ToolData ;
94+ consoleData ?: ConsoleData ;
8795 eventIndex ?: number ;
8896}
8997
@@ -98,6 +106,9 @@ function parseSessionNotification(
98106 notification : SessionNotification ,
99107) : ParseResult {
100108 const { update } = notification ;
109+ if ( ! update ?. sessionUpdate ) {
110+ return null ;
111+ }
101112
102113 switch ( update . sessionUpdate ) {
103114 case "user_message_chunk" :
@@ -161,86 +172,132 @@ function mapToolStatus(
161172 }
162173}
163174
164- function processEvents ( events : SessionEvent [ ] ) : ParsedMessage [ ] {
165- const messages : ParsedMessage [ ] = [ ] ;
166- let currentAgentText = "" ;
167- let agentMessageKey = 0 ;
168- let agentStartEventIndex = 0 ;
169- const toolCalls = new Map <
170- string ,
171- { toolData : ParsedMessage [ "toolData" ] ; eventIndex : number }
172- > ( ) ;
173-
174- const flushAgentMessage = ( _eventIndex : number ) => {
175- if ( currentAgentText ) {
176- messages . push ( {
177- id : `agent-${ agentMessageKey } ` ,
178- type : "agent" ,
179- content : currentAgentText ,
180- eventIndex : agentStartEventIndex ,
181- } ) ;
182- currentAgentText = "" ;
183- agentMessageKey ++ ;
175+ class MessageBuilder {
176+ private messages : ParsedMessage [ ] = [ ] ;
177+ private pendingAgentText = "" ;
178+ private agentStartIndex = 0 ;
179+ private agentMessageCount = 0 ;
180+ private toolMessages = new Map < string , ParsedMessage > ( ) ;
181+
182+ flushAgentText ( ) : void {
183+ if ( ! this . pendingAgentText ) return ;
184+ this . messages . push ( {
185+ id : `agent-${ this . agentMessageCount ++ } ` ,
186+ type : "agent" ,
187+ content : this . pendingAgentText ,
188+ eventIndex : this . agentStartIndex ,
189+ } ) ;
190+ this . pendingAgentText = "" ;
191+ }
192+
193+ addUser ( content : string , ts : number , eventIndex : number ) : void {
194+ this . flushAgentText ( ) ;
195+ this . messages . push ( {
196+ id : `user-${ ts } ` ,
197+ type : "user" ,
198+ content,
199+ eventIndex,
200+ } ) ;
201+ }
202+
203+ addAgentChunk ( content : string , eventIndex : number ) : void {
204+ if ( ! this . pendingAgentText ) {
205+ this . agentStartIndex = eventIndex ;
184206 }
207+ this . pendingAgentText += content ;
208+ }
209+
210+ addTool ( toolData : ToolData , eventIndex : number ) : void {
211+ this . flushAgentText ( ) ;
212+ const msg : ParsedMessage = {
213+ id : `tool-${ toolData . toolCallId } ` ,
214+ type : "tool" ,
215+ content : "" ,
216+ toolData,
217+ eventIndex,
218+ } ;
219+ this . toolMessages . set ( toolData . toolCallId , msg ) ;
220+ this . messages . push ( msg ) ;
221+ }
222+
223+ updateTool ( toolData : ToolData ) : void {
224+ const existing = this . toolMessages . get ( toolData . toolCallId ) ;
225+ if ( ! existing ?. toolData ) return ;
226+ existing . toolData . status = toolData . status ;
227+ existing . toolData . result = toolData . result ;
228+ if ( toolData . kind ) existing . toolData . kind = toolData . kind ;
229+ }
230+
231+ addConsole ( consoleData : ConsoleData , _ts : number , eventIndex : number ) : void {
232+ this . flushAgentText ( ) ;
233+ this . messages . push ( {
234+ id : `console-${ eventIndex } ` ,
235+ type : "console" ,
236+ content : consoleData . message ,
237+ consoleData,
238+ eventIndex,
239+ } ) ;
240+ }
241+
242+ build ( ) : ParsedMessage [ ] {
243+ this . flushAgentText ( ) ;
244+ return this . messages ;
245+ }
246+ }
247+
248+ function tryParseConsoleMessage (
249+ event : SessionEvent ,
250+ ) : { level : ConsoleData [ "level" ] ; message : string } | null {
251+ if ( event . type !== "acp_message" ) return null ;
252+ const msg = event . message as {
253+ method ?: string ;
254+ params ?: { level ?: string ; message ?: string } ;
185255 } ;
256+ if ( msg ?. method !== "_posthog/console" || ! msg . params ?. message ) return null ;
257+ return {
258+ level : ( msg . params . level ?? "info" ) as ConsoleData [ "level" ] ,
259+ message : msg . params . message ,
260+ } ;
261+ }
262+
263+ function processEvents ( events : SessionEvent [ ] ) : ParsedMessage [ ] {
264+ const builder = new MessageBuilder ( ) ;
186265
187266 for ( let i = 0 ; i < events . length ; i ++ ) {
188267 const event = events [ i ] ;
189- if ( event . type === "session_update" ) {
190- const parsed = parseSessionNotification ( event . notification ) ;
191- if ( ! parsed ) continue ;
192-
193- switch ( parsed . type ) {
194- case "user" : {
195- flushAgentMessage ( i ) ;
196- messages . push ( {
197- id : `user-${ event . ts } ` ,
198- type : "user" ,
199- content : parsed . content ,
200- eventIndex : i ,
201- } ) ;
202- break ;
203- }
204- case "agent" : {
205- if ( ! currentAgentText ) {
206- agentStartEventIndex = i ;
207- }
208- currentAgentText += parsed . content ;
209- break ;
210- }
211- case "tool" : {
212- flushAgentMessage ( i ) ;
213- toolCalls . set ( parsed . toolData . toolCallId , {
214- toolData : parsed . toolData ,
215- eventIndex : i ,
216- } ) ;
217- messages . push ( {
218- id : `tool-${ parsed . toolData . toolCallId } ` ,
219- type : "tool" ,
220- content : "" ,
221- toolData : parsed . toolData ,
222- eventIndex : i ,
223- } ) ;
224- break ;
225- }
226- case "tool_update" : {
227- const existing = toolCalls . get ( parsed . toolData . toolCallId ) ;
228- if ( existing ) {
229- existing . toolData ! . status = parsed . toolData . status ;
230- existing . toolData ! . result = parsed . toolData . result ;
231- if ( parsed . toolData . kind ) {
232- existing . toolData ! . kind = parsed . toolData . kind ;
233- }
234- }
235- break ;
236- }
237- }
268+
269+ const consoleMsg = tryParseConsoleMessage ( event ) ;
270+ if ( consoleMsg ) {
271+ builder . addConsole (
272+ { ...consoleMsg , timestamp : new Date ( event . ts ) . toISOString ( ) } ,
273+ event . ts ,
274+ i ,
275+ ) ;
276+ continue ;
238277 }
239- }
240278
241- flushAgentMessage ( events . length - 1 ) ;
279+ if ( event . type !== "session_update" ) continue ;
280+
281+ const parsed = parseSessionNotification ( event . notification ) ;
282+ if ( ! parsed ) continue ;
283+
284+ switch ( parsed . type ) {
285+ case "user" :
286+ builder . addUser ( parsed . content , event . ts , i ) ;
287+ break ;
288+ case "agent" :
289+ builder . addAgentChunk ( parsed . content , i ) ;
290+ break ;
291+ case "tool" :
292+ builder . addTool ( parsed . toolData , i ) ;
293+ break ;
294+ case "tool_update" :
295+ builder . updateTool ( parsed . toolData ) ;
296+ break ;
297+ }
298+ }
242299
243- return messages ;
300+ return builder . build ( ) ;
244301}
245302
246303interface ConversationTurn {
@@ -374,6 +431,15 @@ export function SessionView({
374431 switch ( message . type ) {
375432 case "agent" :
376433 return < AgentMessage key = { message . id } content = { message . content } /> ;
434+ case "console" :
435+ return message . consoleData ? (
436+ < ConsoleMessage
437+ key = { message . id }
438+ level = { message . consoleData . level }
439+ message = { message . consoleData . message }
440+ timestamp = { message . consoleData . timestamp }
441+ />
442+ ) : null ;
377443 case "tool" :
378444 return message . toolData ? (
379445 < ToolCallBlock
0 commit comments