1- import  {  spawn  }  from  "child_process" ; 
21import  {  type  Api  }  from  "coder/site/src/api/api" ; 
32import  { 
43	type  WorkspaceAgentLog , 
54	type  Workspace , 
5+ 	type  WorkspaceAgent , 
66}  from  "coder/site/src/api/typesGenerated" ; 
7+ import  {  spawn  }  from  "node:child_process" ; 
78import  *  as  vscode  from  "vscode" ; 
89
910import  {  type  FeatureSet  }  from  "../featureSet" ; 
@@ -40,35 +41,33 @@ export async function startWorkspaceIfStoppedOrFailed(
4041			createWorkspaceIdentifier ( workspace ) , 
4142		] ; 
4243		if  ( featureSet . buildReason )  { 
43- 			startArgs . push ( ... [ "--reason" ,  "vscode_connection" ] ) ; 
44+ 			startArgs . push ( "--reason" ,  "vscode_connection" ) ; 
4445		} 
4546
4647		// { shell: true } requires one shell-safe command string, otherwise we lose all escaping 
4748		const  cmd  =  `${ escapeCommandArg ( binPath ) } ${ startArgs . join ( " " ) }  ; 
4849		const  startProcess  =  spawn ( cmd ,  {  shell : true  } ) ; 
4950
5051		startProcess . stdout . on ( "data" ,  ( data : Buffer )  =>  { 
51- 			data 
52+ 			const   lines   =   data 
5253				. toString ( ) 
5354				. split ( / \r * \n / ) 
54- 				. forEach ( ( line : string )  =>  { 
55- 					if  ( line  !==  "" )  { 
56- 						writeEmitter . fire ( line . toString ( )  +  "\r\n" ) ; 
57- 					} 
58- 				} ) ; 
55+ 				. filter ( ( line )  =>  line  !==  "" ) ; 
56+ 			for  ( const  line  of  lines )  { 
57+ 				writeEmitter . fire ( line . toString ( )  +  "\r\n" ) ; 
58+ 			} 
5959		} ) ; 
6060
6161		let  capturedStderr  =  "" ; 
6262		startProcess . stderr . on ( "data" ,  ( data : Buffer )  =>  { 
63- 			data 
63+ 			const   lines   =   data 
6464				. toString ( ) 
6565				. split ( / \r * \n / ) 
66- 				. forEach ( ( line : string )  =>  { 
67- 					if  ( line  !==  "" )  { 
68- 						writeEmitter . fire ( line . toString ( )  +  "\r\n" ) ; 
69- 						capturedStderr  +=  line . toString ( )  +  "\n" ; 
70- 					} 
71- 				} ) ; 
66+ 				. filter ( ( line )  =>  line  !==  "" ) ; 
67+ 			for  ( const  line  of  lines )  { 
68+ 				writeEmitter . fire ( line . toString ( )  +  "\r\n" ) ; 
69+ 				capturedStderr  +=  line . toString ( )  +  "\n" ; 
70+ 			} 
7271		} ) ; 
7372
7473		startProcess . on ( "close" ,  ( code : number )  =>  { 
@@ -85,43 +84,6 @@ export async function startWorkspaceIfStoppedOrFailed(
8584	} ) ; 
8685} 
8786
88- /** 
89-  * Wait for the latest build to finish while streaming logs to the emitter. 
90-  * 
91-  * Once completed, fetch the workspace again and return it. 
92-  */ 
93- export  async  function  writeAgentLogs ( 
94- 	client : CoderApi , 
95- 	writeEmitter : vscode . EventEmitter < string > , 
96- 	agentId : string , 
97- ) : Promise < OneWayWebSocket < WorkspaceAgentLog [ ] > >  { 
98- 	// This fetches the initial bunch of logs. 
99- 	const  logs  =  await  client . getWorkspaceAgentLogs ( agentId ) ; 
100- 	logs . forEach ( ( log )  =>  writeEmitter . fire ( log . output  +  "\r\n" ) ) ; 
101- 
102- 	const  socket  =  await  client . watchWorkspaceAgentLogs ( agentId ,  logs ) ; 
103- 
104- 	socket . addEventListener ( "message" ,  ( data )  =>  { 
105- 		if  ( data . parseError )  { 
106- 			writeEmitter . fire ( 
107- 				errToStr ( data . parseError ,  "Failed to parse message" )  +  "\r\n" , 
108- 			) ; 
109- 		}  else  { 
110- 			data . parsedMessage . forEach ( ( message )  => 
111- 				writeEmitter . fire ( message . output  +  "\r\n" ) , 
112- 			) ; 
113- 		} 
114- 	} ) ; 
115- 
116- 	socket . addEventListener ( "error" ,  ( error )  =>  { 
117- 		const  baseUrlRaw  =  client . getAxiosInstance ( ) . defaults . baseURL ; 
118- 		throw  new  Error ( 
119- 			`Failed to watch workspace build on ${ baseUrlRaw } ${ errToStr ( error ,  "no further details" ) }  , 
120- 		) ; 
121- 	} ) ; 
122- 	return  socket ; 
123- } 
124- 
12587/** 
12688 * Wait for the latest build to finish while streaming logs to the emitter. 
12789 * 
@@ -134,7 +96,9 @@ export async function waitForBuild(
13496) : Promise < Workspace >  { 
13597	// This fetches the initial bunch of logs. 
13698	const  logs  =  await  client . getWorkspaceBuildLogs ( workspace . latest_build . id ) ; 
137- 	logs . forEach ( ( log )  =>  writeEmitter . fire ( log . output  +  "\r\n" ) ) ; 
99+ 	for  ( const  log  of  logs )  { 
100+ 		writeEmitter . fire ( log . output  +  "\r\n" ) ; 
101+ 	} 
138102
139103	const  socket  =  await  client . watchBuildLogsByBuildId ( 
140104		workspace . latest_build . id , 
@@ -171,3 +135,55 @@ export async function waitForBuild(
171135	) ; 
172136	return  updatedWorkspace ; 
173137} 
138+ 
139+ /** 
140+  * Streams agent logs to the emitter in real-time. 
141+  * Fetches existing logs and subscribes to new logs via websocket. 
142+  * Returns the websocket and a completion promise that rejects on error. 
143+  */ 
144+ export  async  function  streamAgentLogs ( 
145+ 	client : CoderApi , 
146+ 	writeEmitter : vscode . EventEmitter < string > , 
147+ 	agent : WorkspaceAgent , 
148+ ) : Promise < { 
149+ 	socket : OneWayWebSocket < WorkspaceAgentLog [ ] > ; 
150+ 	completion : Promise < void > ; 
151+ } >  { 
152+ 	// This fetches the initial bunch of logs. 
153+ 	const  logs  =  await  client . getWorkspaceAgentLogs ( agent . id ) ; 
154+ 	for  ( const  log  of  logs )  { 
155+ 		writeEmitter . fire ( log . output  +  "\r\n" ) ; 
156+ 	} 
157+ 
158+ 	const  socket  =  await  client . watchWorkspaceAgentLogs ( agent . id ,  logs ) ; 
159+ 
160+ 	const  completion  =  new  Promise < void > ( ( resolve ,  reject )  =>  { 
161+ 		socket . addEventListener ( "message" ,  ( data )  =>  { 
162+ 			if  ( data . parseError )  { 
163+ 				writeEmitter . fire ( 
164+ 					errToStr ( data . parseError ,  "Failed to parse message" )  +  "\r\n" , 
165+ 				) ; 
166+ 			}  else  { 
167+ 				for  ( const  log  of  data . parsedMessage )  { 
168+ 					writeEmitter . fire ( log . output  +  "\r\n" ) ; 
169+ 				} 
170+ 			} 
171+ 		} ) ; 
172+ 
173+ 		socket . addEventListener ( "error" ,  ( error )  =>  { 
174+ 			const  baseUrlRaw  =  client . getAxiosInstance ( ) . defaults . baseURL ; 
175+ 			writeEmitter . fire ( 
176+ 				`Error watching agent logs on ${ baseUrlRaw } ${ errToStr ( error ,  "no further details" ) }  , 
177+ 			) ; 
178+ 			return  reject ( 
179+ 				new  Error ( 
180+ 					`Failed to watch agent logs on ${ baseUrlRaw } ${ errToStr ( error ,  "no further details" ) }  , 
181+ 				) , 
182+ 			) ; 
183+ 		} ) ; 
184+ 
185+ 		socket . addEventListener ( "close" ,  ( )  =>  resolve ( ) ) ; 
186+ 	} ) ; 
187+ 
188+ 	return  {  socket,  completion } ; 
189+ } 
0 commit comments