mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-248623 rename Agent Workbench chat UI to thread view
Replace the Agent Workbench product/API terminology that exposed chat as a top-level surface with Agent Thread View naming. This keeps the UI aligned with the persisted agent thread model and avoids mixing terminal/editor surfaces with an interaction paradigm. The change intentionally drops legacy chat compatibility names, updates extension points, module names, API dumps, specs, and generated project metadata, while retaining tab wording only where it refers to IntelliJ editor-tab mechanics. GitOrigin-RevId: 14fce40e99d40f6303e969d0b9394a801816bcf8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4de1c0d870
commit
90d5ef0959
Generated
+4
-4
@@ -49,16 +49,14 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/fleet/util/serialization/fleet.util.serialization.iml" filepath="$PROJECT_DIR$/fleet/util/serialization/fleet.util.serialization.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/ai-review/intellij.agent.workbench.ai.review.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/ai-review/intellij.agent.workbench.ai.review.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/ai-review/intellij.agent.workbench.ai.review.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/ai-review/intellij.agent.workbench.ai.review.tests.iml" />
|
||||
<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/chat/intellij.agent.workbench.chat.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/chat/intellij.agent.workbench.chat.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/claude/awb/intellij.agent.workbench.claude.awb.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/claude/awb/intellij.agent.workbench.claude.awb.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/claude/awb/intellij.agent.workbench.claude.awb.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/claude/awb/intellij.agent.workbench.claude.awb.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/codex/chat/intellij.agent.workbench.codex.chat.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/codex/chat/intellij.agent.workbench.codex.chat.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/codex/chat/intellij.agent.workbench.codex.chat.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/codex/chat/intellij.agent.workbench.codex.chat.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/codex/ide/intellij.agent.workbench.codex.ide.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/codex/ide/intellij.agent.workbench.codex.ide.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/codex/ide/intellij.agent.workbench.codex.ide.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/codex/ide/intellij.agent.workbench.codex.ide.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/codex/prompt-suggestions/intellij.agent.workbench.codex.prompt.suggestions.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/codex/prompt-suggestions/intellij.agent.workbench.codex.prompt.suggestions.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/codex/prompt-suggestions/intellij.agent.workbench.codex.prompt.suggestions.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/codex/prompt-suggestions/intellij.agent.workbench.codex.prompt.suggestions.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/codex/thread-view/intellij.agent.workbench.codex.thread.view.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/codex/thread-view/intellij.agent.workbench.codex.thread.view.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/codex/thread-view/intellij.agent.workbench.codex.thread.view.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/codex/thread-view/intellij.agent.workbench.codex.thread.view.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/engine/intellij.agent.workbench.engine.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/engine/intellij.agent.workbench.engine.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/engine/intellij.agent.workbench.engine.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/engine/intellij.agent.workbench.engine.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/pi/awb/intellij.agent.workbench.pi.awb.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/pi/awb/intellij.agent.workbench.pi.awb.iml" />
|
||||
@@ -90,6 +88,8 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/sessions-toolwindow/intellij.agent.workbench.sessions.toolwindow.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/sessions-toolwindow/intellij.agent.workbench.sessions.toolwindow.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/settings/intellij.agent.workbench.settings.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/settings/intellij.agent.workbench.settings.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/settings/intellij.agent.workbench.settings.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/settings/intellij.agent.workbench.settings.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/thread-view/intellij.agent.workbench.thread.view.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/thread-view/intellij.agent.workbench.thread.view.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/thread-view/intellij.agent.workbench.thread.view.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/thread-view/intellij.agent.workbench.thread.view.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/ui/intellij.agent.workbench.ui.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/ui/intellij.agent.workbench.ui.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/ui/intellij.agent.workbench.ui.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/ui/intellij.agent.workbench.ui.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/vcs-merge/intellij.agent.workbench.vcs.merge.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/vcs-merge/intellij.agent.workbench.vcs.merge.iml" />
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<compilerArguments>
|
||||
<stringArguments>
|
||||
<stringArg name="jvmTarget" arg="25" />
|
||||
<stringArg name="apiVersion" arg="2.3" />
|
||||
<stringArg name="languageVersion" arg="2.3" />
|
||||
<stringArg name="apiVersion" arg="2.4" />
|
||||
<stringArg name="languageVersion" arg="2.4" />
|
||||
</stringArguments>
|
||||
<arrayArguments>
|
||||
<arrayArg name="pluginClasspaths">
|
||||
@@ -25,35 +25,33 @@
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="com.intellij.agent.workbench.acp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.frontend" />
|
||||
<orderEntry type="module" module-name="intellij.aiCodeReview" />
|
||||
<orderEntry type="module" module-name="intellij.aiCodeReview.vcs" />
|
||||
<orderEntry type="module" module-name="intellij.aiCodeReview.ui.shared" />
|
||||
<orderEntry type="module" module-name="intellij.aiCodeReview.ui.frontend" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.kotlinx.serialization.core" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.kotlinx.serialization.json" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.analysis" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.projectModel" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core.ui" />
|
||||
<orderEntry type="module" module-name="intellij.platform.editor.ui" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.execution.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.projectModel" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util" />
|
||||
<orderEntry type="module" module-name="intellij.platform.diff" />
|
||||
<orderEntry type="module" module-name="intellij.platform.diff.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.diff" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.kotlinx.coroutines.core" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.kotlinx.serialization.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.vcs" />
|
||||
<orderEntry type="module" module-name="intellij.platform.vcs.impl.shared" />
|
||||
<orderEntry type="module" module-name="intellij.platform.vcs.shared" />
|
||||
<orderEntry type="module" module-name="intellij.platform.vcs.log" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.ui" />
|
||||
<orderEntry type="module" module-name="intellij.platform.acp" />
|
||||
<orderEntry type="module" module-name="intellij.platform.eel" />
|
||||
<orderEntry type="module" module-name="intellij.terminal.frontend" />
|
||||
<orderEntry type="module" module-name="intellij.terminal" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.acp.runtime" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.engine" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.sessions.core" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.toolwindow" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.thread.view" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.ui" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -42,7 +42,7 @@ jvm_library(
|
||||
"//plugins/agent-workbench/lib-agent/core",
|
||||
"//plugins/agent-workbench/prompt/core",
|
||||
"//plugins/agent-workbench/lib-agent/sessions-core:ai-agent-sessions-core",
|
||||
"//plugins/agent-workbench/chat",
|
||||
"//plugins/agent-workbench/thread-view:agent-workbench-thread-view",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -72,7 +72,7 @@ jvm_library(
|
||||
"//plugins/agent-workbench/lib-agent/core:core_test_lib",
|
||||
"//plugins/agent-workbench/prompt/core:core_test_lib",
|
||||
"//plugins/agent-workbench/lib-agent/sessions-core:ai-agent-sessions-core_test_lib",
|
||||
"//plugins/agent-workbench/chat:chat_test_lib",
|
||||
"//plugins/agent-workbench/thread-view:agent-workbench-thread-view_test_lib",
|
||||
],
|
||||
)
|
||||
### auto-generated section `build intellij.agent.workbench.engine` end
|
||||
|
||||
@@ -48,6 +48,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.core" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.prompt.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.sessions.core" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.chat" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.thread.view" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -5,7 +5,7 @@
|
||||
<module name="intellij.platform.ai.agent.core"/>
|
||||
<module name="intellij.agent.workbench.prompt.core"/>
|
||||
<module name="intellij.platform.ai.agent.sessions.core"/>
|
||||
<module name="intellij.agent.workbench.chat"/>
|
||||
<module name="intellij.agent.workbench.thread.view"/>
|
||||
</dependencies>
|
||||
|
||||
<extensionPoints>
|
||||
|
||||
@@ -22,21 +22,18 @@
|
||||
- name: lib/modules/intellij.agent.workbench.ai.review.space.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.ai.review.space
|
||||
- name: lib/modules/intellij.agent.workbench.chat.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.chat
|
||||
- name: lib/modules/intellij.agent.workbench.claude.awb.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.claude.awb
|
||||
- name: lib/modules/intellij.agent.workbench.codex.chat.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.codex.chat
|
||||
- name: lib/modules/intellij.agent.workbench.codex.ide.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.codex.ide
|
||||
- name: lib/modules/intellij.agent.workbench.codex.prompt.suggestions.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.codex.prompt.suggestions
|
||||
- name: lib/modules/intellij.agent.workbench.codex.thread.view.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.codex.thread.view
|
||||
- name: lib/modules/intellij.agent.workbench.container.eel.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.container.eel
|
||||
@@ -98,6 +95,9 @@
|
||||
- name: lib/modules/intellij.agent.workbench.settings.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.settings
|
||||
- name: lib/modules/intellij.agent.workbench.thread.view.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.thread.view
|
||||
- name: lib/modules/intellij.agent.workbench.ui.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.ui
|
||||
|
||||
@@ -23,8 +23,8 @@ import com.intellij.openapi.application.UI
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.platform.ai.agent.common.session.isClaudeMenuCommandPrompt
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
@@ -33,6 +33,7 @@ import com.intellij.platform.ai.agent.sessions.core.providers.AGENT_PROMPT_PROVI
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOption
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionMenuCommand
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderDescriptor
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionSource
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
|
||||
@@ -682,7 +683,7 @@ class AgentPromptPaletteSubmitControllerTest {
|
||||
updatedAt = 100,
|
||||
archived = false,
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
activity = AgentThreadActivity.PROCESSING,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.PROCESSING),
|
||||
subAgents = emptyList(),
|
||||
)
|
||||
),
|
||||
@@ -790,6 +791,12 @@ class AgentPromptPaletteSubmitControllerTest {
|
||||
override val displayNameKey: String = if (provider == AgentSessionProvider.from("claude")) "provider.claude" else "provider.codex"
|
||||
override val newSessionLabelKey: String = displayNameKey
|
||||
override val promptOptions: List<AgentPromptProviderOption> = promptOptions
|
||||
override val menuCommands: List<AgentSessionMenuCommand> = if (provider == AgentSessionProvider.from("claude")) {
|
||||
listOf(AgentSessionMenuCommand("/mcp"), AgentSessionMenuCommand("/rename", "[title]"))
|
||||
}
|
||||
else {
|
||||
emptyList()
|
||||
}
|
||||
override val sessionSource: AgentSessionSource
|
||||
get() = error("Not required for this test")
|
||||
override val cliMissingMessageKey: String = displayNameKey
|
||||
@@ -808,10 +815,6 @@ class AgentPromptPaletteSubmitControllerTest {
|
||||
override fun buildInitialMessagePlan(request: AgentPromptInitialMessageRequest): AgentInitialMessagePlan {
|
||||
return initialMessagePlanBuilder(request)
|
||||
}
|
||||
|
||||
override fun shouldStripContextForPrompt(prompt: String): Boolean {
|
||||
return this.provider == AgentSessionProvider.from("claude") && prompt.isClaudeMenuCommandPrompt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@ package com.intellij.agent.workbench.sessions.actions
|
||||
// @spec community/plugins/agent-workbench/spec/sessions/agent-terminal-sessions.spec.md
|
||||
// @spec community/plugins/agent-workbench/spec/actions/new-thread.spec.md
|
||||
|
||||
import com.intellij.agent.workbench.chat.AgentChatDeferredStartContent
|
||||
import com.intellij.agent.workbench.chat.installAgentChatDeferredStartContent
|
||||
import com.intellij.agent.workbench.thread.view.AgentThreadViewDeferredStartContent
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptContextEnvelopeFormatter
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptContextItem
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptContextResolverService
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptInitialMessageRequest
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptInvocationData
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptLaunchProfile
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptLaunchRequest
|
||||
@@ -14,9 +17,11 @@ import com.intellij.agent.workbench.prompt.core.AgentPromptLauncherBridge
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptLaunchers
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptProjectPathCandidate
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptReusableSourceEntry
|
||||
import com.intellij.agent.workbench.prompt.ui.emptyState.createAgentWorkbenchInlinePromptEditorHost
|
||||
import com.intellij.agent.workbench.prompt.ui.emptyState.createAgentWorkbenchInlineNewThreadPromptComponent
|
||||
import com.intellij.agent.workbench.sessions.AgentSessionsBundle
|
||||
import com.intellij.agent.workbench.sessions.projectLabelForPath
|
||||
import com.intellij.agent.workbench.sessions.newThreadActionText
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.initialMessageRequestForLaunchProfile
|
||||
import com.intellij.agent.workbench.sessions.statistics.AgentWorkbenchEntryPoint
|
||||
import com.intellij.agent.workbench.sessions.service.AgentDeferredNewSessionHandle
|
||||
@@ -26,21 +31,21 @@ import com.intellij.platform.ai.agent.core.normalizeAgentWorkbenchPath
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderDescriptor
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviders
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.components.serviceAsync
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.registry.RegistryManager
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
internal const val AGENT_WORKBENCH_NEW_THREAD_INLINE_PROMPT_REGISTRY_KEY: String = "agent.workbench.new.thread.inline.prompt"
|
||||
|
||||
@@ -49,11 +54,33 @@ fun createNewThreadViaService(
|
||||
profile: AgentPromptLaunchProfile,
|
||||
currentProject: Project,
|
||||
entryPoint: AgentWorkbenchEntryPoint,
|
||||
) {
|
||||
createNewThreadViaService(
|
||||
path = path,
|
||||
profile = profile,
|
||||
currentProject = currentProject,
|
||||
entryPoint = entryPoint,
|
||||
invocationData = null,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun createNewThreadViaService(
|
||||
path: String,
|
||||
profile: AgentPromptLaunchProfile,
|
||||
currentProject: Project,
|
||||
entryPoint: AgentWorkbenchEntryPoint,
|
||||
invocationData: AgentPromptInvocationData?,
|
||||
) {
|
||||
val provider = AgentSessionProvider.from(profile.providerId)
|
||||
val descriptor = AgentSessionProviders.find(provider)
|
||||
if (!shouldOpenInlineNewThreadPrompt(descriptor)) {
|
||||
createNewThreadDirectly(path = path, profile = profile, currentProject = currentProject, entryPoint = entryPoint)
|
||||
if (!shouldOpenInlineNewThreadPrompt(descriptor = descriptor)) {
|
||||
createNewThreadDirectly(
|
||||
path = path,
|
||||
profile = profile,
|
||||
currentProject = currentProject,
|
||||
entryPoint = entryPoint,
|
||||
invocationData = invocationData,
|
||||
)
|
||||
return
|
||||
}
|
||||
service<AgentSessionsInlineNewThreadPromptService>().openInlinePrompt(
|
||||
@@ -61,11 +88,13 @@ fun createNewThreadViaService(
|
||||
profile = profile,
|
||||
currentProject = currentProject,
|
||||
entryPoint = entryPoint,
|
||||
descriptor = checkNotNull(descriptor),
|
||||
invocationData = invocationData,
|
||||
)
|
||||
}
|
||||
|
||||
private fun shouldOpenInlineNewThreadPrompt(descriptor: AgentSessionProviderDescriptor?): Boolean {
|
||||
private fun shouldOpenInlineNewThreadPrompt(
|
||||
descriptor: AgentSessionProviderDescriptor?,
|
||||
): Boolean {
|
||||
return shouldOpenInlineNewThreadPrompt(
|
||||
registryEnabled = RegistryManager.getInstance().get(AGENT_WORKBENCH_NEW_THREAD_INLINE_PROMPT_REGISTRY_KEY).asBoolean(),
|
||||
descriptor = descriptor,
|
||||
@@ -82,16 +111,60 @@ private fun createNewThreadDirectly(
|
||||
profile: AgentPromptLaunchProfile,
|
||||
currentProject: Project,
|
||||
entryPoint: AgentWorkbenchEntryPoint,
|
||||
invocationData: AgentPromptInvocationData?,
|
||||
) {
|
||||
val normalizedPath = normalizeAgentWorkbenchPath(path)
|
||||
service<AgentSessionLaunchService>().createNewSession(
|
||||
path = path,
|
||||
path = normalizedPath,
|
||||
launchProfileId = profile.id,
|
||||
entryPoint = entryPoint,
|
||||
currentProject = currentProject,
|
||||
initialMessageRequest = initialMessageRequestForLaunchProfile(profile),
|
||||
initialMessageRequest = buildNewThreadInitialMessageRequest(
|
||||
profile = profile,
|
||||
projectPath = normalizedPath,
|
||||
invocationData = invocationData,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun buildNewThreadInitialMessageRequest(
|
||||
profile: AgentPromptLaunchProfile,
|
||||
projectPath: String,
|
||||
invocationData: AgentPromptInvocationData?,
|
||||
collectDefaultContext: (AgentPromptInvocationData) -> List<AgentPromptContextItem> = ::collectDefaultNewThreadContext,
|
||||
): AgentPromptInitialMessageRequest {
|
||||
val baseRequest = initialMessageRequestForLaunchProfile(profile)
|
||||
val contextItems = invocationData?.let(collectDefaultContext).orEmpty()
|
||||
if (contextItems.isEmpty()) {
|
||||
return baseRequest
|
||||
}
|
||||
|
||||
val normalizedProjectPath = normalizeAgentWorkbenchPath(projectPath)
|
||||
val selection = AgentPromptContextEnvelopeFormatter.prepareContextEnvelopeSelection(
|
||||
items = contextItems,
|
||||
softCapChars = AgentPromptContextEnvelopeFormatter.DEFAULT_SOFT_CAP_CHARS,
|
||||
projectPath = normalizedProjectPath,
|
||||
)
|
||||
val preparedSelection = if (selection.exceedsSoftCap) {
|
||||
AgentPromptContextEnvelopeFormatter.autoTrimContextEnvelopeSelection(
|
||||
selection = selection,
|
||||
projectPath = normalizedProjectPath,
|
||||
)
|
||||
}
|
||||
else {
|
||||
selection
|
||||
}
|
||||
return baseRequest.copy(
|
||||
projectPath = normalizedProjectPath,
|
||||
contextItems = preparedSelection.items,
|
||||
contextEnvelopeSummary = preparedSelection.summary,
|
||||
)
|
||||
}
|
||||
|
||||
private fun collectDefaultNewThreadContext(invocationData: AgentPromptInvocationData): List<AgentPromptContextItem> {
|
||||
return invocationData.project.service<AgentPromptContextResolverService>().collectDefaultContext(invocationData)
|
||||
}
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
internal class AgentSessionsInlineNewThreadPromptService internal constructor(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
@@ -101,15 +174,15 @@ internal class AgentSessionsInlineNewThreadPromptService internal constructor(
|
||||
profile: AgentPromptLaunchProfile,
|
||||
currentProject: Project,
|
||||
entryPoint: AgentWorkbenchEntryPoint,
|
||||
descriptor: AgentSessionProviderDescriptor,
|
||||
invocationData: AgentPromptInvocationData?,
|
||||
) {
|
||||
coroutineScope.launch(CoroutineName("Agent Workbench inline New Thread prompt")) {
|
||||
coroutineScope.launch(Dispatchers.EDT + CoroutineName("Agent Workbench inline New Thread prompt")) {
|
||||
openInlinePromptSuspending(
|
||||
path = path,
|
||||
profile = profile,
|
||||
currentProject = currentProject,
|
||||
entryPoint = entryPoint,
|
||||
descriptor = descriptor,
|
||||
invocationData = invocationData,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -119,101 +192,108 @@ internal class AgentSessionsInlineNewThreadPromptService internal constructor(
|
||||
profile: AgentPromptLaunchProfile,
|
||||
currentProject: Project,
|
||||
entryPoint: AgentWorkbenchEntryPoint,
|
||||
descriptor: AgentSessionProviderDescriptor,
|
||||
invocationData: AgentPromptInvocationData?,
|
||||
) {
|
||||
val normalizedPath = normalizeAgentWorkbenchPath(path)
|
||||
val provider = AgentSessionProvider.from(profile.providerId)
|
||||
var promptProject: Project? = null
|
||||
val handleDeferred = CompletableDeferred<AgentDeferredNewSessionHandle>()
|
||||
val handle = try {
|
||||
serviceAsync<AgentSessionLaunchService>().createDeferredNewSession(
|
||||
path = normalizedPath,
|
||||
provider = provider,
|
||||
mode = profile.launchMode,
|
||||
entryPoint = entryPoint,
|
||||
openedChatHandler = { project, _ -> promptProject = project },
|
||||
launchProfileId = profile.id,
|
||||
launchTargetId = profile.launchTargetId,
|
||||
generationSettings = profile.generationSettings,
|
||||
waitingTitle = AgentSessionsBundle.message("toolwindow.thread.preparing.title", providerDisplayName(descriptor)),
|
||||
waitingMessage = AgentSessionsBundle.message("toolwindow.thread.preparing.body"),
|
||||
waitingTitle = AgentSessionsBundle.message("toolwindow.thread.preparing.title"),
|
||||
deferredStartContentProvider = { project ->
|
||||
createInlinePromptContent(
|
||||
project = project,
|
||||
path = normalizedPath,
|
||||
profile = profile,
|
||||
entryPoint = entryPoint,
|
||||
handleDeferred = handleDeferred,
|
||||
invocationData = invocationData,
|
||||
)
|
||||
},
|
||||
).handle
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
handleDeferred.cancel()
|
||||
throw e
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
handleDeferred.cancel()
|
||||
LOG.warn("Failed to open inline New Thread prompt for ${profile.providerId}:$normalizedPath", e)
|
||||
createNewThreadDirectly(path = normalizedPath, profile = profile, currentProject = currentProject, entryPoint = entryPoint)
|
||||
createNewThreadDirectly(
|
||||
path = normalizedPath,
|
||||
profile = profile,
|
||||
currentProject = currentProject,
|
||||
entryPoint = entryPoint,
|
||||
invocationData = invocationData,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (handle == null) {
|
||||
createNewThreadDirectly(path = normalizedPath, profile = profile, currentProject = currentProject, entryPoint = entryPoint)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
installInlinePromptContent(
|
||||
project = promptProject ?: currentProject,
|
||||
handleDeferred.cancel()
|
||||
createNewThreadDirectly(
|
||||
path = normalizedPath,
|
||||
profile = profile,
|
||||
currentProject = currentProject,
|
||||
entryPoint = entryPoint,
|
||||
handle = handle,
|
||||
invocationData = invocationData,
|
||||
)
|
||||
return
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.warn("Failed to install inline New Thread prompt for ${profile.providerId}:$normalizedPath", e)
|
||||
handle.start(initialMessageRequestForLaunchProfile(profile))
|
||||
}
|
||||
handleDeferred.complete(handle)
|
||||
}
|
||||
|
||||
private suspend fun installInlinePromptContent(
|
||||
private fun createInlinePromptContent(
|
||||
project: Project,
|
||||
path: String,
|
||||
profile: AgentPromptLaunchProfile,
|
||||
entryPoint: AgentWorkbenchEntryPoint,
|
||||
handle: AgentDeferredNewSessionHandle,
|
||||
) {
|
||||
withContext(Dispatchers.EDT) {
|
||||
val launcher = InlineNewThreadPromptLauncherBridge(projectPath = path, handle = handle)
|
||||
handleDeferred: CompletableDeferred<AgentDeferredNewSessionHandle>,
|
||||
invocationData: AgentPromptInvocationData?,
|
||||
): AgentThreadViewDeferredStartContent {
|
||||
val launcher = InlineNewThreadPromptLauncherBridge(projectPath = path, handleProvider = { handleDeferred.await() })
|
||||
val component = createAgentWorkbenchInlineNewThreadPromptComponent(
|
||||
project = project,
|
||||
invocationData = AgentPromptInvocationData(
|
||||
project = project,
|
||||
actionId = AgentWorkbenchActionIds.Sessions.MainToolbar.NEW_THREAD,
|
||||
actionText = AgentSessionsBundle.message("action.AgentWorkbenchSessions.MainToolbar.NewThread.text"),
|
||||
actionPlace = entryPoint.name,
|
||||
invokedAtMs = System.currentTimeMillis(),
|
||||
),
|
||||
invocationData = (invocationData ?: defaultNewThreadInvocationData(project, entryPoint, profile.name)).copy(project = project),
|
||||
launcherProvider = { launcher },
|
||||
initialLaunchProfileId = profile.id,
|
||||
)
|
||||
val installed = installAgentChatDeferredStartContent(
|
||||
project = project,
|
||||
file = handle.file,
|
||||
content = AgentChatDeferredStartContent(
|
||||
component = component,
|
||||
preferredFocusedComponent = component.preferredFocusedComponent,
|
||||
disposeContent = { Disposer.dispose(component) },
|
||||
),
|
||||
)
|
||||
if (!installed) {
|
||||
Disposer.dispose(component)
|
||||
throw IllegalStateException("Inline New Thread prompt could not be installed into ${handle.file}")
|
||||
}
|
||||
}
|
||||
return AgentThreadViewDeferredStartContent(
|
||||
component = createAgentWorkbenchInlinePromptEditorHost(component),
|
||||
preferredFocusedComponent = component.preferredFocusedComponent,
|
||||
disposeContent = { Disposer.dispose(component) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun defaultNewThreadInvocationData(
|
||||
project: Project,
|
||||
entryPoint: AgentWorkbenchEntryPoint,
|
||||
profileName: @Nls String,
|
||||
): AgentPromptInvocationData {
|
||||
return AgentPromptInvocationData(
|
||||
project = project,
|
||||
actionId = AgentWorkbenchActionIds.Sessions.MainToolbar.NEW_THREAD,
|
||||
actionText = newThreadActionText(profileName),
|
||||
actionPlace = entryPoint.name,
|
||||
invokedAtMs = System.currentTimeMillis(),
|
||||
)
|
||||
}
|
||||
|
||||
private class InlineNewThreadPromptLauncherBridge(
|
||||
private val projectPath: String,
|
||||
private val handle: AgentDeferredNewSessionHandle,
|
||||
private val handleProvider: suspend () -> AgentDeferredNewSessionHandle,
|
||||
private val delegateProvider: () -> AgentPromptLauncherBridge? = AgentPromptLaunchers::find,
|
||||
) : AgentPromptLauncherBridge {
|
||||
override suspend fun launch(request: AgentPromptLaunchRequest): AgentPromptLaunchResult {
|
||||
return handle.launch(request)
|
||||
return handleProvider().launch(request)
|
||||
}
|
||||
|
||||
override fun loadProviderPreferences(): AgentPromptLauncherBridge.ProviderPreferences {
|
||||
@@ -249,9 +329,4 @@ private class InlineNewThreadPromptLauncherBridge(
|
||||
}
|
||||
}
|
||||
|
||||
private fun providerDisplayName(descriptor: AgentSessionProviderDescriptor): @NlsSafe String {
|
||||
return runCatching { AgentSessionsBundle.message(descriptor.displayNameKey) }
|
||||
.getOrDefault(descriptor.displayNameFallback)
|
||||
}
|
||||
|
||||
private val LOG = logger<AgentSessionsInlineNewThreadPromptService>()
|
||||
|
||||
+12
-12
@@ -1,7 +1,7 @@
|
||||
// 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
|
||||
|
||||
import com.intellij.agent.workbench.chat.AgentChatEditorTabActionContext
|
||||
import com.intellij.agent.workbench.thread.view.AgentThreadViewEditorTabActionContext
|
||||
import com.intellij.agent.workbench.ui.AgentWorkbenchActionIds
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentWorkbenchPath
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
|
||||
@@ -1122,22 +1122,22 @@ class AgentSessionsMainToolbarNewThreadActionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun projectMainToolbarContextPrefersSelectedChatSourcePathOverProjectBasePath() {
|
||||
fun projectMainToolbarContextPrefersSelectedThreadViewSourcePathOverProjectBasePath() {
|
||||
val project = sourceProjectProxy()
|
||||
val event = eventWithProject(project)
|
||||
|
||||
val context = resolveAgentSessionsMainToolbarNewThreadContext(
|
||||
event = event,
|
||||
isDedicatedProject = { false },
|
||||
selectedSourcePath = { "/work/chat-repo" },
|
||||
selectedSourcePath = { "/work/threadView-repo" },
|
||||
)
|
||||
|
||||
val target = checkNotNull(context).target as AgentSessionsNewThreadTarget.Direct
|
||||
assertThat(target.path).isEqualTo("/work/chat-repo")
|
||||
assertThat(target.path).isEqualTo("/work/threadView-repo")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun projectMainToolbarContextUsesProjectBasePathWhenNoSelectedChatSourcePathExists() {
|
||||
fun projectMainToolbarContextUsesProjectBasePathWhenNoSelectedThreadViewSourcePathExists() {
|
||||
val project = sourceProjectProxy()
|
||||
val event = eventWithProject(project)
|
||||
|
||||
@@ -1152,19 +1152,19 @@ class AgentSessionsMainToolbarNewThreadActionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun projectMainToolbarContextPrefersEventChatContextWhenAvailable() {
|
||||
fun projectMainToolbarContextPrefersEventThreadViewContextWhenAvailable() {
|
||||
val project = sourceProjectProxy()
|
||||
val event = eventWithProject(project)
|
||||
|
||||
val context = resolveAgentSessionsMainToolbarNewThreadContext(
|
||||
event = event,
|
||||
isDedicatedProject = { false },
|
||||
resolveChatContext = { editorContext() },
|
||||
selectedSourcePath = { "/work/selected-chat-repo" },
|
||||
resolveThreadViewContext = { editorContext() },
|
||||
selectedSourcePath = { "/work/selected-threadView-repo" },
|
||||
)
|
||||
|
||||
val target = checkNotNull(context).target as AgentSessionsNewThreadTarget.Direct
|
||||
assertThat(target.path).isEqualTo("/work/event-chat-repo")
|
||||
assertThat(target.path).isEqualTo("/work/event-threadView-repo")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1224,10 +1224,10 @@ class AgentSessionsMainToolbarNewThreadActionsTest {
|
||||
|
||||
private const val MANAGE_LAUNCH_PROFILES_TEXT: String = "Manage Launch Profiles…"
|
||||
|
||||
private fun editorContext(): AgentChatEditorTabActionContext {
|
||||
val path = "/work/event-chat-repo"
|
||||
private fun editorContext(): AgentThreadViewEditorTabActionContext {
|
||||
val path = "/work/event-threadView-repo"
|
||||
val normalizedPath = normalizeAgentWorkbenchPath(path)
|
||||
return AgentChatEditorTabActionContext(
|
||||
return AgentThreadViewEditorTabActionContext(
|
||||
project = ProjectManager.getInstance().defaultProject,
|
||||
path = normalizedPath,
|
||||
tabKey = "codex:$normalizedPath:thread-1",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
||||
// 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
|
||||
|
||||
import com.intellij.agent.workbench.chat.AgentChatPendingTabRebindOutcome
|
||||
import com.intellij.agent.workbench.chat.AgentChatPendingTabRebindReport
|
||||
import com.intellij.agent.workbench.chat.AgentChatPendingTabRebindRequest
|
||||
import com.intellij.agent.workbench.chat.AgentChatPendingTabRebindStatus
|
||||
import com.intellij.agent.workbench.chat.AgentChatPendingTabSnapshot
|
||||
import com.intellij.agent.workbench.thread.view.AgentThreadViewPendingTabRebindOutcome
|
||||
import com.intellij.agent.workbench.thread.view.AgentThreadViewPendingTabRebindReport
|
||||
import com.intellij.agent.workbench.thread.view.AgentThreadViewPendingTabRebindRequest
|
||||
import com.intellij.agent.workbench.thread.view.AgentThreadViewPendingTabRebindStatus
|
||||
import com.intellij.agent.workbench.thread.view.AgentThreadViewPendingTabSnapshot
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
@@ -17,6 +17,9 @@ import com.intellij.agent.workbench.prompt.core.AgentPromptLaunchError
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptLaunchRequest
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptLaunchResult
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptReasoningEffort
|
||||
import com.intellij.platform.ai.agent.sessions.core.launch.AGENT_SESSION_SURFACE_ACP
|
||||
import com.intellij.platform.ai.agent.sessions.core.launch.AGENT_SESSION_SURFACE_TERMINAL
|
||||
import com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionSurfaces
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviders
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.InMemoryAgentSessionProviderRegistry
|
||||
@@ -27,7 +30,9 @@ import com.intellij.agent.workbench.sessions.state.AgentSessionLaunchProfileStat
|
||||
import com.intellij.agent.workbench.sessions.state.AgentSessionUiPreferencesStateService
|
||||
import com.intellij.agent.workbench.sessions.util.buildAgentSessionIdentity
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -59,7 +64,7 @@ class AgentSessionLaunchServiceTest {
|
||||
true
|
||||
},
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val sessionSource = sourceForActiveThreads(activeThreads)
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
@@ -67,22 +72,22 @@ class AgentSessionLaunchServiceTest {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sessionSource) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
archivedSessionsRefreshIfLoaded = { archivedRefreshCalls.incrementAndGet() },
|
||||
) { service, launchService ->
|
||||
launchService.openChatThread(
|
||||
launchService.openThreadViewThread(
|
||||
path = PROJECT_PATH,
|
||||
thread = thread(id = "codex-archived", updatedAt = 200, provider = AgentSessionProvider.from("codex")).copy(archived = true),
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
)
|
||||
|
||||
waitForCondition {
|
||||
chatOpenExecutor.openChatCalls.get() == 1 &&
|
||||
threadViewOpenExecutor.openThreadViewCalls.get() == 1 &&
|
||||
archivedRefreshCalls.get() == 1 &&
|
||||
activeThreadIds(service.state.value).contains("codex-archived")
|
||||
}
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.thread.id).isEqualTo("codex-archived")
|
||||
assertThat(openRequest.thread.archived).isFalse()
|
||||
assertThat(openRequest.subAgent).isNull()
|
||||
@@ -103,25 +108,25 @@ class AgentSessionLaunchServiceTest {
|
||||
true
|
||||
},
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sourceForActiveThreads(emptyList())) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
archivedSessionsRefreshIfLoaded = { archivedRefreshCalls.incrementAndGet() },
|
||||
) { _, launchService ->
|
||||
launchService.openChatThread(
|
||||
launchService.openThreadViewThread(
|
||||
path = PROJECT_PATH,
|
||||
thread = thread(id = "codex-active", updatedAt = 200, provider = AgentSessionProvider.from("codex")),
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
)
|
||||
|
||||
waitForCondition { chatOpenExecutor.openChatCalls.get() == 1 }
|
||||
waitForCondition { threadViewOpenExecutor.openThreadViewCalls.get() == 1 }
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.thread.id).isEqualTo("codex-active")
|
||||
assertThat(openRequest.thread.archived).isFalse()
|
||||
assertThat(unarchiveCalls.get()).isZero()
|
||||
@@ -131,13 +136,44 @@ class AgentSessionLaunchServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun openThreadViewThreadPassesOpenedThreadViewHandlerToExecutor() {
|
||||
val descriptor = testDescriptor(
|
||||
supportsUnarchiveThread = false,
|
||||
unarchiveThreadHandler = { _, _ -> false },
|
||||
)
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val openedThreadViewHandler: suspend (Project, VirtualFile) -> Unit = { _, _ -> }
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sourceForActiveThreads(emptyList())) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.openThreadViewThread(
|
||||
path = PROJECT_PATH,
|
||||
thread = thread(id = "codex-active", updatedAt = 200, provider = AgentSessionProvider.from("codex")),
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
openedThreadViewHandler = openedThreadViewHandler,
|
||||
)
|
||||
|
||||
waitForCondition { threadViewOpenExecutor.openThreadViewCalls.get() == 1 }
|
||||
|
||||
assertThat(threadViewOpenExecutor.lastOpenThreadViewHandler.get()).isSameAs(openedThreadViewHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun openThreadWithBranchMismatchAllowsDialogToAcquireWriteIntentReadLock() {
|
||||
val descriptor = testDescriptor(
|
||||
supportsUnarchiveThread = false,
|
||||
unarchiveThreadHandler = { _, _ -> false },
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val confirmations = AtomicInteger(0)
|
||||
val currentProject = ProjectManager.getInstance().defaultProject
|
||||
|
||||
@@ -146,7 +182,7 @@ class AgentSessionLaunchServiceTest {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sourceForActiveThreads(emptyList())) },
|
||||
projectEntriesProvider = { listOf(featureWorktreeProjectEntry()) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
branchMismatchConfirmation = { project, originBranch, currentBranch ->
|
||||
confirmations.incrementAndGet()
|
||||
assertThat(project).isSameAs(currentProject)
|
||||
@@ -162,14 +198,14 @@ class AgentSessionLaunchServiceTest {
|
||||
service.refreshCatalogAndLoadNewlyOpened()
|
||||
waitForCondition { hasFeatureWorktree(service.state.value) }
|
||||
|
||||
launchService.openChatThread(
|
||||
launchService.openThreadViewThread(
|
||||
path = WORKTREE_PATH,
|
||||
thread = branchMismatchThread(),
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
currentProject = currentProject,
|
||||
)
|
||||
|
||||
waitForCondition { chatOpenExecutor.openChatCalls.get() == 1 }
|
||||
waitForCondition { threadViewOpenExecutor.openThreadViewCalls.get() == 1 }
|
||||
assertThat(confirmations.get()).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
@@ -182,7 +218,7 @@ class AgentSessionLaunchServiceTest {
|
||||
supportsUnarchiveThread = false,
|
||||
unarchiveThreadHandler = { _, _ -> false },
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val confirmations = AtomicInteger(0)
|
||||
val promptResults = CopyOnWriteArrayList<AgentPromptLaunchResult>()
|
||||
|
||||
@@ -191,7 +227,7 @@ class AgentSessionLaunchServiceTest {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sourceForActiveThreads(emptyList())) },
|
||||
projectEntriesProvider = { listOf(featureWorktreeProjectEntry()) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
branchMismatchConfirmation = { _, originBranch, currentBranch ->
|
||||
confirmations.incrementAndGet()
|
||||
assertThat(originBranch).isEqualTo("main")
|
||||
@@ -202,7 +238,7 @@ class AgentSessionLaunchServiceTest {
|
||||
service.refreshCatalogAndLoadNewlyOpened()
|
||||
waitForCondition { hasFeatureWorktree(service.state.value) }
|
||||
|
||||
launchService.openChatThread(
|
||||
launchService.openThreadViewThread(
|
||||
path = WORKTREE_PATH,
|
||||
thread = branchMismatchThread(),
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
@@ -210,7 +246,7 @@ class AgentSessionLaunchServiceTest {
|
||||
)
|
||||
|
||||
waitForCondition { promptResults.singleOrNull()?.error == AgentPromptLaunchError.CANCELLED }
|
||||
assertThat(chatOpenExecutor.openChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openThreadViewCalls.get()).isZero()
|
||||
assertThat(confirmations.get()).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
@@ -224,24 +260,24 @@ class AgentSessionLaunchServiceTest {
|
||||
unarchiveThreadHandler = { _, _ -> false },
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD, AgentSessionLaunchMode.YOLO),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sourceForActiveThreads(emptyList())) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.openChatThread(
|
||||
launchService.openThreadViewThread(
|
||||
path = PROJECT_PATH,
|
||||
thread = thread(id = "codex-active", updatedAt = 200, provider = AgentSessionProvider.from("codex")),
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
)
|
||||
|
||||
waitForCondition { chatOpenExecutor.openChatCalls.get() == 1 }
|
||||
waitForCondition { threadViewOpenExecutor.openThreadViewCalls.get() == 1 }
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.launchMode).isNull()
|
||||
}
|
||||
}
|
||||
@@ -255,7 +291,7 @@ class AgentSessionLaunchServiceTest {
|
||||
unarchiveThreadHandler = { _, _ -> false },
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD, AgentSessionLaunchMode.YOLO),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val activeThread = thread(id = "codex-active", updatedAt = 200, provider = AgentSessionProvider.from("codex"))
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
@@ -263,7 +299,7 @@ class AgentSessionLaunchServiceTest {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sourceForActiveThreads(listOf(activeThread))) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
service.refresh()
|
||||
waitForCondition { activeThreadIds(service.state.value).contains(activeThread.id) }
|
||||
@@ -280,9 +316,9 @@ class AgentSessionLaunchServiceTest {
|
||||
|
||||
assertThat(result.launched).isTrue()
|
||||
assertThat(result.error).isNull()
|
||||
waitForCondition { chatOpenExecutor.openChatCalls.get() == 1 }
|
||||
waitForCondition { threadViewOpenExecutor.openThreadViewCalls.get() == 1 }
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.thread.id).isEqualTo(activeThread.id)
|
||||
assertThat(openRequest.launchMode).isEqualTo(AgentSessionLaunchMode.YOLO)
|
||||
}
|
||||
@@ -307,10 +343,12 @@ class AgentSessionLaunchServiceTest {
|
||||
name = "Codex Yolo",
|
||||
providerId = AgentSessionProvider.from("codex").value,
|
||||
launchMode = AgentSessionLaunchMode.YOLO,
|
||||
launchTargetId = "codex.test.target",
|
||||
surfaceId = AGENT_SESSION_SURFACE_TERMINAL,
|
||||
generationSettings = profileSettings,
|
||||
)
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val activeThread = thread(id = "codex-active", updatedAt = 200, provider = AgentSessionProvider.from("codex"))
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
@@ -319,7 +357,7 @@ class AgentSessionLaunchServiceTest {
|
||||
sessionSourcesProvider = { listOf(sourceForActiveThreads(listOf(activeThread))) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
uiPreferencesState = uiPreferencesState,
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
service.refresh()
|
||||
waitForCondition { activeThreadIds(service.state.value).contains(activeThread.id) }
|
||||
@@ -330,6 +368,7 @@ class AgentSessionLaunchServiceTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
projectPath = PROJECT_PATH,
|
||||
launchMode = AgentSessionLaunchMode.STANDARD,
|
||||
surfaceId = AGENT_SESSION_SURFACE_ACP,
|
||||
initialMessageRequest = AgentPromptInitialMessageRequest(prompt = "Continue this thread"),
|
||||
targetThreadId = activeThread.id,
|
||||
)
|
||||
@@ -337,12 +376,14 @@ class AgentSessionLaunchServiceTest {
|
||||
|
||||
assertThat(result.launched).isTrue()
|
||||
assertThat(result.error).isNull()
|
||||
waitForCondition { chatOpenExecutor.openChatCalls.get() == 1 }
|
||||
waitForCondition { threadViewOpenExecutor.openThreadViewCalls.get() == 1 }
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.thread.id).isEqualTo(activeThread.id)
|
||||
assertThat(openRequest.launchMode).isEqualTo(AgentSessionLaunchMode.YOLO)
|
||||
assertThat(openRequest.launchProfileId).isEqualTo(profileId)
|
||||
assertThat(openRequest.launchTargetId).isEqualTo("codex.test.target")
|
||||
assertThat(openRequest.surfaceId).isEqualTo(AgentSessionSurfaces.TERMINAL)
|
||||
assertThat(openRequest.generationSettings).isEqualTo(profileSettings)
|
||||
}
|
||||
}
|
||||
@@ -366,10 +407,12 @@ class AgentSessionLaunchServiceTest {
|
||||
name = "Codex New Yolo",
|
||||
providerId = AgentSessionProvider.from("codex").value,
|
||||
launchMode = AgentSessionLaunchMode.YOLO,
|
||||
launchTargetId = "codex.active.target",
|
||||
surfaceId = AGENT_SESSION_SURFACE_TERMINAL,
|
||||
generationSettings = profileSettings,
|
||||
)
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
@@ -377,7 +420,7 @@ class AgentSessionLaunchServiceTest {
|
||||
sessionSourcesProvider = { listOf(descriptor.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
uiPreferencesState = uiPreferencesState,
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.createNewSession(
|
||||
path = PROJECT_PATH,
|
||||
@@ -385,11 +428,13 @@ class AgentSessionLaunchServiceTest {
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
)
|
||||
|
||||
waitForCondition { chatOpenExecutor.openNewChatCalls.get() == 1 }
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenNewChatRequest.get())
|
||||
waitForCondition { threadViewOpenExecutor.openNewThreadViewCalls.get() == 1 }
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.identity).startsWith("codex:new-")
|
||||
assertThat(openRequest.launchMode).isEqualTo(AgentSessionLaunchMode.YOLO)
|
||||
assertThat(openRequest.launchProfileId).isEqualTo(profileId)
|
||||
assertThat(openRequest.launchTargetId).isEqualTo("codex.active.target")
|
||||
assertThat(openRequest.surfaceId).isEqualTo(AgentSessionSurfaces.TERMINAL)
|
||||
assertThat(openRequest.generationSettings).isEqualTo(profileSettings)
|
||||
}
|
||||
}
|
||||
@@ -397,7 +442,7 @@ class AgentSessionLaunchServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createNewSessionOpensPreparingChatBeforeLaunchSpecIsPrepared() {
|
||||
fun createNewSessionOpensPreparingThreadViewBeforeLaunchSpecIsPrepared() {
|
||||
val launchSpecRequested = CompletableDeferred<Unit>()
|
||||
val releaseLaunchSpec = CompletableDeferred<Unit>()
|
||||
val descriptor = TestAgentSessionProviderDescriptor(
|
||||
@@ -410,14 +455,14 @@ class AgentSessionLaunchServiceTest {
|
||||
AgentSessionTerminalLaunchSpec(command = listOf("test", "new", mode.name))
|
||||
},
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(descriptor.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.createNewSession(
|
||||
path = PROJECT_PATH,
|
||||
@@ -426,27 +471,131 @@ class AgentSessionLaunchServiceTest {
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
)
|
||||
|
||||
chatOpenExecutor.awaitOpenPreparingNewChatCalls(1)
|
||||
threadViewOpenExecutor.awaitOpenPreparingNewThreadViewCalls(1)
|
||||
val preparingRequest = checkNotNull(threadViewOpenExecutor.lastOpenPreparingNewThreadViewRequest.get())
|
||||
assertThat(preparingRequest.hasDeferredStartContentProvider).isFalse()
|
||||
assertThat(preparingRequest.surfaceId).isEqualTo(AgentSessionSurfaces.TERMINAL)
|
||||
withTimeout(5_000.milliseconds) { launchSpecRequested.await() }
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
|
||||
releaseLaunchSpec.complete(Unit)
|
||||
chatOpenExecutor.awaitOpenNewChatCalls(1)
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenNewChatRequest.get())
|
||||
threadViewOpenExecutor.awaitOpenNewThreadViewCalls(1)
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.launchSpec.command).containsExactly("test", "new", AgentSessionLaunchMode.STANDARD.name)
|
||||
assertThat(openRequest.surfaceId).isEqualTo(AgentSessionSurfaces.TERMINAL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createNewSessionReportsPreparationFailureInOpenedChat() {
|
||||
fun createNewSessionDefaultsToDescriptorSurface() {
|
||||
val descriptor = TestAgentSessionProviderDescriptor(
|
||||
provider = AgentSessionProvider.from("acp"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
cliAvailable = true,
|
||||
defaultLaunchSurface = AgentSessionSurfaces.ACP,
|
||||
supportedLaunchSurfaces = setOf(AgentSessionSurfaces.ACP),
|
||||
)
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(descriptor.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.createNewSession(
|
||||
path = PROJECT_PATH,
|
||||
provider = AgentSessionProvider.from("acp"),
|
||||
mode = AgentSessionLaunchMode.STANDARD,
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
)
|
||||
|
||||
threadViewOpenExecutor.awaitOpenNewThreadViewCalls(1)
|
||||
val preparingRequest = checkNotNull(threadViewOpenExecutor.lastOpenPreparingNewThreadViewRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(preparingRequest.surfaceId).isEqualTo(AgentSessionSurfaces.ACP)
|
||||
assertThat(openRequest.surfaceId).isEqualTo(AgentSessionSurfaces.ACP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createNewSessionUsesGenericDeferredWaitingCopy() {
|
||||
val descriptor = TestAgentSessionProviderDescriptor(
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
cliAvailable = true,
|
||||
)
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(descriptor.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.createNewSession(
|
||||
path = PROJECT_PATH,
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
mode = AgentSessionLaunchMode.STANDARD,
|
||||
entryPoint = AgentWorkbenchEntryPoint.PROMPT,
|
||||
)
|
||||
|
||||
threadViewOpenExecutor.awaitOpenPreparingNewThreadViewCalls(1)
|
||||
val state = checkNotNull(threadViewOpenExecutor.lastOpenPreparingNewThreadViewRequest.get()).waitingState
|
||||
assertThat(state.title).isEqualTo("Starting new thread…")
|
||||
assertThat(state.message).isNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createDeferredNewSessionPassesDeferredStartContentProviderToPreparingThreadView() {
|
||||
val descriptor = TestAgentSessionProviderDescriptor(
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
cliAvailable = true,
|
||||
)
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(descriptor.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
val result = launchService.createDeferredNewSession(
|
||||
path = PROJECT_PATH,
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
mode = AgentSessionLaunchMode.STANDARD,
|
||||
entryPoint = AgentWorkbenchEntryPoint.PROMPT,
|
||||
waitingTitle = "Preparing",
|
||||
deferredStartContentProvider = { error("test executor records the provider without rendering it") },
|
||||
)
|
||||
|
||||
assertThat(result.handle).isNotNull()
|
||||
threadViewOpenExecutor.awaitOpenPreparingNewThreadViewCalls(1)
|
||||
assertThat(checkNotNull(threadViewOpenExecutor.lastOpenPreparingNewThreadViewRequest.get()).hasDeferredStartContentProvider).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createNewSessionReportsPreparationFailureInOpenedThreadView() {
|
||||
val descriptor = TestAgentSessionProviderDescriptor(
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
cliAvailable = false,
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val launchResult = CompletableDeferred<AgentPromptLaunchResult>()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
@@ -454,7 +603,7 @@ class AgentSessionLaunchServiceTest {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(descriptor.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.createNewSession(
|
||||
path = PROJECT_PATH,
|
||||
@@ -464,13 +613,13 @@ class AgentSessionLaunchServiceTest {
|
||||
promptLaunchResolved = { result -> launchResult.complete(result) },
|
||||
)
|
||||
|
||||
chatOpenExecutor.awaitOpenPreparingNewChatCalls(1)
|
||||
waitForCondition { chatOpenExecutor.failPreparingNewChatCalls.get() == 1 }
|
||||
threadViewOpenExecutor.awaitOpenPreparingNewThreadViewCalls(1)
|
||||
waitForCondition { threadViewOpenExecutor.failPreparingNewThreadViewCalls.get() == 1 }
|
||||
val result = withTimeout(5_000.milliseconds) { launchResult.await() }
|
||||
assertThat(result.launched).isFalse()
|
||||
assertThat(result.error).isEqualTo(AgentPromptLaunchError.PROVIDER_UNAVAILABLE)
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(chatOpenExecutor.lastFailPreparingNewChatMessage.get()).isNotBlank()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.lastFailPreparingNewThreadViewMessage.get()).isNotBlank()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -483,7 +632,7 @@ class AgentSessionLaunchServiceTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
cliAvailable = true,
|
||||
newSessionLaunchSpecProvider = {
|
||||
newSessionLaunchSpecProvider = { _ ->
|
||||
val attempt = launchSpecAttempts.incrementAndGet()
|
||||
AgentSessionTerminalLaunchSpec(command = listOf("test", "retry", attempt.toString()))
|
||||
},
|
||||
@@ -491,14 +640,14 @@ class AgentSessionLaunchServiceTest {
|
||||
override val supportedReasoningEfforts: Set<AgentPromptReasoningEffort>
|
||||
get() = setOf(AgentPromptReasoningEffort.HIGH)
|
||||
}
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(descriptor.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
val handle = checkNotNull(
|
||||
launchService.createDeferredNewSession(
|
||||
@@ -509,7 +658,7 @@ class AgentSessionLaunchServiceTest {
|
||||
waitingTitle = "Preparing",
|
||||
).handle
|
||||
)
|
||||
chatOpenExecutor.awaitOpenPreparingNewChatCalls(1)
|
||||
threadViewOpenExecutor.awaitOpenPreparingNewThreadViewCalls(1)
|
||||
|
||||
val rejectedRequest = AgentPromptLaunchRequest(
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
@@ -530,16 +679,16 @@ class AgentSessionLaunchServiceTest {
|
||||
val failedResult = handle.launch(rejectedRequest)
|
||||
assertThat(failedResult.launched).isFalse()
|
||||
assertThat(failedResult.error).isEqualTo(AgentPromptLaunchError.UNSUPPORTED_LAUNCH_MODE)
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(chatOpenExecutor.failPreparingNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.failPreparingNewThreadViewCalls.get()).isZero()
|
||||
assertThat(launchSpecAttempts.get()).isZero()
|
||||
|
||||
val successfulResult = handle.launch(request)
|
||||
assertThat(successfulResult.launched).isTrue()
|
||||
assertThat(successfulResult.error).isNull()
|
||||
chatOpenExecutor.awaitOpenNewChatCalls(1)
|
||||
threadViewOpenExecutor.awaitOpenNewThreadViewCalls(1)
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenNewChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.launchSpec.command).containsExactly("test", "retry", "1")
|
||||
assertThat(openRequest.initialComposedMessage).isEqualTo("Start after retry")
|
||||
assertThat(openRequest.generationSettings).isEqualTo(request.generationSettings)
|
||||
@@ -547,7 +696,7 @@ class AgentSessionLaunchServiceTest {
|
||||
val duplicateResult = handle.launch(request)
|
||||
assertThat(duplicateResult.launched).isFalse()
|
||||
assertThat(duplicateResult.error).isEqualTo(AgentPromptLaunchError.DROPPED_DUPLICATE)
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isEqualTo(1)
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isEqualTo(1)
|
||||
assertThat(launchSpecAttempts.get()).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
@@ -561,7 +710,7 @@ class AgentSessionLaunchServiceTest {
|
||||
unarchiveThreadHandler = { _, _ -> false },
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val pendingThread = thread(id = "new-global-prompt", updatedAt = 200, provider = AgentSessionProvider.from("codex"))
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
@@ -569,7 +718,7 @@ class AgentSessionLaunchServiceTest {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sourceForActiveThreads(listOf(pendingThread))) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
service.refresh()
|
||||
waitForCondition { activeThreadIds(service.state.value).contains(pendingThread.id) }
|
||||
@@ -586,8 +735,8 @@ class AgentSessionLaunchServiceTest {
|
||||
|
||||
assertThat(result.launched).isFalse()
|
||||
assertThat(result.error).isEqualTo(AgentPromptLaunchError.TARGET_THREAD_NOT_FOUND)
|
||||
assertThat(chatOpenExecutor.openChatCalls.get()).isZero()
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openThreadViewCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -602,14 +751,14 @@ class AgentSessionLaunchServiceTest {
|
||||
supportsPromptLaunch = false,
|
||||
)
|
||||
val activeThread = thread(id = "codex-active", updatedAt = 200, provider = AgentSessionProvider.from("codex"))
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sourceForActiveThreads(listOf(activeThread))) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
service.refresh()
|
||||
waitForCondition { activeThreadIds(service.state.value).contains(activeThread.id) }
|
||||
@@ -636,8 +785,8 @@ class AgentSessionLaunchServiceTest {
|
||||
assertThat(existingThreadResult.error).isEqualTo(AgentPromptLaunchError.PROVIDER_UNAVAILABLE)
|
||||
assertThat(newThreadResult.launched).isFalse()
|
||||
assertThat(newThreadResult.error).isEqualTo(AgentPromptLaunchError.PROVIDER_UNAVAILABLE)
|
||||
assertThat(chatOpenExecutor.openChatCalls.get()).isZero()
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openThreadViewCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -651,7 +800,7 @@ class AgentSessionLaunchServiceTest {
|
||||
cliAvailable = true,
|
||||
supportsPromptLaunch = false,
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val uiPreferencesState = AgentSessionUiPreferencesStateService()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
@@ -660,7 +809,7 @@ class AgentSessionLaunchServiceTest {
|
||||
sessionSourcesProvider = { listOf(descriptor.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
uiPreferencesState = uiPreferencesState,
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.createNewSession(
|
||||
path = PROJECT_PATH,
|
||||
@@ -668,7 +817,7 @@ class AgentSessionLaunchServiceTest {
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
)
|
||||
|
||||
waitForCondition { chatOpenExecutor.openNewChatCalls.get() == 1 }
|
||||
waitForCondition { threadViewOpenExecutor.openNewThreadViewCalls.get() == 1 }
|
||||
|
||||
assertThat(uiPreferencesState.getProviderPreferences().providerOptionsByProviderId).isEmpty()
|
||||
}
|
||||
@@ -692,14 +841,14 @@ class AgentSessionLaunchServiceTest {
|
||||
)
|
||||
},
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(ScriptedSessionSource(provider = provider)) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.createNewSession(
|
||||
path = PROJECT_PATH,
|
||||
@@ -707,9 +856,9 @@ class AgentSessionLaunchServiceTest {
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_POPUP,
|
||||
)
|
||||
|
||||
waitForCondition { chatOpenExecutor.openNewChatCalls.get() == 1 }
|
||||
waitForCondition { threadViewOpenExecutor.openNewThreadViewCalls.get() == 1 }
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenNewChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.identity).isEqualTo(buildAgentSessionIdentity(provider, preallocatedSessionId))
|
||||
assertThat(openRequest.launchSpec.preallocatedSessionId).isEqualTo(preallocatedSessionId)
|
||||
}
|
||||
@@ -717,6 +866,57 @@ class AgentSessionLaunchServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createNewSessionBuilderAndPreparedHandlerUsePreallocatedLaunchSpecSessionId() {
|
||||
val provider = AgentSessionProvider.from("pi")
|
||||
val preallocatedSessionId = "f174b4df-e942-49fe-bb30-8b5f8e7f4857"
|
||||
val builderThreadIds = CopyOnWriteArrayList<String>()
|
||||
val preparedThreadIds = CopyOnWriteArrayList<String>()
|
||||
val descriptor = TestAgentSessionProviderDescriptor(
|
||||
provider = provider,
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
cliAvailable = true,
|
||||
newSessionLaunchSpecProvider = {
|
||||
AgentSessionTerminalLaunchSpec(
|
||||
command = listOf("test", "new"),
|
||||
preallocatedSessionId = preallocatedSessionId,
|
||||
)
|
||||
},
|
||||
)
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(ScriptedSessionSource(provider = provider)) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.createNewSession(
|
||||
path = PROJECT_PATH,
|
||||
provider = provider,
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_POPUP,
|
||||
initialMessageRequestBuilder = { context ->
|
||||
builderThreadIds += context.threadId
|
||||
AgentPromptInitialMessageRequest(prompt = "Start task folder ${context.threadId}")
|
||||
},
|
||||
preparedLaunchHandler = { context ->
|
||||
preparedThreadIds += context.threadId
|
||||
},
|
||||
)
|
||||
|
||||
waitForCondition { threadViewOpenExecutor.openNewThreadViewCalls.get() == 1 }
|
||||
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.identity).isEqualTo(buildAgentSessionIdentity(provider, preallocatedSessionId))
|
||||
assertThat(openRequest.initialComposedMessage).isEqualTo("Start task folder $preallocatedSessionId")
|
||||
assertThat(builderThreadIds).containsExactly(preallocatedSessionId)
|
||||
assertThat(preparedThreadIds).containsExactly(preallocatedSessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun openCodexThreadRebindsMatchingPendingTabBeforeOpening() {
|
||||
assertOpenThreadRebindsMatchingPendingTabBeforeOpening(AgentSessionProvider.from("codex"))
|
||||
@@ -738,25 +938,25 @@ class AgentSessionLaunchServiceTest {
|
||||
false
|
||||
},
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sourceForActiveThreads(emptyList())) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
archivedSessionsRefreshIfLoaded = { archivedRefreshCalls.incrementAndGet() },
|
||||
) { _, launchService ->
|
||||
launchService.openChatThread(
|
||||
launchService.openThreadViewThread(
|
||||
path = PROJECT_PATH,
|
||||
thread = thread(id = "codex-archived", updatedAt = 200, provider = AgentSessionProvider.from("codex")).copy(archived = true),
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
)
|
||||
|
||||
waitForCondition { chatOpenExecutor.openChatCalls.get() == 1 }
|
||||
waitForCondition { threadViewOpenExecutor.openThreadViewCalls.get() == 1 }
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.thread.id).isEqualTo("codex-archived")
|
||||
assertThat(openRequest.thread.archived).isTrue()
|
||||
assertThat(unarchiveCalls.get()).isEqualTo(1)
|
||||
@@ -777,25 +977,25 @@ class AgentSessionLaunchServiceTest {
|
||||
true
|
||||
},
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sourceForActiveThreads(emptyList())) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
archivedSessionsRefreshIfLoaded = { archivedRefreshCalls.incrementAndGet() },
|
||||
) { _, launchService ->
|
||||
launchService.openChatThread(
|
||||
launchService.openThreadViewThread(
|
||||
path = PROJECT_PATH,
|
||||
thread = thread(id = "codex-archived", updatedAt = 200, provider = AgentSessionProvider.from("codex")).copy(archived = true),
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
)
|
||||
|
||||
waitForCondition { chatOpenExecutor.openChatCalls.get() == 1 }
|
||||
waitForCondition { threadViewOpenExecutor.openThreadViewCalls.get() == 1 }
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.thread.id).isEqualTo("codex-archived")
|
||||
assertThat(openRequest.thread.archived).isTrue()
|
||||
assertThat(unarchiveCalls.get()).isZero()
|
||||
@@ -829,7 +1029,7 @@ class AgentSessionLaunchServiceTest {
|
||||
true
|
||||
},
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val sessionSource = sourceForActiveThreads(activeThreads)
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
@@ -837,7 +1037,7 @@ class AgentSessionLaunchServiceTest {
|
||||
withServiceAndArchiveAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
archivedSessionsRefreshIfLoaded = { archivedRefreshCalls.incrementAndGet() },
|
||||
) { service, archiveService, launchService ->
|
||||
service.refresh()
|
||||
@@ -854,19 +1054,19 @@ class AgentSessionLaunchServiceTest {
|
||||
!activeThreadIds(service.state.value).contains(activeThread.id)
|
||||
}
|
||||
|
||||
launchService.openChatThread(
|
||||
launchService.openThreadViewThread(
|
||||
path = PROJECT_PATH,
|
||||
thread = activeThread.copy(archived = true),
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
)
|
||||
|
||||
waitForCondition {
|
||||
chatOpenExecutor.openChatCalls.get() == 1 &&
|
||||
threadViewOpenExecutor.openThreadViewCalls.get() == 1 &&
|
||||
archivedRefreshCalls.get() == 1 &&
|
||||
activeThreadIds(service.state.value).contains(activeThread.id)
|
||||
}
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.thread.id).isEqualTo(activeThread.id)
|
||||
assertThat(openRequest.thread.archived).isFalse()
|
||||
assertThat(archiveCalls.get()).isEqualTo(1)
|
||||
@@ -896,7 +1096,7 @@ class AgentSessionLaunchServiceTest {
|
||||
threadId == archivedThread.id
|
||||
},
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val sessionSource = sourceForActiveThreads(activeThreads)
|
||||
|
||||
AgentSessionProviders.withRegistryForTest(InMemoryAgentSessionProviderRegistry(listOf(descriptor))) {
|
||||
@@ -904,10 +1104,10 @@ class AgentSessionLaunchServiceTest {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(sessionSource) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
archivedSessionsRefreshIfLoaded = { archivedRefreshCalls.incrementAndGet() },
|
||||
) { service, launchService ->
|
||||
launchService.openChatSubAgent(
|
||||
launchService.openThreadViewSubAgent(
|
||||
path = PROJECT_PATH,
|
||||
thread = archivedThread,
|
||||
subAgent = subAgent,
|
||||
@@ -915,12 +1115,12 @@ class AgentSessionLaunchServiceTest {
|
||||
)
|
||||
|
||||
waitForCondition {
|
||||
chatOpenExecutor.openChatCalls.get() == 1 &&
|
||||
threadViewOpenExecutor.openThreadViewCalls.get() == 1 &&
|
||||
archivedRefreshCalls.get() == 1 &&
|
||||
activeThreadIds(service.state.value).contains("codex-archived")
|
||||
}
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.thread.id).isEqualTo("codex-archived")
|
||||
assertThat(openRequest.thread.archived).isFalse()
|
||||
assertThat(openRequest.subAgent).isEqualTo(subAgent)
|
||||
@@ -945,7 +1145,7 @@ class AgentSessionLaunchServiceTest {
|
||||
title = "Resolved open",
|
||||
provider = provider,
|
||||
)
|
||||
val pendingTab = AgentChatPendingTabSnapshot(
|
||||
val pendingTab = AgentThreadViewPendingTabSnapshot(
|
||||
projectPath = PROJECT_PATH,
|
||||
pendingTabKey = "pending-$pendingThreadIdentity",
|
||||
pendingThreadIdentity = pendingThreadIdentity,
|
||||
@@ -953,9 +1153,9 @@ class AgentSessionLaunchServiceTest {
|
||||
pendingFirstInputAtMs = null,
|
||||
pendingLaunchMode = "standard",
|
||||
)
|
||||
val rebindInvocations = CopyOnWriteArrayList<AgentChatPendingTabRebindRequest>()
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor(
|
||||
onOpenChat = { _, _ ->
|
||||
val rebindInvocations = CopyOnWriteArrayList<AgentThreadViewPendingTabRebindRequest>()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor(
|
||||
onOpenThreadView = { _, _ ->
|
||||
assertThat(rebindInvocations).hasSize(1)
|
||||
}
|
||||
)
|
||||
@@ -965,23 +1165,23 @@ class AgentSessionLaunchServiceTest {
|
||||
withTestServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(ScriptedSessionSource(provider = provider)) },
|
||||
projectEntriesProvider = { listOf(openTestProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
openPendingAgentChatTabsProvider = { requestedProvider ->
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
openPendingAgentThreadViewTabsProvider = { requestedProvider ->
|
||||
if (requestedProvider == provider) mapOf(PROJECT_PATH to listOf(pendingTab)) else emptyMap()
|
||||
},
|
||||
openAgentChatPendingTabsBinderWithProvider = { requestedProvider, requestsByPath ->
|
||||
openAgentThreadViewPendingTabsBinderWithProvider = { requestedProvider, requestsByPath ->
|
||||
assertThat(requestedProvider.value).isEqualTo(provider.value)
|
||||
requestsByPath.values.flatten().forEach(rebindInvocations::add)
|
||||
successfulPendingRebindReport(requestsByPath)
|
||||
},
|
||||
) { _, launchService ->
|
||||
launchService.openChatThread(
|
||||
launchService.openThreadViewThread(
|
||||
path = PROJECT_PATH,
|
||||
thread = resolvedThread,
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW,
|
||||
)
|
||||
|
||||
waitForCondition { chatOpenExecutor.openChatCalls.get() == 1 }
|
||||
waitForCondition { threadViewOpenExecutor.openThreadViewCalls.get() == 1 }
|
||||
|
||||
val rebindRequest = rebindInvocations.single()
|
||||
assertThat(rebindRequest.pendingTabKey).isEqualTo("pending-$pendingThreadIdentity")
|
||||
@@ -989,7 +1189,7 @@ class AgentSessionLaunchServiceTest {
|
||||
assertThat(rebindRequest.target.provider).isEqualTo(provider)
|
||||
assertThat(rebindRequest.target.threadIdentity).isEqualTo(buildAgentSessionIdentity(provider, resolvedThread.id))
|
||||
assertThat(rebindRequest.target.threadId).isEqualTo(resolvedThread.id)
|
||||
assertThat(checkNotNull(chatOpenExecutor.lastOpenChatRequest.get()).thread.id).isEqualTo(resolvedThread.id)
|
||||
assertThat(checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get()).thread.id).isEqualTo(resolvedThread.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1062,22 +1262,22 @@ private fun uiPreferencesStateWithProfiles(vararg profiles: AgentPromptLaunchPro
|
||||
}
|
||||
|
||||
private fun successfulPendingRebindReport(
|
||||
requestsByPath: Map<String, List<AgentChatPendingTabRebindRequest>>,
|
||||
): AgentChatPendingTabRebindReport {
|
||||
val outcomesByPath = LinkedHashMap<String, List<AgentChatPendingTabRebindOutcome>>()
|
||||
requestsByPath: Map<String, List<AgentThreadViewPendingTabRebindRequest>>,
|
||||
): AgentThreadViewPendingTabRebindReport {
|
||||
val outcomesByPath = LinkedHashMap<String, List<AgentThreadViewPendingTabRebindOutcome>>()
|
||||
var requestedBindings = 0
|
||||
for ((path, requests) in requestsByPath) {
|
||||
requestedBindings += requests.size
|
||||
outcomesByPath[path] = requests.map { request ->
|
||||
AgentChatPendingTabRebindOutcome(
|
||||
AgentThreadViewPendingTabRebindOutcome(
|
||||
projectPath = path,
|
||||
request = request,
|
||||
status = AgentChatPendingTabRebindStatus.REBOUND,
|
||||
status = AgentThreadViewPendingTabRebindStatus.REBOUND,
|
||||
reboundFiles = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
return AgentChatPendingTabRebindReport(
|
||||
return AgentThreadViewPendingTabRebindReport(
|
||||
requestedBindings = requestedBindings,
|
||||
reboundBindings = requestedBindings,
|
||||
reboundFiles = requestedBindings,
|
||||
|
||||
+125
-125
@@ -18,7 +18,7 @@ import com.intellij.agent.workbench.prompt.core.AgentPromptLauncherBridge
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptProjectPathCandidate
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptProjectPathContext
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptReasoningEffort
|
||||
import com.intellij.agent.workbench.sessions.frame.AgentChatOpenModeSettings
|
||||
import com.intellij.agent.workbench.sessions.frame.AgentThreadViewOpenModeSettings
|
||||
import com.intellij.agent.workbench.sessions.frame.AgentWorkbenchDedicatedFrameProjectManager
|
||||
import com.intellij.agent.workbench.sessions.model.AgentSessionProviderLoadState
|
||||
import com.intellij.agent.workbench.sessions.service.AgentSessionLaunchService
|
||||
@@ -96,7 +96,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -104,7 +104,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val request = promptLaunchRequest(projectPath = INVALID_PROMPT_PROJECT_PATH)
|
||||
@@ -120,7 +120,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
providerBridge.createCalls.get() == 1 &&
|
||||
providerBridge.composeCalls.get() == 1 &&
|
||||
providerBridge.startupCommandCalls.get() == 1 &&
|
||||
chatOpenExecutor.openNewChatCalls.get() == 1 &&
|
||||
threadViewOpenExecutor.openNewThreadViewCalls.get() == 1 &&
|
||||
telemetryEvents.any { it.id == AgentWorkbenchTelemetry.PROMPT_LAUNCH_RESOLVED_EVENT_ID }
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(providerBridge.lastStartupBaseLaunchSpec.get()?.command)
|
||||
.containsExactly("test", "new", AgentSessionLaunchMode.STANDARD.name)
|
||||
assertThat(providerBridge.lastStartupPrompt.get()).isEqualTo("composed:Refactor selected code")
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenNewChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.normalizedPath).isEqualTo(INVALID_PROMPT_PROJECT_PATH)
|
||||
assertThat(openRequest.identity).startsWith("codex:new-")
|
||||
assertThat(openRequest.launchSpec.command)
|
||||
@@ -471,8 +471,8 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
)
|
||||
val releaseFirstOpen = CompletableDeferred<Unit>()
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor(
|
||||
onOpenNewChat = { _, callIndex ->
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor(
|
||||
onOpenNewThreadView = { _, callIndex ->
|
||||
if (callIndex == 1) {
|
||||
releaseFirstOpen.await()
|
||||
}
|
||||
@@ -486,7 +486,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val telemetryEvents = CopyOnWriteArrayList<AgentWorkbenchTelemetryEvent>()
|
||||
@@ -495,7 +495,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
try {
|
||||
val firstResult = bridge.launch(promptLaunchRequest(projectPath = INVALID_PROMPT_PROJECT_PATH))
|
||||
waitForCondition {
|
||||
chatOpenExecutor.openNewChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openNewThreadViewCalls.get() == 1
|
||||
}
|
||||
|
||||
val secondResult = bridge.launch(promptLaunchRequest(projectPath = INVALID_PROMPT_PROJECT_PATH))
|
||||
@@ -540,8 +540,8 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
)
|
||||
val releaseFirstOpen = CompletableDeferred<Unit>()
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor(
|
||||
onOpenNewChat = { _, callIndex ->
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor(
|
||||
onOpenNewThreadView = { _, callIndex ->
|
||||
if (callIndex == 1) {
|
||||
releaseFirstOpen.await()
|
||||
}
|
||||
@@ -555,7 +555,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
try {
|
||||
launchService.createNewSession(
|
||||
@@ -566,7 +566,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
singleFlightDiscriminator = "merge-session-1",
|
||||
)
|
||||
waitForCondition {
|
||||
chatOpenExecutor.openNewChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openNewThreadViewCalls.get() == 1
|
||||
}
|
||||
|
||||
launchService.createNewSession(
|
||||
@@ -579,7 +579,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
|
||||
waitForCondition {
|
||||
providerBridge.createCalls.get() == 2 &&
|
||||
chatOpenExecutor.openNewChatCalls.get() == 2
|
||||
threadViewOpenExecutor.openNewThreadViewCalls.get() == 2
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@@ -592,15 +592,15 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createNewSessionWaitsForOpenedChatHandlerBeforeReleasingSingleFlight() {
|
||||
fun createNewSessionWaitsForOpenedThreadViewHandlerBeforeReleasingSingleFlight() {
|
||||
val providerBridge = RecordingPromptLaunchProviderBridge(
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
)
|
||||
val releaseOpenedChatHandler = CompletableDeferred<Unit>()
|
||||
val releaseOpenedThreadViewHandler = CompletableDeferred<Unit>()
|
||||
val firstLaunchResult = CompletableDeferred<AgentPromptLaunchResult>()
|
||||
val secondLaunchResult = CompletableDeferred<AgentPromptLaunchResult>()
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
withOpenInNonDedicatedFrameSettingForTest {
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
@@ -609,17 +609,17 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.createNewSession(
|
||||
path = PROJECT_PATH,
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
mode = AgentSessionLaunchMode.STANDARD,
|
||||
entryPoint = AgentWorkbenchEntryPoint.TOOLBAR,
|
||||
openedChatHandler = { _, _ -> releaseOpenedChatHandler.await() },
|
||||
openedThreadViewHandler = { _, _ -> releaseOpenedThreadViewHandler.await() },
|
||||
promptLaunchResolved = { result -> firstLaunchResult.complete(result) },
|
||||
)
|
||||
chatOpenExecutor.awaitOpenPreparingNewChatCalls(1)
|
||||
threadViewOpenExecutor.awaitOpenPreparingNewThreadViewCalls(1)
|
||||
|
||||
launchService.createNewSession(
|
||||
path = PROJECT_PATH,
|
||||
@@ -636,12 +636,12 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(firstLaunchResult.isCompleted).isFalse()
|
||||
assertThat(droppedDuplicateResult.launched).isFalse()
|
||||
assertThat(droppedDuplicateResult.error).isEqualTo(AgentPromptLaunchError.DROPPED_DUPLICATE)
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
|
||||
releaseOpenedChatHandler.complete(Unit)
|
||||
releaseOpenedThreadViewHandler.complete(Unit)
|
||||
|
||||
assertThat(firstLaunchResult.await()).isEqualTo(AgentPromptLaunchResult.SUCCESS)
|
||||
chatOpenExecutor.awaitOpenNewChatCalls(1)
|
||||
threadViewOpenExecutor.awaitOpenNewThreadViewCalls(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -649,7 +649,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createNewSessionRunsOpenAndOpenedChatHandlerInProvidedLaunchModality() {
|
||||
fun createNewSessionRunsOpenAndOpenedThreadViewHandlerInProvidedLaunchModality() {
|
||||
val providerBridge = RecordingPromptLaunchProviderBridge(
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
@@ -657,8 +657,8 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
val openModality = AtomicReference<ModalityState?>(null)
|
||||
val handlerModality = AtomicReference<ModalityState?>(null)
|
||||
val launchModalityState = createTestModalityState()
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor(
|
||||
onOpenNewChat = { _, _ ->
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor(
|
||||
onOpenNewThreadView = { _, _ ->
|
||||
openModality.set(currentCoroutineContext().contextModality())
|
||||
}
|
||||
)
|
||||
@@ -670,7 +670,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
assertThat(launchModalityState).isNotEqualTo(ModalityState.nonModal())
|
||||
|
||||
@@ -680,7 +680,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
mode = AgentSessionLaunchMode.STANDARD,
|
||||
entryPoint = AgentWorkbenchEntryPoint.TOOLBAR,
|
||||
launchModalityState = launchModalityState,
|
||||
openedChatHandler = { _, _ ->
|
||||
openedThreadViewHandler = { _, _ ->
|
||||
handlerModality.set(currentCoroutineContext().contextModality())
|
||||
},
|
||||
)
|
||||
@@ -690,7 +690,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
handlerModality.get() === launchModalityState
|
||||
}
|
||||
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isEqualTo(1)
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -703,7 +703,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
provider = AgentSessionProvider.from("claude"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val uiPreferencesState = AgentSessionUiPreferencesStateService()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
@@ -713,7 +713,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
uiPreferencesState = uiPreferencesState,
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val request = promptLaunchRequest(provider = AgentSessionProvider.from("claude"))
|
||||
@@ -746,7 +746,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
launchSpec.copy(command = launchSpec.command + listOf("--effort", settings.reasoningEffort.name.lowercase()))
|
||||
},
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -754,7 +754,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
|
||||
@@ -766,10 +766,10 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
)
|
||||
|
||||
assertThat(result.launched).isTrue()
|
||||
waitForCondition { chatOpenExecutor.openNewChatCalls.get() == 1 }
|
||||
assertThat(chatOpenExecutor.lastOpenNewChatRequest.get()?.launchSpec?.command)
|
||||
waitForCondition { threadViewOpenExecutor.openNewThreadViewCalls.get() == 1 }
|
||||
assertThat(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get()?.launchSpec?.command)
|
||||
.containsExactly("test", "new", AgentSessionLaunchMode.STANDARD.name, "--effort", "high")
|
||||
assertThat(chatOpenExecutor.lastOpenNewChatRequest.get()?.generationSettings)
|
||||
assertThat(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get()?.generationSettings)
|
||||
.isEqualTo(AgentPromptGenerationSettings(reasoningEffort = AgentPromptReasoningEffort.HIGH))
|
||||
}
|
||||
}
|
||||
@@ -787,7 +787,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
launchSpec.copy(command = launchSpec.command + listOf("--model", settings.modelId.orEmpty()))
|
||||
},
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -809,7 +809,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
)
|
||||
},
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
service.refresh()
|
||||
waitForCondition {
|
||||
@@ -829,13 +829,13 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
)
|
||||
|
||||
assertThat(result.launched).isTrue()
|
||||
waitForCondition { chatOpenExecutor.openChatCalls.get() == 1 }
|
||||
val openRequest = chatOpenExecutor.lastOpenChatRequest.get()
|
||||
waitForCondition { threadViewOpenExecutor.openThreadViewCalls.get() == 1 }
|
||||
val openRequest = threadViewOpenExecutor.lastOpenThreadViewRequest.get()
|
||||
assertThat(openRequest?.launchSpecOverride?.command)
|
||||
.containsExactly("test", "resume", "thread-existing", "--model", "pi:custom-model")
|
||||
assertThat(openRequest?.generationSettings).isEqualTo(generationSettings)
|
||||
assertThat(providerBridge.lastGenerationSettings.get()).isEqualTo(generationSettings)
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -847,7 +847,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
val uiPreferencesState = AgentSessionUiPreferencesStateService().apply {
|
||||
setProviderPreferences(
|
||||
AgentPromptLauncherBridge.ProviderPreferences(
|
||||
@@ -863,7 +863,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
uiPreferencesState = uiPreferencesState,
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { _, launchService ->
|
||||
launchService.createNewSession(
|
||||
path = PROJECT_PATH,
|
||||
@@ -874,7 +874,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
)
|
||||
|
||||
waitForCondition {
|
||||
chatOpenExecutor.openNewChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openNewThreadViewCalls.get() == 1
|
||||
}
|
||||
assertThat(uiPreferencesState.getProviderPreferences().providerOptionsByProviderId)
|
||||
.isEqualTo(mapOf("claude" to setOf("plan_mode")))
|
||||
@@ -890,7 +890,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
startupPromptCommandEnvVariables = mapOf("DISABLE_AUTOUPDATER" to "1"),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -898,7 +898,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val result = bridge.launch(promptLaunchRequest(projectPath = INVALID_PROMPT_PROJECT_PATH))
|
||||
@@ -906,9 +906,9 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(result.launched).isTrue()
|
||||
assertThat(result.error).isNull()
|
||||
waitForCondition {
|
||||
chatOpenExecutor.openNewChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openNewThreadViewCalls.get() == 1
|
||||
}
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenNewChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.startupLaunchSpecOverride?.envVariables)
|
||||
.containsExactlyEntriesOf(mapOf("DISABLE_AUTOUPDATER" to "1"))
|
||||
}
|
||||
@@ -923,7 +923,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
startupPromptCommandEnvVariables = mapOf("DISABLE_AUTOUPDATER" to "1"),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -932,7 +932,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val result = bridge.launch(promptLaunchRequest(projectPath = PROJECT_PATH))
|
||||
@@ -940,10 +940,10 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(result.launched).isTrue()
|
||||
assertThat(result.error).isNull()
|
||||
waitForCondition {
|
||||
chatOpenExecutor.openNewChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openNewThreadViewCalls.get() == 1
|
||||
}
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenNewChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.launchSpec.envVariables)
|
||||
.containsEntry(AGENT_WORKBENCH_TEST_ENV_NAME, AGENT_WORKBENCH_TEST_ENV_VALUE)
|
||||
.containsEntry("PATH", AGENT_WORKBENCH_TEST_PATH_PREPEND)
|
||||
@@ -964,7 +964,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
startupPromptCommandSupported = false,
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -972,7 +972,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val result = bridge.launch(promptLaunchRequest(projectPath = INVALID_PROMPT_PROJECT_PATH))
|
||||
@@ -988,9 +988,9 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(providerBridge.startupCommandCalls.get()).isEqualTo(1)
|
||||
assertThat(providerBridge.lastStartupPrompt.get()).isEqualTo("composed:Refactor selected code")
|
||||
waitForCondition {
|
||||
chatOpenExecutor.openNewChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openNewThreadViewCalls.get() == 1
|
||||
}
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenNewChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.startupLaunchSpecOverride).isNull()
|
||||
assertThat(openRequest.initialComposedMessage).isEqualTo("composed:Refactor selected code")
|
||||
assertThat(openRequest.initialMessageToken).isNotNull()
|
||||
@@ -1006,7 +1006,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
startupPromptCommandPolicyEnabled = false,
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -1014,7 +1014,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val request = promptLaunchRequest(projectPath = INVALID_PROMPT_PROJECT_PATH)
|
||||
@@ -1036,9 +1036,9 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(providerBridge.startupCommandCalls.get()).isZero()
|
||||
|
||||
waitForCondition {
|
||||
chatOpenExecutor.openNewChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openNewThreadViewCalls.get() == 1
|
||||
}
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenNewChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.startupLaunchSpecOverride).isNull()
|
||||
assertThat(openRequest.initialComposedMessage).isEqualTo("composed:Refactor selected code")
|
||||
assertThat(openRequest.initialMessageToken).isNotNull()
|
||||
@@ -1056,7 +1056,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
composedMessageBuilder = { request -> request.prompt.trim() },
|
||||
prestartPlanPromptThreadId = "thread-prestarted-plan",
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -1064,7 +1064,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val baseRequest = promptLaunchRequest(projectPath = INVALID_PROMPT_PROJECT_PATH)
|
||||
@@ -1082,7 +1082,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
providerBridge.composeCalls.get() == 1 &&
|
||||
providerBridge.createCalls.get() == 1 &&
|
||||
providerBridge.prestartPlanPromptCalls.get() == 1 &&
|
||||
chatOpenExecutor.openNewChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openNewThreadViewCalls.get() == 1
|
||||
}
|
||||
|
||||
assertThat(providerBridge.createCalls.get()).isEqualTo(1)
|
||||
@@ -1094,9 +1094,9 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(providerBridge.startupCommandCalls.get()).isZero()
|
||||
assertThat(providerBridge.lastStartupBaseLaunchSpec.get()).isNull()
|
||||
assertThat(providerBridge.lastStartupPrompt.get()).isNull()
|
||||
assertThat(chatOpenExecutor.openChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openThreadViewCalls.get()).isZero()
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenNewChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.identity).isEqualTo("codex:thread-prestarted-plan")
|
||||
assertThat(openRequest.launchSpec.command)
|
||||
.containsExactly(
|
||||
@@ -1136,7 +1136,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
composedMessageBuilder = { request -> request.prompt.trim() },
|
||||
supportedReasoningEffortsOverride = setOf(AgentPromptReasoningEffort.HIGH),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -1144,7 +1144,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val baseRequest = promptLaunchRequest(projectPath = INVALID_PROMPT_PROJECT_PATH)
|
||||
@@ -1159,7 +1159,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(result.error).isNull()
|
||||
waitForCondition {
|
||||
providerBridge.composeCalls.get() == 1 &&
|
||||
chatOpenExecutor.openNewChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openNewThreadViewCalls.get() == 1
|
||||
}
|
||||
|
||||
assertThat(providerBridge.createCalls.get()).isEqualTo(1)
|
||||
@@ -1168,9 +1168,9 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(providerBridge.lastStartupBaseLaunchSpec.get()?.command)
|
||||
.containsExactly("test", "new", AgentSessionLaunchMode.STANDARD.name)
|
||||
assertThat(providerBridge.lastStartupPrompt.get()).isEqualTo("Refactor selected code")
|
||||
assertThat(chatOpenExecutor.openChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openThreadViewCalls.get()).isZero()
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenNewChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenNewThreadViewRequest.get())
|
||||
assertThat(openRequest.launchSpec.command)
|
||||
.containsExactly("test", "new", AgentSessionLaunchMode.STANDARD.name)
|
||||
assertThat(openRequest.startupLaunchSpecOverride?.command)
|
||||
@@ -1190,7 +1190,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -1212,7 +1212,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
)
|
||||
},
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
service.refresh()
|
||||
waitForCondition {
|
||||
@@ -1231,14 +1231,14 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(providerBridge.createCalls.get()).isZero()
|
||||
waitForCondition {
|
||||
providerBridge.composeCalls.get() == 1 &&
|
||||
chatOpenExecutor.openChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openThreadViewCalls.get() == 1
|
||||
}
|
||||
assertThat(providerBridge.composeCalls.get()).isEqualTo(1)
|
||||
assertThat(providerBridge.lastComposeRequest.get()).isEqualTo(request.initialMessageRequest)
|
||||
assertThat(providerBridge.startupCommandCalls.get()).isZero()
|
||||
assertThat(providerBridge.lastStartupBaseLaunchSpec.get()).isNull()
|
||||
assertThat(providerBridge.lastStartupPrompt.get()).isNull()
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.normalizedPath).isEqualTo(PROJECT_PATH)
|
||||
assertThat(openRequest.thread.id).isEqualTo("thread-existing")
|
||||
assertThat(openRequest.subAgent).isNull()
|
||||
@@ -1248,7 +1248,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
AgentInitialMessageDispatchStep(text = "composed:Refactor selected code"),
|
||||
)
|
||||
assertThat(openRequest.initialMessageToken).isNotNull()
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1267,7 +1267,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
launchSpec.copy(command = launchSpec.command + listOf("--effort", settings.reasoningEffort.name.lowercase()))
|
||||
},
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -1289,7 +1289,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
)
|
||||
},
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
service.refresh()
|
||||
waitForCondition {
|
||||
@@ -1316,7 +1316,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(providerBridge.createCalls.get()).isZero()
|
||||
waitForCondition {
|
||||
providerBridge.composeCalls.get() == 1 &&
|
||||
chatOpenExecutor.openChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openThreadViewCalls.get() == 1
|
||||
}
|
||||
|
||||
assertThat(providerBridge.lastComposeRequest.get()).isEqualTo(request.initialMessageRequest)
|
||||
@@ -1325,9 +1325,9 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
.containsExactly("test", "resume", "thread-existing", "--effort", "high")
|
||||
assertThat(providerBridge.lastStartupPrompt.get()).isEqualTo("Refactor selected code")
|
||||
assertThat(providerBridge.generationSettingsApplyCalls.get()).isEqualTo(1)
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.normalizedPath).isEqualTo(PROJECT_PATH)
|
||||
assertThat(openRequest.thread.id).isEqualTo("thread-existing")
|
||||
assertThat(openRequest.subAgent).isNull()
|
||||
@@ -1343,13 +1343,13 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun openChatThreadRechecksPromptPlanModeAgainstLatestThreadActivity() {
|
||||
fun openThreadViewThreadRechecksPromptPlanModeAgainstLatestThreadActivity() {
|
||||
val providerBridge = RecordingPromptLaunchProviderBridge(
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
composedMessageBuilder = { request -> request.prompt.trim() },
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -1378,7 +1378,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
)
|
||||
},
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
service.refresh()
|
||||
waitForCondition {
|
||||
@@ -1388,7 +1388,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
}
|
||||
|
||||
val promptLaunchResults = CopyOnWriteArrayList<AgentPromptLaunchResult>()
|
||||
launchService.openChatThread(
|
||||
launchService.openThreadViewThread(
|
||||
path = PROJECT_PATH,
|
||||
thread = thread(
|
||||
id = "thread-existing",
|
||||
@@ -1410,7 +1410,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(promptLaunchResults).containsExactly(
|
||||
AgentPromptLaunchResult.failure(AgentPromptLaunchError.TARGET_THREAD_BUSY_FOR_PLAN_MODE)
|
||||
)
|
||||
assertThat(chatOpenExecutor.openChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openThreadViewCalls.get()).isZero()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1424,8 +1424,8 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
)
|
||||
val firstOpenStarted = CompletableDeferred<Unit>()
|
||||
val releaseFirstOpen = CompletableDeferred<Unit>()
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor(
|
||||
onOpenChat = { _, callIndex ->
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor(
|
||||
onOpenThreadView = { _, callIndex ->
|
||||
if (callIndex == 1) {
|
||||
firstOpenStarted.complete(Unit)
|
||||
releaseFirstOpen.await()
|
||||
@@ -1454,7 +1454,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
)
|
||||
},
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
service.refresh()
|
||||
waitForCondition {
|
||||
@@ -1471,7 +1471,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
?.firstOrNull { thread -> thread.id == "thread-existing" }
|
||||
)
|
||||
|
||||
launchService.openChatThread(
|
||||
launchService.openThreadViewThread(
|
||||
path = PROJECT_PATH,
|
||||
thread = existingThread,
|
||||
entryPoint = AgentWorkbenchEntryPoint.TREE_ROW
|
||||
@@ -1479,7 +1479,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
waitForCondition {
|
||||
firstOpenStarted.isCompleted
|
||||
}
|
||||
assertThat(chatOpenExecutor.openChatCalls.get()).isEqualTo(1)
|
||||
assertThat(threadViewOpenExecutor.openThreadViewCalls.get()).isEqualTo(1)
|
||||
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val request = promptLaunchRequest(targetThreadId = "thread-existing")
|
||||
@@ -1493,7 +1493,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
|
||||
releaseFirstOpen.complete(Unit)
|
||||
waitForCondition(timeoutMs = 5_000) {
|
||||
chatOpenExecutor.openChatCalls.get() == 2
|
||||
threadViewOpenExecutor.openThreadViewCalls.get() == 2
|
||||
}
|
||||
|
||||
assertThat(providerBridge.composeCalls.get()).isEqualTo(1)
|
||||
@@ -1502,15 +1502,15 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(providerBridge.lastStartupBaseLaunchSpec.get()).isNull()
|
||||
assertThat(providerBridge.lastStartupPrompt.get()).isNull()
|
||||
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(chatOpenExecutor.openChatRequests).hasSize(2)
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openThreadViewRequests).hasSize(2)
|
||||
|
||||
val initialOpen = chatOpenExecutor.openChatRequests[0]
|
||||
val initialOpen = threadViewOpenExecutor.openThreadViewRequests[0]
|
||||
assertThat(initialOpen.startupLaunchSpecOverride).isNull()
|
||||
assertThat(initialOpen.initialComposedMessage).isNull()
|
||||
assertThat(initialOpen.initialMessageToken).isNull()
|
||||
|
||||
val promptOpen = chatOpenExecutor.openChatRequests[1]
|
||||
val promptOpen = threadViewOpenExecutor.openThreadViewRequests[1]
|
||||
assertThat(promptOpen.normalizedPath).isEqualTo(PROJECT_PATH)
|
||||
assertThat(promptOpen.thread.id).isEqualTo("thread-existing")
|
||||
assertThat(promptOpen.subAgent).isNull()
|
||||
@@ -1537,7 +1537,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
startupPromptCommandSupported = false,
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -1559,7 +1559,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
)
|
||||
},
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
service.refresh()
|
||||
waitForCondition {
|
||||
@@ -1578,14 +1578,14 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(providerBridge.createCalls.get()).isZero()
|
||||
waitForCondition {
|
||||
providerBridge.composeCalls.get() == 1 &&
|
||||
chatOpenExecutor.openChatCalls.get() == 1
|
||||
threadViewOpenExecutor.openThreadViewCalls.get() == 1
|
||||
}
|
||||
assertThat(providerBridge.composeCalls.get()).isEqualTo(1)
|
||||
assertThat(providerBridge.lastComposeRequest.get()).isEqualTo(request.initialMessageRequest)
|
||||
assertThat(providerBridge.startupCommandCalls.get()).isZero()
|
||||
assertThat(providerBridge.lastStartupBaseLaunchSpec.get()).isNull()
|
||||
assertThat(providerBridge.lastStartupPrompt.get()).isNull()
|
||||
val openRequest = checkNotNull(chatOpenExecutor.lastOpenChatRequest.get())
|
||||
val openRequest = checkNotNull(threadViewOpenExecutor.lastOpenThreadViewRequest.get())
|
||||
assertThat(openRequest.normalizedPath).isEqualTo(PROJECT_PATH)
|
||||
assertThat(openRequest.thread.id).isEqualTo("thread-existing")
|
||||
assertThat(openRequest.subAgent).isNull()
|
||||
@@ -1595,7 +1595,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
AgentInitialMessageDispatchStep(text = "composed:Refactor selected code"),
|
||||
)
|
||||
assertThat(openRequest.initialMessageToken).isNotNull()
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1607,7 +1607,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -1629,7 +1629,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
)
|
||||
},
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
service.refresh()
|
||||
waitForCondition {
|
||||
@@ -1644,8 +1644,8 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(result.error).isEqualTo(AgentPromptLaunchError.TARGET_THREAD_NOT_FOUND)
|
||||
assertThat(providerBridge.createCalls.get()).isZero()
|
||||
assertThat(providerBridge.composeCalls.get()).isZero()
|
||||
assertThat(chatOpenExecutor.openChatCalls.get()).isZero()
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openThreadViewCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1653,7 +1653,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
|
||||
@Test
|
||||
fun launchReturnsProviderUnavailableWhenBridgeIsMissing() {
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(emptyList())
|
||||
) {
|
||||
@@ -1661,7 +1661,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { emptyList() },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val telemetryEvents = CopyOnWriteArrayList<AgentWorkbenchTelemetryEvent>()
|
||||
@@ -1672,8 +1672,8 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
|
||||
assertThat(result.launched).isFalse()
|
||||
assertThat(result.error).isEqualTo(AgentPromptLaunchError.PROVIDER_UNAVAILABLE)
|
||||
assertThat(chatOpenExecutor.openChatCalls.get()).isZero()
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openThreadViewCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
assertThat(telemetryEvents).contains(
|
||||
AgentWorkbenchTelemetryEvent(
|
||||
id = AgentWorkbenchTelemetry.PROMPT_LAUNCH_RESOLVED_EVENT_ID,
|
||||
@@ -1698,7 +1698,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
supportedModes = setOf(AgentSessionLaunchMode.STANDARD),
|
||||
)
|
||||
val chatOpenExecutor = RecordingChatOpenExecutor()
|
||||
val threadViewOpenExecutor = RecordingThreadViewOpenExecutor()
|
||||
AgentSessionProviders.withRegistryForTest(
|
||||
InMemoryAgentSessionProviderRegistry(listOf(providerBridge))
|
||||
) {
|
||||
@@ -1706,7 +1706,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
withServiceAndLaunch(
|
||||
sessionSourcesProvider = { listOf(providerBridge.sessionSource) },
|
||||
projectEntriesProvider = { listOf(openProjectEntry(PROJECT_PATH, "Project A")) },
|
||||
chatOpenExecutor = chatOpenExecutor,
|
||||
threadViewOpenExecutor = threadViewOpenExecutor,
|
||||
) { service, launchService ->
|
||||
val bridge = promptLauncherBridge(service, launchService)
|
||||
val telemetryEvents = CopyOnWriteArrayList<AgentWorkbenchTelemetryEvent>()
|
||||
@@ -1723,8 +1723,8 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
assertThat(result.launched).isFalse()
|
||||
assertThat(result.error).isEqualTo(AgentPromptLaunchError.UNSUPPORTED_LAUNCH_MODE)
|
||||
assertThat(providerBridge.createCalls.get()).isZero()
|
||||
assertThat(chatOpenExecutor.openChatCalls.get()).isZero()
|
||||
assertThat(chatOpenExecutor.openNewChatCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openThreadViewCalls.get()).isZero()
|
||||
assertThat(threadViewOpenExecutor.openNewThreadViewCalls.get()).isZero()
|
||||
assertThat(telemetryEvents).contains(
|
||||
AgentWorkbenchTelemetryEvent(
|
||||
id = AgentWorkbenchTelemetry.PROMPT_LAUNCH_RESOLVED_EVENT_ID,
|
||||
@@ -2162,7 +2162,7 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addContextToOpenChatTargetDelegatesToInjectedHandler() {
|
||||
fun addContextToOpenThreadViewTargetDelegatesToInjectedHandler() {
|
||||
val capturedRequest = AtomicReference<AgentPromptAddContextToTargetRequest>()
|
||||
val bridge = AgentSessionPromptLauncherBridge(
|
||||
launchPromptRequest = { error("not used") },
|
||||
@@ -2170,39 +2170,39 @@ class AgentSessionPromptLauncherBridgeTest {
|
||||
pathStateResolver = ::resolveAgentSessionPathState,
|
||||
refreshCatalogAndLoadNewlyOpened = {},
|
||||
refreshProviderForPath = { _, _ -> },
|
||||
addContextToOpenChatTarget = { request ->
|
||||
addContextToOpenThreadViewTarget = { request ->
|
||||
capturedRequest.set(request)
|
||||
AgentPromptAddContextToTargetResult.ADDED_TO_CHAT
|
||||
AgentPromptAddContextToTargetResult.ADDED_TO_THREAD_VIEW
|
||||
},
|
||||
)
|
||||
val request = addContextToTargetRequest()
|
||||
|
||||
val result = runBlocking(Dispatchers.Default) {
|
||||
bridge.addContextToOpenChatTarget(request)
|
||||
bridge.addContextToOpenThreadViewTarget(request)
|
||||
}
|
||||
|
||||
assertThat(result).isEqualTo(AgentPromptAddContextToTargetResult.ADDED_TO_CHAT)
|
||||
assertThat(result).isEqualTo(AgentPromptAddContextToTargetResult.ADDED_TO_THREAD_VIEW)
|
||||
assertThat(capturedRequest.get()).isEqualTo(request)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addContextToOpenChatTargetPropagatesAlreadyAddedResult() {
|
||||
fun addContextToOpenThreadViewTargetPropagatesAlreadyAddedResult() {
|
||||
val bridge = AgentSessionPromptLauncherBridge(
|
||||
launchPromptRequest = { error("not used") },
|
||||
stateFlowProvider = { error("not used") },
|
||||
pathStateResolver = ::resolveAgentSessionPathState,
|
||||
refreshCatalogAndLoadNewlyOpened = {},
|
||||
refreshProviderForPath = { _, _ -> },
|
||||
addContextToOpenChatTarget = {
|
||||
AgentPromptAddContextToTargetResult.ALREADY_ADDED_TO_CHAT
|
||||
addContextToOpenThreadViewTarget = {
|
||||
AgentPromptAddContextToTargetResult.ALREADY_ADDED_TO_THREAD_VIEW
|
||||
},
|
||||
)
|
||||
|
||||
val result = runBlocking(Dispatchers.Default) {
|
||||
bridge.addContextToOpenChatTarget(addContextToTargetRequest())
|
||||
bridge.addContextToOpenThreadViewTarget(addContextToTargetRequest())
|
||||
}
|
||||
|
||||
assertThat(result).isEqualTo(AgentPromptAddContextToTargetResult.ALREADY_ADDED_TO_CHAT)
|
||||
assertThat(result).isEqualTo(AgentPromptAddContextToTargetResult.ALREADY_ADDED_TO_THREAD_VIEW)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2226,13 +2226,13 @@ private fun addContextToTargetRequest(): AgentPromptAddContextToTargetRequest {
|
||||
}
|
||||
|
||||
private fun <T> withOpenInNonDedicatedFrameSettingForTest(action: () -> T): T {
|
||||
val previousValue = AgentChatOpenModeSettings.openInDedicatedFrame()
|
||||
AgentChatOpenModeSettings.setOpenInDedicatedFrame(false)
|
||||
val previousValue = AgentThreadViewOpenModeSettings.openInDedicatedFrame()
|
||||
AgentThreadViewOpenModeSettings.setOpenInDedicatedFrame(false)
|
||||
try {
|
||||
return action()
|
||||
}
|
||||
finally {
|
||||
AgentChatOpenModeSettings.setOpenInDedicatedFrame(previousValue)
|
||||
AgentThreadViewOpenModeSettings.setOpenInDedicatedFrame(previousValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ targets:
|
||||
- ../../sessions-actions/src/actions/NewThreadMenuActions.kt
|
||||
- ../../sessions/src/service/AgentSessionPromptLauncherBridge.kt
|
||||
- ../../sessions/src/service/AgentSessionLaunchService.kt
|
||||
- ../../sessions/src/state/AgentSessionUiPreferencesStateService.kt
|
||||
- ../../prompt/ui/testSrc/*.kt
|
||||
- ../../sessions/testSrc/AgentSessionPromptLauncherBridgeTest.kt
|
||||
---
|
||||
@@ -23,7 +24,7 @@ targets:
|
||||
# Global Prompt Entry
|
||||
|
||||
Status: Draft
|
||||
Date: 2026-05-16
|
||||
Date: 2026-06-30
|
||||
|
||||
## Summary
|
||||
The global prompt opens a project-scoped prompt surface for starting a new task or sending a prompt to an existing loaded task. The surface is normally a popup, but the empty editor state may host a compact inline prompt composer instead of standard empty-state hints. This spec owns popup and inline lifecycle, target mode, validation, keyboard behavior, provider selection, and launcher handoff. Context collection and Add-to-Agent-Context routing are specified separately.
|
||||
@@ -34,7 +35,7 @@ The global prompt opens a project-scoped prompt surface for starting a new task
|
||||
|
||||
- When the main editor area is empty, Agent Workbench contributes a compact inline composer to the platform empty editor state. The composer owns the rich empty-state surface, uses the shared prompt content/session machinery, and suppresses the painted empty editor action hints while it is visible. Standard empty-state hints remain a fallback when no rich composer is available.
|
||||
[@test] ../../prompt/ui/testSrc/emptyState/AgentWorkbenchInlinePromptEmptyStateProviderTest.kt
|
||||
[@test] ../../../platform/platform-impl/testSrc/com/intellij/openapi/fileEditor/impl/EditorEmptyTextPainterTest.kt
|
||||
[@test] ../../../../platform/platform-impl/testSrc/com/intellij/openapi/fileEditor/impl/EditorEmptyTextPainterTest.kt
|
||||
|
||||
- The inline empty-state composer is gated by the `agent.workbench.inline.empty.state.prompt` system property, enabled by default. When enabled, the inline provider creates the composer and the redundant `AgentWorkbenchPrompt.OpenGlobalPalette` promoted-text hint is suppressed. When disabled, the inline provider creates no component and the promoted-text hint is shown as the empty-editor affordance instead.
|
||||
[@test] ../../prompt/ui/testSrc/emptyState/AgentWorkbenchInlinePromptEmptyStateProviderTest.kt
|
||||
@@ -55,13 +56,13 @@ The global prompt opens a project-scoped prompt surface for starting a new task
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptPaletteViewStructureTest.kt
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptPaletteViewLayoutTest.kt
|
||||
|
||||
- Existing-task mode observes loaded threads through `AgentPromptLauncherBridge`, scoped to the resolved working project path, and may preselect the focused open chat thread or a single loaded thread without automatically switching modes.
|
||||
- Existing-task mode observes loaded threads through `AgentPromptLauncherBridge`, scoped to the resolved working project path, and may preselect the focused open thread view thread or a single loaded thread without automatically switching modes.
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptExistingTaskControllerTest.kt
|
||||
|
||||
- Submit validation must block empty prompts, missing provider, unavailable provider CLI, missing project path, missing launcher bridge, and existing-task submits without a selected task.
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptSubmitValidationDecisionsTest.kt
|
||||
|
||||
- Working project path resolution must never use the dedicated-frame project path. In a dedicated Agent frame, it resolves from selected Sessions context, selected chat tab source path, then most recent non-dedicated project; unresolved submits prompt for a source project and keep the popup open on cancel.
|
||||
- Working project path resolution must never use the dedicated-frame project path. In non-dedicated frames it resolves from the current open project's identity path, so Bazel projects contribute their `.bazelproject` identity instead of raw `project.basePath`. In a dedicated Agent frame, it resolves from selected Sessions context, selected Thread View source path, then most recent non-dedicated project; unresolved submits prompt for a source project and keep the popup open on cancel.
|
||||
[@test] ../../sessions/testSrc/AgentSessionPromptLauncherBridgeTest.kt
|
||||
|
||||
- Keyboard behavior is: Enter submits, Shift+Enter inserts a line break, Tab/Shift+Tab switch prompt tabs unless completion or Codex tab-queue handling consumes the key.
|
||||
@@ -69,7 +70,7 @@ The global prompt opens a project-scoped prompt surface for starting a new task
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptFooterHintDecisionsTest.kt
|
||||
|
||||
- Claude slash completion is available only for Claude provider prompts and only for explicit slash-token completion; it merges built-in Claude menu commands with project `.claude/commands` and `.claude/skills` definitions.
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptClaudeSlashCompletionProviderTest.kt
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptCommandCompletionProviderTest.kt
|
||||
|
||||
- New-task provider, mode, and generation defaults restore through launch profiles. Prompt drafts may persist provider options and container mode, but they must not persist a separate provider id default.
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptLaunchProfileStateTest.kt
|
||||
@@ -86,20 +87,26 @@ The global prompt opens a project-scoped prompt surface for starting a new task
|
||||
- Inline empty-state mode is always a `NEW_TASK` prompt. It must not restore `EXISTING_TASK` draft mode or extension-tab auto-selection because those controls are hidden in the compact empty-state surface.
|
||||
[@test] ../../prompt/ui/testSrc/emptyState/AgentWorkbenchInlinePromptEmptyStateProviderTest.kt
|
||||
|
||||
- Inline new-thread mode is also always a `NEW_TASK` prompt. It is hosted inside a deferred chat tab, starts from the selected launch profile, skips extension-tab auto-selection, and keeps the inline prompt visible when `AgentPromptLauncherBridge.launch(...)` returns a failure so the same pending tab can be retried.
|
||||
- Inline new-thread mode is also always a `NEW_TASK` prompt. It is hosted inside a deferred Thread View, starts from the selected launch profile, skips extension-tab auto-selection, and keeps the inline prompt visible when `AgentPromptLauncherBridge.launch(...)` returns a failure so the same pending tab can be retried.
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptPaletteSessionControllerTest.kt
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptPaletteSubmitControllerTest.kt
|
||||
[@test] ../../sessions/testSrc/AgentSessionLaunchServiceTest.kt
|
||||
|
||||
- Plan mode is available only when the selected provider exposes the plan-mode option, persists in project prompt draft state, and is forced off/rejected for busy existing tasks. A typed `/plan` prefix remains prompt text and does not toggle the option.
|
||||
- New-task launches accepted from the global prompt use the shared generic new-thread deferred tab: provider-neutral centered copy appears immediately, and the spinner appears only after a short delay.
|
||||
[@test] ../../thread-view/testSrc/AgentThreadViewFileEditorLifecycleTest.kt
|
||||
[@test] ../../sessions/testSrc/AgentSessionLaunchServiceTest.kt
|
||||
|
||||
- Plan mode is available as a compact header icon toggle only when the selected provider exposes the plan-mode option. Its last-used state is stored per provider as provider option preferences, restored on prompt open, sent in the launch payload, and forced off or rejected for busy existing tasks. The prompt UI filters selectable options before submit, but launch service preserves explicit launch-request option ids so providers can resolve capability-dependent behavior. A typed `/plan` prefix remains prompt text and does not toggle the option.
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptPlanModeDecisionsTest.kt
|
||||
[@test] ../../sessions/testSrc/AgentSessionPromptLauncherBridgeTest.kt
|
||||
[@test] ../../lib-agent/providers/junie/sessions/testSrc/JunieExistingThreadPromptLaunchIntegrationTest.kt
|
||||
[@test] ../../lib-agent/providers/junie/sessions/testSrc/JunieNewThreadPromptLaunchIntegrationTest.kt
|
||||
|
||||
- Codex `NEW_TASK` prompts start through the Codex app-server remote-resume path. Standard prompts start plain app-server turns; Plan prompts do not type `/plan`, and the resumed TUI must visibly enter Plan mode before the prompt starts. An acknowledged no-op `thread/settings/update` without `thread/settings/updated` must not be treated as prompt-send failure.
|
||||
[@test] ../../lib-agent/providers/codex/sessions/testSrc/CodexPlanPromptRealAppServerIntegrationTest.kt
|
||||
[@test] ../../lib-agent/providers/codex/sessions/testSrc/CodexNewThreadPromptLaunchIntegrationTest.kt
|
||||
|
||||
- `NEW_TASK` and `EXISTING_TASK` expose the provider selector. Changing provider in `EXISTING_TASK` reloads the selectable task list for that provider. Provider-backed model and reasoning-effort controls are exposed for `NEW_TASK` as specified by `global-prompt-generation-controls.spec.md`. Header actions stay limited to prompt-surface tools such as provider selection, Plan mode, Run in container, preview, and prompt library.
|
||||
- `NEW_TASK` and `EXISTING_TASK` expose the provider selector. Changing provider in `EXISTING_TASK` reloads the selectable task list for that provider. Provider-backed model and reasoning-effort controls are exposed for `NEW_TASK` through the unified launch-settings control specified by `global-prompt-generation-controls.spec.md`. The prompt composer mental model, context/text/tray ownership, and content-lane layout contract are owned by `global-prompt-composer.spec.md`.
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptPaletteViewStructureTest.kt
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptProviderSelectorTest.kt
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptPaletteSessionControllerTest.kt
|
||||
@@ -123,10 +130,11 @@ The global prompt opens a project-scoped prompt surface for starting a new task
|
||||
- The empty editor inline composer is a compact persistent empty-state affordance, not a full embedded popup.
|
||||
- Empty editor rendering must not steal focus; users focus the inline composer by clicking it or invoking the global prompt action while it is visible.
|
||||
- The inline prompt editor exposes localized accessible name and description metadata, while validation and status feedback continue to use the shared prompt status strip behavior.
|
||||
- The inline prompt starts compact, grows with longer prompt text up to a bounded height, then scrolls inside the editor.
|
||||
- The popup keep-open toggle is a secondary footer control, not part of the primary header action cluster.
|
||||
- Validation errors appear inline and keep the popup open.
|
||||
- Successful launches close the popup and clear the submitted draft.
|
||||
- Successful inline new-thread launches clear the submitted draft and replace the prompt surface by starting the deferred chat tab.
|
||||
- Successful inline new-thread launches clear the submitted draft and replace the prompt surface by starting the deferred Thread View.
|
||||
|
||||
## Testing / Local Run
|
||||
- `./tests.cmd --module intellij.agent.workbench.prompt.ui.tests --test "com.intellij.agent.workbench.prompt.ui.AgentPrompt*Test"`
|
||||
@@ -137,6 +145,7 @@ The global prompt opens a project-scoped prompt surface for starting a new task
|
||||
- `./tests.cmd --module intellij.agent.workbench.sessions.tests --test com.intellij.agent.workbench.sessions.AgentSessionLaunchServiceTest`
|
||||
|
||||
## References
|
||||
- `global-prompt-composer.spec.md`
|
||||
- `add-to-agent-context.spec.md`
|
||||
- `global-prompt-generation-controls.spec.md`
|
||||
- `global-prompt-suggestions.spec.md`
|
||||
|
||||
@@ -5,8 +5,8 @@ targets:
|
||||
- ../../sessions-toolwindow/src/**/*.kt
|
||||
- ../../sessions-actions/src/**/*.kt
|
||||
- ../../sessions/src/service/AgentSessionLaunchService.kt
|
||||
- ../../chat/src/AgentChatDeferredStartContent.kt
|
||||
- ../../chat/src/AgentChatFileEditor.kt
|
||||
- ../../thread-view/src/AgentThreadViewDeferredStartContent.kt
|
||||
- ../../thread-view/src/AgentThreadViewFileEditor.kt
|
||||
- ../../sessions/src/service/AgentSessionProjectCatalog.kt
|
||||
- ../../sessions/resources/messages/AgentSessionsBundle.properties
|
||||
- ../../sessions-actions/resources/intellij.agent.workbench.sessions.actions.xml
|
||||
@@ -18,7 +18,7 @@ targets:
|
||||
# Agent Sessions New-Thread Actions
|
||||
|
||||
Status: Draft
|
||||
Date: 2026-05-09
|
||||
Date: 2026-06-28
|
||||
|
||||
## Summary
|
||||
New-thread actions let users start provider-backed threads from project/worktree rows and the main toolbar. This spec owns action availability, launch-profile menus, target resolution, inline prompt handoff, and launch deduplication. Codex pending/concrete rebind behavior is specified separately.
|
||||
@@ -51,9 +51,13 @@ New-thread actions let users start provider-backed threads from project/worktree
|
||||
toolbar action is the supported new-thread entry point for the single-project UI.
|
||||
[@test] ../../sessions-toolwindow/testSrc/AgentSessionsTreePopupActionsTest.kt
|
||||
|
||||
- Dedicated Agent frame main-toolbar new-thread resolves source projects lazily on click or popup expansion. Multiple source candidates require explicit selection; a single candidate may be used directly; selected chat-tab source path is a fallback when no open source-project candidate exists.
|
||||
- Dedicated Agent frame main-toolbar new-thread resolves source projects lazily on click or popup expansion. Multiple source candidates require explicit selection; a single candidate may be used directly; selected thread view-tab source path is a fallback when no open source-project candidate exists.
|
||||
[@test] ../../sessions-actions/testSrc/AgentSessionsMainToolbarNewThreadActionsTest.kt
|
||||
|
||||
- New-thread target resolution uses the source project identity path for row selection, thread view-tab keys, and session state, while launch requests carry the resolved project directory for provider cwd. For Bazel project-view opens, the identity is the `.bazelproject` path and the project directory is the containing workspace/Git root.
|
||||
[@test] ../../sessions/testSrc/AgentSessionPromptLauncherBridgeTest.kt
|
||||
[@test] ../../sessions/testSrc/AgentSessionLaunchServiceTest.kt
|
||||
|
||||
- Source-project labels in dedicated-frame new-thread popups reuse Sessions tree naming and fall back to full normalized paths for collisions.
|
||||
[@test] ../../sessions/testSrc/AgentSessionProjectCatalogTest.kt
|
||||
[@test] ../../sessions-actions/testSrc/AgentSessionsMainToolbarNewThreadActionsTest.kt
|
||||
@@ -61,7 +65,7 @@ New-thread actions let users start provider-backed threads from project/worktree
|
||||
- Main-toolbar new-thread is one split-button action on `MainToolbarRight`, after `NewUiRunWidget`. Icon click quick-launches only for a direct eligible target; otherwise it opens the same launch-profile picker as the chevron.
|
||||
[@test] ../../sessions-actions/testSrc/AgentSessionsMainToolbarNewThreadActionsTest.kt
|
||||
|
||||
- Main-toolbar target resolution prefers chat context path, selected chat source project path, then `project.basePath`; in dedicated Agent frames it uses the lazy source-candidate path.
|
||||
- Main-toolbar target resolution prefers thread view context path, selected thread view source project path, then `project.basePath`; in dedicated Agent frames it uses the lazy source-candidate path.
|
||||
[@test] ../../sessions-actions/testSrc/AgentSessionsMainToolbarNewThreadActionsTest.kt
|
||||
|
||||
- Launching must go through `AgentSessionLaunchService.createNewSession(...)`, update persisted provider-option state on accepted prompt-capable launches, and deduplicate semantically identical in-flight launches with single-flight drop semantics.
|
||||
@@ -69,25 +73,32 @@ New-thread actions let users start provider-backed threads from project/worktree
|
||||
[@test] ../../sessions/testSrc/AgentSessionLaunchServiceTest.kt
|
||||
[@test] ../../sessions/testSrc/AgentSessionRefreshCoordinatorTest.kt
|
||||
|
||||
- Main-toolbar new-thread opens a deferred chat tab with an inline prompt when `agent.workbench.new.thread.inline.prompt` is enabled and the selected provider supports prompt launch. The inline prompt is seeded with the selected launch profile, submits through `AgentDeferredNewSessionHandle.launch(...)`, and completes the same pending chat tab instead of opening a separate TUI tab first.
|
||||
- Main-toolbar new-thread opens a deferred Thread View with an inline prompt when `agent.workbench.new.thread.inline.prompt` is enabled and the selected provider supports prompt launch. The inline prompt is seeded with the selected launch profile, submits through `AgentDeferredNewSessionHandle.launch(...)`, and completes the same pending Thread View instead of opening a separate TUI tab first.
|
||||
[@test] ../../sessions-actions/testSrc/AgentSessionsMainToolbarNewThreadActionsTest.kt
|
||||
[@test] ../../sessions/testSrc/AgentSessionLaunchServiceTest.kt
|
||||
[@test] ../../prompt/ui/testSrc/AgentPromptPaletteSessionControllerTest.kt
|
||||
[@test] ../../chat/testSrc/AgentChatFileEditorLifecycleTest.kt
|
||||
[@test] ../../thread-view/testSrc/AgentThreadViewFileEditorLifecycleTest.kt
|
||||
|
||||
- Inline new-thread routing falls back to direct `createNewSession(...)` when the registry key is disabled, the provider descriptor is missing, the provider does not support prompt launch, deferred chat opening fails, or inline prompt installation fails.
|
||||
- Main-toolbar new-thread captures the toolbar `AnActionEvent` data context at the UI action boundary. Inline prompt launches reuse that invocation data, and non-inline launches collect the same default prompt context into the initial message request, auto-trimming oversized context without showing an extra dialog.
|
||||
[@test] ../../sessions-actions/testSrc/AgentSessionsMainToolbarNewThreadActionsTest.kt
|
||||
|
||||
- Generic new-thread deferred tabs show provider-neutral centered regular-weight progress copy with secondary detail text when present. The spinner appears only after a short delay so quick launches do not flash progress chrome.
|
||||
[@test] ../../sessions/testSrc/AgentSessionLaunchServiceTest.kt
|
||||
[@test] ../../thread-view/testSrc/AgentThreadViewFileEditorLifecycleTest.kt
|
||||
|
||||
- Inline new-thread routing falls back to direct `createNewSession(...)` when the registry key is disabled, the provider descriptor is missing, the provider does not support prompt launch, deferred thread view opening fails, or inline prompt installation fails.
|
||||
[@test] ../../sessions-actions/testSrc/AgentSessionsMainToolbarNewThreadActionsTest.kt
|
||||
[@test] ../../sessions/testSrc/AgentSessionLaunchServiceTest.kt
|
||||
|
||||
- Command construction for each provider and launch mode follows `../core/agent-core-contracts.spec.md`.
|
||||
[@test] ../../claude/sessions/testSrc/ClaudeAgentSessionProviderDescriptorTest.kt
|
||||
[@test] ../../codex/sessions/testSrc/CodexAgentSessionProviderDescriptorTest.kt
|
||||
[@test] ../../junie/sessions/testSrc/JunieAgentSessionProviderDescriptorTest.kt
|
||||
[@test] ../../lib-agent/providers/claude/sessions/testSrc/ClaudeAgentSessionProviderDescriptorTest.kt
|
||||
[@test] ../../lib-agent/providers/codex/sessions/testSrc/CodexAgentSessionProviderDescriptorTest.kt
|
||||
[@test] ../../lib-agent/providers/junie/sessions/testSrc/JunieAgentSessionProviderDescriptorTest.kt
|
||||
|
||||
## User Experience
|
||||
- Quick actions use the active launch profile when that profile is still launchable, otherwise they fall back to the first launchable built-in/user profile.
|
||||
- Launch-profile pickers keep Standard and YOLO choices explicit.
|
||||
- Registry-gated inline new-thread uses the chat tab itself as the prompt surface; failed submit keeps that prompt visible for correction or retry.
|
||||
- Registry-gated inline new-thread uses the Thread View itself as the prompt surface; failed submit keeps that prompt visible for correction or retry.
|
||||
- Dedicated-frame source selection appears only when the user invokes the action, not during toolbar update.
|
||||
|
||||
## Testing / Local Run
|
||||
@@ -95,7 +106,7 @@ New-thread actions let users start provider-backed threads from project/worktree
|
||||
- `./tests.cmd --module intellij.agent.workbench.sessions.toolwindow.tests --test com.intellij.agent.workbench.sessions.toolwindow.AgentSessionsTreePopupActionsTest`
|
||||
- `./tests.cmd --module intellij.agent.workbench.sessions.actions.tests --test com.intellij.agent.workbench.sessions.AgentSessionsGearActionsTest`
|
||||
- `./tests.cmd --module intellij.agent.workbench.sessions.actions.tests --test com.intellij.agent.workbench.sessions.AgentSessionsMainToolbarNewThreadActionsTest`
|
||||
- `./tests.cmd --module intellij.agent.workbench.chat.tests --test com.intellij.agent.workbench.chat.AgentChatFileEditorLifecycleTest`
|
||||
- `./tests.cmd --module intellij.agent.workbench.thread.view.tests --test com.intellij.agent.workbench.thread.view.AgentThreadViewFileEditorLifecycleTest`
|
||||
- `./tests.cmd --module intellij.agent.workbench.prompt.ui.tests --test com.intellij.agent.workbench.prompt.ui.AgentPromptPaletteSessionControllerTest`
|
||||
- `./tests.cmd --module intellij.agent.workbench.sessions.tests --test com.intellij.agent.workbench.sessions.AgentSessionPromptLauncherBridgeTest`
|
||||
- `./tests.cmd --module intellij.agent.workbench.sessions.tests --test com.intellij.agent.workbench.sessions.AgentSessionLaunchServiceTest`
|
||||
|
||||
@@ -1,60 +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.chat
|
||||
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import javax.swing.JComponent
|
||||
|
||||
@ApiStatus.Internal
|
||||
class AgentChatDeferredStartContent(
|
||||
@JvmField val component: JComponent,
|
||||
@JvmField val preferredFocusedComponent: JComponent? = null,
|
||||
private val disposeContent: () -> Unit = {},
|
||||
) {
|
||||
private var disposed: Boolean = false
|
||||
|
||||
fun dispose() {
|
||||
if (disposed) {
|
||||
return
|
||||
}
|
||||
disposed = true
|
||||
disposeContent()
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun installAgentChatDeferredStartContent(
|
||||
project: Project,
|
||||
file: VirtualFile,
|
||||
content: AgentChatDeferredStartContent,
|
||||
): Boolean {
|
||||
val chatFile = file as? AgentChatVirtualFile ?: return false
|
||||
chatFile.replaceDeferredStartContent(content)
|
||||
FileEditorManager.getInstance(project).getEditors(chatFile).forEach { editor ->
|
||||
(editor as? AgentChatFileEditor)?.refreshForFileStateChange()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private val AGENT_CHAT_DEFERRED_START_CONTENT_KEY: Key<AgentChatDeferredStartContent> =
|
||||
Key.create("agent.workbench.chat.deferred.start.content")
|
||||
|
||||
internal fun AgentChatVirtualFile.deferredStartContent(): AgentChatDeferredStartContent? {
|
||||
return getUserData(AGENT_CHAT_DEFERRED_START_CONTENT_KEY)
|
||||
}
|
||||
|
||||
internal fun AgentChatVirtualFile.replaceDeferredStartContent(content: AgentChatDeferredStartContent?) {
|
||||
val previous = deferredStartContent()
|
||||
if (previous === content) {
|
||||
return
|
||||
}
|
||||
putUserData(AGENT_CHAT_DEFERRED_START_CONTENT_KEY, content)
|
||||
previous?.dispose()
|
||||
}
|
||||
|
||||
internal fun AgentChatVirtualFile.clearDeferredStartContent() {
|
||||
replaceDeferredStartContent(null)
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
// 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.thread.view
|
||||
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import javax.swing.JComponent
|
||||
|
||||
@@ -22,8 +25,27 @@ class AgentThreadViewDeferredStartContent(
|
||||
}
|
||||
}
|
||||
|
||||
<<<<<<<< HEAD:community/plugins/agent-workbench/thread-view/src/AgentChatDeferredStartContent.kt
|
||||
@ApiStatus.Internal
|
||||
fun installAgentChatDeferredStartContent(
|
||||
project: Project,
|
||||
file: VirtualFile,
|
||||
content: AgentChatDeferredStartContent,
|
||||
): Boolean {
|
||||
val chatFile = file as? AgentChatVirtualFile ?: return false
|
||||
chatFile.replaceDeferredStartContent(content)
|
||||
FileEditorManager.getInstance(project).getEditors(chatFile).forEach { editor ->
|
||||
(editor as? AgentChatFileEditor)?.refreshForFileStateChange()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private val AGENT_CHAT_DEFERRED_START_CONTENT_KEY: Key<AgentChatDeferredStartContent> =
|
||||
Key.create("agent.workbench.chat.deferred.start.content")
|
||||
========
|
||||
private val AGENT_THREAD_VIEW_DEFERRED_START_CONTENT_KEY: Key<AgentThreadViewDeferredStartContent> =
|
||||
Key.create("agent.workbench.threadView.deferred.start.content")
|
||||
>>>>>>>> 968b450c78c65 (IJPL-248623 rename Agent Workbench chat UI to thread view):community/plugins/agent-workbench/thread-view/src/AgentThreadViewDeferredStartContent.kt
|
||||
|
||||
internal fun AgentThreadViewVirtualFile.deferredStartContent(): AgentThreadViewDeferredStartContent? {
|
||||
return getUserData(AGENT_THREAD_VIEW_DEFERRED_START_CONTENT_KEY)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.chat
|
||||
package com.intellij.agent.workbench.thread.view
|
||||
|
||||
// @spec community/plugins/agent-workbench/spec/chat/agent-chat-editor.spec.md
|
||||
// @spec community/plugins/agent-workbench/spec/thread-view/agent-thread-view.spec.md
|
||||
|
||||
import com.intellij.CommonBundle
|
||||
import com.intellij.agent.workbench.ui.AgentWorkbenchActionIds
|
||||
@@ -14,7 +14,10 @@ import com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadRebindPoli
|
||||
import com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent
|
||||
import com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchOperation
|
||||
import com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchPlanner
|
||||
import com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionSurfaces
|
||||
import com.intellij.platform.ai.agent.sessions.core.launch.effectiveAgentSessionSurfaceId
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionArchivedSource
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionLaunchProfileResolver
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderDescriptor
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviders
|
||||
@@ -25,6 +28,7 @@ import com.intellij.openapi.actionSystem.ActionGroup
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.components.serviceAsync
|
||||
import com.intellij.openapi.components.serviceOrNull
|
||||
@@ -39,6 +43,9 @@ import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
import com.intellij.openapi.wm.StatusBar
|
||||
import com.intellij.terminal.frontend.view.TerminalInputInterceptor
|
||||
import com.intellij.util.ui.AsyncProcessIcon
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -51,36 +58,46 @@ import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Component
|
||||
import java.awt.Container
|
||||
import java.awt.Dimension
|
||||
import java.awt.GridBagLayout
|
||||
import java.awt.event.HierarchyEvent
|
||||
import java.awt.event.HierarchyListener
|
||||
import java.awt.event.KeyEvent
|
||||
import java.beans.PropertyChangeListener
|
||||
import javax.swing.BorderFactory
|
||||
import javax.swing.Box
|
||||
import javax.swing.BoxLayout
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.JProgressBar
|
||||
import javax.swing.JTextArea
|
||||
import javax.swing.Timer
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
internal class AgentChatFileEditor(
|
||||
private const val DEFERRED_START_PROGRESS_DELAY_MS = 300
|
||||
private const val DEFERRED_START_PROGRESS_NAME = "Agent Thread View Start Progress"
|
||||
private const val DEFERRED_START_PROGRESS_TIMER_PROPERTY = "AgentThreadViewFileEditor.deferredStartProgressTimer"
|
||||
|
||||
internal class AgentThreadViewFileEditor(
|
||||
private val project: Project,
|
||||
private val file: AgentChatVirtualFile,
|
||||
private val terminalTabs: AgentChatTerminalTabs = ToolWindowAgentChatTerminalTabs,
|
||||
private val liveTerminalRegistry: AgentChatLiveTerminalRegistry? = null,
|
||||
private val tabSnapshotWriter: AgentChatTabSnapshotWriter = ApplicationAgentChatTabSnapshotWriter,
|
||||
private val archivedRestoreHandler: AgentChatArchivedRestoreHandler = ApplicationAgentChatArchivedRestoreHandler,
|
||||
private val file: AgentThreadViewVirtualFile,
|
||||
private val terminalTabs: AgentThreadViewTerminalTabs = ToolWindowAgentThreadViewTerminalTabs,
|
||||
private val liveTerminalRegistry: AgentThreadViewLiveTerminalRegistry? = null,
|
||||
private val tabSnapshotWriter: AgentThreadViewTabSnapshotWriter = ApplicationAgentThreadViewTabSnapshotWriter,
|
||||
private val archivedRestoreHandler: AgentThreadViewArchivedRestoreHandler = ApplicationAgentThreadViewArchivedRestoreHandler,
|
||||
private val currentTimeProvider: () -> Long = System::currentTimeMillis,
|
||||
private val pendingScopedRefreshRetryIntervalMs: Long = AgentSessionThreadRebindPolicy.PENDING_THREAD_REFRESH_RETRY_INTERVAL_MS,
|
||||
editorCoroutineScope: CoroutineScope? = null,
|
||||
private val providerDescriptorResolver: (AgentSessionProvider) -> AgentSessionProviderDescriptor? = AgentSessionProviders::find,
|
||||
private val behaviorResolver: (AgentSessionProvider?) -> AgentChatProviderBehavior = ::resolveAgentChatProviderBehavior,
|
||||
private val customContentProviderResolver: (AgentThreadViewContentContext) -> AgentThreadViewCustomContentProvider? = AgentThreadViewCustomContent::find,
|
||||
private val behaviorResolver: (AgentSessionProvider?) -> AgentThreadViewProviderBehavior = ::resolveAgentThreadViewProviderBehavior,
|
||||
) : UserDataHolderBase(), FileEditor {
|
||||
private val ownedTerminalStartupJob = if (editorCoroutineScope == null) SupervisorJob() else null
|
||||
|
||||
@Suppress("RAW_SCOPE_CREATION")
|
||||
private val terminalStartupScope = editorCoroutineScope ?: CoroutineScope(checkNotNull(ownedTerminalStartupJob) + Dispatchers.Default)
|
||||
private val component = AgentChatFileEditorComponent()
|
||||
private val component = AgentThreadViewFileEditorComponent()
|
||||
|
||||
private fun buildEditorTabActions(): ActionGroup? {
|
||||
val actionManager = ActionManager.getInstance()
|
||||
@@ -94,27 +111,29 @@ internal class AgentChatFileEditor(
|
||||
actionManager.getAction(actionId)?.let(::add)
|
||||
}
|
||||
}
|
||||
return buildAgentChatEditorTabActionGroup(actions)
|
||||
return buildAgentThreadViewEditorTabActionGroup(actions)
|
||||
}
|
||||
|
||||
private var cachedTabActionsProvider: AgentSessionProvider? = null
|
||||
private var cachedTabActions: ActionGroup? = null
|
||||
private var cachedTabActionsInitialized: Boolean = false
|
||||
private var tab: AgentChatTerminalTab? = null
|
||||
private var pendingContextPanel: AgentChatPendingContextPanel? = null
|
||||
private var tab: AgentThreadViewTerminalTab? = null
|
||||
private var pendingContextPanel: AgentThreadViewPendingContextPanel? = null
|
||||
private var pendingContextPanelInstalled: Boolean = false
|
||||
private var initializationStarted: Boolean = false
|
||||
private var initializationRequested: Boolean = false
|
||||
private var customContentInstalled: Boolean = false
|
||||
private var customContentPreferredFocusedComponent: JComponent? = null
|
||||
private var focusTerminalAfterInitialization: Boolean = false
|
||||
private var stateApplied: Boolean = file.projectPath.isNotBlank() || file.threadIdentity.isNotBlank()
|
||||
private var initializationJob: Job? = null
|
||||
private var disposed: Boolean = false
|
||||
private var pendingThreadRefreshController: AgentChatPendingThreadRefreshController? = null
|
||||
private var terminalTitleThreadRebindController: AgentChatDisposableController? = null
|
||||
private var concreteThreadRebindController: AgentChatConcreteThreadRebindController? = null
|
||||
private var initialMessageDispatcher: AgentChatInitialMessageDispatcher? = null
|
||||
private var scopedTerminalRefreshController: AgentChatDisposableController? = null
|
||||
private var terminalRestoreContextController: AgentChatDisposableController? = null
|
||||
private var pendingThreadRefreshController: AgentThreadViewPendingThreadRefreshController? = null
|
||||
private var terminalTitleThreadRebindController: AgentThreadViewDisposableController? = null
|
||||
private var concreteThreadRebindController: AgentThreadViewConcreteThreadRebindController? = null
|
||||
private var initialMessageDispatcher: AgentThreadViewInitialMessageDispatcher? = null
|
||||
private var scopedTerminalRefreshController: AgentThreadViewDisposableController? = null
|
||||
private var terminalRestoreContextController: AgentThreadViewDisposableController? = null
|
||||
private var crossProjectDockTargetRegistration: Disposable? = null
|
||||
|
||||
private val providerDescriptor
|
||||
@@ -124,10 +143,11 @@ internal class AgentChatFileEditor(
|
||||
|
||||
override fun getPreferredFocusedComponent(): JComponent {
|
||||
file.deferredStartContent()?.preferredFocusedComponent?.let { return it }
|
||||
customContentPreferredFocusedComponent?.let { return it }
|
||||
return tab?.preferredFocusableComponent ?: component
|
||||
}
|
||||
|
||||
override fun getName(): String = AgentChatBundle.message("chat.filetype.name")
|
||||
override fun getName(): String = AgentThreadViewBundle.message("thread.view.filetype.name")
|
||||
|
||||
override fun getTabActions(): ActionGroup? {
|
||||
val provider = file.provider
|
||||
@@ -141,20 +161,20 @@ internal class AgentChatFileEditor(
|
||||
|
||||
override fun getState(level: FileEditorStateLevel): FileEditorState {
|
||||
if (!file.shouldRestoreOnRestart() || file.projectPath.isBlank() || file.threadIdentity.isBlank()) {
|
||||
return AgentChatFileEditorState(snapshot = null)
|
||||
return AgentThreadViewFileEditorState(snapshot = null)
|
||||
}
|
||||
return AgentChatFileEditorState(snapshot = file.toSnapshot(), startupIntent = file.startupIntent())
|
||||
return AgentThreadViewFileEditorState(snapshot = file.toSnapshot(), startupIntent = file.startupIntent())
|
||||
}
|
||||
|
||||
override fun setState(state: FileEditorState) {
|
||||
val chatState = state as? AgentChatFileEditorState ?: return
|
||||
val threadViewState = state as? AgentThreadViewFileEditorState ?: return
|
||||
stateApplied = true
|
||||
val providerBeforeUpdate = file.provider
|
||||
val snapshot = chatState.snapshot
|
||||
val snapshot = threadViewState.snapshot
|
||||
if (snapshot != null) {
|
||||
file.updateRestoreOnRestart(true)
|
||||
file.updateFromResolution(AgentChatTabResolution.Resolved(snapshot))
|
||||
file.updateStartupIntent(chatState.startupIntent)
|
||||
file.updateFromResolution(AgentThreadViewTabResolution.Resolved(snapshot))
|
||||
file.updateStartupIntent(threadViewState.startupIntent)
|
||||
ensureCrossProjectDockTargetRegistration()
|
||||
FileEditorManager.getInstance(project).updateFilePresentation(file)
|
||||
}
|
||||
@@ -178,7 +198,7 @@ internal class AgentChatFileEditor(
|
||||
|
||||
override fun removePropertyChangeListener(listener: PropertyChangeListener) = Unit
|
||||
|
||||
override fun getFile(): AgentChatVirtualFile = file
|
||||
override fun getFile(): AgentThreadViewVirtualFile = file
|
||||
|
||||
override fun selectNotify() {
|
||||
if (tab == null) {
|
||||
@@ -204,6 +224,7 @@ internal class AgentChatFileEditor(
|
||||
pendingContextPanel = null
|
||||
pendingContextPanelInstalled = false
|
||||
file.clearDeferredStartContent()
|
||||
disposeDeferredStartProgressTimers(component)
|
||||
component.removeAll()
|
||||
}
|
||||
|
||||
@@ -220,10 +241,13 @@ internal class AgentChatFileEditor(
|
||||
renderDeferredStartState(checkNotNull(deferredStartState))
|
||||
return
|
||||
}
|
||||
if (tryInstallCustomContent()) {
|
||||
return
|
||||
}
|
||||
if (initializationStarted) {
|
||||
return
|
||||
}
|
||||
val validationError = validateAgentChatFile(file)
|
||||
val validationError = validateAgentThreadViewFile(file)
|
||||
if (validationError != null) {
|
||||
handleRestoreValidationError(validationError)
|
||||
return
|
||||
@@ -233,7 +257,7 @@ internal class AgentChatFileEditor(
|
||||
val suppressInitialMessageDispatch = startupLaunchSpecOverride != null && file.consumeSuppressInitialMessageDispatchOnStartup()
|
||||
val startupIntent = file.startupIntent()
|
||||
if (startupLaunchSpecOverride == null && file.isPendingThread && startupIntent == null) {
|
||||
handleRestoreValidationError(AgentChatBundle.message("chat.restore.validation.pending.thread"))
|
||||
handleRestoreValidationError(AgentThreadViewBundle.message("thread.view.restore.validation.pending.thread"))
|
||||
return
|
||||
}
|
||||
initializationJob = terminalStartupScope.launch {
|
||||
@@ -252,18 +276,47 @@ internal class AgentChatFileEditor(
|
||||
throw e
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
AgentChatRestoreNotificationService.reportTerminalInitializationFailure(project, file, e)
|
||||
AgentThreadViewRestoreNotificationService.reportTerminalInitializationFailure(project, file, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun isRestoredArchivedThread(descriptor: AgentSessionProviderDescriptor?): Boolean {
|
||||
val source = descriptor?.sessionSource ?: return false
|
||||
if (!source.supportsArchivedThreads) {
|
||||
return false
|
||||
/**
|
||||
* Installs provider-supplied non-terminal content (e.g. the ACP screen) into the same
|
||||
* editor tab and skips the terminal lifecycle. Returns true when custom content owns this tab.
|
||||
*/
|
||||
private fun tryInstallCustomContent(): Boolean {
|
||||
if (customContentInstalled) {
|
||||
return true
|
||||
}
|
||||
val contentContext = resolveCustomContentContext() ?: return false
|
||||
val contentProvider = customContentProviderResolver(contentContext) ?: return false
|
||||
val deferredStartState = file.deferredStartState
|
||||
if (deferredStartState?.phase == AgentThreadViewDeferredStartPhase.READY_TO_START) {
|
||||
file.updateDeferredStartState(null)
|
||||
}
|
||||
customContentInstalled = true
|
||||
initializationStarted = true
|
||||
ensureCrossProjectDockTargetRegistration()
|
||||
val customComponent = contentProvider.createComponent(
|
||||
project = project,
|
||||
context = contentContext,
|
||||
parent = this,
|
||||
)
|
||||
customContentPreferredFocusedComponent = (customComponent as? AgentThreadViewPreferredFocusableContent)?.preferredFocusedComponent
|
||||
disposeDeferredStartProgressTimers(component)
|
||||
component.removeAll()
|
||||
component.add(customComponent, BorderLayout.CENTER)
|
||||
component.revalidate()
|
||||
component.repaint()
|
||||
file.updateStartupIntent(null)
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun isRestoredArchivedThread(descriptor: AgentSessionProviderDescriptor?): Boolean {
|
||||
val source = descriptor?.sessionSource as? AgentSessionArchivedSource ?: return false
|
||||
val archivedThreads = try {
|
||||
source.listArchivedThreadsFromOpenProject(path = file.projectPath, project = project)
|
||||
source.listArchivedThreads(path = file.projectPath, openProject = project)
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw e
|
||||
@@ -271,7 +324,7 @@ internal class AgentChatFileEditor(
|
||||
catch (_: Throwable) {
|
||||
return false
|
||||
}
|
||||
return archivedThreads.any { thread -> thread.matchesRestoredAgentChatFile(file) }
|
||||
return archivedThreads.any { thread -> thread.matchesRestoredAgentThreadViewFile(file) }
|
||||
}
|
||||
|
||||
private suspend fun awaitEditorComponentShowing() {
|
||||
@@ -285,36 +338,40 @@ internal class AgentChatFileEditor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun resolveLiveTerminalRegistry(): AgentChatLiveTerminalRegistry {
|
||||
return liveTerminalRegistry ?: project.serviceAsync<AgentChatLiveTerminalRegistryService>()
|
||||
private suspend fun resolveLiveTerminalRegistry(): AgentThreadViewLiveTerminalRegistry {
|
||||
return liveTerminalRegistry ?: project.serviceAsync<AgentThreadViewLiveTerminalRegistryService>()
|
||||
}
|
||||
|
||||
private suspend fun resolveStartupLaunchSpec(startupIntent: AgentChatStartupIntent?): AgentSessionTerminalLaunchSpec {
|
||||
private suspend fun resolveStartupLaunchSpec(startupIntent: AgentThreadViewStartupIntent?): AgentSessionTerminalLaunchSpec {
|
||||
return when (startupIntent) {
|
||||
is AgentChatStartupIntent.NewSession -> resolveNewSessionLaunchSpec(startupIntent)
|
||||
is AgentThreadViewStartupIntent.NewSession -> resolveNewSessionLaunchSpec(startupIntent)
|
||||
null -> resolveResumeLaunchSpec()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun resolveNewSessionLaunchSpec(startupIntent: AgentChatStartupIntent.NewSession): AgentSessionTerminalLaunchSpec {
|
||||
private suspend fun resolveNewSessionLaunchSpec(startupIntent: AgentThreadViewStartupIntent.NewSession): AgentSessionTerminalLaunchSpec {
|
||||
val resolvedLaunchProfile = resolveLaunchProfile(
|
||||
launchProfileId = startupIntent.launchProfileId,
|
||||
requiredProvider = startupIntent.provider,
|
||||
)
|
||||
val provider = resolvedLaunchProfile?.provider ?: startupIntent.provider
|
||||
val launchMode = resolvedLaunchProfile?.launchMode ?: startupIntent.launchMode
|
||||
val launchTargetId = resolvedLaunchProfile?.launchTargetId ?: startupIntent.launchTargetId
|
||||
val generationSettings = resolvedLaunchProfile?.generationSettings ?: file.generationSettings
|
||||
val descriptor = AgentSessionProviders.find(provider)
|
||||
?: throw IllegalStateException("Missing Agent Chat provider for ${provider.value}")
|
||||
?: throw IllegalStateException("Missing Agent Thread View provider for ${provider.value}")
|
||||
if (launchMode !in descriptor.supportedLaunchModes) {
|
||||
throw IllegalStateException("Unsupported Agent Chat launch mode $launchMode for ${provider.value}")
|
||||
throw IllegalStateException("Unsupported Agent Thread View launch mode $launchMode for ${provider.value}")
|
||||
}
|
||||
return AgentSessionLaunchPlanner.plan(
|
||||
intent = AgentSessionLaunchIntent(
|
||||
projectPath = file.projectPath,
|
||||
projectDirectory = file.projectDirectory,
|
||||
provider = provider,
|
||||
operation = AgentSessionLaunchOperation.NEW,
|
||||
launchMode = launchMode,
|
||||
launchTargetId = launchTargetId,
|
||||
surfaceId = resolvedLaunchProfile?.surfaceId ?: startupIntent.surfaceId,
|
||||
generationSettings = generationSettings,
|
||||
),
|
||||
project = project,
|
||||
@@ -322,18 +379,23 @@ internal class AgentChatFileEditor(
|
||||
}
|
||||
|
||||
private suspend fun resolveResumeLaunchSpec(): AgentSessionTerminalLaunchSpec {
|
||||
val provider = file.provider ?: throw IllegalStateException("Missing Agent Chat provider for ${file.url}")
|
||||
val provider = file.provider ?: throw IllegalStateException("Missing Agent Thread View provider for ${file.url}")
|
||||
val resolvedLaunchProfile = resolveLaunchProfile(
|
||||
launchProfileId = file.launchProfileId,
|
||||
requiredProvider = provider,
|
||||
)
|
||||
val launchTargetId = resolvedLaunchProfile?.launchTargetId ?: file.launchTargetId
|
||||
val surfaceId = resolvedLaunchProfile?.surfaceId ?: parseAgentThreadViewSurfaceId(file.surfaceId)
|
||||
return AgentSessionLaunchPlanner.plan(
|
||||
intent = AgentSessionLaunchIntent(
|
||||
projectPath = file.projectPath,
|
||||
projectDirectory = file.projectDirectory,
|
||||
provider = provider,
|
||||
operation = AgentSessionLaunchOperation.RESUME,
|
||||
sessionId = file.threadId.ifBlank { file.sessionId },
|
||||
launchMode = resolvedLaunchProfile?.launchMode ?: parseAgentChatLaunchMode(file.launchMode),
|
||||
launchMode = resolvedLaunchProfile?.launchMode ?: parseAgentThreadViewLaunchMode(file.launchMode),
|
||||
launchTargetId = launchTargetId,
|
||||
surfaceId = surfaceId,
|
||||
generationSettings = resolvedLaunchProfile?.generationSettings ?: file.generationSettings,
|
||||
),
|
||||
project = project,
|
||||
@@ -350,8 +412,29 @@ internal class AgentChatFileEditor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveCustomContentContext(): AgentThreadViewContentContext? {
|
||||
val provider = file.provider ?: return null
|
||||
val startupIntent = file.startupIntent() as? AgentThreadViewStartupIntent.NewSession
|
||||
val launchProfileId = startupIntent?.launchProfileId ?: file.launchProfileId
|
||||
val resolvedLaunchProfile = resolveLaunchProfile(
|
||||
launchProfileId = launchProfileId,
|
||||
requiredProvider = provider,
|
||||
)
|
||||
val requestedSurfaceId = resolvedLaunchProfile?.surfaceId ?: startupIntent?.surfaceId ?: parseAgentThreadViewSurfaceId(file.surfaceId)
|
||||
val surfaceId = providerDescriptorResolver(provider)?.let { descriptor ->
|
||||
effectiveAgentSessionSurfaceId(descriptor, requestedSurfaceId)
|
||||
} ?: requestedSurfaceId ?: AgentSessionSurfaces.TERMINAL
|
||||
return AgentThreadViewContentContext(
|
||||
provider = provider,
|
||||
surfaceId = surfaceId,
|
||||
launchTargetId = resolvedLaunchProfile?.launchTargetId ?: startupIntent?.launchTargetId ?: file.launchTargetId,
|
||||
threadIdentity = file.threadIdentity,
|
||||
threadId = file.threadId.ifBlank { file.threadIdentity },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun attachTerminal(
|
||||
liveTerminalRegistry: AgentChatLiveTerminalRegistry,
|
||||
liveTerminalRegistry: AgentThreadViewLiveTerminalRegistry,
|
||||
startupLaunchSpec: AgentSessionTerminalLaunchSpec,
|
||||
suppressInitialMessageDispatch: Boolean,
|
||||
) {
|
||||
@@ -366,7 +449,7 @@ internal class AgentChatFileEditor(
|
||||
}
|
||||
|
||||
private fun attachTerminalOnEdt(
|
||||
liveTerminalRegistry: AgentChatLiveTerminalRegistry,
|
||||
liveTerminalRegistry: AgentThreadViewLiveTerminalRegistry,
|
||||
startupLaunchSpec: AgentSessionTerminalLaunchSpec,
|
||||
suppressInitialMessageDispatch: Boolean = false,
|
||||
) {
|
||||
@@ -375,7 +458,7 @@ internal class AgentChatFileEditor(
|
||||
}
|
||||
ensureCrossProjectDockTargetRegistration()
|
||||
val deferredStartState = file.deferredStartState
|
||||
if (deferredStartState?.phase == AgentChatDeferredStartPhase.READY_TO_START) {
|
||||
if (deferredStartState?.phase == AgentThreadViewDeferredStartPhase.READY_TO_START) {
|
||||
file.updateDeferredStartState(null)
|
||||
}
|
||||
file.clearDeferredStartContent()
|
||||
@@ -393,7 +476,7 @@ internal class AgentChatFileEditor(
|
||||
if (file.isPendingThread) {
|
||||
file.updateRestoreOnRestart(false)
|
||||
}
|
||||
val pendingController = AgentChatPendingThreadRefreshController(
|
||||
val pendingController = AgentThreadViewPendingThreadRefreshController(
|
||||
file = file,
|
||||
behavior = behavior,
|
||||
tabSnapshotWriter = tabSnapshotWriter,
|
||||
@@ -401,14 +484,14 @@ internal class AgentChatFileEditor(
|
||||
retryIntervalMs = pendingScopedRefreshRetryIntervalMs,
|
||||
)
|
||||
pendingThreadRefreshController = pendingController
|
||||
val concreteController = AgentChatConcreteThreadRebindController(
|
||||
val concreteController = AgentThreadViewConcreteThreadRebindController(
|
||||
file = file,
|
||||
behavior = behavior,
|
||||
tabSnapshotWriter = tabSnapshotWriter,
|
||||
currentTimeProvider = currentTimeProvider,
|
||||
)
|
||||
concreteThreadRebindController = concreteController
|
||||
val messageDispatcher = AgentChatInitialMessageDispatcher(
|
||||
val messageDispatcher = AgentThreadViewInitialMessageDispatcher(
|
||||
project = project,
|
||||
file = file,
|
||||
behavior = behavior,
|
||||
@@ -421,31 +504,32 @@ internal class AgentChatFileEditor(
|
||||
if (!suppressInitialMessageDispatch) {
|
||||
messageDispatcher.schedule(createdTab)
|
||||
}
|
||||
scopedTerminalRefreshController = createAgentChatScopedTerminalRefreshController(file, createdTab, providerDescriptor)
|
||||
val restoreContextController = AgentChatTerminalRestoreContextController(
|
||||
scopedTerminalRefreshController = createAgentThreadViewScopedTerminalRefreshController(file, createdTab, providerDescriptor)
|
||||
val restoreContextController = AgentThreadViewTerminalRestoreContextController(
|
||||
file = file,
|
||||
descriptor = providerDescriptor,
|
||||
parentDisposable = this,
|
||||
)
|
||||
terminalRestoreContextController = restoreContextController
|
||||
restoreContextController.attach(createdTab)
|
||||
terminalTitleThreadRebindController = createAgentChatTerminalTitleThreadRebindController(
|
||||
terminalTitleThreadRebindController = createAgentThreadViewTerminalTitleThreadRebindController(
|
||||
file = file,
|
||||
tab = createdTab,
|
||||
tabSnapshotWriter = tabSnapshotWriter,
|
||||
)
|
||||
installPendingContextInterceptor(createdTab)
|
||||
disposeDeferredStartProgressTimers(component)
|
||||
component.removeAll()
|
||||
pendingContextPanelInstalled = false
|
||||
component.add(createdTab.component, BorderLayout.CENTER)
|
||||
installAgentChatTerminalFileDropSupport(createdTab.component, createdTab, this)
|
||||
installAgentThreadViewTerminalFileDropSupport(createdTab.component, createdTab, this)
|
||||
pendingContextPanel?.let(::ensurePendingContextPanelInstalled)
|
||||
component.revalidate()
|
||||
component.repaint()
|
||||
focusTerminalIfRequested(createdTab)
|
||||
}
|
||||
|
||||
private fun focusTerminalIfRequested(createdTab: AgentChatTerminalTab) {
|
||||
private fun focusTerminalIfRequested(createdTab: AgentThreadViewTerminalTab) {
|
||||
if (!focusTerminalAfterInitialization) {
|
||||
return
|
||||
}
|
||||
@@ -453,21 +537,21 @@ internal class AgentChatFileEditor(
|
||||
createdTab.preferredFocusableComponent.requestFocusInWindow()
|
||||
}
|
||||
|
||||
private fun getOrCreatePendingContextPanel(): AgentChatPendingContextPanel {
|
||||
private fun getOrCreatePendingContextPanel(): AgentThreadViewPendingContextPanel {
|
||||
pendingContextPanel?.let {
|
||||
return it
|
||||
}
|
||||
return AgentChatPendingContextPanel(file.projectPath).also { panel ->
|
||||
return AgentThreadViewPendingContextPanel(file.projectPath).also { panel ->
|
||||
pendingContextPanel = panel
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensurePendingContextPanelInstalled(panel: AgentChatPendingContextPanel) {
|
||||
private fun ensurePendingContextPanelInstalled(panel: AgentThreadViewPendingContextPanel) {
|
||||
if (pendingContextPanelInstalled) {
|
||||
return
|
||||
}
|
||||
component.add(panel.component, BorderLayout.SOUTH)
|
||||
installAgentChatContextFileDropSupport(panel.component, ::addPendingContextItems, this)
|
||||
installAgentThreadViewContextFileDropSupport(panel.component, ::addPendingContextItems, this)
|
||||
pendingContextPanelInstalled = true
|
||||
component.revalidate()
|
||||
component.repaint()
|
||||
@@ -475,7 +559,7 @@ internal class AgentChatFileEditor(
|
||||
|
||||
private fun ensureCrossProjectDockTargetRegistration() {
|
||||
if (crossProjectDockTargetRegistration == null && file.projectPath.isNotBlank()) {
|
||||
crossProjectDockTargetRegistration = AgentChatCrossProjectDockTargetRegistrar().register(project, file)
|
||||
crossProjectDockTargetRegistration = AgentThreadViewCrossProjectDockTargetRegistrar().register(project, file)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,8 +570,8 @@ internal class AgentChatFileEditor(
|
||||
}
|
||||
return
|
||||
}
|
||||
forgetAgentChatTabMetadata(file.tabKey)
|
||||
AgentChatRestoreNotificationService.reportRestoreFailure(project, file, validationError)
|
||||
forgetAgentThreadViewTabMetadata(file.tabKey)
|
||||
AgentThreadViewRestoreNotificationService.reportRestoreFailure(project, file, validationError)
|
||||
if (!project.isDisposed) {
|
||||
FileEditorManager.getInstance(project).closeFile(file)
|
||||
}
|
||||
@@ -553,11 +637,12 @@ internal class AgentChatFileEditor(
|
||||
component.showForTests()
|
||||
}
|
||||
|
||||
private fun renderDeferredStartState(state: AgentChatDeferredStartState) {
|
||||
val deferredContent = if (state.phase == AgentChatDeferredStartPhase.WAITING) file.deferredStartContent() else null
|
||||
private fun renderDeferredStartState(state: AgentThreadViewDeferredStartState) {
|
||||
val deferredContent = if (state.phase == AgentThreadViewDeferredStartPhase.WAITING) file.deferredStartContent() else null
|
||||
if (deferredContent == null) {
|
||||
file.clearDeferredStartContent()
|
||||
}
|
||||
disposeDeferredStartProgressTimers(component)
|
||||
component.removeAll()
|
||||
component.add(deferredContent?.component ?: createDeferredStartComponent(state), BorderLayout.CENTER)
|
||||
component.revalidate()
|
||||
@@ -565,7 +650,7 @@ internal class AgentChatFileEditor(
|
||||
}
|
||||
|
||||
private fun restartTerminalOnEdt(
|
||||
liveTerminalRegistry: AgentChatLiveTerminalRegistry,
|
||||
liveTerminalRegistry: AgentThreadViewLiveTerminalRegistry,
|
||||
startupLaunchSpec: AgentSessionTerminalLaunchSpec,
|
||||
replaceRetainedTerminal: Boolean,
|
||||
): Boolean {
|
||||
@@ -602,6 +687,8 @@ internal class AgentChatFileEditor(
|
||||
terminalRestoreContextController?.dispose()
|
||||
terminalRestoreContextController = null
|
||||
tab = null
|
||||
customContentPreferredFocusedComponent = null
|
||||
disposeDeferredStartProgressTimers(component)
|
||||
component.removeAll()
|
||||
pendingContextPanelInstalled = false
|
||||
if (clearPendingContextPanel) {
|
||||
@@ -609,11 +696,11 @@ internal class AgentChatFileEditor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun installPendingContextInterceptor(tab: AgentChatTerminalTab) {
|
||||
private fun installPendingContextInterceptor(tab: AgentThreadViewTerminalTab) {
|
||||
tab.addInputInterceptor(this, TerminalInputInterceptor { event -> handlePendingContextInput(tab, event) })
|
||||
}
|
||||
|
||||
private fun handlePendingContextInput(tab: AgentChatTerminalTab, event: KeyEvent): Boolean {
|
||||
private fun handlePendingContextInput(tab: AgentThreadViewTerminalTab, event: KeyEvent): Boolean {
|
||||
val panel = pendingContextPanel
|
||||
if (panel == null || !panel.hasItems() || !isPlainEnter(event)) {
|
||||
return false
|
||||
@@ -621,15 +708,15 @@ internal class AgentChatFileEditor(
|
||||
|
||||
val promptSuffix = resolvePendingContextPromptSuffix(panel) ?: return true
|
||||
when (tab.sendPendingContextAndExecute(promptSuffix)) {
|
||||
AgentChatPendingContextSubmissionResult.SUBMITTED -> panel.clear()
|
||||
AgentChatPendingContextSubmissionResult.UNAVAILABLE -> {
|
||||
StatusBar.Info.set(AgentChatBundle.message("chat.pending.context.terminal.unavailable"), project)
|
||||
AgentThreadViewPendingContextSubmissionResult.SUBMITTED -> panel.clear()
|
||||
AgentThreadViewPendingContextSubmissionResult.UNAVAILABLE -> {
|
||||
StatusBar.Info.set(AgentThreadViewBundle.message("thread.view.pending.context.terminal.unavailable"), project)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun resolvePendingContextPromptSuffix(panel: AgentChatPendingContextPanel): String? {
|
||||
private fun resolvePendingContextPromptSuffix(panel: AgentThreadViewPendingContextPanel): String? {
|
||||
val items = panel.pendingItemsSnapshot()
|
||||
if (items.isEmpty()) {
|
||||
return null
|
||||
@@ -650,11 +737,11 @@ internal class AgentChatFileEditor(
|
||||
|
||||
val choice = Messages.showDialog(
|
||||
project,
|
||||
AgentChatBundle.message("chat.pending.context.softcap.message", serializedChars, softCapChars),
|
||||
AgentChatBundle.message("chat.pending.context.softcap.title"),
|
||||
AgentThreadViewBundle.message("thread.view.pending.context.softcap.message", serializedChars, softCapChars),
|
||||
AgentThreadViewBundle.message("thread.view.pending.context.softcap.title"),
|
||||
arrayOf(
|
||||
AgentChatBundle.message("chat.pending.context.softcap.action.send.full"),
|
||||
AgentChatBundle.message("chat.pending.context.softcap.action.auto.trim"),
|
||||
AgentThreadViewBundle.message("thread.view.pending.context.softcap.action.send.full"),
|
||||
AgentThreadViewBundle.message("thread.view.pending.context.softcap.action.auto.trim"),
|
||||
CommonBundle.getCancelButtonText(),
|
||||
),
|
||||
0,
|
||||
@@ -695,24 +782,98 @@ internal class AgentChatFileEditor(
|
||||
cachedTabActionsInitialized = false
|
||||
}
|
||||
|
||||
private fun createDeferredStartComponent(state: AgentChatDeferredStartState): JComponent {
|
||||
private fun createDeferredStartComponent(state: AgentThreadViewDeferredStartState): JComponent {
|
||||
val rootPanel = JPanel(GridBagLayout())
|
||||
val content = JPanel().apply {
|
||||
layout = BoxLayout(this, BoxLayout.Y_AXIS)
|
||||
border = BorderFactory.createEmptyBorder(16, 16, 16, 16)
|
||||
border = JBUI.Borders.empty(16)
|
||||
isOpaque = false
|
||||
}
|
||||
content.add(createMessageArea(state.title, bold = true))
|
||||
if (state.phase == AgentThreadViewDeferredStartPhase.WAITING) {
|
||||
content.add(createDelayedDeferredStartProgressIcon())
|
||||
content.add(Box.createVerticalStrut(JBUI.scale(8)))
|
||||
}
|
||||
content.add(createMessageArea(state.title).apply {
|
||||
alignmentX = Component.CENTER_ALIGNMENT
|
||||
})
|
||||
val stateMessage = state.message
|
||||
if (!stateMessage.isNullOrBlank()) {
|
||||
content.add(Box.createVerticalStrut(8))
|
||||
content.add(createMessageArea(stateMessage, bold = false))
|
||||
content.add(Box.createVerticalStrut(JBUI.scale(4)))
|
||||
content.add(createMessageArea(stateMessage, secondary = true).apply {
|
||||
alignmentX = Component.CENTER_ALIGNMENT
|
||||
})
|
||||
}
|
||||
return JPanel(BorderLayout()).apply {
|
||||
add(content, BorderLayout.NORTH)
|
||||
rootPanel.accessibleContext.accessibleName = buildDeferredStartAccessibleName(state)
|
||||
rootPanel.add(content)
|
||||
return rootPanel
|
||||
}
|
||||
|
||||
private fun createDelayedDeferredStartProgressIcon(): JComponent {
|
||||
val icon = createDeferredStartProgressComponent().apply {
|
||||
name = DEFERRED_START_PROGRESS_NAME
|
||||
isVisible = false
|
||||
suspendDeferredStartProgress()
|
||||
}
|
||||
val iconSize = icon.preferredSize
|
||||
val iconPanel = JPanel(BorderLayout()).apply {
|
||||
alignmentX = Component.CENTER_ALIGNMENT
|
||||
isOpaque = false
|
||||
preferredSize = iconSize
|
||||
minimumSize = iconSize
|
||||
maximumSize = Dimension(iconSize.width, iconSize.height)
|
||||
add(icon, BorderLayout.CENTER)
|
||||
}
|
||||
val timer = Timer(DEFERRED_START_PROGRESS_DELAY_MS) {
|
||||
iconPanel.putClientProperty(DEFERRED_START_PROGRESS_TIMER_PROPERTY, null)
|
||||
if (iconPanel.parent != null) {
|
||||
icon.isVisible = true
|
||||
icon.resumeDeferredStartProgress()
|
||||
iconPanel.revalidate()
|
||||
iconPanel.repaint()
|
||||
}
|
||||
}.apply {
|
||||
isRepeats = false
|
||||
start()
|
||||
}
|
||||
iconPanel.putClientProperty(DEFERRED_START_PROGRESS_TIMER_PROPERTY, timer)
|
||||
return iconPanel
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMessageArea(text: @Nls String, bold: Boolean): JComponent {
|
||||
private fun disposeDeferredStartProgressTimers(component: Component) {
|
||||
if (component is JComponent) {
|
||||
(component.getClientProperty(DEFERRED_START_PROGRESS_TIMER_PROPERTY) as? Timer)?.stop()
|
||||
component.putClientProperty(DEFERRED_START_PROGRESS_TIMER_PROPERTY, null)
|
||||
}
|
||||
if (component is Container) {
|
||||
component.components.forEach(::disposeDeferredStartProgressTimers)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDeferredStartProgressComponent(): JComponent {
|
||||
if (ApplicationManager.getApplication() == null) {
|
||||
return JProgressBar().apply {
|
||||
isIndeterminate = true
|
||||
isBorderPainted = false
|
||||
}
|
||||
}
|
||||
return AsyncProcessIcon(DEFERRED_START_PROGRESS_NAME)
|
||||
}
|
||||
|
||||
private fun JComponent.suspendDeferredStartProgress() {
|
||||
(this as? AsyncProcessIcon)?.suspend()
|
||||
}
|
||||
|
||||
private fun JComponent.resumeDeferredStartProgress() {
|
||||
(this as? AsyncProcessIcon)?.resume()
|
||||
}
|
||||
|
||||
private fun buildDeferredStartAccessibleName(state: AgentThreadViewDeferredStartState): @Nls String {
|
||||
val stateMessage = state.message?.takeIf { it.isNotBlank() } ?: return state.title
|
||||
return "${state.title}. $stateMessage"
|
||||
}
|
||||
|
||||
private fun createMessageArea(text: @Nls String, secondary: Boolean = false): JComponent {
|
||||
return JTextArea(text).apply {
|
||||
isEditable = false
|
||||
isFocusable = false
|
||||
@@ -720,7 +881,9 @@ private fun createMessageArea(text: @Nls String, bold: Boolean): JComponent {
|
||||
wrapStyleWord = true
|
||||
isOpaque = false
|
||||
border = null
|
||||
font = if (bold) font.deriveFont(font.style or java.awt.Font.BOLD) else font
|
||||
if (secondary) {
|
||||
foreground = UIUtil.getContextHelpForeground()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,18 +892,18 @@ private fun isPlainEnter(event: KeyEvent): Boolean {
|
||||
return event.id == KeyEvent.KEY_PRESSED && event.keyCode == KeyEvent.VK_ENTER && event.modifiersEx == 0
|
||||
}
|
||||
|
||||
private fun shouldBlockTerminalInitialization(state: AgentChatDeferredStartState?): Boolean {
|
||||
private fun shouldBlockTerminalInitialization(state: AgentThreadViewDeferredStartState?): Boolean {
|
||||
return when (state?.phase) {
|
||||
AgentChatDeferredStartPhase.WAITING,
|
||||
AgentChatDeferredStartPhase.SUCCESS_NO_START,
|
||||
AgentChatDeferredStartPhase.FAILURE_NO_START,
|
||||
AgentThreadViewDeferredStartPhase.WAITING,
|
||||
AgentThreadViewDeferredStartPhase.SUCCESS_NO_START,
|
||||
AgentThreadViewDeferredStartPhase.FAILURE_NO_START,
|
||||
-> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private class AgentChatFileEditorComponent : JPanel(BorderLayout()) {
|
||||
private class AgentThreadViewFileEditorComponent : JPanel(BorderLayout()) {
|
||||
private var showingForTests: Boolean = false
|
||||
private var showingContinuation: CancellableContinuation<Unit>? = null
|
||||
private var showingListener: HierarchyListener? = null
|
||||
@@ -754,7 +917,7 @@ private class AgentChatFileEditorComponent : JPanel(BorderLayout()) {
|
||||
return
|
||||
}
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
showingContinuation?.cancel(CancellationException("Superseded by a new Agent Chat editor showing waiter"))
|
||||
showingContinuation?.cancel(CancellationException("Superseded by a new Agent Thread View editor showing waiter"))
|
||||
removeShowingListener()
|
||||
showingContinuation = continuation
|
||||
val listener = HierarchyListener { event ->
|
||||
@@ -777,7 +940,7 @@ private class AgentChatFileEditorComponent : JPanel(BorderLayout()) {
|
||||
}
|
||||
|
||||
fun cancelAwaitShowing() {
|
||||
showingContinuation?.cancel(CancellationException("Agent Chat editor disposed before it was shown"))
|
||||
showingContinuation?.cancel(CancellationException("Agent Thread View editor disposed before it was shown"))
|
||||
showingContinuation = null
|
||||
removeShowingListener()
|
||||
}
|
||||
@@ -804,22 +967,22 @@ private class AgentChatFileEditorComponent : JPanel(BorderLayout()) {
|
||||
|
||||
}
|
||||
|
||||
internal fun interface AgentChatTabSnapshotWriter {
|
||||
suspend fun upsert(snapshot: AgentChatTabSnapshot)
|
||||
internal fun interface AgentThreadViewTabSnapshotWriter {
|
||||
suspend fun upsert(snapshot: AgentThreadViewTabSnapshot)
|
||||
}
|
||||
|
||||
private object ApplicationAgentChatTabSnapshotWriter : AgentChatTabSnapshotWriter {
|
||||
private object ApplicationAgentThreadViewTabSnapshotWriter : AgentThreadViewTabSnapshotWriter {
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
override suspend fun upsert(snapshot: AgentChatTabSnapshot) = Unit
|
||||
override suspend fun upsert(snapshot: AgentThreadViewTabSnapshot) = Unit
|
||||
}
|
||||
|
||||
internal fun interface AgentChatArchivedRestoreHandler {
|
||||
suspend fun closeAndForget(file: AgentChatVirtualFile)
|
||||
internal fun interface AgentThreadViewArchivedRestoreHandler {
|
||||
suspend fun closeAndForget(file: AgentThreadViewVirtualFile)
|
||||
}
|
||||
|
||||
private object ApplicationAgentChatArchivedRestoreHandler : AgentChatArchivedRestoreHandler {
|
||||
override suspend fun closeAndForget(file: AgentChatVirtualFile) {
|
||||
closeAndForgetAgentChatsForThread(
|
||||
private object ApplicationAgentThreadViewArchivedRestoreHandler : AgentThreadViewArchivedRestoreHandler {
|
||||
override suspend fun closeAndForget(file: AgentThreadViewVirtualFile) {
|
||||
closeAndForgetAgentThreadViewsForThread(
|
||||
projectPath = file.projectPath,
|
||||
threadIdentity = file.threadIdentity,
|
||||
subAgentId = file.subAgentId,
|
||||
@@ -827,7 +990,7 @@ private object ApplicationAgentChatArchivedRestoreHandler : AgentChatArchivedRes
|
||||
}
|
||||
}
|
||||
|
||||
internal fun AgentSessionThread.matchesRestoredAgentChatFile(file: AgentChatVirtualFile): Boolean {
|
||||
internal fun AgentSessionThread.matchesRestoredAgentThreadViewFile(file: AgentThreadViewVirtualFile): Boolean {
|
||||
if (provider != file.provider) {
|
||||
return false
|
||||
}
|
||||
@@ -838,18 +1001,18 @@ internal fun AgentSessionThread.matchesRestoredAgentChatFile(file: AgentChatVirt
|
||||
return subAgents.any { subAgent -> subAgent.id == restoredSubAgentId || subAgent.id == file.threadId }
|
||||
}
|
||||
|
||||
internal fun buildAgentChatEditorTabActionGroup(actions: List<AnAction>): ActionGroup? {
|
||||
internal fun buildAgentThreadViewEditorTabActionGroup(actions: List<AnAction>): ActionGroup? {
|
||||
if (actions.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
if (actions.size == 1) {
|
||||
val singleAction = actions.single()
|
||||
return singleAction as? ActionGroup ?: DumbAwareAgentChatActionGroup(singleAction)
|
||||
return singleAction as? ActionGroup ?: DumbAwareAgentThreadViewActionGroup(singleAction)
|
||||
}
|
||||
return DumbAwareAgentChatActionGroup(actions)
|
||||
return DumbAwareAgentThreadViewActionGroup(actions)
|
||||
}
|
||||
|
||||
private class DumbAwareAgentChatActionGroup : DefaultActionGroup, DumbAware {
|
||||
private class DumbAwareAgentThreadViewActionGroup : DefaultActionGroup, DumbAware {
|
||||
constructor(vararg actions: AnAction) : super(*actions)
|
||||
|
||||
constructor(actions: List<AnAction>) : super(actions)
|
||||
|
||||
+551
-251
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user