diff --git a/agent/app/api/v2/agents.go b/agent/app/api/v2/agents.go index d4948f78c7e4..65cb7181c0fa 100644 --- a/agent/app/api/v2/agents.go +++ b/agent/app/api/v2/agents.go @@ -477,19 +477,19 @@ func (b *BaseApi) CheckAgentPlugin(c *gin.Context) { } // @Tags AI -// @Summary Get Agent Browser config +// @Summary Get Agent Security config // @Accept json -// @Param request body dto.AgentBrowserConfigReq true "request" -// @Success 200 {object} dto.AgentBrowserConfig +// @Param request body dto.AgentSecurityConfigReq true "request" +// @Success 200 {object} dto.AgentSecurityConfig // @Security ApiKeyAuth // @Security Timestamp -// @Router /ai/agents/browser/get [post] -func (b *BaseApi) GetAgentBrowserConfig(c *gin.Context) { - var req dto.AgentBrowserConfigReq +// @Router /ai/agents/security/get [post] +func (b *BaseApi) GetAgentSecurityConfig(c *gin.Context) { + var req dto.AgentSecurityConfigReq if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - data, err := agentService.GetBrowserConfig(req) + data, err := agentService.GetSecurityConfig(req) if err != nil { helper.BadRequest(c, err) return @@ -498,19 +498,19 @@ func (b *BaseApi) GetAgentBrowserConfig(c *gin.Context) { } // @Tags AI -// @Summary Update Agent Browser config +// @Summary Update Agent Security config // @Accept json -// @Param request body dto.AgentBrowserConfigUpdateReq true "request" +// @Param request body dto.AgentSecurityConfigUpdateReq true "request" // @Success 200 // @Security ApiKeyAuth // @Security Timestamp -// @Router /ai/agents/browser/update [post] -func (b *BaseApi) UpdateAgentBrowserConfig(c *gin.Context) { - var req dto.AgentBrowserConfigUpdateReq +// @Router /ai/agents/security/update [post] +func (b *BaseApi) UpdateAgentSecurityConfig(c *gin.Context) { + var req dto.AgentSecurityConfigUpdateReq if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - if err := agentService.UpdateBrowserConfig(req); err != nil { + if err := agentService.UpdateSecurityConfig(req); err != nil { helper.BadRequest(c, err) return } diff --git a/agent/app/dto/agents.go b/agent/app/dto/agents.go index 0afb2eb8fa33..743bf0485aba 100644 --- a/agent/app/dto/agents.go +++ b/agent/app/dto/agents.go @@ -3,32 +3,33 @@ package dto import "time" type AgentCreateReq struct { - Name string `json:"name" validate:"required"` - AppVersion string `json:"appVersion" validate:"required"` - WebUIPort int `json:"webUIPort" validate:"required"` - BridgePort int `json:"bridgePort"` - AgentType string `json:"agentType"` - Provider string `json:"provider"` - Model string `json:"model"` - APIType string `json:"apiType"` - MaxTokens int `json:"maxTokens"` - ContextWindow int `json:"contextWindow"` - AccountID uint `json:"accountId"` - APIKey string `json:"apiKey"` - BaseURL string `json:"baseURL"` - Token string `json:"token"` - TaskID string `json:"taskID"` - Advanced bool `json:"advanced"` - ContainerName string `json:"containerName"` - AllowPort bool `json:"allowPort"` - SpecifyIP string `json:"specifyIP"` - RestartPolicy string `json:"restartPolicy"` - CpuQuota float64 `json:"cpuQuota"` - MemoryLimit float64 `json:"memoryLimit"` - MemoryUnit string `json:"memoryUnit"` - PullImage bool `json:"pullImage"` - EditCompose bool `json:"editCompose"` - DockerCompose string `json:"dockerCompose"` + Name string `json:"name" validate:"required"` + AppVersion string `json:"appVersion" validate:"required"` + WebUIPort int `json:"webUIPort" validate:"required"` + BridgePort int `json:"bridgePort"` + AllowedOrigins []string `json:"allowedOrigins"` + AgentType string `json:"agentType"` + Provider string `json:"provider"` + Model string `json:"model"` + APIType string `json:"apiType"` + MaxTokens int `json:"maxTokens"` + ContextWindow int `json:"contextWindow"` + AccountID uint `json:"accountId"` + APIKey string `json:"apiKey"` + BaseURL string `json:"baseURL"` + Token string `json:"token"` + TaskID string `json:"taskID"` + Advanced bool `json:"advanced"` + ContainerName string `json:"containerName"` + AllowPort bool `json:"allowPort"` + SpecifyIP string `json:"specifyIP"` + RestartPolicy string `json:"restartPolicy"` + CpuQuota float64 `json:"cpuQuota"` + MemoryLimit float64 `json:"memoryLimit"` + MemoryUnit string `json:"memoryUnit"` + PullImage bool `json:"pullImage"` + EditCompose bool `json:"editCompose"` + DockerCompose string `json:"dockerCompose"` } type AgentItem struct { @@ -272,24 +273,17 @@ type AgentDiscordConfig struct { Proxy string `json:"proxy"` } -type AgentBrowserConfigReq struct { +type AgentSecurityConfigReq struct { AgentID uint `json:"agentId" validate:"required"` } -type AgentBrowserConfigUpdateReq struct { - AgentID uint `json:"agentId" validate:"required"` - Enabled bool `json:"enabled"` - Headless bool `json:"headless"` - NoSandbox bool `json:"noSandbox"` - DefaultProfile string `json:"defaultProfile" validate:"required"` +type AgentSecurityConfigUpdateReq struct { + AgentID uint `json:"agentId" validate:"required"` + AllowedOrigins []string `json:"allowedOrigins"` } -type AgentBrowserConfig struct { - Enabled bool `json:"enabled"` - ExecutablePath string `json:"executablePath"` - Headless bool `json:"headless"` - NoSandbox bool `json:"noSandbox"` - DefaultProfile string `json:"defaultProfile"` +type AgentSecurityConfig struct { + AllowedOrigins []string `json:"allowedOrigins"` } type AgentOtherConfigReq struct { @@ -297,10 +291,12 @@ type AgentOtherConfigReq struct { } type AgentOtherConfigUpdateReq struct { - AgentID uint `json:"agentId" validate:"required"` - UserTimezone string `json:"userTimezone" validate:"required"` + AgentID uint `json:"agentId" validate:"required"` + UserTimezone string `json:"userTimezone" validate:"required"` + BrowserEnabled bool `json:"browserEnabled"` } type AgentOtherConfig struct { - UserTimezone string `json:"userTimezone"` + UserTimezone string `json:"userTimezone"` + BrowserEnabled bool `json:"browserEnabled"` } diff --git a/agent/app/service/agents.go b/agent/app/service/agents.go index 6509ea3fbc63..905a9a27f007 100644 --- a/agent/app/service/agents.go +++ b/agent/app/service/agents.go @@ -57,8 +57,8 @@ type IAgentService interface { UpdateQQBotConfig(req dto.AgentQQBotConfigUpdateReq) error InstallPlugin(req dto.AgentPluginInstallReq) error CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error) - GetBrowserConfig(req dto.AgentBrowserConfigReq) (*dto.AgentBrowserConfig, error) - UpdateBrowserConfig(req dto.AgentBrowserConfigUpdateReq) error + GetSecurityConfig(req dto.AgentSecurityConfigReq) (*dto.AgentSecurityConfig, error) + UpdateSecurityConfig(req dto.AgentSecurityConfigUpdateReq) error GetOtherConfig(req dto.AgentOtherConfigReq) (*dto.AgentOtherConfig, error) UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error @@ -133,8 +133,17 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) { token := "" configPath := "" storedModel := "" + var allowedOrigins []string if agentType == constant.AppOpenclaw { + var err error + allowedOrigins, err = normalizeAllowedOrigins(req.AllowedOrigins) + if err != nil { + return nil, err + } + if len(allowedOrigins) == 0 { + return nil, fmt.Errorf("allowed origins is required") + } provider = strings.ToLower(strings.TrimSpace(req.Provider)) if !isSupportedAgentProvider(provider) { return nil, buserr.New("ErrAgentProviderNotSupported") @@ -267,7 +276,19 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) { return nil, err } if agentType == constant.AppOpenclaw { - go a.writeConfigWithRetry(appInstall, provider, req.Model, apiType, maxTokens, contextWindow, baseURL, apiKey, token, agent.ID) + go a.writeConfigWithRetry( + appInstall, + provider, + req.Model, + apiType, + maxTokens, + contextWindow, + baseURL, + apiKey, + token, + agent.ID, + allowedOrigins, + ) } item := buildAgentItem(agent, appInstall, nil) @@ -412,7 +433,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error if confDir == "" { return buserr.New("ErrRecordNotFound") } - if err := writeOpenclawConfig(confDir, provider, modelName, apiType, maxTokens, contextWindow, baseURL, account.APIKey, agent.Token); err != nil { + if err := writeOpenclawConfig(confDir, provider, modelName, apiType, maxTokens, contextWindow, baseURL, account.APIKey, agent.Token, nil); err != nil { return err } agent.Provider = provider @@ -900,35 +921,42 @@ func (a AgentService) CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPlugin return &dto.AgentPluginStatus{Installed: installed}, nil } -func (a AgentService) GetBrowserConfig(req dto.AgentBrowserConfigReq) (*dto.AgentBrowserConfig, error) { +func (a AgentService) GetSecurityConfig(req dto.AgentSecurityConfigReq) (*dto.AgentSecurityConfig, error) { agent, _, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } + if normalizeAgentType(agent.AgentType) == constant.AppCopaw { + return nil, fmt.Errorf("copaw does not support security config") + } conf, err := readOpenclawConfig(agent.ConfigPath) if err != nil { return nil, err } - result := extractBrowserConfig(conf) + result := extractSecurityConfig(conf) return &result, nil } -func (a AgentService) UpdateBrowserConfig(req dto.AgentBrowserConfigUpdateReq) error { +func (a AgentService) UpdateSecurityConfig(req dto.AgentSecurityConfigUpdateReq) error { agent, _, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } + if normalizeAgentType(agent.AgentType) == constant.AppCopaw { + return fmt.Errorf("copaw does not support security config") + } + allowedOrigins, err := normalizeAllowedOrigins(req.AllowedOrigins) + if err != nil { + return err + } + if len(allowedOrigins) == 0 { + return fmt.Errorf("allowed origins is required") + } conf, err := readOpenclawConfig(agent.ConfigPath) if err != nil { return err } - setBrowserConfig(conf, dto.AgentBrowserConfig{ - Enabled: req.Enabled, - ExecutablePath: defaultBrowserExecutablePath, - Headless: req.Headless, - NoSandbox: req.NoSandbox, - DefaultProfile: strings.TrimSpace(req.DefaultProfile), - }) + setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins}) if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil { return err } @@ -957,7 +985,10 @@ func (a AgentService) UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error if err != nil { return err } - setOtherConfig(conf, dto.AgentOtherConfig{UserTimezone: strings.TrimSpace(req.UserTimezone)}) + setOtherConfig(conf, dto.AgentOtherConfig{ + UserTimezone: strings.TrimSpace(req.UserTimezone), + BrowserEnabled: req.BrowserEnabled, + }) if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil { return err } @@ -1027,6 +1058,100 @@ func writeOpenclawConfigRaw(configPath string, conf map[string]interface{}) erro return fileOp.SaveFile(configPath, string(payload), 0600) } +func normalizeAllowedOrigins(origins []string) ([]string, error) { + if len(origins) == 0 { + return nil, nil + } + result := make([]string, 0, len(origins)) + seen := make(map[string]struct{}, len(origins)) + for _, origin := range origins { + origin = strings.TrimSpace(origin) + if origin == "" { + continue + } + normalized, err := normalizeAllowedOrigin(origin) + if err != nil { + return nil, err + } + if _, ok := seen[normalized]; ok { + continue + } + seen[normalized] = struct{}{} + result = append(result, normalized) + } + return result, nil +} + +func normalizeAllowedOrigin(origin string) (string, error) { + parsed, err := url.Parse(strings.TrimSpace(origin)) + if err != nil { + return "", fmt.Errorf("invalid allowed origin: %s", origin) + } + if parsed.Scheme != "http" && parsed.Scheme != "https" { + return "", fmt.Errorf("invalid allowed origin: %s", origin) + } + if parsed.User != nil || parsed.Host == "" || parsed.Hostname() == "" { + return "", fmt.Errorf("invalid allowed origin: %s", origin) + } + if parsed.RawQuery != "" || parsed.Fragment != "" { + return "", fmt.Errorf("invalid allowed origin: %s", origin) + } + if pathValue := strings.TrimSpace(parsed.EscapedPath()); pathValue != "" && pathValue != "/" { + return "", fmt.Errorf("invalid allowed origin: %s", origin) + } + host := parsed.Hostname() + if strings.Contains(host, ":") { + host = "[" + host + "]" + } + normalized := parsed.Scheme + "://" + host + if port := parsed.Port(); port != "" { + normalized += ":" + port + } + return normalized, nil +} + +func extractSecurityConfig(conf map[string]interface{}) dto.AgentSecurityConfig { + result := dto.AgentSecurityConfig{AllowedOrigins: []string{}} + gateway, ok := conf["gateway"].(map[string]interface{}) + if !ok { + return result + } + controlUi, ok := gateway["controlUi"].(map[string]interface{}) + if !ok { + return result + } + switch values := controlUi["allowedOrigins"].(type) { + case []interface{}: + for _, value := range values { + if text, ok := value.(string); ok && strings.TrimSpace(text) != "" { + result.AllowedOrigins = append(result.AllowedOrigins, strings.TrimSpace(text)) + } + } + case []string: + for _, value := range values { + if strings.TrimSpace(value) != "" { + result.AllowedOrigins = append(result.AllowedOrigins, strings.TrimSpace(value)) + } + } + } + return result +} + +func setSecurityConfig(conf map[string]interface{}, config dto.AgentSecurityConfig) { + gateway := ensureChildMap(conf, "gateway") + controlUi := ensureChildMap(gateway, "controlUi") + if _, ok := controlUi["dangerouslyDisableDeviceAuth"]; !ok { + controlUi["dangerouslyDisableDeviceAuth"] = true + } + allowedOrigins := append([]string(nil), config.AllowedOrigins...) + if len(allowedOrigins) > 0 { + controlUi["allowedOrigins"] = allowedOrigins + } else { + delete(controlUi, "allowedOrigins") + } + delete(controlUi, "dangerouslyAllowHostHeaderOriginFallback") +} + func extractFeishuConfig(conf map[string]interface{}) dto.AgentFeishuConfig { result := dto.AgentFeishuConfig{Enabled: true, DmPolicy: "pairing"} channels, ok := conf["channels"].(map[string]interface{}) @@ -1184,8 +1309,8 @@ func setDiscordConfig(conf map[string]interface{}, config dto.AgentDiscordConfig delete(discord, "dm") } -func extractBrowserConfig(conf map[string]interface{}) dto.AgentBrowserConfig { - result := dto.AgentBrowserConfig{ +func extractBrowserConfig(conf map[string]interface{}) browserConfig { + result := browserConfig{ Enabled: true, ExecutablePath: defaultBrowserExecutablePath, Headless: true, @@ -1214,7 +1339,7 @@ func extractBrowserConfig(conf map[string]interface{}) dto.AgentBrowserConfig { return result } -func setBrowserConfig(conf map[string]interface{}, config dto.AgentBrowserConfig) { +func setBrowserConfig(conf map[string]interface{}, config browserConfig) { browser := ensureChildMap(conf, "browser") browser["enabled"] = config.Enabled browser["executablePath"] = defaultBrowserExecutablePath @@ -1335,18 +1460,24 @@ func checkPluginInstalled(containerName, pluginType string) (bool, error) { } func extractOtherConfig(conf map[string]interface{}) dto.AgentOtherConfig { - result := dto.AgentOtherConfig{UserTimezone: resolveServerTimezone()} + result := dto.AgentOtherConfig{UserTimezone: resolveServerTimezone(), BrowserEnabled: true} agents, ok := conf["agents"].(map[string]interface{}) if !ok { + browser := extractBrowserConfig(conf) + result.BrowserEnabled = browser.Enabled return result } defaults, ok := agents["defaults"].(map[string]interface{}) if !ok { + browser := extractBrowserConfig(conf) + result.BrowserEnabled = browser.Enabled return result } if timezone, ok := defaults["userTimezone"].(string); ok && strings.TrimSpace(timezone) != "" { result.UserTimezone = strings.TrimSpace(timezone) } + browser := extractBrowserConfig(conf) + result.BrowserEnabled = browser.Enabled return result } @@ -1358,6 +1489,13 @@ func setOtherConfig(conf map[string]interface{}, config dto.AgentOtherConfig) { timezone = resolveServerTimezone() } defaults["userTimezone"] = timezone + setBrowserConfig(conf, browserConfig{ + Enabled: config.BrowserEnabled, + ExecutablePath: defaultBrowserExecutablePath, + Headless: true, + NoSandbox: true, + DefaultProfile: defaultBrowserProfile, + }) } func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error { @@ -1389,7 +1527,7 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error { if strings.EqualFold(account.Provider, "vllm") && strings.TrimSpace(account.Model) != "" { modelName = account.Model } - if err := writeOpenclawConfig(confDir, account.Provider, modelName, apiType, maxTokens, contextWindow, baseURL, account.APIKey, agent.Token); err != nil { + if err := writeOpenclawConfig(confDir, account.Provider, modelName, apiType, maxTokens, contextWindow, baseURL, account.APIKey, agent.Token, nil); err != nil { return err } agent.BaseURL = baseURL @@ -1493,7 +1631,7 @@ func (a AgentService) waitAndDeleteAgent(agentID uint, appInstallID uint) { } } -func (a AgentService) writeConfigWithRetry(appInstall *model.AppInstall, provider, modelName, apiType string, maxTokens, contextWindow int, baseURL, apiKey, token string, agentID uint) { +func (a AgentService) writeConfigWithRetry(appInstall *model.AppInstall, provider, modelName, apiType string, maxTokens, contextWindow int, baseURL, apiKey, token string, agentID uint, allowedOrigins []string) { if appInstall == nil { return } @@ -1506,7 +1644,7 @@ func (a AgentService) writeConfigWithRetry(appInstall *model.AppInstall, provide time.Sleep(time.Second) } confDir := path.Join(appInstall.GetPath(), "data", "conf") - if err := writeOpenclawConfig(confDir, provider, modelName, apiType, maxTokens, contextWindow, baseURL, apiKey, token); err != nil { + if err := writeOpenclawConfig(confDir, provider, modelName, apiType, maxTokens, contextWindow, baseURL, apiKey, token, allowedOrigins); err != nil { global.LOG.Errorf("write openclaw config failed: %v", err) agent, errGet := agentRepo.GetFirst(repo.WithByID(agentID)) if errGet == nil && agent != nil { @@ -1560,8 +1698,8 @@ type gatewayConfig struct { } type gatewayControlUi struct { - DangerouslyDisableDeviceAuth bool `json:"dangerouslyDisableDeviceAuth"` - DangerouslyAllowHostHeaderOriginFallback bool `json:"dangerouslyAllowHostHeaderOriginFallback"` + DangerouslyDisableDeviceAuth bool `json:"dangerouslyDisableDeviceAuth"` + AllowedOrigins []string `json:"allowedOrigins,omitempty"` } type gatewayAuth struct { @@ -1619,7 +1757,7 @@ type browserConfig struct { DefaultProfile string `json:"defaultProfile"` } -func writeOpenclawConfig(confDir, provider, modelName, apiType string, maxTokens, contextWindow int, baseURL, apiKey, token string) error { +func writeOpenclawConfig(confDir, provider, modelName, apiType string, maxTokens, contextWindow int, baseURL, apiKey, token string, allowedOrigins []string) error { if strings.TrimSpace(confDir) == "" { return fmt.Errorf("config dir is required") } @@ -1646,8 +1784,8 @@ func writeOpenclawConfig(confDir, provider, modelName, apiType string, maxTokens Token: token, }, ControlUi: gatewayControlUi{ - DangerouslyDisableDeviceAuth: true, - DangerouslyAllowHostHeaderOriginFallback: true, + DangerouslyDisableDeviceAuth: true, + AllowedOrigins: append([]string(nil), allowedOrigins...), }, }, Agents: agentsConfig{ @@ -1738,6 +1876,9 @@ func writeOpenclawConfig(confDir, provider, modelName, apiType string, maxTokens } authMap["token"] = token } + if allowedOrigins != nil { + setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins}) + } if err := writeOpenclawConfigRaw(configPath, conf); err != nil { return err } diff --git a/agent/router/ro_ai.go b/agent/router/ro_ai.go index 7777caa9492f..b2d79e1e7c64 100644 --- a/agent/router/ro_ai.go +++ b/agent/router/ro_ai.go @@ -63,8 +63,8 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) { aiToolsRouter.POST("/agents/channel/qqbot/update", baseApi.UpdateAgentQQBotConfig) aiToolsRouter.POST("/agents/plugin/install", baseApi.InstallAgentPlugin) aiToolsRouter.POST("/agents/plugin/check", baseApi.CheckAgentPlugin) - aiToolsRouter.POST("/agents/browser/get", baseApi.GetAgentBrowserConfig) - aiToolsRouter.POST("/agents/browser/update", baseApi.UpdateAgentBrowserConfig) + aiToolsRouter.POST("/agents/security/get", baseApi.GetAgentSecurityConfig) + aiToolsRouter.POST("/agents/security/update", baseApi.UpdateAgentSecurityConfig) aiToolsRouter.POST("/agents/other/get", baseApi.GetAgentOtherConfig) aiToolsRouter.POST("/agents/other/update", baseApi.UpdateAgentOtherConfig) aiToolsRouter.POST("/agents/channel/pairing/approve", baseApi.ApproveAgentChannelPairing) diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index d700cc84f264..bab030ce8f48 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -241,6 +241,7 @@ export namespace AI { appVersion: string; webUIPort: number; bridgePort?: number; + allowedOrigins?: string[]; agentType: 'openclaw' | 'copaw'; provider?: string; model?: string; @@ -506,24 +507,17 @@ export namespace AI { proxy: string; } - export interface AgentBrowserConfigReq { + export interface AgentSecurityConfigReq { agentId: number; } - export interface AgentBrowserConfig { - enabled: boolean; - executablePath: string; - headless: boolean; - noSandbox: boolean; - defaultProfile: string; + export interface AgentSecurityConfig { + allowedOrigins: string[]; } - export interface AgentBrowserConfigUpdateReq { + export interface AgentSecurityConfigUpdateReq { agentId: number; - enabled: boolean; - headless: boolean; - noSandbox: boolean; - defaultProfile: string; + allowedOrigins: string[]; } export interface AgentOtherConfigReq { @@ -532,10 +526,12 @@ export namespace AI { export interface AgentOtherConfig { userTimezone: string; + browserEnabled: boolean; } export interface AgentOtherConfigUpdateReq { agentId: number; userTimezone: string; + browserEnabled: boolean; } } diff --git a/frontend/src/api/modules/ai.ts b/frontend/src/api/modules/ai.ts index 9f7f8e2ff411..b87556d5caf2 100644 --- a/frontend/src/api/modules/ai.ts +++ b/frontend/src/api/modules/ai.ts @@ -185,12 +185,12 @@ export const checkAgentPlugin = (req: AI.AgentPluginCheckReq) => { return http.post(`/ai/agents/plugin/check`, req); }; -export const getAgentBrowserConfig = (req: AI.AgentBrowserConfigReq) => { - return http.post(`/ai/agents/browser/get`, req); +export const getAgentSecurityConfig = (req: AI.AgentSecurityConfigReq) => { + return http.post(`/ai/agents/security/get`, req); }; -export const updateAgentBrowserConfig = (req: AI.AgentBrowserConfigUpdateReq) => { - return http.post(`/ai/agents/browser/update`, req); +export const updateAgentSecurityConfig = (req: AI.AgentSecurityConfigUpdateReq) => { + return http.post(`/ai/agents/security/update`, req); }; export const getAgentOtherConfig = (req: AI.AgentOtherConfigReq) => { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index dcb50512d26a..52b8972143bb 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -680,6 +680,12 @@ const message = { appVersion: 'App Version', webuiPort: 'WebUI Port', bridgePort: 'Bridge Port', + allowedOrigins: 'Access Addresses', + allowedOriginsHelper: + 'Enter one full access address per line, for example http://192.168.1.2:18789. Fill it manually if the default access address is not configured.', + allowedOriginsPlaceholder: 'http://192.168.1.2:18789', + allowedOriginsRequired: 'Enter at least one access address', + allowedOriginsInvalid: 'Use the format http(s)://host-or-ip:port', provider: 'Provider', apiKey: 'API Key', baseUrl: 'Base URL', @@ -689,14 +695,10 @@ const message = { verifySkipped: 'Skipped', configTitle: 'Configuration', settingsTab: 'Settings', - browserTab: 'Browser', + securityTab: 'Security', otherTab: 'Other', timeZone: 'Time Zone', browserEnabled: 'Browser Enabled', - headless: 'Headless', - noSandbox: 'No Sandbox', - defaultProfile: 'Default Profile', - executablePath: 'Executable Path', switchModelSuccess: 'Model switched successfully', channelsTab: 'Channels', wecom: 'WeCom', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 8c8f0c65a69d..4abca60dedf3 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -688,6 +688,12 @@ const message = { appVersion: 'Versión de la app', webuiPort: 'Puerto WebUI', bridgePort: 'Puerto Bridge', + allowedOrigins: 'Direcciones de acceso', + allowedOriginsHelper: + 'Introduce una dirección de acceso completa por línea, por ejemplo http://192.168.1.2:18789. Si no hay una dirección predeterminada configurada, rellénala manualmente.', + allowedOriginsPlaceholder: 'http://192.168.1.2:18789', + allowedOriginsRequired: 'Introduce al menos una dirección de acceso', + allowedOriginsInvalid: 'Usa el formato http(s)://host-o-ip:puerto', provider: 'Proveedor de modelos', apiKey: 'Clave API', baseUrl: 'URL base', @@ -697,14 +703,10 @@ const message = { verifySkipped: 'Omitido', configTitle: 'Configuration', settingsTab: 'Settings', - browserTab: 'Browser', + securityTab: 'Security', otherTab: 'Other', timeZone: 'Zona horaria', browserEnabled: 'Browser Enabled', - headless: 'Headless', - noSandbox: 'No Sandbox', - defaultProfile: 'Default Profile', - executablePath: 'Executable Path', switchModelSuccess: 'Model switched successfully', channelsTab: 'Channels', wecom: 'WeCom', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 6a50b41fee59..a223c2e5b6d8 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -681,6 +681,12 @@ const message = { appVersion: 'アプリバージョン', webuiPort: 'WebUI ポート', bridgePort: 'Bridge ポート', + allowedOrigins: 'アクセスアドレス', + allowedOriginsHelper: + '1 行に 1 つずつ完全なアクセスアドレスを入力してください。例: http://192.168.1.2:18789。デフォルトのアクセスアドレスが未設定の場合は手動で入力してください。', + allowedOriginsPlaceholder: 'http://192.168.1.2:18789', + allowedOriginsRequired: '少なくとも 1 つのアクセスアドレスを入力してください', + allowedOriginsInvalid: 'http(s)://host-or-ip:port の形式で入力してください', provider: 'モデルプロバイダー', apiKey: 'API キー', baseUrl: 'ベースURL', @@ -690,14 +696,10 @@ const message = { verifySkipped: 'スキップ済み', configTitle: 'Configuration', settingsTab: 'Settings', - browserTab: 'Browser', + securityTab: 'Security', otherTab: 'Other', timeZone: 'タイムゾーン', browserEnabled: 'Browser Enabled', - headless: 'Headless', - noSandbox: 'No Sandbox', - defaultProfile: 'Default Profile', - executablePath: 'Executable Path', switchModelSuccess: 'Model switched successfully', channelsTab: 'Channels', wecom: 'WeCom', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 7114c79acaed..a03d12d9126d 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -673,6 +673,12 @@ const message = { appVersion: '앱 버전', webuiPort: 'WebUI 포트', bridgePort: 'Bridge 포트', + allowedOrigins: '접속 주소', + allowedOriginsHelper: + '한 줄에 하나의 전체 접속 주소를 입력하세요. 예: http://192.168.1.2:18789. 기본 접속 주소가 설정되지 않은 경우 수동으로 입력하세요.', + allowedOriginsPlaceholder: 'http://192.168.1.2:18789', + allowedOriginsRequired: '접속 주소를 하나 이상 입력하세요', + allowedOriginsInvalid: 'http(s)://host-or-ip:port 형식으로 입력하세요', provider: '모델 제공자', apiKey: 'API 키', baseUrl: '기본 URL', @@ -682,14 +688,10 @@ const message = { verifySkipped: '건너뜀', configTitle: 'Configuration', settingsTab: 'Settings', - browserTab: 'Browser', + securityTab: 'Security', otherTab: 'Other', timeZone: '시간대', browserEnabled: 'Browser Enabled', - headless: 'Headless', - noSandbox: 'No Sandbox', - defaultProfile: 'Default Profile', - executablePath: 'Executable Path', switchModelSuccess: 'Model switched successfully', channelsTab: 'Channels', wecom: 'WeCom', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 3a9965cce437..02a1d1317a47 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -688,6 +688,12 @@ const message = { appVersion: 'Versi aplikasi', webuiPort: 'Port WebUI', bridgePort: 'Port Bridge', + allowedOrigins: 'Alamat akses', + allowedOriginsHelper: + 'Masukkan satu alamat akses penuh bagi setiap baris, contohnya http://192.168.1.2:18789. Isikan secara manual jika alamat akses lalai belum dikonfigurasi.', + allowedOriginsPlaceholder: 'http://192.168.1.2:18789', + allowedOriginsRequired: 'Masukkan sekurang-kurangnya satu alamat akses', + allowedOriginsInvalid: 'Gunakan format http(s)://hos-atau-ip:port', provider: 'Penyedia model', apiKey: 'Kunci API', baseUrl: 'URL asas', @@ -697,14 +703,10 @@ const message = { verifySkipped: 'Dilangkau', configTitle: 'Configuration', settingsTab: 'Settings', - browserTab: 'Browser', + securityTab: 'Security', otherTab: 'Other', timeZone: 'Zon Waktu', browserEnabled: 'Browser Enabled', - headless: 'Headless', - noSandbox: 'No Sandbox', - defaultProfile: 'Default Profile', - executablePath: 'Executable Path', switchModelSuccess: 'Model switched successfully', channelsTab: 'Channels', wecom: 'WeCom', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 491f0f78a698..f180270433b9 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -683,6 +683,12 @@ const message = { appVersion: 'Versão do app', webuiPort: 'Porta WebUI', bridgePort: 'Porta Bridge', + allowedOrigins: 'Endereços de acesso', + allowedOriginsHelper: + 'Informe um endereço de acesso completo por linha, por exemplo http://192.168.1.2:18789. Preencha manualmente se o endereço padrão não estiver configurado.', + allowedOriginsPlaceholder: 'http://192.168.1.2:18789', + allowedOriginsRequired: 'Informe pelo menos um endereço de acesso', + allowedOriginsInvalid: 'Use o formato http(s)://host-ou-ip:porta', provider: 'Provedor de modelos', apiKey: 'Chave API', baseUrl: 'URL base', @@ -692,14 +698,10 @@ const message = { verifySkipped: 'Ignorado', configTitle: 'Configuration', settingsTab: 'Settings', - browserTab: 'Browser', + securityTab: 'Security', otherTab: 'Other', timeZone: 'Fuso horário', browserEnabled: 'Browser Enabled', - headless: 'Headless', - noSandbox: 'No Sandbox', - defaultProfile: 'Default Profile', - executablePath: 'Executable Path', switchModelSuccess: 'Model switched successfully', channelsTab: 'Channels', wecom: 'WeCom', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 86ac9e2d41fa..1b680f7de297 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -680,6 +680,12 @@ const message = { appVersion: 'Версия приложения', webuiPort: 'Порт WebUI', bridgePort: 'Порт Bridge', + allowedOrigins: 'Адреса доступа', + allowedOriginsHelper: + 'Указывайте по одному полному адресу доступа в строке, например http://192.168.1.2:18789. Если адрес по умолчанию не настроен, введите его вручную.', + allowedOriginsPlaceholder: 'http://192.168.1.2:18789', + allowedOriginsRequired: 'Укажите хотя бы один адрес доступа', + allowedOriginsInvalid: 'Используйте формат http(s)://host-or-ip:port', provider: 'Поставщик моделей', apiKey: 'API ключ', baseUrl: 'Базовый URL', @@ -689,14 +695,10 @@ const message = { verifySkipped: 'Пропущено', configTitle: 'Configuration', settingsTab: 'Settings', - browserTab: 'Browser', + securityTab: 'Security', otherTab: 'Other', timeZone: 'Часовой пояс', browserEnabled: 'Browser Enabled', - headless: 'Headless', - noSandbox: 'No Sandbox', - defaultProfile: 'Default Profile', - executablePath: 'Executable Path', switchModelSuccess: 'Model switched successfully', channelsTab: 'Channels', wecom: 'WeCom', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 47d7b1a8638b..37be7e1ef68b 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -684,6 +684,12 @@ const message = { appVersion: 'Uygulama sürümü', webuiPort: 'WebUI portu', bridgePort: 'Bridge portu', + allowedOrigins: 'Erişim adresleri', + allowedOriginsHelper: + 'Her satıra bir tam erişim adresi girin. Örnek: http://192.168.1.2:18789. Varsayılan erişim adresi yapılandırılmamışsa elle girin.', + allowedOriginsPlaceholder: 'http://192.168.1.2:18789', + allowedOriginsRequired: 'En az bir erişim adresi girin', + allowedOriginsInvalid: 'http(s)://host-veya-ip:port biçimini kullanın', provider: 'Model sağlayıcı', apiKey: 'API anahtarı', baseUrl: 'Temel URL', @@ -693,14 +699,10 @@ const message = { verifySkipped: 'Atlandı', configTitle: 'Configuration', settingsTab: 'Settings', - browserTab: 'Browser', + securityTab: 'Security', otherTab: 'Other', timeZone: 'Saat Dilimi', browserEnabled: 'Browser Enabled', - headless: 'Headless', - noSandbox: 'No Sandbox', - defaultProfile: 'Default Profile', - executablePath: 'Executable Path', switchModelSuccess: 'Model switched successfully', channelsTab: 'Channels', wecom: 'WeCom', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 7cb1b06633e2..d2440de12752 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -648,6 +648,11 @@ const message = { appVersion: '應用版本', webuiPort: 'WebUI 埠', bridgePort: 'Bridge 埠', + allowedOrigins: '訪問地址', + allowedOriginsHelper: '一行一個完整訪問地址,例如 http://192.168.1.2:18789;未設定預設訪問地址時請手動填寫', + allowedOriginsPlaceholder: 'http://192.168.1.2:18789', + allowedOriginsRequired: '請至少填寫一個訪問地址', + allowedOriginsInvalid: '訪問地址格式錯誤,請輸入 http(s)://網域或IP:埠', provider: '模型供應商', apiKey: 'API Key', baseUrl: 'Base URL', @@ -657,14 +662,10 @@ const message = { verifySkipped: '已跳過', configTitle: '設定', settingsTab: '設定', - browserTab: '瀏覽器', + securityTab: '安全', otherTab: '其他', timeZone: '時區', browserEnabled: '瀏覽器開關', - headless: '無頭模式', - noSandbox: '禁用沙箱', - defaultProfile: '預設設定檔', - executablePath: '瀏覽器可執行路徑', switchModelSuccess: '模型切換成功', channelsTab: '頻道', wecom: '企業微信', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 1d0a61790bd8..4a11f1af428a 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -647,6 +647,11 @@ const message = { appVersion: '应用版本', webuiPort: 'WebUI 端口', bridgePort: 'Bridge 端口', + allowedOrigins: '访问地址', + allowedOriginsHelper: '一行一个完整访问地址,例如 http://192.168.1.2:18789;未配置默认访问地址时请手动填写', + allowedOriginsPlaceholder: 'http://192.168.1.2:18789', + allowedOriginsRequired: '请至少填写一个访问地址', + allowedOriginsInvalid: '访问地址格式错误,请输入 http(s)://域名或IP:端口', provider: '模型供应商', apiKey: 'API Key', baseUrl: 'Base URL', @@ -656,14 +661,10 @@ const message = { verifySkipped: '已跳过', configTitle: '配置', settingsTab: '设置', - browserTab: '浏览器', + securityTab: '安全', otherTab: '其他', timeZone: '时区', browserEnabled: '浏览器开关', - headless: '无头模式', - noSandbox: '禁用沙箱', - defaultProfile: '默认配置文件', - executablePath: '浏览器可执行路径', switchModelSuccess: '模型切换成功', channelsTab: '频道', wecom: '企业微信', diff --git a/frontend/src/utils/agent.ts b/frontend/src/utils/agent.ts index 856eb971c1be..8472ba09ad76 100644 --- a/frontend/src/utils/agent.ts +++ b/frontend/src/utils/agent.ts @@ -9,3 +9,67 @@ export const getAgentProviderDisplayName = (provider: string, displayName?: stri } return displayName || provider; }; + +export const buildDefaultAllowedOrigin = (systemIP: string, port?: number | string): string => { + const target = String(systemIP || '').trim(); + if (!target || !port) { + return ''; + } + return `http://${target}:${port}`; +}; + +export const normalizeAllowedOrigin = (value: string): string => { + const target = String(value || '').trim(); + if (!target) { + throw new Error(i18n.global.t('aiTools.agents.allowedOriginsInvalid')); + } + let parsed: URL; + try { + parsed = new URL(target); + } catch (error) { + throw new Error(i18n.global.t('aiTools.agents.allowedOriginsInvalid')); + } + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { + throw new Error(i18n.global.t('aiTools.agents.allowedOriginsInvalid')); + } + if (parsed.username || parsed.password || parsed.search || parsed.hash) { + throw new Error(i18n.global.t('aiTools.agents.allowedOriginsInvalid')); + } + if (parsed.pathname && parsed.pathname !== '/') { + throw new Error(i18n.global.t('aiTools.agents.allowedOriginsInvalid')); + } + if (!parsed.host) { + throw new Error(i18n.global.t('aiTools.agents.allowedOriginsInvalid')); + } + return `${parsed.protocol}//${parsed.host}`; +}; + +export const parseAllowedOriginsInput = (value: string): string[] => { + const result: string[] = []; + const seen = new Set(); + for (const line of String(value || '').split(/\r?\n/)) { + const target = line.trim(); + if (!target) { + continue; + } + const normalized = normalizeAllowedOrigin(target); + if (seen.has(normalized)) { + continue; + } + seen.add(normalized); + result.push(normalized); + } + return result; +}; + +export const validateAllowedOriginsInput = (value: string): string => { + try { + const parsed = parseAllowedOriginsInput(value); + if (parsed.length === 0) { + return i18n.global.t('aiTools.agents.allowedOriginsRequired'); + } + return ''; + } catch (error: any) { + return error?.message || i18n.global.t('aiTools.agents.allowedOriginsInvalid'); + } +}; diff --git a/frontend/src/views/ai/agents/agent/add/index.vue b/frontend/src/views/ai/agents/agent/add/index.vue index deb99f4e560c..55c9e3ce8395 100644 --- a/frontend/src/views/ai/agents/agent/add/index.vue +++ b/frontend/src/views/ai/agents/agent/add/index.vue @@ -26,6 +26,22 @@ > + + + + {{ $t('aiTools.agents.allowedOriginsHelper') }} + + @@ -103,8 +119,14 @@ import { checkNumberRange, Rules } from '@/global/form-rules'; import { createAgent, getAgentProviders, pageAgentAccounts } from '@/api/modules/ai'; import { AI } from '@/api/interface/ai'; import { getAppByKey, getAppDetail } from '@/api/modules/app'; +import { getAgentSettingByKey } from '@/api/modules/setting'; import { getRandomStr, newUUID } from '@/utils/util'; -import { getAgentProviderDisplayName } from '@/utils/agent'; +import { + buildDefaultAllowedOrigin, + getAgentProviderDisplayName, + parseAllowedOriginsInput, + validateAllowedOriginsInput, +} from '@/utils/agent'; import { App } from '@/api/interface/app'; import AdvancedSetting from '@/components/advanced-setting/index.vue'; import AccountAddDialog from '@/views/ai/agents/model/add/index.vue'; @@ -122,6 +144,9 @@ const providerAccountCount = ref>({}); const manualModel = ref(false); const appInfo = ref(); const accountAddRef = ref(); +const systemIP = ref(''); +const lastAutoAllowedOrigins = ref(''); +const allowedOriginsAutoFilled = ref(true); const { isIntl } = useGlobalStore(); const form = reactive({ @@ -130,6 +155,7 @@ const form = reactive({ appVersion: '', webUIPort: 18789, bridgePort: 18790, + allowedOrigins: '', provider: 'deepseek', accountId: undefined as unknown as number, model: '', @@ -152,37 +178,29 @@ const form = reactive({ dockerCompose: '', }); -const validateOpenclawOnly = (field: 'provider' | 'accountId' | 'model' | 'bridgePort') => { - return (_rule: any, value: any, callback: (error?: Error) => void) => { - if (form.agentType !== 'openclaw') { - callback(); - return; - } - if (field === 'bridgePort') { - if (!value || Number(value) <= 0) { - callback(new Error('bridge port is required')); - return; - } - callback(); - return; - } - if (value === undefined || value === null || value === '') { - callback(new Error(`${field} is required`)); - return; - } - callback(); - }; -}; - const rules = reactive({ name: [Rules.requiredInput], agentType: [Rules.requiredSelect], appVersion: [Rules.requiredSelect], webUIPort: [Rules.requiredInput], - bridgePort: [{ validator: validateOpenclawOnly('bridgePort'), trigger: 'blur' }], - provider: [{ validator: validateOpenclawOnly('provider'), trigger: 'change' }], - accountId: [{ validator: validateOpenclawOnly('accountId'), trigger: 'change' }], - model: [{ validator: validateOpenclawOnly('model'), trigger: 'change' }], + bridgePort: [Rules.requiredInput], + allowedOrigins: [ + Rules.requiredInput, + { + validator: (_rule: any, value: any, callback: (error?: Error) => void) => { + const message = validateAllowedOriginsInput(String(value || '')); + if (message) { + callback(new Error(message)); + return; + } + callback(); + }, + trigger: 'blur', + }, + ], + provider: [Rules.requiredSelect], + accountId: [Rules.requiredSelect], + model: [Rules.requiredInput], containerName: [Rules.containerName], restartPolicy: [Rules.requiredSelect], cpuQuota: [checkNumberRange(0, 99999)], @@ -192,6 +210,28 @@ const rules = reactive({ const filteredModels = computed(() => providerModels.value[form.provider] || []); +const syncAllowedOriginsWithDefault = (force = false) => { + if (form.agentType !== 'openclaw') { + return; + } + const defaultOrigin = buildDefaultAllowedOrigin(systemIP.value, form.webUIPort); + if (!force && !allowedOriginsAutoFilled.value && form.allowedOrigins !== lastAutoAllowedOrigins.value) { + return; + } + form.allowedOrigins = defaultOrigin; + lastAutoAllowedOrigins.value = defaultOrigin; + allowedOriginsAutoFilled.value = true; +}; + +const loadSystemIP = async () => { + try { + const res = await getAgentSettingByKey('SystemIP'); + systemIP.value = String(res.data || '').trim(); + } catch (error) { + systemIP.value = ''; + } +}; + const loadVersions = async (appKey: 'openclaw' | 'copaw') => { const res = await getAppByKey(appKey); appInfo.value = res.data; @@ -301,11 +341,17 @@ const handleAgentTypeChange = async () => { form.apiType = 'openai-completions'; if (form.agentType === 'openclaw') { form.bridgePort = form.bridgePort || 18790; + await loadSystemIP(); + allowedOriginsAutoFilled.value = true; + syncAllowedOriginsWithDefault(true); await loadVersions('openclaw'); await loadProviders(); await loadAccounts(); return; } + form.allowedOrigins = ''; + lastAutoAllowedOrigins.value = ''; + allowedOriginsAutoFilled.value = true; await loadVersions('copaw'); }; @@ -353,6 +399,10 @@ const setDefaultModel = () => { } }; +const handleAllowedOriginsInput = () => { + allowedOriginsAutoFilled.value = form.allowedOrigins === lastAutoAllowedOrigins.value; +}; + const submit = async () => { if (!formRef.value) { return; @@ -368,6 +418,7 @@ const submit = async () => { appVersion: form.appVersion, webUIPort: form.webUIPort, bridgePort: form.agentType === 'openclaw' ? form.bridgePort : undefined, + allowedOrigins: form.agentType === 'openclaw' ? parseAllowedOriginsInput(form.allowedOrigins) : undefined, agentType: form.agentType, provider: form.agentType === 'openclaw' ? form.provider : undefined, model: form.agentType === 'openclaw' ? form.model : undefined, @@ -408,7 +459,10 @@ const submit = async () => { const handleClose = () => { formRef.value?.resetFields(); form.token = ''; + form.allowedOrigins = ''; form.dockerCompose = ''; + lastAutoAllowedOrigins.value = ''; + allowedOriginsAutoFilled.value = true; }; const openDrawer = async (agentType?: 'openclaw' | 'copaw') => { @@ -419,12 +473,18 @@ const openDrawer = async (agentType?: 'openclaw' | 'copaw') => { form.agentType = targetType; form.token = getRandomStr(32).toLowerCase(); if (form.agentType === 'copaw') { + form.allowedOrigins = ''; + lastAutoAllowedOrigins.value = ''; + allowedOriginsAutoFilled.value = true; await loadVersions('copaw'); providerOptions.value = []; providerModels.value = {}; accountOptions.value = []; return; } + await loadSystemIP(); + allowedOriginsAutoFilled.value = true; + syncAllowedOriginsWithDefault(true); await loadVersions('openclaw'); await loadProviders(); await loadAccounts(); @@ -464,6 +524,13 @@ watch( }, ); +watch( + () => form.webUIPort, + () => { + syncAllowedOriginsWithDefault(); + }, +); + defineExpose({ open: openDrawer, }); diff --git a/frontend/src/views/ai/agents/agent/config/index.vue b/frontend/src/views/ai/agents/agent/config/index.vue index 32518d037d61..4c7ce5574fab 100644 --- a/frontend/src/views/ai/agents/agent/config/index.vue +++ b/frontend/src/views/ai/agents/agent/config/index.vue @@ -37,11 +37,11 @@ const modelRef = ref(); const settingsRef = ref(); const loadSettings = async () => { - if (agentId.value <= 0) { + if (!currentAgent.value) { return; } await nextTick(); - await settingsRef.value?.load(agentId.value); + await settingsRef.value?.load(currentAgent.value); }; const loadModel = async () => { diff --git a/frontend/src/views/ai/agents/agent/config/tabs/settings.vue b/frontend/src/views/ai/agents/agent/config/tabs/settings.vue index f4b488e22a62..5c7403fb0a52 100644 --- a/frontend/src/views/ai/agents/agent/config/tabs/settings.vue +++ b/frontend/src/views/ai/agents/agent/config/tabs/settings.vue @@ -1,7 +1,7 @@