feat(kanban): add typed models and KanbanApiService for desktop parity

This commit is contained in:
M57 committed 2026-09-14 22:12:30 +03:00
1 parent 7520008ae3
commit a486191d9d
10 files changed
+1307 -56

No files matched your search

@@ -1,44 +0,0 @@
package com.m57.hermescontrol.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class KanbanBoard(
@SerialName("slug") val id: String,
val name: String,
val description: String? = null,
)
@Serializable
data class KanbanTask(
val id: String,
val title: String,
@SerialName("body") val description: String? = null,
val status: String,
@SerialName("assignee") val assignedTo: String? = null,
)
@Serializable
data class CreateTaskBody(
val title: String,
val body: String? = null,
)
@Serializable
data class KanbanColumn(
val name: String,
val tasks: List<KanbanTask>,
)
@Serializable
data class KanbanBoardResponse(
val columns: List<KanbanColumn>,
val assignees: List<String>? = null,
val tenants: List<String>? = null,
)
@Serializable
data class KanbanBoardsResponse(
val boards: List<KanbanBoard>,
val current: String? = null,
)
@@ -0,0 +1,61 @@
package com.m57.hermescontrol.data.model
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
@Serializable
data class KanbanComment(
val id: Long,
val author: String,
val body: String,
val createdAt: Long,
)
@Serializable
data class KanbanDetailEvent(
val id: Long,
val kind: String,
val payload: JsonElement? = null,
val createdAt: Long,
)
@Serializable
data class KanbanRun(
val id: Long,
val profile: String? = null,
val status: String,
val outcome: String? = null,
val summary: String? = null,
val error: String? = null,
val metadata: JsonElement? = null,
val workerPid: Int? = null,
val startedAt: Long? = null,
val endedAt: Long? = null,
)
@Serializable
data class WorkerLog(
val taskId: String,
val path: String? = null,
val exists: Boolean = false,
val sizeBytes: Long = 0L,
val content: String = "",
val truncated: Boolean = false,
)
@Serializable
data class KanbanAttachment(
val id: Long,
val taskId: String? = null,
val filename: String,
val contentType: String? = null,
val size: Long? = null,
val uploadedBy: String? = null,
val storedPath: String? = null,
val createdAt: Long? = null,
)
@Serializable
data class AttachmentUploadResponse(
val attachment: KanbanAttachment? = null,
)
@@ -0,0 +1,94 @@
package com.m57.hermescontrol.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
@Serializable
data class KanbanBoard(
@SerialName("slug") val id: String,
val name: String? = null,
val description: String? = null,
val isCurrent: Boolean = false,
val total: Int? = null,
val counts: Map<String, Int> = emptyMap(),
val defaultWorkdir: String? = null,
val defaultWorkspaceKind: String? = null,
val projectId: String? = null,
val projectName: String? = null,
val createdAt: Long? = null,
val archived: Boolean = false,
) {
val slug: String get() = id
val displayName: String get() = name?.takeIf { it.isNotBlank() } ?: id
}
@Serializable
data class KanbanColumn(
val name: String,
val tasks: List<KanbanTask> = emptyList(),
)
@Serializable
data class KanbanBoardResponse(
val columns: List<KanbanColumn> = emptyList(),
val assignees: List<String>? = null,
val tenants: List<String>? = null,
val latestEventId: Long = 0L,
val now: Long = 0L,
)
@Serializable
data class KanbanBoardsResponse(
val boards: List<KanbanBoard> = emptyList(),
val current: String? = null,
)
@Serializable
data class KanbanProject(
val id: String,
val slug: String,
val name: String,
val primaryPath: String? = null,
val icon: String? = null,
val color: String? = null,
)
@Serializable
data class KanbanProjectsResponse(
val projects: List<KanbanProject> = emptyList(),
)
@Serializable
data class CreateBoardBody(
val slug: String,
val name: String? = null,
val description: String? = null,
val projectId: String? = null,
val switch: Boolean = false,
)
@Serializable
data class CreateBoardResponse(
val board: KanbanBoard? = null,
val current: String? = null,
)
@Serializable
data class RenameBoardBody(
val name: String? = null,
val description: String? = null,
val defaultWorkdir: String? = null,
val projectId: String? = null,
)
@Serializable
data class RenameBoardResponse(
val board: KanbanBoard? = null,
)
@Serializable
data class DeleteBoardResponse(
val result: JsonElement? = null,
val current: String? = null,
)
@@ -0,0 +1,70 @@
package com.m57.hermescontrol.data.model
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
@Serializable
data class KanbanProfile(
val name: String,
val isDefault: Boolean = false,
val description: String = "",
val descriptionAuto: Boolean = false,
val model: String? = null,
val provider: String? = null,
val skillCount: Int = 0,
)
@Serializable
data class KanbanProfilesResponse(
val profiles: List<KanbanProfile> = emptyList(),
)
@Serializable
data class OrchestrationSettings(
val orchestratorProfile: String = "",
val defaultAssignee: String = "",
val autoDecompose: Boolean = true,
val autoPromoteChildren: Boolean = true,
val resolvedOrchestratorProfile: String = "",
val resolvedDefaultAssignee: String = "",
val activeProfile: String = "default",
)
@Serializable
data class OrchestrationSettingsUpdate(
val orchestratorProfile: String? = null,
val defaultAssignee: String? = null,
val autoDecompose: Boolean? = null,
val autoPromoteChildren: Boolean? = null,
)
@Serializable
data class AutoDescribeResponse(
val ok: Boolean = false,
val profile: String? = null,
val reason: String? = null,
val description: String? = null,
)
@Serializable
data class BoardExportResult(
val board: String,
val archive: String,
val size: Long = 0L,
)
@Serializable
data class BoardImportResult(
val board: String,
val name: String = "",
val renamed: Boolean = false,
val requestedBoard: String = "",
val counts: Map<String, Int> = emptyMap(),
val warnings: List<String> = emptyList(),
val current: String? = null,
)
@Serializable
data class DispatchResult(
val spawned: List<JsonElement> = emptyList(),
)
@@ -0,0 +1,255 @@
package com.m57.hermescontrol.data.model
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
@Serializable
data class TaskLinkCounts(
val parents: Int = 0,
val children: Int = 0,
)
@Serializable
data class TaskProgress(
val done: Int = 0,
val total: Int = 0,
)
@Serializable
data class TaskWarnings(
val count: Int = 0,
val highestSeverity: String? = null,
)
@Serializable
data class TaskDiagnosticAction(
val kind: String,
val label: String,
val payload: Map<String, JsonElement> = emptyMap(),
val suggested: Boolean = false,
)
@Serializable
data class TaskDiagnostic(
val kind: String,
val severity: String,
val title: String,
val detail: String,
val actions: List<TaskDiagnosticAction> = emptyList(),
val count: Int = 1,
val lastSeenAt: Long = 0L,
val data: Map<String, JsonElement> = emptyMap(),
)
@Serializable
data class TaskLinks(
val parents: List<String> = emptyList(),
val children: List<String> = emptyList(),
)
@Serializable
data class ChildTaskResult(
val id: String,
val title: String,
val status: String,
val latestSummary: String? = null,
val result: String? = null,
)
@Serializable
data class KanbanTask(
val id: String,
val title: String,
val body: String? = null,
val status: String,
val assignee: String? = null,
val priority: Int? = null,
val tenant: String? = null,
val createdAt: Long? = null,
val latestSummary: String? = null,
val commentCount: Int = 0,
val linkCounts: TaskLinkCounts? = null,
val progress: TaskProgress? = null,
val warnings: TaskWarnings? = null,
val startedAt: Long? = null,
val workerPid: Int? = null,
val lastHeartbeatAt: Long? = null,
) {
val description: String? get() = body
val assignedTo: String? get() = assignee
constructor(
id: String,
title: String,
description: String? = null,
status: String,
assignedTo: String? = null,
) : this(
id = id,
title = title,
body = description,
status = status,
assignee = assignedTo,
)
}
@Serializable
data class KanbanTaskFull(
val id: String,
val title: String,
val body: String? = null,
val status: String,
val assignee: String? = null,
val priority: Int? = null,
val tenant: String? = null,
val createdAt: Long? = null,
val latestSummary: String? = null,
val commentCount: Int = 0,
val linkCounts: TaskLinkCounts? = null,
val progress: TaskProgress? = null,
val warnings: TaskWarnings? = null,
val startedAt: Long? = null,
val workerPid: Int? = null,
val lastHeartbeatAt: Long? = null,
val result: String? = null,
val createdBy: String? = null,
val modelOverride: String? = null,
val providerOverride: String? = null,
val reasoningEffort: String? = null,
val completedAt: Long? = null,
val lastFailureError: String? = null,
val workspaceKind: String? = null,
val workspacePath: String? = null,
val branchName: String? = null,
val consecutiveFailures: Int = 0,
val diagnostics: List<TaskDiagnostic> = emptyList(),
) {
val description: String? get() = body
val assignedTo: String? get() = assignee
}
@Serializable
data class KanbanTaskDetailResponse(
val task: KanbanTaskFull,
val comments: List<KanbanComment> = emptyList(),
val events: List<KanbanDetailEvent> = emptyList(),
val attachments: List<KanbanAttachment>? = null,
val links: TaskLinks = TaskLinks(),
val childResults: List<ChildTaskResult> = emptyList(),
val runs: List<KanbanRun> = emptyList(),
)
@Serializable
data class CreateTaskBody(
val title: String,
val body: String? = null,
val assignee: String? = null,
val tenant: String? = null,
val priority: Int = 0,
val workspaceKind: String? = null,
val workspacePath: String? = null,
val parents: List<String> = emptyList(),
val triage: Boolean = false,
val maxRuntimeSeconds: Int? = null,
val skills: List<String>? = null,
val goalMode: Boolean = false,
val goalMaxTurns: Int? = null,
val modelOverride: String? = null,
val providerOverride: String? = null,
val reasoningEffort: String? = null,
val projectId: String? = null,
)
@Serializable
data class CreateTaskResponse(
val task: KanbanTask? = null,
val warning: String? = null,
)
@Serializable
data class UpdateTaskBody(
val status: String? = null,
val assignee: String? = null,
val priority: Int? = null,
val title: String? = null,
val body: String? = null,
val result: String? = null,
val blockReason: String? = null,
val summary: String? = null,
val metadata: Map<String, JsonElement>? = null,
val modelOverride: String? = null,
val providerOverride: String? = null,
val clearModelOverride: Boolean = false,
val reasoningEffort: String? = null,
val clearReasoningEffort: Boolean = false,
)
@Serializable
data class UpdateTaskResponse(
val task: KanbanTask? = null,
)
@Serializable
data class BulkTasksBody(
val ids: List<String>,
val status: String? = null,
val assignee: String? = null,
val priority: Int? = null,
val archive: Boolean = false,
val result: String? = null,
val summary: String? = null,
val metadata: Map<String, JsonElement>? = null,
val reclaimFirst: Boolean = false,
val modelOverride: String? = null,
val providerOverride: String? = null,
val clearModelOverride: Boolean = false,
val reasoningEffort: String? = null,
val clearReasoningEffort: Boolean = false,
)
@Serializable
data class BulkTaskResult(
val id: String,
val ok: Boolean,
val error: String? = null,
)
@Serializable
data class BulkTasksResponse(
val results: List<BulkTaskResult> = emptyList(),
)
@Serializable
data class ReassignTaskBody(
val profile: String? = null,
val reclaimFirst: Boolean = true,
val reason: String? = null,
)
@Serializable
data class ReassignTaskResponse(
val ok: Boolean = true,
val taskId: String,
val assignee: String? = null,
)
@Serializable
data class ReclaimTaskBody(
val reason: String? = null,
)
@Serializable
data class ReclaimTaskResponse(
val ok: Boolean = true,
val taskId: String,
)
@Serializable
data class TaskEstimate(
val ok: Boolean,
val reason: String? = null,
val estTokens: Int? = null,
val complexity: String? = null,
val rationale: String? = null,
val model: String? = null,
)