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