release: promote dev to main for v1.30.0 (#1271)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Lx-204 <15377891490@163.com>
Co-authored-by: AlexDev_ <56083016+alexdev03@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nikhil Nair <32665919+nikhilnair31@users.noreply.github.com>
Co-authored-by: marciodps <95321123+marciodps@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-09-25 23:23:12 +03:00
1 parent 7386c3035b
commit e4004decb4
85 files changed
+9284 -465

No files matched your search

@@ -17,6 +17,7 @@ import com.m57.hermescontrol.data.remote.NetworkMonitor
import com.m57.hermescontrol.data.session.ActiveSessionHolder
import com.m57.hermescontrol.data.ws.HermesWsClient
import com.m57.hermescontrol.data.ws.WsEvent
import com.m57.hermescontrol.ui.chat.replyFailureFromPayload
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -27,6 +28,46 @@ import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicBoolean
internal data class MessageCompleteNotificationPlan(
val text: String,
val sessionId: String?,
val isReplyMessage: Boolean,
val completionId: String?,
val correlationText: String?,
val allowInlineReply: Boolean,
)
internal fun messageCompleteNotificationPlan(
event: WsEvent.MessageComplete,
targetSessionId: String?,
newMessageText: String,
failureText: String,
): MessageCompleteNotificationPlan {
if (replyFailureFromPayload(event.rawPayload, fallback = "") != null) {
return MessageCompleteNotificationPlan(
text = failureText,
sessionId = targetSessionId,
isReplyMessage = false,
completionId = null,
correlationText = null,
allowInlineReply = false,
)
}
return MessageCompleteNotificationPlan(
text =
event.text
.take(100)
.replace("\n", " ")
.ifBlank { newMessageText },
sessionId = targetSessionId,
isReplyMessage = true,
completionId = event.completionId,
correlationText = event.text,
allowInlineReply = true,
)
}
/**
* Foreground service that keeps the WebSocket connection alive while the app
* is backgrounded and posts a notification when a new assistant reply
@@ -111,19 +152,22 @@ class ChatNotificationService : Service() {
if (!isAppInForeground.get()) {
when (event) {
is WsEvent.MessageComplete -> {
val preview =
event.text
.take(100)
.replace("\n", " ")
.ifBlank { getString(R.string.notif_new_message) }
val targetSessionId =
event.storedSessionId
?: ActiveSessionHolder.resolveStoredSessionId(event.sessionId)
val plan =
messageCompleteNotificationPlan(
event = event,
targetSessionId = targetSessionId,
newMessageText = getString(R.string.notif_new_message),
failureText = getString(R.string.chat_reply_failed_title),
)
showReplyNotification(
text = preview,
sessionId = targetSessionId,
isReplyMessage = true,
completionId = event.completionId,
text = plan.text,
sessionId = plan.sessionId,
isReplyMessage = plan.isReplyMessage,
completionId = plan.completionId,
allowInlineReply = plan.allowInlineReply,
// The durable REST row for this turn, when the
// boundary armed before the prompt was submitted
// still lets us name it unambiguously. Null is a
@@ -132,7 +176,9 @@ class ChatNotificationService : Service() {
// is strictly better than dismissing the wrong
// duplicate reply.
serverMessageId =
coalesceTurnRow(targetSessionId, event.text),
plan.correlationText?.let {
coalesceTurnRow(plan.sessionId, it)
},
)
// The wait is over — retire the foreground
// service. The reply notification above
@@ -216,6 +262,7 @@ class ChatNotificationService : Service() {
isReplyMessage: Boolean = false,
completionId: String? = null,
serverMessageId: Int? = null,
allowInlineReply: Boolean = true,
) {
val builder =
NotificationCompat
@@ -262,7 +309,7 @@ class ChatNotificationService : Service() {
)
}
if (!sessionId.isNullOrBlank()) {
if (allowInlineReply && !sessionId.isNullOrBlank()) {
val replyLabel = getString(R.string.notif_reply_placeholder)
val remoteInput =
RemoteInput