IJPL-233558 fix after merge

GitOrigin-RevId: 43768e934687f8c2439341b6df14b8e698a1ab23
This commit is contained in:
Vladimir Krivosheev
2026-02-13 21:24:05 +00:00
committed by intellij-monorepo-bot
parent 6396c310d6
commit 2fb1cd6085
14 changed files with 165 additions and 159 deletions
@@ -283,7 +283,7 @@ class CodexAppServerClient(
val configuredExecutable = executablePathProvider()
?.trim()
?.takeIf { it.isNotEmpty() }
val executable = configuredExecutable ?: CODEX_COMMAND
val executable = configuredExecutable ?: CodexCliUtils.CODEX_COMMAND
val process = try {
GeneralCommandLine(executable, "app-server")
.withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE)
@@ -295,7 +295,6 @@ class CodexAppServerClient(
}
}
.createProcess()
.createProcess()
}
catch (t: Throwable) {
if (configuredExecutable == null && isExecutableNotFound(t)) {
+1 -1
View File
@@ -82,4 +82,4 @@ jps_test(
name = "sessions_test",
runtime_deps = [":sessions_test_lib"]
)
### auto-generated section `test intellij.agent.workbench.sessions` end
### auto-generated section `test intellij.agent.workbench.sessions` end
@@ -41,7 +41,6 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.nio.file.InvalidPathException
import java.nio.file.Path
import java.util.UUID
import kotlin.io.path.invariantSeparatorsPathString
import kotlin.io.path.name
@@ -49,7 +48,7 @@ private val LOG = logger<AgentSessionsService>()
private const val SUPPRESS_BRANCH_MISMATCH_DIALOG_KEY = "agent.workbench.suppress.branch.mismatch.dialog"
private const val OPEN_PROJECT_ACTION_KEY_PREFIX = "project-open"
private const val CREATE_THREAD_ACTION_KEY_PREFIX = "thread-create"
private const val CREATE_SESSION_ACTION_KEY_PREFIX = "session-create"
private const val OPEN_THREAD_ACTION_KEY_PREFIX = "thread-open"
private const val OPEN_SUB_AGENT_ACTION_KEY_PREFIX = "subagent-open"
@@ -337,26 +336,6 @@ internal class AgentSessionsService private constructor(
}
}
fun createAndOpenThread(path: String, currentProject: Project? = null) {
val normalized = normalizePath(path)
val key = buildCreateThreadActionKey(normalized)
actionGate.launch(
scope = serviceScope,
key = key,
policy = SingleFlightPolicy.DROP,
progress = dedicatedFrameOpenProgressRequest(currentProject),
onDrop = { LOG.debug("Dropped duplicate create thread action for $normalized") },
) {
val freshThread = createFreshCodexThread()
openChat(
path = normalized,
thread = freshThread,
subAgent = null,
shellCommandOverride = buildAgentSessionNewCommand(AgentSessionProvider.CODEX),
)
}
}
fun showMoreProjects() {
mutableState.update { it.copy(visibleProjectCount = it.visibleProjectCount + DEFAULT_VISIBLE_PROJECT_COUNT) }
}
@@ -430,9 +409,23 @@ internal class AgentSessionsService private constructor(
}
}
fun createNewSession(path: String, provider: AgentSessionProvider, yolo: Boolean = false) {
serviceScope.launch {
val normalized = normalizePath(path)
fun createNewSession(
path: String,
provider: AgentSessionProvider,
yolo: Boolean = false,
currentProject: Project? = null,
) {
val normalized = normalizePath(path)
val key = buildCreateSessionActionKey(normalized, provider, yolo)
actionGate.launch(
scope = serviceScope,
key = key,
policy = SingleFlightPolicy.DROP,
progress = dedicatedFrameOpenProgressRequest(currentProject),
onDrop = {
LOG.debug("Dropped duplicate create session action for $normalized:$provider:yolo=$yolo")
},
) {
service<AgentSessionsTreeUiStateService>().setLastUsedProvider(provider)
val identity: String
@@ -639,8 +632,8 @@ internal class AgentSessionsService private constructor(
return "$OPEN_PROJECT_ACTION_KEY_PREFIX:$path"
}
private fun buildCreateThreadActionKey(path: String): String {
return "$CREATE_THREAD_ACTION_KEY_PREFIX:$path"
private fun buildCreateSessionActionKey(path: String, provider: AgentSessionProvider, yolo: Boolean): String {
return "$CREATE_SESSION_ACTION_KEY_PREFIX:$path:$provider:yolo=$yolo"
}
private fun buildOpenThreadActionKey(path: String, thread: AgentSessionThread): String {
@@ -1126,17 +1119,6 @@ internal class AgentSessionsService private constructor(
return null
}
private fun createFreshCodexThread(): AgentSessionThread {
return AgentSessionThread(
id = "new-${UUID.randomUUID()}",
title = AgentSessionsBundle.message("toolwindow.provider.codex"),
updatedAt = System.currentTimeMillis(),
archived = false,
activity = AgentSessionActivity.PROCESSING,
provider = AgentSessionProvider.CODEX,
)
}
internal data class ProjectEntry(
val path: String,
val name: String,
@@ -50,15 +50,14 @@ internal fun agentSessionsToolWindow(currentProject: Project) {
state = state,
onRefresh = { service.refresh() },
onOpenProject = { service.openOrFocusProject(it) },
onCreateThread = { path -> service.createAndOpenThread(path, currentProject) },
onProjectExpanded = { service.loadProjectThreadsOnDemand(it) },
onWorktreeExpanded = { projectPath, worktreePath ->
service.loadWorktreeThreadsOnDemand(projectPath, worktreePath)
},
onOpenThread = { path, thread -> service.openChatThread(path, thread, currentProject) },
onOpenSubAgent = { path, thread, subAgent -> service.openChatSubAgent(path, thread, subAgent, currentProject) },
onCreateSession = { path, provider, yolo -> service.createNewSession(path, provider, yolo) },
lastUsedProvider = lastUsedProvider,
onOpenThread = { path, thread -> service.openChatThread(path, thread, currentProject) },
onOpenSubAgent = { path, thread, subAgent -> service.openChatSubAgent(path, thread, subAgent, currentProject) },
onCreateSession = { path, provider, yolo -> service.createNewSession(path, provider, yolo, currentProject) },
lastUsedProvider = lastUsedProvider,
visibleProjectCount = state.visibleProjectCount,
onShowMoreProjects = { service.showMoreProjects() },
visibleThreadCounts = state.visibleThreadCounts,
@@ -72,7 +71,6 @@ internal fun agentSessionsToolWindowContent(
state: AgentSessionsState,
onRefresh: () -> Unit,
onOpenProject: (String) -> Unit,
onCreateThread: (String) -> Unit = {},
onProjectExpanded: (String) -> Unit = {},
onWorktreeExpanded: (String, String) -> Unit = { _, _ -> },
onOpenThread: (String, AgentSessionThread) -> Unit = { _, _ -> },
@@ -98,7 +96,6 @@ internal fun agentSessionsToolWindowContent(
projects = state.projects,
onRefresh = onRefresh,
onOpenProject = onOpenProject,
onCreateThread = onCreateThread,
onProjectExpanded = onProjectExpanded,
onWorktreeExpanded = onWorktreeExpanded,
onOpenThread = onOpenThread,
@@ -4,10 +4,7 @@ import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.intellij.agent.workbench.chat.AgentChatTabSelection
import org.jetbrains.jewel.foundation.ExperimentalJewelApi
@@ -21,15 +18,12 @@ import org.jetbrains.jewel.ui.component.styling.LazyTreeMetrics
import org.jetbrains.jewel.ui.component.styling.LazyTreeStyle
import org.jetbrains.jewel.ui.theme.treeStyle
private const val PROJECT_CLICK_SUPPRESSION_MS = 500L
@OptIn(ExperimentalJewelApi::class)
@Composable
internal fun sessionTree(
projects: List<AgentProjectSessions>,
onRefresh: () -> Unit,
onOpenProject: (String) -> Unit,
onCreateThread: (String) -> Unit,
onProjectExpanded: (String) -> Unit,
onWorktreeExpanded: (String, String) -> Unit = { _, _ -> },
onOpenThread: (String, AgentSessionThread) -> Unit,
@@ -78,27 +72,6 @@ internal fun sessionTree(
val tree = remember(projects, visibleProjectCount, visibleThreadCounts) {
buildSessionTree(projects, visibleProjectCount, visibleThreadCounts)
}
var suppressedProjectClick by remember { mutableStateOf<SuppressedProjectClick?>(null) }
fun suppressNextProjectClick(path: String) {
suppressedProjectClick = SuppressedProjectClick(
path = path,
expiresAt = System.currentTimeMillis() + PROJECT_CLICK_SUPPRESSION_MS,
)
}
fun shouldSuppressProjectClick(path: String): Boolean {
val suppression = suppressedProjectClick ?: return false
if (System.currentTimeMillis() > suppression.expiresAt) {
suppressedProjectClick = null
return false
}
if (suppression.path != path) {
return false
}
suppressedProjectClick = null
return true
}
val treeStyle = run {
val baseStyle = JewelTheme.treeStyle
@@ -127,9 +100,7 @@ internal fun sessionTree(
onElementClick = { element ->
when (val node = element.data) {
is SessionTreeNode.Project -> {
if (!shouldSuppressProjectClick(node.project.path)) {
onOpenProject(node.project.path)
}
onOpenProject(node.project.path)
}
is SessionTreeNode.Thread -> {
val path = when (val id = element.id) {
@@ -166,10 +137,6 @@ internal fun sessionTree(
sessionTreeNodeContent(
element = element,
onOpenProject = onOpenProject,
onCreateThread = { path ->
suppressNextProjectClick(path)
onCreateThread(path)
},
onRefresh = onRefresh,
onCreateSession = onCreateSession,
lastUsedProvider = lastUsedProvider,
@@ -179,11 +146,6 @@ internal fun sessionTree(
}
}
private data class SuppressedProjectClick(
val path: String,
val expiresAt: Long,
)
private fun buildSessionTree(
projects: List<AgentProjectSessions>,
visibleProjectCount: Int,
@@ -3,7 +3,6 @@ package com.intellij.agent.workbench.sessions
import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
@@ -25,8 +24,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
@@ -37,20 +34,17 @@ import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.foundation.theme.LocalContentColor
import org.jetbrains.jewel.ui.component.CircularProgressIndicator
import org.jetbrains.jewel.ui.component.ContextMenuItemOption
import org.jetbrains.jewel.ui.component.Icon
import org.jetbrains.jewel.ui.component.OutlinedButton
import org.jetbrains.jewel.ui.component.Text
import org.jetbrains.jewel.ui.component.Tooltip
import org.jetbrains.jewel.ui.component.search.highlightSpeedSearchMatches
import org.jetbrains.jewel.ui.component.search.highlightTextSearch
import org.jetbrains.jewel.ui.icons.AllIconsKeys
@OptIn(ExperimentalJewelApi::class)
@Composable
internal fun SelectableLazyItemScope.sessionTreeNodeContent(
element: Tree.Element<SessionTreeNode>,
onOpenProject: (String) -> Unit,
onCreateThread: (String) -> Unit,
onRefresh: () -> Unit,
onCreateSession: (String, AgentSessionProvider, Boolean) -> Unit = { _, _, _ -> },
lastUsedProvider: AgentSessionProvider? = null,
@@ -61,7 +55,6 @@ internal fun SelectableLazyItemScope.sessionTreeNodeContent(
is SessionTreeNode.Project -> projectNodeRow(
project = node.project,
onOpenProject = onOpenProject,
onCreateThread = onCreateThread,
onCreateSession = onCreateSession,
lastUsedProvider = lastUsedProvider,
)
@@ -138,8 +131,6 @@ private fun rememberTreeRowChrome(
private fun SelectableLazyItemScope.projectNodeRow(
project: AgentProjectSessions,
onOpenProject: (String) -> Unit,
onCreateThread: (String) -> Unit,
onCreateThread: (String) -> Unit,
onCreateSession: (String, AgentSessionProvider, Boolean) -> Unit,
lastUsedProvider: AgentSessionProvider?,
) {
@@ -203,14 +194,7 @@ private fun SelectableLazyItemScope.projectNodeRow(
NewSessionHoverActions(
path = project.path,
lastUsedProvider = lastUsedProvider,
onCreateSession = { path, provider, yolo ->
if (provider == AgentSessionProvider.CODEX && !yolo && lastUsedProvider == null) {
onCreateThread(path)
}
else {
onCreateSession(path, provider, yolo)
}
},
onCreateSession = onCreateSession,
popupVisible = newSessionPopupVisible,
onPopupVisibleChange = { newSessionPopupVisible = it },
)
@@ -1,41 +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.CodexAppServerClient
import com.intellij.agent.workbench.codex.common.CodexAppServerException
import com.intellij.agent.workbench.sessions.codex.CodexProjectSessionService
import com.intellij.agent.workbench.sessions.codex.resolveProjectDirectoryFromPath
import com.intellij.openapi.project.Project
import kotlinx.coroutines.CoroutineScope
@Suppress("unused")
internal class CodexAppServerSessionBackend(
private val coroutineScope: CoroutineScope,
) : CodexSessionBackend {
override suspend fun listThreads(path: String, openProject: Project?): List<CodexBackendThread> {
if (openProject != null) {
return listThreadsFromOpenProject(openProject)
}
return listThreadsFromClosedPath(path)
}
private suspend fun listThreadsFromOpenProject(project: Project): List<CodexBackendThread> {
val service = project.getService(CodexProjectSessionService::class.java)
if (service == null || !service.hasWorkingDirectory()) {
throw CodexAppServerException("Project directory is not available")
}
return service.listThreads().map(::CodexBackendThread)
}
private suspend fun listThreadsFromClosedPath(path: String): List<CodexBackendThread> {
val workingDirectory = resolveProjectDirectoryFromPath(path)
?: return emptyList()
val client = CodexAppServerClient(coroutineScope = coroutineScope, workingDirectory = workingDirectory)
try {
return client.listThreads(archived = false).map(::CodexBackendThread)
}
finally {
client.shutdown()
}
}
}
@@ -100,8 +100,10 @@ class AgentSessionsToolWindowTest {
}
@Test
fun hoveringProjectRowShowsNewThreadActionAndDoesNotInvokeOpenCallback() {
var createdPath: String? = null
fun hoveringProjectRowShowsQuickCreateSessionActionAndDoesNotInvokeOpenCallback() {
var createdSessionPath: String? = null
var createdSessionProvider: AgentSessionProvider? = null
var createdSessionYolo: Boolean? = null
var openedPath: String? = null
val projectPath = "/work/project-plus"
val projects = listOf(
@@ -117,23 +119,30 @@ class AgentSessionsToolWindowTest {
state = AgentSessionsState(projects = projects),
onRefresh = {},
onOpenProject = { openedPath = it },
onCreateThread = { createdPath = it },
onCreateSession = { path, provider, yolo ->
createdSessionPath = path
createdSessionProvider = provider
createdSessionYolo = yolo
},
lastUsedProvider = AgentSessionProvider.CLAUDE,
)
}
val newThreadLabel = AgentSessionsBundle.message("toolwindow.action.new.thread")
composeRule.onAllNodesWithContentDescription(newThreadLabel).assertCountEquals(0)
val quickActionLabel = AgentSessionsBundle.message("toolwindow.provider.claude")
composeRule.onAllNodesWithContentDescription(quickActionLabel).assertCountEquals(0)
composeRule.onNodeWithText("Project Plus")
.assertIsDisplayed()
.performMouseInput { moveTo(center) }
composeRule.onNodeWithContentDescription(newThreadLabel)
composeRule.onNodeWithContentDescription(quickActionLabel)
.assertIsDisplayed()
.performClick()
composeRule.runOnIdle {
assertThat(createdPath).isEqualTo(projectPath)
assertThat(createdSessionPath).isEqualTo(projectPath)
assertThat(createdSessionProvider).isEqualTo(AgentSessionProvider.CLAUDE)
assertThat(createdSessionYolo).isFalse()
assertThat(openedPath).isNull()
}
}
@@ -215,10 +215,21 @@ private fun resolveTestClasspath(): String {
.map(String::trim)
.filter(String::isNotEmpty)
if (entries.isNotEmpty()) {
return entries.joinToString(File.pathSeparator)
return absolutizeClasspathEntries(entries)
}
}
return System.getProperty("java.class.path")
val classpath = System.getProperty("java.class.path")
val entries = classpath.split(File.pathSeparator)
.map(String::trim)
.filter(String::isNotEmpty)
return absolutizeClasspathEntries(entries)
}
private fun absolutizeClasspathEntries(entries: List<String>): String {
return entries.joinToString(File.pathSeparator) { entry ->
val path = Path.of(entry)
if (path.isAbsolute) entry else path.toAbsolutePath().normalize().toString()
}
}
private fun writeAppServerArgsFile(tempDir: Path, classpath: String, configPath: Path): Path {
@@ -20,11 +20,17 @@ import org.jetbrains.jewel.foundation.theme.ThemeDefinition
import org.jetbrains.jewel.foundation.theme.ThemeIconData
import org.jetbrains.jewel.intui.standalone.theme.default
import org.jetbrains.jewel.ui.ComponentStyling
import org.jetbrains.jewel.ui.LocalMenuItemShortcutHintProvider
import org.jetbrains.jewel.ui.LocalMenuItemShortcutProvider
import org.jetbrains.jewel.ui.LocalTypography
import org.jetbrains.jewel.ui.MenuItemShortcutHintProvider
import org.jetbrains.jewel.ui.MenuItemShortcutProvider
import org.jetbrains.jewel.ui.Typography
import org.jetbrains.jewel.ui.component.ContextMenuItemOptionAction
import org.jetbrains.jewel.ui.icon.LocalNewUiChecker
import org.jetbrains.jewel.ui.icon.NewUiChecker
import org.jetbrains.jewel.ui.theme.BaseJewelTheme
import javax.swing.KeyStroke
internal fun ComposeContentTestRule.setContentWithTheme(content: @Composable () -> Unit) {
setContent {
@@ -32,6 +38,8 @@ internal fun ComposeContentTestRule.setContentWithTheme(content: @Composable ()
CompositionLocalProvider(
LocalTypography provides TestTypography,
LocalNewUiChecker provides TestNewUiChecker,
LocalMenuItemShortcutProvider provides EmptyMenuItemShortcutProvider,
LocalMenuItemShortcutHintProvider provides EmptyMenuItemShortcutHintProvider,
) {
content()
}
@@ -131,3 +139,11 @@ private object TestTypography : Typography {
private object TestNewUiChecker : NewUiChecker {
override fun isNewUi(): Boolean = true
}
private object EmptyMenuItemShortcutProvider : MenuItemShortcutProvider {
override fun getShortcutKeyStroke(actionType: ContextMenuItemOptionAction): KeyStroke? = null
}
private object EmptyMenuItemShortcutHintProvider : MenuItemShortcutHintProvider {
override fun getShortcutHint(actionType: ContextMenuItemOptionAction): String = ""
}
@@ -0,0 +1,84 @@
---
name: Agent Sessions New-Session Actions
description: Requirements for project/worktree new-session actions and provider-specific new-session routing in Agent Threads.
targets:
- ../../sessions/src/SessionTreeNewSessionActions.kt
- ../../sessions/src/SessionTreeRows.kt
- ../../sessions/src/AgentSessionsToolWindow.kt
- ../../sessions/src/AgentSessionsService.kt
- ../../sessions/src/codex/SharedCodexAppServerService.kt
- ../../sessions/src/AgentSessionCli.kt
- ../../sessions/src/providers/codex/CodexCliCommands.kt
- ../../sessions/resources/messages/AgentSessionsBundle.properties
- ../../sessions/testSrc/AgentSessionsToolWindowTest.kt
- ../../sessions/testSrc/AgentSessionCliTest.kt
- ../../sessions/testSrc/CodexAppServerClientTest.kt
---
# Agent Sessions New-Session Actions
Status: Draft
Date: 2026-02-13
## Summary
Define project/worktree `New Session` actions (`+` and quick-provider icon) and map each provider + mode to the chat-open flow.
## Goals
- Keep session-creation behavior identical across project and worktree rows.
- Keep provider/mode semantics explicit and testable.
- Avoid reintroducing legacy Codex "fresh session without thread id" flow.
## Non-goals
- Thread loading/sorting/paging behavior.
- Dedicated-frame routing rules outside new-session open progress integration.
- New user-facing warning text for `Codex (Full Auto)`.
## Requirements
- Project/worktree row hover actions must route through `onCreateSession(path, provider, yolo)`; no separate create-thread callback path may be used.
[@test] ../../sessions/testSrc/AgentSessionsToolWindowTest.kt
- Quick provider icon (when `lastUsedProvider` exists) must create a session with `yolo=false`.
- Popup must expose four selectable entries: Claude, Codex, Claude YOLO, Codex YOLO.
- Popup section label remains `YOLO`; Codex YOLO entry label remains `Codex (Full Auto)`.
- `AgentSessionsService.createNewSession(path, provider, yolo, currentProject)` is the service entry point for row actions.
- `createNewSession` must deduplicate in-flight actions by normalized `path + provider + yolo` with single-flight `DROP`.
- `createNewSession` must set `lastUsedProvider` to the selected provider before opening chat.
- Claude `yolo=false` new-session command must be `claude`.
- Claude `yolo=true` new-session command must be `claude --dangerously-skip-permissions`.
- Codex new-session must not use direct `codex`/`--full-auto` command construction for session creation.
[@test] ../../sessions/testSrc/AgentSessionCliTest.kt
- Codex new-session must call app-server `thread/start`, persist the new thread id, then open chat with `codex resume <threadId>`.
- Codex `yolo=true` must call `thread/start` with `approvalPolicy="on-request"` and `sandbox="workspace-write"`.
- Codex `yolo=false` must call `thread/start` with default app-server parameters (no forced approval/sandbox overrides).
[@test] ../../sessions/testSrc/CodexAppServerClientTest.kt
- `Codex (Full Auto)` semantics are documented in spec and do not require extra warning text in UI.
## User Experience
- Hovering a project/worktree row reveals new-session controls without opening the project.
- The quick-provider icon provides one-click repeat creation.
- Popup grouping keeps normal and YOLO choices explicit.
## Data & Backend
- Codex action flow uses `SharedCodexAppServerService.createThread(cwd, yolo)` and persists created threads before resume.
- Codex new-session identity is concrete (`CODEX:<threadId>`), not synthetic `new-*`.
## Error Handling
- Provider CLI/app-server failures must continue using provider-specific error paths in existing service flow.
- Duplicate clicks on the same action tuple must be dropped rather than opening multiple sessions.
## Testing / Local Run
- `./tests.cmd '-Dintellij.build.test.patterns=com.intellij.agent.workbench.sessions.AgentSessionsToolWindowTest'`
- `./tests.cmd '-Dintellij.build.test.patterns=com.intellij.agent.workbench.sessions.AgentSessionCliTest'`
- `./tests.cmd '-Dintellij.build.test.patterns=com.intellij.agent.workbench.sessions.CodexAppServerClientTest'`
## Open Questions / Risks
- `createNewSession` dedup + yolo parameter mapping currently has limited direct service-level test coverage.
## References
- `../agent-sessions.spec.md`
- `../agent-sessions-codex-rollout-source.spec.md`
- `../agent-dedicated-frame.spec.md`
@@ -54,7 +54,7 @@ Define dedicated-frame behavior for Agent chat opening. By default, chat opens i
- Chat resume command remains provider-specific in dedicated mode as in current-project mode:
- Codex: `codex resume <threadId>`
- Claude: `claude --resume <threadId>`
- Codex fresh-thread opens (project-row `New Thread`) use `codex` without `resume`.
- New-session action semantics (including Codex `Codex (Full Auto)` mapping) are defined in `spec/actions/new-thread.spec.md` and do not change between dedicated/current-project modes.
[@test] ../sessions/testSrc/AgentSessionsGearActionsTest.kt
[@test] ../sessions/testSrc/AgentSessionsOpenModeRoutingTest.kt
@@ -94,4 +94,5 @@ Define dedicated-frame behavior for Agent chat opening. By default, chat opens i
## References
- `spec/agent-chat-editor.spec.md`
- `spec/agent-sessions.spec.md`
- `spec/actions/new-thread.spec.md`
- `community/platform/platform-impl/src/com/intellij/openapi/wm/ex/WelcomeScreenProjectProvider.kt`
@@ -21,7 +21,7 @@ Codex thread discovery for Agent Threads defaults to rollout files under `~/.cod
- Make Codex thread indicators reflect real activity based on rollout data.
- Keep app-server implementation in code as an alternate backend.
- Avoid archived-session handling in this iteration.
- Fix Codex `+` action to open fresh CLI chat (`codex`) without pre-creating a session.
- Keep rollout backend changes independent from new-session action semantics.
## Non-goals
- Archived session browsing or unarchive actions.
@@ -41,7 +41,7 @@ Codex thread discovery for Agent Threads defaults to rollout files under `~/.cod
- `reviewing`: teal (`#2FD1C4`)
- `processing`: orange (`#FF9F43`)
- `ready`: green (`#3FE47E`)
- Codex project-row `+` action must open a fresh chat command `codex` (no `codex resume <id>`).
- New-session action semantics (including Codex `Codex (Full Auto)` parameters) are defined in `spec/actions/new-thread.spec.md` and are backend-invariant.
- Existing thread open behavior remains `codex resume <threadId>`.
## Data & Backend
@@ -57,3 +57,4 @@ Codex thread discovery for Agent Threads defaults to rollout files under `~/.cod
## References
- `spec/agent-sessions.spec.md`
- `spec/agent-chat-editor.spec.md`
- `spec/actions/new-thread.spec.md`
@@ -14,7 +14,7 @@ targets:
# Agent Threads Tool Window
Status: Draft
Date: 2026-02-11
Date: 2026-02-13
## Summary
Define the Agent Threads tool window as a provider-agnostic, project-scoped session browser. Threads from supported providers are aggregated per project/worktree, rendered in one tree, and opened through a shared chat routing flow.
@@ -46,7 +46,7 @@ Define the Agent Threads tool window as a provider-agnostic, project-scoped sess
- Resume command must be provider-specific:
- Codex: `codex resume <sessionId>`
- Claude: `claude --resume <sessionId>`
- Codex project-row `New Thread` action must open fresh CLI chat with `codex` (without pre-creating/resuming a session id).
- New-session action behavior (provider options, Codex/Claude command mapping, and Full Auto semantics) is defined in `spec/actions/new-thread.spec.md` and must be used by both project and worktree rows.
- Branch mismatch between thread origin and current worktree branch must show a warning confirmation before opening chat.
[@test] ../sessions/testSrc/AgentSessionLoadAggregationTest.kt
@@ -86,3 +86,4 @@ Define the Agent Threads tool window as a provider-agnostic, project-scoped sess
- `spec/agent-sessions-thread-visibility.spec.md`
- `spec/agent-dedicated-frame.spec.md`
- `spec/agent-chat-editor.spec.md`
- `spec/actions/new-thread.spec.md`