Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions packages/desktop/src/renderer/pages/guid/hooks/useGuidSend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { getConversationCreateErrorMessage } from '@/renderer/pages/conversation
import type { AcpModelInfo, AvailableAgent, EffectiveAgentInfo } from '../types';

const EVAOS_MAC_CONTROL_AGENT_PATTERN = /(evaos|openclaw)/i;
const OPENCLAW_AGENT_TYPES = new Set(['openclaw', 'openclaw-gateway']);

function isBuiltinEvaosMacControlServer(server: IMcpServer): boolean {
return (
Expand Down Expand Up @@ -54,6 +55,15 @@ function isEvaosMacControlAgent(params: {
return candidates.some((candidate) => EVAOS_MAC_CONTROL_AGENT_PATTERN.test(String(candidate || '')));
}

function isOpenClawAgent(params: {
selectedAgentInfo: AvailableAgent | undefined;
effectiveAgentType: string;
}): boolean {
const { selectedAgentInfo, effectiveAgentType } = params;
const candidates = [effectiveAgentType, selectedAgentInfo?.agent_type, selectedAgentInfo?.backend];
return candidates.some((candidate) => OPENCLAW_AGENT_TYPES.has(String(candidate || '').toLowerCase()));
}

function appendEvaosMacControlMcpId(
ids: string[] | undefined,
availableMcpServers: IMcpServer[],
Expand Down Expand Up @@ -212,12 +222,14 @@ export const useGuidSend = (deps: GuidSendDeps): GuidSendResult => {
const excludeBuiltinSkills =
guidDisabledBuiltinSkills ??
(is_presetAgent ? assistantDefaultDisabledBuiltinSkillIds : resolveDisabledBuiltinSkills(agentInfo));
const shouldAttachEvaosMacControl = isEvaosMacControlAgent({
const agentIdentity = {
selectedAgent,
selectedAgentKey,
selectedAgentInfo: agentInfo,
effectiveAgentType,
});
};
const isOpenClaw = isOpenClawAgent(agentIdentity);
const shouldAttachEvaosMacControl = !isOpenClaw && isEvaosMacControlAgent(agentIdentity);
const selectedAllMcpServerIds = appendEvaosMacControlMcpId(
selectedMcpServerIds ?? [],
availableMcpServers,
Expand Down Expand Up @@ -251,6 +263,11 @@ export const useGuidSend = (deps: GuidSendDeps): GuidSendResult => {
: availableMcpServers
.filter((server) => (defaultSelectedMcpServerIds ?? []).includes(server.id))
.map((server) => toSessionMcpServer(server));
const acpSelectedSessionMcpServersToSend = isOpenClaw
? []
: selectedMcpServerIds !== undefined
? selectedSessionMcpServers
: selectedSessionMcpServersToSend;

const finalEffectiveAgentType = effectiveAgentType;
const assistantOverrideModel =
Expand Down Expand Up @@ -427,8 +444,7 @@ export const useGuidSend = (deps: GuidSendDeps): GuidSendResult => {
default_files: files,
...nonPresetSkillExtra,
selected_mcp_server_ids: selectedUserMcpServerIdsToSend,
selected_session_mcp_servers:
selectedMcpServerIds !== undefined ? selectedSessionMcpServers : selectedSessionMcpServersToSend,
selected_session_mcp_servers: acpSelectedSessionMcpServersToSend,
pending_config_options:
!is_preset && compatibleThoughtLevelValue && thoughtLevelOptionId
? { [thoughtLevelOptionId]: compatibleThoughtLevelValue }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { resolveAgentLogo } from '@/renderer/utils/model/agentLogo';
import { resolveExtensionAssetUrl } from '@/renderer/utils/platform';
import { getEvaosAgentDisplayName, isEvaosCustomAgentPresentation } from '@/renderer/evaos/evaosAgentPresentation';
import type { EvaosNativeAgentAvailability } from '@/renderer/evaos/evaosNativeAgentAvailability';
import { getCatalogMcpTransportPresentation } from '@/renderer/utils/model/agentTypes';

type DetectedAgent = {
agent_type: string;
Expand Down Expand Up @@ -74,6 +75,7 @@ const AgentCard: React.FC<AgentCardProps> = (props) => {
: t('settings.agentManagement.goToChat');
const isRepairRequired = nativeAvailability?.status === 'repair_required';
const extensionAvatar = resolveExtensionAssetUrl(agent.isExtension ? agent.avatar : undefined);
const mcpTransportPresentation = getCatalogMcpTransportPresentation(agent);
const logo = useNeutralCustomVisual
? undefined
: extensionAvatar ||
Expand Down Expand Up @@ -106,6 +108,18 @@ const AgentCard: React.FC<AgentCardProps> = (props) => {
{repairReason}
</Typography.Text>
) : null}
{mcpTransportPresentation ? (
<div className='mt-6px flex flex-col gap-2px'>
<Typography.Text className='block text-10px font-medium leading-14px text-t-secondary'>
{t(mcpTransportPresentation.labelKey)}
</Typography.Text>
{mcpTransportPresentation.detailKeys.map((key) => (
<Typography.Text key={key} className='block text-10px leading-14px text-t-tertiary'>
{t(key)}
</Typography.Text>
))}
</div>
) : null}
</div>

<Button size='small' type='secondary' onClick={onGoToChat} className={goToChatButtonClassName}>
Expand Down
4 changes: 4 additions & 0 deletions packages/desktop/src/renderer/services/i18n/i18n-keys.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1286,13 +1286,15 @@ export type I18nKey =
| 'settings.agentManagement.custom'
| 'settings.agentManagement.customAgents'
| 'settings.agentManagement.customEmpty'
| 'settings.agentManagement.desktopBridgeGatewayPluginRequired'
| 'settings.agentManagement.detectCustomAgent'
| 'settings.agentManagement.detected'
| 'settings.agentManagement.discoverMoreAgents'
| 'settings.agentManagement.editCustomAgent'
| 'settings.agentManagement.filterAll'
| 'settings.agentManagement.filterAvailable'
| 'settings.agentManagement.filterSetupNeeded'
| 'settings.agentManagement.gatewayManagedTools'
| 'settings.agentManagement.geminiDescription'
| 'settings.agentManagement.goToChat'
| 'settings.agentManagement.installFromMarket'
Expand Down Expand Up @@ -1321,6 +1323,8 @@ export type I18nKey =
| 'settings.agentManagement.nativeStatusReason'
| 'settings.agentManagement.remoteAgents'
| 'settings.agentManagement.remoteAgentsDescription'
| 'settings.agentManagement.sessionMcpSupportDeterminedAtConnection'
| 'settings.agentManagement.workbenchSessionMcpUnsupported'
| 'settings.agentModelCustomNote'
| 'settings.agentModelInvalid'
| 'settings.agentModelLoadFailed'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,11 @@
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterAll": "All",
"filterAvailable": "Available",
"filterSetupNeeded": "Needs setup"
"filterSetupNeeded": "Needs setup",
"gatewayManagedTools": "Gateway-managed tools",
"workbenchSessionMcpUnsupported": "Workbench session MCP unsupported",
"desktopBridgeGatewayPluginRequired": "Desktop Bridge gateway plugin required",
"sessionMcpSupportDeterminedAtConnection": "Session MCP support is determined at connection time"
},
"remoteAgent": {
"title": "Remote Agents",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,11 @@
"nativeRepairAction": "Repair in Mac & iPhone",
"nativeRepairBadge": "Repair",
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterSetupNeeded": "Needs setup"
"filterSetupNeeded": "Needs setup",
"gatewayManagedTools": "Herramientas administradas por el gateway",
"workbenchSessionMcpUnsupported": "El MCP de sesión de Workbench no es compatible",
"desktopBridgeGatewayPluginRequired": "Se requiere el complemento de gateway de Desktop Bridge",
"sessionMcpSupportDeterminedAtConnection": "La compatibilidad con MCP de sesión se determina al conectarse"
},
"remoteAgent": {
"title": "Agentes remotos",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1146,7 +1146,11 @@
"nativeRepairAction": "Repair in Mac & iPhone",
"nativeRepairBadge": "Repair",
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterSetupNeeded": "نیاز به راه‌اندازی"
"filterSetupNeeded": "نیاز به راه‌اندازی",
"gatewayManagedTools": "ابزارهای مدیریت‌شده توسط دروازه",
"workbenchSessionMcpUnsupported": "MCP نشست Workbench پشتیبانی نمی‌شود",
"desktopBridgeGatewayPluginRequired": "افزونه دروازه Desktop Bridge الزامی است",
"sessionMcpSupportDeterminedAtConnection": "پشتیبانی MCP نشست هنگام اتصال تعیین می‌شود"
},
"repair": {
"pathLabel": "مسیر راه‌اندازی",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,11 @@
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterAll": "Tous",
"filterAvailable": "Disponible",
"filterSetupNeeded": "Needs setup"
"filterSetupNeeded": "Needs setup",
"gatewayManagedTools": "Outils gérés par la passerelle",
"workbenchSessionMcpUnsupported": "Le MCP de session Workbench n’est pas pris en charge",
"desktopBridgeGatewayPluginRequired": "Le plug-in de passerelle Desktop Bridge est requis",
"sessionMcpSupportDeterminedAtConnection": "La prise en charge du MCP de session est déterminée lors de la connexion"
},
"remoteAgent": {
"title": "Agents distants",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1012,7 +1012,11 @@
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterAll": "All",
"filterAvailable": "Available",
"filterSetupNeeded": "Needs setup"
"filterSetupNeeded": "Needs setup",
"gatewayManagedTools": "ゲートウェイ管理ツール",
"workbenchSessionMcpUnsupported": "Workbench セッション MCP はサポートされていません",
"desktopBridgeGatewayPluginRequired": "Desktop Bridge ゲートウェイプラグインが必要です",
"sessionMcpSupportDeterminedAtConnection": "セッション MCP のサポートは接続時に決定されます"
},
"remoteAgent": {
"title": "リモートエージェント",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1012,7 +1012,11 @@
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterAll": "All",
"filterAvailable": "Available",
"filterSetupNeeded": "Needs setup"
"filterSetupNeeded": "Needs setup",
"gatewayManagedTools": "게이트웨이 관리 도구",
"workbenchSessionMcpUnsupported": "Workbench 세션 MCP는 지원되지 않음",
"desktopBridgeGatewayPluginRequired": "Desktop Bridge 게이트웨이 플러그인 필요",
"sessionMcpSupportDeterminedAtConnection": "세션 MCP 지원은 연결 시 결정됩니다"
},
"remoteAgent": {
"title": "원격 에이전트",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,11 @@
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterAll": "All",
"filterAvailable": "Available",
"filterSetupNeeded": "Needs setup"
"filterSetupNeeded": "Needs setup",
"gatewayManagedTools": "Ferramentas gerenciadas pelo gateway",
"workbenchSessionMcpUnsupported": "MCP de sessão do Workbench não é suportado",
"desktopBridgeGatewayPluginRequired": "Plugin de gateway do Desktop Bridge obrigatório",
"sessionMcpSupportDeterminedAtConnection": "O suporte a MCP de sessão é determinado no momento da conexão"
},
"remoteAgent": {
"title": "Agentes Remotos",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1002,7 +1002,11 @@
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterAll": "All",
"filterAvailable": "Available",
"filterSetupNeeded": "Needs setup"
"filterSetupNeeded": "Needs setup",
"gatewayManagedTools": "Инструменты под управлением шлюза",
"workbenchSessionMcpUnsupported": "MCP сеанса Workbench не поддерживается",
"desktopBridgeGatewayPluginRequired": "Требуется плагин шлюза Desktop Bridge",
"sessionMcpSupportDeterminedAtConnection": "Поддержка MCP сеанса определяется при подключении"
},
"remoteAgent": {
"title": "Удалённые агенты",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1010,7 +1010,11 @@
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterAll": "All",
"filterAvailable": "Available",
"filterSetupNeeded": "Needs setup"
"filterSetupNeeded": "Needs setup",
"gatewayManagedTools": "Ağ geçidi tarafından yönetilen araçlar",
"workbenchSessionMcpUnsupported": "Workbench oturum MCP'si desteklenmiyor",
"desktopBridgeGatewayPluginRequired": "Desktop Bridge ağ geçidi eklentisi gerekli",
"sessionMcpSupportDeterminedAtConnection": "Oturum MCP desteği bağlantı sırasında belirlenir"
},
"remoteAgent": {
"title": "Uzak Ajanlar",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,11 @@
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterAll": "All",
"filterAvailable": "Available",
"filterSetupNeeded": "Needs setup"
"filterSetupNeeded": "Needs setup",
"gatewayManagedTools": "Інструменти під керуванням шлюзу",
"workbenchSessionMcpUnsupported": "MCP сеансу Workbench не підтримується",
"desktopBridgeGatewayPluginRequired": "Потрібен плагін шлюзу Desktop Bridge",
"sessionMcpSupportDeterminedAtConnection": "Підтримка MCP сеансу визначається під час підключення"
},
"remoteAgent": {
"title": "Віддалені агенти",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,11 @@
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterAll": "All",
"filterAvailable": "Available",
"filterSetupNeeded": "Needs setup"
"filterSetupNeeded": "Needs setup",
"gatewayManagedTools": "网关托管工具",
"workbenchSessionMcpUnsupported": "Workbench 会话 MCP 不受支持",
"desktopBridgeGatewayPluginRequired": "需要 Desktop Bridge 网关插件",
"sessionMcpSupportDeterminedAtConnection": "会话 MCP 支持在连接时确定"
},
"remoteAgent": {
"title": "远程 Agent",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,11 @@
"nativeRepairTitle": "{{displayName}} Mac control needs pairing",
"filterAll": "All",
"filterAvailable": "Available",
"filterSetupNeeded": "Needs setup"
"filterSetupNeeded": "Needs setup",
"gatewayManagedTools": "閘道管理的工具",
"workbenchSessionMcpUnsupported": "Workbench 工作階段 MCP 不受支援",
"desktopBridgeGatewayPluginRequired": "需要 Desktop Bridge 閘道外掛程式",
"sessionMcpSupportDeterminedAtConnection": "工作階段 MCP 支援會在連線時決定"
},
"remoteAgent": {
"title": "遠端 Agent",
Expand Down
36 changes: 36 additions & 0 deletions packages/desktop/src/renderer/utils/model/agentTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,42 @@ export const DETECTED_AGENTS_SWR_KEY = 'agents.detected';
export const MANAGED_AGENTS_SWR_KEY = 'agents.managed';
export const ASSISTANT_AGENT_CATALOG_SWR_KEY = 'agents.assistant-management-catalog';

export type CatalogMcpTransportPresentation = {
kind: 'gateway-managed' | 'connection-time';
labelKey:
| 'settings.agentManagement.gatewayManagedTools'
| 'settings.agentManagement.sessionMcpSupportDeterminedAtConnection';
detailKeys: Array<
| 'settings.agentManagement.workbenchSessionMcpUnsupported'
| 'settings.agentManagement.desktopBridgeGatewayPluginRequired'
>;
};

/** Catalog-time MCP copy must not infer transport support from absent handshake capabilities. */
export function getCatalogMcpTransportPresentation(agent: {
agent_type: string;
backend?: string;
}): CatalogMcpTransportPresentation | undefined {
if (agent.backend === 'openclaw' || agent.agent_type === 'openclaw-gateway') {
return {
kind: 'gateway-managed',
labelKey: 'settings.agentManagement.gatewayManagedTools',
detailKeys: [
'settings.agentManagement.workbenchSessionMcpUnsupported',
'settings.agentManagement.desktopBridgeGatewayPluginRequired',
],
};
}
if (agent.agent_type === 'acp') {
return {
kind: 'connection-time',
labelKey: 'settings.agentManagement.sessionMcpSupportDeterminedAtConnection',
detailKeys: [],
};
}
return undefined;
}

function projectManagementAgent(agent: ManagedAgent): AgentMetadata {
const {
installed: _installed,
Expand Down
33 changes: 32 additions & 1 deletion tests/unit/agent/agentCatalogProjection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ vi.mock('@/common', () => ({

import { ipcBridge } from '@/common';
import type { ManagedAgent } from '@/common/types/agent/agentMetadata';
import { fetchDetectedAgents, fetchManagedAgents, reprobeEnabledAgents } from '@/renderer/utils/model/agentTypes';
import * as agentTypes from '@/renderer/utils/model/agentTypes';

const { fetchDetectedAgents, fetchManagedAgents, reprobeEnabledAgents } = agentTypes;

const onlineAgent: ManagedAgent = {
id: 'claude-row',
Expand Down Expand Up @@ -95,6 +97,35 @@ describe('detected agent catalog projection', () => {
]);
});

it('presents OpenClaw catalog rows as gateway-managed without inferring session MCP support', async () => {
vi.mocked(ipcBridge.acpConversation.getManagedAgents.invoke).mockResolvedValue([
{ ...onlineAgent, id: 'openclaw-row', name: 'OpenClaw', backend: 'openclaw' },
]);

const [openclaw] = await fetchManagedAgents();

expect(agentTypes.getCatalogMcpTransportPresentation(openclaw)).toEqual({
kind: 'gateway-managed',
labelKey: 'settings.agentManagement.gatewayManagedTools',
detailKeys: [
'settings.agentManagement.workbenchSessionMcpUnsupported',
'settings.agentManagement.desktopBridgeGatewayPluginRequired',
],
});
});

it('presents generic ACP session MCP support as connection-time truth', async () => {
vi.mocked(ipcBridge.acpConversation.getManagedAgents.invoke).mockResolvedValue([onlineAgent]);

const [genericAcp] = await fetchManagedAgents();

expect(agentTypes.getCatalogMcpTransportPresentation(genericAcp)).toEqual({
kind: 'connection-time',
labelKey: 'settings.agentManagement.sessionMcpSupportDeterminedAtConnection',
detailKeys: [],
});
});

it('re-probes every enabled row before an explicit catalog refresh', async () => {
vi.mocked(ipcBridge.acpConversation.getManagedAgents.invoke).mockResolvedValue([
onlineAgent,
Expand Down
Loading
Loading