feat(ui): add Bots and Groups tab strip with GroupCard and disband action (#987)

This commit is contained in:
M57 authored and GitHub committed 2026-08-27 01:53:54 +03:00
1 parent 6ca248d28b
commit 2fed891d78
9 files changed
+497 -92

No files matched your search

@@ -69,8 +69,8 @@ data class CanonicalSessionInfo(
val root_title: String? = null,
val title: String? = null,
val preview: String? = null,
val started_at: Long? = null,
val last_active: Long? = null,
val started_at: Double? = null,
val last_active: Double? = null,
val message_count: Int? = null,
)
@@ -79,8 +79,8 @@ data class ProfileSessionSummary(
val id: String,
val title: String? = null,
val preview: String? = null,
val started_at: Long? = null,
val last_active: Long? = null,
val started_at: Double? = null,
val last_active: Double? = null,
val message_count: Int? = null,
)
@@ -89,7 +89,7 @@ data class ProfileWorkerSummary(
val id: String,
val source: String? = null,
val title: String? = null,
val last_active: Long? = null,
val last_active: Double? = null,
)
@Serializable
@@ -100,7 +100,7 @@ data class BotRosterMeta(
val hidden: Boolean? = null,
val groups: List<String>? = null,
val group: String? = null,
val created: Long? = null,
val created: Double? = null,
)
@Serializable
@@ -939,6 +939,16 @@ object HermesWsClient {
val event =
try {
val rpc = OkHttpProvider.json.decodeFromString<JsonRpcResponse>(text)
// Resolve pending request() calls with the RAW JsonElement from
// rpc.result, BEFORE EventParser.parse() converts it via toAny().
// This preserves integer/float type fidelity needed by callers
// that re-deserialize the result into typed data classes.
val rpcId = rpc.id
if (rpcId != null && rpc.error == null && rpc.result != null) {
synchronized(outboundLock) { pendingPromptSubmits.remove(rpcId) }
removeQueuedMessage(rpcId)
resolvePending(rpcId, rpc.result, null)
}
val parsed = EventParser.parse(rpc, text)
if (parsed is WsEvent.MessageComplete) {
parsed.copy(
@@ -8,6 +8,7 @@ import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.doubleOrNull
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.longOrNull
/**
@@ -46,6 +47,19 @@ fun Any?.toJsonElement(): JsonElement =
null -> JsonNull
is JsonElement -> this
is Boolean -> JsonPrimitive(this)
is Int -> JsonPrimitive(this)
is Long -> JsonPrimitive(this)
is Float -> JsonPrimitive(this)
is Double -> {
// Preserve integer semantics: 177.0 → JsonPrimitive(177) not "177.0"
if (this % 1.0 == 0.0 && this >= Int.MIN_VALUE.toDouble() && this <= Int.MAX_VALUE.toDouble()) {
JsonPrimitive(this.toInt())
} else if (this % 1.0 == 0.0 && this >= Long.MIN_VALUE.toDouble() && this <= Long.MAX_VALUE.toDouble()) {
JsonPrimitive(this.toLong())
} else {
JsonPrimitive(this)
}
}
is Number -> JsonPrimitive(this)
is String -> JsonPrimitive(this)
is Map<*, *> -> JsonObject(this.map { it.key.toString() to it.value.toJsonElement() }.toMap())
@@ -63,7 +77,7 @@ fun JsonElement.toAny(): Any? =
if (isString) {
content
} else {
booleanOrNull ?: doubleOrNull ?: longOrNull ?: content
booleanOrNull ?: intOrNull ?: longOrNull ?: doubleOrNull ?: content
}
}