From cd30ec763294e2648ee38c20b9f384e02f2d5270 Mon Sep 17 00:00:00 2001 From: CityFun <31820853+zhengkunwang223@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:08:56 +0800 Subject: [PATCH] feat: support session delete for hermes-agent (#12533) --- agent/app/api/v2/agents.go | 20 ++++++ agent/app/dto/agents.go | 5 ++ agent/app/service/agents.go | 16 ++++- agent/app/service/agents_hermes.go | 29 ++------ agent/app/service/agents_hermes_chat.go | 16 +++++ agent/app/service/agents_skills.go | 9 ++- agent/router/ro_ai.go | 1 + frontend/src/api/interface/ai.ts | 5 ++ frontend/src/api/modules/ai.ts | 4 ++ frontend/src/lang/modules/en.ts | 2 + frontend/src/lang/modules/es-es.ts | 2 + frontend/src/lang/modules/ja.ts | 2 + frontend/src/lang/modules/ko.ts | 2 + frontend/src/lang/modules/ms.ts | 2 + frontend/src/lang/modules/pt-br.ts | 2 + frontend/src/lang/modules/ru.ts | 2 + frontend/src/lang/modules/tr.ts | 2 + frontend/src/lang/modules/zh-Hant.ts | 2 + frontend/src/lang/modules/zh.ts | 2 + .../agents/agent/components/hermes-chat.vue | 69 ++++++++++++++++++- .../config/tabs/channels/openclaw/feishu.vue | 9 --- .../agent/config/tabs/settings/other.vue | 1 + 22 files changed, 164 insertions(+), 40 deletions(-) diff --git a/agent/app/api/v2/agents.go b/agent/app/api/v2/agents.go index e4d31537e..5492a64ec 100644 --- a/agent/app/api/v2/agents.go +++ b/agent/app/api/v2/agents.go @@ -275,6 +275,26 @@ func (b *BaseApi) RenameHermesChatSession(c *gin.Context) { helper.Success(c) } +// @Tags AI +// @Summary Delete Hermes chat session +// @Accept json +// @Param request body dto.AgentHermesChatSessionDeleteReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/agents/hermes/chat/sessions/delete [post] +func (b *BaseApi) DeleteHermesChatSession(c *gin.Context) { + var req dto.AgentHermesChatSessionDeleteReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := agentService.DeleteHermesChatSession(req); err != nil { + helper.BadRequest(c, err) + return + } + helper.Success(c) +} + // @Tags AI // @Summary Get Providers // @Success 200 {array} dto.ProviderInfo diff --git a/agent/app/dto/agents.go b/agent/app/dto/agents.go index cea1804df..56af9e1fd 100644 --- a/agent/app/dto/agents.go +++ b/agent/app/dto/agents.go @@ -115,6 +115,11 @@ type AgentHermesChatSessionRenameReq struct { Title string `json:"title" validate:"required"` } +type AgentHermesChatSessionDeleteReq struct { + AgentID uint `json:"agentId" validate:"required"` + ID string `json:"id" validate:"required"` +} + type AgentOverview struct { Snapshot AgentOverviewSnapshot `json:"snapshot"` } diff --git a/agent/app/service/agents.go b/agent/app/service/agents.go index eb8e6e64f..a4c551b0b 100644 --- a/agent/app/service/agents.go +++ b/agent/app/service/agents.go @@ -41,6 +41,7 @@ type IAgentService interface { UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error GetHermesChatSessions(req dto.AgentIDReq) ([]dto.AgentHermesChatSessionItem, error) RenameHermesChatSession(req dto.AgentHermesChatSessionRenameReq) error + DeleteHermesChatSession(req dto.AgentHermesChatSessionDeleteReq) error GetOverview(req dto.AgentOverviewReq) (*dto.AgentOverview, error) GetProviders() ([]dto.ProviderInfo, error) GetSecurityConfig(req dto.AgentIDReq) (*dto.AgentSecurityConfig, error) @@ -456,7 +457,7 @@ func (a AgentService) GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig, if err != nil { return nil, err } - model, err := resolveHermesConfiguredModelIDStrict(account, accountModels, cfg.Model.Default) + model, err := resolveHermesConfiguredModelID(account, accountModels, cfg.Model.Default) if err != nil { return nil, err } @@ -905,7 +906,13 @@ func (a AgentService) UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error if err != nil { return err } - return writeHermesConfig(path.Dir(agent.ConfigPath), account, agent.Model, strings.TrimSpace(req.UserTimezone)) + if err := writeHermesConfig(path.Dir(agent.ConfigPath), account, agent.Model, strings.TrimSpace(req.UserTimezone)); err != nil { + return err + } + return NewIAppInstalledService().Operate(request.AppInstalledOperate{ + InstallId: install.ID, + Operate: constant.Restart, + }) } if err := ensureContainerRunning(install.ContainerName); err != nil { return err @@ -921,7 +928,10 @@ func (a AgentService) UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil { return err } - return setOpenclawNPMRegistry(install.ContainerName, req.NPMRegistry) + if err := setOpenclawNPMRegistry(install.ContainerName, req.NPMRegistry); err != nil { + return err + } + return nil } func (a AgentService) GetConfigFile(req dto.AgentConfigFileReq) (*dto.AgentConfigFile, error) { diff --git a/agent/app/service/agents_hermes.go b/agent/app/service/agents_hermes.go index 21d61c58e..e50b55ed4 100644 --- a/agent/app/service/agents_hermes.go +++ b/agent/app/service/agents_hermes.go @@ -54,17 +54,6 @@ func buildHermesDockerExecArgs(containerName string, hermesArgs ...string) []str return buildHermesDockerExecCommandArgs(containerName, "hermes", hermesArgs...) } -func buildHermesSkillUninstallArgs(containerName, skillName string) []string { - return buildHermesDockerExecCommandArgs( - containerName, - "sh", - "-lc", - fmt.Sprintf(`printf 'y\n' | %s skills uninstall "$1"`, hermesExecutablePath), - "sh", - skillName, - ) -} - func writeHermesConfig(confDir string, account *model.AgentAccount, modelName string, timezone string) error { if strings.TrimSpace(confDir) == "" { return fmt.Errorf("config dir is required") @@ -382,29 +371,21 @@ func resolveHermesModel(sourceProvider, targetProvider, modelName string) string return target } -func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels []dto.AgentAccountModel, configuredModel string) string { +func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels []dto.AgentAccountModel, configuredModel string) (string, error) { if account == nil { - return "" + return "", buserr.New("ErrAgentModelNotInAccount") } configuredModel = strings.TrimSpace(configuredModel) if configuredModel == "" { - return "" + return "", buserr.New("ErrAgentModelNotInAccount") } provider := resolveHermesProvider(account.Provider) for _, item := range accountModels { if resolveHermesModel(account.Provider, provider, item.ID) == configuredModel { - return item.ID + return item.ID, nil } } - return "" -} - -func resolveHermesConfiguredModelIDStrict(account *model.AgentAccount, accountModels []dto.AgentAccountModel, configuredModel string) (string, error) { - modelID := resolveHermesConfiguredModelID(account, accountModels, configuredModel) - if modelID == "" { - return "", buserr.New("ErrAgentModelNotInAccount") - } - return modelID, nil + return "", buserr.New("ErrAgentModelNotInAccount") } func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry { diff --git a/agent/app/service/agents_hermes_chat.go b/agent/app/service/agents_hermes_chat.go index a671e53a8..aa562e377 100644 --- a/agent/app/service/agents_hermes_chat.go +++ b/agent/app/service/agents_hermes_chat.go @@ -43,6 +43,22 @@ func (a AgentService) RenameHermesChatSession(req dto.AgentHermesChatSessionRena return err } +func (a AgentService) DeleteHermesChatSession(req dto.AgentHermesChatSessionDeleteReq) error { + agent, install, err := a.loadAgentAndInstall(req.AgentID) + if err != nil { + return err + } + if agent.AgentType != constant.AppHermesAgent { + return fmt.Errorf("%s does not support", agent.AgentType) + } + + _, err = cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout( + "docker", + buildHermesDockerExecArgs(install.ContainerName, "sessions", "delete", req.ID, "--yes")..., + ) + return err +} + func listHermesChatSessionsFromStateDB(stateDBPath string) ([]dto.AgentHermesChatSessionItem, error) { if !files.NewFileOp().Stat(stateDBPath) { return []dto.AgentHermesChatSessionItem{}, nil diff --git a/agent/app/service/agents_skills.go b/agent/app/service/agents_skills.go index defb55214..578248909 100644 --- a/agent/app/service/agents_skills.go +++ b/agent/app/service/agents_skills.go @@ -166,7 +166,14 @@ func (a AgentService) UninstallSkill(req dto.AgentSkillUninstallReq) error { } return cmd.NewCommandMgr(cmd.WithTimeout(5*time.Minute)).Run( "docker", - buildHermesSkillUninstallArgs(install.ContainerName, req.Name)..., + buildHermesDockerExecCommandArgs( + install.ContainerName, + "sh", + "-lc", + fmt.Sprintf(`printf 'y\n' | %s skills uninstall "$1"`, hermesExecutablePath), + "sh", + req.Name, + )..., ) } diff --git a/agent/router/ro_ai.go b/agent/router/ro_ai.go index 4776623ca..a33f3e19e 100644 --- a/agent/router/ro_ai.go +++ b/agent/router/ro_ai.go @@ -52,6 +52,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) { aiToolsRouter.POST("/agents/model/update", baseApi.UpdateAgentModelConfig) aiToolsRouter.POST("/agents/hermes/chat/sessions", baseApi.GetHermesChatSessions) aiToolsRouter.POST("/agents/hermes/chat/sessions/rename", baseApi.RenameHermesChatSession) + aiToolsRouter.POST("/agents/hermes/chat/sessions/delete", baseApi.DeleteHermesChatSession) aiToolsRouter.POST("/agents/overview", baseApi.GetAgentOverview) 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 1bc8128eb..821134855 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -343,6 +343,11 @@ export namespace AI { title: string; } + export interface AgentHermesChatSessionDeleteReq { + agentId: number; + id: string; + } + export interface AgentOverviewReq { agentId: number; } diff --git a/frontend/src/api/modules/ai.ts b/frontend/src/api/modules/ai.ts index d96f627cf..e1bda0810 100644 --- a/frontend/src/api/modules/ai.ts +++ b/frontend/src/api/modules/ai.ts @@ -142,6 +142,10 @@ export const renameAgentHermesChatSession = (req: AI.AgentHermesChatSessionRenam return http.post(`/ai/agents/hermes/chat/sessions/rename`, req); }; +export const deleteAgentHermesChatSession = (req: AI.AgentHermesChatSessionDeleteReq) => { + return http.post(`/ai/agents/hermes/chat/sessions/delete`, req); +}; + export const getAgentOverview = (req: AI.AgentOverviewReq) => { return http.post(`/ai/agents/overview`, req, TimeoutEnum.T_5M); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index bcbb02b8e..2223784ad 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -783,6 +783,8 @@ const message = { hermesChatEmptyHint: 'Select a session or click New Chat', hermesChatTitlePlaceholder: 'Enter session title', hermesChatRenameSuccess: 'Session title updated', + hermesChatDeleteConfirm: 'Delete session {0}?', + hermesChatDeleteSuccess: 'Session deleted', weixin: 'Weixin', wecom: 'WeCom', dingtalk: 'DingTalk', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 13b746c96..54e89d6da 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -793,6 +793,8 @@ const message = { hermesChatEmptyHint: 'Seleccione una sesión a la izquierda o haga clic en Nueva conversación', hermesChatTitlePlaceholder: 'Introduzca el título de la sesión', hermesChatRenameSuccess: 'El título de la sesión se ha actualizado', + hermesChatDeleteConfirm: '¿Eliminar la sesión {0}?', + hermesChatDeleteSuccess: 'Sesión eliminada', weixin: 'Weixin', wecom: 'WeCom', dingtalk: 'DingTalk', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 1f62d5039..69ea1e505 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -786,6 +786,8 @@ const message = { hermesChatEmptyHint: '左側のセッションを選択するか、「新しい会話」をクリックしてください', hermesChatTitlePlaceholder: 'セッションタイトルを入力してください', hermesChatRenameSuccess: 'セッションタイトルを更新しました', + hermesChatDeleteConfirm: 'セッション {0} を削除しますか?', + hermesChatDeleteSuccess: 'セッションを削除しました', weixin: 'Weixin', wecom: 'WeCom', dingtalk: 'DingTalk', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index afc483edf..7e74b11fe 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -772,6 +772,8 @@ const message = { hermesChatEmptyHint: '왼쪽 세션을 선택하거나 새 대화를 클릭하세요', hermesChatTitlePlaceholder: '세션 제목을 입력하세요', hermesChatRenameSuccess: '세션 제목이 업데이트되었습니다', + hermesChatDeleteConfirm: '세션 {0}을(를) 삭제하시겠습니까?', + hermesChatDeleteSuccess: '세션이 삭제되었습니다', weixin: 'Weixin', wecom: 'WeCom', dingtalk: 'DingTalk', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 2c924e1f8..ce4aaec90 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -791,6 +791,8 @@ const message = { hermesChatEmptyHint: 'Pilih sesi di sebelah kiri atau klik Perbualan baharu', hermesChatTitlePlaceholder: 'Masukkan tajuk sesi', hermesChatRenameSuccess: 'Tajuk sesi telah dikemas kini', + hermesChatDeleteConfirm: 'Padam sesi {0}?', + hermesChatDeleteSuccess: 'Sesi berjaya dipadam', weixin: 'Weixin', wecom: 'WeCom', dingtalk: 'DingTalk', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 4ae1e36a4..814a1ef6d 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -788,6 +788,8 @@ const message = { hermesChatEmptyHint: 'Selecione uma sessão à esquerda ou clique em Nova conversa', hermesChatTitlePlaceholder: 'Digite o título da sessão', hermesChatRenameSuccess: 'O título da sessão foi atualizado', + hermesChatDeleteConfirm: 'Excluir a sessão {0}?', + hermesChatDeleteSuccess: 'Sessão excluída', weixin: 'Weixin', wecom: 'WeCom', dingtalk: 'DingTalk', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index dfa913796..12327792e 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -783,6 +783,8 @@ const message = { hermesChatEmptyHint: 'Выберите сессию слева или нажмите «Новый диалог»', hermesChatTitlePlaceholder: 'Введите название сессии', hermesChatRenameSuccess: 'Название сессии обновлено', + hermesChatDeleteConfirm: 'Удалить сессию {0}?', + hermesChatDeleteSuccess: 'Сессия удалена', weixin: 'Weixin', wecom: 'WeCom', dingtalk: 'DingTalk', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 53e3819d3..ecda60080 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -790,6 +790,8 @@ const message = { hermesChatEmptyHint: "Soldan bir oturum seçin veya Yeni sohbet'e tıklayın", hermesChatTitlePlaceholder: 'Oturum başlığını girin', hermesChatRenameSuccess: 'Oturum başlığı güncellendi', + hermesChatDeleteConfirm: '{0} oturumu silinsin mi?', + hermesChatDeleteSuccess: 'Oturum silindi', weixin: 'Weixin', wecom: 'WeCom', dingtalk: 'DingTalk', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 1bae6c25f..a658c43c1 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -737,6 +737,8 @@ const message = { hermesChatEmptyHint: '選擇左側會話或點擊新對話', hermesChatTitlePlaceholder: '請輸入會話標題', hermesChatRenameSuccess: '會話標題已更新', + hermesChatDeleteConfirm: '確認刪除會話 {0}?', + hermesChatDeleteSuccess: '會話已刪除', weixin: '微信', wecom: '企業微信', dingtalk: '釘釘', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 017f01d07..f17cd890a 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -732,6 +732,8 @@ const message = { hermesChatEmptyHint: '选择左侧会话或点击新对话', hermesChatTitlePlaceholder: '请输入会话标题', hermesChatRenameSuccess: '会话标题已更新', + hermesChatDeleteConfirm: '确认删除会话 {0}?', + hermesChatDeleteSuccess: '会话已删除', weixin: '微信', wecom: '企业微信', dingtalk: '钉钉', diff --git a/frontend/src/views/ai/agents/agent/components/hermes-chat.vue b/frontend/src/views/ai/agents/agent/components/hermes-chat.vue index e753a5c6c..7f1ca8ec5 100644 --- a/frontend/src/views/ai/agents/agent/components/hermes-chat.vue +++ b/frontend/src/views/ai/agents/agent/components/hermes-chat.vue @@ -43,7 +43,12 @@
{{ item.title || item.id }}