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
@@ -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" />
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 `<projectPath>/.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 <file> --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)
|
||||
}
|
||||
}
|
||||