feat(chat): surface session terminal_backend (ssh/docker) in header (#869)

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-10 01:53:47 +03:00
1 parent 57dc4cdf5c
commit ea8fead087
4 files changed
+35 -1

No files matched your search

@@ -22,6 +22,7 @@ data class SessionInfo(
val parent_session_id: String? = null,
val display_name: String? = null,
val model: String? = null,
val terminal_backend: String? = null,
)
@Serializable
@@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Close
@@ -44,6 +45,7 @@ import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
@@ -423,6 +425,24 @@ fun ChatScreen(
.background(LocalHermesStatusColors.current.error),
)
}
// Terminal backend chip (issue #860) — ssh/docker/... next to the
// title; hidden when local (default) or absent.
val terminalBackend = state.terminalBackend
if (!terminalBackend.isNullOrBlank() && terminalBackend != "local") {
Spacer(modifier = Modifier.width(8.dp))
Surface(
shape = RoundedCornerShape(6.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
) {
Text(
text = terminalBackend,
modifier = Modifier.padding(horizontal = 6.dp, vertical = 1.dp),
style = MaterialTheme.typography.labelSmall,
maxLines = 1,
)
}
}
}
},
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
@@ -282,6 +282,7 @@ data class ChatUiState(
val currentSessionModel: String? = null,
// Reasoning effort level for the current session
val reasoningLevel: String? = null,
val terminalBackend: String? = null,
// Context-window meter (issue #756): tokens currently used by the session
// prompt (numerator) and the active model's full context window (denominator).
// Both null until the first successful fetch.
@@ -727,6 +728,7 @@ class ChatViewModel(
val model = info["model"] as? String
val provider = info["provider"] as? String
val reasoningEffort = info["reasoning_effort"] as? String
val terminalBackend = info["terminal_backend"] as? String
val newModelLabel =
if (model != null && provider != null) {
"$provider/$model"
@@ -752,6 +754,7 @@ class ChatViewModel(
} else {
reasoningEffort
},
terminalBackend = terminalBackend ?: state.terminalBackend,
fullContextTokens = if (modelSwapped) null else state.fullContextTokens,
)
}
@@ -988,6 +991,7 @@ class ChatViewModel(
val model = infoMap?.get("model") as? String
val provider = infoMap?.get("provider") as? String
val reasoningEffort = infoMap?.get("reasoning_effort") as? String
val terminalBackend = infoMap?.get("terminal_backend") as? String
// B8 (Jun 20 2026, kanban t_session_resume): do NOT reload
// cached messages here — switchSession() already did so before
@@ -1011,6 +1015,7 @@ class ChatViewModel(
} else {
reasoningEffort
},
terminalBackend = terminalBackend ?: it.terminalBackend,
)
}
// Mirror the active runtime session id app-wide (issue #532).
@@ -1983,6 +1988,7 @@ class ChatViewModel(
modelPickerLoading = false,
currentSessionModel = null,
reasoningLevel = null,
terminalBackend = null,
usedContextTokens = null,
fullContextTokens = null,
contextBreakdown = null,