ddd9d076c1
ci / JVM build + tests (push) Failing after 1m25s
Закрывает разрыв между :proto и UI-композицией: TuiBackend маршрутизирует
onUserMessage → Conversation.send и Event → AppState.
Изменения:
- agentik-tui/.../TuiBackend.kt — новый commonMain-файл (138 строк):
инкапсулирует Agent-общение, авто-создание первого диалога,
подписку на Conversation.events, диспетчеризацию Event в AppState.
- agentik-tui/.../Main.kt — обязательный health-check GET {baseUrl}/health
ДО старта UI: понятная ошибка и exit 1 при недоступном сервере,
понятное сообщение при не-200/не-'ok'. JVM-only API (java.net.*,
ktor.*Timeout) обёрнуты в catch (Exception) — commonMain собирается
под все desktop-native.
- agentik-tui/.../AppState.kt — добавлены attachBackend/setConversation/
newConversation/postSystem; submitInput теперь не пишет AssistantStreaming
сам (его рисует TuiBackend по Event.AppendText).
- agentik-tui/.../TuiApp.kt — TuiBackend монтируется в LaunchedEffect,
делит scope с recomposer'ом.
- agentik-tui/.../Platform.jvm.kt — expect/actual platformEnv + platformCreateAgent.
- agentik-tui/.../Platform.native.kt — stub actual.
- agentik-tui/build.gradle.kts — kotlinx-coroutines-test в commonTest.
- agentik-cli/build.gradle.kts — binaries.executable entryPoint для native
(тот же фикс, что прошёл для agentik-tui в предыдущем коммите).
- TuiBackend.dispatch: Event.End теперь зовёт finishAssistant()
(конвертирует streaming-чанк в финальный Assistant), Interrupted —
finishAssistant + 'прервано' system message. Раньше оба только
выключали streaming, и последний чанк висел как AssistantStreaming
с курсором.
Тесты: agentik-tui/src/commonTest/.../TuiBackendTest.kt — 10 кейсов
против FakeAgent/FakeConversation: auto-create, переиспользование,
AppendText-coalesce, End finalize, Interrupted system, ToolCall/ToolResult
visibility, Error handling, exception path, StartReasoning, connect
message. Используется runTest.backgroundScope + runCurrent — backgroundScope
не двигается через advanceUntilIdle (документированное поведение).
Сборка: jvm + linuxX64 + linuxArm64 + macosX64 + macosArm64 + mingwX64,
10/10 jvmTest green, full project jvmTest не задет.
111 lines
4.4 KiB
Kotlin
111 lines
4.4 KiB
Kotlin
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||
|
||
plugins {
|
||
alias(libs.plugins.kotlin.multiplatform)
|
||
alias(libs.plugins.kotlin.serialization)
|
||
alias(libs.plugins.kotlin.compose)
|
||
alias(libs.plugins.shadow)
|
||
}
|
||
|
||
kotlin {
|
||
jvmToolchain(21)
|
||
// Suppress Beta-предупреждения от expect/actual объектов.
|
||
compilerOptions {
|
||
freeCompilerArgs.add("-Xexpect-actual-classes")
|
||
}
|
||
|
||
// Mosaic 0.18 поддерживает JVM + desktop-native (macosX64/macosArm64/linuxX64/linuxArm64/mingwX64).
|
||
// iOS пропускаем — на iOS не бывает TUI-сессий.
|
||
jvm()
|
||
macosX64()
|
||
macosArm64()
|
||
linuxX64()
|
||
linuxArm64()
|
||
mingwX64()
|
||
|
||
// Native executables. По умолчанию Kotlin/Native для каждого target'а
|
||
// собирает только .klib (библиотеку) — для запускаемого .kexe надо
|
||
// явно попросить binaries.executable(). entryPoint нужно задать явно:
|
||
// KMP-линкер ищет функцию по FQN (без `Kt`-суффикса), а Kotlin/Native
|
||
// добавляет суффикс только для файлов с именем `Main.kt`, поэтому
|
||
// указываем точку входа как `pw.binom.agentik.tui.main` (без суффикса).
|
||
//
|
||
// Применяем к каждому из linuxX64/macosX64/macosArm64/linuxArm64/mingwX64
|
||
// явно (а не через targets.withType), потому что targets DSL в KMP не
|
||
// поддерживает реифицированный withType<KotlinNativeTarget>().
|
||
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||
listOf(linuxX64(), linuxArm64(), macosX64(), macosArm64(), mingwX64()).forEach {
|
||
it.binaries.executable {
|
||
entryPoint = "pw.binom.agentik.tui.main"
|
||
}
|
||
}
|
||
|
||
sourceSets {
|
||
commonMain.dependencies {
|
||
implementation(project(":proto"))
|
||
|
||
implementation(libs.kotlinx.coroutines.core)
|
||
implementation(libs.kotlinx.serialization.json)
|
||
|
||
// JetBrains Compose runtime — тащит Mosaic как обёртку.
|
||
implementation(libs.mosaic.runtime)
|
||
implementation(libs.mosaic.tty.terminal)
|
||
|
||
// Health-check в Main.kt: Ktor CIO на JVM, на native не собирается —
|
||
// там работает stub actual через expect/actual.
|
||
implementation(libs.ktor.client.core)
|
||
implementation(libs.ktor.client.cio)
|
||
}
|
||
jvmMain.dependencies {
|
||
implementation(project(":client"))
|
||
}
|
||
commonTest.dependencies {
|
||
implementation(kotlin("test"))
|
||
implementation(libs.kotlinx.coroutines.core)
|
||
implementation(libs.kotlinx.coroutines.test)
|
||
}
|
||
}
|
||
|
||
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||
jvm {
|
||
binaries {
|
||
executable {
|
||
mainClass.set("pw.binom.agentik.tui.MainKt")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- Fatjar (uberjar) ---
|
||
//
|
||
// Аналогично `:agentik-cli`: shadowJar склеивает `jvmJar` + `jvmRuntimeClasspath` в self-contained
|
||
// `*-all.jar`. Shadow 8.x не авторегистрирует shadowJar в KMP-проектах — регистрируем явно.
|
||
val shadowJarTask = tasks.register<ShadowJar>("shadowJar") {
|
||
archiveBaseName.set("agentik-tui")
|
||
archiveClassifier.set("all")
|
||
description = "Self-contained fatjar with all runtime dependencies bundled (incl. Compose-runtime + Mosaic)."
|
||
group = "build"
|
||
|
||
from(tasks.named("jvmJar"))
|
||
val cc = try {
|
||
@Suppress("UNCHECKED_CAST")
|
||
configurations as org.gradle.api.artifacts.ConfigurationContainer
|
||
} catch (_: ClassCastException) {
|
||
@Suppress("UNCHECKED_CAST")
|
||
(project as org.gradle.api.Project).configurations as org.gradle.api.artifacts.ConfigurationContainer
|
||
}
|
||
from(cc.getByName("jvmRuntimeClasspath"))
|
||
|
||
mergeServiceFiles()
|
||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||
|
||
manifest {
|
||
attributes["Main-Class"] = "pw.binom.agentik.tui.MainKt"
|
||
attributes["Implementation-Title"] = "agentik-tui"
|
||
attributes["Implementation-Version"] = project.version.toString()
|
||
}
|
||
|
||
includeEmptyDirs = false
|
||
}
|