From abb305a82fd1545d9d19bba7f5fce8cbe05f2123 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 20 Apr 2026 10:36:09 +0800 Subject: [PATCH] feat(hermes): render providers: dict overlays as read-only cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProviderCard now detects Hermes provider entries sourced from the v12+ `providers:` dict via the `_cc_source` marker that the backend injects, and renders a "Hermes Managed" badge beside the title. ProviderActions receives an `isReadOnly` prop that disables the Edit and Delete buttons (with a tooltip pointing the user at Hermes Web UI) while keeping Switch and Duplicate enabled — switching only touches `model.*`, and duplicate lets users fork the overlay into their own `custom_providers:` list. Three-locale i18n keys `provider.managedByHermes` / `provider.managedByHermesHint` added. --- src/components/providers/ProviderActions.tsx | 21 +++++++++++++++----- src/components/providers/ProviderCard.tsx | 20 +++++++++++++++++++ src/i18n/locales/en.json | 2 ++ src/i18n/locales/ja.json | 2 ++ src/i18n/locales/zh.json | 2 ++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/components/providers/ProviderActions.tsx b/src/components/providers/ProviderActions.tsx index 50872decf..30dfe3d90 100644 --- a/src/components/providers/ProviderActions.tsx +++ b/src/components/providers/ProviderActions.tsx @@ -38,6 +38,8 @@ interface ProviderActionsProps { isInFailoverQueue?: boolean; onToggleFailover?: (enabled: boolean) => void; isOfficialBlockedByProxy?: boolean; + // Hermes v12+ providers: dict overlay — edit/delete must go through Web UI + isReadOnly?: boolean; // OpenClaw: default model isDefaultModel?: boolean; onSetAsDefault?: () => void; @@ -63,6 +65,7 @@ export function ProviderActions({ isInFailoverQueue = false, onToggleFailover, isOfficialBlockedByProxy = false, + isReadOnly = false, // OpenClaw: default model isDefaultModel = false, onSetAsDefault, @@ -205,7 +208,11 @@ export function ProviderActions({ const buttonState = getMainButtonState(); - const canDelete = isOmo || isAdditiveMode ? true : !isCurrent; + const canDelete = + !isReadOnly && (isOmo || isAdditiveMode ? true : !isCurrent); + const readOnlyHint = t("provider.managedByHermesHint", { + defaultValue: "由 Hermes 管理,请在 Hermes Web UI 中编辑", + }); return (
@@ -255,9 +262,13 @@ export function ProviderActions({ @@ -323,7 +334,7 @@ export function ProviderActions({ size="icon" variant="ghost" onClick={canDelete ? onDelete : undefined} - title={t("common.delete")} + title={isReadOnly ? readOnlyHint : t("common.delete")} className={cn( iconButtonClass, canDelete && "hover:text-red-500 dark:hover:text-red-400", diff --git a/src/components/providers/ProviderCard.tsx b/src/components/providers/ProviderCard.tsx index 37d33303a..a54d3da15 100644 --- a/src/components/providers/ProviderCard.tsx +++ b/src/components/providers/ProviderCard.tsx @@ -180,6 +180,12 @@ export function ProviderCard({ const isCopilot = provider.meta?.providerType === PROVIDER_TYPES.GITHUB_COPILOT || provider.meta?.usage_script?.templateType === "github_copilot"; + // Hermes v12+ overlay entries live under the `providers:` dict and are + // read-only here — writes have to go through Hermes Web UI. + const isHermesReadOnly = + appId === "hermes" && + (provider.settingsConfig as Record)?._cc_source === + "providers_dict"; const isCodexOauth = provider.meta?.providerType === PROVIDER_TYPES.CODEX_OAUTH; @@ -336,6 +342,19 @@ export function ProviderCard({ ⭐ )} + + {isHermesReadOnly && ( + + {t("provider.managedByHermes", { + defaultValue: "Hermes Managed", + })} + + )}
{displayUrl && ( @@ -429,6 +448,7 @@ export function ProviderCard({ isTesting={isTesting} isProxyTakeover={isProxyTakeover} isOfficialBlockedByProxy={isOfficialBlockedByProxy} + isReadOnly={isHermesReadOnly} isOmo={isAnyOmo} onSwitch={() => onSwitch(provider)} onEdit={() => onEdit(provider)} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index ecc8e8b5e..f3616a1d3 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -141,6 +141,8 @@ "sortUpdateFailed": "Failed to update sort order", "configureUsage": "Configure usage query", "officialPartner": "Official Partner", + "managedByHermes": "Hermes Managed", + "managedByHermesHint": "Defined in Hermes' providers: dict. Edit or remove it via Hermes Web UI.", "openTerminal": "Open Terminal", "terminalOpened": "Terminal opened", "terminalOpenFailed": "Failed to open terminal", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 657a12fe6..1389b7ce6 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -141,6 +141,8 @@ "sortUpdateFailed": "並び順の更新に失敗しました", "configureUsage": "利用状況を設定", "officialPartner": "公式パートナー", + "managedByHermes": "Hermes 管理", + "managedByHermesHint": "Hermes の providers: dict で定義されています。Hermes Web UI で編集または削除してください。", "openTerminal": "ターミナルを開く", "terminalOpened": "ターミナルを開きました", "terminalOpenFailed": "ターミナルを開けませんでした", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 7017fab4e..74ec9e379 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -141,6 +141,8 @@ "sortUpdateFailed": "排序更新失败", "configureUsage": "配置用量查询", "officialPartner": "官方合作伙伴", + "managedByHermes": "Hermes 托管", + "managedByHermesHint": "该条目定义在 Hermes 的 providers: dict,请在 Hermes Web UI 中编辑或删除。", "openTerminal": "打开终端", "terminalOpened": "终端已打开", "terminalOpenFailed": "打开终端失败",