@@ -432,4 +432,25 @@ describe('ACTIVITY events (F5 subagent activities)', () => {
432432 // merge: status updated, text preserved
433433 expect ( store . activities ( ) . get ( 'tc-1' ) ?. content ( ) ) . toEqual ( { status : 'complete' , text : 'hello' } ) ;
434434 } ) ;
435+
436+ it ( 'ACTIVITY_DELTA with a malformed patch (non-existent path) does not throw and leaves content unchanged' , ( ) => {
437+ // Regression guard: an out-of-order ACTIVITY_DELTA (e.g. replace /messages/5/content
438+ // when there are 0 messages) must be dropped — not thrown — so the stream stays usable.
439+ const store = makeStore ( ) ;
440+ reduceEvent ( { type : 'ACTIVITY_SNAPSHOT' , messageId : 'tc-1' , activityType : 'subagent' ,
441+ content : { status : 'running' , text : 'prior' } } as any , store ) ;
442+ // Send a patch that targets a non-existent array index — applyPatch throws without the guard.
443+ expect ( ( ) =>
444+ reduceEvent ( { type : 'ACTIVITY_DELTA' , messageId : 'tc-1' , activityType : 'subagent' ,
445+ patch : [ { op : 'replace' , path : '/messages/5/content' , value : 'x' } ] } as any , store ) ,
446+ ) . not . toThrow ( ) ;
447+ // Prior content must be preserved unchanged.
448+ const content = store . activities ( ) . get ( 'tc-1' ) ?. content ( ) ;
449+ expect ( content ?. [ 'text' ] ) . toBe ( 'prior' ) ;
450+ expect ( content ?. [ 'status' ] ) . toBe ( 'running' ) ;
451+ // Subsequent valid patches must still apply (store remains usable).
452+ reduceEvent ( { type : 'ACTIVITY_DELTA' , messageId : 'tc-1' , activityType : 'subagent' ,
453+ patch : [ { op : 'replace' , path : '/text' , value : 'updated' } ] } as any , store ) ;
454+ expect ( store . activities ( ) . get ( 'tc-1' ) ?. content ( ) [ 'text' ] ) . toBe ( 'updated' ) ;
455+ } ) ;
435456} ) ;
0 commit comments