From 8c1d02c06ac5d48f57429af4bb57145f22e294dc Mon Sep 17 00:00:00 2001 From: "nedas.vi" Date: Tue, 21 Jul 2026 10:05:41 +0300 Subject: [PATCH 1/6] Add human handoff, escalation banner, and CSAT prompt support Handles the three new backend SSE event types (session_escalated, human_message, feedback_requested) and the new HUMAN_AGENT message role: renders an escalated-state banner, a distinct human-agent message bubble, and a post-turn CSAT prompt. Adds ResolveKitAPIClient.submitFeedback for POST /v1/sessions/{id}/feedback. Co-Authored-By: Claude Sonnet 5 --- .../networking/ResolveKitAPIClient.kt | 7 ++ .../networking/ResolveKitEventStreamClient.kt | 12 ++++ .../networking/models/EventModels.kt | 26 +++++++ .../networking/models/SessionModels.kt | 14 ++++ .../app/resolvekit/ui/ResolveKitChatView.kt | 71 +++++++++++++++++-- .../app/resolvekit/ui/ResolveKitRuntime.kt | 54 ++++++++++++++ .../app/resolvekit/ui/models/ChatMessage.kt | 2 +- 7 files changed, 181 insertions(+), 5 deletions(-) diff --git a/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitAPIClient.kt b/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitAPIClient.kt index 94f4d1c..aba98e8 100644 --- a/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitAPIClient.kt +++ b/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitAPIClient.kt @@ -92,6 +92,13 @@ class ResolveKitAPIClient( request: ToolResultRequest ): ToolResultResponse = post("/v1/sessions/$sessionId/tool-results", request, token) + /** POST /v1/sessions/{id}/feedback — submit a CSAT rating (1-5). */ + suspend fun submitFeedback( + sessionId: String, + token: String, + request: FeedbackRequest + ): FeedbackResponse = post("/v1/sessions/$sessionId/feedback", request, token) + // ------------------------------------------------------------------------- // Internal helpers // ------------------------------------------------------------------------- diff --git a/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitEventStreamClient.kt b/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitEventStreamClient.kt index 25d0243..99e75a5 100644 --- a/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitEventStreamClient.kt +++ b/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitEventStreamClient.kt @@ -114,6 +114,18 @@ class ResolveKitEventStreamClient( ResolveKitEvent.ServerError(envelope.eventId, p.code, p.message, p.recoverable) }.getOrNull() + "session_escalated" -> runCatching { + val p = json.decodeFromJsonElement(envelope.payload) + ResolveKitEvent.SessionEscalated(envelope.eventId, p.reason) + }.getOrNull() + + "human_message" -> runCatching { + val p = json.decodeFromJsonElement(envelope.payload) + ResolveKitEvent.HumanMessage(envelope.eventId, p.messageId, p.text) + }.getOrNull() + + "feedback_requested" -> ResolveKitEvent.FeedbackRequested(envelope.eventId) + else -> ResolveKitEvent.Unknown(envelope.eventId, envelope.type) } } diff --git a/networking/src/main/kotlin/app/resolvekit/networking/models/EventModels.kt b/networking/src/main/kotlin/app/resolvekit/networking/models/EventModels.kt index 94f7a17..a1bdfe5 100644 --- a/networking/src/main/kotlin/app/resolvekit/networking/models/EventModels.kt +++ b/networking/src/main/kotlin/app/resolvekit/networking/models/EventModels.kt @@ -42,6 +42,17 @@ data class ServerErrorPayload( val recoverable: Boolean = true ) +@Serializable +data class SessionEscalatedPayload( + val reason: String +) + +@Serializable +data class HumanMessagePayload( + @SerialName("message_id") val messageId: String, + val text: String +) + sealed class ResolveKitEvent { data class TextDelta( val eventId: String, @@ -69,6 +80,21 @@ sealed class ResolveKitEvent { val recoverable: Boolean ) : ResolveKitEvent() + data class SessionEscalated( + val eventId: String, + val reason: String + ) : ResolveKitEvent() + + data class HumanMessage( + val eventId: String, + val messageId: String, + val text: String + ) : ResolveKitEvent() + + data class FeedbackRequested( + val eventId: String + ) : ResolveKitEvent() + data class Unknown( val eventId: String, val type: String diff --git a/networking/src/main/kotlin/app/resolvekit/networking/models/SessionModels.kt b/networking/src/main/kotlin/app/resolvekit/networking/models/SessionModels.kt index b16de09..76f6223 100644 --- a/networking/src/main/kotlin/app/resolvekit/networking/models/SessionModels.kt +++ b/networking/src/main/kotlin/app/resolvekit/networking/models/SessionModels.kt @@ -78,6 +78,20 @@ data class ToolResultResponse( val deduplicated: Boolean = false ) +@Serializable +data class FeedbackRequest( + val rating: Int, + val comment: String? = null +) + +@Serializable +data class FeedbackResponse( + val id: String, + @SerialName("session_id") val sessionId: String, + val rating: Int, + val comment: String? = null +) + @Serializable data class SessionLocalization( val locale: String, diff --git a/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitChatView.kt b/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitChatView.kt index 369877d..29e9f3d 100644 --- a/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitChatView.kt +++ b/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitChatView.kt @@ -45,6 +45,8 @@ fun ResolveKitChatView( val messagePlaceholder by runtime.messagePlaceholder.collectAsState() val appearanceMode by runtime.appearanceMode.collectAsState() val chatTheme by runtime.chatTheme.collectAsState() + val isEscalated by runtime.isEscalated.collectAsState() + val pendingFeedbackRequest by runtime.pendingFeedbackRequest.collectAsState() val scope = rememberCoroutineScope() val listState = rememberLazyListState() @@ -117,6 +119,10 @@ fun ResolveKitChatView( else -> Unit } + if (isEscalated) { + ConnectionBanner("Connecting you with a human agent…", palette) + } + // ----- Message list ----- LazyColumn( state = listState, @@ -157,6 +163,14 @@ fun ResolveKitChatView( ) } + if (pendingFeedbackRequest) { + FeedbackPromptCard( + palette = palette, + onRate = { rating -> scope.launch { runtime.submitFeedback(rating) } }, + onDismiss = { runtime.dismissFeedbackRequest() } + ) + } + var inputText by remember { mutableStateOf("") } val canSend = inputText.isNotBlank() && !isTurnInProgress && connectionState == ResolveKitConnectionState.ACTIVE @@ -252,12 +266,61 @@ private fun ChatBubble(message: ResolveKitChatMessage, palette: ResolveKitPalett color = if (isUser) palette.userBubbleBackground else palette.assistantBubbleBackground, modifier = Modifier.widthIn(max = 300.dp) ) { + Column(modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp)) { + if (message.role == ChatMessageRole.HUMAN_AGENT) { + Text( + text = "Support Agent", + color = palette.statusText, + style = MaterialTheme.typography.labelSmall + ) + Spacer(Modifier.height(2.dp)) + } + Text( + text = message.text, + color = if (isUser) palette.userBubbleText else palette.assistantBubbleText, + style = MaterialTheme.typography.bodyMedium + ) + } + } + } +} + +@Composable +private fun FeedbackPromptCard( + palette: ResolveKitPaletteColors, + onRate: (Int) -> Unit, + onDismiss: () -> Unit +) { + Surface( + color = palette.toolCardBackground, + shape = RoundedCornerShape(14.dp), + border = BorderStroke(1.dp, palette.toolCardBorder), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 4.dp) + ) { + Column(modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp)) { Text( - text = message.text, - modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp), - color = if (isUser) palette.userBubbleText else palette.assistantBubbleText, - style = MaterialTheme.typography.bodyMedium + text = "How did we do?", + color = palette.titleText, + style = MaterialTheme.typography.titleSmall ) + Spacer(Modifier.height(8.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + for (rating in 1..5) { + TextButton(onClick = { onRate(rating) }) { + Text( + text = "★", + color = palette.statusText, + style = MaterialTheme.typography.titleMedium + ) + } + } + Spacer(Modifier.weight(1f)) + TextButton(onClick = onDismiss) { + Text("Dismiss", color = palette.statusText) + } + } } } } diff --git a/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt b/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt index 3d4f42c..af72c91 100644 --- a/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt +++ b/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt @@ -81,6 +81,15 @@ class ResolveKitRuntime( private val _executionLog = MutableStateFlow>(emptyList()) val executionLog: StateFlow> = _executionLog.asStateFlow() + private val _isEscalated = MutableStateFlow(false) + val isEscalated: StateFlow = _isEscalated.asStateFlow() + + private val _escalationReason = MutableStateFlow(null) + val escalationReason: StateFlow = _escalationReason.asStateFlow() + + private val _pendingFeedbackRequest = MutableStateFlow(false) + val pendingFeedbackRequest: StateFlow = _pendingFeedbackRequest.asStateFlow() + // ------------------------------------------------------------------------- // Internal session state // ------------------------------------------------------------------------- @@ -189,6 +198,7 @@ class ResolveKitRuntime( val role = when (msg.role) { "user" -> ChatMessageRole.USER "assistant" -> ChatMessageRole.ASSISTANT + "human_agent" -> ChatMessageRole.HUMAN_AGENT else -> return@mapNotNull null } ResolveKitChatMessage(role = role, text = msg.content ?: "") @@ -244,6 +254,9 @@ class ResolveKitRuntime( _toolCallChecklist.value = emptyList() _toolCallBatchState.value = ResolveKitToolCallBatchState.IDLE _isTurnInProgress.value = false + _isEscalated.value = false + _escalationReason.value = null + _pendingFeedbackRequest.value = false sessionId = null chatCapabilityToken = null eventsUrl = null @@ -301,6 +314,23 @@ class ResolveKitRuntime( } } + /** Submit a CSAT rating (1-5) for the current session, dismissing the pending feedback prompt. */ + suspend fun submitFeedback(rating: Int, comment: String? = null) { + val sid = sessionId ?: return + val tok = chatCapabilityToken ?: return + _pendingFeedbackRequest.value = false + runCatching { + apiClient.submitFeedback(sid, tok, FeedbackRequest(rating = rating, comment = comment)) + }.onFailure { + log("submitFeedback failed: ${it.message}") + } + } + + /** Dismiss the feedback prompt without submitting a rating. */ + fun dismissFeedbackRequest() { + _pendingFeedbackRequest.value = false + } + /** Approve all tools in the current batch. Executes them and submits results. */ suspend fun approveToolCallBatch() { val items = _toolCallChecklist.value.toList() @@ -498,6 +528,30 @@ class ResolveKitRuntime( } } + is app.resolvekit.networking.models.ResolveKitEvent.SessionEscalated -> { + lastEventCursor = event.eventId + saveEventCursor(event.eventId, sessionId ?: return) + _escalationReason.value = event.reason + _isEscalated.value = true + _isTurnInProgress.value = false + currentTurnId = null + } + + is app.resolvekit.networking.models.ResolveKitEvent.HumanMessage -> { + lastEventCursor = event.eventId + saveEventCursor(event.eventId, sessionId ?: return) + _messages.value = _messages.value + ResolveKitChatMessage( + role = ChatMessageRole.HUMAN_AGENT, + text = event.text + ) + } + + is app.resolvekit.networking.models.ResolveKitEvent.FeedbackRequested -> { + lastEventCursor = event.eventId + saveEventCursor(event.eventId, sessionId ?: return) + _pendingFeedbackRequest.value = true + } + is app.resolvekit.networking.models.ResolveKitEvent.Unknown -> { lastEventCursor = event.eventId saveEventCursor(event.eventId, sessionId ?: return) diff --git a/ui/src/main/kotlin/app/resolvekit/ui/models/ChatMessage.kt b/ui/src/main/kotlin/app/resolvekit/ui/models/ChatMessage.kt index 8fb6260..8723a64 100644 --- a/ui/src/main/kotlin/app/resolvekit/ui/models/ChatMessage.kt +++ b/ui/src/main/kotlin/app/resolvekit/ui/models/ChatMessage.kt @@ -3,7 +3,7 @@ package app.resolvekit.ui.models import java.util.Date import java.util.UUID -enum class ChatMessageRole { USER, ASSISTANT, SYSTEM } +enum class ChatMessageRole { USER, ASSISTANT, SYSTEM, HUMAN_AGENT } data class ResolveKitChatMessage( val id: UUID = UUID.randomUUID(), From 41863adc95f84314c510dc37c4a8b8478f53b464 Mon Sep 17 00:00:00 2001 From: "nedas.vi" Date: Tue, 21 Jul 2026 11:20:38 +0300 Subject: [PATCH 2/6] Debounce the CSAT prompt instead of showing it instantly feedback_requested fired the rating prompt the moment the event arrived, even mid-conversation. Wait a few seconds before showing it, and cancel if the user sends another message in the meantime. Co-Authored-By: Claude Sonnet 5 --- .../app/resolvekit/ui/ResolveKitRuntime.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt b/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt index af72c91..ae0ccfb 100644 --- a/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt +++ b/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt @@ -103,6 +103,7 @@ class ResolveKitRuntime( private var eventStreamJob: Job? = null private var heartbeatJob: Job? = null private var batchCoalesceJob: Job? = null + private var feedbackPromptDelayJob: Job? = null private var networkCallback: ConnectivityManager.NetworkCallback? = null private val lastEventTimeMs = java.util.concurrent.atomic.AtomicLong(0L) private val batchMutex = Mutex() @@ -257,6 +258,7 @@ class ResolveKitRuntime( _isEscalated.value = false _escalationReason.value = null _pendingFeedbackRequest.value = false + cancelPendingFeedbackPrompt() sessionId = null chatCapabilityToken = null eventsUrl = null @@ -300,6 +302,8 @@ class ResolveKitRuntime( text = text ) _isTurnInProgress.value = true + cancelPendingFeedbackPrompt() + _pendingFeedbackRequest.value = false val requestId = UUID.randomUUID().toString() log("Sending message requestId=$requestId") @@ -319,6 +323,7 @@ class ResolveKitRuntime( val sid = sessionId ?: return val tok = chatCapabilityToken ?: return _pendingFeedbackRequest.value = false + cancelPendingFeedbackPrompt() runCatching { apiClient.submitFeedback(sid, tok, FeedbackRequest(rating = rating, comment = comment)) }.onFailure { @@ -329,6 +334,7 @@ class ResolveKitRuntime( /** Dismiss the feedback prompt without submitting a rating. */ fun dismissFeedbackRequest() { _pendingFeedbackRequest.value = false + cancelPendingFeedbackPrompt() } /** Approve all tools in the current batch. Executes them and submits results. */ @@ -549,7 +555,7 @@ class ResolveKitRuntime( is app.resolvekit.networking.models.ResolveKitEvent.FeedbackRequested -> { lastEventCursor = event.eventId saveEventCursor(event.eventId, sessionId ?: return) - _pendingFeedbackRequest.value = true + scheduleFeedbackPrompt() } is app.resolvekit.networking.models.ResolveKitEvent.Unknown -> { @@ -754,6 +760,20 @@ class ResolveKitRuntime( _executionLog.value = (_executionLog.value + message).takeLast(200) } + /** Show the CSAT prompt only after a short pause with no follow-up message. */ + private fun scheduleFeedbackPrompt() { + feedbackPromptDelayJob?.cancel() + feedbackPromptDelayJob = scope.launch { + delay(6_000) + _pendingFeedbackRequest.value = true + } + } + + private fun cancelPendingFeedbackPrompt() { + feedbackPromptDelayJob?.cancel() + feedbackPromptDelayJob = null + } + private fun applySession(session: SessionResponse, clearPendingResults: Boolean) { val previousSessionId = sessionId sessionId = session.id From 877148701062950091018e955b45f439d433fa21 Mon Sep 17 00:00:00 2001 From: "nedas.vi" Date: Tue, 21 Jul 2026 11:40:11 +0300 Subject: [PATCH 3/6] Suppress stale CSAT prompt during escalation; honor immediate flag session_escalated now cancels any CSAT prompt still pending from the AI's last reply -- it shouldn't surface while the user is waiting on a human handoff. feedback_requested's new "immediate" flag (true only for the human-resolve trigger) skips the debounce and shows the prompt right away, since that signal means the conversation is definitively over. Co-Authored-By: Claude Sonnet 5 --- .../networking/ResolveKitEventStreamClient.kt | 5 ++++- .../app/resolvekit/networking/models/EventModels.kt | 8 +++++++- .../kotlin/app/resolvekit/ui/ResolveKitRuntime.kt | 11 ++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitEventStreamClient.kt b/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitEventStreamClient.kt index 99e75a5..e23830b 100644 --- a/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitEventStreamClient.kt +++ b/networking/src/main/kotlin/app/resolvekit/networking/ResolveKitEventStreamClient.kt @@ -124,7 +124,10 @@ class ResolveKitEventStreamClient( ResolveKitEvent.HumanMessage(envelope.eventId, p.messageId, p.text) }.getOrNull() - "feedback_requested" -> ResolveKitEvent.FeedbackRequested(envelope.eventId) + "feedback_requested" -> runCatching { + val p = json.decodeFromJsonElement(envelope.payload) + ResolveKitEvent.FeedbackRequested(envelope.eventId, p.immediate) + }.getOrDefault(ResolveKitEvent.FeedbackRequested(envelope.eventId, immediate = false)) else -> ResolveKitEvent.Unknown(envelope.eventId, envelope.type) } diff --git a/networking/src/main/kotlin/app/resolvekit/networking/models/EventModels.kt b/networking/src/main/kotlin/app/resolvekit/networking/models/EventModels.kt index a1bdfe5..e7fb6e3 100644 --- a/networking/src/main/kotlin/app/resolvekit/networking/models/EventModels.kt +++ b/networking/src/main/kotlin/app/resolvekit/networking/models/EventModels.kt @@ -53,6 +53,11 @@ data class HumanMessagePayload( val text: String ) +@Serializable +data class FeedbackRequestedPayload( + val immediate: Boolean = false +) + sealed class ResolveKitEvent { data class TextDelta( val eventId: String, @@ -92,7 +97,8 @@ sealed class ResolveKitEvent { ) : ResolveKitEvent() data class FeedbackRequested( - val eventId: String + val eventId: String, + val immediate: Boolean ) : ResolveKitEvent() data class Unknown( diff --git a/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt b/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt index ae0ccfb..572f475 100644 --- a/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt +++ b/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt @@ -541,6 +541,10 @@ class ResolveKitRuntime( _isEscalated.value = true _isTurnInProgress.value = false currentTurnId = null + // Suppress any CSAT prompt still pending from the AI's last reply — + // it shouldn't surface while the user is waiting on a human handoff. + cancelPendingFeedbackPrompt() + _pendingFeedbackRequest.value = false } is app.resolvekit.networking.models.ResolveKitEvent.HumanMessage -> { @@ -555,7 +559,12 @@ class ResolveKitRuntime( is app.resolvekit.networking.models.ResolveKitEvent.FeedbackRequested -> { lastEventCursor = event.eventId saveEventCursor(event.eventId, sessionId ?: return) - scheduleFeedbackPrompt() + if (event.immediate) { + cancelPendingFeedbackPrompt() + _pendingFeedbackRequest.value = true + } else { + scheduleFeedbackPrompt() + } } is app.resolvekit.networking.models.ResolveKitEvent.Unknown -> { From c8709203a3c842f5893a8e8de5fb51ee97a7a091 Mon Sep 17 00:00:00 2001 From: "nedas.vi" Date: Mon, 27 Jul 2026 08:55:52 +0300 Subject: [PATCH 4/6] Bump CSAT debounce to 15s Give the user more room to keep chatting before the rating prompt interrupts. Co-Authored-By: Claude Sonnet 5 --- ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt b/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt index 572f475..55df0a7 100644 --- a/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt +++ b/ui/src/main/kotlin/app/resolvekit/ui/ResolveKitRuntime.kt @@ -773,7 +773,7 @@ class ResolveKitRuntime( private fun scheduleFeedbackPrompt() { feedbackPromptDelayJob?.cancel() feedbackPromptDelayJob = scope.launch { - delay(6_000) + delay(15_000) _pendingFeedbackRequest.value = true } } From 2c381a925c0c4c7555de1da33940755cd23cd9a3 Mon Sep 17 00:00:00 2001 From: "nedas.vi" Date: Mon, 27 Jul 2026 09:26:05 +0300 Subject: [PATCH 5/6] Fix sample app build: Compose/KSP version mismatch and API drift The sample module (and therefore :ui, which it depends on) has never actually compiled in a clean environment: - kotlinCompilerExtensionVersion 1.5.8 (in :ui and :sample) is only certified for Kotlin 1.9.22, but gradle/libs.versions.toml pins Kotlin 1.9.25. Bumped to 1.5.15, the compatible Compose Compiler for that Kotlin version. - ksp was pinned to 1.9.22-1.0.17, incompatible with Kotlin 1.9.25 for the same reason. Bumped to 1.9.25-1.0.20. - Sample source had drifted from current :core/:ui APIs: ResolveKitFunctionPack.id -> packName (+ missing supportedPlatforms), a missing ResolveKitChatActivity import, rememberSaveable imported from the wrong package, and a resolveKitParametersSchema type mismatch (Map vs the JSONObject typealias). - AndroidManifest.xml was missing usesCleartextTraffic="true", so every plain-HTTP request (used for staging/local dev, same as the iOS sample's NSAllowsArbitraryLoads) was silently blocked by Android. Verified via a full clean build + running on an emulator against the staging backend: session creation, message history, and SSE reconnect all work correctly end to end. Co-Authored-By: Claude Sonnet 5 --- gradle/libs.versions.toml | 2 +- sample/build.gradle.kts | 5 ++++- sample/src/main/AndroidManifest.xml | 5 ++++- .../main/kotlin/app/resolvekit/sample/DashboardActivity.kt | 1 + .../src/main/kotlin/app/resolvekit/sample/MainActivity.kt | 2 +- .../main/kotlin/app/resolvekit/sample/SampleApplication.kt | 7 +++++-- ui/build.gradle.kts | 5 ++++- 7 files changed, 20 insertions(+), 7 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d3ea6e9..66681ff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "1.9.25" -ksp = "1.9.22-1.0.17" +ksp = "1.9.25-1.0.20" coroutines = "1.7.3" serialization = "1.6.3" okhttp = "4.12.0" diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index eb294ea..05efe93 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -38,7 +38,10 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = "1.5.8" + // Must track the Compose-Kotlin compatibility map for the Kotlin + // version pinned in gradle/libs.versions.toml (currently 1.9.25). + // https://developer.android.com/jetpack/androidx/releases/compose-kotlin + kotlinCompilerExtensionVersion = "1.5.15" } compileOptions { diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index b29d3b5..a1279ce 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -6,7 +6,10 @@ + android:theme="@style/Theme.Material3.Light.NoActionBar" + android:usesCleartextTraffic="true"> + = mapOf( + override val resolveKitParametersSchema: app.resolvekit.core.JSONObject = mapOf( "type" to JSONValue.String("object"), "properties" to JSONValue.Object(mapOf( "message" to JSONValue.Object(mapOf("type" to JSONValue.String("string"))) diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index f22cd60..a700742 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -25,7 +25,10 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = "1.5.8" + // Must track the Compose-Kotlin compatibility map for the Kotlin + // version pinned in gradle/libs.versions.toml (currently 1.9.25). + // https://developer.android.com/jetpack/androidx/releases/compose-kotlin + kotlinCompilerExtensionVersion = "1.5.15" } compileOptions { From 298966d2a7cde7b82dd03e8aa4bed8e42e1776f9 Mon Sep 17 00:00:00 2001 From: "nedas.vi" Date: Mon, 27 Jul 2026 09:49:18 +0300 Subject: [PATCH 6/6] Fix agent-docs CI check: reference docs/INDEX.md and agent-first README scripts/check_agent_docs.sh requires AGENTS.md to reference both docs/INDEX.md and docs/agent-first/README.md -- neither was linked, which was failing the required build check on main itself, independent of this branch's actual changes. Also corrected the documented Kotlin version (1.9.22 -> 1.9.25) to match gradle/libs.versions.toml. Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 70df22c..677e7e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,7 +13,7 @@ ResolveKit Android SDK — native runtime, UI, and tool function integration for ResolveKit Android SDK provides runtime orchestration, tool calling, and chat UI surfaces for Kotlin apps. -**Tech Stack**: Kotlin 1.9.22, Compose, KSP, OkHttp, kotlinx.serialization +**Tech Stack**: Kotlin 1.9.25, Compose, KSP, OkHttp, kotlinx.serialization **Min SDK**: 26 | **Compile SDK**: 36 | **JDK**: 17 **Packages**: `app.resolvekit:sdk`, `app.resolvekit:authoring`, `app.resolvekit:ksp` @@ -33,6 +33,11 @@ curl -sL https://raw.githubusercontent.com/resolve-kit/resolvekit-skills/master/ When a user asks to integrate ResolveKit into their Android project, load `resolvekit-android-integration` and follow its steps. +## Documentation + +- [Documentation Index](docs/INDEX.md) — full map of durable docs, execution plans, and tech debt tracking. +- [Agent-First Harness Notes](docs/agent-first/README.md) — how this repo's agent-first doc structure is enforced in CI. + ## First Read 1. `build.gradle.kts` for project structure and plugins.