@@ -15,10 +15,14 @@ import cc.unitmesh.devins.idea.toolwindow.codereview.IdeaCodeReviewContent
1515import cc.unitmesh.devins.idea.toolwindow.codereview.IdeaCodeReviewViewModel
1616import cc.unitmesh.devins.idea.components.header.IdeaAgentTabsHeader
1717import cc.unitmesh.devins.idea.components.IdeaVerticalResizableSplitPane
18+ import cc.unitmesh.devins.idea.toolwindow.acp.IdeaAcpAgentContent
19+ import cc.unitmesh.devins.idea.toolwindow.acp.IdeaAcpAgentViewModel
1820import cc.unitmesh.devins.idea.toolwindow.knowledge.IdeaKnowledgeContent
1921import cc.unitmesh.devins.idea.toolwindow.knowledge.IdeaKnowledgeViewModel
2022import cc.unitmesh.devins.idea.toolwindow.remote.IdeaRemoteAgentContent
2123import cc.unitmesh.devins.idea.toolwindow.remote.IdeaRemoteAgentViewModel
24+ import cc.unitmesh.devins.idea.toolwindow.remote.IdeaRemoteModeSelector
25+ import cc.unitmesh.devins.idea.toolwindow.remote.RemoteAgentMode
2226import cc.unitmesh.devins.idea.toolwindow.remote.getEffectiveProjectId
2327import cc.unitmesh.devins.idea.toolwindow.webedit.IdeaWebEditContent
2428import cc.unitmesh.devins.idea.toolwindow.webedit.IdeaWebEditViewModel
@@ -130,6 +134,9 @@ fun IdeaAgentApp(
130134 // Remote Agent ViewModel (created lazily when needed)
131135 var remoteAgentViewModel by remember { mutableStateOf<IdeaRemoteAgentViewModel ?>(null ) }
132136
137+ // ACP Agent ViewModel (created lazily when needed)
138+ var acpAgentViewModel by remember { mutableStateOf<IdeaAcpAgentViewModel ?>(null ) }
139+
133140 // WebEdit ViewModel (created lazily when needed)
134141 var webEditViewModel by remember { mutableStateOf<IdeaWebEditViewModel ?>(null ) }
135142
@@ -164,30 +171,28 @@ fun IdeaAgentApp(
164171 serverUrl = " http://localhost:8080"
165172 )
166173 }
174+ if (currentAgentType == AgentType .REMOTE && acpAgentViewModel == null ) {
175+ acpAgentViewModel = IdeaAcpAgentViewModel (project, coroutineScope)
176+ }
167177 if (currentAgentType == AgentType .WEB_EDIT && webEditViewModel == null ) {
168178 webEditViewModel = IdeaWebEditViewModel (project, coroutineScope)
169179 }
170180 }
171181
172- // Dispose ViewModels when leaving their tabs
173- DisposableEffect (currentAgentType) {
182+ // Dispose ViewModels when the tool window is disposed.
183+ // Keeping them alive across tab switches avoids reconnect churn and prevents leaks.
184+ DisposableEffect (Unit ) {
174185 onDispose {
175- if (currentAgentType != AgentType .CODE_REVIEW ) {
176- codeReviewViewModel?.dispose()
177- codeReviewViewModel = null
178- }
179- if (currentAgentType != AgentType .KNOWLEDGE ) {
180- knowledgeViewModel?.dispose()
181- knowledgeViewModel = null
182- }
183- if (currentAgentType != AgentType .REMOTE ) {
184- remoteAgentViewModel?.dispose()
185- remoteAgentViewModel = null
186- }
187- if (currentAgentType != AgentType .WEB_EDIT ) {
188- webEditViewModel?.dispose()
189- webEditViewModel = null
190- }
186+ codeReviewViewModel?.dispose()
187+ knowledgeViewModel?.dispose()
188+ remoteAgentViewModel?.dispose()
189+ acpAgentViewModel?.dispose()
190+ webEditViewModel?.dispose()
191+ codeReviewViewModel = null
192+ knowledgeViewModel = null
193+ remoteAgentViewModel = null
194+ acpAgentViewModel = null
195+ webEditViewModel = null
191196 }
192197 }
193198
@@ -306,41 +311,89 @@ fun IdeaAgentApp(
306311 )
307312 }
308313 AgentType .REMOTE -> {
309- remoteAgentViewModel?.let { remoteVm ->
310- // Use manual state collection for remote agent states
314+ val remoteVm = remoteAgentViewModel
315+ val acpVm = acpAgentViewModel
316+ if (remoteVm != null && acpVm != null ) {
317+ var remoteMode by remember { mutableStateOf(RemoteAgentMode .SERVER ) }
311318 var remoteIsExecuting by remember { mutableStateOf(false ) }
319+ var acpIsExecuting by remember { mutableStateOf(false ) }
312320
313321 IdeaLaunchedEffect (remoteVm, project = project) {
314322 remoteVm.isExecuting.collect { remoteIsExecuting = it }
315323 }
324+ IdeaLaunchedEffect (acpVm, project = project) {
325+ acpVm.isExecuting.collect { acpIsExecuting = it }
326+ }
327+
328+ val isRemoteProcessing = remoteMode == RemoteAgentMode .SERVER && remoteIsExecuting
329+ val isAcpProcessing = remoteMode == RemoteAgentMode .ACP && acpIsExecuting
316330
317331 IdeaVerticalResizableSplitPane (
318332 modifier = Modifier .fillMaxWidth().weight(1f ),
319333 initialSplitRatio = 0.75f ,
320334 minRatio = 0.3f ,
321335 maxRatio = 0.9f ,
322336 top = {
323- IdeaRemoteAgentContent (
324- viewModel = remoteVm,
325- listState = listState,
326- onProjectIdChange = { remoteProjectId = it },
327- onGitUrlChange = { remoteGitUrl = it }
328- )
337+ Column (modifier = Modifier .fillMaxSize()) {
338+ Row (
339+ modifier = Modifier
340+ .fillMaxWidth()
341+ .padding(horizontal = 12 .dp, vertical = 8 .dp),
342+ horizontalArrangement = Arrangement .Start
343+ ) {
344+ IdeaRemoteModeSelector (
345+ mode = remoteMode,
346+ onModeChange = { remoteMode = it }
347+ )
348+ }
349+ Divider (Orientation .Horizontal , modifier = Modifier .fillMaxWidth().height(1 .dp))
350+
351+ when (remoteMode) {
352+ RemoteAgentMode .SERVER -> {
353+ IdeaRemoteAgentContent (
354+ viewModel = remoteVm,
355+ listState = listState,
356+ onProjectIdChange = { remoteProjectId = it },
357+ onGitUrlChange = { remoteGitUrl = it },
358+ modifier = Modifier .fillMaxSize()
359+ )
360+ }
361+ RemoteAgentMode .ACP -> {
362+ IdeaAcpAgentContent (
363+ viewModel = acpVm,
364+ listState = listState,
365+ modifier = Modifier .fillMaxSize()
366+ )
367+ }
368+ }
369+ }
329370 },
330371 bottom = {
331372 IdeaDevInInputArea (
332373 project = project,
333374 parentDisposable = viewModel,
334- isProcessing = remoteIsExecuting ,
375+ isProcessing = isRemoteProcessing || isAcpProcessing ,
335376 onSend = { task ->
336- val effectiveProjectId = getEffectiveProjectId(remoteProjectId, remoteGitUrl)
337- if (effectiveProjectId.isNotBlank()) {
338- remoteVm.executeTask(effectiveProjectId, task, remoteGitUrl)
339- } else {
340- remoteVm.renderer.renderError(" Please provide a project or Git URL" )
377+ when (remoteMode) {
378+ RemoteAgentMode .SERVER -> {
379+ val effectiveProjectId = getEffectiveProjectId(remoteProjectId, remoteGitUrl)
380+ if (effectiveProjectId.isNotBlank()) {
381+ remoteVm.executeTask(effectiveProjectId, task, remoteGitUrl)
382+ } else {
383+ remoteVm.renderer.renderError(" Please provide a project or Git URL" )
384+ }
385+ }
386+ RemoteAgentMode .ACP -> {
387+ acpVm.sendMessage(task)
388+ }
389+ }
390+ },
391+ onAbort = {
392+ when (remoteMode) {
393+ RemoteAgentMode .SERVER -> remoteVm.cancelTask()
394+ RemoteAgentMode .ACP -> acpVm.cancelTask()
341395 }
342396 },
343- onAbort = { remoteVm.cancelTask() },
344397 workspacePath = project.basePath,
345398 totalTokens = null ,
346399 onAtClick = {},
@@ -355,7 +408,9 @@ fun IdeaAgentApp(
355408 )
356409 }
357410 )
358- } ? : IdeaEmptyStateMessage (" Loading Remote Agent..." )
411+ } else {
412+ IdeaEmptyStateMessage (" Loading Remote Agent..." )
413+ }
359414 }
360415 AgentType .CODE_REVIEW -> {
361416 Box (modifier = Modifier .fillMaxWidth().weight(1f )) {
0 commit comments