diff --git a/.idea/modules.xml b/.idea/modules.xml
index b45d65d478c1..b8d8fc8f0481 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -11,6 +11,7 @@
+
diff --git a/build/bazel-generated-file-list.txt b/build/bazel-generated-file-list.txt
index c8319df15fed..b744f8606c68 100644
--- a/build/bazel-generated-file-list.txt
+++ b/build/bazel-generated-file-list.txt
@@ -228,6 +228,7 @@ fleet/kernel
fleet/ktor/network/tls
fleet/lsp.protocol
fleet/multiplatform.shims
+fleet/preferences
fleet/reporting/api
fleet/reporting/shared
fleet/rhizomedb
diff --git a/fleet/preferences/BUILD.bazel b/fleet/preferences/BUILD.bazel
new file mode 100644
index 000000000000..addbc7418182
--- /dev/null
+++ b/fleet/preferences/BUILD.bazel
@@ -0,0 +1,30 @@
+### auto-generated section `build fleet.preferences` start
+load("//build:compiler-options.bzl", "create_kotlinc_options")
+load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_provided_library")
+
+create_kotlinc_options(
+ name = "custom_preferences",
+ x_consistent_data_class_copy_visibility = True,
+ x_context_parameters = True,
+ x_jvm_default = "all-compatibility",
+ x_lambdas = "class"
+)
+
+jvm_provided_library(
+ name = "fleet_util_multiplatform_provided",
+ lib = "//fleet/util/multiplatform"
+)
+
+jvm_library(
+ name = "preferences",
+ module_name = "fleet.preferences",
+ visibility = ["//visibility:public"],
+ srcs = glob(["srcCommonMain/**/*.kt", "srcCommonMain/**/*.java", "srcCommonMain/**/*.form", "srcJvmMain/**/*.kt", "srcJvmMain/**/*.java", "srcJvmMain/**/*.form"], allow_empty = True, exclude = ["**/module-info.java"]),
+ kotlinc_opts = ":custom_preferences",
+ deps = [
+ "@lib//:kotlin-stdlib",
+ "@lib//:kotlinx-io-core",
+ ":fleet_util_multiplatform_provided",
+ ]
+)
+### auto-generated section `build fleet.preferences` end
\ No newline at end of file
diff --git a/fleet/preferences/fleet.preferences.iml b/fleet/preferences/fleet.preferences.iml
new file mode 100644
index 000000000000..a30911748cbe
--- /dev/null
+++ b/fleet/preferences/fleet.preferences.iml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $MAVEN_REPOSITORY$/jetbrains/fleet/expects-compiler-plugin/2.2.20-0.1/expects-compiler-plugin-2.2.20-0.1.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fleet/preferences/gradlebuild/build.gradle.kts b/fleet/preferences/gradlebuild/build.gradle.kts
new file mode 100644
index 000000000000..a64a0aa96e99
--- /dev/null
+++ b/fleet/preferences/gradlebuild/build.gradle.kts
@@ -0,0 +1,72 @@
+// IMPORT__MARKER_START
+import fleet.buildtool.conventions.configureAtMostOneJvmTargetOrThrow
+import fleet.buildtool.conventions.withJavaSourceSet
+// IMPORT__MARKER_END
+plugins {
+ alias(libs.plugins.kotlin.multiplatform)
+ id("fleet.project-module-conventions")
+ id("fleet.toolchain-conventions")
+ id("fleet.module-publishing-conventions")
+ id("fleet.sdk-repositories-publishing-conventions")
+ id("fleet.open-source-module-conventions")
+ alias(libs.plugins.dokka)
+ // GRADLE_PLUGINS__MARKER_START
+ id("fleet-module")
+ alias(jps.plugins.expects)
+ // GRADLE_PLUGINS__MARKER_END
+}
+
+fleetModule {
+ module {
+ name = "fleet.preferences"
+ importedFromJps {}
+ }
+}
+
+@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
+kotlin {
+ sourceSets.wasmJsMain.dependencies {
+ implementation("org.jetbrains.kotlinx:kotlinx-browser:0.3")
+ }
+
+ // KOTLIN__MARKER_START
+ compilerOptions.freeCompilerArgs = listOf(
+ "-Xlambdas=class",
+ "-Xconsistent-data-class-copy-visibility",
+ "-Xcontext-parameters",
+ "-XXLanguage:+AllowEagerSupertypeAccessibilityChecks",
+ )
+ jvm {}
+ wasmJs {
+ browser {}
+ }
+ sourceSets.commonMain.configure { kotlin.srcDir(layout.projectDirectory.dir("../srcCommonMain")) }
+ sourceSets.commonMain.configure { resources.srcDir(layout.projectDirectory.dir("../resourcesCommonMain")) }
+ sourceSets.commonTest.configure { kotlin.srcDir(layout.projectDirectory.dir("../srcCommonTest")) }
+ sourceSets.commonTest.configure { resources.srcDir(layout.projectDirectory.dir("../resourcesCommonTest")) }
+ sourceSets.jvmMain.configure { kotlin.srcDir(layout.projectDirectory.dir("../srcJvmMain")) }
+ configureAtMostOneJvmTargetOrThrow { compilations.named("main") { withJavaSourceSet { javaSourceSet -> javaSourceSet.java.srcDir(layout.projectDirectory.dir("../srcJvmMain")) } } }
+ sourceSets.jvmMain.configure { resources.srcDir(layout.projectDirectory.dir("../resourcesJvmMain")) }
+ sourceSets.jvmTest.configure { kotlin.srcDir(layout.projectDirectory.dir("../srcJvmTest")) }
+ configureAtMostOneJvmTargetOrThrow { compilations.named("test") { withJavaSourceSet { javaSourceSet -> javaSourceSet.java.srcDir(layout.projectDirectory.dir("../srcJvmTest")) } } }
+ sourceSets.jvmTest.configure { resources.srcDir(layout.projectDirectory.dir("../resourcesJvmTest")) }
+ sourceSets.wasmJsMain.configure { kotlin.srcDir(layout.projectDirectory.dir("../srcWasmJsMain")) }
+ sourceSets.wasmJsMain.configure { resources.srcDir(layout.projectDirectory.dir("../resourcesWasmJsMain")) }
+ sourceSets.wasmJsTest.configure { kotlin.srcDir(layout.projectDirectory.dir("../srcWasmJsTest")) }
+ sourceSets.wasmJsTest.configure { resources.srcDir(layout.projectDirectory.dir("../resourcesWasmJsTest")) }
+ sourceSets.commonMain.dependencies {
+ implementation(jps.org.jetbrains.kotlin.kotlin.stdlib1993400674.get().let { "${it.group}:${it.name}:${it.version}" }) {
+ exclude(group = "org.jetbrains", module = "annotations")
+ }
+ implementation(jps.org.jetbrains.kotlinx.kotlinx.io.core.jvm479158162.get().let { "${it.group}:kotlinx-io-core:${it.version}" }) {
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
+ }
+ }
+ sourceSets.jvmMain.dependencies {
+ compileOnly(project(":fleet.util.multiplatform"))
+ }
+ sourceSets.wasmJsMain.dependencies {
+ implementation(project(":fleet.util.multiplatform"))
+ }
+ // KOTLIN__MARKER_END
+}
\ No newline at end of file
diff --git a/fleet/preferences/srcCommonMain/.pseudoCommonKotlinSourceSet b/fleet/preferences/srcCommonMain/.pseudoCommonKotlinSourceSet
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/fleet/preferences/srcCommonMain/fleet/preferences/FleetFromSourcesPaths.kt b/fleet/preferences/srcCommonMain/fleet/preferences/FleetFromSourcesPaths.kt
new file mode 100644
index 000000000000..af725e5183a3
--- /dev/null
+++ b/fleet/preferences/srcCommonMain/fleet/preferences/FleetFromSourcesPaths.kt
@@ -0,0 +1,64 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package fleet.preferences
+
+import kotlinx.io.IOException
+import kotlinx.io.files.Path
+import kotlinx.io.files.SystemFileSystem
+
+// Paths for resources while running Fleet from sources
+// None of its methods should be called on production call path
+object FleetFromSourcesPaths {
+ val isRunningFromSources: Boolean by lazy {
+ !isFleetDistributionMode && findRepositoryRoot() != null
+ }
+
+ val intellijProjectRoot: Path by lazy {
+ requireNotNull(findRepositoryRoot()) {
+ "Cannot find IntelliJ repository root"
+ }.let { SystemFileSystem.resolve(it) }
+ }
+
+ val projectRoot: Path by lazy {
+ Path(intellijProjectRoot, "fleet")
+ }
+
+ // TODO: remove the usages of this property and delete it.
+ // Ideally `skiko.library.path` should always be set by the tooling or distribution argfile, making `skikoLibraryDirectory` property redundant.
+ // Currently, this is needed for GalleryApp and some isolated UI tests.
+ // Once our test running logic is unified, we will be able to wire the proper preparation steps to test runs avoiding such code.
+ val skikoLibraryDirectory: Path by lazy {
+ val skiko = Path(projectRoot, "build/fleet-skiko/build/skiko/buildPlatform")
+ require(skiko.takeIf { SystemFileSystem.exists(it) }?.let { SystemFileSystem.list(it) }?.isNotEmpty() == true) {
+ """
+ '$skiko' is empty or does not exist.
+
+ Usually, this directory is automatically populated when required. However a few use cases are outside of the normal test flow.
+ If you are in such case (standalone noria UI tests, etc.), you should either:
+ - run `./fleet.sh :fleet-skiko:downloadSkikoForJps` Gradle command
+ - or, set the JVM system property `skiko.library.path` to a valid skiko downloaded on your machine
+
+ If you ran through a JPS configuration, probably it is misconfigured, please contact #fleet-platform.
+ If you ran through the Fleet Gradle build tooling, probably it is misconfigured, please contact #fleet-platform.
+ """.trimIndent()
+ }
+ skiko
+ }
+
+ private fun findRepositoryRoot(): Path? {
+ var directory: Path? = findFleetRootByClass()
+ while (directory != null) {
+ if (directory.name != "community") {
+ try {
+ val children = SystemFileSystem.list(directory).map(Path::name).toSet()
+ if (children.contains(".idea") && children.contains("fleet")) {
+ return directory
+ }
+ }
+ catch (_: IOException) {
+ }
+ }
+ directory = directory.parent
+ }
+ return null
+ }
+}
\ No newline at end of file
diff --git a/fleet/preferences/srcCommonMain/fleet/preferences/FleetProperties.kt b/fleet/preferences/srcCommonMain/fleet/preferences/FleetProperties.kt
new file mode 100644
index 000000000000..63cd8f0029a4
--- /dev/null
+++ b/fleet/preferences/srcCommonMain/fleet/preferences/FleetProperties.kt
@@ -0,0 +1,48 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package fleet.preferences
+
+import fleet.util.multiplatform.linkToActual
+
+// True means one wants to investigate how fleet itself works (e.g. enable verbose logging or increase transport delays)
+val isFleetDebugMode: Boolean by lazy { fleetFlag("fleet.debug.mode") }
+
+// Enable auxiliary features to facilitate dev flow e.g. code reload etc. It means that we're running from sources.
+@Deprecated("most probably you want use FleetFromSourcesPaths.isRunningFromSources or FleetCommonSettingsKeys.kt#isInternalMode")
+val isFleetDevMode: Boolean by lazy { getFleetEnvironmentType() == FleetEnvironmentType.DEVELOPMENT }
+
+// Defined the default value for internal mode
+@Deprecated(
+ "we still have usages due to lack of dependency injection, most probably you want use FleetCommonSettingsKeys.kt#isInternalMode")
+val isFleetInternalDefaultValue: Boolean by lazy { fleetFlag("fleet.internal.mode.default") }
+
+// Fleet is running as a built distribution
+val isFleetDistributionMode: Boolean by lazy { getFleetEnvironmentType() == FleetEnvironmentType.PRODUCTION }
+
+// The distribution is not published on the marketplace, features that depend on it, should do workarounds:
+// e.g. upload required resources to the Space in the runtime
+val isFleetUnpublishedBuild: Boolean by lazy { fleetFlag("fleet.unpublished.build") }
+
+// Defines whether we are under testing. If set to true, some components will be mocked.
+val isFleetTestMode: Boolean by lazy { getFleetEnvironmentType() == FleetEnvironmentType.TEST }
+
+val isFleetShortCircuitMode: Boolean by lazy {
+ fleetFlag("fleet.shortCircuit", when {
+ isFleetTestMode -> false
+ isFleetInternalDefaultValue -> false
+ else -> true
+ })
+}
+
+val coroutinesDebugProbesEnabled: Boolean by lazy { fleetFlag("fleet.coroutines.debug.probes.enabled") }
+
+fun fleetFlag(name: String, defaultValue: Boolean = false): Boolean = fleetProperty(name)?.toBoolean() ?: defaultValue
+
+fun fleetProperty(name: String, defaultValue: String? = null): String? = linkToActual()
+
+internal enum class FleetEnvironmentType {
+ PRODUCTION,
+ DEVELOPMENT,
+ TEST
+}
+
+internal fun getFleetEnvironmentType(): FleetEnvironmentType = linkToActual()
\ No newline at end of file
diff --git a/fleet/preferences/srcCommonMain/fleet/preferences/FleetRootByClass.kt b/fleet/preferences/srcCommonMain/fleet/preferences/FleetRootByClass.kt
new file mode 100644
index 000000000000..f9c1e3be6725
--- /dev/null
+++ b/fleet/preferences/srcCommonMain/fleet/preferences/FleetRootByClass.kt
@@ -0,0 +1,7 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package fleet.preferences
+
+import fleet.util.multiplatform.linkToActual
+import kotlinx.io.files.Path
+
+internal fun findFleetRootByClass(): Path? = linkToActual()
\ No newline at end of file
diff --git a/fleet/preferences/srcCommonMain/fleet/preferences/TracingConfigProperties.kt b/fleet/preferences/srcCommonMain/fleet/preferences/TracingConfigProperties.kt
new file mode 100644
index 000000000000..16030a80e335
--- /dev/null
+++ b/fleet/preferences/srcCommonMain/fleet/preferences/TracingConfigProperties.kt
@@ -0,0 +1,46 @@
+package fleet.preferences
+
+object TracingConfigProperties {
+ //@fleet.kernel.plugins.InternalInPluginModules(where = ["fleet.reporting.opentelemetry"])
+ val otelGrpcEndpoint: String? by lazy { fleetProperty("fleet.diagnostic.otel.grpc.endpoint", null) }
+ val otelHttpEndpoint: String? by lazy { fleetProperty("fleet.diagnostic.otel.http.endpoint", null) }
+ val otelTracesDir: String? by lazy { fleetProperty("fleet.diagnostic.otel.traces.dir", null) }
+
+ //@fleet.kernel.plugins.InternalInPluginModules(where = ["fleet.reporting.opentelemetry"])
+ val metricsFile: String? by lazy { fleetProperty("fleet.diagnostic.metrics.file", null) }
+
+ val fahrplanStartupFile: String? by lazy { fleetProperty("fahrplan.startup", null) }
+
+ //@fleet.kernel.plugins.InternalInPluginModules(where = ["fleet.reporting.opentelemetry"])
+ val fahrplanEnabled: Boolean by lazy {
+ fahrplanStartupFile != null ||
+ (fleetProperty("fleet.diagnostic.fahrplan.enabled", null)?.toBoolean() ?: isFleetInternalDefaultValue)
+ }
+ val openTelemetryEnabled: Boolean by lazy {
+ isFleetInternalDefaultValue ||
+ otelGrpcEndpoint != null ||
+ otelHttpEndpoint != null ||
+ otelTracesDir != null ||
+ metricsFile != null ||
+ ijPerfFile != null
+ }
+
+ //@fleet.kernel.plugins.InternalInPluginModules(where = ["fleet.reporting.opentelemetry"])
+ val ijPerfFile: String? by lazy { fleetProperty("fleet.diagnostic.ijperf.file", null) }
+
+ val fusSchemaDir: String? by lazy { fleetProperty("fleet.diagnostic.fus.schema.dir", null) }
+ val schemaGenerationMode: Boolean by lazy { fusSchemaDir != null } //todo [MM] maybe rewrite to fleet.dock.impl.RunMode
+ val ijPerfSpanPrefix: String? by lazy { fleetProperty("fleet.diagnostic.ijperf.span.prefix") }
+
+ val performanceTestMode: Boolean by lazy {
+ (ijPerfFile != null || fahrplanStartupFile != null) &&
+ !fleetProperty("fleet.diagnostic.suppress.quit", "false").toBoolean()
+ }
+
+ //does a number of things to simplify FUS testing
+ // 1. does not update metadata (allows to work with modified metadata)
+ // 2. disables bucket check (sends always)
+ // 3. does not remove fus files (allows reading what is actually sent)
+ // 4. shows a directory with fus files on startup
+ val fusTestingMode: Boolean by lazy { fleetProperty("fleet.diagnostic.fus.testing.mode", "false").toBoolean() }
+}
\ No newline at end of file
diff --git a/fleet/preferences/srcJvmMain/fleet/preferences/FleetProperty.jvm.kt b/fleet/preferences/srcJvmMain/fleet/preferences/FleetProperty.jvm.kt
new file mode 100644
index 000000000000..f3a4ba57e193
--- /dev/null
+++ b/fleet/preferences/srcJvmMain/fleet/preferences/FleetProperty.jvm.kt
@@ -0,0 +1,19 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package fleet.preferences
+
+import fleet.util.multiplatform.Actual
+
+@Actual("fleetProperty")
+internal fun fleetPropertyJvm(name: String, defaultValue: String?): String? {
+ val formattedName = name.replace('.', '_').uppercase()
+ return System.getProperty(name) ?: System.getenv(formattedName) ?: defaultValue
+}
+
+@Actual
+internal fun getFleetEnvironmentTypeJvm(): FleetEnvironmentType {
+ return when {
+ fleetFlag("fleet.distribution.mode") -> FleetEnvironmentType.PRODUCTION
+ fleetFlag("fleet.dev.mode") -> FleetEnvironmentType.DEVELOPMENT
+ else -> FleetEnvironmentType.TEST
+ }
+}
\ No newline at end of file
diff --git a/fleet/preferences/srcJvmMain/fleet/preferences/FleetRootByClass.jvm.kt b/fleet/preferences/srcJvmMain/fleet/preferences/FleetRootByClass.jvm.kt
new file mode 100644
index 000000000000..40294f9fc857
--- /dev/null
+++ b/fleet/preferences/srcJvmMain/fleet/preferences/FleetRootByClass.jvm.kt
@@ -0,0 +1,20 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package fleet.preferences
+
+import fleet.util.multiplatform.Actual
+import kotlinx.io.files.Path
+import java.net.URL
+import kotlin.io.path.absolutePathString
+import kotlin.io.path.pathString
+import kotlin.io.path.toPath
+import kotlin.jvm.javaClass
+
+@Actual("findFleetRootByClass")
+internal fun findFleetRootByClassJvm(): Path? {
+ val url: URL? = FleetFromSourcesPaths.javaClass.getResource("/${FleetFromSourcesPaths.javaClass.name.replace('.', '/')}.class")
+ return when (url?.protocol) {
+ "file" -> url.toURI().toPath()
+ "jar" -> URL(url.file).toURI().toPath().pathString.split("!").firstOrNull()?.let { java.nio.file.Path.of(it) }
+ else -> null
+ }?.absolutePathString()?.let { Path(it) }
+}
\ No newline at end of file
diff --git a/fleet/preferences/srcJvmMain/fleet/preferences/PathExt.kt b/fleet/preferences/srcJvmMain/fleet/preferences/PathExt.kt
new file mode 100644
index 000000000000..7418b2026c16
--- /dev/null
+++ b/fleet/preferences/srcJvmMain/fleet/preferences/PathExt.kt
@@ -0,0 +1,9 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package fleet.preferences
+
+import java.nio.file.Path
+
+@Deprecated("java Paths are not multiplatform. Please consider using kotlnx.io Path if possible")
+fun kotlinx.io.files.Path.toNioPath(): Path = Path.of(this.toString())
+
+fun kotlinx.io.files.Path.resolve(childPath: String): kotlinx.io.files.Path = kotlinx.io.files.Path(this, childPath)
\ No newline at end of file
diff --git a/fleet/preferences/srcWasmJsMain/fleet/preferences/FleetProperty.wasm.kt b/fleet/preferences/srcWasmJsMain/fleet/preferences/FleetProperty.wasm.kt
new file mode 100644
index 000000000000..6663df06f636
--- /dev/null
+++ b/fleet/preferences/srcWasmJsMain/fleet/preferences/FleetProperty.wasm.kt
@@ -0,0 +1,34 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package fleet.preferences
+
+import fleet.util.multiplatform.Actual
+import kotlinx.browser.window
+
+@Actual("fleetProperty")
+fun fleetPropertyWasmJs(name: String, defaultValue: String?): String? = when (name) {
+ "fleet.ai.service.configuration.url" -> url("aiconfig")
+ "fleet.ai.service.url" -> url("ai")
+ else -> defaultValue
+}
+
+@Actual
+internal fun getFleetEnvironmentTypeWasmJs(): FleetEnvironmentType {
+ return when (ENVIRONMENT) {
+ "production" -> FleetEnvironmentType.PRODUCTION
+ "development" -> FleetEnvironmentType.DEVELOPMENT
+ else -> FleetEnvironmentType.TEST
+ }
+}
+
+/**
+ * the property represents webpack's `config.mode` value
+ */
+@JsName("ENVIRONMENT")
+external val ENVIRONMENT: String
+
+private fun url(suffix: String): String {
+ return sequenceOf(window.location.origin, window.location.pathname, suffix)
+ .map { it.trim('/') }
+ .filter { it.isNotEmpty() }
+ .joinToString(separator = "/")
+}
\ No newline at end of file
diff --git a/fleet/preferences/srcWasmJsMain/fleet/preferences/FleetRootByClass.wasm.kt b/fleet/preferences/srcWasmJsMain/fleet/preferences/FleetRootByClass.wasm.kt
new file mode 100644
index 000000000000..6c7890d4efdb
--- /dev/null
+++ b/fleet/preferences/srcWasmJsMain/fleet/preferences/FleetRootByClass.wasm.kt
@@ -0,0 +1,8 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package fleet.preferences
+
+import fleet.util.multiplatform.Actual
+import kotlinx.io.files.Path
+
+@Actual("findFleetRootByClass")
+internal fun findFleetRootByClassWasmJs(): Path? = null
\ No newline at end of file