feat: add Hermes frontend types, API layer, and hooks (Phase 7)

- Add "hermes" to AppId union type and all exhaustive Record<AppId>
- Add HermesModelConfig, HermesAgentConfig, HermesEnvConfig types
- Add hermes field to VisibleApps, McpApps, ProxyTakeoverStatus
- Create src/lib/api/hermes.ts with Tauri invoke wrappers
- Create src/hooks/useHermes.ts with 5 query + 3 mutation hooks
- Register hermes in APP_IDS, APP_ICON_MAP (violet color scheme)
- Split MCP_SKILLS_APP_IDS into MCP_APP_IDS (includes hermes) and
  SKILLS_APP_IDS (excludes hermes, since Hermes has no Skills support)
- Wire hermes additive-mode into App.tsx (remove/duplicate handlers),
  ProviderList.tsx (live provider ID query + In Config badge),
  mutations.ts (cache invalidation on switch/add/delete)
- Add Hermes checkbox to McpFormModal
- Add basic hermes i18n keys (en/zh/ja)
This commit is contained in:
Jason
2026-04-15 17:41:33 +08:00
parent 576ff53a75
commit a0b585992a
23 changed files with 467 additions and 33 deletions
+25 -5
View File
@@ -39,6 +39,7 @@ import {
import { checkAllEnvConflicts, checkEnvConflicts } from "@/lib/api/env";
import { useProviderActions } from "@/hooks/useProviderActions";
import { openclawKeys, useOpenClawHealth } from "@/hooks/useOpenClaw";
import { hermesKeys } from "@/hooks/useHermes";
import { useProxyStatus } from "@/hooks/useProxyStatus";
import { useAutoCompact } from "@/hooks/useAutoCompact";
import { useLastValidValue } from "@/hooks/useLastValidValue";
@@ -114,6 +115,7 @@ const VALID_APPS: AppId[] = [
"gemini",
"opencode",
"openclaw",
"hermes",
];
const getInitialApp = (): AppId => {
@@ -174,6 +176,7 @@ function App() {
gemini: true,
opencode: true,
openclaw: true,
hermes: true,
};
const getFirstVisibleApp = (): AppId => {
@@ -182,6 +185,7 @@ function App() {
if (visibleApps.gemini) return "gemini";
if (visibleApps.opencode) return "opencode";
if (visibleApps.openclaw) return "openclaw";
if (visibleApps.hermes) return "hermes";
return "claude"; // fallback
};
@@ -654,6 +658,13 @@ function App() {
await queryClient.invalidateQueries({
queryKey: openclawKeys.health,
});
} else if (activeApp === "hermes") {
await queryClient.invalidateQueries({
queryKey: hermesKeys.liveProviderIds,
});
await queryClient.invalidateQueries({
queryKey: hermesKeys.health,
});
}
toast.success(
t("notifications.removeFromConfigSuccess", {
@@ -704,7 +715,11 @@ function App() {
iconColor: provider.iconColor,
};
if (activeApp === "opencode" || activeApp === "openclaw") {
if (
activeApp === "opencode" ||
activeApp === "openclaw" ||
activeApp === "hermes"
) {
let liveProviderIds: string[] = [];
try {
liveProviderIds =
@@ -713,10 +728,15 @@ function App() {
queryKey: ["opencodeLiveProviderIds"],
queryFn: () => providersApi.getOpenCodeLiveProviderIds(),
})
: await queryClient.ensureQueryData({
queryKey: openclawKeys.liveProviderIds,
queryFn: () => providersApi.getOpenClawLiveProviderIds(),
});
: activeApp === "openclaw"
? await queryClient.ensureQueryData({
queryKey: openclawKeys.liveProviderIds,
queryFn: () => providersApi.getOpenClawLiveProviderIds(),
})
: await queryClient.ensureQueryData({
queryKey: hermesKeys.liveProviderIds,
queryFn: () => providersApi.getHermesLiveProviderIds(),
});
} catch (error) {
console.error(
"[App] Failed to load live provider IDs for duplication",