From d673f2a758671af76381686a51aea74ff2c9bd60 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Fri, 13 Feb 2026 20:08:18 +0100 Subject: [PATCH] IJPL-233558 extract codex session support GitOrigin-RevId: d6844015bdfa7e74935877310f518d8cd65ec740 --- .idea/modules.xml | 1 + build/bazel-generated-file-list.txt | 1 + .../codex/sessions/BUILD.bazel | 59 +++++++++++++++++++ ...ntellij.agent.workbench.codex.sessions.iml | 25 ++++++++ ...ntellij.agent.workbench.codex.sessions.xml | 8 +++ .../src/CodexAppServerSessionBackend.kt | 32 ++++++++++ .../sessions/src}/CodexProjectPaths.kt | 3 +- .../src}/CodexRolloutSessionBackend.kt | 20 ++++--- .../codex/sessions/src/CodexSessionBackend.kt | 28 +++++++++ .../sessions/src/CodexSessionBackends.kt | 13 ++++ .../src}/SharedCodexAppServerService.kt | 9 +-- .../testSrc/CodexProjectSessionServiceTest.kt | 37 ++++++------ .../testSrc/CodexRolloutSessionBackendTest.kt | 14 ++--- plugins/agent-workbench/plugin-content.yaml | 1 + .../plugin/plugin-content.yaml | 3 + .../plugin/resources/META-INF/plugin.xml | 1 + .../testSrc/AgentWorkbenchAllTestsSuite.kt | 1 + plugins/agent-workbench/sessions/BUILD.bazel | 3 + .../intellij.agent.workbench.sessions.iml | 1 + .../intellij.agent.workbench.sessions.xml | 1 + .../sessions/src/AgentSessionsService.kt | 2 +- .../providers/codex/CodexSessionBackend.kt | 16 ----- .../src/providers/codex/CodexSessionSource.kt | 50 +++++++++------- .../spec/actions/new-thread.spec.md | 3 +- ...gent-sessions-codex-rollout-source.spec.md | 4 +- 25 files changed, 255 insertions(+), 81 deletions(-) create mode 100644 plugins/agent-workbench/codex/sessions/BUILD.bazel create mode 100644 plugins/agent-workbench/codex/sessions/intellij.agent.workbench.codex.sessions.iml create mode 100644 plugins/agent-workbench/codex/sessions/resources/intellij.agent.workbench.codex.sessions.xml create mode 100644 plugins/agent-workbench/codex/sessions/src/CodexAppServerSessionBackend.kt rename plugins/agent-workbench/{sessions/src/codex => codex/sessions/src}/CodexProjectPaths.kt (97%) rename plugins/agent-workbench/{sessions/src/providers/codex => codex/sessions/src}/CodexRolloutSessionBackend.kt (95%) create mode 100644 plugins/agent-workbench/codex/sessions/src/CodexSessionBackend.kt create mode 100644 plugins/agent-workbench/codex/sessions/src/CodexSessionBackends.kt rename plugins/agent-workbench/{sessions/src/codex => codex/sessions/src}/SharedCodexAppServerService.kt (82%) rename plugins/agent-workbench/{ => codex}/sessions/testSrc/CodexProjectSessionServiceTest.kt (73%) rename plugins/agent-workbench/{ => codex}/sessions/testSrc/CodexRolloutSessionBackendTest.kt (94%) delete mode 100644 plugins/agent-workbench/sessions/src/providers/codex/CodexSessionBackend.kt diff --git a/.idea/modules.xml b/.idea/modules.xml index 7c3028e0db86..76599a3992d9 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -50,6 +50,7 @@ + diff --git a/build/bazel-generated-file-list.txt b/build/bazel-generated-file-list.txt index f00d30d26617..afaae2c7ff6f 100644 --- a/build/bazel-generated-file-list.txt +++ b/build/bazel-generated-file-list.txt @@ -877,6 +877,7 @@ plugins/IntelliLang/tests plugins/agent-workbench/chat plugins/agent-workbench/claude/common plugins/agent-workbench/codex/common +plugins/agent-workbench/codex/sessions plugins/agent-workbench/plugin plugins/agent-workbench/sessions plugins/ant diff --git a/plugins/agent-workbench/codex/sessions/BUILD.bazel b/plugins/agent-workbench/codex/sessions/BUILD.bazel new file mode 100644 index 000000000000..e3b3f6ec153b --- /dev/null +++ b/plugins/agent-workbench/codex/sessions/BUILD.bazel @@ -0,0 +1,59 @@ +### auto-generated section `build intellij.agent.workbench.codex.sessions` start +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +resourcegroup( + name = "sessions_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "sessions", + module_name = "intellij.agent.workbench.codex.sessions", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":sessions_resources"], + deps = [ + "@lib//:kotlin-stdlib", + "//platform/core-api:core", + "//platform/projectModel-api:projectModel", + "//platform/util", + "//platform/platform-api:ide", + "//platform/platform-util-io:ide-util-io", + "//plugins/agent-workbench/codex/common", + "//libraries/jackson/jackson", + ] +) + +jvm_library( + name = "sessions_test_lib", + visibility = ["//visibility:public"], + srcs = glob(["testSrc/**/*.kt", "testSrc/**/*.java", "testSrc/**/*.form"], allow_empty = True), + associates = [":sessions"], + deps = [ + "@lib//:kotlin-stdlib", + "//platform/core-api:core", + "//platform/projectModel-api:projectModel", + "//platform/util", + "//platform/platform-api:ide", + "//platform/platform-util-io:ide-util-io", + "//plugins/agent-workbench/codex/common", + "//libraries/jackson/jackson", + "//libraries/junit5", + "//platform/testFramework", + "//platform/testFramework:testFramework_test_lib", + "//platform/testFramework/junit5", + "//platform/testFramework/junit5:junit5_test_lib", + "//libraries/assertj-core", + ] +) +### auto-generated section `build intellij.agent.workbench.codex.sessions` end + +### auto-generated section `test intellij.agent.workbench.codex.sessions` start +load("@community//build:tests-options.bzl", "jps_test") + +jps_test( + name = "sessions_test", + runtime_deps = [":sessions_test_lib"] +) +### auto-generated section `test intellij.agent.workbench.codex.sessions` end \ No newline at end of file diff --git a/plugins/agent-workbench/codex/sessions/intellij.agent.workbench.codex.sessions.iml b/plugins/agent-workbench/codex/sessions/intellij.agent.workbench.codex.sessions.iml new file mode 100644 index 000000000000..084e53da12fd --- /dev/null +++ b/plugins/agent-workbench/codex/sessions/intellij.agent.workbench.codex.sessions.iml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/agent-workbench/codex/sessions/resources/intellij.agent.workbench.codex.sessions.xml b/plugins/agent-workbench/codex/sessions/resources/intellij.agent.workbench.codex.sessions.xml new file mode 100644 index 000000000000..bb86a02fc558 --- /dev/null +++ b/plugins/agent-workbench/codex/sessions/resources/intellij.agent.workbench.codex.sessions.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/plugins/agent-workbench/codex/sessions/src/CodexAppServerSessionBackend.kt b/plugins/agent-workbench/codex/sessions/src/CodexAppServerSessionBackend.kt new file mode 100644 index 000000000000..f724782703a4 --- /dev/null +++ b/plugins/agent-workbench/codex/sessions/src/CodexAppServerSessionBackend.kt @@ -0,0 +1,32 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.agent.workbench.codex.sessions + +import com.intellij.agent.workbench.codex.common.normalizeRootPath +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import kotlin.io.path.invariantSeparatorsPathString + +class CodexAppServerSessionBackend : CodexSessionBackend { + override suspend fun listThreads(path: String, @Suppress("UNUSED_PARAMETER") openProject: Project?): List { + val workingDirectory = resolveProjectDirectoryFromPath(path) ?: return emptyList() + val codexService = service() + val threads = codexService.listThreads(workingDirectory) + return threads.map { CodexBackendThread(it) } + } + + override suspend fun prefetchThreads(paths: List): Map> { + if (paths.isEmpty()) return emptyMap() + val codexService = service() + val allThreads = codexService.listAllThreads() + val pathToCwd = paths.mapNotNull { path -> + resolveProjectDirectoryFromPath(path)?.let { dir -> + path to normalizeRootPath(dir.invariantSeparatorsPathString) + } + } + return pathToCwd.associate { (path, cwdFilter) -> + val matching = allThreads.filter { it.cwd == cwdFilter } + path to matching.map { CodexBackendThread(it) } + } + } +} + diff --git a/plugins/agent-workbench/sessions/src/codex/CodexProjectPaths.kt b/plugins/agent-workbench/codex/sessions/src/CodexProjectPaths.kt similarity index 97% rename from plugins/agent-workbench/sessions/src/codex/CodexProjectPaths.kt rename to plugins/agent-workbench/codex/sessions/src/CodexProjectPaths.kt index 3d35ae4bbf96..25181f0f5280 100644 --- a/plugins/agent-workbench/sessions/src/codex/CodexProjectPaths.kt +++ b/plugins/agent-workbench/codex/sessions/src/CodexProjectPaths.kt @@ -1,5 +1,5 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.agent.workbench.sessions.codex +package com.intellij.agent.workbench.codex.sessions import com.intellij.openapi.diagnostic.debug import com.intellij.openapi.diagnostic.logger @@ -77,3 +77,4 @@ internal fun registerShutdownOnCancellation(scope: CoroutineScope, onShutdown: ( onShutdown() } } + diff --git a/plugins/agent-workbench/sessions/src/providers/codex/CodexRolloutSessionBackend.kt b/plugins/agent-workbench/codex/sessions/src/CodexRolloutSessionBackend.kt similarity index 95% rename from plugins/agent-workbench/sessions/src/providers/codex/CodexRolloutSessionBackend.kt rename to plugins/agent-workbench/codex/sessions/src/CodexRolloutSessionBackend.kt index 4251432696d2..5cf131633ddc 100644 --- a/plugins/agent-workbench/sessions/src/providers/codex/CodexRolloutSessionBackend.kt +++ b/plugins/agent-workbench/codex/sessions/src/CodexRolloutSessionBackend.kt @@ -1,5 +1,5 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.agent.workbench.sessions.providers.codex +package com.intellij.agent.workbench.codex.sessions // @spec community/plugins/agent-workbench/spec/agent-sessions-codex-rollout-source.spec.md @@ -9,8 +9,6 @@ import com.fasterxml.jackson.core.JsonToken import com.intellij.agent.workbench.codex.common.CodexThread import com.intellij.agent.workbench.codex.common.forEachObjectField import com.intellij.agent.workbench.codex.common.readStringOrNull -import com.intellij.agent.workbench.sessions.AgentSessionActivity -import com.intellij.agent.workbench.sessions.codex.resolveProjectDirectoryFromPath import com.intellij.openapi.project.Project import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -24,7 +22,7 @@ private const val ROLLOUT_FILE_PREFIX = "rollout-" private const val ROLLOUT_FILE_SUFFIX = ".jsonl" private const val MAX_TITLE_LENGTH = 120 -internal class CodexRolloutSessionBackend( +class CodexRolloutSessionBackend( private val codexHomeProvider: () -> Path = { Path.of(System.getProperty("user.home"), ".codex") }, ) : CodexSessionBackend { private val jsonFactory = JsonFactory() @@ -97,6 +95,7 @@ internal class CodexRolloutSessionBackend( pendingUserInputAt = null } } + "agent_message" -> { latestAgentMessageAt = maxTimestamp(latestAgentMessageAt, eventTimestamp) } @@ -122,6 +121,7 @@ internal class CodexRolloutSessionBackend( pendingUserInputAt = null } } + "assistant" -> { latestAgentMessageAt = maxTimestamp(latestAgentMessageAt, eventTimestamp) } @@ -143,10 +143,10 @@ internal class CodexRolloutSessionBackend( val hasUnread = latestAgentMessageAt > latestUserMessageAt val hasPendingUserInput = pendingUserInputAt != null val activity = when { - hasPendingUserInput || hasUnread -> AgentSessionActivity.UNREAD - reviewing -> AgentSessionActivity.REVIEWING - processing -> AgentSessionActivity.PROCESSING - else -> AgentSessionActivity.READY + hasPendingUserInput || hasUnread -> CodexSessionActivity.UNREAD + reviewing -> CodexSessionActivity.REVIEWING + processing -> CodexSessionActivity.PROCESSING + else -> CodexSessionActivity.READY } val fallbackUpdatedAt = runCatching { Files.getLastModifiedTime(path).toMillis() }.getOrDefault(0L) @@ -198,9 +198,11 @@ internal class CodexRolloutSessionBackend( "git" -> { gitBranch = parseNestedStringField(parser, "branch") } + "item" -> { itemType = parseNestedStringField(parser, "type") } + else -> parser.skipChildren() } true @@ -210,6 +212,7 @@ internal class CodexRolloutSessionBackend( parser.skipChildren() } } + else -> parser.skipChildren() } true @@ -304,3 +307,4 @@ private fun maxTimestamp(current: Long, candidate: Long?): Long { if (candidate == null) return current return if (candidate > current) candidate else current } + diff --git a/plugins/agent-workbench/codex/sessions/src/CodexSessionBackend.kt b/plugins/agent-workbench/codex/sessions/src/CodexSessionBackend.kt new file mode 100644 index 000000000000..f0050ef4d7c2 --- /dev/null +++ b/plugins/agent-workbench/codex/sessions/src/CodexSessionBackend.kt @@ -0,0 +1,28 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.agent.workbench.codex.sessions + +import com.intellij.agent.workbench.codex.common.CodexThread +import com.intellij.openapi.project.Project + +data class CodexBackendThread( + val thread: CodexThread, + val activity: CodexSessionActivity = CodexSessionActivity.READY, +) + +enum class CodexSessionActivity { + UNREAD, + REVIEWING, + PROCESSING, + READY, +} + +interface CodexSessionBackend { + suspend fun listThreads(path: String, openProject: Project?): List + + /** + * Prefetch threads for multiple paths in a single backend call. + * Returns a map of path to threads. Empty map means no prefetch (use per-path calls). + */ + suspend fun prefetchThreads(paths: List): Map> = emptyMap() +} + diff --git a/plugins/agent-workbench/codex/sessions/src/CodexSessionBackends.kt b/plugins/agent-workbench/codex/sessions/src/CodexSessionBackends.kt new file mode 100644 index 000000000000..127a5b78b0bb --- /dev/null +++ b/plugins/agent-workbench/codex/sessions/src/CodexSessionBackends.kt @@ -0,0 +1,13 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.agent.workbench.codex.sessions + +private const val BACKEND_OVERRIDE_PROPERTY = "agent.workbench.codex.sessions.backend" +private const val APP_SERVER_BACKEND = "app-server" + +fun createDefaultCodexSessionBackend(): CodexSessionBackend { + return when (System.getProperty(BACKEND_OVERRIDE_PROPERTY)) { + APP_SERVER_BACKEND -> CodexAppServerSessionBackend() + else -> CodexRolloutSessionBackend() + } +} + diff --git a/plugins/agent-workbench/sessions/src/codex/SharedCodexAppServerService.kt b/plugins/agent-workbench/codex/sessions/src/SharedCodexAppServerService.kt similarity index 82% rename from plugins/agent-workbench/sessions/src/codex/SharedCodexAppServerService.kt rename to plugins/agent-workbench/codex/sessions/src/SharedCodexAppServerService.kt index b48d500648a8..a8c81407a670 100644 --- a/plugins/agent-workbench/sessions/src/codex/SharedCodexAppServerService.kt +++ b/plugins/agent-workbench/codex/sessions/src/SharedCodexAppServerService.kt @@ -1,5 +1,5 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.agent.workbench.sessions.codex +package com.intellij.agent.workbench.codex.sessions import com.intellij.agent.workbench.codex.common.CodexAppServerClient import com.intellij.agent.workbench.codex.common.CodexThread @@ -10,19 +10,19 @@ import java.nio.file.Path import kotlin.io.path.invariantSeparatorsPathString @Service(Service.Level.APP) -internal class SharedCodexAppServerService(serviceScope: CoroutineScope) { +class SharedCodexAppServerService(serviceScope: CoroutineScope) { private val client = CodexAppServerClient(coroutineScope = serviceScope) init { registerShutdownOnCancellation(serviceScope) { client.shutdown() } } - suspend fun listThreads(projectPath: Path): List { + internal suspend fun listThreads(projectPath: Path): List { val cwdFilter = normalizeRootPath(projectPath.invariantSeparatorsPathString) return client.listThreads(archived = false, cwdFilter = cwdFilter) } - suspend fun listAllThreads(): List { + internal suspend fun listAllThreads(): List { return client.listThreads(archived = false, cwdFilter = null) } @@ -37,3 +37,4 @@ internal class SharedCodexAppServerService(serviceScope: CoroutineScope) { return thread } } + diff --git a/plugins/agent-workbench/sessions/testSrc/CodexProjectSessionServiceTest.kt b/plugins/agent-workbench/codex/sessions/testSrc/CodexProjectSessionServiceTest.kt similarity index 73% rename from plugins/agent-workbench/sessions/testSrc/CodexProjectSessionServiceTest.kt rename to plugins/agent-workbench/codex/sessions/testSrc/CodexProjectSessionServiceTest.kt index 6923a1bd78cb..6e84895fa2d5 100644 --- a/plugins/agent-workbench/sessions/testSrc/CodexProjectSessionServiceTest.kt +++ b/plugins/agent-workbench/codex/sessions/testSrc/CodexProjectSessionServiceTest.kt @@ -1,8 +1,6 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.agent.workbench.sessions +package com.intellij.agent.workbench.codex.sessions -import com.intellij.agent.workbench.sessions.codex.registerShutdownOnCancellation -import com.intellij.agent.workbench.sessions.codex.resolveProjectDirectory import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -15,6 +13,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.nio.file.Files import java.nio.file.Path +import kotlin.time.Duration.Companion.seconds class CodexProjectSessionServiceTest { @TempDir @@ -27,13 +26,13 @@ class CodexProjectSessionServiceTest { val scope = CoroutineScope(coroutineContext + Job(parentJob)) val shutdown = CompletableDeferred() - registerShutdownOnCancellation(scope) { - shutdown.complete(Unit) - } + registerShutdownOnCancellation(scope) { + shutdown.complete(Unit) + } scope.cancel() - withTimeout(1_000) { + withTimeout(1.seconds) { shutdown.await() } } @@ -47,10 +46,10 @@ class CodexProjectSessionServiceTest { Files.writeString(miscXml, "") val resolved = resolveProjectDirectory( - recentProjectPath = miscXml, - projectFilePath = null, - basePath = null, - guessedProjectDir = null, + recentProjectPath = miscXml, + projectFilePath = null, + basePath = null, + guessedProjectDir = null, ) assertThat(resolved).isEqualTo(projectRoot) @@ -64,10 +63,10 @@ class CodexProjectSessionServiceTest { Files.writeString(iprFile, "") val resolved = resolveProjectDirectory( - recentProjectPath = null, - projectFilePath = iprFile.toString(), - basePath = null, - guessedProjectDir = null, + recentProjectPath = null, + projectFilePath = iprFile.toString(), + basePath = null, + guessedProjectDir = null, ) assertThat(resolved).isEqualTo(projectRoot) @@ -79,10 +78,10 @@ class CodexProjectSessionServiceTest { Files.createDirectories(projectRoot) val resolved = resolveProjectDirectory( - recentProjectPath = null, - projectFilePath = null, - basePath = null, - guessedProjectDir = projectRoot, + recentProjectPath = null, + projectFilePath = null, + basePath = null, + guessedProjectDir = projectRoot, ) assertThat(resolved).isEqualTo(projectRoot) diff --git a/plugins/agent-workbench/sessions/testSrc/CodexRolloutSessionBackendTest.kt b/plugins/agent-workbench/codex/sessions/testSrc/CodexRolloutSessionBackendTest.kt similarity index 94% rename from plugins/agent-workbench/sessions/testSrc/CodexRolloutSessionBackendTest.kt rename to plugins/agent-workbench/codex/sessions/testSrc/CodexRolloutSessionBackendTest.kt index d14a40183bf3..c1ab66cc2ef3 100644 --- a/plugins/agent-workbench/sessions/testSrc/CodexRolloutSessionBackendTest.kt +++ b/plugins/agent-workbench/codex/sessions/testSrc/CodexRolloutSessionBackendTest.kt @@ -1,7 +1,6 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.agent.workbench.sessions +package com.intellij.agent.workbench.codex.sessions -import com.intellij.agent.workbench.sessions.providers.codex.CodexRolloutSessionBackend import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -42,7 +41,7 @@ class CodexRolloutSessionBackendTest { assertThat(thread.thread.id).isEqualTo("session-abc") assertThat(thread.thread.title).isEqualTo("Fix flaky test") assertThat(thread.thread.updatedAt).isEqualTo(Instant.parse("2026-02-13T10:00:30.000Z").toEpochMilli()) - assertThat(thread.activity).isEqualTo(AgentSessionActivity.UNREAD) + assertThat(thread.activity).isEqualTo(CodexSessionActivity.UNREAD) } } @@ -91,7 +90,7 @@ class CodexRolloutSessionBackendTest { assertThat(threads).hasSize(1) val thread = threads.single() assertThat(thread.thread.id).isEqualTo("session-review") - assertThat(thread.activity).isEqualTo(AgentSessionActivity.REVIEWING) + assertThat(thread.activity).isEqualTo(CodexSessionActivity.REVIEWING) } } @@ -131,7 +130,7 @@ class CodexRolloutSessionBackendTest { val threads = backend.listThreads(path = projectDir.toString(), openProject = null) assertThat(threads.map { it.thread.id }).containsExactly("session-ready") - assertThat(threads.single().activity).isEqualTo(AgentSessionActivity.READY) + assertThat(threads.single().activity).isEqualTo(CodexSessionActivity.READY) } } @@ -157,7 +156,7 @@ class CodexRolloutSessionBackendTest { val threads = backend.listThreads(path = projectDir.toString(), openProject = null) assertThat(threads).hasSize(1) - assertThat(threads.single().activity).isEqualTo(AgentSessionActivity.PROCESSING) + assertThat(threads.single().activity).isEqualTo(CodexSessionActivity.PROCESSING) } } @@ -183,7 +182,7 @@ class CodexRolloutSessionBackendTest { val threads = backend.listThreads(path = projectDir.toString(), openProject = null) assertThat(threads).hasSize(1) - assertThat(threads.single().activity).isEqualTo(AgentSessionActivity.UNREAD) + assertThat(threads.single().activity).isEqualTo(CodexSessionActivity.UNREAD) } } @@ -222,3 +221,4 @@ private fun writeRollout(file: Path, lines: List) { Files.createDirectories(file.parent) Files.write(file, lines) } + diff --git a/plugins/agent-workbench/plugin-content.yaml b/plugins/agent-workbench/plugin-content.yaml index 5a588181938c..d489c5d8c132 100644 --- a/plugins/agent-workbench/plugin-content.yaml +++ b/plugins/agent-workbench/plugin-content.yaml @@ -3,5 +3,6 @@ - name: intellij.agent.workbench.plugin contentModules: - name: intellij.agent.workbench.codex.common + - name: intellij.agent.workbench.codex.sessions - name: intellij.agent.workbench.sessions - name: intellij.agent.workbench diff --git a/plugins/agent-workbench/plugin/plugin-content.yaml b/plugins/agent-workbench/plugin/plugin-content.yaml index 4349c56c1433..8713703524fb 100644 --- a/plugins/agent-workbench/plugin/plugin-content.yaml +++ b/plugins/agent-workbench/plugin/plugin-content.yaml @@ -10,6 +10,9 @@ - name: lib/modules/intellij.agent.workbench.codex.common.jar contentModules: - name: intellij.agent.workbench.codex.common +- name: lib/modules/intellij.agent.workbench.codex.sessions.jar + contentModules: + - name: intellij.agent.workbench.codex.sessions - name: lib/modules/intellij.agent.workbench.sessions.jar contentModules: - name: intellij.agent.workbench.sessions diff --git a/plugins/agent-workbench/plugin/resources/META-INF/plugin.xml b/plugins/agent-workbench/plugin/resources/META-INF/plugin.xml index 565c9e3152ea..e9e2431d079e 100644 --- a/plugins/agent-workbench/plugin/resources/META-INF/plugin.xml +++ b/plugins/agent-workbench/plugin/resources/META-INF/plugin.xml @@ -7,6 +7,7 @@ + diff --git a/plugins/agent-workbench/plugin/testSrc/AgentWorkbenchAllTestsSuite.kt b/plugins/agent-workbench/plugin/testSrc/AgentWorkbenchAllTestsSuite.kt index a519542bdc27..088c03590b71 100644 --- a/plugins/agent-workbench/plugin/testSrc/AgentWorkbenchAllTestsSuite.kt +++ b/plugins/agent-workbench/plugin/testSrc/AgentWorkbenchAllTestsSuite.kt @@ -7,6 +7,7 @@ import org.junit.platform.suite.api.Suite @Suite @SelectPackages( "com.intellij.agent.workbench.chat", + "com.intellij.agent.workbench.codex.sessions", "com.intellij.agent.workbench.sessions", ) class AgentWorkbenchAllTestsSuite diff --git a/plugins/agent-workbench/sessions/BUILD.bazel b/plugins/agent-workbench/sessions/BUILD.bazel index 0c8e571bf42e..577226b79778 100644 --- a/plugins/agent-workbench/sessions/BUILD.bazel +++ b/plugins/agent-workbench/sessions/BUILD.bazel @@ -27,6 +27,7 @@ jvm_library( "//platform/platform-util-io:ide-util-io", "//plugins/agent-workbench/claude/common", "//plugins/agent-workbench/codex/common", + "//plugins/agent-workbench/codex/sessions", "//plugins/agent-workbench/chat", "//libraries/jackson/jackson", "@lib//:jna", @@ -55,6 +56,8 @@ jvm_library( "//platform/platform-util-io:ide-util-io", "//plugins/agent-workbench/claude/common", "//plugins/agent-workbench/codex/common", + "//plugins/agent-workbench/codex/sessions", + "//plugins/agent-workbench/codex/sessions:sessions_test_lib", "//plugins/agent-workbench/chat", "//plugins/agent-workbench/chat:chat_test_lib", "//libraries/jackson/jackson", diff --git a/plugins/agent-workbench/sessions/intellij.agent.workbench.sessions.iml b/plugins/agent-workbench/sessions/intellij.agent.workbench.sessions.iml index 86764bff4259..c6b0933cc9f5 100644 --- a/plugins/agent-workbench/sessions/intellij.agent.workbench.sessions.iml +++ b/plugins/agent-workbench/sessions/intellij.agent.workbench.sessions.iml @@ -46,6 +46,7 @@ + diff --git a/plugins/agent-workbench/sessions/resources/intellij.agent.workbench.sessions.xml b/plugins/agent-workbench/sessions/resources/intellij.agent.workbench.sessions.xml index 361a36959434..9d761d7cff7d 100644 --- a/plugins/agent-workbench/sessions/resources/intellij.agent.workbench.sessions.xml +++ b/plugins/agent-workbench/sessions/resources/intellij.agent.workbench.sessions.xml @@ -5,6 +5,7 @@ + diff --git a/plugins/agent-workbench/sessions/src/AgentSessionsService.kt b/plugins/agent-workbench/sessions/src/AgentSessionsService.kt index 7b64339a4521..8977bd8b252b 100644 --- a/plugins/agent-workbench/sessions/src/AgentSessionsService.kt +++ b/plugins/agent-workbench/sessions/src/AgentSessionsService.kt @@ -7,7 +7,7 @@ package com.intellij.agent.workbench.sessions import com.intellij.agent.workbench.chat.AgentChatEditorService import com.intellij.agent.workbench.codex.common.CodexCliNotFoundException -import com.intellij.agent.workbench.sessions.codex.SharedCodexAppServerService +import com.intellij.agent.workbench.codex.sessions.SharedCodexAppServerService import com.intellij.agent.workbench.sessions.providers.AgentSessionSource import com.intellij.agent.workbench.sessions.providers.codex.CodexCliCommands import com.intellij.agent.workbench.sessions.providers.createDefaultAgentSessionSources diff --git a/plugins/agent-workbench/sessions/src/providers/codex/CodexSessionBackend.kt b/plugins/agent-workbench/sessions/src/providers/codex/CodexSessionBackend.kt deleted file mode 100644 index b2c42989d403..000000000000 --- a/plugins/agent-workbench/sessions/src/providers/codex/CodexSessionBackend.kt +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.agent.workbench.sessions.providers.codex - -import com.intellij.agent.workbench.codex.common.CodexThread -import com.intellij.agent.workbench.sessions.AgentSessionActivity -import com.intellij.openapi.project.Project - -internal data class CodexBackendThread( - val thread: CodexThread, - val activity: AgentSessionActivity = AgentSessionActivity.READY, -) - -internal interface CodexSessionBackend { - suspend fun listThreads(path: String, openProject: Project?): List -} - diff --git a/plugins/agent-workbench/sessions/src/providers/codex/CodexSessionSource.kt b/plugins/agent-workbench/sessions/src/providers/codex/CodexSessionSource.kt index 4fdb14bc281c..16d6823dd8ac 100644 --- a/plugins/agent-workbench/sessions/src/providers/codex/CodexSessionSource.kt +++ b/plugins/agent-workbench/sessions/src/providers/codex/CodexSessionSource.kt @@ -5,42 +5,38 @@ package com.intellij.agent.workbench.sessions.providers.codex // @spec community/plugins/agent-workbench/spec/agent-sessions-codex-rollout-source.spec.md import com.intellij.agent.workbench.codex.common.CodexThread -import com.intellij.agent.workbench.codex.common.normalizeRootPath +import com.intellij.agent.workbench.codex.sessions.CodexBackendThread +import com.intellij.agent.workbench.codex.sessions.CodexSessionActivity +import com.intellij.agent.workbench.codex.sessions.CodexSessionBackend +import com.intellij.agent.workbench.codex.sessions.createDefaultCodexSessionBackend +import com.intellij.agent.workbench.sessions.AgentSessionActivity import com.intellij.agent.workbench.sessions.AgentSessionProvider import com.intellij.agent.workbench.sessions.AgentSessionThread import com.intellij.agent.workbench.sessions.AgentSubAgent -import com.intellij.agent.workbench.sessions.codex.SharedCodexAppServerService -import com.intellij.agent.workbench.sessions.codex.resolveProjectDirectoryFromPath import com.intellij.agent.workbench.sessions.providers.BaseAgentSessionSource -import com.intellij.openapi.components.service import com.intellij.openapi.project.Project -import kotlin.io.path.invariantSeparatorsPathString -internal class CodexSessionSource : BaseAgentSessionSource(provider = AgentSessionProvider.CODEX, canReportExactThreadCount = false) { +internal class CodexSessionSource( + private val backend: CodexSessionBackend = createDefaultCodexSessionBackend(), +) : BaseAgentSessionSource(provider = AgentSessionProvider.CODEX, canReportExactThreadCount = false) { override suspend fun listThreads(path: String, openProject: Project?): List { - val workingDirectory = resolveProjectDirectoryFromPath(path) ?: return emptyList() - val codexService = service() - val threads = codexService.listThreads(workingDirectory) - return threads.map { it.toAgentSessionThread() } + return backend.listThreads(path = path, openProject = openProject).map { it.toAgentSessionThread() } } override suspend fun prefetchThreads(paths: List): Map> { - if (paths.isEmpty()) return emptyMap() - val codexService = service() - val allThreads = codexService.listAllThreads() - val pathToCwd = paths.mapNotNull { path -> - resolveProjectDirectoryFromPath(path)?.let { dir -> - path to normalizeRootPath(dir.invariantSeparatorsPathString) - } - } - return pathToCwd.associate { (path, cwdFilter) -> - val matching = allThreads.filter { it.cwd == cwdFilter } - path to matching.map { it.toAgentSessionThread() } + val prefetched = backend.prefetchThreads(paths) + if (prefetched.isEmpty()) return emptyMap() + return prefetched.mapValues { (_, threads) -> + threads.map { it.toAgentSessionThread() } } } } -private fun CodexThread.toAgentSessionThread(): AgentSessionThread { +private fun CodexBackendThread.toAgentSessionThread(): AgentSessionThread { + return thread.toAgentSessionThread(activity = activity) +} + +private fun CodexThread.toAgentSessionThread(activity: CodexSessionActivity): AgentSessionThread { return AgentSessionThread( id = id, title = title, @@ -49,5 +45,15 @@ private fun CodexThread.toAgentSessionThread(): AgentSessionThread { provider = AgentSessionProvider.CODEX, subAgents = subAgents.map { AgentSubAgent(it.id, it.name) }, originBranch = gitBranch, + activity = activity.toAgentSessionActivity(), ) } + +private fun CodexSessionActivity.toAgentSessionActivity(): AgentSessionActivity { + return when (this) { + CodexSessionActivity.UNREAD -> AgentSessionActivity.UNREAD + CodexSessionActivity.REVIEWING -> AgentSessionActivity.REVIEWING + CodexSessionActivity.PROCESSING -> AgentSessionActivity.PROCESSING + CodexSessionActivity.READY -> AgentSessionActivity.READY + } +} diff --git a/plugins/agent-workbench/spec/actions/new-thread.spec.md b/plugins/agent-workbench/spec/actions/new-thread.spec.md index 5a65a240840a..774f9adbe2ab 100644 --- a/plugins/agent-workbench/spec/actions/new-thread.spec.md +++ b/plugins/agent-workbench/spec/actions/new-thread.spec.md @@ -6,7 +6,7 @@ targets: - ../../sessions/src/SessionTreeRows.kt - ../../sessions/src/AgentSessionsToolWindow.kt - ../../sessions/src/AgentSessionsService.kt - - ../../sessions/src/codex/SharedCodexAppServerService.kt + - ../../codex/sessions/src/SharedCodexAppServerService.kt - ../../sessions/src/AgentSessionCli.kt - ../../sessions/src/providers/codex/CodexCliCommands.kt - ../../sessions/resources/messages/AgentSessionsBundle.properties @@ -81,4 +81,3 @@ Define project/worktree `New Session` actions (`+` and quick-provider icon) and - `../agent-sessions.spec.md` - `../agent-sessions-codex-rollout-source.spec.md` - `../agent-dedicated-frame.spec.md` - diff --git a/plugins/agent-workbench/spec/agent-sessions-codex-rollout-source.spec.md b/plugins/agent-workbench/spec/agent-sessions-codex-rollout-source.spec.md index 9234b6d91b15..6a61e3d4f257 100644 --- a/plugins/agent-workbench/spec/agent-sessions-codex-rollout-source.spec.md +++ b/plugins/agent-workbench/spec/agent-sessions-codex-rollout-source.spec.md @@ -3,10 +3,12 @@ name: Codex Sessions Rollout Source description: Codex thread list source and activity indicators for Agent Threads. targets: - ../sessions/src/providers/codex/*.kt + - ../codex/sessions/src/*.kt - ../sessions/src/SessionTreeStyle.kt - ../sessions/src/AgentSessionModels.kt - ../sessions/src/AgentSessionsService.kt - ../sessions/testSrc/*.kt + - ../codex/sessions/testSrc/*.kt --- # Codex Sessions Rollout Source @@ -51,7 +53,7 @@ Codex thread discovery for Agent Threads defaults to rollout files under `~/.cod - `CodexSessionSource` maps rollout backend data directly and does not use `CodexSessionBranchStore` fallback. ## Testing / Local Run -- `./tests.cmd '-Dintellij.build.test.patterns=com.intellij.agent.workbench.sessions.CodexRolloutSessionBackendTest'` +- `./tests.cmd '-Dintellij.build.test.patterns=com.intellij.agent.workbench.codex.sessions.CodexRolloutSessionBackendTest'` - `./tests.cmd '-Dintellij.build.test.patterns=com.intellij.agent.workbench.sessions.AgentSessionCliTest'` ## References