IJPL-233558 extract codex session support

GitOrigin-RevId: d6844015bdfa7e74935877310f518d8cd65ec740
This commit is contained in:
Vladimir Krivosheev
2026-02-13 21:24:05 +00:00
committed by intellij-monorepo-bot
parent 52c122642d
commit d673f2a758
25 changed files with 255 additions and 81 deletions
+1
View File
@@ -50,6 +50,7 @@
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/chat/intellij.agent.workbench.chat.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/chat/intellij.agent.workbench.chat.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/claude/common/intellij.agent.workbench.claude.common.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/claude/common/intellij.agent.workbench.claude.common.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/codex/common/intellij.agent.workbench.codex.common.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/codex/common/intellij.agent.workbench.codex.common.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/codex/sessions/intellij.agent.workbench.codex.sessions.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/codex/sessions/intellij.agent.workbench.codex.sessions.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/plugin/intellij.agent.workbench.plugin.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/plugin/intellij.agent.workbench.plugin.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/sessions/intellij.agent.workbench.sessions.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/sessions/intellij.agent.workbench.sessions.iml" />
<module fileurl="file://$PROJECT_DIR$/android/android-adb/intellij.android.adb.iml" filepath="$PROJECT_DIR$/android/android-adb/intellij.android.adb.iml" />
+1
View File
@@ -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
@@ -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
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="com.intellij.agent.workbench.codex.sessions" />
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" packagePrefix="com.intellij.agent.workbench.codex.sessions" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.platform.projectModel" />
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module" module-name="intellij.platform.ide" />
<orderEntry type="module" module-name="intellij.platform.ide.util.io" />
<orderEntry type="module" module-name="intellij.agent.workbench.codex.common" />
<orderEntry type="module" module-name="intellij.libraries.jackson" />
<orderEntry type="module" module-name="intellij.libraries.junit5" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.assertj.core" scope="TEST" />
</component>
</module>
@@ -0,0 +1,8 @@
<idea-plugin>
<!-- region Generated dependencies - run `Generate Product Layouts` to regenerate -->
<dependencies>
<module name="intellij.agent.workbench.codex.common"/>
</dependencies>
<!-- endregion -->
</idea-plugin>
@@ -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<CodexBackendThread> {
val workingDirectory = resolveProjectDirectoryFromPath(path) ?: return emptyList()
val codexService = service<SharedCodexAppServerService>()
val threads = codexService.listThreads(workingDirectory)
return threads.map { CodexBackendThread(it) }
}
override suspend fun prefetchThreads(paths: List<String>): Map<String, List<CodexBackendThread>> {
if (paths.isEmpty()) return emptyMap()
val codexService = service<SharedCodexAppServerService>()
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) }
}
}
}
@@ -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()
}
}
@@ -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
}
@@ -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<CodexBackendThread>
/**
* 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<String>): Map<String, List<CodexBackendThread>> = emptyMap()
}
@@ -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()
}
}
@@ -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<CodexThread> {
internal suspend fun listThreads(projectPath: Path): List<CodexThread> {
val cwdFilter = normalizeRootPath(projectPath.invariantSeparatorsPathString)
return client.listThreads(archived = false, cwdFilter = cwdFilter)
}
suspend fun listAllThreads(): List<CodexThread> {
internal suspend fun listAllThreads(): List<CodexThread> {
return client.listThreads(archived = false, cwdFilter = null)
}
@@ -37,3 +37,4 @@ internal class SharedCodexAppServerService(serviceScope: CoroutineScope) {
return thread
}
}
@@ -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<Unit>()
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, "<project />")
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, "<project></project>")
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)
@@ -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<String>) {
Files.createDirectories(file.parent)
Files.write(file, lines)
}
@@ -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
@@ -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
@@ -7,6 +7,7 @@
<content namespace="jetbrains">
<module name="intellij.agent.workbench.claude.common"/>
<module name="intellij.agent.workbench.codex.common"/>
<module name="intellij.agent.workbench.codex.sessions"/>
<module name="intellij.agent.workbench.sessions"/>
<module name="intellij.agent.workbench.chat"/>
</content>
@@ -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
@@ -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",
@@ -46,6 +46,7 @@
<orderEntry type="module" module-name="intellij.platform.ide.util.io" />
<orderEntry type="module" module-name="intellij.agent.workbench.claude.common" />
<orderEntry type="module" module-name="intellij.agent.workbench.codex.common" />
<orderEntry type="module" module-name="intellij.agent.workbench.codex.sessions" />
<orderEntry type="module" module-name="intellij.agent.workbench.chat" />
<orderEntry type="module" module-name="intellij.libraries.jackson" />
<orderEntry type="library" name="jna" level="project" />
@@ -5,6 +5,7 @@
<!-- region Generated dependencies - run `Generate Product Layouts` to regenerate -->
<module name="intellij.agent.workbench.claude.common"/>
<module name="intellij.agent.workbench.codex.common"/>
<module name="intellij.agent.workbench.codex.sessions"/>
<module name="intellij.agent.workbench.chat"/>
<module name="intellij.platform.compose"/>
<!-- endregion -->
@@ -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
@@ -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<CodexBackendThread>
}
@@ -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<AgentSessionThread> {
val workingDirectory = resolveProjectDirectoryFromPath(path) ?: return emptyList()
val codexService = service<SharedCodexAppServerService>()
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<String>): Map<String, List<AgentSessionThread>> {
if (paths.isEmpty()) return emptyMap()
val codexService = service<SharedCodexAppServerService>()
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
}
}
@@ -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`
@@ -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