From 3565acc37d9af464f651e151a251bec69a69472f Mon Sep 17 00:00:00 2001 From: Ashot Nazaryan Date: Wed, 29 Jul 2026 06:54:47 -0700 Subject: [PATCH 1/7] feat: enable reworked terminal engine and remove legacy compatibility overrides --- README.md | 12 +++--- build.gradle.kts | 11 ++--- .../relay/settings/OpenCodeSettings.kt | 2 +- .../settings/OpenCodeSettingsConfigurable.kt | 7 +++- .../relay/terminal/ClassicTuiPanel.kt | 41 +------------------ .../relay/terminal/ReworkedTuiPanel.kt | 21 +++------- .../opencode/relay/terminal/TuiPanel.kt | 7 ++-- .../toolwindow/OpenCodeToolWindowPanel.kt | 19 ++++----- src/main/resources/META-INF/withTerminal.xml | 5 ++- 9 files changed, 38 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 24dc2af..9773350 100644 --- a/README.md +++ b/README.md @@ -103,8 +103,7 @@ it. ### Permission prompts -When any agent requests permission, a prompt appears at the bottom of -the panel with three choices: +When any agent requests permission, a prompt appears at the bottom of the panel with three choices: - **Allow** — permit this one request - **Allow Always** — permit this type of request for the rest of the session @@ -114,7 +113,7 @@ Responses are sent back to the server immediately. ## Requirements -- A JetBrains IDE based on IntelliJ Platform 2024.3.7 or later +- A JetBrains IDE based on IntelliJ Platform 2025.3 or later - [OpenCode CLI](https://opencode.ai/docs) version `1.16.0+` installed and on `PATH` (or the path configured in settings) @@ -126,8 +125,7 @@ OpenCode Relay will warn you when JetBrains MCP is disabled or when a running Op To enable JetBrains MCP: -1. Open **Settings | Tools | MCP Server** in your JetBrains - IDE and enable the server. +1. Open **Settings | Tools | MCP Server** in your JetBrains IDE and enable the server. 2. Note the MCP server URL or port shown in the IDE settings. 3. Add a `jetbrains` MCP server to your `opencode.json`. 4. Restart the OpenCode server so it reloads the config. @@ -147,8 +145,8 @@ Basic `opencode.json` example: } ``` -Edit `url` to match the host, port, and path from **Settings | Tools | MCP Server**. If your IDE shows a different -port, replace `64342` with that port. +Edit `url` to match the host, port, and path from **Settings | Tools | MCP Server**. If your IDE shows a different port, +replace `64342` with that port. ### MCP Tips diff --git a/build.gradle.kts b/build.gradle.kts index 4315bba..24af8e4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,7 +47,7 @@ val liveTest by sourceSets.creating { dependencies { intellijPlatform { - intellijIdea("2024.3.7") + intellijIdea("2025.3.3") pluginVerifier() bundledPlugin("org.jetbrains.plugins.terminal") compatiblePlugin("com.intellij.mcpServer") @@ -80,14 +80,14 @@ intellijPlatform { } ideaVersion { - sinceBuild = "243" + sinceBuild = "253" untilBuild = provider { null } } } pluginVerification { ides { - create(org.jetbrains.intellij.platform.gradle.IntelliJPlatformType.IntellijIdea, "2024.3.7") + create(org.jetbrains.intellij.platform.gradle.IntelliJPlatformType.IntellijIdea, "2025.3.3") } } @@ -154,9 +154,4 @@ tasks { kotlin { jvmToolchain(21) - - sourceSets.named("main") { - kotlin.exclude("com/ashotn/opencode/relay/terminal/ReworkedTuiPanel.kt") - kotlin.exclude("com/ashotn/opencode/relay/terminal/NewSessionTerminalAllowedActionsProvider.kt") - } } diff --git a/src/main/kotlin/com/ashotn/opencode/relay/settings/OpenCodeSettings.kt b/src/main/kotlin/com/ashotn/opencode/relay/settings/OpenCodeSettings.kt index c421f98..4647489 100644 --- a/src/main/kotlin/com/ashotn/opencode/relay/settings/OpenCodeSettings.kt +++ b/src/main/kotlin/com/ashotn/opencode/relay/settings/OpenCodeSettings.kt @@ -26,7 +26,7 @@ class OpenCodeSettings : PersistentStateComponent { /** JBTerminalWidget (classic terminal plugin, works on all supported IDE versions). */ CLASSIC, - /** Parked reworked terminal option kept for easy re-enable later. */ + /** TerminalToolWindowTabsManager (reworked terminal, requires IntelliJ 2025.3+). */ REWORKED, } diff --git a/src/main/kotlin/com/ashotn/opencode/relay/settings/OpenCodeSettingsConfigurable.kt b/src/main/kotlin/com/ashotn/opencode/relay/settings/OpenCodeSettingsConfigurable.kt index e72751c..32004a9 100644 --- a/src/main/kotlin/com/ashotn/opencode/relay/settings/OpenCodeSettingsConfigurable.kt +++ b/src/main/kotlin/com/ashotn/opencode/relay/settings/OpenCodeSettingsConfigurable.kt @@ -228,6 +228,10 @@ class OpenCodeSettingsConfigurable(private val project: Project) : radioButton("Classic (Recommended)", TerminalEngine.CLASSIC) .comment("Legacy JediTerm widget.") } + row { + radioButton("Reworked", TerminalEngine.REWORKED) + .comment("New terminal engine.") + } }.bind(pendingState::terminalEngine) } group("Diagnostics") { @@ -419,8 +423,7 @@ class OpenCodeSettingsConfigurable(private val project: Project) : pendingState.diffTraceHistoryEnabled = settings.diffTraceHistoryEnabled pendingState.inlineTerminalEnabled = settings.inlineTerminalEnabled pendingState.sessionsSectionVisible = settings.sessionsSectionVisible - pendingState.terminalEngine = - if (settings.terminalEngine == TerminalEngine.REWORKED) TerminalEngine.CLASSIC else settings.terminalEngine + pendingState.terminalEngine = settings.terminalEngine pendingState.braveModeEnabled = settings.braveModeEnabled pendingState.jetBrainsMcpWarningEnabled = settings.jetBrainsMcpWarningEnabled } diff --git a/src/main/kotlin/com/ashotn/opencode/relay/terminal/ClassicTuiPanel.kt b/src/main/kotlin/com/ashotn/opencode/relay/terminal/ClassicTuiPanel.kt index b6f01ca..b6dd1e2 100644 --- a/src/main/kotlin/com/ashotn/opencode/relay/terminal/ClassicTuiPanel.kt +++ b/src/main/kotlin/com/ashotn/opencode/relay/terminal/ClassicTuiPanel.kt @@ -17,7 +17,6 @@ import com.intellij.terminal.JBTerminalPanel import com.intellij.terminal.ui.TerminalWidget import com.intellij.util.ui.ImageUtil import com.jediterm.terminal.TtyConnector -import com.pty4j.PtyProcess import org.jetbrains.plugins.terminal.LocalTerminalDirectRunner import org.jetbrains.plugins.terminal.ShellStartupOptions import org.jetbrains.plugins.terminal.ShellTerminalWidget @@ -35,10 +34,7 @@ import java.awt.image.BufferedImage import java.io.File import java.io.IOException import java.lang.reflect.Proxy -import java.lang.invoke.MethodHandles -import java.lang.invoke.MethodType import java.nio.file.Files -import java.util.concurrent.ExecutionException import javax.imageio.ImageIO import javax.swing.ImageIcon import javax.swing.JPanel @@ -313,41 +309,8 @@ class ClassicTuiPanel( } private class ClipboardAwareTerminalRunner(project: Project) : LocalTerminalDirectRunner(project) { - override fun createTtyConnector(process: PtyProcess): TtyConnector = - wrapTtyConnector(super.createTtyConnector(process)) - - /** - * Runtime override for newer IDEs where terminal startup calls createTtyConnector(ShellStartupOptions). - * This project still compiles against 2024.3, where that method is absent, so this cannot use `override`. - */ - @Suppress("unused") - @Throws(ExecutionException::class) - fun createTtyConnector(startupOptions: ShellStartupOptions): TtyConnector = - wrapTtyConnector(createPlatformTtyConnector(startupOptions)) - - private fun createPlatformTtyConnector(startupOptions: ShellStartupOptions): TtyConnector { - val createTtyConnectorHandle = try { - val methodType = MethodType.methodType(TtyConnector::class.java, ShellStartupOptions::class.java) - MethodHandles.lookup() - .findSpecial( - LocalTerminalDirectRunner::class.java, - "createTtyConnector", - methodType, - ClipboardAwareTerminalRunner::class.java, - ) - .bindTo(this) - } catch (_: NoSuchMethodException) { - return super.createTtyConnector(createProcess(startupOptions)) - } catch (e: IllegalAccessException) { - logger.warn("Unable to call new terminal connector path; falling back to PTY connector", e) - return super.createTtyConnector(createProcess(startupOptions)) - } catch (e: LinkageError) { - logger.warn("Unable to link new terminal connector path; falling back to PTY connector", e) - return super.createTtyConnector(createProcess(startupOptions)) - } - - return createTtyConnectorHandle.invokeWithArguments(startupOptions) as TtyConnector - } + override fun createTtyConnector(startupOptions: ShellStartupOptions): TtyConnector = + wrapTtyConnector(super.createTtyConnector(startupOptions)) private fun wrapTtyConnector(delegate: TtyConnector): TtyConnector = Osc52ClipboardTtyConnector(delegate) { text -> diff --git a/src/main/kotlin/com/ashotn/opencode/relay/terminal/ReworkedTuiPanel.kt b/src/main/kotlin/com/ashotn/opencode/relay/terminal/ReworkedTuiPanel.kt index 2533d1a..95ad2a6 100644 --- a/src/main/kotlin/com/ashotn/opencode/relay/terminal/ReworkedTuiPanel.kt +++ b/src/main/kotlin/com/ashotn/opencode/relay/terminal/ReworkedTuiPanel.kt @@ -12,7 +12,6 @@ import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer -import com.intellij.terminal.frontend.toolwindow.TerminalToolWindowTab import com.intellij.terminal.frontend.toolwindow.TerminalToolWindowTabsManager import com.intellij.terminal.frontend.view.TerminalView import com.intellij.terminal.frontend.view.TerminalViewSessionState @@ -25,11 +24,11 @@ import javax.swing.JPanel /** * Hosts an embedded Reworked Terminal running `opencode attach `. * - * Uses the official [TerminalToolWindowTabsManager] API (available since 2025.3) - * to create a terminal session that is never shown in the Terminal tool window — - * `shouldAddToToolWindow(false)` keeps it fully detached so it lives only inside - * this panel. The [TerminalView.component] is embedded directly in the panel's - * [BorderLayout.CENTER]. + * Uses the experimental [TerminalToolWindowTabsManager] API (available since 2025.3) + * to create a terminal session that is never shown in the Terminal tool window. + * The builder's internal `shouldAddToToolWindow(false)` option is currently the only + * platform API that creates a detached session directly. The [TerminalView.component] + * is embedded in the panel's [BorderLayout.CENTER]. * * The terminal is started lazily on the first call to [startIfNeeded] and lives * for as long as this panel's parent [Disposable] is alive. @@ -47,7 +46,6 @@ class ReworkedTuiPanel( private val onTerminated: (() -> Unit)? = null, ) : JPanel(BorderLayout()), TuiPanel, Disposable { - private var terminalTab: TerminalToolWindowTab? = null private var terminalContent: Content? = null private var terminalView: TerminalView? = null @@ -98,7 +96,6 @@ class ReworkedTuiPanel( val view = tab.view val content = tab.content - terminalTab = tab terminalContent = content terminalView = view @@ -115,12 +112,7 @@ class ReworkedTuiPanel( if (state is TerminalViewSessionState.Terminated) { ApplicationManager.getApplication().invokeLater { if (terminalView === view) { - terminalView = null - terminalTab = null - terminalContent = null - remove(view.component) - revalidate() - repaint() + tearDown() onTerminated?.invoke() } } @@ -156,7 +148,6 @@ class ReworkedTuiPanel( val view = terminalView ?: return val content = terminalContent terminalView = null - terminalTab = null terminalContent = null remove(view.component) revalidate() diff --git a/src/main/kotlin/com/ashotn/opencode/relay/terminal/TuiPanel.kt b/src/main/kotlin/com/ashotn/opencode/relay/terminal/TuiPanel.kt index 7ab3bfb..aa74576 100644 --- a/src/main/kotlin/com/ashotn/opencode/relay/terminal/TuiPanel.kt +++ b/src/main/kotlin/com/ashotn/opencode/relay/terminal/TuiPanel.kt @@ -7,11 +7,12 @@ import javax.swing.JPanel * Common contract for the embeddable terminal panel that runs * `opencode attach ` inside the tool window. * + * Two implementations exist: + * - [ClassicTuiPanel] backed by the classic JediTerm widget + * - [ReworkedTuiPanel] backed by the TerminalToolWindowTabsManager API + * * The active implementation is chosen by * [com.ashotn.opencode.relay.settings.OpenCodeSettings.terminalEngine]. - * - * The reworked implementation is currently parked and excluded from the build, - * so the runtime always uses [ClassicTuiPanel]. */ interface TuiPanel : Disposable { /** The Swing component to embed in the tool window. */ diff --git a/src/main/kotlin/com/ashotn/opencode/relay/toolwindow/OpenCodeToolWindowPanel.kt b/src/main/kotlin/com/ashotn/opencode/relay/toolwindow/OpenCodeToolWindowPanel.kt index 311b86a..f8a7669 100644 --- a/src/main/kotlin/com/ashotn/opencode/relay/toolwindow/OpenCodeToolWindowPanel.kt +++ b/src/main/kotlin/com/ashotn/opencode/relay/toolwindow/OpenCodeToolWindowPanel.kt @@ -14,6 +14,7 @@ import com.ashotn.opencode.relay.settings.OpenCodeSettings import com.ashotn.opencode.relay.settings.OpenCodeSettings.TerminalEngine import com.ashotn.opencode.relay.settings.OpenCodeSettingsChangedListener import com.ashotn.opencode.relay.terminal.ClassicTuiPanel +import com.ashotn.opencode.relay.terminal.ReworkedTuiPanel import com.ashotn.opencode.relay.terminal.TuiPanel import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager @@ -58,8 +59,7 @@ class OpenCodeToolWindowPanel(private val project: Project) : JPanel(BorderLayou private val mcpWarningPanel = JetBrainsMcpWarningPanel(project) private val pendingFilesPanel = PendingFilesPanel(project, this) private var tuiPanel: TuiPanel = createTuiPanel() - private var activeTuiEngine: TerminalEngine = - effectiveTerminalEngine(OpenCodeSettings.getInstance(project).terminalEngine) + private var activeTuiEngine: TerminalEngine = OpenCodeSettings.getInstance(project).terminalEngine private val syncScheduled = AtomicBoolean(false) private val plugin = OpenCodePlugin.getInstance(project) private val serverStateListener = ServerStateListener { requestSyncCard() } @@ -212,7 +212,7 @@ class OpenCodeToolWindowPanel(private val project: Project) : JPanel(BorderLayou * it into the split pane — all without requiring an IDE restart. */ private fun swapTuiPanelIfEngineChanged() { - val configuredEngine = effectiveTerminalEngine(OpenCodeSettings.getInstance(project).terminalEngine) + val configuredEngine = OpenCodeSettings.getInstance(project).terminalEngine if (configuredEngine == activeTuiEngine) return // Stop and dispose the old panel. @@ -244,15 +244,12 @@ class OpenCodeToolWindowPanel(private val project: Project) : JPanel(BorderLayou repaint() } - /** - * Creates the terminal panel for the currently configured [OpenCodeSettings.terminalEngine]. - * The reworked implementation is parked and currently resolved to [ClassicTuiPanel]. - */ + /** Creates the terminal panel for the configured [OpenCodeSettings.terminalEngine]. */ private fun createTuiPanel(): TuiPanel = - ClassicTuiPanel(project, this, onTerminated = { requestSyncCard() }) - - private fun effectiveTerminalEngine(requested: TerminalEngine): TerminalEngine = - if (requested == TerminalEngine.REWORKED) TerminalEngine.CLASSIC else requested + when (OpenCodeSettings.getInstance(project).terminalEngine) { + TerminalEngine.CLASSIC -> ClassicTuiPanel(project, this, onTerminated = { requestSyncCard() }) + TerminalEngine.REWORKED -> ReworkedTuiPanel(project, this, onTerminated = { requestSyncCard() }) + } private var disposed = false diff --git a/src/main/resources/META-INF/withTerminal.xml b/src/main/resources/META-INF/withTerminal.xml index d3f237f..0c0492c 100644 --- a/src/main/resources/META-INF/withTerminal.xml +++ b/src/main/resources/META-INF/withTerminal.xml @@ -1,3 +1,6 @@ - + + + From 90149fc6bdd349cc15d73a49d3a7a0615346c88b Mon Sep 17 00:00:00 2001 From: Ashot Nazaryan Date: Wed, 29 Jul 2026 11:04:28 -0700 Subject: [PATCH 2/7] feat: add IntelliJ 2026.2 compatibility for reworked terminal engine --- .idea/runConfigurations/Run_Plugin.xml | 2 +- build.gradle.kts | 18 +++++++ .../relay/settings/OpenCodeSettings.kt | 18 ++++++- .../settings/OpenCodeSettingsConfigurable.kt | 5 +- .../relay/terminal/ReworkedTuiPanel.kt | 53 +++++++------------ .../toolwindow/OpenCodeToolWindowPanel.kt | 10 ++-- 6 files changed, 65 insertions(+), 41 deletions(-) diff --git a/.idea/runConfigurations/Run_Plugin.xml b/.idea/runConfigurations/Run_Plugin.xml index 81308da..14eab45 100644 --- a/.idea/runConfigurations/Run_Plugin.xml +++ b/.idea/runConfigurations/Run_Plugin.xml @@ -10,7 +10,7 @@