IJPL-248623 move Agent Workbench modules into AIR

Move the remaining Agent Workbench shared libraries, prompt core, and sessions core modules out of the community agent-workbench tree and into the AIR plugin layout. The shared utility modules now live under plugins/air/shared, while prompt and sessions modules are packaged with the rest of AIR.

Update module names, package prefixes, plugin descriptors, API dumps, specs, Patronus rules, and generated Bazel/JPS metadata so product packaging and project-structure checks use the AIR locations directly.

IJ-MR-184958 IJ-MR-184993 IJ-MR-179029 IJ-MR-184126 IJ-MR-181153 IJ-MR-146078 IJ-MR-175479 IJ-MR-186058 IJ-MR-193195 IJ-MR-196957 IJ-MR-199124 IJ-MR-197441 IJ-MR-204135 IJ-MR-204674 IJ-MR-205883 IJ-MR-208539

GitOrigin-RevId: a389389e072d4f58b38cda84156b3e1fbd445ad3
This commit is contained in:
Vladimir Krivosheev
2026-07-09 18:59:28 +00:00
committed by intellij-monorepo-bot
parent 6ed08624c9
commit 019a5880bb
152 changed files with 0 additions and 14564 deletions
-13
View File
@@ -47,8 +47,6 @@
<module fileurl="file://$PROJECT_DIR$/fleet/util/logging/slf4j/fleet.util.logging.slf4j.iml" filepath="$PROJECT_DIR$/fleet/util/logging/slf4j/fleet.util.logging.slf4j.iml" />
<module fileurl="file://$PROJECT_DIR$/fleet/util/multiplatform/fleet.util.multiplatform.iml" filepath="$PROJECT_DIR$/fleet/util/multiplatform/fleet.util.multiplatform.iml" />
<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/prompt/core/intellij.agent.workbench.prompt.core.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/prompt/core/intellij.agent.workbench.prompt.core.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/prompt/core/intellij.agent.workbench.prompt.core.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/prompt/core/intellij.agent.workbench.prompt.core.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/android/android-adb/intellij.android.adb.iml" filepath="$PROJECT_DIR$/android/android-adb/intellij.android.adb.iml" />
<module fileurl="file://$PROJECT_DIR$/android/android-adb/intellij.android.adb.testUtil.iml" filepath="$PROJECT_DIR$/android/android-adb/intellij.android.adb.testUtil.iml" />
<module fileurl="file://$PROJECT_DIR$/android/android-adb/intellij.android.adb.tests.iml" filepath="$PROJECT_DIR$/android/android-adb/intellij.android.adb.tests.iml" />
@@ -1234,17 +1232,6 @@
<module fileurl="file://$PROJECT_DIR$/platform/acp/intellij.platform.acp.iml" filepath="$PROJECT_DIR$/platform/acp/intellij.platform.acp.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/acp/plugin/intellij.platform.acp.plugin.iml" filepath="$PROJECT_DIR$/platform/acp/plugin/intellij.platform.acp.plugin.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/acp/tests/intellij.platform.acp.tests.iml" filepath="$PROJECT_DIR$/platform/acp/tests/intellij.platform.acp.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/lib-agent/cli/intellij.platform.ai.agent.cli.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/lib-agent/cli/intellij.platform.ai.agent.cli.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/lib-agent/cli/intellij.platform.ai.agent.cli.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/lib-agent/cli/intellij.platform.ai.agent.cli.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/lib-agent/common/intellij.platform.ai.agent.common.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/lib-agent/common/intellij.platform.ai.agent.common.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/lib-agent/common/intellij.platform.ai.agent.common.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/lib-agent/common/intellij.platform.ai.agent.common.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/lib-agent/core/intellij.platform.ai.agent.core.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/lib-agent/core/intellij.platform.ai.agent.core.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/lib-agent/core/intellij.platform.ai.agent.core.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/lib-agent/core/intellij.platform.ai.agent.core.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/lib-agent/filewatch/intellij.platform.ai.agent.filewatch.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/lib-agent/filewatch/intellij.platform.ai.agent.filewatch.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/lib-agent/filewatch/intellij.platform.ai.agent.filewatch.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/lib-agent/filewatch/intellij.platform.ai.agent.filewatch.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/lib-agent/json/intellij.platform.ai.agent.json.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/lib-agent/json/intellij.platform.ai.agent.json.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/lib-agent/json/intellij.platform.ai.agent.json.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/lib-agent/json/intellij.platform.ai.agent.json.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/lib-agent/sessions-core/intellij.platform.ai.agent.sessions.core.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/lib-agent/sessions-core/intellij.platform.ai.agent.sessions.core.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/analysis-api/intellij.platform.analysis.iml" filepath="$PROJECT_DIR$/platform/analysis-api/intellij.platform.analysis.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/analysis-impl/intellij.platform.analysis.impl.iml" filepath="$PROJECT_DIR$/platform/analysis-impl/intellij.platform.analysis.impl.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/backend/intellij.platform.backend.iml" filepath="$PROJECT_DIR$/platform/backend/intellij.platform.backend.iml" />
-12
View File
@@ -876,18 +876,6 @@ jvm_library(
"//plugins/lombok:lombok-tests_test_lib",
"//platform/util/concurrency:concurrency-tests",
"//platform/util/concurrency:concurrency-tests_test_lib",
"//plugins/agent-workbench/lib-agent/cli:ai-agent-cli-tests",
"//plugins/agent-workbench/lib-agent/cli:ai-agent-cli-tests_test_lib",
"//plugins/agent-workbench/lib-agent/common:ai-agent-common-tests",
"//plugins/agent-workbench/lib-agent/common:ai-agent-common-tests_test_lib",
"//plugins/agent-workbench/lib-agent/core:ai-agent-core-tests",
"//plugins/agent-workbench/lib-agent/core:ai-agent-core-tests_test_lib",
"//plugins/agent-workbench/lib-agent/filewatch:ai-agent-filewatch-tests",
"//plugins/agent-workbench/lib-agent/filewatch:ai-agent-filewatch-tests_test_lib",
"//plugins/agent-workbench/lib-agent/json:ai-agent-json-tests",
"//plugins/agent-workbench/lib-agent/json:ai-agent-json-tests_test_lib",
"//plugins/agent-workbench/prompt/core:agent-workbench-prompt-core-tests",
"//plugins/agent-workbench/prompt/core:agent-workbench-prompt-core-tests_test_lib",
"//plugins/ant:ant-tests",
"//plugins/ant:ant-tests_test_lib",
"//plugins/commander:commander-tests",
-7
View File
@@ -979,13 +979,6 @@ plugins/ByteCodeViewer
plugins/IntelliLang
plugins/IntelliLang/backend
plugins/IntelliLang/tests
plugins/agent-workbench/lib-agent/cli
plugins/agent-workbench/lib-agent/common
plugins/agent-workbench/lib-agent/core
plugins/agent-workbench/lib-agent/filewatch
plugins/agent-workbench/lib-agent/json
plugins/agent-workbench/lib-agent/sessions-core
plugins/agent-workbench/prompt/core
plugins/ant
plugins/ant/jps-plugin
plugins/built-in-help
-6
View File
@@ -138,12 +138,6 @@
<orderEntry type="module" module-name="intellij.java.rareRefactorings.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.lombok.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.concurrency.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.cli.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.common.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.core.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.filewatch.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.json.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.agent.workbench.prompt.core.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.ant.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.commander.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.community.wintools.tests" scope="TEST" />
@@ -1489,12 +1489,6 @@ private suspend fun publishTestDiscovery(messages: BuildMessages, file: String?)
private val COMMUNITY_AGGREGATOR_BAZEL_MIGRATED_MODULES = listOf(
"intellij.maven.server.eventListener.tests",
"intellij.agent.workbench.chat.tests",
"intellij.platform.ai.agent.cli.tests",
"intellij.platform.ai.agent.common.tests",
"intellij.platform.ai.agent.core.tests",
"intellij.platform.ai.agent.filewatch.tests",
"intellij.platform.ai.agent.json.tests",
"intellij.agent.workbench.prompt.core.tests",
"intellij.ant.tests",
"intellij.commander.tests",
"intellij.completionMlRanking.tests",
@@ -1,133 +0,0 @@
load("@community//build:tests-options.bzl", "jps_test")
load("@rules_jvm//:jvm.bzl", "jvm_library")
### auto-generated section `build intellij.platform.ai.agent.cli` start
jvm_library(
name = "cli",
srcs = glob(
[
"src/**/*.kt",
"src/**/*.java",
"src/**/*.form",
],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.cli",
resource_strip_prefix = "resources",
resources = glob(
["resources/**/*"],
),
visibility = ["//visibility:public"],
deps = [
# do not sort,
"@lib//:kotlin-stdlib",
"//platform/core-api:core",
"//platform/util",
"//platform/platform-util-io:ide-util-io",
"//platform/eel",
"//platform/eel-provider",
"//plugins/terminal",
],
)
jvm_library(
name = "cli_test_lib",
testonly = True,
srcs = glob(
[],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.cli",
visibility = ["//visibility:public"],
runtime_deps = [
# do not sort,
":cli",
"//platform/core-api:core_test_lib",
"//platform/util:util_test_lib",
"//platform/platform-util-io:ide-util-io_test_lib",
"//platform/eel:eel_test_lib",
"//platform/eel-provider:eel-provider_test_lib",
"//plugins/terminal:terminal_test_lib",
],
)
### auto-generated section `build intellij.platform.ai.agent.cli` end
### auto-generated section `iml intellij.platform.ai.agent.cli` start
exports_files(
[
"intellij.platform.ai.agent.cli.iml",
],
visibility = ["//visibility:public"],
)
### auto-generated section `iml intellij.platform.ai.agent.cli` end
### auto-generated section `build intellij.platform.ai.agent.cli.tests` start
jvm_library(
name = "ai-agent-cli-tests",
srcs = glob(
[],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.cli.tests",
visibility = ["//visibility:public"],
)
jvm_library(
name = "ai-agent-cli-tests_test_lib",
testonly = True,
srcs = glob(
[
"testSrc/**/*.kt",
"testSrc/**/*.java",
"testSrc/**/*.form",
],
allow_empty = True,
),
associates = [
"//plugins/agent-workbench/lib-agent/cli",
"//plugins/agent-workbench/lib-agent/cli:cli_test_lib",
],
visibility = ["//visibility:public"],
runtime_deps = [
# do not sort,
":ai-agent-cli-tests",
"//platform/platform-util-io:ide-util-io_test_lib",
],
deps = [
# do not sort,
"@lib//:kotlin-stdlib",
"//libraries/kotlinx/coroutines/core",
"//libraries/kotlinx/coroutines/core:core_test_lib",
"//platform/core-api:core",
"//platform/core-api:core_test_lib",
"//platform/util",
"//platform/util:util_test_lib",
"//platform/platform-util-io:ide-util-io",
"//plugins/terminal",
"//plugins/terminal:terminal_test_lib",
"//libraries/junit5",
"//libraries/junit5:junit5_test_lib",
"//libraries/assertj-core",
"//libraries/assertj-core:assertj-core_test_lib",
],
)
### auto-generated section `build intellij.platform.ai.agent.cli.tests` end
### auto-generated section `iml intellij.platform.ai.agent.cli.tests` start
exports_files(
[
"intellij.platform.ai.agent.cli.tests.iml",
],
visibility = ["//visibility:public"],
)
### auto-generated section `iml intellij.platform.ai.agent.cli.tests` end
### auto-generated section `test intellij.platform.ai.agent.cli.tests` start
jps_test(
name = "ai-agent-cli-tests_test",
runtime_deps = [":ai-agent-cli-tests_test_lib"],
)
### auto-generated section `test intellij.platform.ai.agent.cli.tests` end
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="com.intellij.platform.ai.agent.cli" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module" module-name="intellij.platform.ide.util.io" />
<orderEntry type="module" module-name="intellij.platform.eel" />
<orderEntry type="module" module-name="intellij.platform.eel.provider" />
<orderEntry type="module" module-name="intellij.terminal" />
</component>
</module>
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/testSrc">
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" packagePrefix="com.intellij.platform.ai.agent.cli" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="TEST" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.libraries.kotlinx.coroutines.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.util" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ide.util.io" scope="TEST" />
<orderEntry type="module" module-name="intellij.terminal" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.junit5" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.assertj.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.cli" scope="TEST" />
</component>
<component name="TestModuleProperties" production-module="intellij.platform.ai.agent.cli" />
</module>
@@ -1,7 +0,0 @@
<idea-plugin>
<!-- region Generated dependencies - run `Generate Product Layouts` to regenerate -->
<dependencies>
<plugin id="org.jetbrains.plugins.terminal"/>
</dependencies>
<!-- endregion -->
</idea-plugin>
@@ -1,128 +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.platform.ai.agent.cli
import com.intellij.execution.configurations.PathEnvironmentVariableUtil
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.platform.eel.provider.LocalEelDescriptor
import com.intellij.platform.eel.provider.toEelApi
import kotlinx.coroutines.CancellationException
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.plugins.terminal.agent.TerminalAgent
import org.jetbrains.plugins.terminal.agent.TerminalAgentResolver
import java.nio.file.Files
import java.nio.file.Path
private val LOG = logger<AgentCliExecutableResolverLogCategory>()
private class AgentCliExecutableResolverLogCategory
@ApiStatus.Internal
suspend fun resolveExecutableViaTerminalResolver(
terminalAgent: TerminalAgent,
binaryPathResolver: suspend (TerminalAgent) -> String? = ::resolveTerminalAgentBinaryPath,
): String? {
return try {
binaryPathResolver(terminalAgent)
}
catch (e: CancellationException) {
throw e
}
catch (e: ProcessCanceledException) {
throw e
}
catch (e: Throwable) {
LOG.debug("Failed to resolve binary for terminal agent '${terminalAgent.agentKey.key}'", e)
null
}
}
@ApiStatus.Internal
suspend fun resolveExecutableViaTerminalResolver(
terminalAgentKey: String,
fallbackResolver: (() -> String?)? = null,
missingAgentMessage: String? = null,
terminalAgentLookup: (TerminalAgent.AgentKey) -> TerminalAgent? = TerminalAgent::findByKey,
binaryPathResolver: suspend (TerminalAgent) -> String? = ::resolveTerminalAgentBinaryPath,
): String? {
val terminalAgent = lookupTerminalAgent(terminalAgentKey, terminalAgentLookup)
if (terminalAgent == null) {
if (missingAgentMessage != null) {
LOG.warn(missingAgentMessage)
}
return fallbackResolver?.invoke()
}
return resolveExecutableViaTerminalResolver(terminalAgent, binaryPathResolver) ?: fallbackResolver?.invoke()
}
@ApiStatus.Internal
suspend fun resolveExecutableOrDefaultViaTerminalResolver(
defaultCommand: String,
terminalAgent: TerminalAgent,
binaryPathResolver: suspend (TerminalAgent) -> String? = ::resolveTerminalAgentBinaryPath,
): String {
return resolveExecutableViaTerminalResolver(terminalAgent, binaryPathResolver) ?: defaultCommand
}
@ApiStatus.Internal
suspend fun resolveExecutableOrDefaultViaTerminalResolver(
defaultCommand: String,
terminalAgentKey: String,
fallbackResolver: (() -> String?)? = null,
missingAgentMessage: String? = null,
terminalAgentLookup: (TerminalAgent.AgentKey) -> TerminalAgent? = TerminalAgent::findByKey,
binaryPathResolver: suspend (TerminalAgent) -> String? = ::resolveTerminalAgentBinaryPath,
): String {
return resolveExecutableViaTerminalResolver(
terminalAgentKey = terminalAgentKey,
fallbackResolver = fallbackResolver,
missingAgentMessage = missingAgentMessage,
terminalAgentLookup = terminalAgentLookup,
binaryPathResolver = binaryPathResolver,
) ?: defaultCommand
}
@ApiStatus.Internal
fun resolveExecutableFromPath(command: String): String? {
return PathEnvironmentVariableUtil.findExecutableInPathOnAnyOS(command)?.toPath()?.toAbsolutePath()?.toString()
}
@ApiStatus.Internal
fun resolveExecutableFromPathOrLocalBin(
command: String,
localExecutableNames: List<String> = listOf(command),
userHomePath: Path? = System.getProperty("user.home")?.let(Path::of),
): String? {
resolveExecutableFromPath(command)?.let { return it }
val homePath = userHomePath ?: return null
for (executableName in localExecutableNames) {
val localBin = homePath.resolve(".local").resolve("bin").resolve(executableName)
if (Files.isRegularFile(localBin)) return localBin.toAbsolutePath().toString()
}
return null
}
@ApiStatus.Internal
fun terminalAgentBinaryNameOrDefault(
terminalAgentKey: String,
defaultCommand: String,
terminalAgentLookup: (TerminalAgent.AgentKey) -> TerminalAgent? = TerminalAgent::findByKey,
): String {
return lookupTerminalAgent(terminalAgentKey, terminalAgentLookup)?.binaryName ?: defaultCommand
}
private fun lookupTerminalAgent(
terminalAgentKey: String,
terminalAgentLookup: (TerminalAgent.AgentKey) -> TerminalAgent?,
): TerminalAgent? {
return runCatching {
terminalAgentLookup(TerminalAgent.AgentKey(terminalAgentKey))
}.onFailure { error ->
LOG.debug("Failed to resolve terminal agent for key '$terminalAgentKey'", error)
}.getOrNull()
}
private suspend fun resolveTerminalAgentBinaryPath(terminalAgent: TerminalAgent): String? {
val eelApi = LocalEelDescriptor.toEelApi()
return TerminalAgentResolver.findBinaryPath(terminalAgent, eelApi)
}
@@ -1,210 +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.platform.ai.agent.cli
import com.intellij.openapi.progress.ProcessCanceledException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.jetbrains.plugins.terminal.agent.TerminalAgent
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Files
import java.nio.file.Path
import javax.swing.Icon
class AgentCliExecutableResolverTest {
@Test
fun directAgentResolutionUsesInjectedBinaryResolver(): Unit = runBlocking(Dispatchers.Default) {
val agent = testTerminalAgent(key = "codex", binaryName = "codex")
val executable = resolveExecutableViaTerminalResolver(agent) { terminalAgent ->
"/opt/bin/${terminalAgent.binaryName}"
}
assertThat(executable).isEqualTo("/opt/bin/codex")
}
@Test
fun keyBasedResolutionUsesRegisteredAgent(): Unit = runBlocking(Dispatchers.Default) {
val agent = testTerminalAgent(key = "claude_code", binaryName = "claude")
val executable = resolveExecutableViaTerminalResolver(
terminalAgentKey = "claude_code",
terminalAgentLookup = { key -> if (key.key == "claude_code") agent else null },
binaryPathResolver = { terminalAgent -> "/usr/local/bin/${terminalAgent.binaryName}" },
)
assertThat(executable).isEqualTo("/usr/local/bin/claude")
}
@Test
fun registeredAgentUsesFallbackWhenBinaryLookupFindsNoExecutable(): Unit = runBlocking(Dispatchers.Default) {
val agent = testTerminalAgent(key = "codex", binaryName = "codex")
val executable = resolveExecutableViaTerminalResolver(
terminalAgentKey = "codex",
fallbackResolver = { "/fallback/codex" },
terminalAgentLookup = { key -> if (key.key == "codex") agent else null },
binaryPathResolver = { null },
)
assertThat(executable).isEqualTo("/fallback/codex")
}
@Test
fun registeredAgentUsesFallbackWhenBinaryLookupFails(): Unit = runBlocking(Dispatchers.Default) {
val agent = testTerminalAgent(key = "codex", binaryName = "codex")
val executable = resolveExecutableViaTerminalResolver(
terminalAgentKey = "codex",
fallbackResolver = { "/fallback/codex" },
terminalAgentLookup = { key -> if (key.key == "codex") agent else null },
binaryPathResolver = { error("broken binary lookup") },
)
assertThat(executable).isEqualTo("/fallback/codex")
}
@Test
fun binaryLookupCancellationPropagates() {
val agent = testTerminalAgent(key = "codex", binaryName = "codex")
assertThatThrownBy {
runBlocking(Dispatchers.Default) {
resolveExecutableViaTerminalResolver(agent) {
throw CancellationException("cancelled")
}
}
}.isInstanceOf(CancellationException::class.java)
}
@Test
fun binaryLookupProcessCancellationPropagates() {
val agent = testTerminalAgent(key = "codex", binaryName = "codex")
assertThatThrownBy {
runBlocking(Dispatchers.Default) {
resolveExecutableViaTerminalResolver(agent) {
throw ProcessCanceledException()
}
}
}.isInstanceOf(ProcessCanceledException::class.java)
}
@Test
fun missingAgentUsesFallbackWithoutBinaryLookup(): Unit = runBlocking(Dispatchers.Default) {
var binaryLookupCalled = false
val executable = resolveExecutableViaTerminalResolver(
terminalAgentKey = "missing",
fallbackResolver = { "/fallback/agent" },
terminalAgentLookup = { null },
binaryPathResolver = {
binaryLookupCalled = true
"/unexpected"
},
)
assertThat(executable).isEqualTo("/fallback/agent")
assertThat(binaryLookupCalled).isFalse()
}
@Test
fun lookupFailureUsesFallbackWithoutBinaryLookup(): Unit = runBlocking(Dispatchers.Default) {
var binaryLookupCalled = false
val executable = resolveExecutableViaTerminalResolver(
terminalAgentKey = "broken",
fallbackResolver = { "/fallback/agent" },
terminalAgentLookup = { error("broken terminal agent extension") },
binaryPathResolver = {
binaryLookupCalled = true
"/unexpected"
},
)
assertThat(executable).isEqualTo("/fallback/agent")
assertThat(binaryLookupCalled).isFalse()
}
@Test
fun defaultCommandIsReturnedWhenResolverFindsNoExecutable(): Unit = runBlocking(Dispatchers.Default) {
val executable = resolveExecutableOrDefaultViaTerminalResolver(
defaultCommand = "pi",
terminalAgent = testTerminalAgent(key = "pi", binaryName = "pi"),
binaryPathResolver = { null },
)
assertThat(executable).isEqualTo("pi")
}
@Test
fun terminalAgentBinaryNameFallsBackToDefault() {
val registered = testTerminalAgent(key = "codex", binaryName = "codex-nightly")
assertThat(
terminalAgentBinaryNameOrDefault(
terminalAgentKey = "codex",
defaultCommand = "codex",
terminalAgentLookup = { key -> if (key.key == "codex") registered else null },
)
).isEqualTo("codex-nightly")
assertThat(
terminalAgentBinaryNameOrDefault(
terminalAgentKey = "missing",
defaultCommand = "codex",
terminalAgentLookup = { null },
)
).isEqualTo("codex")
assertThat(
terminalAgentBinaryNameOrDefault(
terminalAgentKey = "broken",
defaultCommand = "codex",
terminalAgentLookup = { error("broken terminal agent extension") },
)
).isEqualTo("codex")
}
@Test
fun localBinFallbackResolvesHomeRelativeExecutable(@TempDir homePath: Path) {
val executable = homePath.resolve(".local").resolve("bin").resolve("agent-test-cli")
Files.createDirectories(executable.parent)
Files.writeString(executable, "")
assertThat(
resolveExecutableFromPathOrLocalBin(
command = "agent-test-cli-command-that-should-not-exist",
localExecutableNames = listOf("agent-test-cli"),
userHomePath = homePath,
)
).isEqualTo(executable.toAbsolutePath().toString())
}
@Test
fun localBinFallbackIgnoresNonFileCandidates(@TempDir homePath: Path) {
val executableDirectory = homePath.resolve(".local").resolve("bin").resolve("agent-test-cli")
Files.createDirectories(executableDirectory)
assertThat(
resolveExecutableFromPathOrLocalBin(
command = "agent-test-cli-command-that-should-not-exist",
localExecutableNames = listOf("agent-test-cli"),
userHomePath = homePath,
)
).isNull()
}
}
private fun testTerminalAgent(
key: String,
binaryName: String,
): TerminalAgent {
return object : TerminalAgent {
override val agentKey: TerminalAgent.AgentKey = TerminalAgent.AgentKey(key)
override val displayName: String = "Test Agent"
override val binaryName: String = binaryName
override val icon: Icon? = null
}
}
@@ -1,128 +0,0 @@
load("@community//build:tests-options.bzl", "jps_test")
load("@rules_jvm//:jvm.bzl", "jvm_library")
### auto-generated section `build intellij.platform.ai.agent.common` start
jvm_library(
name = "common",
srcs = glob(
[
"src/**/*.kt",
"src/**/*.java",
"src/**/*.form",
],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.common",
resource_strip_prefix = "resources",
resources = glob(
["resources/**/*"],
),
visibility = ["//visibility:public"],
deps = [
# do not sort,
"@lib//:kotlin-stdlib",
"//platform/util/concurrency",
"//platform/core-api:core",
"//platform/util",
"//plugins/agent-workbench/lib-agent/core",
"//platform/util:util-ui",
],
)
jvm_library(
name = "common_test_lib",
testonly = True,
srcs = glob(
[],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.common",
visibility = ["//visibility:public"],
runtime_deps = [
# do not sort,
":common",
"//platform/util/concurrency:concurrency_test_lib",
"//platform/core-api:core_test_lib",
"//platform/util:util_test_lib",
"//plugins/agent-workbench/lib-agent/core:core_test_lib",
"//platform/util:util-ui_test_lib",
"//libraries/junit5",
"//libraries/junit5:junit5_test_lib",
"//libraries/assertj-core",
"//libraries/assertj-core:assertj-core_test_lib",
],
)
### auto-generated section `build intellij.platform.ai.agent.common` end
### auto-generated section `iml intellij.platform.ai.agent.common` start
exports_files(
[
"intellij.platform.ai.agent.common.iml",
],
visibility = ["//visibility:public"],
)
### auto-generated section `iml intellij.platform.ai.agent.common` end
### auto-generated section `build intellij.platform.ai.agent.common.tests` start
jvm_library(
name = "ai-agent-common-tests",
srcs = glob(
[],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.common.tests",
visibility = ["//visibility:public"],
)
jvm_library(
name = "ai-agent-common-tests_test_lib",
testonly = True,
srcs = glob(
[
"testSrc/**/*.kt",
"testSrc/**/*.java",
"testSrc/**/*.form",
],
allow_empty = True,
),
associates = [
"//plugins/agent-workbench/lib-agent/common",
"//plugins/agent-workbench/lib-agent/common:common_test_lib",
],
visibility = ["//visibility:public"],
runtime_deps = [":ai-agent-common-tests"],
deps = [
# do not sort,
"@lib//:kotlin-stdlib",
"//platform/util",
"//platform/util:util_test_lib",
"//platform/util:util-ui",
"//platform/util:util-ui_test_lib",
"//plugins/agent-workbench/lib-agent/core",
"//plugins/agent-workbench/lib-agent/core:core_test_lib",
"//libraries/junit5",
"//libraries/junit5:junit5_test_lib",
"//libraries/assertj-core",
"//libraries/assertj-core:assertj-core_test_lib",
],
)
### auto-generated section `build intellij.platform.ai.agent.common.tests` end
### auto-generated section `iml intellij.platform.ai.agent.common.tests` start
exports_files(
[
"intellij.platform.ai.agent.common.tests.iml",
],
visibility = ["//visibility:public"],
)
### auto-generated section `iml intellij.platform.ai.agent.common.tests` end
### auto-generated section `test intellij.platform.ai.agent.common.tests` start
jps_test(
name = "ai-agent-common-tests_test",
runtime_deps = [":ai-agent-common-tests_test_lib"],
)
### auto-generated section `test intellij.platform.ai.agent.common.tests` end
@@ -1,36 +0,0 @@
f:com.intellij.platform.ai.agent.common.AgentThreadActivityIconsKt
- sf:withAgentThreadActivityBadge(javax.swing.Icon,com.intellij.platform.ai.agent.core.AgentThreadActivity):javax.swing.Icon
f:com.intellij.platform.ai.agent.common.AgentThreadActivityPresentation
- f:darkFallbackRgb:java.lang.Integer
- f:lightFallbackRgb:java.lang.Integer
- f:namedColorKey:java.lang.String
- f:showBadge:Z
- f:statusMessageKey:java.lang.String
- <init>(java.lang.String,java.lang.Integer,java.lang.Integer,java.lang.String,Z):V
- b:<init>(java.lang.String,java.lang.Integer,java.lang.Integer,java.lang.String,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():java.lang.Integer
- f:component3():java.lang.Integer
- f:component4():java.lang.String
- f:component5():Z
- f:copy(java.lang.String,java.lang.Integer,java.lang.Integer,java.lang.String,Z):com.intellij.platform.ai.agent.common.AgentThreadActivityPresentation
- bs:copy$default(com.intellij.platform.ai.agent.common.AgentThreadActivityPresentation,java.lang.String,java.lang.Integer,java.lang.Integer,java.lang.String,Z,I,java.lang.Object):com.intellij.platform.ai.agent.common.AgentThreadActivityPresentation
- equals(java.lang.Object):Z
- f:getNamedColor():com.intellij.ui.JBColor
- hashCode():I
f:com.intellij.platform.ai.agent.common.AgentThreadActivityPresentationKt
- sf:presentation(com.intellij.platform.ai.agent.core.AgentThreadActivity):com.intellij.platform.ai.agent.common.AgentThreadActivityPresentation
- sf:statusBadgeColor(com.intellij.platform.ai.agent.core.AgentThreadActivity):java.awt.Color
- sf:statusColor(com.intellij.platform.ai.agent.core.AgentThreadActivity):java.awt.Color
- sf:statusMessageKey(com.intellij.platform.ai.agent.core.AgentThreadActivity):java.lang.String
f:com.intellij.platform.ai.agent.common.icons.AgentWorkbenchCommonIcons
- sf:Claude:javax.swing.Icon
- sf:ClaudeGray:javax.swing.Icon
- sf:Codex:javax.swing.Icon
- sf:CodexGray:javax.swing.Icon
- sf:Junie:javax.swing.Icon
- sf:JunieGray:javax.swing.Icon
- sf:Opencode:javax.swing.Icon
- sf:OpencodeGray:javax.swing.Icon
- sf:PI:javax.swing.Icon
- <init>():V
@@ -1 +0,0 @@
kotlin/jvm/internal/DefaultConstructorMarker
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="com.intellij.platform.ai.agent.common" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.concurrency" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.core" />
<orderEntry type="module" module-name="intellij.platform.util.ui" />
<orderEntry type="module" module-name="intellij.libraries.junit5" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.assertj.core" scope="TEST" />
</component>
</module>
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/testSrc">
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" packagePrefix="com.intellij.platform.ai.agent.common" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="TEST" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.util" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.util.ui" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.junit5" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.assertj.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.common" scope="TEST" />
</component>
<component name="TestModuleProperties" production-module="intellij.platform.ai.agent.common" />
</module>
@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M3.74684 10.3074L6.50076 8.76322L6.54684 8.62864L6.50076 8.55426H6.36608L5.90532 8.52593L4.33165 8.48343L2.96709 8.42676L1.64506 8.35593L1.3119 8.2851L1 7.87427L1.0319 7.66886L1.3119 7.48115L1.71241 7.51657L2.59848 7.57678L3.92759 7.66886L4.89165 7.72552L6.32 7.87427H6.54684L6.57873 7.78219L6.50076 7.72552L6.44051 7.66886L5.06532 6.73741L3.57671 5.75285L2.79696 5.18619L2.37519 4.89932L2.16253 4.63015L2.07038 4.04225L2.45316 3.62079L2.96709 3.65621L3.09823 3.69163L3.61924 4.09183L4.73215 4.95244L6.18532 6.02201L6.39797 6.19909L6.48304 6.13888L6.49367 6.09638L6.39797 5.93701L5.60759 4.50974L4.76405 3.05768L4.38835 2.4556L4.28911 2.09436C4.25367 1.94561 4.22886 1.82165 4.22886 1.66937L4.66481 1.07792L4.90582 1L5.48709 1.07792L5.73165 1.29041L6.09316 2.11561L6.67797 3.41538L7.58532 5.18265L7.85114 5.70681L7.99291 6.19201L8.04608 6.34075H8.13823V6.25576L8.21266 5.26056L8.35089 4.0387L8.48557 2.46623L8.53165 2.02353L8.75139 1.49228L9.18734 1.20541L9.5276 1.36833L9.80759 1.76853L9.76861 2.02707L9.60203 3.10726L9.27595 4.80015L9.06329 5.93347H9.18734L9.32911 5.7918L9.90329 5.03036L10.8673 3.82621L11.2927 3.34809L11.7889 2.82039L12.1078 2.56894H12.7104L13.1534 3.22767L12.9549 3.90767L12.3347 4.6939L11.8208 5.35973L11.0835 6.35138L10.6228 7.1447L10.6653 7.20845L10.7752 7.19782L12.441 6.84366L13.3413 6.68075L14.4152 6.49658L14.9008 6.72325L14.9539 6.95345L14.7625 7.42449L13.6142 7.70782L12.2673 7.97698L10.2613 8.45156L10.2365 8.46926L10.2648 8.50468L11.1686 8.58968L11.5549 8.61093H12.5013L14.2628 8.74197L14.7235 9.04655L15 9.41842L14.9539 9.70175L14.2451 10.063L13.2881 9.83633L11.0552 9.30509L10.2896 9.11384H10.1833V9.17759L10.8213 9.80091L11.9909 10.8563L13.4547 12.2163L13.5291 12.5527L13.3413 12.8184L13.1428 12.79L11.8562 11.8232L11.36 11.3876L10.2365 10.4419H10.162V10.5411L10.4208 10.9201L11.7889 12.9742L11.8597 13.6046L11.7605 13.81L11.4061 13.934L11.0162 13.8631L10.2152 12.7404L9.38937 11.4761L8.72304 10.3428L8.64152 10.3888L8.2481 14.621L8.0638 14.8371L7.63848 15L7.28405 14.7308L7.0962 14.2952L7.28405 13.4346L7.51089 12.3119L7.69519 11.4194L7.86177 10.3109L7.96101 9.94258L7.95392 9.91778L7.87241 9.92841L7.03595 11.0759L5.76354 12.7936L4.75696 13.8702L4.51595 13.9658L4.09772 13.7498L4.13671 13.3638L4.37063 13.0202L5.76354 11.2494L6.60354 10.1515L7.14582 9.51758L7.14228 9.4255H7.11038L3.41013 11.8267L2.75089 11.9117L2.46734 11.6461L2.50278 11.2105L2.63747 11.0688L3.75038 10.3038L3.74684 10.3074Z" fill="#D97757"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M3.74684 10.3074L6.50076 8.76322L6.54684 8.62864L6.50076 8.55426H6.36608L5.90532 8.52593L4.33165 8.48343L2.96709 8.42676L1.64506 8.35593L1.3119 8.2851L1 7.87427L1.0319 7.66886L1.3119 7.48115L1.71241 7.51657L2.59848 7.57678L3.92759 7.66886L4.89165 7.72552L6.32 7.87427H6.54684L6.57873 7.78219L6.50076 7.72552L6.44051 7.66886L5.06532 6.73741L3.57671 5.75285L2.79696 5.18619L2.37519 4.89932L2.16253 4.63015L2.07038 4.04225L2.45316 3.62079L2.96709 3.65621L3.09823 3.69163L3.61924 4.09183L4.73215 4.95244L6.18532 6.02201L6.39797 6.19909L6.48304 6.13888L6.49367 6.09638L6.39797 5.93701L5.60759 4.50974L4.76405 3.05768L4.38835 2.4556L4.28911 2.09436C4.25367 1.94561 4.22886 1.82165 4.22886 1.66937L4.66481 1.07792L4.90582 1L5.48709 1.07792L5.73165 1.29041L6.09316 2.11561L6.67797 3.41538L7.58532 5.18265L7.85114 5.70681L7.99291 6.19201L8.04608 6.34075H8.13823V6.25576L8.21266 5.26056L8.35089 4.0387L8.48557 2.46623L8.53165 2.02353L8.75139 1.49228L9.18734 1.20541L9.5276 1.36833L9.80759 1.76853L9.76861 2.02707L9.60203 3.10726L9.27595 4.80015L9.06329 5.93347H9.18734L9.32911 5.7918L9.90329 5.03036L10.8673 3.82621L11.2927 3.34809L11.7889 2.82039L12.1078 2.56894H12.7104L13.1534 3.22767L12.9549 3.90767L12.3347 4.6939L11.8208 5.35973L11.0835 6.35138L10.6228 7.1447L10.6653 7.20845L10.7752 7.19782L12.441 6.84366L13.3413 6.68075L14.4152 6.49658L14.9008 6.72325L14.9539 6.95345L14.7625 7.42449L13.6142 7.70782L12.2673 7.97698L10.2613 8.45156L10.2365 8.46926L10.2648 8.50468L11.1686 8.58968L11.5549 8.61093H12.5013L14.2628 8.74197L14.7235 9.04655L15 9.41842L14.9539 9.70175L14.2451 10.063L13.2881 9.83633L11.0552 9.30509L10.2896 9.11384H10.1833V9.17759L10.8213 9.80091L11.9909 10.8563L13.4547 12.2163L13.5291 12.5527L13.3413 12.8184L13.1428 12.79L11.8562 11.8232L11.36 11.3876L10.2365 10.4419H10.162V10.5411L10.4208 10.9201L11.7889 12.9742L11.8597 13.6046L11.7605 13.81L11.4061 13.934L11.0162 13.8631L10.2152 12.7404L9.38937 11.4761L8.72304 10.3428L8.64152 10.3888L8.2481 14.621L8.0638 14.8371L7.63848 15L7.28405 14.7308L7.0962 14.2952L7.28405 13.4346L7.51089 12.3119L7.69519 11.4194L7.86177 10.3109L7.96101 9.94258L7.95392 9.91778L7.87241 9.92841L7.03595 11.0759L5.76354 12.7936L4.75696 13.8702L4.51595 13.9658L4.09772 13.7498L4.13671 13.3638L4.37063 13.0202L5.76354 11.2494L6.60354 10.1515L7.14582 9.51758L7.14228 9.4255H7.11038L3.41013 11.8267L2.75089 11.9117L2.46734 11.6461L2.50278 11.2105L2.63747 11.0688L3.75038 10.3038L3.74684 10.3074Z" fill="#6C707E"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M3.74684 10.3074L6.50076 8.76322L6.54684 8.62864L6.50076 8.55426H6.36608L5.90532 8.52593L4.33165 8.48343L2.96709 8.42676L1.64506 8.35593L1.3119 8.2851L1 7.87427L1.0319 7.66886L1.3119 7.48115L1.71241 7.51657L2.59848 7.57678L3.92759 7.66886L4.89165 7.72552L6.32 7.87427H6.54684L6.57873 7.78219L6.50076 7.72552L6.44051 7.66886L5.06532 6.73741L3.57671 5.75285L2.79696 5.18619L2.37519 4.89932L2.16253 4.63015L2.07038 4.04225L2.45316 3.62079L2.96709 3.65621L3.09823 3.69163L3.61924 4.09183L4.73215 4.95244L6.18532 6.02201L6.39797 6.19909L6.48304 6.13888L6.49367 6.09638L6.39797 5.93701L5.60759 4.50974L4.76405 3.05768L4.38835 2.4556L4.28911 2.09436C4.25367 1.94561 4.22886 1.82165 4.22886 1.66937L4.66481 1.07792L4.90582 1L5.48709 1.07792L5.73165 1.29041L6.09316 2.11561L6.67797 3.41538L7.58532 5.18265L7.85114 5.70681L7.99291 6.19201L8.04608 6.34075H8.13823V6.25576L8.21266 5.26056L8.35089 4.0387L8.48557 2.46623L8.53165 2.02353L8.75139 1.49228L9.18734 1.20541L9.5276 1.36833L9.80759 1.76853L9.76861 2.02707L9.60203 3.10726L9.27595 4.80015L9.06329 5.93347H9.18734L9.32911 5.7918L9.90329 5.03036L10.8673 3.82621L11.2927 3.34809L11.7889 2.82039L12.1078 2.56894H12.7104L13.1534 3.22767L12.9549 3.90767L12.3347 4.6939L11.8208 5.35973L11.0835 6.35138L10.6228 7.1447L10.6653 7.20845L10.7752 7.19782L12.441 6.84366L13.3413 6.68075L14.4152 6.49658L14.9008 6.72325L14.9539 6.95345L14.7625 7.42449L13.6142 7.70782L12.2673 7.97698L10.2613 8.45156L10.2365 8.46926L10.2648 8.50468L11.1686 8.58968L11.5549 8.61093H12.5013L14.2628 8.74197L14.7235 9.04655L15 9.41842L14.9539 9.70175L14.2451 10.063L13.2881 9.83633L11.0552 9.30509L10.2896 9.11384H10.1833V9.17759L10.8213 9.80091L11.9909 10.8563L13.4547 12.2163L13.5291 12.5527L13.3413 12.8184L13.1428 12.79L11.8562 11.8232L11.36 11.3876L10.2365 10.4419H10.162V10.5411L10.4208 10.9201L11.7889 12.9742L11.8597 13.6046L11.7605 13.81L11.4061 13.934L11.0162 13.8631L10.2152 12.7404L9.38937 11.4761L8.72304 10.3428L8.64152 10.3888L8.2481 14.621L8.0638 14.8371L7.63848 15L7.28405 14.7308L7.0962 14.2952L7.28405 13.4346L7.51089 12.3119L7.69519 11.4194L7.86177 10.3109L7.96101 9.94258L7.95392 9.91778L7.87241 9.92841L7.03595 11.0759L5.76354 12.7936L4.75696 13.8702L4.51595 13.9658L4.09772 13.7498L4.13671 13.3638L4.37063 13.0202L5.76354 11.2494L6.60354 10.1515L7.14582 9.51758L7.14228 9.4255H7.11038L3.41013 11.8267L2.75089 11.9117L2.46734 11.6461L2.50278 11.2105L2.63747 11.0688L3.75038 10.3038L3.74684 10.3074Z" fill="#CED0D6"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

@@ -1,40 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M7.091 1C8.069 1 8.955 1.394 9.598 2.031C9.895 1.951 10.207 1.909 10.528 1.909C12.496 1.909 14.091 3.504 14.091 5.472C14.091 5.794 14.049 6.105 13.969 6.402C14.606 7.045 15 7.931 15 8.909C15 10.56 13.877 11.949 12.353 12.353C11.949 13.877 10.56 15 8.909 15C7.931 15 7.045 14.606 6.402 13.969C6.105 14.049 5.794 14.091 5.472 14.091C3.504 14.091 1.909 12.496 1.909 10.528C1.909 10.207 1.951 9.895 2.031 9.598C1.394 8.955 1 8.069 1 7.091C1 5.44 2.123 4.051 3.647 3.647C4.051 2.123 5.44 1 7.091 1Z" fill="#5D77FF"/>
<path d="M2.02344 9.6279C6.52406 10.014 13.1707 10.6805 13.9463 11.4336C13.5103 11.8669 12.9648 12.1912 12.3525 12.3535C11.9484 13.8771 10.5599 14.9999 8.90918 15C7.9312 15 7.04534 14.6057 6.40234 13.9687C6.10534 14.0487 5.79368 14.0908 5.47168 14.0908C3.50392 14.0906 1.90934 12.496 1.90918 10.5283C1.90918 10.218 1.94857 9.91594 2.02344 9.6279Z" fill="url(#paint0_linear_4062_1054)"/>
<path d="M1.999 6.49997L14.9785 9.28024C14.8247 10.7651 13.7607 11.9802 12.3525 12.3535C12.2393 12.7801 12.0466 13.1735 11.7949 13.5215L1.91697 10.7568C1.91218 10.6812 1.90916 10.6051 1.90916 10.5283C1.90916 10.2073 1.95123 9.89463 2.03123 9.59763C1.39427 8.95463 0.999977 8.06875 0.999977 7.09079C0.999989 6.88952 1.01672 6.69214 1.04881 6.49997H1.999Z" fill="url(#paint1_linear_4062_1054)"/>
<path d="M7.09082 1C8.06879 1 8.95466 1.39429 9.59766 2.03125C9.89466 1.95125 10.2073 1.90918 10.5283 1.90918C11.8866 1.9093 13.0669 2.6692 13.668 3.78711C13.4705 6.13847 10.8666 7.99987 7.68164 8C4.93145 8 2.61511 6.6115 1.90625 4.71875C2.3631 4.20748 2.96479 3.82843 3.64746 3.64746C4.05145 2.12352 5.4399 1.00008 7.09082 1Z" fill="url(#paint2_radial_4062_1054)"/>
<path d="M7.0908 0.999969C8.06876 0.999969 8.95464 1.39426 9.59763 2.03122C9.71468 1.99969 9.83469 1.9755 9.95603 1.95602C9.74151 3.11876 8.72479 3.99997 7.49998 3.99997C6.17774 3.99996 5.09709 2.97336 5.00779 1.6738C5.59376 1.25056 6.31279 1.00001 7.0908 0.999969Z" fill="url(#paint3_radial_4062_1054)"/>
<path d="M10.5279 1.90915C12.3741 1.90915 13.892 3.31275 14.0728 5.1113C13.8553 5.45927 13.5494 5.74444 13.1636 5.9238C11.9896 6.46962 10.5383 5.83715 9.92145 4.51071C9.51018 3.62609 9.57499 2.65706 10.0113 1.94724C10.1802 1.92284 10.3526 1.90915 10.5279 1.90915Z" fill="url(#paint4_linear_4062_1054)"/>
<path d="M8.76257 1.41599C9.0708 1.57999 9.35184 1.78784 9.59753 2.03122C9.89453 1.95122 10.2072 1.90915 10.5282 1.90915C11.1861 1.90919 11.8021 2.08756 12.3309 2.39841C12.3904 2.69826 12.3604 3.00087 12.2225 3.27243C11.8309 4.04344 10.7294 4.27046 9.7616 3.77927C8.79365 3.28797 8.3262 2.26439 8.71765 1.49313C8.73121 1.46642 8.74735 1.44138 8.76257 1.41599Z" fill="url(#paint5_linear_4062_1054)"/>
<path d="M10.694 9.5H8" stroke="#E5EDFF" stroke-width="0.97" stroke-linecap="round"/>
<path d="M4.412 6.03299L5.5 7.99999C5.539 8.06999 5.538 8.15599 5.498 8.22599L4.412 10.102" stroke="#E5EDFF" stroke-width="0.97" stroke-linecap="round"/>
<defs>
<linearGradient id="paint0_linear_4062_1054" x1="8.5" y1="11.5" x2="7.5" y2="15" gradientUnits="userSpaceOnUse">
<stop offset="0.203079" stop-color="#362CFF"/>
<stop offset="1" stop-color="#4549FF"/>
</linearGradient>
<linearGradient id="paint1_linear_4062_1054" x1="8.5" y1="8" x2="7" y2="12.5" gradientUnits="userSpaceOnUse">
<stop offset="0.120516" stop-color="#5D77FF"/>
<stop offset="0.285745" stop-color="#6B8BFF"/>
<stop offset="0.549877" stop-color="#6582FF"/>
<stop offset="0.873355" stop-color="#3932FF"/>
</linearGradient>
<radialGradient id="paint2_radial_4062_1054" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(7.5 3) rotate(90) scale(4.5 6.75)">
<stop offset="0.32185" stop-color="#A9A3FF"/>
<stop offset="0.811664" stop-color="#5D77FF"/>
</radialGradient>
<radialGradient id="paint3_radial_4062_1054" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(7 0.5) rotate(81.8699) scale(3.53553 2.02031)">
<stop offset="0.0961538" stop-color="#A99FFF"/>
<stop offset="0.495192" stop-color="#9095FF"/>
<stop offset="1" stop-color="#A9A3FF"/>
</radialGradient>
<linearGradient id="paint4_linear_4062_1054" x1="11.5" y1="4" x2="10" y2="3.5" gradientUnits="userSpaceOnUse">
<stop offset="0.0961538" stop-color="#5D77FF"/>
<stop offset="1" stop-color="#A9A3FF"/>
</linearGradient>
<linearGradient id="paint5_linear_4062_1054" x1="11.5" y1="2" x2="9.5" y2="2.5" gradientUnits="userSpaceOnUse">
<stop offset="0.0961538" stop-color="#5D77FF"/>
<stop offset="1" stop-color="#A9A3FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

@@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M7.091 1C8.069 1 8.955 1.394 9.598 2.031C9.895 1.951 10.207 1.909 10.528 1.909C12.496 1.909 14.091 3.504 14.091 5.472C14.091 5.794 14.049 6.105 13.969 6.402C14.606 7.045 15 7.931 15 8.909C15 10.56 13.877 11.949 12.353 12.353C11.949 13.877 10.56 15 8.909 15C7.931 15 7.045 14.606 6.402 13.969C6.105 14.049 5.794 14.091 5.472 14.091C3.504 14.091 1.909 12.496 1.909 10.528C1.909 10.207 1.951 9.895 2.031 9.598C1.394 8.955 1 8.069 1 7.091C1 5.44 2.123 4.051 3.647 3.647C4.051 2.123 5.44 1 7.091 1Z" fill="#6C707E"/>
<path d="M10.694 9.5H8" stroke="#FFFFFF" stroke-width="0.97" stroke-linecap="round"/>
<path d="M4.412 6.03299L5.5 7.99999C5.539 8.06999 5.538 8.15599 5.498 8.22599L4.412 10.102" stroke="#FFFFFF" stroke-width="0.97" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 906 B

@@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M7.091 1C8.069 1 8.955 1.394 9.598 2.031C9.895 1.951 10.207 1.909 10.528 1.909C12.496 1.909 14.091 3.504 14.091 5.472C14.091 5.794 14.049 6.105 13.969 6.402C14.606 7.045 15 7.931 15 8.909C15 10.56 13.877 11.949 12.353 12.353C11.949 13.877 10.56 15 8.909 15C7.931 15 7.045 14.606 6.402 13.969C6.105 14.049 5.794 14.091 5.472 14.091C3.504 14.091 1.909 12.496 1.909 10.528C1.909 10.207 1.951 9.895 2.031 9.598C1.394 8.955 1 8.069 1 7.091C1 5.44 2.123 4.051 3.647 3.647C4.051 2.123 5.44 1 7.091 1Z" fill="#CED0D6"/>
<path d="M10.694 9.5H8" stroke="#2B2D30" stroke-width="0.97" stroke-linecap="round"/>
<path d="M4.412 6.03299L5.5 7.99999C5.539 8.06999 5.538 8.15599 5.498 8.22599L4.412 10.102" stroke="#2B2D30" stroke-width="0.97" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 906 B

@@ -1,6 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M15 5.66988V6.44775C15 11.8922 12.6657 14.9995 6.44482 14.9995H5.66667V10.3328H6.44482C9.16617 10.3328 10.3353 9.16808 10.3353 6.44405V5.66988H15Z" fill="#48E054"/>
<path d="M5.66667 10.3328H1V5.66618H5.66667V10.3328Z" fill="#48E054"/>
<path d="M10.3353 5.66988H10.3313L10.3353 5.66618V5.66988Z" fill="#48E054"/>
<path d="M10.3333 5.66618H5.66667V0.999512H10.3333V5.66618Z" fill="#48E054"/>
</svg>

Before

Width:  |  Height:  |  Size: 547 B

@@ -1,6 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M15 5.66988V6.44775C15 11.8922 12.6657 14.9995 6.44482 14.9995H5.66667V10.3328H6.44482C9.16617 10.3328 10.3353 9.16808 10.3353 6.44405V5.66988H15Z" fill="#6C707E"/>
<path d="M5.66667 10.3328H1V5.66618H5.66667V10.3328Z" fill="#6C707E"/>
<path d="M10.3353 5.66988H10.3313L10.3353 5.66618V5.66988Z" fill="#6C707E"/>
<path d="M10.3333 5.66618H5.66667V0.999512H10.3333V5.66618Z" fill="#6C707E"/>
</svg>

Before

Width:  |  Height:  |  Size: 547 B

@@ -1,6 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M15 5.66988V6.44775C15 11.8922 12.6657 14.9995 6.44482 14.9995H5.66667V10.3328H6.44482C9.16617 10.3328 10.3353 9.16808 10.3353 6.44405V5.66988H15Z" fill="#CED0D6"/>
<path d="M5.66667 10.3328H1V5.66618H5.66667V10.3328Z" fill="#CED0D6"/>
<path d="M10.3353 5.66988H10.3313L10.3353 5.66618V5.66988Z" fill="#CED0D6"/>
<path d="M10.3333 5.66618H5.66667V0.999512H10.3333V5.66618Z" fill="#CED0D6"/>
</svg>

Before

Width:  |  Height:  |  Size: 547 B

@@ -1,7 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M8 1.25C4.272 1.25 1.25 4.272 1.25 8S4.272 14.75 8 14.75 14.75 11.728 14.75 8 11.728 1.25 8 1.25Z" fill="#20B486"/>
<path d="M8 2.75C5.101 2.75 2.75 5.101 2.75 8S5.101 13.25 8 13.25 13.25 10.899 13.25 8 10.899 2.75 8 2.75Z" fill="#123C4A" fill-opacity=".9"/>
<path d="M6.35 5.25 3.9 8l2.45 2.75" stroke="#8DF5C5" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.65 5.25 12.1 8l-2.45 2.75" stroke="#FFB74A" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.9 4.75 7.1 11.25" stroke="#F6FFF8" stroke-width="1.1" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 756 B

@@ -1,7 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M8 1.25C4.272 1.25 1.25 4.272 1.25 8S4.272 14.75 8 14.75 14.75 11.728 14.75 8 11.728 1.25 8 1.25Z" fill="#6C707E"/>
<path d="M8 2.75C5.101 2.75 2.75 5.101 2.75 8S5.101 13.25 8 13.25 13.25 10.899 13.25 8 10.899 2.75 8 2.75Z" fill="#E6E7EB"/>
<path d="M6.35 5.25 3.9 8l2.45 2.75" stroke="#6C707E" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.65 5.25 12.1 8l-2.45 2.75" stroke="#6C707E" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.9 4.75 7.1 11.25" stroke="#6C707E" stroke-width="1.1" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 738 B

@@ -1,7 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path d="M8 1.25C4.272 1.25 1.25 4.272 1.25 8S4.272 14.75 8 14.75 14.75 11.728 14.75 8 11.728 1.25 8 1.25Z" fill="#CED0D6"/>
<path d="M8 2.75C5.101 2.75 2.75 5.101 2.75 8S5.101 13.25 8 13.25 13.25 10.899 13.25 8 10.899 2.75 8 2.75Z" fill="#2B2D30"/>
<path d="M6.35 5.25 3.9 8l2.45 2.75" stroke="#CED0D6" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.65 5.25 12.1 8l-2.45 2.75" stroke="#CED0D6" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.9 4.75 7.1 11.25" stroke="#CED0D6" stroke-width="1.1" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 738 B

@@ -1,5 +0,0 @@
<?xml version="1.0" ?>
<svg width="16" height="16" viewBox="60 60 680 680" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path fill="#6C707E" fill-rule="evenodd" d="M165.29 165.29H517.36V400H400V517.36H282.65V634.72H165.29ZM282.65 282.65V400H400V282.65Z"/>
<path fill="#6C707E" d="M517.36 400H634.72V634.72H517.36Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 367 B

@@ -1,5 +0,0 @@
<?xml version="1.0" ?>
<svg width="16" height="16" viewBox="60 60 680 680" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<path fill="#CED0D6" fill-rule="evenodd" d="M165.29 165.29H517.36V400H400V517.36H282.65V634.72H165.29ZM282.65 282.65V400H400V282.65Z"/>
<path fill="#CED0D6" d="M517.36 400H634.72V634.72H517.36Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 367 B

@@ -1,7 +0,0 @@
<idea-plugin>
<!-- region Generated dependencies - run `Generate Product Layouts` to regenerate -->
<dependencies>
<module name="intellij.platform.ai.agent.core"/>
</dependencies>
<!-- endregion -->
</idea-plugin>
@@ -1,11 +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.platform.ai.agent.common
import com.intellij.platform.ai.agent.core.AgentThreadActivity
import com.intellij.ui.IconManager
import javax.swing.Icon
fun withAgentThreadActivityBadge(baseIcon: Icon, activity: AgentThreadActivity): Icon {
val badgeColor = activity.statusBadgeColor() ?: return baseIcon
return IconManager.getInstance().withIconBadge(baseIcon, badgeColor)
}
@@ -1,106 +0,0 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("UseJBColor")
package com.intellij.platform.ai.agent.common
import com.intellij.platform.ai.agent.core.AgentThreadActivity
import com.intellij.ui.JBColor
import java.awt.Color
data class AgentThreadActivityPresentation(
@JvmField val namedColorKey: String?,
@JvmField val lightFallbackRgb: Int?,
@JvmField val darkFallbackRgb: Int?,
@JvmField val statusMessageKey: String,
@JvmField val showBadge: Boolean = true,
) {
val namedColor: JBColor? = namedColorKey?.let { key ->
JBColor.namedColor(key, JBColor(requireNotNull(lightFallbackRgb), requireNotNull(darkFallbackRgb)))
}
override fun toString(): String {
return "AgentThreadActivityPresentation(" +
"$namedColorKey, " +
"${lightFallbackRgb?.let { Color(it) }}, " +
"${darkFallbackRgb?.let { Color(it) }}, " +
"$statusMessageKey, " +
"$showBadge" +
")"
}
}
private fun threadActivityPresentation(
namedColorKey: String,
lightFallbackColor: Int,
darkFallbackColor: Int,
statusMessageKey: String,
showBadge: Boolean = true,
): AgentThreadActivityPresentation {
return AgentThreadActivityPresentation(
namedColorKey = namedColorKey,
lightFallbackRgb = lightFallbackColor,
darkFallbackRgb = darkFallbackColor,
statusMessageKey = statusMessageKey,
showBadge = showBadge,
)
}
// Idle/no-signal state: intentionally unaccented and unbadged.
private val READY_PRESENTATION = AgentThreadActivityPresentation(
namedColorKey = null,
lightFallbackRgb = null,
darkFallbackRgb = null,
statusMessageKey = "toolwindow.thread.status.ready",
showBadge = false,
)
// Active work in progress.
private val PROCESSING_PRESENTATION = threadActivityPresentation(
namedColorKey = "IconBadge.successBackground",
lightFallbackColor = Color(0x55A76A).rgb,
darkFallbackColor = Color(0x5FAD65).rgb,
statusMessageKey = "toolwindow.thread.status.in.progress",
)
// Review-mode output waiting for user attention.
private val REVIEWING_PRESENTATION = threadActivityPresentation(
namedColorKey = "IconBadge.warningBackground",
lightFallbackColor = Color(0xFFAF0F).rgb,
darkFallbackColor = Color(0xF2C55C).rgb,
statusMessageKey = "toolwindow.thread.status.needs.review",
)
// Completed assistant output waiting to be seen.
private val UNREAD_PRESENTATION = threadActivityPresentation(
namedColorKey = "IconBadge.infoBackground",
lightFallbackColor = Color(0x588CF3).rgb,
darkFallbackColor = Color(0x548AF7).rgb,
statusMessageKey = "toolwindow.thread.status.done",
)
// Explicit user action required.
private val NEEDS_INPUT_PRESENTATION = threadActivityPresentation(
namedColorKey = "IconBadge.warningBackground",
lightFallbackColor = Color(0xFFAF0F).rgb,
darkFallbackColor = Color(0xF2C55C).rgb,
statusMessageKey = "toolwindow.thread.status.needs.input",
)
fun AgentThreadActivity.presentation(): AgentThreadActivityPresentation {
return when (this) {
AgentThreadActivity.READY -> READY_PRESENTATION
AgentThreadActivity.PROCESSING -> PROCESSING_PRESENTATION
AgentThreadActivity.REVIEWING -> REVIEWING_PRESENTATION
AgentThreadActivity.NEEDS_INPUT -> NEEDS_INPUT_PRESENTATION
AgentThreadActivity.UNREAD -> UNREAD_PRESENTATION
}
}
fun AgentThreadActivity.statusColor(): Color? = presentation().namedColor
fun AgentThreadActivity.statusBadgeColor(): Color? {
val presentation = presentation()
return presentation.namedColor?.takeIf { presentation.showBadge }
}
fun AgentThreadActivity.statusMessageKey(): String = presentation().statusMessageKey
@@ -1,26 +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.platform.ai.agent.common.icons;
import com.intellij.ui.IconManager;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
/**
* NOTE THIS FILE IS AUTO-GENERATED
* DO NOT EDIT IT BY HAND, run "Generate icon classes" configuration instead
*/
public final class AgentWorkbenchCommonIcons {
private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) {
return IconManager.getInstance().loadRasterizedIcon(path, AgentWorkbenchCommonIcons.class.getClassLoader(), cacheKey, flags);
}
/** 16x16 */ public static final @NotNull Icon Claude = load("icons/claude.svg", -294097785, 0);
/** 16x16 */ public static final @NotNull Icon ClaudeGray = load("icons/claudeGray.svg", 1540268029, 2);
/** 16x16 */ public static final @NotNull Icon Codex = load("icons/codex.svg", -72483386, 0);
/** 16x16 */ public static final @NotNull Icon CodexGray = load("icons/codexGray.svg", 1835398902, 2);
/** 16x16 */ public static final @NotNull Icon Junie = load("icons/junie.svg", -76396756, 0);
/** 16x16 */ public static final @NotNull Icon JunieGray = load("icons/junieGray.svg", 465929641, 2);
/** 16x16 */ public static final @NotNull Icon Opencode = load("icons/opencode.svg", -1522185687, 0);
/** 16x16 */ public static final @NotNull Icon OpencodeGray = load("icons/opencodeGray.svg", 1279569419, 2);
/** 16x16 */ public static final @NotNull Icon PI = load("icons/pi.svg", 1349425160, 2);
}
@@ -1,94 +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.platform.ai.agent.common
import com.intellij.platform.ai.agent.core.AgentThreadActivity
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import java.awt.Color
import java.util.concurrent.TimeUnit
import javax.swing.UIManager
@Timeout(value = 2, unit = TimeUnit.MINUTES)
class AgentThreadActivityPresentationTest {
@Test
fun usesThreadPresentationsAndMessageKeys() {
val expected = linkedMapOf(
AgentThreadActivity.READY to AgentThreadActivityPresentation(
namedColorKey = null,
lightFallbackRgb = null,
darkFallbackRgb = null,
statusMessageKey = "toolwindow.thread.status.ready",
showBadge = false,
),
AgentThreadActivity.PROCESSING to AgentThreadActivityPresentation(
namedColorKey = "IconBadge.successBackground",
lightFallbackRgb = Color(0x55A76A).rgb,
darkFallbackRgb = Color(0x5FAD65).rgb,
statusMessageKey = "toolwindow.thread.status.in.progress",
),
AgentThreadActivity.REVIEWING to AgentThreadActivityPresentation(
namedColorKey = "IconBadge.warningBackground",
lightFallbackRgb = Color(0xFFAF0F).rgb,
darkFallbackRgb = Color(0xF2C55C).rgb,
statusMessageKey = "toolwindow.thread.status.needs.review",
),
AgentThreadActivity.UNREAD to AgentThreadActivityPresentation(
namedColorKey = "IconBadge.infoBackground",
lightFallbackRgb = Color(0x588CF3).rgb,
darkFallbackRgb = Color(0x548AF7).rgb,
statusMessageKey = "toolwindow.thread.status.done",
),
AgentThreadActivity.NEEDS_INPUT to AgentThreadActivityPresentation(
namedColorKey = "IconBadge.warningBackground",
lightFallbackRgb = Color(0xFFAF0F).rgb,
darkFallbackRgb = Color(0xF2C55C).rgb,
statusMessageKey = "toolwindow.thread.status.needs.input",
),
)
expected.forEach { (activity, presentation) ->
assertThat(activity.presentation()).isEqualTo(presentation)
val statusColor = activity.statusColor()
if (presentation.namedColorKey == null) {
assertThat(statusColor).isNull()
}
else {
assertThat(statusColor?.rgb).isIn(
Color(requireNotNull(presentation.lightFallbackRgb)).rgb,
Color(requireNotNull(presentation.darkFallbackRgb)).rgb,
)
}
assertThat(activity.statusBadgeColor()?.rgb).isEqualTo(if (presentation.showBadge) statusColor?.rgb else null)
assertThat(activity.statusMessageKey()).isEqualTo(presentation.statusMessageKey)
}
}
@Test
fun prefersUiManagerOverrideForNamedColor() {
val overrideColor = Color(0x112233)
withTemporaryNeedsInputUiColor(overrideColor) {
assertThat(AgentThreadActivity.NEEDS_INPUT.statusColor()?.rgb).isEqualTo(overrideColor.rgb)
assertThat(AgentThreadActivity.NEEDS_INPUT.statusBadgeColor()?.rgb).isEqualTo(overrideColor.rgb)
}
}
private fun withTemporaryNeedsInputUiColor(color: Color, action: () -> Unit) {
val key = "IconBadge.warningBackground"
val defaults = UIManager.getDefaults()
val previous = defaults[key]
UIManager.put(key, color)
try {
action()
}
finally {
if (previous == null) {
defaults.remove(key)
}
else {
UIManager.put(key, previous)
}
}
}
}
@@ -1,120 +0,0 @@
load("@community//build:tests-options.bzl", "jps_test")
load("@rules_jvm//:jvm.bzl", "jvm_library")
### auto-generated section `build intellij.platform.ai.agent.core` start
jvm_library(
name = "core",
srcs = glob(
[
"src/**/*.kt",
"src/**/*.java",
"src/**/*.form",
],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.core",
resource_strip_prefix = "resources",
resources = glob(
["resources/**/*"],
),
visibility = ["//visibility:public"],
deps = [
# do not sort,
"@lib//:kotlin-stdlib",
"//platform/util/concurrency",
"//platform/core-api:core",
"//platform/util",
],
)
jvm_library(
name = "core_test_lib",
testonly = True,
srcs = glob(
[],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.core",
visibility = ["//visibility:public"],
runtime_deps = [
# do not sort,
":core",
"//platform/util/concurrency:concurrency_test_lib",
"//platform/core-api:core_test_lib",
"//platform/util:util_test_lib",
],
)
### auto-generated section `build intellij.platform.ai.agent.core` end
### auto-generated section `iml intellij.platform.ai.agent.core` start
exports_files(
[
"intellij.platform.ai.agent.core.iml",
],
visibility = ["//visibility:public"],
)
### auto-generated section `iml intellij.platform.ai.agent.core` end
### auto-generated section `build intellij.platform.ai.agent.core.tests` start
jvm_library(
name = "ai-agent-core-tests",
srcs = glob(
[],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.core.tests",
visibility = ["//visibility:public"],
)
jvm_library(
name = "ai-agent-core-tests_test_lib",
testonly = True,
srcs = glob(
[
"testSrc/**/*.kt",
"testSrc/**/*.java",
"testSrc/**/*.form",
],
allow_empty = True,
),
associates = [
"//plugins/agent-workbench/lib-agent/core",
"//plugins/agent-workbench/lib-agent/core:core_test_lib",
],
visibility = ["//visibility:public"],
runtime_deps = [":ai-agent-core-tests"],
deps = [
# do not sort,
"@lib//:kotlin-stdlib",
"//platform/util/concurrency",
"//platform/util/concurrency:concurrency_test_lib",
"//platform/core-api:core",
"//platform/core-api:core_test_lib",
"//platform/util",
"//platform/util:util_test_lib",
"//libraries/junit5",
"//libraries/junit5:junit5_test_lib",
"//libraries/assertj-core",
"//libraries/assertj-core:assertj-core_test_lib",
],
)
### auto-generated section `build intellij.platform.ai.agent.core.tests` end
### auto-generated section `iml intellij.platform.ai.agent.core.tests` start
exports_files(
[
"intellij.platform.ai.agent.core.tests.iml",
],
visibility = ["//visibility:public"],
)
### auto-generated section `iml intellij.platform.ai.agent.core.tests` end
### auto-generated section `test intellij.platform.ai.agent.core.tests` start
jps_test(
name = "ai-agent-core-tests_test",
runtime_deps = [":ai-agent-core-tests_test_lib"],
)
### auto-generated section `test intellij.platform.ai.agent.core.tests` end
@@ -1,256 +0,0 @@
e:com.intellij.platform.ai.agent.core.AgentThreadActivity
- java.lang.Enum
- sf:NEEDS_INPUT:com.intellij.platform.ai.agent.core.AgentThreadActivity
- sf:PROCESSING:com.intellij.platform.ai.agent.core.AgentThreadActivity
- sf:READY:com.intellij.platform.ai.agent.core.AgentThreadActivity
- sf:REVIEWING:com.intellij.platform.ai.agent.core.AgentThreadActivity
- sf:UNREAD:com.intellij.platform.ai.agent.core.AgentThreadActivity
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.core.AgentThreadActivity
- s:values():com.intellij.platform.ai.agent.core.AgentThreadActivity[]
e:com.intellij.platform.ai.agent.core.AgentThreadActivityBucket
- java.lang.Enum
- sf:ATTENTION:com.intellij.platform.ai.agent.core.AgentThreadActivityBucket
- sf:DONE:com.intellij.platform.ai.agent.core.AgentThreadActivityBucket
- sf:RUNNING:com.intellij.platform.ai.agent.core.AgentThreadActivityBucket
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.core.AgentThreadActivityBucket
- s:values():com.intellij.platform.ai.agent.core.AgentThreadActivityBucket[]
f:com.intellij.platform.ai.agent.core.AgentThreadActivityKt
- sf:chromeBucket(com.intellij.platform.ai.agent.core.AgentThreadActivityReport):com.intellij.platform.ai.agent.core.AgentThreadActivityBucket
- sf:isWorking(com.intellij.platform.ai.agent.core.AgentThreadActivity):Z
f:com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- sf:Companion:com.intellij.platform.ai.agent.core.AgentThreadActivityReport$Companion
- sf:READY:com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- f:chromeActivity:com.intellij.platform.ai.agent.core.AgentThreadActivity
- f:rowActivity:com.intellij.platform.ai.agent.core.AgentThreadActivity
- <init>(com.intellij.platform.ai.agent.core.AgentThreadActivity,com.intellij.platform.ai.agent.core.AgentThreadActivity):V
- b:<init>(com.intellij.platform.ai.agent.core.AgentThreadActivity,com.intellij.platform.ai.agent.core.AgentThreadActivity,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():com.intellij.platform.ai.agent.core.AgentThreadActivity
- f:component2():com.intellij.platform.ai.agent.core.AgentThreadActivity
- f:copy(com.intellij.platform.ai.agent.core.AgentThreadActivity,com.intellij.platform.ai.agent.core.AgentThreadActivity):com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- bs:copy$default(com.intellij.platform.ai.agent.core.AgentThreadActivityReport,com.intellij.platform.ai.agent.core.AgentThreadActivity,com.intellij.platform.ai.agent.core.AgentThreadActivity,I,java.lang.Object):com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.core.AgentThreadActivityReport$Companion
f:com.intellij.platform.ai.agent.core.AgentThreadIdentity
- f:providerId:java.lang.String
- f:threadId:java.lang.String
- <init>(java.lang.String,java.lang.String):V
- f:component1():java.lang.String
- f:component2():java.lang.String
- f:copy(java.lang.String,java.lang.String):com.intellij.platform.ai.agent.core.AgentThreadIdentity
- bs:copy$default(com.intellij.platform.ai.agent.core.AgentThreadIdentity,java.lang.String,java.lang.String,I,java.lang.Object):com.intellij.platform.ai.agent.core.AgentThreadIdentity
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.core.AgentThreadIdentityKt
- sf:buildAgentThreadIdentity(java.lang.String,java.lang.String):java.lang.String
- sf:parseAgentThreadIdentity(java.lang.String):com.intellij.platform.ai.agent.core.AgentThreadIdentity
f:com.intellij.platform.ai.agent.core.AgentWorkbenchPathUtilKt
- sf:normalizeAgentSessionProjectPath(java.lang.String):java.lang.String
- sf:normalizeAgentSessionTitle(java.lang.String):java.lang.String
- sf:normalizeAgentWorkbenchPath(java.lang.String):java.lang.String
- sf:normalizeAgentWorkbenchPathOrNull(java.lang.String):java.lang.String
- sf:parseAgentWorkbenchPathOrNull(java.lang.String):java.nio.file.Path
f:com.intellij.platform.ai.agent.core.extensions.OverridableValue
- <init>(kotlin.jvm.functions.Function0):V
- f:value():java.lang.Object
- f:withOverride(java.lang.Object,kotlin.jvm.functions.Function0):java.lang.Object
f:com.intellij.platform.ai.agent.core.extensions.SingleExtensionPointResolver
- <init>(com.intellij.openapi.diagnostic.Logger,com.intellij.openapi.extensions.ExtensionPointName,java.lang.String,kotlin.jvm.functions.Function1):V
- f:findFirstOrNull():java.lang.Object
f:com.intellij.platform.ai.agent.core.extensions.SnapshotExtensionPointCache
- <init>(com.intellij.openapi.diagnostic.Logger,com.intellij.openapi.extensions.ExtensionPointName,java.lang.Class,java.lang.Object,java.lang.String,kotlin.jvm.functions.Function1):V
- f:getSnapshotOrEmpty():java.lang.Object
f:com.intellij.platform.ai.agent.core.extensions.SuspendingOverridableValue
- <init>(kotlin.jvm.functions.Function0):V
- f:value():java.lang.Object
- f:withOverride(java.lang.Object,kotlin.jvm.functions.Function1,kotlin.coroutines.Continuation):java.lang.Object
f:com.intellij.platform.ai.agent.core.session.AgentSessionArchiveTitlesKt
- sf:AGENT_SESSION_ARCHIVED_THREAD_TITLE_PREFIX:java.lang.String
- sf:buildAgentSessionArchivedThreadTitle(java.lang.String,java.lang.String):java.lang.String
- sf:isAgentSessionArchivedThreadTitle(java.lang.String):Z
- sf:normalizeAgentSessionStoredThreadTitle(java.lang.String):java.lang.String
- sf:resolveAgentSessionArchivedTitleState(java.lang.String,java.lang.String):com.intellij.platform.ai.agent.core.session.AgentSessionArchivedTitleState
- sf:stripAgentSessionArchivedThreadTitlePrefix(java.lang.String):java.lang.String
f:com.intellij.platform.ai.agent.core.session.AgentSessionArchivedTitleState
- f:archived:Z
- f:title:java.lang.String
- <init>(java.lang.String,Z):V
- f:component1():java.lang.String
- f:component2():Z
- f:copy(java.lang.String,Z):com.intellij.platform.ai.agent.core.session.AgentSessionArchivedTitleState
- bs:copy$default(com.intellij.platform.ai.agent.core.session.AgentSessionArchivedTitleState,java.lang.String,Z,I,java.lang.Object):com.intellij.platform.ai.agent.core.session.AgentSessionArchivedTitleState
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.core.session.AgentSessionCost
- f:amountUsd:java.math.BigDecimal
- f:kind:com.intellij.platform.ai.agent.core.session.AgentSessionCostKind
- f:matchedModelId:java.lang.String
- <init>(java.math.BigDecimal,com.intellij.platform.ai.agent.core.session.AgentSessionCostKind,java.lang.String):V
- b:<init>(java.math.BigDecimal,com.intellij.platform.ai.agent.core.session.AgentSessionCostKind,java.lang.String,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.math.BigDecimal
- f:component2():com.intellij.platform.ai.agent.core.session.AgentSessionCostKind
- f:component3():java.lang.String
- f:copy(java.math.BigDecimal,com.intellij.platform.ai.agent.core.session.AgentSessionCostKind,java.lang.String):com.intellij.platform.ai.agent.core.session.AgentSessionCost
- bs:copy$default(com.intellij.platform.ai.agent.core.session.AgentSessionCost,java.math.BigDecimal,com.intellij.platform.ai.agent.core.session.AgentSessionCostKind,java.lang.String,I,java.lang.Object):com.intellij.platform.ai.agent.core.session.AgentSessionCost
- equals(java.lang.Object):Z
- hashCode():I
e:com.intellij.platform.ai.agent.core.session.AgentSessionCostKind
- java.lang.Enum
- sf:ESTIMATED:com.intellij.platform.ai.agent.core.session.AgentSessionCostKind
- sf:EXACT:com.intellij.platform.ai.agent.core.session.AgentSessionCostKind
- sf:UNAVAILABLE:com.intellij.platform.ai.agent.core.session.AgentSessionCostKind
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.core.session.AgentSessionCostKind
- s:values():com.intellij.platform.ai.agent.core.session.AgentSessionCostKind[]
e:com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
- java.lang.Enum
- sf:STANDARD:com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
- sf:YOLO:com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
- s:values():com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode[]
f:com.intellij.platform.ai.agent.core.session.AgentSessionModelsKt
- sf:agentSessionOutlinePhaseTitle(java.lang.String,I):java.lang.String
- bs:agentSessionOutlinePhaseTitle$default(java.lang.String,I,I,java.lang.Object):java.lang.String
- sf:buildAgentSessionOutlineTree(java.util.List):java.util.List
- sf:compactAgentSessionOutlineText(java.lang.String,I):java.lang.String
- sf:dedupeAgentSessionOutlineText(java.lang.String):java.lang.String
- sf:normalizeAgentSessionOutlinePreview(java.lang.String,I):java.lang.String
- bs:normalizeAgentSessionOutlinePreview$default(java.lang.String,I,I,java.lang.Object):java.lang.String
- sf:summarizeAgentSessionOutlineChildren(java.util.List):java.lang.String
f:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItem
- f:children:java.util.List
- f:id:java.lang.String
- f:kind:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- f:preview:java.lang.String
- f:timestampMs:java.lang.Long
- f:title:java.lang.String
- <init>(java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind,java.lang.String,java.lang.String,java.lang.Long,java.util.List):V
- b:<init>(java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind,java.lang.String,java.lang.String,java.lang.Long,java.util.List,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- f:component3():java.lang.String
- f:component4():java.lang.String
- f:component5():java.lang.Long
- f:component6():java.util.List
- f:copy(java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind,java.lang.String,java.lang.String,java.lang.Long,java.util.List):com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItem
- bs:copy$default(com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItem,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind,java.lang.String,java.lang.String,java.lang.Long,java.util.List,I,java.lang.Object):com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItem
- equals(java.lang.Object):Z
- f:getTimestampMillis():java.lang.Long
- hashCode():I
f:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemBuilder
- f:children:java.util.List
- f:id:java.lang.String
- kind:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- preview:java.lang.String
- f:summarizesChildren:Z
- f:timestampMs:java.lang.Long
- title:java.lang.String
- <init>(java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind,java.lang.String,java.lang.String,java.lang.Long,Z,java.util.List):V
- b:<init>(java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind,java.lang.String,java.lang.String,java.lang.Long,Z,java.util.List,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:build():com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItem
e:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- java.lang.Enum
- sf:AGENT_WORK:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- sf:APPROVAL_REQUEST:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- sf:ASSISTANT_RESPONSE:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- sf:INPUT_REQUEST:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- sf:METADATA:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- sf:PLAN:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- sf:SUMMARY:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- sf:TOOL_CALL:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- sf:TOOL_RESULT:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- sf:USER_PROMPT:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- s:values():com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind[]
f:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineTreeRecord
- f:id:java.lang.String
- f:kind:com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- f:parentId:java.lang.String
- f:preview:java.lang.String
- f:timestampMs:java.lang.Long
- f:title:java.lang.String
- f:visible:Z
- <init>(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind,java.lang.String,java.lang.String,java.lang.Long,Z):V
- b:<init>(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind,java.lang.String,java.lang.String,java.lang.Long,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():java.lang.String
- f:component3():com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
- f:component4():java.lang.String
- f:component5():java.lang.String
- f:component6():java.lang.Long
- f:component7():Z
- f:copy(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind,java.lang.String,java.lang.String,java.lang.Long,Z):com.intellij.platform.ai.agent.core.session.AgentSessionOutlineTreeRecord
- bs:copy$default(com.intellij.platform.ai.agent.core.session.AgentSessionOutlineTreeRecord,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind,java.lang.String,java.lang.String,java.lang.Long,Z,I,java.lang.Object):com.intellij.platform.ai.agent.core.session.AgentSessionOutlineTreeRecord
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.core.session.AgentSessionProvider
- sf:Companion:com.intellij.platform.ai.agent.core.session.AgentSessionProvider$Companion
- bsf:box-impl(java.lang.String):com.intellij.platform.ai.agent.core.session.AgentSessionProvider
- equals(java.lang.Object):Z
- s:equals-impl(java.lang.String,java.lang.Object):Z
- sf:equals-impl0(java.lang.String,java.lang.String):Z
- f:getValue():java.lang.String
- hashCode():I
- s:hashCode-impl(java.lang.String):I
- s:toString-impl(java.lang.String):java.lang.String
- bf:unbox-impl():java.lang.String
f:com.intellij.platform.ai.agent.core.session.AgentSessionProvider$Companion
- f:from-V7M2pUA(java.lang.String):java.lang.String
- f:fromOrNull-6Etz2MU(java.lang.String):java.lang.String
f:com.intellij.platform.ai.agent.core.session.AgentSessionThread
- f:activityReport:com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- f:archived:Z
- f:cost:com.intellij.platform.ai.agent.core.session.AgentSessionCost
- f:id:java.lang.String
- f:originBranch:java.lang.String
- f:subAgents:java.util.List
- f:title:java.lang.String
- f:updatedAt:J
- b:<init>(java.lang.String,java.lang.String,J,Z,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.String,java.util.List,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionCost,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():java.lang.String
- f:component3():J
- f:component4():Z
- f:component5():com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- f:component6-dBzBBrw():java.lang.String
- f:component7():java.util.List
- f:component8():java.lang.String
- f:component9():com.intellij.platform.ai.agent.core.session.AgentSessionCost
- f:copy-JxZNnm8(java.lang.String,java.lang.String,J,Z,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.String,java.util.List,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionCost):com.intellij.platform.ai.agent.core.session.AgentSessionThread
- bs:copy-JxZNnm8$default(com.intellij.platform.ai.agent.core.session.AgentSessionThread,java.lang.String,java.lang.String,J,Z,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.String,java.util.List,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionCost,I,java.lang.Object):com.intellij.platform.ai.agent.core.session.AgentSessionThread
- equals(java.lang.Object):Z
- f:getProvider-dBzBBrw():java.lang.String
- hashCode():I
f:com.intellij.platform.ai.agent.core.session.AgentSessionThreadOutline
- f:items:java.util.List
- f:threadId:java.lang.String
- f:title:java.lang.String
- f:updatedAt:J
- f:component1-dBzBBrw():java.lang.String
- f:component2():java.lang.String
- f:component3():java.lang.String
- f:component4():J
- f:component5():java.util.List
- f:copy-fQRq96w(java.lang.String,java.lang.String,java.lang.String,J,java.util.List):com.intellij.platform.ai.agent.core.session.AgentSessionThreadOutline
- bs:copy-fQRq96w$default(com.intellij.platform.ai.agent.core.session.AgentSessionThreadOutline,java.lang.String,java.lang.String,java.lang.String,J,java.util.List,I,java.lang.Object):com.intellij.platform.ai.agent.core.session.AgentSessionThreadOutline
- equals(java.lang.Object):Z
- f:getProvider-dBzBBrw():java.lang.String
- f:getSessionId():java.lang.String
- hashCode():I
f:com.intellij.platform.ai.agent.core.session.AgentSubAgent
- f:activity:com.intellij.platform.ai.agent.core.AgentThreadActivity
- f:id:java.lang.String
- f:name:java.lang.String
- <init>(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivity):V
- b:<init>(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivity,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():java.lang.String
- f:component3():com.intellij.platform.ai.agent.core.AgentThreadActivity
- f:copy(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivity):com.intellij.platform.ai.agent.core.session.AgentSubAgent
- bs:copy$default(com.intellij.platform.ai.agent.core.session.AgentSubAgent,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivity,I,java.lang.Object):com.intellij.platform.ai.agent.core.session.AgentSubAgent
- equals(java.lang.Object):Z
- hashCode():I
@@ -1,2 +0,0 @@
java/math/BigDecimal
kotlin/jvm/internal/DefaultConstructorMarker
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="com.intellij.platform.ai.agent.core" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.concurrency" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.platform.util" />
</component>
</module>
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/testSrc">
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" packagePrefix="com.intellij.platform.ai.agent.core" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="TEST" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.concurrency" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.util" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.junit5" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.assertj.core" scope="TEST" />
</component>
<component name="TestModuleProperties" production-module="intellij.platform.ai.agent.core" />
</module>
@@ -1,2 +0,0 @@
<idea-plugin>
</idea-plugin>
@@ -1,51 +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.platform.ai.agent.core
import org.jetbrains.annotations.ApiStatus
enum class AgentThreadActivity {
READY,
PROCESSING,
REVIEWING,
NEEDS_INPUT,
UNREAD,
}
data class AgentThreadActivityReport(
@JvmField val rowActivity: AgentThreadActivity,
@JvmField val chromeActivity: AgentThreadActivity? = rowActivity,
) {
companion object {
@JvmField
val READY: AgentThreadActivityReport = AgentThreadActivityReport(AgentThreadActivity.READY)
}
}
enum class AgentThreadActivityBucket {
ATTENTION,
RUNNING,
DONE,
}
val AgentThreadActivity.isWorking: Boolean
get() = this == AgentThreadActivity.PROCESSING || this == AgentThreadActivity.REVIEWING
fun AgentThreadActivityReport.chromeBucket(): AgentThreadActivityBucket? {
return when (chromeActivity) {
AgentThreadActivity.NEEDS_INPUT,
AgentThreadActivity.REVIEWING,
-> AgentThreadActivityBucket.ATTENTION
AgentThreadActivity.PROCESSING -> AgentThreadActivityBucket.RUNNING
AgentThreadActivity.UNREAD -> AgentThreadActivityBucket.DONE
AgentThreadActivity.READY,
null,
-> null
}
}
@ApiStatus.Internal
fun AgentThreadActivityReport.chromePresentationActivity(): AgentThreadActivity {
// Chrome consumers need a concrete badge state; null chrome activity means no badge.
return chromeActivity ?: AgentThreadActivity.READY
}
@@ -1,38 +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.platform.ai.agent.core
private val PROVIDER_ID_REGEX = Regex("[a-z][a-z0-9._-]*")
data class AgentThreadIdentity(
@JvmField val providerId: String,
@JvmField val threadId: String,
)
fun buildAgentThreadIdentity(providerId: String, threadId: String): String {
require(PROVIDER_ID_REGEX.matches(providerId)) {
"Invalid provider id '$providerId'. Expected: ${PROVIDER_ID_REGEX.pattern}"
}
require(threadId.isNotBlank()) {
"Thread id must not be blank"
}
return "$providerId:$threadId"
}
fun parseAgentThreadIdentity(identity: String): AgentThreadIdentity? {
val separator = identity.indexOf(':')
if (separator <= 0 || separator == identity.lastIndex) {
return null
}
val providerId = identity.substring(0, separator)
if (!PROVIDER_ID_REGEX.matches(providerId)) {
return null
}
val threadId = identity.substring(separator + 1)
if (threadId.isBlank()) {
return null
}
return AgentThreadIdentity(providerId = providerId, threadId = threadId)
}
@@ -1,40 +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.platform.ai.agent.core
import java.nio.file.InvalidPathException
import java.nio.file.Path
import kotlin.io.path.invariantSeparatorsPathString
fun parseAgentWorkbenchPathOrNull(path: String): Path? {
return try {
Path.of(path)
}
catch (_: InvalidPathException) {
null
}
}
fun normalizeAgentWorkbenchPath(path: String): String {
return parseAgentWorkbenchPathOrNull(path)?.invariantSeparatorsPathString ?: path
}
fun normalizeAgentWorkbenchPathOrNull(path: String): String? {
return parseAgentWorkbenchPathOrNull(path)?.invariantSeparatorsPathString
}
fun normalizeAgentSessionProjectPath(path: String): String? {
val trimmedPath = path.trim().takeIf { it.isNotEmpty() } ?: return null
val normalizedPath = normalizeAgentWorkbenchPathOrNull(trimmedPath) ?: return null
return normalizedPath.trimEnd('/').ifEmpty { "/" }
}
fun normalizeAgentSessionTitle(title: String?): String? {
return title
?.replace('\n', ' ')
?.replace('\r', ' ')
?.replace(AGENT_SESSION_TITLE_WHITESPACE, " ")
?.trim()
?.takeIf { it.isNotEmpty() }
}
private val AGENT_SESSION_TITLE_WHITESPACE = Regex("\\s+")
@@ -1,122 +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.platform.ai.agent.core.extensions
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.ExtensionPointName
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
private sealed interface OverrideState<out T> {
data object Unset : OverrideState<Nothing>
data class Value<T>(val value: T) : OverrideState<T>
}
class OverridableValue<T>(
private val fallback: () -> T,
) {
private val lock = Any()
@Volatile
private var overrideState: OverrideState<T> = OverrideState.Unset
fun value(): T {
return when (val current = overrideState) {
OverrideState.Unset -> fallback()
is OverrideState.Value -> current.value
}
}
fun <R> withOverride(value: T, action: () -> R): R {
return synchronized(lock) {
val previous = overrideState
overrideState = OverrideState.Value(value)
try {
action()
}
finally {
overrideState = previous
}
}
}
}
class SuspendingOverridableValue<T>(
private val fallback: () -> T,
) {
private val mutex = Mutex()
@Volatile
private var overrideState: OverrideState<T> = OverrideState.Unset
fun value(): T {
return when (val current = overrideState) {
OverrideState.Unset -> fallback()
is OverrideState.Value -> current.value
}
}
suspend fun <R> withOverride(value: T, action: suspend () -> R): R {
return mutex.withLock {
val previous = overrideState
overrideState = OverrideState.Value(value)
try {
action()
}
finally {
overrideState = previous
}
}
}
}
class SnapshotExtensionPointCache<T : Any, S : Any>(
private val log: Logger,
private val extensionPoint: ExtensionPointName<T>,
private val cacheId: Class<S>,
private val emptySnapshot: S,
private val unavailableMessage: String,
private val buildSnapshot: (Iterable<T>) -> S,
) {
fun getSnapshotOrEmpty(): S {
return try {
extensionPoint.computeIfAbsent(cacheId) {
buildSnapshot(extensionPoint.extensionList)
}
}
catch (t: IllegalStateException) {
log.debug(unavailableMessage, t)
emptySnapshot
}
catch (t: IllegalArgumentException) {
log.debug(unavailableMessage, t)
emptySnapshot
}
}
}
class SingleExtensionPointResolver<T : Any>(
private val log: Logger,
private val extensionPoint: ExtensionPointName<T>,
private val unavailableMessage: String,
private val multipleExtensionsMessage: (List<T>) -> String,
) {
fun findFirstOrNull(): T? {
val extensions = try {
extensionPoint.extensionList
}
catch (t: IllegalStateException) {
log.debug(unavailableMessage, t)
return null
}
catch (t: IllegalArgumentException) {
log.debug(unavailableMessage, t)
return null
}
if (extensions.size > 1) {
log.warn(multipleExtensionsMessage(extensions))
}
return extensions.firstOrNull()
}
}
@@ -1,38 +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.platform.ai.agent.core.session
const val AGENT_SESSION_ARCHIVED_THREAD_TITLE_PREFIX: String = "[archived] "
data class AgentSessionArchivedTitleState(
@JvmField val title: String,
@JvmField val archived: Boolean,
)
fun resolveAgentSessionArchivedTitleState(rawTitle: String, defaultTitle: String): AgentSessionArchivedTitleState {
val archived = isAgentSessionArchivedThreadTitle(rawTitle)
val visibleTitle = normalizeAgentSessionStoredThreadTitle(stripAgentSessionArchivedThreadTitlePrefix(rawTitle))
?: defaultTitle
return AgentSessionArchivedTitleState(title = visibleTitle, archived = archived)
}
fun buildAgentSessionArchivedThreadTitle(title: String, defaultTitle: String): String {
return AGENT_SESSION_ARCHIVED_THREAD_TITLE_PREFIX + resolveAgentSessionArchivedTitleState(title, defaultTitle).title
}
fun isAgentSessionArchivedThreadTitle(title: String): Boolean {
return title.startsWith(AGENT_SESSION_ARCHIVED_THREAD_TITLE_PREFIX)
}
fun stripAgentSessionArchivedThreadTitlePrefix(title: String): String {
return if (isAgentSessionArchivedThreadTitle(title)) title.removePrefix(AGENT_SESSION_ARCHIVED_THREAD_TITLE_PREFIX) else title
}
fun normalizeAgentSessionStoredThreadTitle(value: String?): String? {
val normalized = value
?.replace('\n', ' ')
?.replace('\r', ' ')
?.replace(Regex("\\s+"), " ")
?.trim()
?: return null
return normalized.takeIf(String::isNotEmpty)
}
@@ -1,16 +0,0 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.ai.agent.core.session
import java.math.BigDecimal
enum class AgentSessionCostKind {
EXACT,
ESTIMATED,
UNAVAILABLE,
}
data class AgentSessionCost(
@JvmField val amountUsd: BigDecimal?,
@JvmField val kind: AgentSessionCostKind,
@JvmField val matchedModelId: String? = null,
)
@@ -1,288 +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.platform.ai.agent.core.session
// @spec plugins/air/spec/sessions/agent-terminal-sessions.spec.md
import com.intellij.platform.ai.agent.core.AgentThreadActivity
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
import com.intellij.openapi.util.NlsSafe
import java.util.IdentityHashMap
private val AGENT_SESSION_PROVIDER_ID_REGEX = Regex("[a-z][a-z0-9._-]*")
private val AGENT_SESSION_OUTLINE_WHITESPACE = Regex("\\s+")
@JvmInline
value class AgentSessionProvider private constructor(val value: String) {
companion object {
fun from(value: String): AgentSessionProvider {
require(AGENT_SESSION_PROVIDER_ID_REGEX.matches(value)) {
"Invalid provider id '$value'. Expected: ${AGENT_SESSION_PROVIDER_ID_REGEX.pattern}"
}
return AgentSessionProvider(value)
}
fun fromOrNull(value: String): AgentSessionProvider? {
return if (AGENT_SESSION_PROVIDER_ID_REGEX.matches(value)) AgentSessionProvider(value) else null
}
}
override fun toString(): String = value
}
enum class AgentSessionLaunchMode {
STANDARD,
YOLO,
}
data class AgentSubAgent(
@JvmField val id: @NlsSafe String,
@JvmField val name: @NlsSafe String,
@JvmField val activity: AgentThreadActivity = AgentThreadActivity.READY,
)
data class AgentSessionThread(
@JvmField val id: String,
@JvmField val title: String,
@JvmField val updatedAt: Long,
@JvmField val archived: Boolean,
@JvmField val activityReport: AgentThreadActivityReport = AgentThreadActivityReport.READY,
val provider: AgentSessionProvider,
@JvmField val subAgents: List<AgentSubAgent> = emptyList(),
@JvmField val originBranch: String? = null,
@JvmField val cost: AgentSessionCost? = null,
)
/**
* Provider-neutral outline for a persisted agent thread.
*
* This is a role-aware conversation outline, not a dump of provider-specific log records. [items] are the visible roots in provider
* history order. Providers should hide bookkeeping containers and expose user prompts, assistant responses, tool activity, plans,
* approvals, and summaries with the closest [AgentSessionOutlineItemKind]. A `null` outline from a source means the provider cannot
* load an outline for the thread; an empty [items] list means the outline is supported but has no visible entries.
*/
data class AgentSessionThreadOutline(
val provider: AgentSessionProvider,
@JvmField val threadId: String,
@JvmField val title: String,
@JvmField val updatedAt: Long,
@JvmField val items: List<AgentSessionOutlineItem>,
) {
val sessionId: String
get() = threadId
}
/**
* Visible item in an [AgentSessionThreadOutline].
*
* [id] must be unique within the outline and stable across reloads for the same provider record. If navigation or fork actions are
* enabled for this item, [id] must be a provider/backend anchor rather than a UI ordinal. [kind] is semantic role data and should be
* mapped from native provider roles, not chosen for presentation only. [title] is the primary row label; [preview] is supporting text
* and may contain the message body when the title is intentionally blank. [children] describe ownership or causality, for example tool
* calls and results under the assistant/work item that produced them.
*/
data class AgentSessionOutlineItem(
@JvmField val id: String,
@JvmField val kind: AgentSessionOutlineItemKind,
@JvmField val title: String,
@JvmField val preview: String? = null,
@JvmField val timestampMs: Long? = null,
@JvmField val children: List<AgentSessionOutlineItem> = emptyList(),
) {
val timestampMillis: Long?
get() = timestampMs
}
/**
* Semantic role of an [AgentSessionOutlineItem].
*
* Providers should map native transcript roles to these values consistently so shared UI and actions can reason about the outline:
* user-authored prompts use [USER_PROMPT], assistant text uses [ASSISTANT_RESPONSE], provider processing phases use [AGENT_WORK],
* invocations use [TOOL_CALL], their outputs use [TOOL_RESULT], and internal or unknown records should be skipped or mapped to
* [METADATA] instead of leaking raw provider event names.
*/
enum class AgentSessionOutlineItemKind {
USER_PROMPT,
ASSISTANT_RESPONSE,
AGENT_WORK,
TOOL_CALL,
TOOL_RESULT,
PLAN,
APPROVAL_REQUEST,
INPUT_REQUEST,
SUMMARY,
METADATA,
}
/** Mutable helper for constructing nested [AgentSessionOutlineItem] trees. */
class AgentSessionOutlineItemBuilder(
@JvmField val id: String,
@JvmField var kind: AgentSessionOutlineItemKind,
@JvmField var title: String,
@JvmField var preview: String?,
@JvmField val timestampMs: Long?,
@JvmField val summarizesChildren: Boolean = false,
@JvmField val children: MutableList<AgentSessionOutlineItemBuilder> = ArrayList(),
) {
fun build(): AgentSessionOutlineItem {
val builtChildren = children.map(AgentSessionOutlineItemBuilder::build)
return AgentSessionOutlineItem(
id = id,
kind = kind,
title = title,
preview = if (summarizesChildren && builtChildren.isNotEmpty()) summarizeAgentSessionOutlineChildren(builtChildren) else preview,
timestampMs = timestampMs,
children = builtChildren,
)
}
}
/**
* Flat source record used by [buildAgentSessionOutlineTree].
*
* [parentId] links provider records before they are converted to visible outline items. Set [visible] to `false` for bookkeeping
* containers such as raw turns or sessions; their visible descendants are promoted to the nearest visible ancestor or to the root.
* Records with missing, blank, or self-referential parents become roots. Duplicate ids keep the first record.
*/
data class AgentSessionOutlineTreeRecord(
@JvmField val id: String,
@JvmField val parentId: String?,
@JvmField val kind: AgentSessionOutlineItemKind,
@JvmField val title: String,
@JvmField val preview: String? = null,
@JvmField val timestampMs: Long? = null,
@JvmField val visible: Boolean = true,
)
/**
* Builds a visible outline tree from flat provider records.
*
* Returned roots and siblings are ordered by [AgentSessionOutlineTreeRecord.timestampMs] when present, then by input order. Invisible
* records are not returned, but their visible descendants are preserved and promoted. This lets providers keep raw parent/child
* relationships for future navigation while presenting a flat, role-aware conversation timeline.
*/
fun buildAgentSessionOutlineTree(records: List<AgentSessionOutlineTreeRecord>): List<AgentSessionOutlineItem> {
if (records.isEmpty()) {
return emptyList()
}
val nodesById = LinkedHashMap<String, AgentSessionOutlineTreeNode>(records.size)
records.forEachIndexed { index, record ->
val id = record.id.trim().takeIf { it.isNotEmpty() } ?: return@forEachIndexed
nodesById.putIfAbsent(id, AgentSessionOutlineTreeNode(record = record, order = index))
}
val roots = ArrayList<AgentSessionOutlineTreeNode>()
for (node in nodesById.values) {
val parentId = node.record.parentId?.trim()?.takeIf { it.isNotEmpty() && it != node.record.id }
val parent = parentId?.let(nodesById::get)
if (parent == null) {
roots += node
}
else {
parent.children += node
}
}
return buildVisibleOutlineItems(roots)
}
fun normalizeAgentSessionOutlinePreview(value: String?, maxLength: Int = 160): String? {
val normalized = value
?.replace('\n', ' ')
?.replace('\r', ' ')
?.replace(AGENT_SESSION_OUTLINE_WHITESPACE, " ")
?.trim()
?.takeIf { it.isNotEmpty() }
?: return null
return if (normalized.length <= maxLength) normalized else normalized.take(maxLength - 3).trimEnd() + "..."
}
fun compactAgentSessionOutlineText(value: String?, maxLength: Int): String? {
return normalizeAgentSessionOutlinePreview(value, maxLength = maxLength)
}
fun agentSessionOutlinePhaseTitle(preview: String?, maxLength: Int = 90): String? {
val normalizedPreview = normalizeAgentSessionOutlinePreview(preview) ?: return null
val sentenceEnd = normalizedPreview.indexOfAny(charArrayOf('.', '!', '?'))
val firstSentence = if (sentenceEnd > 0) normalizedPreview.take(sentenceEnd + 1) else normalizedPreview
return firstSentence.take(maxLength).trimEnd().takeIf { it.isNotEmpty() }
}
fun dedupeAgentSessionOutlineText(value: String): String {
return value.replace(AGENT_SESSION_OUTLINE_WHITESPACE, " ").trim().lowercase()
}
fun summarizeAgentSessionOutlineChildren(children: List<AgentSessionOutlineItem>): String? {
val parts = ArrayList<String>(6)
addAgentSessionOutlineCount(parts, children.count { it.kind == AgentSessionOutlineItemKind.TOOL_CALL }, "tool")
addAgentSessionOutlineCount(parts, children.count { it.kind == AgentSessionOutlineItemKind.TOOL_RESULT }, "result")
addAgentSessionOutlineCount(parts, children.count { it.kind == AgentSessionOutlineItemKind.PLAN }, "plan")
addAgentSessionOutlineCount(parts, children.count { it.kind == AgentSessionOutlineItemKind.APPROVAL_REQUEST }, "approval")
addAgentSessionOutlineCount(parts, children.count { it.kind == AgentSessionOutlineItemKind.INPUT_REQUEST }, "input request")
addAgentSessionOutlineCount(parts, children.count { it.kind == AgentSessionOutlineItemKind.AGENT_WORK }, "work item")
return parts.joinToString(", ").takeIf { it.isNotEmpty() }
}
private fun buildVisibleOutlineItems(roots: List<AgentSessionOutlineTreeNode>): List<AgentSessionOutlineItem> {
val builtByNode = IdentityHashMap<AgentSessionOutlineTreeNode, List<AgentSessionOutlineItem>>()
val stack = ArrayDeque<AgentSessionOutlineTreeStackFrame>()
roots.sortedWith(AGENT_SESSION_OUTLINE_TREE_NODE_COMPARATOR).asReversed().forEach { root ->
stack.addLast(AgentSessionOutlineTreeStackFrame(root, childrenVisited = false))
}
while (stack.isNotEmpty()) {
val frame = stack.removeLast()
val node = frame.node
if (!frame.childrenVisited) {
stack.addLast(AgentSessionOutlineTreeStackFrame(node, childrenVisited = true))
node.children.sortedWith(AGENT_SESSION_OUTLINE_TREE_NODE_COMPARATOR).asReversed().forEach { child ->
stack.addLast(AgentSessionOutlineTreeStackFrame(child, childrenVisited = false))
}
continue
}
val builtChildren = node.children.sortedWith(AGENT_SESSION_OUTLINE_TREE_NODE_COMPARATOR)
.flatMap { child -> builtByNode.remove(child).orEmpty() }
val record = node.record
builtByNode[node] = if (record.visible) {
listOf(
AgentSessionOutlineItem(
id = record.id,
kind = record.kind,
title = record.title,
preview = normalizeAgentSessionOutlinePreview(record.preview),
timestampMs = record.timestampMs,
children = builtChildren,
)
)
}
else {
builtChildren
}
}
return roots.sortedWith(AGENT_SESSION_OUTLINE_TREE_NODE_COMPARATOR)
.flatMap { root -> builtByNode[root].orEmpty() }
}
private fun addAgentSessionOutlineCount(parts: MutableList<String>, count: Int, label: String) {
if (count <= 0) {
return
}
parts += "$count ${if (count == 1) label else label + "s"}"
}
private data class AgentSessionOutlineTreeNode(
@JvmField val record: AgentSessionOutlineTreeRecord,
@JvmField val order: Int,
@JvmField val children: MutableList<AgentSessionOutlineTreeNode> = ArrayList(),
)
private data class AgentSessionOutlineTreeStackFrame(
@JvmField val node: AgentSessionOutlineTreeNode,
@JvmField val childrenVisited: Boolean,
)
private val AGENT_SESSION_OUTLINE_TREE_NODE_COMPARATOR = compareBy<AgentSessionOutlineTreeNode>(
{ node -> node.record.timestampMs ?: Long.MAX_VALUE },
{ node -> node.order },
)
@@ -1,34 +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.platform.ai.agent.core
import com.intellij.platform.ai.agent.core.session.AGENT_SESSION_ARCHIVED_THREAD_TITLE_PREFIX
import com.intellij.platform.ai.agent.core.session.buildAgentSessionArchivedThreadTitle
import com.intellij.platform.ai.agent.core.session.resolveAgentSessionArchivedTitleState
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class AgentSessionArchiveTitlesTest {
@Test
fun `archived title prefix is stripped from visible title`() {
val state = resolveAgentSessionArchivedTitleState("${AGENT_SESSION_ARCHIVED_THREAD_TITLE_PREFIX}Fix tests", defaultTitle = "Fallback")
assertThat(state.archived).isTrue()
assertThat(state.title).isEqualTo("Fix tests")
}
@Test
fun `missing visible title falls back to default`() {
val state = resolveAgentSessionArchivedTitleState(AGENT_SESSION_ARCHIVED_THREAD_TITLE_PREFIX, defaultTitle = "Fallback")
assertThat(state.archived).isTrue()
assertThat(state.title).isEqualTo("Fallback")
}
@Test
fun `building archived title does not duplicate prefix`() {
val archivedTitle =
buildAgentSessionArchivedThreadTitle("${AGENT_SESSION_ARCHIVED_THREAD_TITLE_PREFIX}Fix tests", defaultTitle = "Fallback")
assertThat(archivedTitle).isEqualTo("${AGENT_SESSION_ARCHIVED_THREAD_TITLE_PREFIX}Fix tests")
}
}
@@ -1,59 +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.platform.ai.agent.core
import com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
import com.intellij.platform.ai.agent.core.session.AgentSessionOutlineTreeRecord
import com.intellij.platform.ai.agent.core.session.buildAgentSessionOutlineTree
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class AgentSessionOutlineTreeTest {
@Test
fun `hidden bookkeeping records promote visible children`() {
val items = buildAgentSessionOutlineTree(
listOf(
record(id = "user", parentId = null, kind = AgentSessionOutlineItemKind.USER_PROMPT, title = "User", timestampMs = 1_000L),
record(id = "label", parentId = "user", visible = false, timestampMs = 2_000L),
record(id = "assistant",
parentId = "label",
kind = AgentSessionOutlineItemKind.ASSISTANT_RESPONSE,
title = "Done",
timestampMs = 3_000L),
)
)
assertThat(items).hasSize(1)
assertThat(items.single().id).isEqualTo("user")
assertThat(items.single().children.map { it.id }).containsExactly("assistant")
}
@Test
fun `missing and self parents become timestamp ordered roots`() {
val items = buildAgentSessionOutlineTree(
listOf(
record(id = "late", parentId = "missing", title = "Late", timestampMs = 3_000L),
record(id = "early", parentId = "early", title = "Early", timestampMs = 1_000L),
)
)
assertThat(items.map { it.id }).containsExactly("early", "late")
}
private fun record(
id: String,
parentId: String?,
kind: AgentSessionOutlineItemKind = AgentSessionOutlineItemKind.METADATA,
title: String = "Metadata",
timestampMs: Long?,
visible: Boolean = true,
): AgentSessionOutlineTreeRecord {
return AgentSessionOutlineTreeRecord(
id = id,
parentId = parentId,
kind = kind,
title = title,
timestampMs = timestampMs,
visible = visible,
)
}
}
@@ -1,40 +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.platform.ai.agent.core
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import java.util.concurrent.TimeUnit
@Timeout(value = 2, unit = TimeUnit.MINUTES)
class AgentThreadIdentityTest {
@Test
fun buildsAndParsesValidIdentity() {
val identity = buildAgentThreadIdentity(providerId = "codex", threadId = "thread-1")
assertThat(identity).isEqualTo("codex:thread-1")
assertThat(parseAgentThreadIdentity(identity)).isEqualTo(AgentThreadIdentity(providerId = "codex", threadId = "thread-1"))
}
@Test
fun rejectsInvalidProviderIdWhenBuildingIdentity() {
assertThatThrownBy { buildAgentThreadIdentity(providerId = "Codex", threadId = "thread-1") }
.isInstanceOf(IllegalArgumentException::class.java)
.hasMessageContaining("Invalid provider id")
}
@Test
fun rejectsBlankThreadIdWhenBuildingIdentity() {
assertThatThrownBy { buildAgentThreadIdentity(providerId = "codex", threadId = " ") }
.isInstanceOf(IllegalArgumentException::class.java)
.hasMessageContaining("Thread id must not be blank")
}
@Test
fun rejectsMalformedIdentityWhenParsing() {
assertThat(parseAgentThreadIdentity("codex")).isNull()
assertThat(parseAgentThreadIdentity("Codex:thread-1")).isNull()
assertThat(parseAgentThreadIdentity("codex: ")).isNull()
}
}
@@ -1,46 +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.platform.ai.agent.core
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import java.nio.file.Path
import java.util.concurrent.TimeUnit
@Timeout(value = 2, unit = TimeUnit.MINUTES)
class AgentWorkbenchPathUtilTest {
@Test
fun parsesAndNormalizesValidPaths() {
val nativePath = Path.of("workspace", "child").toString()
assertThat(parseAgentWorkbenchPathOrNull(nativePath)).isEqualTo(Path.of("workspace", "child"))
assertThat(normalizeAgentWorkbenchPath(nativePath)).isEqualTo("workspace/child")
assertThat(normalizeAgentWorkbenchPathOrNull(nativePath)).isEqualTo("workspace/child")
}
@Test
fun preservesInvalidPathsWhenNormalizationFails() {
val invalidPath = "bad" + '\u0000' + "path"
assertThat(parseAgentWorkbenchPathOrNull(invalidPath)).isNull()
assertThat(normalizeAgentWorkbenchPath(invalidPath)).isEqualTo(invalidPath)
assertThat(normalizeAgentWorkbenchPathOrNull(invalidPath)).isNull()
}
@Test
fun normalizesAgentSessionProjectPaths() {
val nativePath = Path.of("workspace", "child").toString()
assertThat(normalizeAgentSessionProjectPath(" $nativePath/ ")).isEqualTo("workspace/child")
assertThat(normalizeAgentSessionProjectPath("/")).isEqualTo("/")
assertThat(normalizeAgentSessionProjectPath(" ")).isNull()
assertThat(normalizeAgentSessionProjectPath("bad" + '\u0000' + "path")).isNull()
}
@Test
fun normalizesAgentSessionTitles() {
assertThat(normalizeAgentSessionTitle(" first\nsecond\tthird\r ")).isEqualTo("first second third")
assertThat(normalizeAgentSessionTitle(" ")).isNull()
assertThat(normalizeAgentSessionTitle(null)).isNull()
}
}
@@ -1,115 +0,0 @@
load("@community//build:tests-options.bzl", "jps_test")
load("@rules_jvm//:jvm.bzl", "jvm_library")
### auto-generated section `build intellij.platform.ai.agent.filewatch` start
jvm_library(
name = "filewatch",
srcs = glob(
[
"src/**/*.kt",
"src/**/*.java",
"src/**/*.form",
],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.filewatch",
resource_strip_prefix = "resources",
resources = glob(
["resources/**/*"],
),
visibility = ["//visibility:public"],
deps = [
# do not sort,
"@lib//:kotlin-stdlib",
"//libraries/kotlinx/coroutines/core",
"@lib//:jna",
"//platform/util",
],
)
jvm_library(
name = "filewatch_test_lib",
testonly = True,
srcs = glob(
[],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.filewatch",
visibility = ["//visibility:public"],
runtime_deps = [
# do not sort,
":filewatch",
"//libraries/kotlinx/coroutines/core:core_test_lib",
"//platform/util:util_test_lib",
],
)
### auto-generated section `build intellij.platform.ai.agent.filewatch` end
### auto-generated section `iml intellij.platform.ai.agent.filewatch` start
exports_files(
[
"intellij.platform.ai.agent.filewatch.iml",
],
visibility = ["//visibility:public"],
)
### auto-generated section `iml intellij.platform.ai.agent.filewatch` end
### auto-generated section `build intellij.platform.ai.agent.filewatch.tests` start
jvm_library(
name = "ai-agent-filewatch-tests",
srcs = glob(
[],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.filewatch.tests",
visibility = ["//visibility:public"],
)
jvm_library(
name = "ai-agent-filewatch-tests_test_lib",
testonly = True,
srcs = glob(
[
"testSrc/**/*.kt",
"testSrc/**/*.java",
"testSrc/**/*.form",
],
allow_empty = True,
),
associates = [
"//plugins/agent-workbench/lib-agent/filewatch",
"//plugins/agent-workbench/lib-agent/filewatch:filewatch_test_lib",
],
visibility = ["//visibility:public"],
runtime_deps = [":ai-agent-filewatch-tests"],
deps = [
# do not sort,
"@lib//:kotlin-stdlib",
"//libraries/kotlinx/coroutines/core",
"//libraries/kotlinx/coroutines/core:core_test_lib",
"//libraries/junit5",
"//libraries/junit5:junit5_test_lib",
"//libraries/assertj-core",
"//libraries/assertj-core:assertj-core_test_lib",
],
)
### auto-generated section `build intellij.platform.ai.agent.filewatch.tests` end
### auto-generated section `iml intellij.platform.ai.agent.filewatch.tests` start
exports_files(
[
"intellij.platform.ai.agent.filewatch.tests.iml",
],
visibility = ["//visibility:public"],
)
### auto-generated section `iml intellij.platform.ai.agent.filewatch.tests` end
### auto-generated section `test intellij.platform.ai.agent.filewatch.tests` start
jps_test(
name = "ai-agent-filewatch-tests_test",
runtime_deps = [":ai-agent-filewatch-tests_test_lib"],
)
### auto-generated section `test intellij.platform.ai.agent.filewatch.tests` end
@@ -1,37 +0,0 @@
f:com.intellij.platform.ai.agent.filewatch.AgentWorkbenchDirectoryWatcher
- java.lang.AutoCloseable
- <init>(java.util.Collection,kotlinx.coroutines.CoroutineScope,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function2):V
- b:<init>(java.util.Collection,kotlinx.coroutines.CoroutineScope,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function2,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- close():V
- f:closeAndJoin(kotlin.coroutines.Continuation):java.lang.Object
- f:isActive():Z
f:com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEvent
- f:count:I
- f:eventType:com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType
- f:isDirectory:Z
- f:path:java.nio.file.Path
- f:rootPath:java.nio.file.Path
- <init>(com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType,java.nio.file.Path,java.nio.file.Path,Z,I):V
- f:component1():com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType
- f:component2():java.nio.file.Path
- f:component3():java.nio.file.Path
- f:component4():Z
- f:component5():I
- f:copy(com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType,java.nio.file.Path,java.nio.file.Path,Z,I):com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEvent
- bs:copy$default(com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEvent,com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType,java.nio.file.Path,java.nio.file.Path,Z,I,I,java.lang.Object):com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEvent
- equals(java.lang.Object):Z
- hashCode():I
e:com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType
- java.lang.Enum
- sf:CREATE:com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType
- sf:DELETE:com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType
- sf:MODIFY:com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType
- sf:OVERFLOW:com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType
- s:values():com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType[]
com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchLoop
- java.lang.AutoCloseable
- a:watch(kotlin.coroutines.Continuation):java.lang.Object
f:com.intellij.platform.ai.agent.filewatch.ImmediateFileChangeFlowKt
- sf:agentWorkbenchImmediateFileChangeFlow(java.util.Collection):kotlinx.coroutines.flow.Flow
@@ -1 +0,0 @@
kotlin/jvm/internal/DefaultConstructorMarker
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="com.intellij.platform.ai.agent.filewatch" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.libraries.kotlinx.coroutines.core" />
<orderEntry type="library" name="jna" level="project" />
<orderEntry type="module" module-name="intellij.platform.util" />
</component>
</module>
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/testSrc">
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" packagePrefix="com.intellij.platform.ai.agent.filewatch" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="TEST" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.libraries.kotlinx.coroutines.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.junit5" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.assertj.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.filewatch" scope="TEST" />
</component>
<component name="TestModuleProperties" production-module="intellij.platform.ai.agent.filewatch" />
</module>
@@ -1,2 +0,0 @@
<idea-plugin>
</idea-plugin>
@@ -1,205 +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.platform.ai.agent.filewatch
import com.intellij.platform.ai.agent.filewatch.impl.DirectoryChangeEvent
import com.intellij.platform.ai.agent.filewatch.impl.DirectoryChangeListener
import com.intellij.platform.ai.agent.filewatch.impl.DirectoryWatcher
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicBoolean
enum class AgentWorkbenchWatchEventType {
CREATE,
MODIFY,
DELETE,
OVERFLOW,
}
data class AgentWorkbenchWatchEvent(
@JvmField val eventType: AgentWorkbenchWatchEventType,
@JvmField val path: Path?,
@JvmField val rootPath: Path?,
@JvmField val isDirectory: Boolean,
@JvmField val count: Int,
)
class AgentWorkbenchDirectoryWatcher private constructor(
roots: Collection<Path>,
scope: CoroutineScope,
private val onWatchEvent: suspend (AgentWorkbenchWatchEvent) -> Unit,
private val onFailure: suspend (Throwable) -> Unit = {},
private val watchLoopFactory: (List<Path>, DirectoryChangeListener) -> AgentWorkbenchWatchLoop,
@Suppress("unused") private val constructorMarker: Unit,
) : AutoCloseable {
private val roots = roots.toList()
private val running = AtomicBoolean(true)
@Volatile
private var watchLoop: AgentWorkbenchWatchLoop?
private val watcherJob: Job?
constructor(
roots: Collection<Path>,
scope: CoroutineScope,
onWatchEvent: suspend (AgentWorkbenchWatchEvent) -> Unit,
onFailure: suspend (Throwable) -> Unit = {},
) : this(roots, scope, onWatchEvent, onFailure, ::createDefaultWatchLoop, Unit)
internal constructor(
roots: Collection<Path>,
scope: CoroutineScope,
onWatchEvent: suspend (AgentWorkbenchWatchEvent) -> Unit,
onFailure: suspend (Throwable) -> Unit = {},
watchLoopFactory: (List<Path>, DirectoryChangeListener) -> AgentWorkbenchWatchLoop,
) : this(roots, scope, onWatchEvent, onFailure, watchLoopFactory, Unit)
init {
watchLoop = createWatchLoop(roots)
watcherJob = if (watchLoop != null) {
scope.launch(Dispatchers.IO) {
runWatchLoops()
}
}
else {
null
}
}
val isActive: Boolean
get() = watchLoop != null && watcherJob?.isActive == true
override fun close() {
if (!running.compareAndSet(true, false)) return
watcherJob?.cancel()
watchLoop?.let { watcher ->
runCatching {
watcher.close()
}
}
}
@Suppress("unused")
suspend fun closeAndJoin() {
if (running.compareAndSet(true, false)) {
watcherJob?.cancel()
runCatching {
watchLoop?.close()
}.onFailure { t ->
onFailure(t)
}
}
watcherJob?.cancelAndJoin()
}
private fun createWatchLoop(roots: Collection<Path>): AgentWorkbenchWatchLoop? {
val watchRoots = LinkedHashSet<Path>()
for (root in roots) {
val normalizedPath = normalizeWatchPath(root)
if (Files.isDirectory(normalizedPath)) {
watchRoots.add(normalizedPath)
}
}
if (watchRoots.isEmpty()) {
return null
}
val listener = object : DirectoryChangeListener {
override suspend fun onEvent(event: DirectoryChangeEvent) {
if (running.get()) {
onWatchEvent(event.toAgentWorkbenchWatchEvent())
}
}
override suspend fun onException(e: Exception) {
if (running.get()) {
onFailure(e)
}
}
}
return watchLoopFactory(ArrayList(watchRoots), listener)
}
private suspend fun runWatchLoops() {
while (running.get()) {
val watcher = watchLoop ?: return
val shouldRestart = try {
watcher.watch()
false
}
catch (e: CancellationException) {
throw e
}
catch (t: Throwable) {
if (running.get()) {
onFailure(t)
true
}
else {
false
}
}
finally {
if (watchLoop === watcher) {
watchLoop = null
}
runCatching {
watcher.close()
}.onFailure { t ->
if (running.get()) {
onFailure(t)
}
}
}
if (!shouldRestart || !running.get()) {
return
}
watchLoop = createWatchLoop(roots)
}
}
}
interface AgentWorkbenchWatchLoop : AutoCloseable {
suspend fun watch()
}
private fun createDefaultWatchLoop(paths: List<Path>, listener: DirectoryChangeListener): AgentWorkbenchWatchLoop {
val watcher = DirectoryWatcher(paths, listener)
return object : AgentWorkbenchWatchLoop {
override suspend fun watch() {
watcher.watch()
}
override fun close() {
watcher.close()
}
}
}
private fun DirectoryChangeEvent.toAgentWorkbenchWatchEvent(): AgentWorkbenchWatchEvent {
return AgentWorkbenchWatchEvent(
eventType = when (eventType) {
DirectoryChangeEvent.EventType.CREATE -> AgentWorkbenchWatchEventType.CREATE
DirectoryChangeEvent.EventType.MODIFY -> AgentWorkbenchWatchEventType.MODIFY
DirectoryChangeEvent.EventType.DELETE -> AgentWorkbenchWatchEventType.DELETE
DirectoryChangeEvent.EventType.OVERFLOW -> AgentWorkbenchWatchEventType.OVERFLOW
},
path = path,
rootPath = rootPath,
isDirectory = isDirectory,
count = count,
)
}
private fun normalizeWatchPath(path: Path): Path {
return runCatching {
path.toAbsolutePath().normalize()
}.getOrElse {
path.normalize()
}
}
@@ -1,224 +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.platform.ai.agent.filewatch
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.KEvent
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.K_EVENT_ADD
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.K_EVENT_CLEAR
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.K_EVENT_FILTER_VNODE
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.K_NOTE_DELETE
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.K_NOTE_EXTEND
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.K_NOTE_RENAME
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.K_NOTE_REVOKE
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.K_NOTE_WRITE
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.K_QUEUE_OPEN_EVENT_ONLY
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.macKQueueApi
import com.intellij.openapi.util.SystemInfoRt.isMac
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.emptyFlow
import java.nio.file.Files
import java.nio.file.Path
fun agentWorkbenchImmediateFileChangeFlow(paths: Collection<Path>): Flow<Path> {
if (!isMac || paths.isEmpty()) {
return emptyFlow()
}
val normalizedPaths = paths.asSequence()
.map(::normalizeImmediateWatchPath)
.distinct()
.toList()
if (normalizedPaths.isEmpty()) {
return emptyFlow()
}
return callbackFlow {
val watcher = MacOSXImmediateFileChangeWatcher.create(
paths = normalizedPaths,
onModify = { path -> trySend(path).isSuccess },
onEmpty = { close() },
)
if (watcher == null) {
close()
return@callbackFlow
}
awaitClose {
watcher.close()
}
}
}
private class MacOSXImmediateFileChangeWatcher private constructor(
private val kqueueFd: Int,
private val onModify: (Path) -> Unit,
private val onEmpty: () -> Unit,
) : AutoCloseable {
companion object {
fun create(paths: Collection<Path>, onModify: (Path) -> Unit, onEmpty: () -> Unit): MacOSXImmediateFileChangeWatcher? {
val kqueueFd = runCatching {
macKQueueApi.kqueue()
}.getOrDefault(-1)
if (kqueueFd < 0) {
return null
}
val watcher = MacOSXImmediateFileChangeWatcher(kqueueFd, onModify, onEmpty)
for (path in paths) {
watcher.watch(path)
}
if (!watcher.hasWatchedFiles()) {
watcher.close()
return null
}
watcher.start()
return watcher
}
}
private val lock = Any()
private val watchedFiles = LinkedHashMap<Path, WatchedFile>()
private val watchedFds = HashMap<Int, WatchedFile>()
@Volatile
private var closed = false
private fun start() {
Thread({ runEventLoop() }, "Agent Workbench macOS immediate file watcher").apply {
isDaemon = true
start()
}
}
private fun hasWatchedFiles(): Boolean {
return synchronized(lock) {
watchedFiles.isNotEmpty()
}
}
private fun watch(path: Path) {
if (!Files.isRegularFile(path)) {
return
}
synchronized(lock) {
if (closed || path in watchedFiles) {
return
}
}
val fd = runCatching {
macKQueueApi.open(path.toString(), K_QUEUE_OPEN_EVENT_ONLY)
}.getOrDefault(-1)
if (fd < 0) {
return
}
var registered = false
try {
val event = KEvent(
ident = fd,
filter = K_EVENT_FILTER_VNODE,
flags = (K_EVENT_ADD.toInt() or K_EVENT_CLEAR.toInt()).toShort(),
fflags = K_NOTE_WRITE or K_NOTE_EXTEND or K_NOTE_DELETE or K_NOTE_RENAME or K_NOTE_REVOKE,
)
val result = runCatching {
macKQueueApi.kevent(kqueueFd, event, 1, null, 0, null)
}.getOrDefault(-1)
if (result < 0) {
return
}
val watchedFile = WatchedFile(path, fd)
synchronized(lock) {
if (!closed && path !in watchedFiles) {
watchedFiles[path] = watchedFile
watchedFds[fd] = watchedFile
registered = true
}
}
}
finally {
if (!registered) {
closeFileDescriptor(fd)
}
}
}
private fun unwatch(path: Path) {
removeWatch(path)?.let { watchedFile ->
closeFileDescriptor(watchedFile.fd)
}
}
override fun close() {
val watchedFilesToClose = synchronized(lock) {
if (closed) {
return
}
closed = true
val files = ArrayList(watchedFiles.values)
watchedFiles.clear()
watchedFds.clear()
files
}
closeFileDescriptor(kqueueFd)
for ((_, fd) in watchedFilesToClose) {
closeFileDescriptor(fd)
}
}
private fun runEventLoop() {
while (!closed) {
val event = KEvent()
val result = runCatching {
macKQueueApi.kevent(kqueueFd, null, 0, event, 1, null)
}.getOrDefault(-1)
if (result < 0) {
return
}
if (result == 0) {
continue
}
handleEvent(event)
}
}
private fun handleEvent(event: KEvent) {
val fd = event.ident.toInt()
val watchedFile = synchronized(lock) {
watchedFds[fd]
} ?: return
val flags = event.fflags
if (flags and (K_NOTE_WRITE or K_NOTE_EXTEND) != 0) {
onModify(watchedFile.path)
}
if (flags and (K_NOTE_DELETE or K_NOTE_RENAME or K_NOTE_REVOKE) != 0) {
unwatch(watchedFile.path)
if (!hasWatchedFiles()) {
onEmpty()
}
}
}
private fun removeWatch(path: Path): WatchedFile? {
return synchronized(lock) {
val watchedFile = watchedFiles.remove(path) ?: return@synchronized null
watchedFds.remove(watchedFile.fd)
watchedFile
}
}
private fun closeFileDescriptor(fd: Int) {
runCatching {
macKQueueApi.close(fd)
}
}
private data class WatchedFile(@JvmField val path: Path, @JvmField val fd: Int)
}
private fun normalizeImmediateWatchPath(path: Path): Path {
return runCatching {
path.toAbsolutePath().normalize()
}.getOrElse {
path.normalize()
}
}
@@ -1,19 +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.platform.ai.agent.filewatch.impl
import java.nio.file.Path
internal data class DirectoryChangeEvent(
val eventType: EventType,
val isDirectory: Boolean,
val path: Path?,
val count: Int,
val rootPath: Path?,
) {
internal enum class EventType {
CREATE,
MODIFY,
DELETE,
OVERFLOW,
}
}
@@ -1,9 +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.platform.ai.agent.filewatch.impl
internal fun interface DirectoryChangeListener {
suspend fun onEvent(event: DirectoryChangeEvent)
suspend fun onException(e: Exception) {
}
}
@@ -1,211 +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.platform.ai.agent.filewatch.impl
import com.intellij.platform.ai.agent.filewatch.impl.DirectoryChangeEvent.EventType
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.MacOSXListeningWatchService
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.WatchablePath
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.sun.nio.file.ExtendedWatchEventModifier
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
import kotlinx.coroutines.runInterruptible
import java.nio.file.ClosedWatchServiceException
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.LinkOption.NOFOLLOW_LINKS
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE
import java.nio.file.StandardWatchEventKinds.ENTRY_DELETE
import java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY
import java.nio.file.StandardWatchEventKinds.OVERFLOW
import java.nio.file.WatchEvent
import java.nio.file.WatchKey
import java.nio.file.WatchService
import java.nio.file.Watchable
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
private val LOG = logger<DirectoryWatcher>()
internal class DirectoryWatcher(
paths: List<Path>,
private val listener: DirectoryChangeListener,
private val watchService: WatchService = createDefaultWatchService(),
private val fileTreeVisitor: FileTreeVisitor = DefaultFileTreeVisitor,
private val watchablePathFactory: (Path) -> Watchable = if (watchService is MacOSXListeningWatchService) ::WatchablePath else { path -> path },
) : AutoCloseable {
private val paths = paths.map { path -> path.toAbsolutePath() }
private val isMac = watchService is MacOSXListeningWatchService
private val registeredPathToRootPath = HashMap<Path, Path>()
private val directories = ConcurrentHashMap.newKeySet<Path>()
private val keyRoots = ConcurrentHashMap<WatchKey, Path>()
private val closed = AtomicBoolean()
private var fileTreeSupported: Boolean? = null
suspend fun watch() {
check(!closed.get()) { "watcher already closed" }
use {
registerPaths()
runEventLoop()
}
}
override fun close() {
if (closed.compareAndSet(false, true)) {
watchService.close()
}
}
private suspend fun registerPaths() {
for (path in paths) {
registerAll(path, path)
}
}
private suspend fun runEventLoop() {
while (!closed.get() && currentCoroutineContext().isActive) {
val key = try {
runInterruptible { watchService.take() }
}
catch (_: InterruptedException) {
return
}
catch (_: ClosedWatchServiceException) {
return
}
for (event in key.pollEvents()) {
try {
handleEvent(key, event)
}
catch (e: Exception) {
LOG.debug("DirectoryWatcher got an exception while watching!", e)
listener.onException(e)
}
}
if (!key.reset()) {
LOG.debug { "WatchKey for [${key.watchable()}] no longer valid; removing." }
val registeredPath = keyRoots.remove(key)
registeredPathToRootPath.remove(registeredPath)
if (keyRoots.isEmpty()) {
LOG.debug("No more directories left to watch; terminating watcher.")
throw IllegalStateException("No more directories left to watch")
}
}
}
}
private suspend fun handleEvent(key: WatchKey, event: WatchEvent<*>) {
val kind = event.kind()
val count = event.count()
val eventPath = event.pathContext()
val registeredPath = keyRoots[key] ?: throw IllegalStateException("WatchService returned key [$key] but it was not found in keyRoots!")
val rootPath = registeredPathToRootPath[registeredPath]
val childPath = eventPath?.let { registeredPath.resolve(it) }
LOG.debug { "$kind [$childPath]" }
when {
kind === OVERFLOW -> onEvent(EventType.OVERFLOW, false, childPath, count, rootPath)
eventPath == null -> throw IllegalStateException("WatchService returned a null path for ${kind.name()}")
kind === ENTRY_CREATE -> handleCreate(childPath!!, count, rootPath)
kind === ENTRY_MODIFY -> onEvent(EventType.MODIFY, directories.contains(childPath), childPath, count, rootPath)
kind === ENTRY_DELETE -> onEvent(EventType.DELETE, directories.remove(childPath), childPath, count, rootPath)
}
}
private suspend fun handleCreate(childPath: Path, count: Int, rootPath: Path?) {
val isDirectory = Files.isDirectory(childPath, NOFOLLOW_LINKS)
if (isDirectory) {
if (!isFileTreeSupported()) {
registerAll(childPath, rootPath)
}
if (!isMac) {
val createdDirectories = ArrayList<Path>()
val createdFiles = ArrayList<Path>()
runInterruptible {
fileTreeVisitor.recursiveVisitFiles(childPath, createdDirectories::add, createdFiles::add)
}
for (dir in createdDirectories) {
notifyCreateEvent(true, dir, count, rootPath)
}
for (file in createdFiles) {
notifyCreateEvent(false, file, count, rootPath)
}
}
else {
notifyCreateEvent(true, childPath, count, rootPath)
}
}
else {
notifyCreateEvent(false, childPath, count, rootPath)
}
}
private suspend fun onEvent(eventType: EventType, isDirectory: Boolean, childPath: Path?, count: Int, rootPath: Path?) {
LOG.debug { "-> $eventType [$childPath] (isDirectory: $isDirectory)" }
listener.onEvent(DirectoryChangeEvent(eventType, isDirectory, childPath, count, rootPath))
}
private suspend fun registerAll(start: Path, context: Path?) {
if (fileTreeSupported != false) {
try {
register(start, true, context)
fileTreeSupported = true
addKnownDirectories(start)
}
catch (e: UnsupportedOperationException) {
LOG.debug("Assuming ExtendedWatchEventModifier.FILE_TREE is not supported", e)
fileTreeSupported = false
registerAll(start, context)
}
}
else {
val directories = ArrayList<Path>()
runInterruptible {
fileTreeVisitor.recursiveVisitFiles(start, directories::add) {}
}
for (directory in directories) {
this.directories.add(directory)
register(directory, false, context)
}
}
}
private suspend fun addKnownDirectories(start: Path) {
runInterruptible {
fileTreeVisitor.recursiveVisitFiles(start, directories::add) {}
}
}
private suspend fun register(directory: Path, useFileTreeModifier: Boolean, context: Path?) {
LOG.debug { "Registering [$directory]." }
val modifiers = if (useFileTreeModifier) arrayOf(ExtendedWatchEventModifier.FILE_TREE) else emptyArray()
val watchKey = runInterruptible {
val watchable = watchablePathFactory(directory)
watchable.register(watchService, arrayOf(ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY), *modifiers)
}
keyRoots[watchKey] = directory
if (context != null) {
registeredPathToRootPath[directory] = context
}
}
private suspend fun notifyCreateEvent(isDirectory: Boolean, path: Path, count: Int, rootPath: Path?) {
if (isDirectory) {
directories.add(path)
}
onEvent(EventType.CREATE, isDirectory, path, count, rootPath)
}
private fun isFileTreeSupported(): Boolean = fileTreeSupported ?: error("fileTreeSupported not initialized")
}
private fun createDefaultWatchService(): WatchService {
return if (System.getProperty("os.name").contains("mac", ignoreCase = true)) {
MacOSXListeningWatchService(object : MacOSXListeningWatchService.Config {
})
}
else {
FileSystems.getDefault().newWatchService()
}
}
@@ -1,28 +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.platform.ai.agent.filewatch.impl
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes
internal interface FileTreeVisitor {
fun recursiveVisitFiles(root: Path, directoryConsumer: (Path) -> Unit, fileConsumer: (Path) -> Unit)
}
internal object DefaultFileTreeVisitor : FileTreeVisitor {
override fun recursiveVisitFiles(root: Path, directoryConsumer: (Path) -> Unit, fileConsumer: (Path) -> Unit) {
Files.walkFileTree(root, object : SimpleFileVisitor<Path>() {
override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {
directoryConsumer(dir)
return FileVisitResult.CONTINUE
}
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
fileConsumer(file)
return FileVisitResult.CONTINUE
}
})
}
}
@@ -1,18 +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.platform.ai.agent.filewatch.impl
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.WatchEvent
@Suppress("UNCHECKED_CAST")
internal fun WatchEvent<*>.pathContext(): Path? = (this as WatchEvent<Path>).context()
internal fun recursiveListFiles(fileTreeVisitor: FileTreeVisitor, root: Path): Set<Path> {
val files = HashSet<Path>()
when {
Files.isDirectory(root) -> fileTreeVisitor.recursiveVisitFiles(root, files::add, files::add)
Files.exists(root) -> files.add(root)
}
return files
}
@@ -1,177 +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.platform.ai.agent.filewatch.impl.watchservice
import java.nio.file.ClosedWatchServiceException
import java.nio.file.Path
import java.nio.file.ProviderMismatchException
import java.nio.file.StandardWatchEventKinds.OVERFLOW
import java.nio.file.WatchEvent
import java.nio.file.WatchKey
import java.nio.file.WatchService
import java.nio.file.Watchable
import java.util.Collections
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
internal abstract class AbstractWatchService : WatchService {
private val queue = LinkedBlockingQueue<WatchKey>()
private val poison = AbstractWatchKey(this, null, emptySet(), 1)
private val open = AtomicBoolean(true)
abstract fun register(watchable: WatchablePath, eventTypes: Iterable<WatchEvent.Kind<*>>): AbstractWatchKey
val isOpen: Boolean
get() = open.get()
internal fun enqueue(key: AbstractWatchKey) {
if (isOpen) {
queue.add(key)
}
}
open fun cancelled(key: AbstractWatchKey) {
}
override fun poll(): WatchKey? {
checkOpen()
return check(queue.poll())
}
override fun poll(timeout: Long, unit: TimeUnit): WatchKey? {
checkOpen()
return check(queue.poll(timeout, unit))
}
override fun take(): WatchKey {
checkOpen()
return check(queue.take()) ?: error("queue.take() returned null")
}
private fun check(key: WatchKey?): WatchKey? {
if (key === poison) {
queue.offer(poison)
throw ClosedWatchServiceException()
}
return key
}
protected fun checkOpen() {
if (!open.get()) {
throw ClosedWatchServiceException()
}
}
override fun close() {
if (open.compareAndSet(true, false)) {
queue.clear()
queue.offer(poison)
}
}
internal data class Event<T>(
private val kind: WatchEvent.Kind<T>,
private val count: Int,
private val context: T?,
) : WatchEvent<T> {
init {
require(count >= 0) { "count ($count) must be non-negative" }
}
override fun kind(): WatchEvent.Kind<T> = kind
override fun count(): Int = count
override fun context(): T? = context
}
}
internal open class AbstractWatchKey(
private val watcher: AbstractWatchService,
private val watchable: Watchable?,
@Suppress("unused") subscribedTypes: Iterable<WatchEvent.Kind<*>>,
queueSize: Int,
) : WatchKey {
private val state = AtomicReference(State.READY)
private val valid = AtomicBoolean(true)
private val overflow = AtomicInteger()
private val events = ArrayBlockingQueue<WatchEvent<*>>(queueSize)
fun post(event: WatchEvent<*>) {
if (!events.offer(event)) {
overflow.incrementAndGet()
}
}
fun signal() {
if (state.getAndSet(State.SIGNALLED) == State.READY) {
watcher.enqueue(this)
}
}
override fun isValid(): Boolean = watcher.isOpen && valid.get()
override fun pollEvents(): List<WatchEvent<*>> {
val result = ArrayList<WatchEvent<*>>(events.size)
events.drainTo(result)
val overflowCount = overflow.getAndSet(0)
if (overflowCount != 0) {
result.add(AbstractWatchService.Event(OVERFLOW, overflowCount, null))
}
return Collections.unmodifiableList(result)
}
override fun reset(): Boolean {
if (isValid && state.compareAndSet(State.SIGNALLED, State.READY) && hasPendingEvents()) {
signal()
}
return isValid
}
private fun hasPendingEvents(): Boolean = !events.isEmpty() || overflow.get() != 0
override fun cancel() {
valid.set(false)
watcher.cancelled(this)
}
override fun watchable(): Watchable? = watchable
fun signalEvent(kind: WatchEvent.Kind<Path>, context: Path) {
post(AbstractWatchService.Event(kind, 1, context))
signal()
}
fun signalOverflow(context: Path?) {
post(AbstractWatchService.Event(OVERFLOW, 1, context))
signal()
}
override fun toString(): String = "AbstractWatchKey{watchable=$watchable, valid=${valid.get()}}"
private enum class State {
READY,
SIGNALLED,
}
}
internal class WatchablePath(val file: Path) : Watchable {
override fun register(watcher: WatchService, events: Array<out WatchEvent.Kind<*>>, vararg modifiers: WatchEvent.Modifier): WatchKey {
if (watcher !is AbstractWatchService) {
throw ProviderMismatchException()
}
return watcher.register(this, events.asList())
}
override fun register(watcher: WatchService, vararg events: WatchEvent.Kind<*>): WatchKey {
if (!java.nio.file.Files.exists(file)) {
throw RuntimeException("Directory to watch doesn't exist: $file")
}
return register(watcher, events)
}
override fun toString(): String = "Path{file=$file}"
}
@@ -1,364 +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.platform.ai.agent.filewatch.impl.watchservice
import com.intellij.platform.ai.agent.filewatch.impl.DefaultFileTreeVisitor
import com.intellij.platform.ai.agent.filewatch.impl.FileTreeVisitor
import com.intellij.platform.ai.agent.filewatch.impl.recursiveListFiles
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.CFArrayRef
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.FSEventStreamRef
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.K_FSEVENT_STREAM_CREATE_FLAG_FILE_EVENTS
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.K_FSEVENT_STREAM_CREATE_FLAG_NO_DEFER
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.MacFSEventsApi
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.cfIndex
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.libDispatchApi
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.macFSEventsApi
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.jna.toCFString
import com.sun.jna.NativeLong
import com.sun.jna.Pointer
import java.nio.file.Files
import java.nio.file.NoSuchFileException
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE
import java.nio.file.StandardWatchEventKinds.ENTRY_DELETE
import java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY
import java.nio.file.WatchEvent
internal class MacOSXListeningWatchService(config: Config) : AbstractWatchService() {
interface Config {
val latency: Double
get() = DEFAULT_LATENCY
val queueSize: Int
get() = DEFAULT_QUEUE_SIZE
val fileTreeVisitor: FileTreeVisitor
get() = DefaultFileTreeVisitor
}
private val callbacks = ArrayList<MacOSXListeningCallback>()
private val streams = ArrayList<FSEventStream>()
private val pathsWatching = HashSet<Path>()
private val latency = config.latency
private val queueSize = config.queueSize
private val fileTreeVisitor = config.fileTreeVisitor
@Synchronized
override fun register(watchable: WatchablePath, eventTypes: Iterable<WatchEvent.Kind<*>>): AbstractWatchKey {
checkOpen()
val watchKey = MacOSXWatchKey(this, watchable, eventTypes, queueSize)
val file = watchable.file.toAbsolutePath()
val pathToWatch = file.toString().toCFString()
val values = arrayOf(pathToWatch.pointer)
val pathsToWatch: CFArrayRef = macFSEventsApi.CFArrayCreate(null, values, cfIndex(1), null)
val callback = MacOSXListeningCallback(watchKey, fileTreeVisitor, file)
try {
val streamRef = macFSEventsApi.FSEventStreamCreate(
Pointer.NULL,
callback,
Pointer.NULL,
pathsToWatch,
-1L,
latency,
K_FSEVENT_STREAM_CREATE_FLAG_NO_DEFER or K_FSEVENT_STREAM_CREATE_FLAG_FILE_EVENTS,
)
val stream = FSEventStream(streamRef, file)
callback.onClose { close(stream, callback, file) }
stream.start()
callbacks.add(callback)
streams.add(stream)
pathsWatching.add(file)
return watchKey
}
catch (e: Exception) {
watchKey.cancel()
throw e
}
finally {
macFSEventsApi.CFRelease(pathsToWatch)
macFSEventsApi.CFRelease(pathToWatch)
}
}
@Synchronized
override fun close() {
super.close()
streams.forEach(FSEventStream::close)
streams.clear()
callbacks.clear()
pathsWatching.clear()
}
@Synchronized
private fun close(stream: FSEventStream, callback: MacOSXListeningCallback, path: Path) {
streams.remove(stream)
callbacks.remove(callback)
pathsWatching.remove(path)
stream.close()
}
internal class FSEventStream(
private val streamRef: FSEventStreamRef,
private val file: Path,
) : AutoCloseable {
private val dispatchQueue = libDispatchApi.dispatch_queue_create("WatchService for $file", null)
private var closed = false
fun start() {
synchronized(streamRef) {
if (closed) {
return
}
macFSEventsApi.FSEventStreamSetDispatchQueue(streamRef, dispatchQueue)
if (!macFSEventsApi.FSEventStreamStart(streamRef)) {
close()
throw IllegalStateException("Could not start FSEventStream for $file")
}
}
}
override fun close() {
synchronized(streamRef) {
if (closed) {
return
}
macFSEventsApi.FSEventStreamStop(streamRef)
macFSEventsApi.FSEventStreamInvalidate(streamRef)
macFSEventsApi.FSEventStreamRelease(streamRef)
libDispatchApi.dispatch_release(dispatchQueue)
closed = true
}
}
}
}
private class MacOSXListeningCallback(
private val watchKey: MacOSXWatchKey,
private val fileTreeVisitor: FileTreeVisitor,
private val absPath: Path,
) : MacFSEventsApi.FSEventStreamCallback {
private val realPathSize: Int
private val knownFiles: MutableSet<Path>
private var onCloseCallback: (() -> Unit)? = null
init {
try {
val realPath = absPath.toRealPath()
knownFiles = HashSet(recursiveListFiles(fileTreeVisitor, absPath))
realPathSize = realPath.toString().length + 1
}
catch (e: Exception) {
throw IllegalStateException("Could not initialize watcher for $absPath", e)
}
}
fun onClose(callback: () -> Unit) {
onCloseCallback = callback
}
override fun invoke(
streamRef: FSEventStreamRef,
clientCallBackInfo: Pointer?,
numEvents: NativeLong,
eventPaths: Pointer,
eventFlags: Pointer?,
eventIds: Pointer?,
) {
val length = numEvents.toInt()
val pathNames = eventPaths.getStringArray(0, length)
for (index in pathNames.indices) {
if (!watchKey.isValid) {
return
}
val path = toWatchedPath(pathNames[index])
val flags = FSEventFlags(eventFlags?.getInt((index * Int.SIZE_BYTES).toLong()) ?: 0)
when {
flags.requiresRescan -> rescanAfterOverflow(path)
flags.removed -> handleRemoved(path)
flags.created -> handleCreated(path, flags)
flags.renamed -> handleRenamed(path, flags)
flags.modified -> handleModified(path)
path in knownFiles && Files.exists(path) -> handleModified(path)
else -> rescanPath(path)
}
}
}
private fun toWatchedPath(fileName: String): Path {
return if (fileName.length + 1 != realPathSize) absPath.resolve(fileName.substring(realPathSize)) else absPath
}
private fun handleCreated(path: Path, flags: FSEventFlags) {
val wasKnown = !knownFiles.add(path)
if (wasKnown) {
handleModified(path)
}
else if (watchKey.reportCreateEvents) {
watchKey.signalEvent(ENTRY_CREATE, path)
}
if (flags.directory) {
rescanPath(path)
}
}
private fun handleRemoved(path: Path) {
val deletedPaths = removeKnownSubtree(path)
if (deletedPaths.isEmpty()) {
return
}
for (deletedPath in deletedPaths) {
if (watchKey.reportDeleteEvents) {
watchKey.signalEvent(ENTRY_DELETE, deletedPath)
}
}
closeIfRootRemoved()
}
private fun handleRenamed(path: Path, flags: FSEventFlags) {
if (Files.exists(path)) {
if (path !in knownFiles) {
handleCreated(path, flags)
}
else {
handleModified(path)
}
}
else {
handleRemoved(path)
}
}
private fun handleModified(path: Path) {
if (path in knownFiles && !Files.isDirectory(path) && watchKey.reportModifyEvents) {
watchKey.signalEvent(ENTRY_MODIFY, path)
}
}
private fun rescanAfterOverflow(path: Path) {
watchKey.signalOverflow(path)
rescanPath(absPath)
}
private fun rescanPath(path: Path) {
val previouslyKnownFiles = HashSet(knownFiles)
val filesOnDisk = try {
recursiveListFiles(fileTreeVisitor, path)
}
catch (_: NoSuchFileException) {
handleRemoved(path)
return
}
catch (_: Exception) {
watchKey.signalOverflow(path)
return
}
for (file in findCreatedFiles(filesOnDisk)) {
if (watchKey.reportCreateEvents) {
watchKey.signalEvent(ENTRY_CREATE, file)
}
}
for (file in findModifiedFiles(filesOnDisk, previouslyKnownFiles)) {
if (watchKey.reportModifyEvents) {
watchKey.signalEvent(ENTRY_MODIFY, file)
}
}
for (file in findDeletedFiles(path, filesOnDisk)) {
if (watchKey.reportDeleteEvents) {
watchKey.signalEvent(ENTRY_DELETE, file)
}
}
closeIfRootRemoved()
}
private fun removeKnownSubtree(path: Path): List<Path> {
val deletedPaths = ArrayList<Path>()
for (file in ArrayList(knownFiles)) {
if (file.startsWith(path)) {
deletedPaths.add(file)
knownFiles.remove(file)
}
}
return deletedPaths
}
private fun closeIfRootRemoved() {
if (knownFiles.isEmpty()) {
watchKey.cancel()
onCloseCallback?.invoke()
}
}
private fun findCreatedFiles(filesOnDisk: Set<Path>): List<Path> {
val createdFiles = ArrayList<Path>()
for (file in filesOnDisk) {
if (knownFiles.add(file)) {
createdFiles.add(file)
}
}
return createdFiles
}
private fun findModifiedFiles(filesOnDisk: Set<Path>, previouslyKnownFiles: Set<Path>): List<Path> {
val modifiedFiles = ArrayList<Path>()
for (file in filesOnDisk) {
if (file in previouslyKnownFiles && file in knownFiles && !Files.isDirectory(file)) {
modifiedFiles.add(file)
}
}
return modifiedFiles
}
private fun findDeletedFiles(path: Path, filesOnDisk: Set<Path>): List<Path> {
val deletedFiles = ArrayList<Path>()
for (file in ArrayList(knownFiles)) {
if (file.startsWith(path) && file !in filesOnDisk) {
deletedFiles.add(file)
knownFiles.remove(file)
}
}
return deletedFiles
}
}
private class FSEventFlags(private val value: Int) {
val created: Boolean = has(K_FSEVENT_STREAM_EVENT_FLAG_ITEM_CREATED)
val removed: Boolean = has(K_FSEVENT_STREAM_EVENT_FLAG_ITEM_REMOVED)
val renamed: Boolean = has(K_FSEVENT_STREAM_EVENT_FLAG_ITEM_RENAMED)
val directory: Boolean = has(K_FSEVENT_STREAM_EVENT_FLAG_ITEM_IS_DIR)
val modified: Boolean = has(K_FSEVENT_STREAM_EVENT_FLAG_ITEM_MODIFIED) ||
has(K_FSEVENT_STREAM_EVENT_FLAG_ITEM_INODE_META_MOD) ||
has(K_FSEVENT_STREAM_EVENT_FLAG_ITEM_FINDER_INFO_MOD) ||
has(K_FSEVENT_STREAM_EVENT_FLAG_ITEM_CHANGE_OWNER) ||
has(K_FSEVENT_STREAM_EVENT_FLAG_ITEM_XATTR_MOD)
val requiresRescan: Boolean = has(K_FSEVENT_STREAM_EVENT_FLAG_MUST_SCAN_SUB_DIRS) ||
has(K_FSEVENT_STREAM_EVENT_FLAG_USER_DROPPED) ||
has(K_FSEVENT_STREAM_EVENT_FLAG_KERNEL_DROPPED) ||
has(K_FSEVENT_STREAM_EVENT_FLAG_ROOT_CHANGED)
private fun has(flag: Int): Boolean = value and flag != 0
}
private const val K_FSEVENT_STREAM_EVENT_FLAG_MUST_SCAN_SUB_DIRS: Int = 0x00000001
private const val K_FSEVENT_STREAM_EVENT_FLAG_USER_DROPPED: Int = 0x00000002
private const val K_FSEVENT_STREAM_EVENT_FLAG_KERNEL_DROPPED: Int = 0x00000004
private const val K_FSEVENT_STREAM_EVENT_FLAG_ROOT_CHANGED: Int = 0x00000020
private const val K_FSEVENT_STREAM_EVENT_FLAG_ITEM_CREATED: Int = 0x00000100
private const val K_FSEVENT_STREAM_EVENT_FLAG_ITEM_REMOVED: Int = 0x00000200
private const val K_FSEVENT_STREAM_EVENT_FLAG_ITEM_INODE_META_MOD: Int = 0x00000400
private const val K_FSEVENT_STREAM_EVENT_FLAG_ITEM_RENAMED: Int = 0x00000800
private const val K_FSEVENT_STREAM_EVENT_FLAG_ITEM_MODIFIED: Int = 0x00001000
private const val K_FSEVENT_STREAM_EVENT_FLAG_ITEM_FINDER_INFO_MOD: Int = 0x00002000
private const val K_FSEVENT_STREAM_EVENT_FLAG_ITEM_CHANGE_OWNER: Int = 0x00004000
private const val K_FSEVENT_STREAM_EVENT_FLAG_ITEM_XATTR_MOD: Int = 0x00008000
private const val K_FSEVENT_STREAM_EVENT_FLAG_ITEM_IS_DIR: Int = 0x00020000
private class MacOSXWatchKey(
macOSXWatchService: AbstractWatchService,
watchable: WatchablePath,
events: Iterable<WatchEvent.Kind<*>>,
queueSize: Int,
) : AbstractWatchKey(macOSXWatchService, watchable, events, queueSize) {
val reportCreateEvents = events.any { it === ENTRY_CREATE }
val reportModifyEvents = events.any { it === ENTRY_MODIFY }
val reportDeleteEvents = events.any { it === ENTRY_DELETE }
}
private const val DEFAULT_LATENCY = 0.5
private const val DEFAULT_QUEUE_SIZE = 1024
@@ -1,90 +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.platform.ai.agent.filewatch.impl.watchservice.jna
import com.sun.jna.Callback
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.NativeLong
import com.sun.jna.Pointer
import com.sun.jna.ptr.PointerByReference
@Suppress("FunctionName")
internal interface MacFSEventsApi : Library {
interface FSEventStreamCallback : Callback {
fun invoke(
streamRef: FSEventStreamRef,
clientCallBackInfo: Pointer?,
numEvents: NativeLong,
eventPaths: Pointer,
eventFlags: Pointer?,
eventIds: Pointer?,
)
}
fun CFArrayCreate(allocator: CFAllocatorRef?, values: Array<Pointer>, numValues: CFIndex, callbacks: Void?): CFArrayRef
fun CFStringCreateWithCharacters(alloc: Void?, chars: CharArray, numChars: CFIndex): CFStringRef
fun FSEventStreamCreate(
allocator: Pointer?,
callback: FSEventStreamCallback,
context: Pointer?,
pathsToWatch: CFArrayRef,
sinceWhen: Long,
latency: Double,
flags: Int,
): FSEventStreamRef
fun FSEventStreamSetDispatchQueue(streamRef: FSEventStreamRef, dispatchQueue: DispatchQueueRef)
fun FSEventStreamStart(streamRef: FSEventStreamRef): Boolean
fun FSEventStreamStop(streamRef: FSEventStreamRef)
fun FSEventStreamInvalidate(streamRef: FSEventStreamRef)
fun FSEventStreamRelease(streamRef: FSEventStreamRef)
fun CFRelease(ref: PointerByReference)
}
internal val macFSEventsApi: MacFSEventsApi = Native.load("CoreServices", MacFSEventsApi::class.java)
internal const val K_FSEVENT_STREAM_CREATE_FLAG_NO_DEFER: Int = 0x00000002
internal const val K_FSEVENT_STREAM_CREATE_FLAG_FILE_EVENTS: Int = 0x00000010
@Suppress("FunctionName")
internal interface LibDispatchApi : Library {
fun dispatch_queue_create(label: String, attr: Pointer?): DispatchQueueRef
fun dispatch_release(dispatchObject: DispatchQueueRef)
}
internal val libDispatchApi: LibDispatchApi = Native.load("System", LibDispatchApi::class.java)
internal class CFAllocatorRef : PointerByReference()
internal class CFArrayRef : PointerByReference()
internal class FSEventStreamRef : PointerByReference()
internal class DispatchQueueRef : PointerByReference()
internal class CFIndex() : NativeLong() {
constructor(value: Long) : this() {
setValue(value)
}
override fun toByte(): Byte = toLong().toByte()
override fun toShort(): Short = toLong().toShort()
}
internal fun cfIndex(value: Int): CFIndex = CFIndex(value.toLong())
internal class CFStringRef : PointerByReference()
internal fun String.toCFString(): CFStringRef {
val chars = toCharArray()
return macFSEventsApi.CFStringCreateWithCharacters(null, chars, cfIndex(chars.size))
}
@@ -1,62 +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.platform.ai.agent.filewatch.impl.watchservice.jna
import com.sun.jna.LastErrorException
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.NativeLong
import com.sun.jna.Pointer
import com.sun.jna.Structure
internal interface MacKQueueApi : Library {
@Throws(LastErrorException::class)
fun kqueue(): Int
@Throws(LastErrorException::class)
fun kevent(
kq: Int,
changelist: KEvent?,
nchanges: Int,
eventlist: KEvent?,
nevents: Int,
timeout: Pointer?,
): Int
@Throws(LastErrorException::class)
fun open(path: String, flags: Int): Int
@Throws(LastErrorException::class)
fun close(fd: Int): Int
}
@Structure.FieldOrder("ident", "filter", "flags", "fflags", "data", "udata")
@Suppress("unused")
internal class KEvent() : Structure() {
@JvmField var ident: NativeLong = NativeLong(0)
@JvmField var filter: Short = 0
@JvmField var flags: Short = 0
@JvmField var fflags: Int = 0
@JvmField var data: NativeLong = NativeLong(0)
@JvmField var udata: Pointer? = null
constructor(ident: Int, filter: Short, flags: Short, fflags: Int) : this() {
this.ident = NativeLong(ident.toLong())
this.filter = filter
this.flags = flags
this.fflags = fflags
}
}
internal val macKQueueApi: MacKQueueApi = Native.load("System", MacKQueueApi::class.java)
internal const val K_QUEUE_OPEN_EVENT_ONLY: Int = 0x8000
internal const val K_EVENT_FILTER_VNODE: Short = -4
internal const val K_EVENT_ADD: Short = 0x0001
internal const val K_EVENT_CLEAR: Short = 0x0020
internal const val K_NOTE_DELETE: Int = 0x00000001
internal const val K_NOTE_WRITE: Int = 0x00000002
internal const val K_NOTE_EXTEND: Int = 0x00000004
internal const val K_NOTE_RENAME: Int = 0x00000020
internal const val K_NOTE_REVOKE: Int = 0x00000040
@@ -1,267 +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.platform.ai.agent.filewatch
import com.intellij.platform.ai.agent.filewatch.impl.DirectoryChangeEvent
import com.intellij.platform.ai.agent.filewatch.impl.DirectoryChangeListener
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import java.util.concurrent.TimeUnit
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path
import java.util.concurrent.LinkedBlockingQueue
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@Timeout(value = 2, unit = TimeUnit.MINUTES)
class AgentWorkbenchDirectoryWatcherTest {
@TempDir
lateinit var tempDir: Path
@Test
fun pathlessOverflowIsDelivered() = runBlocking(Dispatchers.Default) {
val watchLoop = FakeWatchLoop()
val events = LinkedBlockingQueue<AgentWorkbenchWatchEvent>()
val failures = LinkedBlockingQueue<Throwable>()
AgentWorkbenchDirectoryWatcher(
roots = listOf(tempDir),
scope = this,
onWatchEvent = events::add,
onFailure = failures::add,
watchLoopFactory = { _, listener ->
watchLoop.listener = listener
watchLoop
},
).use { watcher ->
watchLoop.offer(DirectoryChangeEvent(DirectoryChangeEvent.EventType.OVERFLOW, false, null, 1, tempDir.toAbsolutePath()))
val event = withTimeout(5.seconds) {
events.take()
}
assertThat(event.eventType).isEqualTo(AgentWorkbenchWatchEventType.OVERFLOW)
assertThat(event.path).isNull()
assertThat(event.rootPath).isEqualTo(tempDir.toAbsolutePath())
assertThat(event.count).isEqualTo(1)
assertThat(failures).isEmpty()
watcher.close()
}
}
@Test
fun rootPathIsPreservedForDeliveredEvents() {
runBlocking(Dispatchers.Default) {
val watchLoop = FakeWatchLoop()
val events = LinkedBlockingQueue<AgentWorkbenchWatchEvent>()
val root = tempDir.toAbsolutePath()
val changedPath = root.resolve("session.jsonl")
AgentWorkbenchDirectoryWatcher(
roots = listOf(tempDir),
scope = this,
onWatchEvent = events::add,
watchLoopFactory = { _, listener ->
watchLoop.listener = listener
watchLoop
},
).use {
watchLoop.offer(DirectoryChangeEvent(DirectoryChangeEvent.EventType.CREATE, false, changedPath, 1, root))
val event = withTimeout(5.seconds) {
events.take()
}
assertThat(event.eventType).isEqualTo(AgentWorkbenchWatchEventType.CREATE)
assertThat(event.path).isEqualTo(changedPath)
assertThat(event.rootPath).isEqualTo(root)
}
}
}
@Test
fun closeSuppressesLateEventsAndFailures() = runBlocking(Dispatchers.Default) {
val watchLoop = FakeWatchLoop()
val events = LinkedBlockingQueue<AgentWorkbenchWatchEvent>()
val failures = LinkedBlockingQueue<Throwable>()
val root = tempDir.toAbsolutePath()
AgentWorkbenchDirectoryWatcher(
roots = listOf(tempDir),
scope = this,
onWatchEvent = events::add,
onFailure = failures::add,
watchLoopFactory = { _, listener ->
watchLoop.listener = listener
watchLoop
},
).use { watcher ->
watcher.close()
watchLoop.offer(DirectoryChangeEvent(DirectoryChangeEvent.EventType.CREATE, false, root.resolve("late.jsonl"), 1, root))
watchLoop.fail(IllegalStateException("late failure"))
delay(200.milliseconds)
assertThat(events).isEmpty()
assertThat(failures).isEmpty()
}
}
@Test
fun watcherWithNoValidRootsIsInactive() {
runBlocking(Dispatchers.Default) {
val watcher = AgentWorkbenchDirectoryWatcher(
roots = listOf(tempDir.resolve("missing")),
scope = this,
onWatchEvent = {},
)
assertThat(watcher.isActive).isFalse()
}
}
@Test
fun closeMakesWatcherInactive() {
runBlocking(Dispatchers.Default) {
val watchLoop = FakeWatchLoop()
val watcher = AgentWorkbenchDirectoryWatcher(
roots = listOf(tempDir),
scope = this,
onWatchEvent = {},
watchLoopFactory = { _, listener ->
watchLoop.listener = listener
watchLoop
},
)
assertThat(watcher.isActive).isTrue()
watcher.closeAndJoin()
assertThat(watcher.isActive).isFalse()
}
}
@Test
fun completedWatchLoopMakesWatcherInactive() {
runBlocking(Dispatchers.Default) {
val watchLoop = CompletingWatchLoop()
val watcher = AgentWorkbenchDirectoryWatcher(
roots = listOf(tempDir),
scope = this,
onWatchEvent = {},
watchLoopFactory = { _, _ -> watchLoop },
)
withTimeout(5.seconds) {
watchLoop.completed.await()
}
withTimeout(5.seconds) {
while (watcher.isActive) {
delay(10.milliseconds)
}
}
assertThat(watcher.isActive).isFalse()
}
}
@Test
fun failedWatchLoopRestartsWithSameRoots() {
runBlocking(Dispatchers.Default) {
val events = LinkedBlockingQueue<AgentWorkbenchWatchEvent>()
val failures = LinkedBlockingQueue<Throwable>()
val restartedLoop = CompletableDeferred<FakeWatchLoop>()
val root = tempDir.toAbsolutePath()
var createCount = 0
AgentWorkbenchDirectoryWatcher(
roots = listOf(tempDir),
scope = this,
onWatchEvent = events::add,
onFailure = failures::add,
watchLoopFactory = { _, listener ->
createCount += 1
if (createCount == 1) {
FailingWatchLoop(IllegalStateException("watch loop failed"))
}
else {
FakeWatchLoop().also { loop ->
loop.listener = listener
restartedLoop.complete(loop)
}
}
},
).use { watcher ->
val failure = withTimeout(5.seconds) { failures.take() }
val loop = withTimeout(5.seconds) { restartedLoop.await() }
loop.offer(DirectoryChangeEvent(DirectoryChangeEvent.EventType.CREATE, false, root.resolve("session.jsonl"), 1, root))
val event = withTimeout(5.seconds) { events.take() }
assertThat(failure).hasMessage("watch loop failed")
assertThat(event.path).isEqualTo(root.resolve("session.jsonl"))
assertThat(watcher.isActive).isTrue()
}
}
}
}
private class CompletingWatchLoop : AgentWorkbenchWatchLoop {
val completed = CompletableDeferred<Unit>()
override suspend fun watch() {
completed.complete(Unit)
}
override fun close() {
}
}
private class FailingWatchLoop(private val error: Throwable) : AgentWorkbenchWatchLoop {
override suspend fun watch() {
throw error
}
override fun close() {
}
}
private class FakeWatchLoop : AgentWorkbenchWatchLoop {
private val events = LinkedBlockingQueue<Any>()
lateinit var listener: DirectoryChangeListener
@Volatile
private var closed = false
private object Stop
fun offer(event: DirectoryChangeEvent) {
events.add(event)
}
fun fail(e: Exception) {
events.add(e)
}
override suspend fun watch() {
while (!closed) {
when (val event = events.take()) {
Stop -> return
is DirectoryChangeEvent -> listener.onEvent(event)
is Exception -> listener.onException(event)
}
}
}
override fun close() {
closed = true
events.offer(Stop)
}
}
@@ -1,424 +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.platform.ai.agent.filewatch.impl
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.AbstractWatchKey
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.AbstractWatchService
import com.intellij.platform.ai.agent.filewatch.impl.watchservice.WatchablePath
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE
import java.nio.file.StandardWatchEventKinds.ENTRY_DELETE
import java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY
import java.nio.file.StandardWatchEventKinds.OVERFLOW
import java.nio.file.WatchEvent
import java.nio.file.WatchEvent.Modifier
import java.nio.file.WatchKey
import java.nio.file.WatchService
import java.nio.file.Watchable
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@Timeout(value = 2, unit = TimeUnit.MINUTES)
class DirectoryWatcherImplTest {
@TempDir
lateinit var tempDir: Path
@Test
fun watchThrowsWhenWatcherIsAlreadyClosed() {
runBlocking(Dispatchers.IO) {
val watcher = DirectoryWatcher(listOf(tempDir), EventRecorder())
watcher.close()
assertThatThrownBy { runBlocking { watcher.watch() } }
.isInstanceOf(IllegalStateException::class.java)
}
}
@Test
fun watchReturnsWhenWatcherIsClosed() = runBlocking(Dispatchers.IO) {
val watcher = DirectoryWatcher(listOf(tempDir), EventRecorder())
val completed = CompletableDeferred<Unit>()
val watchJob = launch {
watcher.watch()
completed.complete(Unit)
}
try {
delay(100.milliseconds)
watcher.close()
withTimeout(5.seconds) {
completed.await()
}
}
finally {
watchJob.cancelAndJoin()
}
}
@Test
fun deletingNestedFilesEmitsDeleteEvents() {
runBlocking(Dispatchers.IO) {
val parentDir = Files.createDirectory(tempDir.resolve("parent"))
val childDir = Files.createDirectory(parentDir.resolve("child"))
val firstFile = Files.createFile(parentDir.resolve("first.jsonl"))
val secondFile = Files.createFile(childDir.resolve("second.jsonl"))
val recorder = EventRecorder()
val watcher = DirectoryWatcher(listOf(tempDir), recorder)
val watchJob = launch { watcher.watch() }
try {
delay(100.milliseconds)
Files.delete(secondFile)
Files.delete(firstFile)
recorder.awaitEvent { event -> event.eventType == DirectoryChangeEvent.EventType.DELETE && event.path == secondFile }
recorder.awaitEvent { event -> event.eventType == DirectoryChangeEvent.EventType.DELETE && event.path == firstFile }
}
finally {
watcher.close()
watchJob.cancelAndJoin()
}
}
}
@Test
fun repeatedStartAndCloseDoesNotCrash() {
repeat(3) {
val watcher = DirectoryWatcher(listOf(tempDir), EventRecorder())
watcher.close()
}
}
@Test
fun invalidatingOnlyWatchKeyFailsWatch() {
runBlocking(Dispatchers.IO) {
val watchService = TestWatchService(queueSize = 8)
val watcher = DirectoryWatcher(
listOf(tempDir),
EventRecorder(),
watchService,
watchablePathFactory = { path -> WatchablePath(path) },
)
val failure = CompletableDeferred<Throwable>()
val watchJob = launch {
try {
watcher.watch()
}
catch (t: Throwable) {
failure.complete(t)
}
}
try {
val key = withTimeout(5.seconds) {
watchService.awaitRegisteredKey()
}
key.cancel()
key.signalOverflow(tempDir)
val error = withTimeout(5.seconds) {
failure.await()
}
assertThat(error)
.isInstanceOf(IllegalStateException::class.java)
.hasMessage("No more directories left to watch")
}
finally {
watcher.close()
watchJob.cancelAndJoin()
}
}
}
@Test
fun unsupportedFileTreeModifierFallbackWalksTreeOnce() {
runBlocking(Dispatchers.IO) {
val childDir = tempDir.resolve("child")
val nestedDir = childDir.resolve("nested")
val fileTreeVisitor = FakeFileTreeVisitor(listOf(tempDir, childDir, nestedDir))
val watchService = FileTreeUnsupportedWatchService()
val watcher = DirectoryWatcher(
listOf(tempDir),
EventRecorder(),
watchService,
fileTreeVisitor,
watchablePathFactory = { path -> FakeWatchablePath(path, watchService) },
)
val completed = CompletableDeferred<Unit>()
val watchJob = launch {
watcher.watch()
completed.complete(Unit)
}
try {
withTimeout(5.seconds) {
watchService.awaitRegisteredDirectories(3)
}
assertThat(fileTreeVisitor.visitCount.get()).isEqualTo(1)
assertThat(watchService.registeredDirectories).containsExactly(tempDir, childDir, nestedDir)
}
finally {
watcher.close()
watchJob.cancelAndJoin()
}
}
}
@Test
fun creatingDirectoryDoesNotReportNewFilesAsModified() {
runBlocking(Dispatchers.IO) {
val createdDirectory = tempDir.resolve("created")
val createdFile = createdDirectory.resolve("session.jsonl")
val fileTreeVisitor = MutableFakeFileTreeVisitor(listOf(tempDir), emptyList())
val watchService = FileTreeUnsupportedWatchService()
val recorder = EventRecorder()
val watcher = DirectoryWatcher(
listOf(tempDir),
recorder,
watchService,
fileTreeVisitor,
watchablePathFactory = { path -> FakeWatchablePath(path, watchService) },
)
val watchJob = launch { watcher.watch() }
try {
withTimeout(5.seconds) {
watchService.awaitRegisteredDirectories(1)
}
Files.createDirectory(createdDirectory)
Files.writeString(createdFile, "created")
fileTreeVisitor.directories = listOf(createdDirectory)
fileTreeVisitor.files = listOf(createdFile)
watchService.signal(tempDir, FakeWatchEvent(ENTRY_CREATE, 1, createdDirectory.fileName))
recorder.awaitEvent { event -> event.eventType == DirectoryChangeEvent.EventType.CREATE && event.path == createdDirectory }
recorder.awaitEvent { event -> event.eventType == DirectoryChangeEvent.EventType.CREATE && event.path == createdFile }
assertThat(recorder.drainEvents().any { event ->
event.eventType == DirectoryChangeEvent.EventType.MODIFY && event.path == createdFile
}).isFalse()
}
finally {
watcher.close()
watchJob.cancelAndJoin()
}
}
}
@Test
fun overflowOnlyEventsResignalWatchKeyOnReset() {
val watchService = TestWatchService(queueSize = 1)
val key = watchService.register(WatchablePath(tempDir), listOf(ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY))
key.signalEvent(ENTRY_CREATE, tempDir.resolve("file"))
val firstEvents = key.pollEvents()
assertThat(firstEvents.map { it.kind() }).containsExactly(ENTRY_CREATE)
key.signalOverflow(tempDir)
assertThat(key.reset()).isTrue()
val secondEvents = watchService.poll()!!.pollEvents()
assertThat(secondEvents.map { it.kind() }).containsExactly(OVERFLOW)
}
}
private class TestWatchService(private val queueSize: Int) : AbstractWatchService() {
@Volatile
private var registeredKey: AbstractWatchKey? = null
override fun register(watchable: WatchablePath, eventTypes: Iterable<WatchEvent.Kind<*>>): AbstractWatchKey {
return AbstractWatchKey(this, watchable, eventTypes, queueSize).also { key ->
registeredKey = key
}
}
fun awaitRegisteredKey(): AbstractWatchKey {
while (true) {
registeredKey?.let { key -> return key }
Thread.sleep(10)
}
}
}
private class FakeFileTreeVisitor(private val directories: List<Path>) : FileTreeVisitor {
val visitCount = AtomicInteger()
override fun recursiveVisitFiles(root: Path, directoryConsumer: (Path) -> Unit, fileConsumer: (Path) -> Unit) {
visitCount.incrementAndGet()
for (directory in directories) {
directoryConsumer(directory)
}
}
}
private class MutableFakeFileTreeVisitor(
@Volatile var directories: List<Path>,
@Volatile var files: List<Path>,
) : FileTreeVisitor {
override fun recursiveVisitFiles(root: Path, directoryConsumer: (Path) -> Unit, fileConsumer: (Path) -> Unit) {
for (directory in directories) {
directoryConsumer(directory)
}
for (file in files) {
fileConsumer(file)
}
}
}
private class FakeWatchablePath(private val path: Path, private val watchService: FileTreeUnsupportedWatchService) : Watchable {
override fun register(watcher: WatchService, events: Array<out WatchEvent.Kind<*>>, vararg modifiers: Modifier): WatchKey {
check(watcher === watchService)
if (modifiers.isNotEmpty()) {
throw UnsupportedOperationException()
}
return watchService.register(path, events)
}
override fun register(watcher: WatchService, vararg events: WatchEvent.Kind<*>): WatchKey {
return register(watcher, events)
}
}
private class FileTreeUnsupportedWatchService : WatchService {
private val keys = LinkedBlockingQueue<FileTreeUnsupportedWatchKey>()
private val registered = ArrayList<Path>()
val registeredDirectories: List<Path>
get() = ArrayList(registered)
fun awaitRegisteredDirectories(count: Int) {
while (registered.size < count) {
Thread.sleep(10)
}
}
fun register(directory: Path, @Suppress("unused") events: Array<out WatchEvent.Kind<*>>): FileTreeUnsupportedWatchKey {
registered.add(directory)
return FileTreeUnsupportedWatchKey(directory, keys).also { key ->
watchKeys[directory] = key
}
}
private val watchKeys = HashMap<Path, FileTreeUnsupportedWatchKey>()
fun signal(directory: Path, event: WatchEvent<*>) {
watchKeys.getValue(directory).post(event)
}
override fun poll(): WatchKey? = keys.poll()
override fun poll(timeout: Long, unit: TimeUnit): WatchKey? = keys.poll(timeout, unit)
override fun take(): WatchKey = keys.take()
override fun close() {
}
}
private class FileTreeUnsupportedWatchKey(
private val directory: Path,
private val keys: LinkedBlockingQueue<FileTreeUnsupportedWatchKey>,
) : WatchKey {
private val events = ArrayList<WatchEvent<*>>()
private var pendingOverflowCount = 0
private var signalled = false
fun post(event: WatchEvent<*>) {
events.add(event)
signal()
}
private fun signal() {
if (!signalled) {
signalled = true
keys.add(this)
}
}
override fun isValid(): Boolean = true
override fun pollEvents(): List<WatchEvent<*>> {
val result = ArrayList(events)
events.clear()
if (pendingOverflowCount != 0) {
result.add(FakeWatchEvent(OVERFLOW, pendingOverflowCount, null))
pendingOverflowCount = 0
}
return result
}
override fun reset(): Boolean {
signalled = false
if (events.isNotEmpty() || pendingOverflowCount != 0) {
signal()
}
return true
}
override fun cancel() {
}
override fun watchable(): Watchable = directory
}
private data class FakeWatchEvent<T>(
private val kind: WatchEvent.Kind<T>,
private val count: Int,
private val context: T?,
) : WatchEvent<T> {
override fun kind(): WatchEvent.Kind<T> = kind
override fun count(): Int = count
override fun context(): T? = context
}
private class EventRecorder : DirectoryChangeListener {
private val events = LinkedBlockingQueue<DirectoryChangeEvent>()
override suspend fun onEvent(event: DirectoryChangeEvent) {
events.add(event)
}
suspend fun awaitEvent(predicate: (DirectoryChangeEvent) -> Boolean): DirectoryChangeEvent {
return withTimeout(5.seconds) {
lateinit var matchingEvent: DirectoryChangeEvent
while (true) {
val event = events.take()
if (predicate(event)) {
matchingEvent = event
break
}
}
matchingEvent
}
}
fun drainEvents(): List<DirectoryChangeEvent> {
val result = ArrayList<DirectoryChangeEvent>()
events.drainTo(result)
return result
}
}
@@ -1,169 +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.platform.ai.agent.filewatch.impl.watchservice
import com.intellij.platform.ai.agent.filewatch.agentWorkbenchImmediateFileChangeFlow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE
import java.nio.file.StandardWatchEventKinds.ENTRY_DELETE
import java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY
import java.nio.file.WatchEvent
import java.nio.file.WatchKey
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@Timeout(value = 2, unit = TimeUnit.MINUTES)
class MacOSXListeningWatchServiceTest {
@TempDir
lateinit var tempDir: Path
private val pendingEvents = ArrayList<WatchEvent<*>>()
@Test
fun createModifyDeleteEventsAreDeliveredByNativeMacWatcher() = runBlocking(Dispatchers.IO) {
assumeMacOS()
val watchService = createWatchService()
val watchKey = WatchablePath(tempDir).register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
watchService.use {
delay(100.milliseconds)
val file = tempDir.resolve("session.jsonl")
Files.writeString(file, "created")
awaitEvent(watchService, watchKey, ENTRY_CREATE, file)
Files.writeString(file, "modified")
awaitEvent(watchService, watchKey, ENTRY_MODIFY, file)
Files.delete(file)
awaitEvent(watchService, watchKey, ENTRY_DELETE, file)
}
}
@Test
fun nestedFileDeleteIsDeliveredByNativeMacWatcher() = runBlocking(Dispatchers.IO) {
assumeMacOS()
val watchService = createWatchService()
val watchKey = WatchablePath(tempDir).register(watchService, ENTRY_CREATE, ENTRY_DELETE)
watchService.use {
delay(100.milliseconds)
val directory = Files.createDirectory(tempDir.resolve("session"))
val file = directory.resolve("nested.jsonl")
Files.writeString(file, "created")
awaitEvent(watchService, watchKey, ENTRY_CREATE, file)
Files.delete(file)
awaitEvent(watchService, watchKey, ENTRY_DELETE, file)
}
}
@Test
fun immediateFileChangeFlowDeliversLongLivedAppendFdBeforeClose(): Unit = runBlocking(Dispatchers.IO) {
// Codex keeps the rollout JSONL fd open for the entire session lifetime. FSEvents alone
// withholds MODIFY events until the fd is closed, so active threadView tabs subscribe to their
// concrete session file with kqueue vnode events.
assumeMacOS()
val file = tempDir.resolve("rollout-long-lived.jsonl")
Files.createFile(file)
val expectedPath = file.toAbsolutePath().normalize()
val changes = Channel<Path>(Channel.UNLIMITED)
val watchJob = launch {
agentWorkbenchImmediateFileChangeFlow(listOf(file)).collect { path ->
changes.trySend(path)
}
}
try {
delay(200.milliseconds)
Files.newOutputStream(file, StandardOpenOption.APPEND).use { stream ->
stream.write("line1\n".toByteArray())
stream.flush()
assertThat(withTimeout(5.seconds) { changes.receive() }).isEqualTo(expectedPath)
stream.write("line2\n".toByteArray())
stream.flush()
assertThat(withTimeout(5.seconds) { changes.receive() }).isEqualTo(expectedPath)
}
}
finally {
watchJob.cancelAndJoin()
changes.close()
}
}
@Test
fun repeatedNativeStartAndCloseDoesNotCrash() {
assumeMacOS()
repeat(3) {
createWatchService().use { watchService ->
WatchablePath(tempDir).register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
}
}
}
private fun createWatchService(): MacOSXListeningWatchService {
return MacOSXListeningWatchService(object : MacOSXListeningWatchService.Config {
override val latency: Double = 0.1
})
}
private fun awaitEvent(
watchService: MacOSXListeningWatchService,
expectedKey: WatchKey,
expectedKind: WatchEvent.Kind<Path>,
expectedPath: Path,
) {
val deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5)
while (System.nanoTime() < deadline) {
val pendingEvent = pendingEvents.findAndRemove(expectedKind, expectedPath)
if (pendingEvent != null) {
assertThat(pendingEvent.context()).isEqualTo(expectedPath)
return
}
val key = watchService.poll(100, TimeUnit.MILLISECONDS) ?: continue
assertThat(key).isSameAs(expectedKey)
val events = key.pollEvents()
val event = events.firstOrNull { event -> event.kind() === expectedKind && event.context() == expectedPath }
if (event != null) {
assertThat(event.context()).isEqualTo(expectedPath)
pendingEvents.addAll(events - event)
key.reset()
return
}
pendingEvents.addAll(events)
key.reset()
}
throw AssertionError("Expected $expectedKind event for $expectedPath")
}
private fun ArrayList<WatchEvent<*>>.findAndRemove(kind: WatchEvent.Kind<Path>, path: Path): WatchEvent<*>? {
val iterator = iterator()
while (iterator.hasNext()) {
val event = iterator.next()
if (event.kind() === kind && event.context() == path) {
iterator.remove()
return event
}
}
return null
}
private fun assumeMacOS() {
assumeTrue(System.getProperty("os.name").contains("mac", ignoreCase = true))
}
}
@@ -1,132 +0,0 @@
load("@community//build:tests-options.bzl", "jps_test")
load("@rules_jvm//:jvm.bzl", "jvm_library")
### auto-generated section `build intellij.platform.ai.agent.json` start
jvm_library(
name = "json",
srcs = glob(
[
"src/**/*.kt",
"src/**/*.java",
"src/**/*.form",
],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.json",
resource_strip_prefix = "resources",
resources = glob(
["resources/**/*"],
),
visibility = ["//visibility:public"],
deps = [
# do not sort,
"@lib//:kotlin-stdlib",
"//libraries/kotlinx/coroutines/core",
"//platform/core-api:core",
"//platform/util",
"//plugins/agent-workbench/lib-agent/filewatch",
"//libraries/jackson3/jackson:libraries-jackson3",
],
)
jvm_library(
name = "json_test_lib",
testonly = True,
srcs = glob(
[],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.json",
visibility = ["//visibility:public"],
runtime_deps = [
# do not sort,
":json",
"//libraries/kotlinx/coroutines/core:core_test_lib",
"//platform/core-api:core_test_lib",
"//platform/util:util_test_lib",
"//plugins/agent-workbench/lib-agent/filewatch:filewatch_test_lib",
"//libraries/jackson3/jackson:libraries-jackson3_test_lib",
"//libraries/junit5",
"//libraries/junit5:junit5_test_lib",
"//libraries/assertj-core",
"//libraries/assertj-core:assertj-core_test_lib",
],
)
### auto-generated section `build intellij.platform.ai.agent.json` end
### auto-generated section `iml intellij.platform.ai.agent.json` start
exports_files(
[
"intellij.platform.ai.agent.json.iml",
],
visibility = ["//visibility:public"],
)
### auto-generated section `iml intellij.platform.ai.agent.json` end
### auto-generated section `build intellij.platform.ai.agent.json.tests` start
jvm_library(
name = "ai-agent-json-tests",
srcs = glob(
[],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.json.tests",
visibility = ["//visibility:public"],
)
jvm_library(
name = "ai-agent-json-tests_test_lib",
testonly = True,
srcs = glob(
[
"testSrc/**/*.kt",
"testSrc/**/*.java",
"testSrc/**/*.form",
],
allow_empty = True,
),
associates = [
"//plugins/agent-workbench/lib-agent/json",
"//plugins/agent-workbench/lib-agent/json:json_test_lib",
],
visibility = ["//visibility:public"],
runtime_deps = [":ai-agent-json-tests"],
deps = [
# do not sort,
"@lib//:kotlin-stdlib",
"//libraries/kotlinx/coroutines/core",
"//libraries/kotlinx/coroutines/core:core_test_lib",
"//platform/core-api:core",
"//platform/core-api:core_test_lib",
"//platform/util",
"//platform/util:util_test_lib",
"//plugins/agent-workbench/lib-agent/filewatch",
"//plugins/agent-workbench/lib-agent/filewatch:filewatch_test_lib",
"//libraries/jackson3/jackson:libraries-jackson3",
"//libraries/jackson3/jackson:libraries-jackson3_test_lib",
"//libraries/junit5",
"//libraries/junit5:junit5_test_lib",
"//libraries/assertj-core",
"//libraries/assertj-core:assertj-core_test_lib",
],
)
### auto-generated section `build intellij.platform.ai.agent.json.tests` end
### auto-generated section `iml intellij.platform.ai.agent.json.tests` start
exports_files(
[
"intellij.platform.ai.agent.json.tests.iml",
],
visibility = ["//visibility:public"],
)
### auto-generated section `iml intellij.platform.ai.agent.json.tests` end
### auto-generated section `test intellij.platform.ai.agent.json.tests` start
jps_test(
name = "ai-agent-json-tests_test",
runtime_deps = [":ai-agent-json-tests_test_lib"],
)
### auto-generated section `test intellij.platform.ai.agent.json.tests` end
@@ -1,97 +0,0 @@
f:com.intellij.platform.ai.agent.json.WorkbenchJsonParsingUtilsKt
- sf:createJsonGenerator(tools.jackson.core.json.JsonFactory,java.io.OutputStream):tools.jackson.core.JsonGenerator
- sf:createJsonGenerator(tools.jackson.core.json.JsonFactory,java.io.Writer):tools.jackson.core.JsonGenerator
- sf:createJsonGenerator(tools.jackson.core.json.JsonFactory,java.io.Writer,tools.jackson.core.PrettyPrinter):tools.jackson.core.JsonGenerator
- sf:createJsonParser(tools.jackson.core.json.JsonFactory,java.io.InputStream):tools.jackson.core.JsonParser
- sf:createJsonParser(tools.jackson.core.json.JsonFactory,java.io.Reader):tools.jackson.core.JsonParser
- sf:createJsonParser(tools.jackson.core.json.JsonFactory,java.lang.String):tools.jackson.core.JsonParser
- sf:createJsonParser(tools.jackson.core.json.JsonFactory,B[]):tools.jackson.core.JsonParser
- sf:forEachJsonObjectField(tools.jackson.core.JsonParser,kotlin.jvm.functions.Function1):V
- sf:readJsonLongOrNull(tools.jackson.core.JsonParser):java.lang.Long
- sf:readJsonStringOrNull(tools.jackson.core.JsonParser):java.lang.String
f:com.intellij.platform.ai.agent.json.WorkbenchJsonlScanner
- sf:INSTANCE:com.intellij.platform.ai.agent.json.WorkbenchJsonlScanner
- f:removeLines(java.nio.file.Path,tools.jackson.core.json.JsonFactory,kotlin.jvm.functions.Function1):V
- f:scanJsonObjects(java.nio.file.Path,tools.jackson.core.json.JsonFactory,I,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2):java.lang.Object
- bs:scanJsonObjects$default(com.intellij.platform.ai.agent.json.WorkbenchJsonlScanner,java.nio.file.Path,tools.jackson.core.json.JsonFactory,I,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2,I,java.lang.Object):java.lang.Object
- f:scanTailLines(java.nio.file.Path,tools.jackson.core.json.JsonFactory,J,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2):java.lang.Object
- bs:scanTailLines$default(com.intellij.platform.ai.agent.json.WorkbenchJsonlScanner,java.nio.file.Path,tools.jackson.core.json.JsonFactory,J,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2,I,java.lang.Object):java.lang.Object
f:com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionCachedFile
- f:lastModifiedNs:J
- f:parsedValue:java.lang.Object
- f:sizeBytes:J
- <init>(J,J,java.lang.Object):V
- f:component1():J
- f:component2():J
- f:component3():java.lang.Object
- f:copy(J,J,java.lang.Object):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionCachedFile
- bs:copy$default(com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionCachedFile,J,J,java.lang.Object,I,java.lang.Object):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionCachedFile
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionChangeSet
- f:changedPaths:java.util.Set
- f:requiresFullRescan:Z
- <init>():V
- <init>(java.util.Set,Z):V
- b:<init>(java.util.Set,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.util.Set
- f:component2():Z
- f:copy(java.util.Set,Z):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionChangeSet
- bs:copy$default(com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionChangeSet,java.util.Set,Z,I,java.lang.Object):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionChangeSet
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionChangeSetKt
- sf:normalizeFileBackedSessionPath(java.nio.file.Path):java.nio.file.Path
- sf:toFileBackedSessionPathKey(java.nio.file.Path):java.lang.String
f:com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionEventClassifierKt
- sf:classifyFileBackedSessionEvent(com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEvent,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionChangeSet
f:com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionFileStat
- f:lastModifiedNs:J
- f:path:java.nio.file.Path
- f:pathKey:java.lang.String
- f:sizeBytes:J
- <init>(java.lang.String,java.nio.file.Path,J,J):V
- f:component1():java.lang.String
- f:component2():java.nio.file.Path
- f:component3():J
- f:component4():J
- f:copy(java.lang.String,java.nio.file.Path,J,J):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionFileStat
- bs:copy$default(com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionFileStat,java.lang.String,java.nio.file.Path,J,J,I,java.lang.Object):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionFileStat
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionInvalidationState
- <init>(kotlin.jvm.functions.Function1):V
- f:applyParsedUpdates(java.util.Map):V
- f:clear():V
- f:markDirty(com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionChangeSet):I
- f:planRescan(java.util.Map):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionRescanPlan
- f:snapshotCachedFiles():java.util.Map
f:com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionInvalidationStateKt
- sf:buildFileBackedSessionFileStat(java.nio.file.Path,J):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionFileStat
- bs:buildFileBackedSessionFileStat$default(java.nio.file.Path,J,I,java.lang.Object):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionFileStat
f:com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionRescanPlan
- f:dirtyPathCount:I
- f:filesToParse:java.util.List
- f:fullRescan:Z
- f:removedAny:Z
- <init>(java.util.List,Z,Z,I):V
- f:component1():java.util.List
- f:component2():Z
- f:component3():Z
- f:component4():I
- f:copy(java.util.List,Z,Z,I):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionRescanPlan
- bs:copy$default(com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionRescanPlan,java.util.List,Z,Z,I,I,java.lang.Object):com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionRescanPlan
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionUpdatesFlowKt
- sf:createFileBackedSessionChangeFlow(com.intellij.openapi.diagnostic.Logger,java.lang.String,kotlin.jvm.functions.Function0,Z,kotlin.jvm.functions.Function2):kotlinx.coroutines.flow.Flow
- bs:createFileBackedSessionChangeFlow$default(com.intellij.openapi.diagnostic.Logger,java.lang.String,kotlin.jvm.functions.Function0,Z,kotlin.jvm.functions.Function2,I,java.lang.Object):kotlinx.coroutines.flow.Flow
f:com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionWatcher
- java.lang.AutoCloseable
- <init>(com.intellij.openapi.diagnostic.Logger,java.lang.String,com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionWatcherSpec,kotlinx.coroutines.CoroutineScope,kotlin.jvm.functions.Function1,java.lang.String):V
- b:<init>(com.intellij.openapi.diagnostic.Logger,java.lang.String,com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionWatcherSpec,kotlinx.coroutines.CoroutineScope,kotlin.jvm.functions.Function1,java.lang.String,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- close():V
f:com.intellij.platform.ai.agent.json.filebacked.FileBackedSessionWatcherSpec
- <init>(java.util.Collection,kotlin.jvm.functions.Function1):V
- f:getEventToChangeSet():kotlin.jvm.functions.Function1
- f:getRoots():java.util.Collection
@@ -1,5 +0,0 @@
kotlin/jvm/internal/DefaultConstructorMarker
tools/jackson/core/JsonGenerator
tools/jackson/core/JsonParser
tools/jackson/core/PrettyPrinter
tools/jackson/core/json/JsonFactory
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="com.intellij.platform.ai.agent.json" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.libraries.kotlinx.coroutines.core" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.filewatch" />
<orderEntry type="module" module-name="intellij.libraries.jackson3" />
<orderEntry type="module" module-name="intellij.libraries.junit5" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.assertj.core" scope="TEST" />
</component>
</module>
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/testSrc">
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" packagePrefix="com.intellij.platform.ai.agent.json" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="TEST" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.libraries.kotlinx.coroutines.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.util" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.filewatch" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.jackson3" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.junit5" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.assertj.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.json" scope="TEST" />
</component>
<component name="TestModuleProperties" production-module="intellij.platform.ai.agent.json" />
</module>
@@ -1,7 +0,0 @@
<idea-plugin>
<!-- region Generated dependencies - run `Generate Product Layouts` to regenerate -->
<dependencies>
<module name="intellij.platform.ai.agent.filewatch"/>
</dependencies>
<!-- endregion -->
</idea-plugin>
@@ -1,93 +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.platform.ai.agent.json
import tools.jackson.core.JsonGenerator
import tools.jackson.core.JsonParser
import tools.jackson.core.JsonToken
import tools.jackson.core.ObjectReadContext
import tools.jackson.core.ObjectWriteContext
import tools.jackson.core.PrettyPrinter
import tools.jackson.core.StreamWriteFeature
import tools.jackson.core.TokenStreamFactory
import tools.jackson.core.json.JsonFactory
import java.io.InputStream
import java.io.OutputStream
import java.io.Reader
import java.io.Writer
fun JsonFactory.createJsonParser(content: String): JsonParser {
return createParser(ObjectReadContext.empty(), content)
}
fun JsonFactory.createJsonParser(content: ByteArray): JsonParser {
return createParser(ObjectReadContext.empty(), content)
}
fun JsonFactory.createJsonParser(input: InputStream): JsonParser {
return createParser(ObjectReadContext.empty(), input)
}
fun JsonFactory.createJsonParser(reader: Reader): JsonParser {
return createParser(ObjectReadContext.empty(), reader)
}
fun JsonFactory.createJsonGenerator(output: OutputStream): JsonGenerator {
return createGenerator(ObjectWriteContext.empty(), output).configure(StreamWriteFeature.AUTO_CLOSE_TARGET, false)
}
fun JsonFactory.createJsonGenerator(writer: Writer): JsonGenerator {
return createGenerator(ObjectWriteContext.empty(), writer).configure(StreamWriteFeature.AUTO_CLOSE_TARGET, false)
}
fun JsonFactory.createJsonGenerator(writer: Writer, prettyPrinter: PrettyPrinter): JsonGenerator {
return createGenerator(PrettyPrinterWriteContext(this, prettyPrinter), writer).configure(StreamWriteFeature.AUTO_CLOSE_TARGET, false)
}
private class PrettyPrinterWriteContext(
private val factory: JsonFactory,
private val prettyPrinter: PrettyPrinter,
) : ObjectWriteContext.Base() {
override fun tokenStreamFactory(): TokenStreamFactory = factory
override fun getPrettyPrinter(): PrettyPrinter = prettyPrinter
}
inline fun forEachJsonObjectField(parser: JsonParser, onField: (String) -> Boolean) {
while (true) {
val token = parser.nextToken() ?: return
if (token == JsonToken.END_OBJECT) return
if (token != JsonToken.PROPERTY_NAME) {
parser.skipChildren()
continue
}
val fieldName = parser.currentName()
if (parser.nextToken() == null) return
if (!onField(fieldName)) return
}
}
fun readJsonStringOrNull(parser: JsonParser): String? {
return when (parser.currentToken()) {
JsonToken.VALUE_STRING -> parser.string
JsonToken.VALUE_NUMBER_INT, JsonToken.VALUE_NUMBER_FLOAT -> parser.numberValue.toString()
JsonToken.VALUE_TRUE, JsonToken.VALUE_FALSE -> parser.booleanValue.toString()
JsonToken.VALUE_NULL -> null
else -> {
parser.skipChildren()
null
}
}
}
fun readJsonLongOrNull(parser: JsonParser): Long? {
return when (parser.currentToken()) {
JsonToken.VALUE_NUMBER_INT -> parser.longValue
JsonToken.VALUE_NUMBER_FLOAT -> parser.doubleValue.toLong()
JsonToken.VALUE_STRING -> parser.string.toLongOrNull() ?: parser.string.toDoubleOrNull()?.toLong()
JsonToken.VALUE_NULL -> null
else -> {
parser.skipChildren()
null
}
}
}
@@ -1,182 +0,0 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("PackageDirectoryMismatch", "unused")
package com.intellij.platform.ai.agent.json
import tools.jackson.core.JsonParser
import tools.jackson.core.JsonToken
import tools.jackson.core.json.JsonFactory
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.channels.Channels
import java.nio.channels.FileChannel
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
object WorkbenchJsonlScanner {
fun <S> scanJsonObjects(
path: Path,
jsonFactory: JsonFactory,
maxObjects: Int = Int.MAX_VALUE,
newState: () -> S,
onObject: (JsonParser, S) -> Boolean,
): S {
if (maxObjects <= 0) return newState()
val fastState = newState()
val parsedWithSingleParser = try {
parseWithSingleParser(path, jsonFactory, maxObjects, fastState, onObject)
true
}
catch (_: Throwable) {
false
}
if (parsedWithSingleParser) {
return fastState
}
val fallbackState = newState()
parseLineByLine(path, jsonFactory, maxObjects, fallbackState, onObject)
return fallbackState
}
fun <S> scanTailLines(
path: Path,
jsonFactory: JsonFactory,
tailBytes: Long = 16_384L,
newState: () -> S,
onObject: (JsonParser, S) -> Boolean,
): S {
val state = newState()
try {
FileChannel.open(path, StandardOpenOption.READ).use { channel ->
val fileSize = channel.size()
val seekPos = maxOf(0L, fileSize - tailBytes)
channel.position(seekPos)
val reader = BufferedReader(InputStreamReader(Channels.newInputStream(channel), Charsets.UTF_8))
// If we seeked mid-file, skip the first partial line.
if (seekPos > 0) {
reader.readLine()
}
while (true) {
val line = reader.readLine() ?: break
val trimmed = line.trim()
if (trimmed.isEmpty()) continue
val shouldContinue = parseLineObject(trimmed, jsonFactory, state, onObject) ?: continue
if (!shouldContinue) break
}
}
}
catch (_: Exception) {
// Tail scan is best-effort; return whatever state we collected.
}
return state
}
/**
* Removes lines from a JSONL file where [shouldRemove] returns `true`.
* Deletes the file entirely if no lines remain after removal.
*
* @param shouldRemove receives a [JsonParser] positioned at `START_OBJECT`; return `true` to remove the line
*/
fun removeLines(
path: Path,
jsonFactory: JsonFactory,
shouldRemove: (JsonParser) -> Boolean,
) {
if (!Files.exists(path)) return
val lines = Files.readAllLines(path)
val remaining = lines.filter { line ->
val trimmed = line.trim()
if (trimmed.isEmpty()) return@filter false
val remove = try {
jsonFactory.createJsonParser(trimmed).use { parser ->
parser.nextToken() == JsonToken.START_OBJECT && shouldRemove(parser)
}
}
catch (_: Throwable) {
false
}
!remove
}
if (remaining.isEmpty()) {
Files.deleteIfExists(path)
}
else {
Files.writeString(path, remaining.joinToString("\n", postfix = "\n"))
}
}
private fun <S> parseWithSingleParser(
path: Path,
jsonFactory: JsonFactory,
maxObjects: Int,
state: S,
onObject: (JsonParser, S) -> Boolean,
) {
Files.newBufferedReader(path).use { reader ->
jsonFactory.createJsonParser(reader).use { parser ->
var parsedObjects = 0
while (parsedObjects < maxObjects) {
val token = parser.nextToken() ?: return
if (token != JsonToken.START_OBJECT) {
parser.skipChildren()
continue
}
parsedObjects++
if (!onObject(parser, state)) {
return
}
}
}
}
}
private fun <S> parseLineByLine(
path: Path,
jsonFactory: JsonFactory,
maxObjects: Int,
state: S,
onObject: (JsonParser, S) -> Boolean,
) {
Files.newBufferedReader(path).use { reader ->
var parsedObjects = 0
while (parsedObjects < maxObjects) {
val line = reader.readLine() ?: return
val trimmed = line.trim()
if (trimmed.isEmpty()) continue
val shouldContinue = parseLineObject(trimmed, jsonFactory, state, onObject)
if (shouldContinue == null) {
continue
}
parsedObjects++
if (!shouldContinue) {
return
}
}
}
}
private fun <S> parseLineObject(
line: String,
jsonFactory: JsonFactory,
state: S,
onObject: (JsonParser, S) -> Boolean,
): Boolean? {
return try {
jsonFactory.createJsonParser(line).use { parser ->
if (parser.nextToken() != JsonToken.START_OBJECT) {
return null
}
onObject(parser, state)
}
}
catch (_: Throwable) {
null
}
}
}
@@ -1,28 +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.platform.ai.agent.json.filebacked
import java.nio.file.Path
import kotlin.io.path.invariantSeparatorsPathString
data class FileBackedSessionChangeSet(
// Known files that must be reparsed regardless of file stat heuristics.
@JvmField val changedPaths: Set<Path> = emptySet(),
// Overflow/ambiguous events where file-level attribution is not reliable.
@JvmField val requiresFullRescan: Boolean = false,
// `changedPaths.isEmpty() && !requiresFullRescan` means "refresh ping":
// re-run stat-based scan without forcing full reparse.
)
fun normalizeFileBackedSessionPath(path: Path): Path {
return runCatching {
path.toAbsolutePath().normalize()
}.getOrElse {
path.normalize()
}
}
fun toFileBackedSessionPathKey(path: Path): String {
return normalizeFileBackedSessionPath(path).invariantSeparatorsPathString
}
@@ -1,44 +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.platform.ai.agent.json.filebacked
import com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEvent
import com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEventType
import java.nio.file.Path
fun classifyFileBackedSessionEvent(
event: AgentWorkbenchWatchEvent,
isChangedPath: (Path) -> Boolean,
isRelevantPath: (Path) -> Boolean,
isRelevantRoot: (Path) -> Boolean,
): FileBackedSessionChangeSet? {
val eventPath = event.path
val rootPath = event.rootPath
if (event.eventType == AgentWorkbenchWatchEventType.OVERFLOW) {
val isRelevantOverflow = when {
eventPath != null && isRelevantPath(eventPath) -> true
rootPath != null && isRelevantRoot(rootPath) -> true
eventPath == null && rootPath == null -> true
else -> false
}
return if (isRelevantOverflow) FileBackedSessionChangeSet(requiresFullRescan = true) else null
}
if (eventPath != null && isChangedPath(eventPath)) {
return FileBackedSessionChangeSet(changedPaths = setOf(normalizeFileBackedSessionPath(eventPath)))
}
val isRelevantEvent = when {
eventPath != null -> isRelevantPath(eventPath)
rootPath != null -> isRelevantRoot(rootPath)
else -> false
}
if (!isRelevantEvent) return null
val isAmbiguousDirectoryEvent = event.isDirectory || eventPath == null
if (isAmbiguousDirectoryEvent) {
return FileBackedSessionChangeSet(requiresFullRescan = true)
}
return FileBackedSessionChangeSet()
}
@@ -1,147 +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.platform.ai.agent.json.filebacked
import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.TimeUnit
data class FileBackedSessionFileStat(
@JvmField val pathKey: String,
@JvmField val path: Path,
@JvmField val lastModifiedNs: Long,
@JvmField val sizeBytes: Long,
)
/**
* Reads the stat (mtime, size) of [candidate] and returns a [FileBackedSessionFileStat],
* or `null` if the file is not a regular file, cannot be stat'd, or its mtime is below
* [minLastModifiedNs].
*/
fun buildFileBackedSessionFileStat(
candidate: Path,
minLastModifiedNs: Long = Long.MIN_VALUE,
): FileBackedSessionFileStat? {
if (!Files.isRegularFile(candidate)) return null
val lastModifiedNs = try {
Files.getLastModifiedTime(candidate).to(TimeUnit.NANOSECONDS)
}
catch (_: Throwable) {
return null
}
if (lastModifiedNs < minLastModifiedNs) return null
val sizeBytes = try {
Files.size(candidate)
}
catch (_: Throwable) {
return null
}
val pathKey = toFileBackedSessionPathKey(candidate)
return FileBackedSessionFileStat(
pathKey = pathKey,
path = candidate,
lastModifiedNs = lastModifiedNs,
sizeBytes = sizeBytes,
)
}
data class FileBackedSessionCachedFile<Parsed>(
@JvmField val lastModifiedNs: Long,
@JvmField val sizeBytes: Long,
@JvmField val parsedValue: Parsed,
)
data class FileBackedSessionRescanPlan(
@JvmField val filesToParse: List<FileBackedSessionFileStat>,
@JvmField val removedAny: Boolean,
@JvmField val fullRescan: Boolean,
@JvmField val dirtyPathCount: Int,
)
class FileBackedSessionInvalidationState<Parsed>(
private val trackedPathPredicate: (Path) -> Boolean,
) {
private val cacheLock = Any()
private val cachedFilesByPath = LinkedHashMap<String, FileBackedSessionCachedFile<Parsed>>()
private val dirtyPathKeys = LinkedHashSet<String>()
private var forceFullRescan = false
fun markDirty(changeSet: FileBackedSessionChangeSet): Int {
if (!changeSet.requiresFullRescan && changeSet.changedPaths.isEmpty()) {
return 0
}
var markedPaths = 0
synchronized(cacheLock) {
forceFullRescan = forceFullRescan || changeSet.requiresFullRescan
for (path in changeSet.changedPaths) {
if (!trackedPathPredicate(path)) continue
if (dirtyPathKeys.add(toFileBackedSessionPathKey(path))) {
markedPaths++
}
}
}
return markedPaths
}
fun planRescan(scannedFiles: Map<String, FileBackedSessionFileStat>): FileBackedSessionRescanPlan {
val filesToParse = ArrayList<FileBackedSessionFileStat>()
var removedAny = false
var fullRescan = false
var dirtyPathCount = 0
synchronized(cacheLock) {
val iterator = cachedFilesByPath.entries.iterator()
while (iterator.hasNext()) {
if (!scannedFiles.containsKey(iterator.next().key)) {
iterator.remove()
removedAny = true
}
}
val dirtyPathsSnapshot = dirtyPathKeys.takeIf { it.isNotEmpty() }?.let(::LinkedHashSet)
fullRescan = forceFullRescan
dirtyPathCount = dirtyPathsSnapshot?.size ?: 0
forceFullRescan = false
dirtyPathKeys.clear()
for ((pathKey, stat) in scannedFiles) {
val cached = cachedFilesByPath[pathKey]
val parseBecauseDirty = dirtyPathsSnapshot?.contains(pathKey) == true
val parseBecauseStatsChanged = cached == null || cached.lastModifiedNs != stat.lastModifiedNs || cached.sizeBytes != stat.sizeBytes
if (fullRescan || parseBecauseDirty || parseBecauseStatsChanged) {
filesToParse.add(stat)
}
}
}
return FileBackedSessionRescanPlan(
filesToParse = filesToParse,
removedAny = removedAny,
fullRescan = fullRescan,
dirtyPathCount = dirtyPathCount,
)
}
fun applyParsedUpdates(parsedUpdates: Map<String, FileBackedSessionCachedFile<Parsed>>) {
if (parsedUpdates.isEmpty()) return
synchronized(cacheLock) {
cachedFilesByPath.putAll(parsedUpdates)
}
}
fun snapshotCachedFiles(): Map<String, FileBackedSessionCachedFile<Parsed>> {
return synchronized(cacheLock) {
LinkedHashMap(cachedFilesByPath)
}
}
fun clear() {
synchronized(cacheLock) {
cachedFilesByPath.clear()
dirtyPathKeys.clear()
forceFullRescan = false
}
}
}
@@ -1,51 +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.platform.ai.agent.json.filebacked
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.debug
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
fun createFileBackedSessionChangeFlow(
logger: Logger,
watcherName: String,
initContext: (() -> String)? = null,
emitInitialRefreshPing: Boolean = false,
createWatcher: (CoroutineScope, onChange: (FileBackedSessionChangeSet) -> Unit) -> AutoCloseable?,
): Flow<FileBackedSessionChangeSet> = callbackFlow {
val context = initContext?.invoke()?.takeUnless { it.isBlank() }
logger.debug {
"Initializing $watcherName updates watcher${context?.let { " ($it)" } ?: ""}"
}
val watcher = runCatching {
createWatcher(this) { changeSet ->
logger.debug {
"$watcherName watcher signaled change; emitting update (fullRescan=${changeSet.requiresFullRescan}, changedPaths=${changeSet.changedPaths.size})"
}
trySend(changeSet)
}
}.onFailure { t ->
logger.warn("Failed to initialize $watcherName watcher", t)
}.getOrNull()
if (watcher == null) {
logger.debug { "$watcherName updates watcher was not initialized; updates flow will stay idle" }
awaitClose { }
return@callbackFlow
}
if (emitInitialRefreshPing) {
val initialRefreshEmitted = trySend(FileBackedSessionChangeSet()).isSuccess
logger.debug {
"$watcherName watcher initialized; initial refresh ping emitted=$initialRefreshEmitted"
}
}
awaitClose {
logger.debug { "Closing $watcherName updates watcher" }
watcher.close()
}
}
@@ -1,78 +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.platform.ai.agent.json.filebacked
import com.intellij.platform.ai.agent.filewatch.AgentWorkbenchDirectoryWatcher
import com.intellij.platform.ai.agent.filewatch.AgentWorkbenchWatchEvent
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.debug
import kotlinx.coroutines.CoroutineScope
import java.nio.file.Files
import java.nio.file.Path
class FileBackedSessionWatcherSpec(
val roots: Collection<Path>,
val eventToChangeSet: (AgentWorkbenchWatchEvent) -> FileBackedSessionChangeSet?,
)
class FileBackedSessionWatcher(
private val logger: Logger,
private val watcherName: String,
spec: FileBackedSessionWatcherSpec,
scope: CoroutineScope,
private val onChange: (FileBackedSessionChangeSet) -> Unit,
private val failureMessage: String = "$watcherName watcher failed",
) : AutoCloseable {
private val watcher: AgentWorkbenchDirectoryWatcher?
init {
val roots = spec.roots.asSequence()
.map(::normalizeFileBackedSessionPath)
.filter { path -> Files.isDirectory(path) }
.distinct()
.toList()
logger.debug { "Registering $watcherName watcher (roots=$roots)" }
watcher = if (roots.isEmpty()) {
logger.debug { "No watcher roots found; $watcherName watcher will stay idle" }
null
}
else {
AgentWorkbenchDirectoryWatcher(
roots = roots,
scope = scope,
onWatchEvent = { event -> handleWatchEvent(event, spec.eventToChangeSet) },
onFailure = { t ->
logger.warn(failureMessage, t)
},
)
}
if (watcher != null) {
logger.debug { "Initialized directory watcher for roots=$roots" }
}
}
override fun close() {
logger.debug { "Closing $watcherName watcher" }
watcher?.close()
}
private fun handleWatchEvent(
event: AgentWorkbenchWatchEvent,
eventToChangeSet: (AgentWorkbenchWatchEvent) -> FileBackedSessionChangeSet?,
) {
val eventPath = event.path
val rootPath = event.rootPath
val eventType = event.eventType
val isDirectory = event.isDirectory
logger.debug {
"$watcherName watcher event type=$eventType path=$eventPath root=$rootPath isDirectory=$isDirectory count=${event.count}"
}
val changeSet = eventToChangeSet(event) ?: return
logger.debug {
"$watcherName watcher detected relevant changes; notifying listeners (fullRescan=${changeSet.requiresFullRescan}, changedPaths=${changeSet.changedPaths.size})"
}
onChange(changeSet)
}
}
@@ -1,121 +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.platform.ai.agent.json.filebacked
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import java.util.concurrent.TimeUnit
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path
@Timeout(value = 2, unit = TimeUnit.MINUTES)
class FileBackedSessionInvalidationStateTest {
@TempDir
lateinit var tempDir: Path
@Test
fun refreshPingDoesNotMarkDirtyPaths() {
val state = newState()
val sessionPath = tempDir.resolve("refresh/session.jsonl")
val stat = stat(sessionPath)
state.applyParsedUpdates(mapOf(stat.pathKey to cachedFile(stat, parsedValue = "initial")))
val markedPaths = state.markDirty(FileBackedSessionChangeSet())
val plan = state.planRescan(mapOf(stat.pathKey to stat))
assertThat(markedPaths).isZero()
assertThat(plan.filesToParse).isEmpty()
assertThat(plan.dirtyPathCount).isZero()
assertThat(plan.fullRescan).isFalse()
}
@Test
fun dirtyPathForcesReparseWhenFileStatsDidNotChange() {
val state = newState()
val sessionPath = tempDir.resolve("dirty/session.jsonl")
val stat = stat(sessionPath)
state.applyParsedUpdates(mapOf(stat.pathKey to cachedFile(stat, parsedValue = "before")))
val markedPaths = state.markDirty(FileBackedSessionChangeSet(changedPaths = setOf(sessionPath)))
val plan = state.planRescan(mapOf(stat.pathKey to stat))
assertThat(markedPaths).isEqualTo(1)
assertThat(plan.filesToParse).containsExactly(stat)
assertThat(plan.dirtyPathCount).isEqualTo(1)
assertThat(plan.fullRescan).isFalse()
}
@Test
fun fullRescanReparsesAllScannedFiles() {
val state = newState()
val firstStat = stat(tempDir.resolve("full/first.jsonl"), lastModifiedNs = 1L)
val secondStat = stat(tempDir.resolve("full/second.jsonl"), lastModifiedNs = 2L)
state.applyParsedUpdates(
mapOf(
firstStat.pathKey to cachedFile(firstStat, parsedValue = "first"),
secondStat.pathKey to cachedFile(secondStat, parsedValue = "second"),
)
)
state.markDirty(FileBackedSessionChangeSet(requiresFullRescan = true))
val plan = state.planRescan(
linkedMapOf(
firstStat.pathKey to firstStat,
secondStat.pathKey to secondStat,
)
)
assertThat(plan.filesToParse).containsExactly(firstStat, secondStat)
assertThat(plan.fullRescan).isTrue()
}
@Test
fun removedFilesArePrunedFromCache() {
val state = newState()
val keptStat = stat(tempDir.resolve("prune/kept.jsonl"), lastModifiedNs = 1L)
val removedStat = stat(tempDir.resolve("prune/removed.jsonl"), lastModifiedNs = 2L)
state.applyParsedUpdates(
mapOf(
keptStat.pathKey to cachedFile(keptStat, parsedValue = "kept"),
removedStat.pathKey to cachedFile(removedStat, parsedValue = "removed"),
)
)
val plan = state.planRescan(mapOf(keptStat.pathKey to keptStat))
val cachedFiles = state.snapshotCachedFiles()
assertThat(plan.removedAny).isTrue()
assertThat(plan.filesToParse).isEmpty()
assertThat(cachedFiles).containsOnlyKeys(keptStat.pathKey)
}
}
private fun newState(): FileBackedSessionInvalidationState<String> {
return FileBackedSessionInvalidationState { path ->
path.fileName?.toString()?.endsWith(".jsonl") == true
}
}
private fun stat(
path: Path,
lastModifiedNs: Long = 1L,
sizeBytes: Long = 100L,
): FileBackedSessionFileStat {
return FileBackedSessionFileStat(
pathKey = toFileBackedSessionPathKey(path),
path = normalizeFileBackedSessionPath(path),
lastModifiedNs = lastModifiedNs,
sizeBytes = sizeBytes,
)
}
private fun cachedFile(
stat: FileBackedSessionFileStat,
parsedValue: String,
): FileBackedSessionCachedFile<String> {
return FileBackedSessionCachedFile(
lastModifiedNs = stat.lastModifiedNs,
sizeBytes = stat.sizeBytes,
parsedValue = parsedValue,
)
}
@@ -1,73 +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.platform.ai.agent.json.filebacked
import com.intellij.openapi.diagnostic.logger
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@Timeout(value = 2, unit = TimeUnit.MINUTES)
class FileBackedSessionUpdatesFlowTest {
@Test
fun staysIdleWhenWatcherWasNotInitialized() = runBlocking(Dispatchers.Default) {
val update = withTimeoutOrNull(1.seconds) {
createFileBackedSessionChangeFlow(
logger = logger<FileBackedSessionUpdatesFlowTest>(),
watcherName = "test watcher",
) { _, _ -> null }.first()
}
assertThat(update).isNull()
}
@Test
fun emitsInitialRefreshPingWhenConfigured() {
runBlocking(Dispatchers.Default) {
val update = withTimeout(5.seconds) {
createFileBackedSessionChangeFlow(
logger = logger<FileBackedSessionUpdatesFlowTest>(),
watcherName = "test watcher",
emitInitialRefreshPing = true,
) { _, _ -> AutoCloseable { } }.first()
}
assertThat(update).isEqualTo(FileBackedSessionChangeSet())
}
}
@Test
fun closesWatcherWhenCollectorStops() = runBlocking(Dispatchers.Default) {
val closed = CompletableDeferred<Unit>()
val updatesJob = launch {
createFileBackedSessionChangeFlow(
logger = logger<FileBackedSessionUpdatesFlowTest>(),
watcherName = "test watcher",
) { _, _ -> AutoCloseable { closed.complete(Unit) } }.collect { }
}
try {
delay(100.milliseconds)
updatesJob.cancelAndJoin()
withTimeout(5.seconds) {
closed.await()
}
}
finally {
if (updatesJob.isActive) {
updatesJob.cancelAndJoin()
}
}
}
}
@@ -1,74 +0,0 @@
load("@rules_jvm//:jvm.bzl", "jvm_library")
### auto-generated section `build intellij.platform.ai.agent.sessions.core` start
jvm_library(
name = "ai-agent-sessions-core",
srcs = glob(
[
"src/**/*.kt",
"src/**/*.java",
"src/**/*.form",
],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.sessions.core",
resource_strip_prefix = "resources",
resources = glob(
["resources/**/*"],
),
visibility = ["//visibility:public"],
deps = [
# do not sort,
"@lib//:kotlin-stdlib",
"//libraries/kotlinx/serialization/core",
"//platform/core-api:core",
"//platform/core-ui",
"//platform/platform-util-io:ide-util-io",
"//platform/projectModel-api:projectModel",
"//platform/util",
"//platform/util-ex",
"//platform/util:util-ui",
"//plugins/agent-workbench/lib-agent/core",
"//plugins/agent-workbench/prompt/core",
"//libraries/jackson3/jackson:libraries-jackson3",
"//plugins/agent-workbench/lib-agent/json",
],
)
jvm_library(
name = "ai-agent-sessions-core_test_lib",
testonly = True,
srcs = glob(
[],
allow_empty = True,
),
module_name = "intellij.platform.ai.agent.sessions.core",
visibility = ["//visibility:public"],
runtime_deps = [
# do not sort,
":ai-agent-sessions-core",
"//libraries/kotlinx/serialization/core:core_test_lib",
"//platform/core-api:core_test_lib",
"//platform/core-ui:core-ui_test_lib",
"//platform/platform-util-io:ide-util-io_test_lib",
"//platform/projectModel-api:projectModel_test_lib",
"//platform/util:util_test_lib",
"//platform/util-ex:util-ex_test_lib",
"//platform/util:util-ui_test_lib",
"//plugins/agent-workbench/lib-agent/core:core_test_lib",
"//plugins/agent-workbench/prompt/core:core_test_lib",
"//libraries/jackson3/jackson:libraries-jackson3_test_lib",
"//plugins/agent-workbench/lib-agent/json:json_test_lib",
],
)
### auto-generated section `build intellij.platform.ai.agent.sessions.core` end
### auto-generated section `iml intellij.platform.ai.agent.sessions.core` start
exports_files(
[
"intellij.platform.ai.agent.sessions.core.iml",
],
visibility = ["//visibility:public"],
)
### auto-generated section `iml intellij.platform.ai.agent.sessions.core` end
@@ -1,740 +0,0 @@
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadActivityPresentationUpdate
- f:activityReport:com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- f:path:java.lang.String
- f:threadId:java.lang.String
- f:updatedAt:java.lang.Long
- <init>(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):V
- b:<init>(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():java.lang.String
- f:component3():com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- f:component4():java.lang.Long
- f:copy(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadActivityPresentationUpdate
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadActivityPresentationUpdate,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadActivityPresentationUpdate
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadIdsKt
- sf:isAgentSessionPendingThreadId(java.lang.String):Z
- sf:normalizeConcreteAgentSessionThreadId(java.lang.String):java.lang.String
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentation
- f:activityReport:com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- f:title:java.lang.String
- f:updatedAt:java.lang.Long
- <init>(java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):V
- b:<init>(java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- f:component3():java.lang.Long
- f:copy(java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentation
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentation,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentation
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
- sf:Companion:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet$Companion
- f:changedKeys:java.util.Set
- f:removedKeys:java.util.Set
- <init>(java.util.Set,java.util.Set):V
- f:component1():java.util.Set
- f:component2():java.util.Set
- f:copy(java.util.Set,java.util.Set):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet,java.util.Set,java.util.Set,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
- equals(java.lang.Object):Z
- hashCode():I
- f:isEmpty():Z
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet$Companion
- f:getEMPTY():com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationKey
- sf:Companion:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationKey$Companion
- f:normalizedProjectPath:java.lang.String
- f:threadId:java.lang.String
- f:component1():java.lang.String
- f:component2-dBzBBrw():java.lang.String
- f:component3():java.lang.String
- f:copy-vFUIKBM(java.lang.String,java.lang.String,java.lang.String):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationKey
- bs:copy-vFUIKBM$default(com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationKey,java.lang.String,java.lang.String,java.lang.String,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationKey
- equals(java.lang.Object):Z
- f:getProvider-dBzBBrw():java.lang.String
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationKey$Companion
- f:create-vFUIKBM(java.lang.String,java.lang.String,java.lang.String):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationKey
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationKt
- sf:formatAgentSessionRelativeTimeShort(J,J,java.lang.String,java.lang.String):java.lang.String
- sf:formatAgentSessionThreadTitle(java.lang.String,java.lang.String,kotlin.jvm.functions.Function1):java.lang.String
- sf:formatCompactAgentSessionThreadTitle(java.lang.String,java.lang.String,kotlin.jvm.functions.Function1,I):java.lang.String
- bs:formatCompactAgentSessionThreadTitle$default(java.lang.String,java.lang.String,kotlin.jvm.functions.Function1,I,I,java.lang.Object):java.lang.String
- sf:formatCompactAgentSessionTitle(java.lang.String,I):java.lang.String
- bs:formatCompactAgentSessionTitle$default(java.lang.String,I,I,java.lang.Object):java.lang.String
- sf:normalizeAgentSessionTitle(java.lang.String):java.lang.String
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationModel
- <init>():V
- f:clearForTests():V
- f:getChanges():kotlinx.coroutines.flow.SharedFlow
- f:getState():kotlinx.coroutines.flow.StateFlow
- f:remove(java.util.Set):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
- f:replaceForTests(java.util.Map):V
- f:resolve(com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationKey):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentation
- f:snapshot():java.util.Map
- f:updateActivityHints-9c6lQVA(java.lang.String,java.util.Collection):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
- f:updatePresentationHints-9c6lQVA(java.lang.String,java.util.Collection):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
- f:updateProviderSnapshot-3n3rsyc(java.lang.String,java.util.Set,java.util.Map):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
- f:updateThread-RmkVQ7I(java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
- bs:updateThread-RmkVQ7I$default(com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationModel,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationPatchUpdate
- f:activityReport:com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- f:path:java.lang.String
- f:threadId:java.lang.String
- f:title:java.lang.String
- f:updatedAt:java.lang.Long
- <init>(java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):V
- b:<init>(java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():java.lang.String
- f:component3():java.lang.String
- f:component4():com.intellij.platform.ai.agent.core.AgentThreadActivityReport
- f:component5():java.lang.Long
- f:copy(java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationPatchUpdate
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationPatchUpdate,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationPatchUpdate
- equals(java.lang.Object):Z
- hashCode():I
com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
f:com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$MoreProjects
- com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
- f:hiddenCount:I
- <init>(I):V
- f:component1():I
- f:copy(I):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$MoreProjects
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$MoreProjects,I,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$MoreProjects
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$MoreThreads
- com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
- f:hiddenCount:java.lang.Integer
- f:path:java.lang.String
- <init>(java.lang.String,java.lang.Integer):V
- f:component1():java.lang.String
- f:component2():java.lang.Integer
- f:copy(java.lang.String,java.lang.Integer):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$MoreThreads
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$MoreThreads,java.lang.String,java.lang.Integer,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$MoreThreads
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Project
- com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
- f:isOpen:Z
- f:path:java.lang.String
- <init>(java.lang.String,Z):V
- f:component1():java.lang.String
- f:component2():Z
- f:copy(java.lang.String,Z):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Project
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Project,java.lang.String,Z,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Project
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$SubAgent
- com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$ThreadTarget
- f:parentThreadId:java.lang.String
- f:subAgent:com.intellij.platform.ai.agent.core.session.AgentSubAgent
- f:subAgentId:java.lang.String
- f:thread:com.intellij.platform.ai.agent.core.session.AgentSessionThread
- b:<init>(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionThread,com.intellij.platform.ai.agent.core.session.AgentSubAgent,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2-dBzBBrw():java.lang.String
- f:component3():java.lang.String
- f:component4():java.lang.String
- f:component5():java.lang.String
- f:component6():com.intellij.platform.ai.agent.core.session.AgentSessionThread
- f:component7():com.intellij.platform.ai.agent.core.session.AgentSubAgent
- f:copy-AlqNjB4(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionThread,com.intellij.platform.ai.agent.core.session.AgentSubAgent):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$SubAgent
- bs:copy-AlqNjB4$default(com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$SubAgent,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionThread,com.intellij.platform.ai.agent.core.session.AgentSubAgent,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$SubAgent
- equals(java.lang.Object):Z
- getPath():java.lang.String
- getProvider-dBzBBrw():java.lang.String
- getThreadId():java.lang.String
- getTitle():java.lang.String
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Thread
- com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$ThreadTarget
- b:<init>(java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionThread,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2-dBzBBrw():java.lang.String
- f:component3():java.lang.String
- f:component4():java.lang.String
- f:component5():com.intellij.platform.ai.agent.core.session.AgentSessionThread
- f:copy-TrybPGk(java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionThread):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Thread
- bs:copy-TrybPGk$default(com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Thread,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionThread,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Thread
- equals(java.lang.Object):Z
- getPath():java.lang.String
- getProvider-dBzBBrw():java.lang.String
- f:getThread():com.intellij.platform.ai.agent.core.session.AgentSessionThread
- getThreadId():java.lang.String
- getTitle():java.lang.String
- hashCode():I
com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$ThreadTarget
- com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
- a:getPath():java.lang.String
- a:getProvider-dBzBBrw():java.lang.String
- a:getThreadId():java.lang.String
- a:getTitle():java.lang.String
f:com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Worktree
- com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
- f:path:java.lang.String
- <init>(java.lang.String):V
- f:component1():java.lang.String
- f:copy(java.lang.String):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Worktree
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Worktree,java.lang.String,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Worktree
- equals(java.lang.Object):Z
- hashCode():I
com.intellij.platform.ai.agent.sessions.core.config.AgentWorkbenchProjectRuntimeConfigProvider
- a:isRefreshVfsOnStatusUpdatesEnabled(java.lang.String):Z
f:com.intellij.platform.ai.agent.sessions.core.config.AgentWorkbenchProjectRuntimeConfigs
- sf:INSTANCE:com.intellij.platform.ai.agent.sessions.core.config.AgentWorkbenchProjectRuntimeConfigs
- f:isRefreshVfsOnStatusUpdatesEnabled(java.lang.String):Z
com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageCostCalculator
- a:calculateCost(com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageSnapshot):com.intellij.platform.ai.agent.core.session.AgentSessionCost
f:com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageCostCalculators
- sf:INSTANCE:com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageCostCalculators
- f:calculateCost(com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageSnapshot):com.intellij.platform.ai.agent.core.session.AgentSessionCost
- f:getEP_NAME():com.intellij.openapi.extensions.ExtensionPointName
f:com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageSnapshot
- f:cacheReadTokens:J
- f:cacheWrite1hTokens:J
- f:cacheWrite5mTokens:J
- f:cacheWriteTokens:J
- f:inputTokens:J
- f:modelId:java.lang.String
- f:nativeExactCostUsd:java.math.BigDecimal
- f:outputTokens:J
- f:reasoningTokens:J
- f:requestCount:J
- <init>(java.lang.String,J,J,J,J,J,J,J,J,java.math.BigDecimal):V
- b:<init>(java.lang.String,J,J,J,J,J,J,J,J,java.math.BigDecimal,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component10():java.math.BigDecimal
- f:component2():J
- f:component3():J
- f:component4():J
- f:component5():J
- f:component6():J
- f:component7():J
- f:component8():J
- f:component9():J
- f:copy(java.lang.String,J,J,J,J,J,J,J,J,java.math.BigDecimal):com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageSnapshot
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageSnapshot,java.lang.String,J,J,J,J,J,J,J,J,java.math.BigDecimal,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageSnapshot
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageSnapshotKt
- sf:aggregateAgentSessionUsageCost(java.util.List,kotlin.jvm.functions.Function1):com.intellij.platform.ai.agent.core.session.AgentSessionCost
com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchContributor
- a:contribute-rVRrr4o(java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,kotlin.coroutines.Continuation):java.lang.Object
f:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchContributors
- sf:INSTANCE:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchContributors
- f:applyAll-rVRrr4o(java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,kotlin.coroutines.Continuation):java.lang.Object
- bs:applyAll-rVRrr4o$default(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchContributors,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,kotlin.coroutines.Continuation,I,java.lang.Object):java.lang.Object
f:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent
- f:generationSettings:com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings
- f:launchMode:com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
- f:launchTargetId:java.lang.String
- f:operation:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchOperation
- f:projectDirectory:java.lang.String
- f:projectPath:java.lang.String
- f:sessionId:java.lang.String
- b:<init>(java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchOperation,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode,java.lang.String,java.lang.String,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():java.lang.String
- f:component3-dBzBBrw():java.lang.String
- f:component4():com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchOperation
- f:component5():java.lang.String
- f:component6():com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
- f:component7():java.lang.String
- f:component8-fi09RKo():java.lang.String
- f:component9():com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings
- f:copy-QUbC3BQ(java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchOperation,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode,java.lang.String,java.lang.String,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings):com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent
- bs:copy-QUbC3BQ$default(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchOperation,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode,java.lang.String,java.lang.String,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent
- equals(java.lang.Object):Z
- f:getProvider-dBzBBrw():java.lang.String
- f:getSurfaceId-fi09RKo():java.lang.String
- hashCode():I
e:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchOperation
- java.lang.Enum
- sf:NEW:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchOperation
- sf:RESUME:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchOperation
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchOperation
- s:values():com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchOperation[]
f:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchPlanner
- sf:INSTANCE:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchPlanner
- f:plan(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent,com.intellij.openapi.project.Project,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan,java.util.List,java.util.Map,java.util.List,kotlin.jvm.functions.Function4,kotlin.coroutines.Continuation):java.lang.Object
- bs:plan$default(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchPlanner,com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent,com.intellij.openapi.project.Project,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan,java.util.List,java.util.Map,java.util.List,kotlin.jvm.functions.Function4,kotlin.coroutines.Continuation,I,java.lang.Object):java.lang.Object
com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchSpecAugmenter
- a:augment-tFgkW8w(java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,kotlin.coroutines.Continuation):java.lang.Object
f:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchSpecAugmenters
- sf:INSTANCE:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchSpecAugmenters
- f:augment-tFgkW8w(java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,kotlin.coroutines.Continuation):java.lang.Object
- bs:augment-tFgkW8w$default(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchSpecAugmenters,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,kotlin.coroutines.Continuation,I,java.lang.Object):java.lang.Object
- f:withAugmenterForTest(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchSpecAugmenter,kotlin.jvm.functions.Function1,kotlin.coroutines.Continuation):java.lang.Object
f:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchSpecs
- sf:INSTANCE:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchSpecs
- f:augment-tFgkW8w(java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,kotlin.coroutines.Continuation):java.lang.Object
- bs:augment-tFgkW8w$default(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchSpecs,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,kotlin.coroutines.Continuation,I,java.lang.Object):java.lang.Object
f:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchSpecsKt
- sf:insertArgumentsBefore(java.util.List,java.util.List,java.util.Set):java.util.List
- sf:removeOptions(java.util.List,java.util.Set,java.util.Set):java.util.List
- bs:removeOptions$default(java.util.List,java.util.Set,java.util.Set,I,java.lang.Object):java.util.List
- sf:replaceOrAddOption(java.util.List,java.lang.String,java.lang.String,java.util.Set):java.util.List
- bs:replaceOrAddOption$default(java.util.List,java.lang.String,java.lang.String,java.util.Set,I,java.lang.Object):java.util.List
com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunch
- sf:Companion:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunch$Companion
- handles(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunchContext):Z
- a:handles-e2Q3-TE(java.lang.String):Z
- a:launch(com.intellij.openapi.project.Project,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunchContext,java.lang.String,kotlin.coroutines.Continuation):java.lang.Object
f:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunch$Companion
- f:forContext(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunchContext):com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunch
- f:forProvider-e2Q3-TE(java.lang.String):com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunch
f:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunchContext
- f:generationSettings:com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings
- f:launchMode:com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
- f:launchProfileId:java.lang.String
- f:launchTargetId:java.lang.String
- f:component1-dBzBBrw():java.lang.String
- f:component2():com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
- f:component3():java.lang.String
- f:component4():java.lang.String
- f:component5-CYdv268():java.lang.String
- f:component6():com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings
- f:copy-NwT8XtU(java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode,java.lang.String,java.lang.String,java.lang.String,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings):com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunchContext
- bs:copy-NwT8XtU$default(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunchContext,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode,java.lang.String,java.lang.String,java.lang.String,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunchContext
- equals(java.lang.Object):Z
- f:getProvider-dBzBBrw():java.lang.String
- f:getSurfaceId-CYdv268():java.lang.String
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionPlannedLaunch
- f:baseLaunchSpec:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- f:generationModelCatalog:java.util.List
- f:intent:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent
- f:launchSpec:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- <init>(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,java.util.List):V
- f:component1():com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent
- f:component2():com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- f:component3():com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- f:component4():java.util.List
- f:copy(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,java.util.List):com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionPlannedLaunch
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionPlannedLaunch,com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchIntent,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,java.util.List,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionPlannedLaunch
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionThreadViewOpenPlan
- f:initialMessageDispatchPlan:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan
- f:launchSpec:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- f:runtimeThreadId:java.lang.String
- f:subAgentId:java.lang.String
- f:threadIdentity:java.lang.String
- f:threadTitle:java.lang.String
- <init>(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan):V
- b:<init>(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- f:component3():java.lang.String
- f:component4():java.lang.String
- f:component5():java.lang.String
- f:component6():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan
- f:copy(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan):com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionThreadViewOpenPlan
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionThreadViewOpenPlan,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionThreadViewOpenPlan
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionThreadViewOpenPlanKt
- sf:resolveAgentSessionThreadViewOpenPlan-e88xiWE(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionThread,com.intellij.platform.ai.agent.core.session.AgentSubAgent,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode,java.lang.String,java.lang.String,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings,com.intellij.openapi.project.Project,kotlin.jvm.functions.Function4,kotlin.coroutines.Continuation):java.lang.Object
- bs:resolveAgentSessionThreadViewOpenPlan-e88xiWE$default(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionThread,com.intellij.platform.ai.agent.core.session.AgentSubAgent,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode,java.lang.String,java.lang.String,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings,com.intellij.openapi.project.Project,kotlin.jvm.functions.Function4,kotlin.coroutines.Continuation,I,java.lang.Object):java.lang.Object
f:com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfig
- <init>(java.util.Set,kotlin.jvm.functions.Function1):V
- f:component1():java.util.Set
- f:component2():kotlin.jvm.functions.Function1
- f:copy(java.util.Set,kotlin.jvm.functions.Function1):com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfig
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfig,java.util.Set,kotlin.jvm.functions.Function1,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfig
- equals(java.lang.Object):Z
- f:getBuildCliArgs():kotlin.jvm.functions.Function1
- f:getMcpServerNames():java.util.Set
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigBuilder
- sf:AWB_DIR:java.lang.String
- sf:INSTANCE:com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigBuilder
- sf:PROJECT_PATH_ENV:java.lang.String
- f:buildForLaunch-_8s3iU4(java.nio.file.Path,java.lang.String):com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigBuilder$LaunchConfig
- f:configFilePath(java.nio.file.Path):java.nio.file.Path
- f:getLEGACY_FILTERED_NAMES():java.util.Set
- f:isDirectHttpEnabled():Z
- f:isEnabled-e2Q3-TE(java.lang.String):Z
- f:writeMergedConfigFile(java.nio.file.Path,java.lang.String,java.util.Set,java.util.List):java.nio.file.Path
- bs:writeMergedConfigFile$default(com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigBuilder,java.nio.file.Path,java.lang.String,java.util.Set,java.util.List,I,java.lang.Object):java.nio.file.Path
f:com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigBuilder$LaunchConfig
- <init>(java.util.List,java.util.Map,java.nio.file.Path):V
- f:component1():java.util.List
- f:component2():java.util.Map
- f:component3():java.nio.file.Path
- f:copy(java.util.List,java.util.Map,java.nio.file.Path):com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigBuilder$LaunchConfig
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigBuilder$LaunchConfig,java.util.List,java.util.Map,java.nio.file.Path,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigBuilder$LaunchConfig
- equals(java.lang.Object):Z
- f:getConfigFile():java.nio.file.Path
- f:getEnvVariables():java.util.Map
- f:getExtraArgs():java.util.List
- hashCode():I
com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigProviderContributor
- sf:Companion:com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigProviderContributor$Companion
- contribute-e2Q3-TE(java.lang.String):com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfig
- a:getFilteredServerNames():java.util.Set
f:com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigProviderContributor$Companion
- f:getEP():com.intellij.openapi.extensions.ExtensionPointName
com.intellij.platform.ai.agent.sessions.core.launch.McpStreamUrlProvider
- sf:Companion:com.intellij.platform.ai.agent.sessions.core.launch.McpStreamUrlProvider$Companion
- a:getStreamUrl():java.lang.String
f:com.intellij.platform.ai.agent.sessions.core.launch.McpStreamUrlProvider$Companion
- f:resolve():java.lang.String
e:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchAction
- java.lang.Enum
- sf:PROVIDER:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchAction
- sf:SEND_TEXT:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchAction
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchAction
- s:values():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchAction[]
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchStep
- f:action:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchAction
- f:recordsPrompt:Z
- f:text:java.lang.String
- f:timeoutPolicy:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy
- <init>():V
- <init>(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchAction,Z):V
- b:<init>(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchAction,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy
- f:component3():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchAction
- f:component4():Z
- f:copy(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchAction,Z):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchStep
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchStep,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchAction,Z,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageDispatchStep
- equals(java.lang.Object):Z
- hashCode():I
- f:isDispatchable():Z
e:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode
- java.lang.Enum
- sf:PLAN:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode
- sf:STANDARD:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode
- s:values():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode[]
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan
- sf:Companion:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan$Companion
- sf:EMPTY:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan
- f:message:java.lang.String
- f:mode:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode
- f:startupPolicy:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy
- f:timeoutPolicy:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy
- <init>(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy):V
- b:<init>(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode
- f:component3():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy
- f:component4():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy
- f:copy(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan$Companion
- f:composeDefault(com.intellij.agent.workbench.prompt.core.AgentPromptInitialMessageRequest):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageProviderDispatchRequest
- f:generationSettings:com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings
- f:message:java.lang.String
- f:mode:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode
- f:project:com.intellij.openapi.project.Project
- f:projectPath:java.lang.String
- f:threadId:java.lang.String
- <init>(com.intellij.openapi.project.Project,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings):V
- f:component1():com.intellij.openapi.project.Project
- f:component2():java.lang.String
- f:component3():java.lang.String
- f:component4():java.lang.String
- f:component5():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode
- f:component6():com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings
- f:copy(com.intellij.openapi.project.Project,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageProviderDispatchRequest
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageProviderDispatchRequest,com.intellij.openapi.project.Project,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageProviderDispatchRequest
- equals(java.lang.Object):Z
- hashCode():I
e:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy
- java.lang.Enum
- sf:POST_START_ONLY:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy
- sf:TRY_STARTUP_COMMAND:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy
- s:values():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy[]
e:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy
- java.lang.Enum
- sf:ALLOW_TIMEOUT_FALLBACK:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy
- sf:REQUIRE_EXPLICIT_READINESS:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy
- s:values():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageTimeoutPolicy[]
e:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel
- java.lang.Enum
- sf:APP_SERVER:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel
- sf:STARTUP_COMMAND:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel
- sf:TERMINAL:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel
- s:values():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel[]
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan
- sf:Companion:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan$Companion
- sf:EMPTY:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan
- f:promptRecord:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptRecord
- f:startupFallbackTerminalDispatch:com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch
- f:startupLaunchSpecOverride:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- f:terminalDispatch:com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch
- <init>():V
- <init>(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptRecord,com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch,com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch):V
- b:<init>(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptRecord,com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch,com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- <init>(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,java.util.List,java.lang.String):V
- b:<init>(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,java.util.List,java.lang.String,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- f:component2():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptRecord
- f:component3():com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch
- f:component4():com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch
- f:copy(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptRecord,com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch,com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptRecord,com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch,com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan
- equals(java.lang.Object):Z
- f:getInitialMessageToken():java.lang.String
- f:getPostStartDispatchSteps():java.util.List
- hashCode():I
- f:withStartupDeliveryIgnored():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan$Companion
e:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryStatus
- java.lang.Enum
- sf:DELIVERED:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryStatus
- sf:PENDING:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryStatus
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryStatus
- s:values():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryStatus[]
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptRecord
- f:deliveryChannel:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel
- f:deliveryStatus:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryStatus
- f:message:java.lang.String
- f:mode:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode
- f:token:java.lang.String
- <init>(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryStatus,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel):V
- b:<init>(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryStatus,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode
- f:component3():java.lang.String
- f:component4():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryStatus
- f:component5():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel
- f:copy(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryStatus,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptRecord
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptRecord,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageMode,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryStatus,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryChannel,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptRecord
- equals(java.lang.Object):Z
- hashCode():I
com.intellij.platform.ai.agent.sessions.core.providers.AgentOpenTopLevelThreadDispatchService
- a:dispatchIfPresent-RmkVQ7I(java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan,kotlin.coroutines.Continuation):java.lang.Object
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentPendingSessionMetadata
- f:createdAtMs:J
- f:launchMode:java.lang.String
- <init>(J,java.lang.String):V
- f:component1():J
- f:component2():java.lang.String
- f:copy(J,java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentPendingSessionMetadata
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentPendingSessionMetadata,J,java.lang.String,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentPendingSessionMetadata
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentPrestartNewSessionLaunchRequest
- f:generationModelCatalog:java.util.List
- f:generationSettings:com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings
- f:initialMessagePlan:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan
- f:launchMode:com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
- f:launchSpec:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- f:projectPath:java.lang.String
- <init>(java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings,java.util.List,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec):V
- f:component1():java.lang.String
- f:component2():com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
- f:component3():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan
- f:component4():com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings
- f:component5():java.util.List
- f:component6():com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- f:copy(java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings,java.util.List,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec):com.intellij.platform.ai.agent.sessions.core.providers.AgentPrestartNewSessionLaunchRequest
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentPrestartNewSessionLaunchRequest,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan,com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings,java.util.List,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentPrestartNewSessionLaunchRequest
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentPrestartedNewSessionLaunch
- f:initialMessageDispatchPlan:com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan
- f:launchSpec:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- <init>(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan):V
- b:<init>(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- f:component2():com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan
- f:copy(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan):com.intellij.platform.ai.agent.sessions.core.providers.AgentPrestartedNewSessionLaunch
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentPrestartedNewSessionLaunch,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialPromptDeliveryPlan,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentPrestartedNewSessionLaunch
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOption
- f:defaultSelected:Z
- f:enabledForExistingTask:Z
- f:enabledForNewTask:Z
- f:id:java.lang.String
- f:labelFallback:java.lang.String
- f:labelKey:java.lang.String
- <init>(java.lang.String,java.lang.String,java.lang.String,Z,Z,Z):V
- b:<init>(java.lang.String,java.lang.String,java.lang.String,Z,Z,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():java.lang.String
- f:component3():java.lang.String
- f:component4():Z
- f:component5():Z
- f:component6():Z
- f:copy(java.lang.String,java.lang.String,java.lang.String,Z,Z,Z):com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOption
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOption,java.lang.String,java.lang.String,java.lang.String,Z,Z,Z,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOption
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOptionKt
- sf:AGENT_PROMPT_PROVIDER_OPTION_PLAN_MODE:java.lang.String
- sf:getAGENT_PROMPT_PROVIDER_PLAN_MODE_OPTION():com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOption
e:com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOptionTarget
- java.lang.Enum
- sf:EXISTING_TASK:com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOptionTarget
- sf:NEW_TASK:com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOptionTarget
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOptionTarget
- s:values():com.intellij.platform.ai.agent.sessions.core.providers.AgentPromptProviderOptionTarget[]
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionLaunchProfileSnapshot
- f:builtInProfiles:java.util.List
- f:userProfiles:java.util.List
- <init>(java.util.List,java.util.List):V
- f:component1():java.util.List
- f:component2():java.util.List
- f:copy(java.util.List,java.util.List):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionLaunchProfileSnapshot
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionLaunchProfileSnapshot,java.util.List,java.util.List,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionLaunchProfileSnapshot
- equals(java.lang.Object):Z
- f:getAllProfiles():java.util.List
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionLaunchProfilesKt
- sf:BUILT_IN_LAUNCH_PROFILE_PREFIX:java.lang.String
- sf:builtInLaunchProfileId-9c6lQVA(java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode):java.lang.String
- sf:effectiveLaunchProfiles(java.util.List,java.util.List):java.util.List
- sf:generationSettingsForPlanMode(com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings,Z):com.intellij.agent.workbench.prompt.core.AgentPromptGenerationSettings
- sf:initialMessageRequestForLaunchProfile(com.intellij.agent.workbench.prompt.core.AgentPromptLaunchProfile):com.intellij.agent.workbench.prompt.core.AgentPromptInitialMessageRequest
- sf:launchProfileEditablePayload(com.intellij.agent.workbench.prompt.core.AgentPromptLaunchProfile):com.intellij.agent.workbench.prompt.core.AgentPromptLaunchProfile
- sf:launchProfileMatchesBuiltIn(com.intellij.agent.workbench.prompt.core.AgentPromptLaunchProfile,com.intellij.agent.workbench.prompt.core.AgentPromptLaunchProfile):Z
- sf:normalizedUserLaunchProfile(com.intellij.agent.workbench.prompt.core.AgentPromptLaunchProfile):com.intellij.agent.workbench.prompt.core.AgentPromptLaunchProfile
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionMenuCommand
- f:argumentHint:java.lang.String
- f:command:java.lang.String
- <init>(java.lang.String,java.lang.String):V
- b:<init>(java.lang.String,java.lang.String,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():java.lang.String
- f:copy(java.lang.String,java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionMenuCommand
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionMenuCommand,java.lang.String,java.lang.String,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionMenuCommand
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionEntry
- f:argumentHint:java.lang.String
- f:command:java.lang.String
- f:kind:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind
- f:sourceKey:java.lang.String
- f:sourcePath:java.nio.file.Path
- <init>(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind,java.lang.String,java.nio.file.Path,java.lang.String):V
- b:<init>(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind,java.lang.String,java.nio.file.Path,java.lang.String,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component2():com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind
- f:component3():java.lang.String
- f:component4():java.nio.file.Path
- f:component5():java.lang.String
- f:copy(java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind,java.lang.String,java.nio.file.Path,java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionEntry
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionEntry,java.lang.String,com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind,java.lang.String,java.nio.file.Path,java.lang.String,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionEntry
- equals(java.lang.Object):Z
- hashCode():I
e:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind
- java.lang.Enum
- sf:COMMAND:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind
- sf:MENU:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind
- sf:SKILL:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind
- s:values():com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionPromptCommandCompletionKind[]
e:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderCliVisibilityPolicy
- java.lang.Enum
- sf:DISCOVER_WHEN_AVAILABLE:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderCliVisibilityPolicy
- sf:PROMINENT:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderCliVisibilityPolicy
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderCliVisibilityPolicy
- s:values():com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderCliVisibilityPolicy[]
com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderFeatureSettings
- isProviderFeatureEnabled-9c6lQVA(java.lang.String,java.lang.String):Z
- setProviderFeatureEnabled-3n3rsyc(java.lang.String,java.lang.String,Z):V
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderFeatureSettingsExtensions
- sf:INSTANCE:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderFeatureSettingsExtensions
- f:getEP_NAME():com.intellij.openapi.extensions.ExtensionPointName
- f:isProviderFeatureEnabled-9c6lQVA(java.lang.String,java.lang.String):Z
- f:setProviderFeatureEnabled-3n3rsyc(java.lang.String,java.lang.String,Z):V
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderPlanModeKt
- sf:buildPlanModeInitialMessagePlan(com.intellij.agent.workbench.prompt.core.AgentPromptInitialMessageRequest,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan
- bs:buildPlanModeInitialMessagePlan$default(com.intellij.agent.workbench.prompt.core.AgentPromptInitialMessageRequest,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy,com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessageStartupPolicy,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan
- sf:isBlockedForExistingThreadPlanMode(com.intellij.platform.ai.agent.sessions.core.providers.AgentInitialMessagePlan,com.intellij.platform.ai.agent.core.AgentThreadActivity):Z
- sf:isBusyForExistingThreadPlanMode(com.intellij.platform.ai.agent.core.AgentThreadActivity):Z
- sf:isPlanModeRequested(com.intellij.agent.workbench.prompt.core.AgentPromptInitialMessageRequest):Z
com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderUiContributor
- createToolWindowNorthComponent(com.intellij.openapi.project.Project):javax.swing.JComponent
- onThreadViewOpened():V
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderUiContributorBean
- com.intellij.serviceContainer.BaseKeyedLazyInstance
- implementation:java.lang.String
- providerId:java.lang.String
- <init>():V
- f:matches-e2Q3-TE(java.lang.String):Z
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderUiContributors
- sf:INSTANCE:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderUiContributors
- f:forProvider-e2Q3-TE(java.lang.String):java.util.List
- f:getEP_NAME():com.intellij.openapi.extensions.ExtensionPointName
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- f:command:java.util.List
- f:containerSessionId:java.lang.String
- f:envVariables:java.util.Map
- f:preallocatedSessionId:java.lang.String
- f:useTerminalDefaultShell:Z
- f:workingDirectory:java.lang.String
- <init>(java.util.List,java.util.Map,java.lang.String,Z,java.lang.String,java.lang.String):V
- b:<init>(java.util.List,java.util.Map,java.lang.String,Z,java.lang.String,java.lang.String,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.util.List
- f:component2():java.util.Map
- f:component3():java.lang.String
- f:component4():Z
- f:component5():java.lang.String
- f:component6():java.lang.String
- f:copy(java.util.List,java.util.Map,java.lang.String,Z,java.lang.String,java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec,java.util.List,java.util.Map,java.lang.String,Z,java.lang.String,java.lang.String,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalLaunchSpec
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalRestoreContext
- f:workingDirectory:java.lang.String
- <init>():V
- <init>(java.lang.String):V
- b:<init>(java.lang.String,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:copy(java.lang.String):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalRestoreContext
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalRestoreContext,java.lang.String,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalRestoreContext
- equals(java.lang.Object):Z
- hashCode():I
f:com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch
- f:stepIndex:I
- f:steps:java.util.List
- <init>(java.util.List,I):V
- b:<init>(java.util.List,I,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.util.List
- f:component2():I
- f:copy(java.util.List,I):com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch,java.util.List,I,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch
- equals(java.lang.Object):Z
- hashCode():I
- f:normalized():com.intellij.platform.ai.agent.sessions.core.providers.AgentTerminalPromptDispatch
f:com.intellij.platform.ai.agent.sessions.core.providers.BaseAgentSessionSourceKt$sam$i$java_util_function_BiFunction$0
- java.util.function.BiFunction
- <init>(kotlin.jvm.functions.Function2):V
- bf:apply(java.lang.Object,java.lang.Object):java.lang.Object
f:com.intellij.platform.ai.agent.sessions.core.util.JbCentralCliSupport
- sf:INSTANCE:com.intellij.platform.ai.agent.sessions.core.util.JbCentralCliSupport
- f:findExecutable():java.lang.String
- f:isAvailable():Z
f:com.intellij.platform.ai.agent.sessions.core.util.JbCentralCliSupportKt
- sf:AGENT_WORKBENCH_JBCENTRAL_PATH_PROPERTY:java.lang.String
f:com.intellij.platform.ai.agent.sessions.core.util.JbCentralCliSupportTestHook
- sf:INSTANCE:com.intellij.platform.ai.agent.sessions.core.util.JbCentralCliSupportTestHook
- f:pathLookupOverride():kotlin.jvm.functions.Function0
- f:replacePathLookupForTest(kotlin.jvm.functions.Function0):kotlin.jvm.functions.Function0
@@ -1,5 +0,0 @@
com/intellij/agent/workbench/prompt/core/AgentPromptGenerationSettings
com/intellij/agent/workbench/prompt/core/AgentPromptInitialMessageRequest
com/intellij/agent/workbench/prompt/core/AgentPromptLaunchProfile
java/math/BigDecimal
kotlin/jvm/internal/DefaultConstructorMarker
@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="5" platform="JVM 25" allPlatforms="JVM [25]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-Xjvm-default=all -XXLanguage:+AllowEagerSupertypeAccessibilityChecks -progressive" />
</compilerSettings>
<compilerArguments>
<stringArguments>
<stringArg name="jvmTarget" arg="25" />
<stringArg name="apiVersion" arg="2.4" />
<stringArg name="languageVersion" arg="2.4" />
</stringArguments>
<arrayArguments>
<arrayArg name="pluginClasspaths">
<args>
<arg>$KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar</arg>
</args>
</arrayArg>
</arrayArguments>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="com.intellij.platform.ai.agent.sessions.core" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.libraries.kotlinx.serialization.core" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="module" module-name="intellij.platform.ide.util.io" />
<orderEntry type="module" module-name="intellij.platform.projectModel" />
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module" module-name="intellij.platform.util.ex" />
<orderEntry type="module" module-name="intellij.platform.util.ui" />
<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.libraries.jackson3" />
<orderEntry type="module" module-name="intellij.platform.ai.agent.json" />
</component>
</module>
@@ -1,72 +0,0 @@
<idea-plugin visibility="internal">
<!-- region Generated dependencies - run `Generate Product Layouts` to regenerate -->
<dependencies>
<module name="intellij.platform.ai.agent.core"/>
<module name="intellij.platform.ai.agent.json"/>
<module name="intellij.agent.workbench.prompt.core"/>
</dependencies>
<!-- endregion -->
<extensionPoints>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.sessionProvider"
interface="com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderDescriptor"
dynamic="true"/>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.sessionLaunchSpecAugmenter"
interface="com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchSpecAugmenter"
dynamic="true"/>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.projectRuntimeConfigProvider"
interface="com.intellij.platform.ai.agent.sessions.core.config.AgentWorkbenchProjectRuntimeConfigProvider"
dynamic="true"/>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.sessionLaunchContributor"
interface="com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionLaunchContributor"
dynamic="true"/>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.sessionOutOfBandLaunch"
interface="com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionOutOfBandLaunch"
dynamic="true"/>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.sessionLaunchProfileContributor"
interface="com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionLaunchProfileContributor"
dynamic="true"/>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.sessionSurfaceContributor"
interface="com.intellij.platform.ai.agent.sessions.core.launch.AgentSessionSurfaceContributor"
dynamic="true"/>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.mcpStreamUrlProvider"
interface="com.intellij.platform.ai.agent.sessions.core.launch.McpStreamUrlProvider"
dynamic="true"/>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.mcpConfigProviderContributor"
interface="com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigProviderContributor"
dynamic="true"/>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.sessionUsageCostCalculator"
interface="com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageCostCalculator"
dynamic="true"/>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.sessionProviderFeatureSettings"
interface="com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderFeatureSettings"
dynamic="true"/>
<extensionPoint
qualifiedName="com.intellij.agent.workbench.sessionProviderUiContributor"
beanClass="com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderUiContributorBean"
dynamic="true">
<with attribute="implementation" implements="com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderUiContributor"/>
</extensionPoint>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<registryKey key="agent.workbench.mcp.use.direct.http"
defaultValue="false"
description="When enabled, AWB writes a merged `&lt;projectPath&gt;/.awb/awb-mcp.json` containing the IDE's current streamable-HTTP MCP URL plus the user's other servers, and launches the agent CLI with `--mcp-config &lt;file&gt; --strict-mcp-config`. When disabled, AWB falls through to whatever the user's checked-in `.mcp.json` does (commonly the legacy ij-proxy stdio path)."/>
</extensions>
<extensions defaultExtensionNs="com.intellij.agent.workbench">
<sessionLaunchContributor implementation="com.intellij.platform.ai.agent.sessions.core.launch.AwbMcpConfigContributor"/>
</extensions>
</idea-plugin>
@@ -1,13 +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.platform.ai.agent.sessions.core
private const val PENDING_THREAD_ID_PREFIX = "new-"
fun isAgentSessionPendingThreadId(threadId: String): Boolean {
return threadId.trim().startsWith(PENDING_THREAD_ID_PREFIX)
}
fun normalizeConcreteAgentSessionThreadId(threadId: String): String? {
val normalizedThreadId = threadId.trim()
return normalizedThreadId.takeIf { it.isNotEmpty() && !isAgentSessionPendingThreadId(it) }
}
@@ -1,90 +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.platform.ai.agent.sessions.core
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.text.StringUtil
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToLong
private const val COMPACT_AGENT_SESSION_TITLE_LENGTH = 50
private val THREAD_TITLE_WHITESPACE = Regex("\\s+")
fun normalizeAgentSessionTitle(title: String?): @NlsSafe String? {
return title
?.replace('\n', ' ')
?.replace('\r', ' ')
?.replace(THREAD_TITLE_WHITESPACE, " ")
?.trim()
?.takeIf { it.isNotEmpty() }
}
fun formatCompactAgentSessionTitle(
title: String,
maxLength: Int = COMPACT_AGENT_SESSION_TITLE_LENGTH,
): @NlsSafe String {
return StringUtil.trimMiddle(title, maxLength)
}
fun formatCompactAgentSessionThreadTitle(
threadId: String,
title: String,
fallbackTitle: (idPrefix: String) -> String,
maxLength: Int = COMPACT_AGENT_SESSION_TITLE_LENGTH,
): @NlsSafe String {
return formatCompactAgentSessionTitle(
title = formatAgentSessionThreadTitle(threadId = threadId, title = title, fallbackTitle = fallbackTitle),
maxLength = maxLength,
)
}
fun formatAgentSessionThreadTitle(
threadId: String,
title: String,
fallbackTitle: (idPrefix: String) -> String,
): @NlsSafe String {
val normalized = normalizeAgentSessionTitle(title)
if (normalized != null) {
return normalized
}
val idPrefix = threadId.trim().takeIf { it.isNotEmpty() }?.take(8) ?: "unknown"
return fallbackTitle(idPrefix)
}
fun formatAgentSessionRelativeTimeShort(
timestamp: Long,
now: Long,
nowLabel: String,
unknownLabel: String,
): String {
if (timestamp <= 0L) {
return unknownLabel
}
val absSeconds = abs(((timestamp - now) / 1000.0).roundToLong())
if (absSeconds < 60) {
return nowLabel
}
if (absSeconds < 60 * 60) {
val value = max(1, (absSeconds / 60.0).roundToLong())
return "${value}m"
}
if (absSeconds < 60 * 60 * 24) {
val value = max(1, (absSeconds / (60.0 * 60.0)).roundToLong())
return "${value}h"
}
if (absSeconds < 60 * 60 * 24 * 7) {
val value = max(1, (absSeconds / (60.0 * 60.0 * 24.0)).roundToLong())
return "${value}d"
}
if (absSeconds < 60 * 60 * 24 * 30) {
val value = max(1, (absSeconds / (60.0 * 60.0 * 24.0 * 7.0)).roundToLong())
return "${value}w"
}
if (absSeconds < 60 * 60 * 24 * 365) {
val value = max(1, (absSeconds / (60.0 * 60.0 * 24.0 * 30.0)).roundToLong())
return "${value}mo"
}
val value = max(1, (absSeconds / (60.0 * 60.0 * 24.0 * 365.0)).roundToLong())
return "${value}y"
}
@@ -1,316 +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.platform.ai.agent.sessions.core
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
import com.intellij.platform.ai.agent.core.normalizeAgentWorkbenchPath
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
import com.intellij.openapi.components.Service
import com.intellij.openapi.util.NlsSafe
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import org.jetbrains.annotations.TestOnly
data class AgentSessionThreadPresentationKey(
@JvmField val normalizedProjectPath: String,
val provider: AgentSessionProvider,
@JvmField val threadId: String,
) {
companion object {
fun create(projectPath: String, provider: AgentSessionProvider, threadId: String): AgentSessionThreadPresentationKey? {
val normalizedProjectPath = normalizeAgentWorkbenchPath(projectPath).takeIf { it.isNotBlank() } ?: return null
val normalizedThreadId = threadId.trim().takeIf { it.isNotEmpty() } ?: return null
return AgentSessionThreadPresentationKey(
normalizedProjectPath = normalizedProjectPath,
provider = provider,
threadId = normalizedThreadId,
)
}
}
}
/**
* Shared thread presentation for editor/tab chrome and fresh title projection.
* Tool-window activity counters must use canonical [AgentSessionThread.activityReport].
*/
data class AgentSessionThreadPresentation(
@JvmField val title: @NlsSafe String,
@JvmField val activityReport: AgentThreadActivityReport,
@JvmField val updatedAt: Long? = null,
)
data class AgentSessionThreadPresentationChangeSet(
@JvmField val changedKeys: Set<AgentSessionThreadPresentationKey>,
@JvmField val removedKeys: Set<AgentSessionThreadPresentationKey>,
) {
companion object {
val EMPTY: AgentSessionThreadPresentationChangeSet = AgentSessionThreadPresentationChangeSet(
changedKeys = emptySet(),
removedKeys = emptySet(),
)
}
val isEmpty: Boolean
get() = changedKeys.isEmpty() && removedKeys.isEmpty()
}
data class AgentSessionThreadActivityPresentationUpdate(
@JvmField val path: String,
@JvmField val threadId: String,
@JvmField val activityReport: AgentThreadActivityReport,
@JvmField val updatedAt: Long? = null,
)
@Service(Service.Level.APP)
class AgentSessionThreadPresentationModel {
private val lock = Any()
private val mutableState = MutableStateFlow<Map<AgentSessionThreadPresentationKey, AgentSessionThreadPresentation>>(emptyMap())
private val mutableChanges = MutableSharedFlow<AgentSessionThreadPresentationChangeSet>(extraBufferCapacity = 64)
val state: StateFlow<Map<AgentSessionThreadPresentationKey, AgentSessionThreadPresentation>> = mutableState.asStateFlow()
val changes: SharedFlow<AgentSessionThreadPresentationChangeSet> = mutableChanges.asSharedFlow()
fun snapshot(): Map<AgentSessionThreadPresentationKey, AgentSessionThreadPresentation> = mutableState.value
fun resolve(key: AgentSessionThreadPresentationKey): AgentSessionThreadPresentation? = mutableState.value[key]
fun updateThread(
path: String,
provider: AgentSessionProvider,
threadId: String,
title: String,
activityReport: AgentThreadActivityReport? = null,
updatedAt: Long? = null,
): AgentSessionThreadPresentationChangeSet {
val key = AgentSessionThreadPresentationKey.create(projectPath = path, provider = provider, threadId = threadId)
?: return AgentSessionThreadPresentationChangeSet.EMPTY
val normalizedTitle = normalizeAgentSessionTitle(title) ?: title
return putMerged(mapOf(key to AgentSessionThreadPresentationPatch(title = normalizedTitle,
activityReport = activityReport,
updatedAt = updatedAt)))
}
fun updateActivityHints(
provider: AgentSessionProvider,
updates: Collection<AgentSessionThreadActivityPresentationUpdate>,
): AgentSessionThreadPresentationChangeSet {
if (updates.isEmpty()) {
return AgentSessionThreadPresentationChangeSet.EMPTY
}
return updatePresentationHints(
provider = provider,
updates = updates.map { update ->
AgentSessionThreadPresentationPatchUpdate(
path = update.path,
threadId = update.threadId,
activityReport = update.activityReport,
updatedAt = update.updatedAt,
)
},
)
}
fun updatePresentationHints(
provider: AgentSessionProvider,
updates: Collection<AgentSessionThreadPresentationPatchUpdate>,
): AgentSessionThreadPresentationChangeSet {
if (updates.isEmpty()) {
return AgentSessionThreadPresentationChangeSet.EMPTY
}
val inputs = LinkedHashMap<AgentSessionThreadPresentationKey, AgentSessionThreadPresentationPatch>(updates.size)
for ((path, threadId, title, activityReport, updatedAt) in updates) {
val key = AgentSessionThreadPresentationKey.create(projectPath = path, provider = provider, threadId = threadId) ?: continue
inputs[key] = AgentSessionThreadPresentationPatch(
title = normalizeAgentSessionTitle(title),
activityReport = activityReport,
updatedAt = updatedAt,
)
}
return putMerged(inputs)
}
fun updateProviderSnapshot(
provider: AgentSessionProvider,
authoritativePaths: Set<String>,
threadsByPath: Map<String, List<AgentSessionThread>>,
): AgentSessionThreadPresentationChangeSet {
if (authoritativePaths.isEmpty() && threadsByPath.isEmpty()) {
return AgentSessionThreadPresentationChangeSet.EMPTY
}
val normalizedAuthoritativePaths = authoritativePaths
.asSequence()
.map(::normalizeAgentWorkbenchPath)
.filter(String::isNotBlank)
.toCollection(LinkedHashSet())
val presentationsByKey = LinkedHashMap<AgentSessionThreadPresentationKey, AgentSessionThreadPresentation>()
for ((path, threads) in threadsByPath) {
threads.forEach { thread ->
if (thread.provider != provider) return@forEach
val key =
AgentSessionThreadPresentationKey.create(projectPath = path, provider = thread.provider, threadId = thread.id) ?: return@forEach
presentationsByKey[key] = AgentSessionThreadPresentation(
title = normalizeAgentSessionTitle(thread.title) ?: thread.title,
activityReport = thread.activityReport,
updatedAt = thread.updatedAt,
)
}
}
return updateState { current ->
val next = LinkedHashMap<AgentSessionThreadPresentationKey, AgentSessionThreadPresentation>(current.size + presentationsByKey.size)
val changedKeys = LinkedHashSet<AgentSessionThreadPresentationKey>()
val removedKeys = LinkedHashSet<AgentSessionThreadPresentationKey>()
for ((key, presentation) in current) {
if (key.provider == provider && key.normalizedProjectPath in normalizedAuthoritativePaths && key !in presentationsByKey) {
removedKeys.add(key)
continue
}
next[key] = presentation
}
for ((key, presentation) in presentationsByKey) {
val mergedPresentation = mergePresentation(existing = next[key], incoming = presentation)
if (next[key] == mergedPresentation) continue
next[key] = mergedPresentation
changedKeys.add(key)
}
PresentationStateUpdate(next = next, changedKeys = changedKeys, removedKeys = removedKeys)
}
}
fun remove(keys: Set<AgentSessionThreadPresentationKey>): AgentSessionThreadPresentationChangeSet {
if (keys.isEmpty()) {
return AgentSessionThreadPresentationChangeSet.EMPTY
}
return updateState { current ->
val next = LinkedHashMap(current)
val removedKeys = LinkedHashSet<AgentSessionThreadPresentationKey>()
for (key in keys) {
if (next.remove(key) != null) {
removedKeys.add(key)
}
}
PresentationStateUpdate(next = next, changedKeys = emptySet(), removedKeys = removedKeys)
}
}
@TestOnly
fun replaceForTests(presentationsByKey: Map<AgentSessionThreadPresentationKey, AgentSessionThreadPresentation>) {
synchronized(lock) {
mutableState.value = LinkedHashMap(presentationsByKey)
}
}
@TestOnly
fun clearForTests() {
synchronized(lock) {
mutableState.value = emptyMap()
}
}
private fun putMerged(
updates: Map<AgentSessionThreadPresentationKey, AgentSessionThreadPresentationPatch>,
): AgentSessionThreadPresentationChangeSet {
if (updates.isEmpty()) return AgentSessionThreadPresentationChangeSet.EMPTY
return updateState { current ->
val next = LinkedHashMap(current)
val changedKeys = LinkedHashSet<AgentSessionThreadPresentationKey>()
for ((key, update) in updates) {
val existing = next[key]
val presentation = mergePresentation(existing = existing, patch = update)
if (existing == presentation) continue
next[key] = presentation
changedKeys.add(key)
}
PresentationStateUpdate(next = next, changedKeys = changedKeys, removedKeys = emptySet())
}
}
private fun updateState(transform: (Map<AgentSessionThreadPresentationKey, AgentSessionThreadPresentation>) -> PresentationStateUpdate): AgentSessionThreadPresentationChangeSet {
val changeSet = synchronized(lock) {
val update = transform(mutableState.value)
if (update.changedKeys.isEmpty() && update.removedKeys.isEmpty()) {
return@synchronized AgentSessionThreadPresentationChangeSet.EMPTY
}
mutableState.value = update.next
AgentSessionThreadPresentationChangeSet(
changedKeys = update.changedKeys,
removedKeys = update.removedKeys,
)
}
if (!changeSet.isEmpty) {
mutableChanges.tryEmit(changeSet)
}
return changeSet
}
}
data class AgentSessionThreadPresentationPatchUpdate(
@JvmField val path: String,
@JvmField val threadId: String,
@JvmField val title: String? = null,
@JvmField val activityReport: AgentThreadActivityReport? = null,
@JvmField val updatedAt: Long? = null,
)
private fun mergePresentation(
existing: AgentSessionThreadPresentation?,
incoming: AgentSessionThreadPresentation,
): AgentSessionThreadPresentation {
return mergePresentation(
existing = existing,
patch = AgentSessionThreadPresentationPatch(
title = incoming.title,
activityReport = incoming.activityReport,
updatedAt = incoming.updatedAt,
),
)
}
private fun mergePresentation(
existing: AgentSessionThreadPresentation?,
patch: AgentSessionThreadPresentationPatch,
): AgentSessionThreadPresentation {
val shouldApplyActivity = shouldApplyActivityUpdate(existingUpdatedAt = existing?.updatedAt, incomingUpdatedAt = patch.updatedAt)
return AgentSessionThreadPresentation(
title = patch.title ?: existing?.title.orEmpty(),
activityReport = if (shouldApplyActivity) {
patch.activityReport ?: existing?.activityReport ?: AgentThreadActivityReport.READY
}
else {
existing?.activityReport ?: AgentThreadActivityReport.READY
},
updatedAt = mergeUpdatedAt(existingUpdatedAt = existing?.updatedAt, incomingUpdatedAt = patch.updatedAt),
)
}
private fun shouldApplyActivityUpdate(existingUpdatedAt: Long?, incomingUpdatedAt: Long?): Boolean {
return existingUpdatedAt == null || incomingUpdatedAt == null || incomingUpdatedAt >= existingUpdatedAt
}
private fun mergeUpdatedAt(existingUpdatedAt: Long?, incomingUpdatedAt: Long?): Long? {
return when {
existingUpdatedAt == null -> incomingUpdatedAt
incomingUpdatedAt == null -> existingUpdatedAt
else -> maxOf(existingUpdatedAt, incomingUpdatedAt)
}
}
private data class PresentationStateUpdate(
@JvmField val next: Map<AgentSessionThreadPresentationKey, AgentSessionThreadPresentation>,
@JvmField val changedKeys: Set<AgentSessionThreadPresentationKey>,
@JvmField val removedKeys: Set<AgentSessionThreadPresentationKey>,
)
private data class AgentSessionThreadPresentationPatch(
@JvmField val title: String? = null,
@JvmField val activityReport: AgentThreadActivityReport? = null,
@JvmField val updatedAt: Long? = null,
)
@@ -1,36 +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.platform.ai.agent.sessions.core
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
object AgentSessionThreadRebindPolicy {
const val PENDING_THREAD_MATCH_PRE_WINDOW_MS: Long = 20_000L
const val PENDING_THREAD_MATCH_POST_WINDOW_MS: Long = 120_000L
const val PENDING_THREAD_NO_BASELINE_AUTO_BIND_MAX_AGE_MS: Long = PENDING_THREAD_MATCH_POST_WINDOW_MS
const val PENDING_THREAD_REFRESH_RETRY_INTERVAL_MS: Long = 2_000L
const val CONCRETE_NEW_THREAD_MATCH_POST_WINDOW_MS: Long = 30_000L
const val CONCRETE_NEW_THREAD_REBIND_MAX_AGE_MS: Long = CONCRETE_NEW_THREAD_MATCH_POST_WINDOW_MS
const val CONCRETE_NEW_THREAD_REFRESH_RETRY_INTERVAL_MS: Long = 2_000L
fun isConcreteNewThreadRebindAnchorActive(rebindRequestedAtMs: Long, currentTimeMs: Long): Boolean {
return currentTimeMs >= rebindRequestedAtMs &&
currentTimeMs - rebindRequestedAtMs < CONCRETE_NEW_THREAD_REBIND_MAX_AGE_MS
}
fun concreteNewThreadRefreshRetryDelayMs(
rebindRequestedAtMs: Long,
currentTimeMs: Long,
retryIntervalMs: Long = CONCRETE_NEW_THREAD_REFRESH_RETRY_INTERVAL_MS,
): Long? {
if (currentTimeMs < rebindRequestedAtMs) {
return null
}
val retryDeadlineMs = rebindRequestedAtMs + CONCRETE_NEW_THREAD_REBIND_MAX_AGE_MS
val remainingMs = retryDeadlineMs - currentTimeMs
if (remainingMs <= 0L) {
return null
}
return minOf(retryIntervalMs, remainingMs)
}
}
@@ -1,54 +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.platform.ai.agent.sessions.core
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
import com.intellij.platform.ai.agent.core.session.AgentSubAgent
sealed interface SessionActionTarget {
data class Project(
@JvmField val path: String,
@JvmField val isOpen: Boolean,
) : SessionActionTarget
data class Worktree(
@JvmField val path: String,
) : SessionActionTarget
sealed interface ThreadTarget : SessionActionTarget {
val path: String
val provider: AgentSessionProvider
val threadId: String
val title: String
}
data class Thread(
override val path: String,
override val provider: AgentSessionProvider,
override val threadId: String,
override val title: String,
val thread: AgentSessionThread? = null,
) : ThreadTarget
data class SubAgent(
override val path: String,
override val provider: AgentSessionProvider,
@JvmField val parentThreadId: String,
@JvmField val subAgentId: String,
override val title: String,
@JvmField val thread: AgentSessionThread? = null,
@JvmField val subAgent: AgentSubAgent? = null,
) : ThreadTarget {
override val threadId: String
get() = subAgentId
}
data class MoreProjects(
@JvmField val hiddenCount: Int,
) : SessionActionTarget
data class MoreThreads(
@JvmField val path: String,
@JvmField val hiddenCount: Int?,
) : SessionActionTarget
}
@@ -1,38 +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.platform.ai.agent.sessions.core.config
import com.intellij.platform.ai.agent.core.extensions.SingleExtensionPointResolver
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.extensions.ExtensionPointName
fun interface AgentWorkbenchProjectRuntimeConfigProvider {
fun isRefreshVfsOnStatusUpdatesEnabled(projectRoot: String): Boolean
}
private class AgentWorkbenchProjectRuntimeConfigProviderRegistry
private val LOG = logger<AgentWorkbenchProjectRuntimeConfigProviderRegistry>()
private val AGENT_WORKBENCH_PROJECT_RUNTIME_CONFIG_PROVIDER_EP: ExtensionPointName<AgentWorkbenchProjectRuntimeConfigProvider> =
ExtensionPointName("com.intellij.agent.workbench.projectRuntimeConfigProvider")
private val REGISTERED_PROVIDER = SingleExtensionPointResolver(
log = LOG,
extensionPoint = AGENT_WORKBENCH_PROJECT_RUNTIME_CONFIG_PROVIDER_EP,
unavailableMessage = "Agent Workbench project runtime config provider EP is unavailable in this context",
multipleExtensionsMessage = { providers ->
"Multiple Agent Workbench project runtime config providers registered; using first: ${providers.map { it::class.java.name }}"
},
)
object AgentWorkbenchProjectRuntimeConfigs {
fun isRefreshVfsOnStatusUpdatesEnabled(projectRoot: String): Boolean {
val provider = REGISTERED_PROVIDER.findFirstOrNull() ?: return true
return try {
provider.isRefreshVfsOnStatusUpdatesEnabled(projectRoot)
}
catch (t: Throwable) {
LOG.warn("Failed to resolve Agent Workbench project runtime config for $projectRoot", t)
true
}
}
}
@@ -1,63 +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.platform.ai.agent.sessions.core.cost
import com.intellij.platform.ai.agent.core.session.AgentSessionCost
import com.intellij.platform.ai.agent.core.session.AgentSessionCostKind
import com.intellij.openapi.extensions.ExtensionPointName
import java.math.BigDecimal
data class AgentSessionUsageSnapshot(
@JvmField val modelId: String?,
@JvmField val inputTokens: Long = 0,
@JvmField val outputTokens: Long = 0,
@JvmField val cacheReadTokens: Long = 0,
@JvmField val cacheWriteTokens: Long = 0,
@JvmField val cacheWrite5mTokens: Long = 0,
@JvmField val cacheWrite1hTokens: Long = 0,
@JvmField val reasoningTokens: Long = 0,
@JvmField val requestCount: Long = 0,
@JvmField val nativeExactCostUsd: BigDecimal? = null,
)
fun List<AgentSessionUsageSnapshot>.aggregateAgentSessionUsageCost(
calculateCost: (AgentSessionUsageSnapshot) -> AgentSessionCost,
): AgentSessionCost? {
if (isEmpty()) return null
val componentCosts = map(calculateCost)
if (componentCosts.any { it.amountUsd == null }) {
return AgentSessionCost(amountUsd = null, kind = AgentSessionCostKind.UNAVAILABLE)
}
val totalAmount = componentCosts.fold(BigDecimal.ZERO) { acc, cost ->
acc + checkNotNull(cost.amountUsd)
}
val kind = if (componentCosts.all { it.kind == AgentSessionCostKind.EXACT }) {
AgentSessionCostKind.EXACT
}
else {
AgentSessionCostKind.ESTIMATED
}
val matchedModelId = componentCosts.mapNotNull(AgentSessionCost::matchedModelId).distinct().singleOrNull()
return AgentSessionCost(amountUsd = totalAmount, kind = kind, matchedModelId = matchedModelId)
}
interface AgentSessionUsageCostCalculator {
fun calculateCost(usage: AgentSessionUsageSnapshot): AgentSessionCost
}
object AgentSessionUsageCostCalculators {
val EP_NAME: ExtensionPointName<AgentSessionUsageCostCalculator> =
ExtensionPointName("com.intellij.agent.workbench.sessionUsageCostCalculator")
fun calculateCost(usage: AgentSessionUsageSnapshot): AgentSessionCost {
val calculator = EP_NAME.extensionList.firstOrNull()
if (calculator != null) {
return calculator.calculateCost(usage)
}
usage.nativeExactCostUsd?.let { exactCost ->
return AgentSessionCost(amountUsd = exactCost, kind = AgentSessionCostKind.EXACT)
}
return AgentSessionCost(amountUsd = null, kind = AgentSessionCostKind.UNAVAILABLE)
}
}

Some files were not shown because too many files have changed in this diff Show More