@@ -14,6 +14,8 @@ const {
1414 mockFetch,
1515 mockHandleExecutionCancelledConsole,
1616 mockHandleExecutionErrorConsole,
17+ mockPersistenceExecutionEnded,
18+ mockPersistenceExecutionStarted,
1719 mockRequestJson,
1820 mockResolveStartCandidates,
1921 mockSelectBestTrigger,
@@ -89,6 +91,8 @@ const {
8991 mockFetch : vi . fn ( ) ,
9092 mockHandleExecutionCancelledConsole : vi . fn ( ) ,
9193 mockHandleExecutionErrorConsole : vi . fn ( ) ,
94+ mockPersistenceExecutionEnded : vi . fn ( ) ,
95+ mockPersistenceExecutionStarted : vi . fn ( ( ) => ( { } ) ) ,
9296 mockRequestJson : vi . fn ( ) ,
9397 mockResolveStartCandidates : vi . fn ( ) ,
9498 mockSelectBestTrigger : vi . fn ( ) ,
@@ -237,8 +241,8 @@ vi.mock('@/stores/execution', () => ({
237241vi . mock ( '@/stores/terminal' , ( ) => ( {
238242 clearExecutionPointer : vi . fn ( ) ,
239243 consolePersistence : {
240- executionStarted : vi . fn ( ) ,
241- executionEnded : vi . fn ( ) ,
244+ executionStarted : mockPersistenceExecutionStarted ,
245+ executionEnded : mockPersistenceExecutionEnded ,
242246 persist : vi . fn ( ) ,
243247 } ,
244248 loadExecutionPointer : vi . fn ( ) ,
@@ -402,6 +406,9 @@ describe('useWorkflowExecution cancellation', () => {
402406describe ( 'useWorkflowExecution attachment uploads' , ( ) => {
403407 beforeEach ( ( ) => {
404408 vi . clearAllMocks ( )
409+ executionStoreState . getWorkflowExecution . mockReturnValue (
410+ executionStoreState . workflowExecutions . get ( 'workflow-1' ) !
411+ )
405412 executionStoreState . getCurrentExecutionId . mockReturnValue ( null )
406413 mockResolveStartCandidates . mockReturnValue ( [ ] )
407414 mockSelectBestTrigger . mockReturnValue ( [ ] )
@@ -538,6 +545,46 @@ describe('useWorkflowExecution attachment uploads', () => {
538545 unmount ( )
539546 } )
540547
548+ it ( 'does not let an overlapping run without lifecycle ownership end the active run' , async ( ) => {
549+ const persistenceExecution = { }
550+ let resolveActiveRun : ( ( ) => void ) | undefined
551+ mockPersistenceExecutionStarted . mockReturnValueOnce ( persistenceExecution )
552+ mockExecute . mockImplementationOnce (
553+ ( ) =>
554+ new Promise < void > ( ( resolve ) => {
555+ resolveActiveRun = resolve
556+ } )
557+ )
558+ const { result, unmount } = renderWorkflowExecutionHook ( )
559+
560+ let activeRun : unknown
561+ await act ( async ( ) => {
562+ activeRun = await result ( ) . handleRunWorkflow ( { input : 'active run' } )
563+ } )
564+
565+ executionStoreState . getWorkflowExecution . mockReturnValue ( {
566+ ...executionStoreState . getWorkflowExecution ( ) ,
567+ isExecuting : true ,
568+ } )
569+
570+ await act ( async ( ) => {
571+ await result ( ) . handleRunWorkflow ( )
572+ } )
573+
574+ expect ( mockPersistenceExecutionStarted ) . toHaveBeenCalledTimes ( 1 )
575+ expect ( mockPersistenceExecutionEnded ) . not . toHaveBeenCalled ( )
576+
577+ await act ( async ( ) => {
578+ resolveActiveRun ?.( )
579+ await drainStream ( activeRun )
580+ } )
581+
582+ expect ( mockPersistenceExecutionEnded ) . toHaveBeenCalledOnce ( )
583+ expect ( mockPersistenceExecutionEnded ) . toHaveBeenCalledWith ( persistenceExecution )
584+
585+ unmount ( )
586+ } )
587+
541588 it ( 'uses only projected live thinking without changing normal settle behavior' , async ( ) => {
542589 mockExecute . mockImplementationOnce ( async ( options ) => {
543590 options . onExecutionId ?.( 'execution-1' )
0 commit comments