feat(sessions): empty-session cleanup + latest-descendant resume + fresh-session 404 fix (#787, #840) (#841)
* feat(sessions): empty-session cleanup button + latest-descendant resume + fresh-session 404 fix (issues #787, #840) - Sessions top bar: Delete-empty icon with count badge, grey/disabled when 0 (GET /api/sessions/empty/count + DELETE /api/sessions/empty, confirm dialog) - Resume flow resolves GET /api/sessions/{id}/latest-descendant first — branched sessions continue in the newest child leaf, not the stale parent (best-effort; falls back to the requested id) - syncCurrentSession guarded by sessionHasServerPresence — a freshly created (never prompted) session has no server row yet; the REST transcript 404s and the resume retry spun forever (logcat 404 evidence) * fix(sessions): clamp message-count fallback to backend limit cap (422) fetchServerMessageCount requested limit=500 but the backend caps session list page size at 100 (sessions.py Query le=100) — every fallback scan 422'd (seen in device logcat after a branch). Sessions are ordered recent, so the target is always in the top page. * test(slash): make dispatch-capture assertions order-independent (CI flake) The capture-race flake (SlashCommandDispatchRpcTest, hit 3x across CI and local runs 2026-08-07): tests asserted on methodSlot.captured — the LAST request captured — but the create flow's session.usage/context_breakdown requests can land after the slash-dispatch one depending on suite order (the class's own comment documents this cross-wiring). Converted the three capture-based tests to capture ALL requests and find the target record by method, with dispatch-before-fallback ordering for the 4018 test. Same assertions, no dependence on request interleaving. * test(slash): convert remaining capture-race assertions (flake round 2) The capture race moved to the focus/init tests (:424) after the first three were fixed — every request-capture test in the class used the same last-captured assumption. Converted the remaining five (init, init+arg, focus, focus on, type=send) to the find-record-by-method pattern; the type=send answers block reads methodCalls.last() since the capture precedes the answer. Zero request() slot captures remain. * test(slash): blocklist assertion counts command RPCs only (flake round 3) The blocklisted-command test counted ALL request() calls and asserted zero — the create flow's session.usage/context_breakdown noise can land in the window after the stub registers (the same capture race that hit the dispatch tests). Asserted instead that no COMMAND_DISPATCH or SLASH_EXEC fires, which is the actual blocklist contract. E2E's static Dispatchers mock stays (E2E VMs need IO routed to the test dispatcher — removing it broke 20 E2E tests). --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
7998248507
commit
f20b50b48f
7 files changed
+274
-48
No files matched your search
@@ -27,6 +27,8 @@ import com.m57.hermescontrol.data.model.DebugShareResponse
|
||||
import com.m57.hermescontrol.data.model.DeleteWebhookResponse
|
||||
import com.m57.hermescontrol.data.model.DeliveryTargetsResponse
|
||||
import com.m57.hermescontrol.data.model.DoctorResponse
|
||||
import com.m57.hermescontrol.data.model.EmptySessionsCountResponse
|
||||
import com.m57.hermescontrol.data.model.EmptySessionsDeleteResponse
|
||||
import com.m57.hermescontrol.data.model.EnvVarConfig
|
||||
import com.m57.hermescontrol.data.model.EnvVarDeleteRequest
|
||||
import com.m57.hermescontrol.data.model.EnvVarRevealRequest
|
||||
@@ -37,6 +39,7 @@ import com.m57.hermescontrol.data.model.HookResponse
|
||||
import com.m57.hermescontrol.data.model.InstantiateBlueprintRequest
|
||||
import com.m57.hermescontrol.data.model.KanbanBoardResponse
|
||||
import com.m57.hermescontrol.data.model.KanbanBoardsResponse
|
||||
import com.m57.hermescontrol.data.model.LatestDescendantResponse
|
||||
import com.m57.hermescontrol.data.model.LearningGraphResponse
|
||||
import com.m57.hermescontrol.data.model.LogResponse
|
||||
import com.m57.hermescontrol.data.model.ManagedDirectoryCreate
|
||||
@@ -217,6 +220,18 @@ interface HermesApiService {
|
||||
@Body body: PruneRequest,
|
||||
): Response<Unit>
|
||||
|
||||
// ── Admin: Empty-session cleanup + branch resolution (issue #787) ──
|
||||
@GET("api/sessions/empty/count")
|
||||
suspend fun getEmptySessionCount(): Response<EmptySessionsCountResponse>
|
||||
|
||||
@DELETE("api/sessions/empty")
|
||||
suspend fun deleteEmptySessions(): Response<EmptySessionsDeleteResponse>
|
||||
|
||||
@GET("api/sessions/{id}/latest-descendant")
|
||||
suspend fun getLatestDescendant(
|
||||
@Path("id", encoded = true) sessionId: String,
|
||||
): Response<LatestDescendantResponse>
|
||||
|
||||
@GET("api/sessions/{id}/prompt")
|
||||
suspend fun getSessionPrompt(
|
||||
// Preserve slashes in session IDs — backend generates IDs containing '/' characters (issue #468).
|
||||
|
||||
Reference in new issue
Block a user