feat(#443): Telegram onboarding, remove platform, credential expiry, field validation (#467)

* feat(#443): Telegram onboarding, remove platform, credential expiry, gateway command, field validation

- Add Telegram onboarding wizard: start (QR), poll status, cancel, apply
  with allowed-user-IDs flow. Full state machine in ChannelsViewModel.
- Add remove platform button with confirmation dialog (per card).
- Add credential expiry display in env var config fields.
- Add gateway_start_command to the offline banner with monospace hint.
- Add field validation for Slack token prefixes and member IDs.
- Add admin section showing env path and gateway command.
- Add DELETE, POST, GET API endpoints for onboarding + remove platform
  + action status polling.
- Extend MessagingPlatform model with envVar expiresAt, homeChannel,
  updatedAt fields.

* fix(#443): address review feedback + fix CI compilation & ktlint

- Remove duplicate getActionStatus overload (compilation blocker)
- Add missing Box import in ChannelsScreen (compilation blocker)
- Remove unused gatewayCommand param from AdminSection
- Centralize ISO timestamp parsing with UTC timezone
- Clean up WAITING phase dialog (remove redundant confirmButton)
- Use LocalHermesStatusColors for semantic surfaces
- Fix all ktlint violations in changed files

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-07-04 13:22:49 +03:00
1 parent b46e0ef46d
commit 9689d17be0
4 files changed
+847 -32

No files matched your search

@@ -14,6 +14,15 @@ data class MessagingPlatform(
@SerializedName("error_code") val errorCode: String? = null,
@SerializedName("error_message") val errorMessage: String? = null,
@SerializedName("env_vars") val envVars: List<EnvVarField>? = null,
@SerializedName("updated_at") val updatedAt: String? = null,
@SerializedName("home_channel") val homeChannel: HomeChannelInfo? = null,
)
data class HomeChannelInfo(
val platform: String,
@SerializedName("chat_id") val chatId: String,
val name: String,
@SerializedName("thread_id") val threadId: String? = null,
)
data class EnvVarField(
@@ -27,6 +36,7 @@ data class EnvVarField(
@SerializedName("is_password") val isPassword: Boolean,
val advanced: Boolean = false,
val url: String? = null,
@SerializedName("expires_at") val expiresAt: String? = null,
)
data class MessagingPlatformResponse(
@@ -47,3 +57,40 @@ data class MessagingPlatformTestResult(
val state: String,
val message: String,
)
// ── Telegram onboarding ─────────────────────────────────────────────────
data class TelegramOnboardingStartRequest(
@SerializedName("bot_name") val botName: String = "Hermes Agent",
)
data class TelegramOnboardingStartResponse(
@SerializedName("pairing_id") val pairingId: String,
@SerializedName("suggested_username") val suggestedUsername: String,
@SerializedName("deep_link") val deepLink: String,
@SerializedName("qr_payload") val qrPayload: String,
@SerializedName("expires_at") val expiresAt: String,
)
data class TelegramOnboardingStatusResponse(
val status: String,
@SerializedName("bot_username") val botUsername: String? = null,
@SerializedName("owner_user_id") val ownerUserId: String? = null,
@SerializedName("expires_at") val expiresAt: String? = null,
)
data class TelegramOnboardingApplyRequest(
@SerializedName("allowed_user_ids") val allowedUserIds: List<String>,
val profile: String? = null,
)
data class TelegramOnboardingApplyResponse(
val ok: Boolean,
val platform: String,
@SerializedName("bot_username") val botUsername: String? = null,
@SerializedName("needs_restart") val needsRestart: Boolean,
@SerializedName("restart_started") val restartStarted: Boolean? = null,
@SerializedName("restart_action") val restartAction: String? = null,
@SerializedName("restart_pid") val restartPid: Int? = null,
@SerializedName("restart_error") val restartError: String? = null,
)
@@ -73,6 +73,11 @@ import com.m57.hermescontrol.data.model.SkillHubUninstallRequest
import com.m57.hermescontrol.data.model.SkillScanResponse
import com.m57.hermescontrol.data.model.StatusResponse
import com.m57.hermescontrol.data.model.SystemStatsResponse
import com.m57.hermescontrol.data.model.TelegramOnboardingApplyRequest
import com.m57.hermescontrol.data.model.TelegramOnboardingApplyResponse
import com.m57.hermescontrol.data.model.TelegramOnboardingStartRequest
import com.m57.hermescontrol.data.model.TelegramOnboardingStartResponse
import com.m57.hermescontrol.data.model.TelegramOnboardingStatusResponse
import com.m57.hermescontrol.data.model.ToggleSkillRequest
import com.m57.hermescontrol.data.model.Toolset
import com.m57.hermescontrol.data.model.ToolsetToggleRequest
@@ -499,6 +504,32 @@ interface HermesApiService {
@Path("platform_id") platformId: String,
): Response<MessagingPlatformTestResult>
@DELETE("api/messaging/platforms/{platform_id}")
suspend fun removeMessagingPlatform(
@Path("platform_id") platformId: String,
): Response<Unit>
@POST("api/messaging/telegram/onboarding/start")
suspend fun startTelegramOnboarding(
@Body body: TelegramOnboardingStartRequest,
): Response<TelegramOnboardingStartResponse>
@GET("api/messaging/telegram/onboarding/{pairing_id}")
suspend fun getTelegramOnboardingStatus(
@Path("pairing_id") pairingId: String,
): Response<TelegramOnboardingStatusResponse>
@POST("api/messaging/telegram/onboarding/{pairing_id}/apply")
suspend fun applyTelegramOnboarding(
@Path("pairing_id") pairingId: String,
@Body body: TelegramOnboardingApplyRequest,
): Response<TelegramOnboardingApplyResponse>
@HTTP(method = "DELETE", path = "api/messaging/telegram/onboarding/{pairing_id}", hasBody = false)
suspend fun cancelTelegramOnboarding(
@Path("pairing_id") pairingId: String,
): Response<Unit>
@GET("api/env")
suspend fun getEnvVars(): Response<Map<String, EnvVarConfig>>