fix(sessions): send profile in destructive bodies and prune the asked window (#1398)

Co-authored-by: M57 <41645758+Hy4ri@users.noreply.github.com>
This commit is contained in:
Nikhil NairandM57 authored and GitHub committed 2026-09-30 11:27:40 +03:00
1 parent 012440b8f7
commit 7a2822fade
9 files changed
+107 -4

No files matched your search

@@ -123,7 +123,10 @@ data class SessionRenameRequest(
@Serializable
data class BulkDeleteRequest(
val ids: List<String>,
val delete_all: Boolean = false,
// Destructive POSTs read the profile off the BODY (not the ?profile=
// query rewrite) — an unnamed bulk delete is refused with 400 on a
// multi-profile backend. Null stays valid for single-profile hosts.
val profile: String? = null,
)
@Serializable
@@ -134,5 +137,13 @@ data class BulkDeleteResponse(
@Serializable
data class PruneRequest(
val days: Int,
// Server contract: `older_than_days` (hermes_cli/web_models.py SessionPrune).
// The old `days` key was unknown to the backend, so the user's choice was
// silently dropped and the server pruned its own 90-day default.
@SerialName("older_than_days") val olderThanDays: Int,
// Destructive POSTs read the profile off the BODY (not the ?profile= query
// rewrite): on a multi-profile backend an unnamed prune is refused with
// 400 (web_routers/_common.py destructive_profile). Null stays valid for
// single-profile hosts.
val profile: String? = null,
)
@@ -33,6 +33,10 @@ object ProfileScopeInterceptor : Interceptor {
// multi-profile host transcribes under the launch profile
// (review, PR #1250).
"/api/audio",
// Curator state is per profile (config-scoped reads) and the run
// trigger is privileged: `POST /api/curator/run` is refused with
// 400 on a multi-profile backend without ?profile=.
"/api/curator",
"/api/config",
"/api/credentials/pool",
"/api/cron",
@@ -66,6 +66,7 @@ import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
@@ -507,6 +508,24 @@ fun SessionsScreen(
// (The scaffold already applies top-bar padding via its inner Box, so we
// must NOT re-apply paddingValues here.)
Column(modifier = Modifier.fillMaxSize()) {
// Prune can take a while on large histories — show it's working.
if (state.isPruning) {
Column(
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = spacing.md, vertical = spacing.sm)
.testTag("sessions_pruning_indicator"),
verticalArrangement = Arrangement.spacedBy(spacing.xs),
) {
Text(
text = stringResource(R.string.sessions_pruning),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
}
}
Row(
modifier =
Modifier
@@ -963,7 +963,13 @@ class SessionsViewModel(
val result =
safeApiCall {
ApiClient.hermesApi.bulkDeleteSessions(
body = BulkDeleteRequest(ids = ids),
body =
BulkDeleteRequest(
ids = ids,
// Destructive POSTs scope by BODY profile; the
// ?profile= query rewrite never reaches them.
profile = AuthManager.activeProfileId.value,
),
)
}
when (result) {
@@ -1021,7 +1027,16 @@ class SessionsViewModel(
val result =
safeApiCall {
ApiClient.hermesApi.pruneSessions(
body = PruneRequest(days = days),
body =
PruneRequest(
// The server model is `older_than_days`; the
// old `days` key was silently ignored and the
// backend pruned its own 90-day default.
olderThanDays = days,
// Destructive POSTs scope by BODY profile; the
// ?profile= query rewrite never reaches them.
profile = AuthManager.activeProfileId.value,
),
)
}
when (result) {