@@ -154,208 +154,201 @@ export class PlanDataService {
154154
155155 static parsePlanApprovalRequest ( rawData : any ) : MPlanData | null {
156156 try {
157- console . log ( '🔍 Parsing plan approval request:' , rawData , 'Type:' , typeof rawData ) ;
158-
159- // Already parsed object passthrough
160- if ( rawData && typeof rawData === 'object' && rawData . type === WebsocketMessageType . PLAN_APPROVAL_REQUEST ) {
161- return rawData . parsedData || null ;
162- }
163-
164- // Wrapper form: { type: 'plan_approval_request', data: 'PlanApprovalRequest(plan=MPlan(...), ...)' }
165- if (
166- rawData &&
167- typeof rawData === 'object' &&
168- rawData . type === 'plan_approval_request' &&
169- typeof rawData . data === 'string'
170- ) {
171- // Recurse using the contained string
172- return this . parsePlanApprovalRequest ( rawData . data ) ;
173- }
174-
175- // Structured v3 style: { plan: { id, steps, user_request, ... }, context?: {...} }
176- if ( rawData && typeof rawData === 'object' && rawData . plan && typeof rawData . plan === 'object' ) {
177- const mplan = rawData . plan ;
178-
179- // Extract user_request text
180- let userRequestText = 'Plan approval required' ;
181- if ( mplan . user_request ) {
182- if ( typeof mplan . user_request === 'string' ) {
183- userRequestText = mplan . user_request ;
184- } else if ( Array . isArray ( mplan . user_request . items ) ) {
185- const textContent = mplan . user_request . items . find ( ( item : any ) => item . text ) ;
186- if ( textContent ?. text ) {
187- userRequestText = textContent . text . replace ( / \u200b / g, '' ) . trim ( ) ;
188- }
189- } else if ( mplan . user_request . content ) {
190- userRequestText = mplan . user_request . content ;
191- }
192- }
193-
194- const steps = ( mplan . steps || [ ] )
195- . map ( ( step : any , index : number ) => {
157+ if ( ! rawData ) return null ;
158+
159+ // Normalize to the PlanApprovalRequest(...) string that contains MPlan(...)
160+ let source : string | null = null ;
161+
162+ if ( typeof rawData === 'object' ) {
163+ if ( typeof rawData . data === 'string' && / P l a n A p p r o v a l R e q u e s t \( p l a n = M P l a n \( / . test ( rawData . data ) ) {
164+ source = rawData . data ;
165+ } else if ( rawData . plan && typeof rawData . plan === 'object' ) {
166+ // Already structured style
167+ const mplan = rawData . plan ;
168+ const userRequestText =
169+ typeof mplan . user_request === 'string'
170+ ? mplan . user_request
171+ : ( Array . isArray ( mplan . user_request ?. items )
172+ ? ( mplan . user_request . items . find ( ( i : any ) => i . text ) ?. text || '' )
173+ : ( mplan . user_request ?. content || '' )
174+ ) . replace ?.( / \u200b / g, '' ) . trim ( ) || 'Plan approval required' ;
175+
176+ const steps = ( mplan . steps || [ ] ) . map ( ( step : any , i : number ) => {
196177 const action = step . action || '' ;
197178 const cleanAction = action
198179 . replace ( / \* \* / g, '' )
199180 . replace ( / ^ C e r t a i n l y ! \s * / i, '' )
200181 . replace ( / ^ G i v e n t h e t e a m c o m p o s i t i o n a n d t h e a v a i l a b l e f a c t s , ? \s * / i, '' )
201- . replace ( / ^ h e r e i s a (?: c o n c i s e ) ? p l a n t o a d d r e s s t h e o r i g i n a l r e q u e s t [ ^ . ] * \. \s * / i, '' )
182+ . replace ( / ^ h e r e i s a (?: c o n c i s e ) ? p l a n [ ^ . ] * \. \s * / i, '' )
202183 . replace ( / ^ (?: h e r e i s | t h i s i s ) a (?: c o n c i s e ) ? (?: p l a n | a p p r o a c h | s t r a t e g y ) [ ^ . ] * [ . : ] \s * / i, '' )
203184 . replace ( / ^ \* \* ( [ ^ * ] + ) \* \* : ? \s * / g, '$1: ' )
204185 . replace ( / ^ [ - • ] \s * / , '' )
205186 . replace ( / \s + / g, ' ' )
206187 . trim ( ) ;
207-
208188 return {
209- id : index + 1 ,
189+ id : i + 1 ,
210190 action,
211191 cleanAction,
212192 agent : step . agent || step . _agent || 'System'
213193 } ;
214- } )
215- . filter ( ( s : any ) =>
216- s . cleanAction . length > 3 &&
217- ! / ^ (?: i n v o l v e m e n t | c e r t a i n l y | g i v e n | h e r e i s ) / i. test ( s . cleanAction )
218- ) ;
219-
220- return {
221- id : mplan . id || mplan . plan_id || 'unknown' ,
222- status : ( mplan . overall_status || rawData . status || 'PENDING_APPROVAL' ) ,
223- user_request : userRequestText ,
224- team : Array . isArray ( mplan . team ) ? mplan . team : [ ] ,
225- facts : mplan . facts || '' ,
226- steps,
227- context : {
228- task : userRequestText ,
229- participant_descriptions : rawData . context ?. participant_descriptions || { }
230- } ,
231- user_id : mplan . user_id ,
232- team_id : mplan . team_id ,
233- plan_id : mplan . plan_id ,
234- overall_status : mplan . overall_status ,
235- raw_data : rawData
236- } ;
194+ } ) . filter ( ( s : any ) => s . cleanAction . length > 3 && ! / ^ (?: i n v o l v e m e n t | c e r t a i n l y | g i v e n | h e r e i s ) / i. test ( s . cleanAction ) ) ;
195+
196+
197+ const result : MPlanData = {
198+ id : mplan . id || mplan . plan_id || 'unknown' ,
199+ status : ( mplan . overall_status || rawData . status || 'PENDING_APPROVAL' ) . toString ( ) . toUpperCase ( ) ,
200+ user_request : userRequestText ,
201+ team : Array . isArray ( mplan . team ) ? mplan . team : [ ] ,
202+ facts : mplan . facts || '' ,
203+ steps,
204+ context : {
205+ task : userRequestText ,
206+ participant_descriptions : rawData . context ?. participant_descriptions || { }
207+ } ,
208+ user_id : mplan . user_id ,
209+ team_id : mplan . team_id ,
210+ plan_id : mplan . plan_id ,
211+ overall_status : mplan . overall_status ,
212+ raw_data : rawData
213+ } ;
214+ return result ;
215+ }
216+ } else if ( typeof rawData === 'string' ) {
217+ if ( / P l a n A p p r o v a l R e q u e s t \( p l a n = M P l a n \( / . test ( rawData ) ) {
218+ source = rawData ;
219+ } else if ( / ^ M P l a n \( / . test ( rawData ) ) {
220+ source = `PlanApprovalRequest(plan=${ rawData } )` ;
221+ }
237222 }
238223
239- // String representation parsing (PlanApprovalRequest(...MPlan(...)) or raw repr)
240- if ( typeof rawData === 'string' ) {
241- const source = rawData ;
242-
243- // Extract MPlan(...) block (optional)
244- // Not strictly needed but could be used for scoping later.
245- // const mplanBlock = source.match(/MPlan\(([\s\S]*?)\)\)/);
246-
247- // User request (first text='...')
248- let user_request =
249- source . match ( / t e x t = [ ' " ] ( [ ^ ' " ] + ?) [ ' " ] / )
250- ?. [ 1 ]
251- ?. replace ( / \\ u 2 0 0 b / g, '' )
252- . trim ( ) || 'Plan approval required' ;
253-
254- const id = source . match ( / M P l a n \( i d = [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / ) ?. [ 1 ] ||
255- source . match ( / i d = [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / ) ?. [ 1 ] ||
256- 'unknown' ;
257-
258- let status =
259- source . match ( / o v e r a l l _ s t a t u s = < P l a n S t a t u s \. ( [ a - z A - Z _ ] + ) : / ) ?. [ 1 ] ||
260- source . match ( / o v e r a l l _ s t a t u s = [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / ) ?. [ 1 ] ||
261- 'PENDING_APPROVAL' ;
262- if ( status ) {
263- status = status . toUpperCase ( ) ;
264- }
224+ if ( ! source ) return null ;
265225
266- const teamRaw =
267- source . match ( / t e a m = \[ ( [ ^ \] ] * ) \] / ) ?. [ 1 ] || '' ;
268- const team = teamRaw
269- . split ( ',' )
270- . map ( s => s . trim ( ) . replace ( / [ ' " ] / g, '' ) )
271- . filter ( Boolean ) ;
272-
273- const facts =
274- source
275- . match ( / f a c t s = " ( [ ^ " ] * (?: \\ .[ ^ " ] * ) * ) " / ) ?. [ 1 ]
276- ?. replace ( / \\ n / g, '\n' )
277- . replace ( / \\ " / g, '"' ) || '' ;
278-
279- // Steps: accept single or double quotes: action='...' or action="..."
280- const stepRegex = / M S t e p \( ( [ ^ ) ] * ?) \) / g;
281- const steps : any [ ] = [ ] ;
282- const uniqueActions = new Set < string > ( ) ;
283- let match : RegExpExecArray | null ;
284- let stepIndex = 1 ;
285-
286- while ( ( match = stepRegex . exec ( source ) ) !== null ) {
287- const chunk = match [ 1 ] ;
288- const agent =
289- chunk . match ( / a g e n t = [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / ) ?. [ 1 ] || 'System' ;
290- const actionRaw =
291- chunk . match ( / a c t i o n = [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / ) ?. [ 1 ] || '' ;
292-
293- if ( ! actionRaw ) continue ;
294-
295- let cleanAction = actionRaw
296- . replace ( / \* \* / g, '' )
297- . replace ( / ^ C e r t a i n l y ! \s * / i, '' )
298- . replace ( / ^ G i v e n t h e t e a m c o m p o s i t i o n a n d t h e a v a i l a b l e f a c t s , ? \s * / i, '' )
299- . replace ( / ^ h e r e i s a (?: c o n c i s e ) ? p l a n t o [ ^ . ] * \. \s * / i, '' )
300- . replace ( / ^ \* \* ( [ ^ * ] + ) \* \* : ? \s * / g, '$1: ' )
301- . replace ( / ^ [ - • ] \s * / , '' )
302- . replace ( / \s + / g, ' ' )
303- . trim ( ) ;
304-
305- if (
306- cleanAction . length > 3 &&
307- ! uniqueActions . has ( cleanAction . toLowerCase ( ) ) &&
308- ! / ^ (?: h e r e i s | t h i s i s | g i v e n | c e r t a i n l y | i n v o l v e m e n t ) $ / i. test ( cleanAction )
309- ) {
310- uniqueActions . add ( cleanAction . toLowerCase ( ) ) ;
311- steps . push ( {
312- id : stepIndex ++ ,
313- action : actionRaw ,
314- cleanAction,
315- agent
316- } ) ;
317- }
318- }
226+ // Extract inner MPlan body
227+ const mplanMatch =
228+ source . match ( / p l a n = M P l a n \( ( [ \s \S ] * ?) \) , \s * s t a t u s = / ) ||
229+ source . match ( / p l a n = M P l a n \( ( [ \s \S ] * ?) \) \s * \) / ) ;
230+ const body = mplanMatch ? mplanMatch [ 1 ] : null ;
231+ if ( ! body ) return null ;
319232
320- // participant_descriptions (best-effort)
321- let participant_descriptions : Record < string , string > = { } ;
322- const pdMatch =
323- source . match ( / p a r t i c i p a n t _ d e s c r i p t i o n s [ ' " ] ? \s * : \s * ( { [ ^ } ] * } ) / ) ||
324- source . match ( / ' p a r t i c i p a n t _ d e s c r i p t i o n s ' : \s * ( { [ ^ } ] * } ) / ) ;
325-
326- if ( pdMatch ?. [ 1 ] ) {
327- const transformed = pdMatch [ 1 ]
328- . replace ( / ' / g, '"' )
329- . replace ( / ( [ a - z A - Z 0 - 9 _ ] + ) \s * : / g, '"$1":' ) ;
330- try {
331- participant_descriptions = JSON . parse ( transformed ) ;
332- } catch {
333- participant_descriptions = { } ;
334- }
233+ const pick = ( re : RegExp , upper = false ) : string | undefined => {
234+ const m = body . match ( re ) ;
235+ return m ? ( upper ? m [ 1 ] . toUpperCase ( ) : m [ 1 ] ) : undefined ;
236+ } ;
237+
238+ const id = pick ( / i d = ' ( [ ^ ' ] + ) ' / ) || pick ( / i d = " ( [ ^ " ] + ) " / ) || 'unknown' ;
239+ const user_id = pick ( / u s e r _ i d = ' ( [ ^ ' ] * ) ' / ) || '' ;
240+ const team_id = pick ( / t e a m _ i d = ' ( [ ^ ' ] * ) ' / ) || '' ;
241+ const plan_id = pick ( / p l a n _ i d = ' ( [ ^ ' ] * ) ' / ) || '' ;
242+ let overall_status =
243+ pick ( / o v e r a l l _ s t a t u s = < P l a n S t a t u s \. ( [ a - z A - Z _ ] + ) : / , true ) ||
244+ pick ( / o v e r a l l _ s t a t u s = ' ( [ ^ ' ] + ) ' / , true ) ||
245+ 'PENDING_APPROVAL' ;
246+
247+ const outerStatus =
248+ source . match ( / s t a t u s = ' ( [ ^ ' ] + ) ' / ) ?. [ 1 ] ||
249+ source . match ( / s t a t u s = " ( [ ^ " ] + ) " / ) ?. [ 1 ] ;
250+ const status = ( outerStatus || overall_status || 'PENDING_APPROVAL' ) . toUpperCase ( ) ;
251+
252+ let user_request =
253+ source . match ( / t e x t = ' ( [ ^ ' ] + ) ' / ) ?. [ 1 ] ||
254+ source . match ( / t e x t = " ( [ ^ " ] + ) " / ) ?. [ 1 ] ||
255+ 'Plan approval required' ;
256+ user_request = user_request . replace ( / \\ u 2 0 0 b / g, '' ) . trim ( ) ;
257+
258+ const teamRaw = body . match ( / t e a m = \[ ( [ ^ \] ] * ) \] / ) ?. [ 1 ] || '' ;
259+ const team = teamRaw
260+ . split ( ',' )
261+ . map ( s => s . trim ( ) . replace ( / [ ' " ] / g, '' ) )
262+ . filter ( Boolean ) ;
263+
264+ const facts =
265+ body
266+ . match ( / f a c t s = " ( [ ^ " ] * (?: \\ .[ ^ " ] * ) * ) " / ) ?. [ 1 ]
267+ ?. replace ( / \\ n / g, '\n' )
268+ . replace ( / \\ " / g, '"' ) || '' ;
269+
270+ const steps : MPlanData [ 'steps' ] = [ ] ;
271+ const stepRegex = / M S t e p \( ( [ ^ ) ] * ?) \) / g;
272+ let stepMatch : RegExpExecArray | null ;
273+ let idx = 1 ;
274+ const seen = new Set < string > ( ) ;
275+ while ( ( stepMatch = stepRegex . exec ( body ) ) !== null ) {
276+ const chunk = stepMatch [ 1 ] ;
277+ const agent =
278+ chunk . match ( / a g e n t = ' ( [ ^ ' ] + ) ' / ) ?. [ 1 ] ||
279+ chunk . match ( / a g e n t = " ( [ ^ " ] + ) " / ) ?. [ 1 ] ||
280+ 'System' ;
281+ const actionRaw =
282+ chunk . match ( / a c t i o n = ' ( [ ^ ' ] + ) ' / ) ?. [ 1 ] ||
283+ chunk . match ( / a c t i o n = " ( [ ^ " ] + ) " / ) ?. [ 1 ] ||
284+ '' ;
285+ if ( ! actionRaw ) continue ;
286+
287+ const cleanAction = actionRaw
288+ . replace ( / \* \* / g, '' )
289+ . replace ( / ^ C e r t a i n l y ! \s * / i, '' )
290+ . replace ( / ^ G i v e n t h e t e a m c o m p o s i t i o n a n d t h e a v a i l a b l e f a c t s , ? \s * / i, '' )
291+ . replace ( / ^ h e r e i s a (?: c o n c i s e ) ? p l a n t o [ ^ . ] * \. \s * / i, '' )
292+ . replace ( / ^ \* \* ( [ ^ * ] + ) \* \* : ? \s * / g, '$1: ' )
293+ . replace ( / ^ [ - • ] \s * / , '' )
294+ . replace ( / \s + / g, ' ' )
295+ . trim ( ) ;
296+
297+ const key = cleanAction . toLowerCase ( ) ;
298+ if (
299+ cleanAction . length > 3 &&
300+ ! seen . has ( key ) &&
301+ ! / ^ (?: h e r e i s | t h i s i s | g i v e n | c e r t a i n l y | i n v o l v e m e n t ) $ / i. test ( cleanAction )
302+ ) {
303+ seen . add ( key ) ;
304+ steps . push ( {
305+ id : idx ++ ,
306+ action : actionRaw ,
307+ cleanAction,
308+ agent
309+ } ) ;
335310 }
311+ }
336312
337- return {
338- id ,
339- status ,
340- user_request ,
341- team ,
342- facts ,
343- steps ,
344- context : {
345- task : user_request ,
346- participant_descriptions
347- } ,
348- raw_data : rawData
349- } ;
313+ let participant_descriptions : Record < string , string > = { } ;
314+ const pdMatch =
315+ source . match ( / p a r t i c i p a n t _ d e s c r i p t i o n s [ ' " ] ? \s * : \s * ( { [ ^ } ] * } ) / ) ||
316+ source . match ( / ' p a r t i c i p a n t _ d e s c r i p t i o n s ' : \s * ( { [ ^ } ] * } ) / ) ;
317+ if ( pdMatch ?. [ 1 ] ) {
318+ const jsonish = pdMatch [ 1 ]
319+ . replace ( / ' / g , '"' )
320+ . replace ( / ( [ a - z A - Z 0 - 9 _ ] + ) \s * : / g , '"$1":' ) ;
321+ try {
322+ participant_descriptions = JSON . parse ( jsonish ) ;
323+ } catch {
324+ participant_descriptions = { } ;
325+ }
350326 }
351327
352- return null ;
353- } catch ( error ) {
354- console . error ( 'Error parsing plan approval request:' , error ) ;
328+ const result : MPlanData = {
329+ id,
330+ status,
331+ user_request,
332+ team,
333+ facts,
334+ steps,
335+ context : {
336+ task : user_request ,
337+ participant_descriptions
338+ } ,
339+ user_id,
340+ team_id,
341+ plan_id,
342+ overall_status,
343+ raw_data : rawData
344+ } ;
345+
346+ return result ;
347+ } catch ( e ) {
348+ console . error ( 'parsePlanApprovalRequest failed:' , e ) ;
355349 return null ;
356350 }
357351 }
358- // ...existing code...
359352
360353 /**
361354 * Parse an agent message object or repr string:
0 commit comments