@@ -362,6 +362,7 @@ export class PlanDataService {
362362 * Parse an agent message object or repr string:
363363 * Input forms supported:
364364 * - { type: 'agent_message', data: "AgentMessage(agent_name='X', timestamp=..., content='...')"}
365+ * - { type: 'agent_message', data: { agent_name: 'X', timestamp: 12345, content: '...' } }
365366 * - "AgentMessage(agent_name='X', timestamp=..., content='...')"
366367 * Returns a structured object with steps parsed from markdown-ish content.
367368 */
@@ -380,11 +381,62 @@ export class PlanDataService {
380381 raw_data : any ;
381382 } | null {
382383 try {
383- // Unwrap wrapper
384- if ( rawData && typeof rawData === 'object' && rawData . type === WebsocketMessageType . AGENT_MESSAGE && typeof rawData . data === 'string' ) {
385- return this . parseAgentMessage ( rawData . data ) ;
384+ // Handle JSON string input - parse it first
385+ if ( typeof rawData === 'string' && rawData . startsWith ( '{' ) ) {
386+ try {
387+ rawData = JSON . parse ( rawData ) ;
388+ } catch ( e ) {
389+ console . error ( 'Failed to parse JSON string:' , e ) ;
390+ // Fall through to handle as regular string
391+ }
392+ }
393+
394+ // Unwrap wrapper - handle object format
395+ if ( rawData && typeof rawData === 'object' && rawData . type === WebsocketMessageType . AGENT_MESSAGE ) {
396+ if ( typeof rawData . data === 'object' && rawData . data . agent_name ) {
397+ // New format: { type: 'agent_message', data: { agent_name: '...', timestamp: 123, content: '...' } }
398+ const data = rawData . data ;
399+ const content = data . content || '' ;
400+ const timestamp = typeof data . timestamp === 'number' ? data . timestamp : null ;
401+
402+ // Parse the content for steps and next_steps (reuse existing logic)
403+ const { steps, next_steps } = this . parseContentForStepsAndNextSteps ( content ) ;
404+
405+ return {
406+ agent : data . agent_name || 'UnknownAgent' ,
407+ agent_type : AgentMessageType . AI_AGENT ,
408+ timestamp,
409+ steps,
410+ next_steps,
411+ content,
412+ raw_data : rawData
413+ } ;
414+ } else if ( typeof rawData . data === 'string' ) {
415+ // Old format: { type: 'agent_message', data: "AgentMessage(...)" }
416+ return this . parseAgentMessage ( rawData . data ) ;
417+ }
386418 }
387419
420+ // Handle direct object format
421+ if ( rawData && typeof rawData === 'object' && rawData . agent_name ) {
422+ const content = rawData . content || '' ;
423+ const timestamp = typeof rawData . timestamp === 'number' ? rawData . timestamp : null ;
424+
425+ // Parse the content for steps and next_steps
426+ const { steps, next_steps } = this . parseContentForStepsAndNextSteps ( content ) ;
427+
428+ return {
429+ agent : rawData . agent_name || 'UnknownAgent' ,
430+ agent_type : AgentMessageType . AI_AGENT ,
431+ timestamp,
432+ steps,
433+ next_steps,
434+ content,
435+ raw_data : rawData
436+ } ;
437+ }
438+
439+ // Handle old string format: "AgentMessage(...)"
388440 if ( typeof rawData !== 'string' ) return null ;
389441 if ( ! rawData . startsWith ( 'AgentMessage(' ) ) return null ;
390442
@@ -409,70 +461,15 @@ export class PlanDataService {
409461 . replace ( / \\ " / g, '"' )
410462 . replace ( / \\ \\ / g, '\\' ) ;
411463
412- // Parse sections of the form "##### Title Completed"
413- // Each block ends at --- line or next "##### " or end.
414- const lines = content . split ( '\n' ) ;
415- const steps : Array < { title : string ; fields : Record < string , string > ; summary ?: string ; raw_block : string ; } > = [ ] ;
416- let i = 0 ;
417- while ( i < lines . length ) {
418- const headingMatch = lines [ i ] . match ( / ^ # # # # # \s + ( .+ ?) \s + C o m p l e t e d \s * $ / i) ;
419- if ( headingMatch ) {
420- const title = headingMatch [ 1 ] . trim ( ) ;
421- const blockLines : string [ ] = [ ] ;
422- i ++ ;
423- while ( i < lines . length && ! / ^ - - - \s * $ / . test ( lines [ i ] ) && ! / ^ # # # # # \s + / . test ( lines [ i ] ) ) {
424- blockLines . push ( lines [ i ] ) ;
425- i ++ ;
426- }
427- // Skip separator line if present
428- if ( i < lines . length && / ^ - - - \s * $ / . test ( lines [ i ] ) ) i ++ ;
429-
430- const fields : Record < string , string > = { } ;
431- let summary : string | undefined ;
432- for ( const bl of blockLines ) {
433- const fieldMatch = bl . match ( / ^ \* \* ( .+ ?) \* \* : \s * ( .* ) $ / ) ;
434- if ( fieldMatch ) {
435- const fieldName = fieldMatch [ 1 ] . trim ( ) . replace ( / : $ / , '' ) ;
436- const value = fieldMatch [ 2 ] . trim ( ) . replace ( / \\ s + $ / , '' ) ;
437- if ( fieldName ) fields [ fieldName ] = value ;
438- } else {
439- const summaryMatch = bl . match ( / ^ A G E N T S U M M A R Y : \s * ( .+ ) $ / i) ;
440- if ( summaryMatch ) {
441- summary = summaryMatch [ 1 ] . trim ( ) ;
442- }
443- }
444- }
445-
446- steps . push ( {
447- title,
448- fields,
449- summary,
450- raw_block : blockLines . join ( '\n' ) . trim ( )
451- } ) ;
452- } else {
453- i ++ ;
454- }
455- }
456-
457- // Next Steps section
458- const nextSteps : string [ ] = [ ] ;
459- const nextIdx = lines . findIndex ( l => / ^ N e x t S t e p s : / . test ( l . trim ( ) ) ) ;
460- if ( nextIdx !== - 1 ) {
461- for ( let j = nextIdx + 1 ; j < lines . length ; j ++ ) {
462- const l = lines [ j ] . trim ( ) ;
463- if ( ! l ) continue ;
464- if ( / ^ [ - * ] \s + / . test ( l ) ) {
465- nextSteps . push ( l . replace ( / ^ [ - * ] \s + / , '' ) . trim ( ) ) ;
466- }
467- }
468- }
464+ // Parse the content for steps and next_steps
465+ const { steps, next_steps } = this . parseContentForStepsAndNextSteps ( content ) ;
469466
470467 return {
471468 agent,
472469 agent_type : AgentMessageType . AI_AGENT ,
473470 timestamp,
474471 steps,
475- next_steps : nextSteps ,
472+ next_steps,
476473 content,
477474 raw_data : rawData
478475 } ;
@@ -481,12 +478,86 @@ export class PlanDataService {
481478 return null ;
482479 }
483480 }
484- // ...inside export class PlanDataService { (place near other parsers)
481+
482+ /**
483+ * Helper method to parse content for steps and next_steps
484+ * Extracted to avoid code duplication
485+ */
486+ private static parseContentForStepsAndNextSteps ( content : string ) : {
487+ steps : Array < {
488+ title : string ;
489+ fields : Record < string , string > ;
490+ summary ?: string ;
491+ raw_block : string ;
492+ } > ;
493+ next_steps : string [ ] ;
494+ } {
495+ // Parse sections of the form "##### Title Completed"
496+ // Each block ends at --- line or next "##### " or end.
497+ const lines = content . split ( '\n' ) ;
498+ const steps : Array < { title : string ; fields : Record < string , string > ; summary ?: string ; raw_block : string ; } > = [ ] ;
499+ let i = 0 ;
500+ while ( i < lines . length ) {
501+ const headingMatch = lines [ i ] . match ( / ^ # # # # # \s + ( .+ ?) \s + C o m p l e t e d \s * $ / i) ;
502+ if ( headingMatch ) {
503+ const title = headingMatch [ 1 ] . trim ( ) ;
504+ const blockLines : string [ ] = [ ] ;
505+ i ++ ;
506+ while ( i < lines . length && ! / ^ - - - \s * $ / . test ( lines [ i ] ) && ! / ^ # # # # # \s + / . test ( lines [ i ] ) ) {
507+ blockLines . push ( lines [ i ] ) ;
508+ i ++ ;
509+ }
510+ // Skip separator line if present
511+ if ( i < lines . length && / ^ - - - \s * $ / . test ( lines [ i ] ) ) i ++ ;
512+
513+ const fields : Record < string , string > = { } ;
514+ let summary : string | undefined ;
515+ for ( const bl of blockLines ) {
516+ const fieldMatch = bl . match ( / ^ \* \* ( .+ ?) \* \* : \s * ( .* ) $ / ) ;
517+ if ( fieldMatch ) {
518+ const fieldName = fieldMatch [ 1 ] . trim ( ) . replace ( / : $ / , '' ) ;
519+ const value = fieldMatch [ 2 ] . trim ( ) . replace ( / \\ s + $ / , '' ) ;
520+ if ( fieldName ) fields [ fieldName ] = value ;
521+ } else {
522+ const summaryMatch = bl . match ( / ^ A G E N T S U M M A R Y : \s * ( .+ ) $ / i) ;
523+ if ( summaryMatch ) {
524+ summary = summaryMatch [ 1 ] . trim ( ) ;
525+ }
526+ }
527+ }
528+
529+ steps . push ( {
530+ title,
531+ fields,
532+ summary,
533+ raw_block : blockLines . join ( '\n' ) . trim ( )
534+ } ) ;
535+ } else {
536+ i ++ ;
537+ }
538+ }
539+
540+ // Next Steps section
541+ const next_steps : string [ ] = [ ] ;
542+ const nextIdx = lines . findIndex ( l => / ^ N e x t S t e p s : / . test ( l . trim ( ) ) ) ;
543+ if ( nextIdx !== - 1 ) {
544+ for ( let j = nextIdx + 1 ; j < lines . length ; j ++ ) {
545+ const l = lines [ j ] . trim ( ) ;
546+ if ( ! l ) continue ;
547+ if ( / ^ [ - * ] \s + / . test ( l ) ) {
548+ next_steps . push ( l . replace ( / ^ [ - * ] \s + / , '' ) . trim ( ) ) ;
549+ }
550+ }
551+ }
552+
553+ return { steps, next_steps } ;
554+ }
485555
486556 /**
487557 * Parse streaming agent message fragments.
488558 * Supports:
489559 * - { type: 'agent_message_streaming', data: "AgentMessageStreaming(agent_name='X', content='partial', is_final=False)" }
560+ * - { type: 'agent_message_streaming', data: { agent_name: 'X', content: 'partial', is_final: true } }
490561 * - "AgentMessageStreaming(agent_name='X', content='partial', is_final=False)"
491562 */
492563 static parseAgentMessageStreaming ( rawData : any ) : {
@@ -496,11 +567,44 @@ export class PlanDataService {
496567 raw_data : any ;
497568 } | null {
498569 try {
499- // Unwrap wrapper
500- if ( rawData && typeof rawData === 'object' && rawData . type === 'agent_message_streaming' && typeof rawData . data === 'string' ) {
501- return this . parseAgentMessageStreaming ( rawData . data ) ;
570+ // Handle JSON string input - parse it first
571+ if ( typeof rawData === 'string' && rawData . startsWith ( '{' ) ) {
572+ try {
573+ rawData = JSON . parse ( rawData ) ;
574+ } catch ( e ) {
575+ console . error ( 'Failed to parse JSON string:' , e ) ;
576+ // Fall through to handle as regular string
577+ }
578+ }
579+
580+ // Unwrap wrapper - handle object format
581+ if ( rawData && typeof rawData === 'object' && rawData . type === 'agent_message_streaming' ) {
582+ if ( typeof rawData . data === 'object' && rawData . data . agent_name ) {
583+ // New format: { type: 'agent_message_streaming', data: { agent_name: '...', content: '...', is_final: true } }
584+ const data = rawData . data ;
585+ return {
586+ agent : data . agent_name || 'UnknownAgent' ,
587+ content : data . content || '' ,
588+ is_final : Boolean ( data . is_final ) ,
589+ raw_data : rawData
590+ } ;
591+ } else if ( typeof rawData . data === 'string' ) {
592+ // Old format: { type: 'agent_message_streaming', data: "AgentMessageStreaming(...)" }
593+ return this . parseAgentMessageStreaming ( rawData . data ) ;
594+ }
595+ }
596+
597+ // Handle direct object format
598+ if ( rawData && typeof rawData === 'object' && rawData . agent_name ) {
599+ return {
600+ agent : rawData . agent_name || 'UnknownAgent' ,
601+ content : rawData . content || '' ,
602+ is_final : Boolean ( rawData . is_final ) ,
603+ raw_data : rawData
604+ } ;
502605 }
503606
607+ // Handle old string format: "AgentMessageStreaming(...)"
504608 if ( typeof rawData !== 'string' ) return null ;
505609 if ( ! rawData . startsWith ( 'AgentMessageStreaming(' ) ) return null ;
506610
0 commit comments