feat(sessions): show project and last activity on history cards (#1133)
This commit is contained in:
1 parent
529b424eb4
commit
aed8ba4a6d
25 files changed
+1617
-80
No files matched your search
@@ -0,0 +1,39 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Result of the `projects.list` JSON-RPC: the profile's named workspaces. */
|
||||
@Serializable
|
||||
data class ProjectsListResponse(
|
||||
val projects: List<ProjectInfo> = emptyList(),
|
||||
val activeId: String? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* A user-created project from the backend's per-profile `projects.db`. A
|
||||
* session belongs to the project whose folder contains its cwd or repo root.
|
||||
*/
|
||||
@Serializable
|
||||
data class ProjectInfo(
|
||||
val id: String,
|
||||
val name: String = "",
|
||||
val slug: String? = null,
|
||||
// CSS color string chosen in the desktop app, e.g. "hsl(210 68% 58%)".
|
||||
val color: String? = null,
|
||||
val icon: String? = null,
|
||||
val primaryPath: String? = null,
|
||||
// Older backends store SQLite 0/1 here.
|
||||
@Serializable(with = LenientNullableBooleanSerializer::class)
|
||||
val archived: Boolean? = null,
|
||||
val folders: List<ProjectFolder> = emptyList(),
|
||||
) {
|
||||
val isArchived: Boolean get() = archived == true
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ProjectFolder(
|
||||
val path: String,
|
||||
val label: String? = null,
|
||||
@Serializable(with = LenientNullableBooleanSerializer::class)
|
||||
val isPrimary: Boolean? = null,
|
||||
)
|
||||
@@ -79,6 +79,10 @@ data class SessionInfo(
|
||||
val display_name: String? = null,
|
||||
val model: String? = null,
|
||||
val terminal_backend: String? = null,
|
||||
// Workspace the session ran in. The project label resolves from these
|
||||
// (explicit project folder, else the repo root, else the cwd leaf).
|
||||
val cwd: String? = null,
|
||||
val git_repo_root: String? = null,
|
||||
// Epoch seconds of the latest message (backend-computed, present on all
|
||||
// list rows). Recency source of truth; started_at is the fallback.
|
||||
val last_active: Double? = null,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.m57.hermescontrol.data.ws
|
||||
|
||||
import com.m57.hermescontrol.data.model.ProjectInfo
|
||||
import com.m57.hermescontrol.data.model.ProjectsListResponse
|
||||
import com.m57.hermescontrol.data.remote.OkHttpProvider
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
/** Supplies the profile's named projects so history rows can show which one a chat belongs to. */
|
||||
interface ProjectsSource {
|
||||
/** The project list, or null when the backend can't answer (offline, older server). */
|
||||
suspend fun fetchProjects(): List<ProjectInfo>?
|
||||
}
|
||||
|
||||
/** [ProjectsSource] backed by the gateway's `projects.list` JSON-RPC. */
|
||||
class HermesProjectsSource(
|
||||
// Only ask a live socket: an offline request would be queued and force a reconnect on
|
||||
// every refresh. The view model refetches when the connection comes back.
|
||||
private val isConnected: () -> Boolean = { HermesWsClient.connectionStatus.value == ConnectionStatus.CONNECTED },
|
||||
private val rpcRequest: suspend (method: String, params: Map<String, Any>) -> Any? = { method, params ->
|
||||
HermesWsClient.request(method, params).await()
|
||||
},
|
||||
) : ProjectsSource {
|
||||
override suspend fun fetchProjects(): List<ProjectInfo>? {
|
||||
if (!isConnected()) return null
|
||||
return try {
|
||||
val element = SessionLiveStatusDecoder.anyToJsonElement(rpcRequest(WsMethods.PROJECTS_LIST, emptyMap()))
|
||||
if (element is JsonObject && element.containsKey("projects")) {
|
||||
OkHttpProvider.json.decodeFromJsonElement(ProjectsListResponse.serializer(), element).projects
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,11 @@ object WsMethods {
|
||||
const val PROFILES_CONFIGURE = "profiles.configure"
|
||||
const val PROFILES_LIST = "profiles.list"
|
||||
|
||||
// ── Projects ──────────────────────────────────────────────────────────
|
||||
|
||||
/** Named workspaces (id, name, color, folders) used to label history rows. */
|
||||
const val PROJECTS_LIST = "projects.list"
|
||||
|
||||
// ── Interaction ───────────────────────────────────────────────────────
|
||||
const val PROMPT_SUBMIT = "prompt.submit"
|
||||
const val PROMPT_BTW = "prompt.btw"
|
||||
@@ -159,6 +164,7 @@ object WsMethods {
|
||||
// Binds HERMES_HOME so config/skills/pets resolve to the
|
||||
// focused profile.
|
||||
"verification.status", // line 282
|
||||
PROJECTS_LIST, // methods_projects.py: @_registry.profile_scoped
|
||||
"pet.info", // line 1253
|
||||
"pet.info.meta", // line 1279
|
||||
"pet.cells", // line 1302
|
||||
|
||||
Reference in new issue
Block a user