@@ -3,6 +3,7 @@ package com.coder.toolbox.util
33import com.coder.toolbox.CoderToolboxContext
44import com.coder.toolbox.cli.CoderCLIManager
55import com.coder.toolbox.feed.IdeFeedManager
6+ import com.coder.toolbox.feed.IdeType
67import com.coder.toolbox.models.WorkspaceAndAgentStatus
78import com.coder.toolbox.sdk.CoderRestClient
89import com.coder.toolbox.sdk.v2.models.Workspace
@@ -27,7 +28,7 @@ private const val CAN_T_HANDLE_URI_TITLE = "Can't handle URI"
2728@Suppress(" UnstableApiUsage" )
2829open class CoderProtocolHandler (
2930private val context : CoderToolboxContext ,
30- ideFeedManager : IdeFeedManager ,
31+ private val ideFeedManager : IdeFeedManager ,
3132) {
3233private val settings= context.settingsStore.readOnly()
3334
@@ -236,72 +237,136 @@ open class CoderProtocolHandler(
236237private fun launchIde (
237238environmentId : String ,
238239productCode : String ,
239- buildNumber : String ,
240+ buildNumberHint : String ,
240241projectFolder : String?
241242 ) {
242243 context.cs.launch(CoroutineName (" Launch Remote IDE" )) {
243- val selectedIde= selectAndInstallRemoteIde(productCode, buildNumber, environmentId)? : return @launch
244- context.logger.info(" $productCode -$buildNumber is already on$environmentId . Going to launch JBClient" )
244+ val selectedIde= selectAndInstallRemoteIde(productCode, buildNumberHint, environmentId)? : return @launch
245+ context.logger.info(" Selected IDE$selectedIde for$productCode with hint$buildNumberHint " )
246+
247+ // Ensure JBClient is prepared (installed/downloaded locally)
245248 installJBClient(selectedIde, environmentId).join()
249+
250+ // Launch
246251 launchJBClient(selectedIde, environmentId, projectFolder)
247252 }
248253 }
249254
250255private suspend fun selectAndInstallRemoteIde (
251256productCode : String ,
252- buildNumber : String ,
257+ buildNumberHint : String ,
253258environmentId : String
254259 ):String? {
255- val installedIdes= context.remoteIdeOrchestrator.getInstalledRemoteTools(environmentId, productCode)
260+ val selectedIdeVersion= resolveIdeIdentifier(environmentId, productCode, buildNumberHint)? : return null
261+ val installedIdeVersions= context.remoteIdeOrchestrator.getInstalledRemoteTools(environmentId, productCode)
262+
263+ if (installedIdeVersions.contains(selectedIdeVersion)) {
264+ context.logger.info(" $selectedIdeVersion is already installed on$environmentId " )
265+ return selectedIdeVersion
266+ }
267+
268+ val selectedIde= " $productCode -$selectedIdeVersion "
269+ context.logger.info(" Installing$selectedIde on$environmentId ..." )
270+ context.remoteIdeOrchestrator.installRemoteTool(environmentId, selectedIde)
256271
257- var selectedIde= " $productCode -$buildNumber "
258- if (installedIdes.firstOrNull { it.contains(buildNumber) }!= null ) {
259- context.logger.info(" $selectedIde is already installed on$environmentId " )
272+ if (context.remoteIdeOrchestrator.waitForIdeToBeInstalled(environmentId, selectedIde)) {
273+ context.logger.info(" Successfully installed$selectedIdeVersion on$environmentId ." )
260274return selectedIde
275+ }else {
276+ context.ui.showSnackbar(
277+ UUID .randomUUID().toString(),
278+ context.i18n.pnotr(" $selectedIde could not be installed" ),
279+ context.i18n.pnotr(" $selectedIde could not be installed on time. Check the logs for more details" ),
280+ context.i18n.ptrl(" OK" )
281+ )
282+ return null
261283 }
284+ }
262285
263- selectedIde= resolveAvailableIde(environmentId, productCode, buildNumber)? : return null
286+ /* *
287+ * Resolves the full IDE identifier (e.g., "RR-241.14494.240") based on the build hint.
288+ * Supports: latest_eap, latest_release, latest_installed, or specific build number.
289+ */
290+ internal suspend fun resolveIdeIdentifier (
291+ environmentId : String ,
292+ productCode : String ,
293+ buildNumberHint : String
294+ ):String? {
295+ val availableBuilds= context.remoteIdeOrchestrator.getAvailableRemoteTools(environmentId, productCode)
296+
297+ when (buildNumberHint) {
298+ " latest_eap" -> {
299+ // Use IdeFeedManager to find best EAP match from available builds
300+ val bestEap= ideFeedManager.findBestMatch(
301+ productCode,
302+ IdeType .EAP ,
303+ availableBuilds
304+ )
264305
265- // needed otherwise TBX will install it again
266- if (! installedIdes.contains(selectedIde)) {
267- context.logger.info(" Installing$selectedIde on$environmentId ..." )
268- context.remoteIdeOrchestrator.installRemoteTool(environmentId, selectedIde)
306+ return if (bestEap!= null ) {
307+ bestEap.build
308+ }else {
309+ // Fallback to latest available if valid
310+ if (availableBuilds.isEmpty()) {
311+ context.logAndShowError(
312+ CAN_T_HANDLE_URI_TITLE ,
313+ " $productCode is not available on$environmentId "
314+ )
315+ return null
316+ }
317+ val fallback= availableBuilds.maxBy { it }
318+ context.logger.info(" No EAP found for$productCode , falling back to latest available:$fallback " )
319+ fallback
320+ }
321+ }
269322
270- if (context.remoteIdeOrchestrator.waitForIdeToBeInstalled(environmentId, selectedIde)) {
271- context.logger.info(" Successfully installed$selectedIde on$environmentId ..." )
272- return selectedIde
273- }else {
274- context.ui.showSnackbar(
275- UUID .randomUUID().toString(),
276- context.i18n.pnotr(" $selectedIde could not be installed" ),
277- context.i18n.pnotr(" $selectedIde could not be installed on time. Check the logs for more details" ),
278- context.i18n.ptrl(" OK" )
323+ " latest_release" -> {
324+ val bestRelease= ideFeedManager.findBestMatch(
325+ productCode,
326+ IdeType .RELEASE ,
327+ availableBuilds
279328 )
280- return null
329+
330+ return if (bestRelease!= null ) {
331+ bestRelease.build
332+ }else {
333+ // Fallback to latest available if valid
334+ if (availableBuilds.isEmpty()) {
335+ context.logAndShowError(
336+ CAN_T_HANDLE_URI_TITLE ,
337+ " $productCode is not available on$environmentId "
338+ )
339+ return null
340+ }
341+ val fallback= availableBuilds.maxBy { it }
342+ context.logger.info(" No Release found for$productCode , falling back to latest available:$fallback " )
343+ fallback
344+ }
281345 }
282- }else {
283- context.logger.info(" $selectedIde is already present on$environmentId ..." )
284- return selectedIde
285- }
286- }
287346
288- private suspend fun resolveAvailableIde (environmentId : String ,productCode : String ,buildNumber : String ):String? {
289- val availableVersions= context
290- .remoteIdeOrchestrator
291- .getAvailableRemoteTools(environmentId, productCode)
347+ " latest_installed" -> {
348+ val installed= context.remoteIdeOrchestrator.getInstalledRemoteTools(environmentId, productCode)
349+ if (installed.isNotEmpty()) {
350+ return installed.maxBy { it }
351+ }
352+ // Fallback to latest available if valid
353+ if (availableBuilds.isEmpty()) {
354+ context.logAndShowError(CAN_T_HANDLE_URI_TITLE ," $productCode is not available on$environmentId " )
355+ return null
356+ }
357+ val fallback= availableBuilds.maxBy { it }
358+ context.logger.info(" No installed IDE found, falling back to latest available:$fallback " )
359+ return fallback
360+ }
292361
293- if (availableVersions.isEmpty()) {
294- context.logAndShowError( CAN_T_HANDLE_URI_TITLE , " $productCode is not available on $environmentId " )
295- return null
296- }
362+ else -> {
363+ // Specific build number
364+ // Check if exact match exists in available or installed (implicitly handled by install check later)
365+ // Often the input buildNumber might be just "241" or "241.1234", but full build version is in the form of 241.1234.234"
297366
298- val buildNumberIsNotAvailable= availableVersions.firstOrNull { it.contains(buildNumber) }== null
299- if (buildNumberIsNotAvailable) {
300- val selectedIde= availableVersions.maxOf { it }
301- context.logger.info(" $productCode -$buildNumber is not available, we've selected the latest$selectedIde " )
302- return selectedIde
367+ return availableBuilds.filter { it.contains(buildNumberHint) }.maxByOrNull { it }
368+ }
303369 }
304- return " $productCode -$buildNumber "
305370 }
306371
307372private fun installJBClient (selectedIde : String ,environmentId : String ):Job =
@@ -340,7 +405,9 @@ open class CoderProtocolHandler(
340405 withTimeout(waitTime.toJavaDuration()) {
341406while (! isInstalled) {
342407 delay(5 .seconds)
343- isInstalled= getInstalledRemoteTools(environmentId, ideHint).isNotEmpty()
408+ val installed= getInstalledRemoteTools(environmentId, ideHint)// Hint matching
409+ // Check if *specific* IDE is installed now
410+ isInstalled= installed.contains(ideHint)|| installed.any { it.contains(ideHint) }
344411 }
345412 }
346413return true