From 77938b736d4e30450b29cb776dabcfd26c4fe51a Mon Sep 17 00:00:00 2001
From: zhengkunwang223 <1paneldev@sina.com>
Date: Wed, 11 Feb 2026 14:47:52 +0800
Subject: [PATCH] feat: add token reset for agent
---
agent/app/api/v2/agents.go | 20 +++
agent/app/dto/agents.go | 4 +
agent/app/service/agents.go | 45 ++++-
agent/init/migration/migrations/init.go | 55 +++---
.../migrations/utils/openclaw_agent.go | 164 ++++++++++++++++++
agent/router/ro_ai.go | 1 +
frontend/src/api/interface/ai.ts | 4 +
frontend/src/api/modules/ai.ts | 4 +
.../ai/agents/agent/config/tabs/channels.vue | 9 +
frontend/src/views/ai/agents/agent/index.vue | 27 ++-
10 files changed, 301 insertions(+), 32 deletions(-)
create mode 100644 agent/init/migration/migrations/utils/openclaw_agent.go
diff --git a/agent/app/api/v2/agents.go b/agent/app/api/v2/agents.go
index e59050a9b219..f13d3b33a20a 100644
--- a/agent/app/api/v2/agents.go
+++ b/agent/app/api/v2/agents.go
@@ -71,6 +71,26 @@ func (b *BaseApi) DeleteAgent(c *gin.Context) {
helper.Success(c)
}
+// @Tags AI
+// @Summary Reset Agent token
+// @Accept json
+// @Param request body dto.AgentTokenResetReq true "request"
+// @Success 200
+// @Security ApiKeyAuth
+// @Security Timestamp
+// @Router /ai/agents/token/reset [post]
+func (b *BaseApi) ResetAgentToken(c *gin.Context) {
+ var req dto.AgentTokenResetReq
+ if err := helper.CheckBindAndValidate(&req, c); err != nil {
+ return
+ }
+ if err := agentService.ResetToken(req); err != nil {
+ helper.BadRequest(c, err)
+ return
+ }
+ helper.Success(c)
+}
+
// @Tags AI
// @Summary Update Agent model config
// @Accept json
diff --git a/agent/app/dto/agents.go b/agent/app/dto/agents.go
index a1a9f1495711..f78741c327c7 100644
--- a/agent/app/dto/agents.go
+++ b/agent/app/dto/agents.go
@@ -56,6 +56,10 @@ type AgentDeleteReq struct {
ForceDelete bool `json:"forceDelete"`
}
+type AgentTokenResetReq struct {
+ ID uint `json:"id" validate:"required"`
+}
+
type AgentModelConfigUpdateReq struct {
AgentID uint `json:"agentId" validate:"required"`
AccountID uint `json:"accountId" validate:"required"`
diff --git a/agent/app/service/agents.go b/agent/app/service/agents.go
index a36cb52bd3fa..0d0dbc24a935 100644
--- a/agent/app/service/agents.go
+++ b/agent/app/service/agents.go
@@ -33,6 +33,7 @@ type IAgentService interface {
Create(req dto.AgentCreateReq) (*dto.AgentItem, error)
Page(req dto.SearchWithPage) (int64, []dto.AgentItem, error)
Delete(req dto.AgentDeleteReq) error
+ ResetToken(req dto.AgentTokenResetReq) error
UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
GetProviders() ([]dto.ProviderInfo, error)
CreateAccount(req dto.AgentAccountCreateReq) error
@@ -103,7 +104,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
token := strings.TrimSpace(req.Token)
if token == "" {
- token = randomToken()
+ token = generateToken()
}
params := map[string]interface{}{
"PROVIDER": provider,
@@ -211,6 +212,46 @@ func (a AgentService) Delete(req dto.AgentDeleteReq) error {
return nil
}
+func (a AgentService) ResetToken(req dto.AgentTokenResetReq) error {
+ agent, err := agentRepo.GetFirst(repo.WithByID(req.ID))
+ if err != nil {
+ return err
+ }
+ configPath := strings.TrimSpace(agent.ConfigPath)
+ if configPath == "" && agent.AppInstallID > 0 {
+ install, err := appInstallRepo.GetFirst(repo.WithByID(agent.AppInstallID))
+ if err != nil {
+ return err
+ }
+ configPath = path.Join(install.GetPath(), "data", "conf", "openclaw.json")
+ }
+ if configPath == "" {
+ return buserr.New("ErrRecordNotFound")
+ }
+ conf, err := readOpenclawConfig(configPath)
+ if err != nil {
+ return err
+ }
+ newToken := generateToken()
+ if newToken == "" {
+ return fmt.Errorf("generate token failed")
+ }
+ gatewayMap := ensureChildMap(conf, "gateway")
+ authMap := ensureChildMap(gatewayMap, "auth")
+ if _, ok := authMap["mode"]; !ok {
+ authMap["mode"] = "token"
+ }
+ authMap["token"] = newToken
+ if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
+ return err
+ }
+ agent.Token = newToken
+ if agent.ConfigPath == "" {
+ agent.ConfigPath = configPath
+ }
+ return agentRepo.Save(agent)
+}
+
func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error {
agent, err := agentRepo.GetFirst(repo.WithByID(req.AgentID))
if err != nil {
@@ -1226,7 +1267,7 @@ func toInt(value interface{}) int {
}
}
-func randomToken() string {
+func generateToken() string {
bytes := make([]byte, 24)
if _, err := rand.Read(bytes); err != nil {
return ""
diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go
index 53fb16ec715b..975f38abe6a5 100644
--- a/agent/init/migration/migrations/init.go
+++ b/agent/init/migration/migrations/init.go
@@ -14,10 +14,10 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/app/model"
- providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
"github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
+ migrationutils "github.com/1Panel-dev/1Panel/agent/init/migration/migrations/utils"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
@@ -864,17 +864,38 @@ var MigrateOpenclawAgents = &gormigrate.Migration{
if strings.TrimSpace(install.Env) != "" {
_ = json.Unmarshal([]byte(install.Env), &envMap)
}
- provider := strings.ToLower(getEnvStr(envMap, "PROVIDER"))
+ configPath := path.Join(install.GetPath(), "data", "conf", "openclaw.json")
+ cfgMeta := migrationutils.OpenclawMeta{}
+ if fileData, err := os.ReadFile(configPath); err == nil {
+ cfgMeta = migrationutils.ParseOpenclawMeta(fileData)
+ }
+ provider := strings.ToLower(migrationutils.GetEnvStr(envMap, "PROVIDER"))
+ if provider == "" {
+ provider = strings.ToLower(cfgMeta.Provider)
+ }
if provider == "" {
continue
}
- modelName := getEnvStr(envMap, "MODEL")
- baseURL := getEnvStr(envMap, "BASE_URL")
- apiKey := getEnvStr(envMap, "API_KEY")
- token := getEnvStr(envMap, "OPENCLAW_GATEWAY_TOKEN")
+ provider = migrationutils.NormalizeOpenclawProvider(provider, cfgMeta.BaseURL)
+ modelName := migrationutils.GetEnvStr(envMap, "MODEL")
+ if modelName == "" {
+ modelName = cfgMeta.Model
+ }
+ baseURL := migrationutils.GetEnvStr(envMap, "BASE_URL")
+ if baseURL == "" {
+ baseURL = cfgMeta.BaseURL
+ }
+ apiKey := migrationutils.GetEnvStr(envMap, "API_KEY")
+ if apiKey == "" {
+ apiKey = cfgMeta.APIKey
+ }
+ token := migrationutils.GetEnvStr(envMap, "OPENCLAW_GATEWAY_TOKEN")
+ if token == "" {
+ token = cfgMeta.Token
+ }
if provider != "ollama" {
if baseURL == "" {
- if defaultURL, ok := defaultBaseURL(provider); ok {
+ if defaultURL, ok := migrationutils.DefaultBaseURL(provider); ok {
baseURL = defaultURL
}
}
@@ -900,7 +921,6 @@ var MigrateOpenclawAgents = &gormigrate.Migration{
return err
}
}
- configPath := path.Join(install.GetPath(), "data", "conf", "openclaw.json")
agent := model.Agent{
Name: install.Name,
Provider: provider,
@@ -921,22 +941,3 @@ var MigrateOpenclawAgents = &gormigrate.Migration{
return nil
},
}
-
-func getEnvStr(envMap map[string]interface{}, key string) string {
- if envMap == nil {
- return ""
- }
- if value, ok := envMap[key]; ok {
- switch v := value.(type) {
- case string:
- return strings.TrimSpace(v)
- default:
- return strings.TrimSpace(fmt.Sprintf("%v", v))
- }
- }
- return ""
-}
-
-func defaultBaseURL(provider string) (string, bool) {
- return providercatalog.DefaultBaseURL(provider)
-}
diff --git a/agent/init/migration/migrations/utils/openclaw_agent.go b/agent/init/migration/migrations/utils/openclaw_agent.go
new file mode 100644
index 000000000000..8d1ef013c907
--- /dev/null
+++ b/agent/init/migration/migrations/utils/openclaw_agent.go
@@ -0,0 +1,164 @@
+package utils
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
+)
+
+type OpenclawMeta struct {
+ Provider string
+ Model string
+ BaseURL string
+ APIKey string
+ Token string
+}
+
+func GetEnvStr(envMap map[string]interface{}, key string) string {
+ if envMap == nil {
+ return ""
+ }
+ if value, ok := envMap[key]; ok {
+ switch v := value.(type) {
+ case string:
+ return strings.TrimSpace(v)
+ default:
+ return strings.TrimSpace(fmt.Sprintf("%v", v))
+ }
+ }
+ return ""
+}
+
+func ParseOpenclawMeta(fileData []byte) OpenclawMeta {
+ meta := OpenclawMeta{}
+ content := map[string]interface{}{}
+ if err := json.Unmarshal(fileData, &content); err != nil {
+ return meta
+ }
+
+ meta.Token = getNestedString(content, "gateway", "auth", "token")
+ meta.Model = getNestedString(content, "agents", "defaults", "model", "primary")
+
+ providerKey := ""
+ if parts := strings.SplitN(meta.Model, "/", 2); len(parts) == 2 {
+ providerKey = strings.TrimSpace(parts[0])
+ }
+
+ providers := getNestedMap(content, "models", "providers")
+ providerConfig := map[string]interface{}{}
+ if providerKey != "" {
+ if cfg, ok := providers[providerKey].(map[string]interface{}); ok {
+ providerConfig = cfg
+ }
+ }
+ if len(providerConfig) == 0 && len(providers) == 1 {
+ for key, value := range providers {
+ if cfg, ok := value.(map[string]interface{}); ok {
+ providerKey = key
+ providerConfig = cfg
+ break
+ }
+ }
+ }
+
+ meta.Provider = providerKey
+ meta.BaseURL = getString(providerConfig, "baseUrl")
+ meta.APIKey = getString(providerConfig, "apiKey")
+
+ if meta.Model == "" && providerKey != "" {
+ if modelID := getProviderFirstModelID(providerConfig); modelID != "" {
+ meta.Model = providerKey + "/" + modelID
+ }
+ }
+
+ return meta
+}
+
+func NormalizeOpenclawProvider(provider, baseURL string) string {
+ p := strings.ToLower(strings.TrimSpace(provider))
+ base := strings.ToLower(strings.TrimSpace(baseURL))
+ switch p {
+ case "minimax-portal":
+ return "minimax"
+ case "moonshot":
+ if strings.Contains(base, "moonshot.cn") {
+ return "kimi"
+ }
+ return "moonshot"
+ default:
+ return p
+ }
+}
+
+func DefaultBaseURL(provider string) (string, bool) {
+ return providercatalog.DefaultBaseURL(provider)
+}
+
+func getNestedMap(data map[string]interface{}, keys ...string) map[string]interface{} {
+ current := data
+ for _, key := range keys {
+ next, ok := current[key].(map[string]interface{})
+ if !ok {
+ return map[string]interface{}{}
+ }
+ current = next
+ }
+ return current
+}
+
+func getNestedString(data map[string]interface{}, keys ...string) string {
+ current := data
+ for i, key := range keys {
+ value, ok := current[key]
+ if !ok {
+ return ""
+ }
+ if i == len(keys)-1 {
+ switch v := value.(type) {
+ case string:
+ return strings.TrimSpace(v)
+ default:
+ return strings.TrimSpace(fmt.Sprintf("%v", v))
+ }
+ }
+ next, ok := value.(map[string]interface{})
+ if !ok {
+ return ""
+ }
+ current = next
+ }
+ return ""
+}
+
+func getString(data map[string]interface{}, key string) string {
+ if data == nil {
+ return ""
+ }
+ value, ok := data[key]
+ if !ok {
+ return ""
+ }
+ switch v := value.(type) {
+ case string:
+ return strings.TrimSpace(v)
+ default:
+ return strings.TrimSpace(fmt.Sprintf("%v", v))
+ }
+}
+
+func getProviderFirstModelID(data map[string]interface{}) string {
+ if data == nil {
+ return ""
+ }
+ rawModels, ok := data["models"].([]interface{})
+ if !ok || len(rawModels) == 0 {
+ return ""
+ }
+ first, ok := rawModels[0].(map[string]interface{})
+ if !ok {
+ return ""
+ }
+ return getString(first, "id")
+}
diff --git a/agent/router/ro_ai.go b/agent/router/ro_ai.go
index 1e76d4007ce1..e426a58fb09b 100644
--- a/agent/router/ro_ai.go
+++ b/agent/router/ro_ai.go
@@ -43,6 +43,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.POST("/agents", baseApi.CreateAgent)
aiToolsRouter.POST("/agents/search", baseApi.PageAgents)
aiToolsRouter.POST("/agents/delete", baseApi.DeleteAgent)
+ aiToolsRouter.POST("/agents/token/reset", baseApi.ResetAgentToken)
aiToolsRouter.POST("/agents/model/update", baseApi.UpdateAgentModelConfig)
aiToolsRouter.GET("/agents/providers", baseApi.GetAgentProviders)
aiToolsRouter.POST("/agents/accounts", baseApi.CreateAgentAccount)
diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts
index 7deab24befed..22d266ea58bf 100644
--- a/frontend/src/api/interface/ai.ts
+++ b/frontend/src/api/interface/ai.ts
@@ -290,6 +290,10 @@ export namespace AI {
forceDelete: boolean;
}
+ export interface AgentTokenResetReq {
+ id: number;
+ }
+
export interface AgentModelConfigUpdateReq {
agentId: number;
accountId: number;
diff --git a/frontend/src/api/modules/ai.ts b/frontend/src/api/modules/ai.ts
index d5cba785cc89..58373fb978fd 100644
--- a/frontend/src/api/modules/ai.ts
+++ b/frontend/src/api/modules/ai.ts
@@ -104,6 +104,10 @@ export const deleteAgent = (req: AI.AgentDeleteReq) => {
return http.post(`/ai/agents/delete`, req);
};
+export const resetAgentToken = (req: AI.AgentTokenResetReq) => {
+ return http.post(`/ai/agents/token/reset`, req);
+};
+
export const updateAgentModelConfig = (req: AI.AgentModelConfigUpdateReq) => {
return http.post(`/ai/agents/model/update`, req);
};
diff --git a/frontend/src/views/ai/agents/agent/config/tabs/channels.vue b/frontend/src/views/ai/agents/agent/config/tabs/channels.vue
index e7bcd2d7db27..a958bd121726 100644
--- a/frontend/src/views/ai/agents/agent/config/tabs/channels.vue
+++ b/frontend/src/views/ai/agents/agent/config/tabs/channels.vue
@@ -3,6 +3,11 @@
+
+
+ {{ t('container.mirrorsHelper2') }}
+
+
@@ -67,6 +72,10 @@ const rules = reactive({
appSecret: [Rules.requiredInput],
});
+const toFeishuDoc = () => {
+ window.open('https://openclaw.club/guides/feishu-platform', '_blank');
+};
+
const load = async (id: number) => {
agentId.value = id;
pairingCode.value = '';
diff --git a/frontend/src/views/ai/agents/agent/index.vue b/frontend/src/views/ai/agents/agent/index.vue
index 39ba592dc025..b7d1da5fa25a 100644
--- a/frontend/src/views/ai/agents/agent/index.vue
+++ b/frontend/src/views/ai/agents/agent/index.vue
@@ -58,7 +58,12 @@
-
+
+
+
+ {{ $t('commons.button.reset') }}
+
+
import { onMounted, reactive, ref } from 'vue';
-import { pageAgents } from '@/api/modules/ai';
+import { pageAgents, resetAgentToken } from '@/api/modules/ai';
import { installedOp, searchAppInstalled } from '@/api/modules/app';
import { AI } from '@/api/interface/ai';
import { App } from '@/api/interface/app';
import { SearchWithPage } from '@/api/interface';
import { dateFormat, newUUID } from '@/utils/util';
+import { MsgSuccess } from '@/utils/message';
import RouterMenu from '@/views/ai/agents/index.vue';
import AddDialog from '@/views/ai/agents/agent/add/index.vue';
@@ -242,12 +248,27 @@ const onDelete = (row: AI.AgentItem) => {
deleteRef.value?.acceptParams(row.id, row.name);
};
+const onResetToken = async (row: AI.AgentItem) => {
+ await ElMessageBox.confirm(
+ i18n.global.t('aiTools.mcp.operatorHelper', ['token', i18n.global.t('commons.button.reset')]),
+ i18n.global.t('commons.button.reset'),
+ {
+ confirmButtonText: i18n.global.t('commons.button.confirm'),
+ cancelButtonText: i18n.global.t('commons.button.cancel'),
+ type: 'info',
+ },
+ );
+ await resetAgentToken({ id: row.id });
+ MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
+ await search();
+};
+
const openConfig = (row: AI.AgentItem) => {
configRef.value?.open(row);
};
const openUpgrade = async (row: AI.AgentItem) => {
- const res = await searchAppInstalled({ page: 1, pageSize: 200, name: row.name });
+ const res = await searchAppInstalled({ page: 1, pageSize: 200, name: row.name, update: true });
const appInstall = (res.data.items || []).find((item: App.AppInstallDto) => item.id === row.appInstallId);
if (!appInstall) {
return;