Skip to content

Commit 487f3ea

Browse files
feat: add Feishu configuration for OpenClaw
1 parent 635d86a commit 487f3ea

21 files changed

Lines changed: 698 additions & 6 deletions

File tree

agent/app/api/v2/agents.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,64 @@ func (b *BaseApi) DeleteAgentAccount(c *gin.Context) {
189189
}
190190
helper.Success(c)
191191
}
192+
193+
// @Tags AI
194+
// @Summary Get Agent Feishu channel config
195+
// @Accept json
196+
// @Param request body dto.AgentFeishuConfigReq true "request"
197+
// @Success 200 {object} dto.AgentFeishuConfig
198+
// @Security ApiKeyAuth
199+
// @Security Timestamp
200+
// @Router /ai/agents/channel/feishu/get [post]
201+
func (b *BaseApi) GetAgentFeishuConfig(c *gin.Context) {
202+
var req dto.AgentFeishuConfigReq
203+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
204+
return
205+
}
206+
data, err := agentService.GetFeishuConfig(req)
207+
if err != nil {
208+
helper.BadRequest(c, err)
209+
return
210+
}
211+
helper.SuccessWithData(c, data)
212+
}
213+
214+
// @Tags AI
215+
// @Summary Update Agent Feishu channel config
216+
// @Accept json
217+
// @Param request body dto.AgentFeishuConfigUpdateReq true "request"
218+
// @Success 200
219+
// @Security ApiKeyAuth
220+
// @Security Timestamp
221+
// @Router /ai/agents/channel/feishu/update [post]
222+
func (b *BaseApi) UpdateAgentFeishuConfig(c *gin.Context) {
223+
var req dto.AgentFeishuConfigUpdateReq
224+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
225+
return
226+
}
227+
if err := agentService.UpdateFeishuConfig(req); err != nil {
228+
helper.BadRequest(c, err)
229+
return
230+
}
231+
helper.Success(c)
232+
}
233+
234+
// @Tags AI
235+
// @Summary Approve Agent Feishu pairing code
236+
// @Accept json
237+
// @Param request body dto.AgentFeishuPairingApproveReq true "request"
238+
// @Success 200
239+
// @Security ApiKeyAuth
240+
// @Security Timestamp
241+
// @Router /ai/agents/channel/feishu/approve [post]
242+
func (b *BaseApi) ApproveAgentFeishuPairing(c *gin.Context) {
243+
var req dto.AgentFeishuPairingApproveReq
244+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
245+
return
246+
}
247+
if err := agentService.ApproveFeishuPairing(req); err != nil {
248+
helper.BadRequest(c, err)
249+
return
250+
}
251+
helper.Success(c)
252+
}

agent/app/dto/agents.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type AgentItem struct {
4444
BridgePort int `json:"bridgePort"`
4545
Path string `json:"path"`
4646
ConfigPath string `json:"configPath"`
47+
Upgradable bool `json:"upgradable"`
4748
CreatedAt time.Time `json:"createdAt"`
4849
}
4950

@@ -108,3 +109,29 @@ type ProviderInfo struct {
108109
BaseURL string `json:"baseUrl"`
109110
Models []ProviderModelInfo `json:"models"`
110111
}
112+
113+
type AgentFeishuConfigReq struct {
114+
AgentID uint `json:"agentId" validate:"required"`
115+
}
116+
117+
type AgentFeishuConfigUpdateReq struct {
118+
AgentID uint `json:"agentId" validate:"required"`
119+
BotName string `json:"botName" validate:"required"`
120+
AppID string `json:"appId" validate:"required"`
121+
AppSecret string `json:"appSecret" validate:"required"`
122+
Enabled bool `json:"enabled"`
123+
DmPolicy string `json:"dmPolicy" validate:"required"`
124+
}
125+
126+
type AgentFeishuPairingApproveReq struct {
127+
AgentID uint `json:"agentId" validate:"required"`
128+
PairingCode string `json:"pairingCode" validate:"required"`
129+
}
130+
131+
type AgentFeishuConfig struct {
132+
Enabled bool `json:"enabled"`
133+
DmPolicy string `json:"dmPolicy"`
134+
BotName string `json:"botName"`
135+
AppID string `json:"appId"`
136+
AppSecret string `json:"appSecret"`
137+
}

agent/app/service/agents.go

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"github.com/1Panel-dev/1Panel/agent/buserr"
2121
"github.com/1Panel-dev/1Panel/agent/constant"
2222
"github.com/1Panel-dev/1Panel/agent/global"
23+
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
24+
"github.com/1Panel-dev/1Panel/agent/utils/common"
2325
"github.com/1Panel-dev/1Panel/agent/utils/files"
2426
)
2527

@@ -35,6 +37,9 @@ type IAgentService interface {
3537
PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error)
3638
VerifyAccount(req dto.AgentAccountVerifyReq) error
3739
DeleteAccount(req dto.AgentAccountDeleteReq) error
40+
GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error)
41+
UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error
42+
ApproveFeishuPairing(req dto.AgentFeishuPairingApproveReq) error
3843
}
3944

4045
func NewIAgentService() IAgentService {
@@ -172,7 +177,9 @@ func (a AgentService) Page(req dto.SearchWithPage) (int64, []dto.AgentItem, erro
172177
for _, item := range list {
173178
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(item.AppInstallID))
174179
envMap := readInstallEnv(appInstall.Env)
175-
items = append(items, buildAgentItem(&item, &appInstall, envMap))
180+
agentItem := buildAgentItem(&item, &appInstall, envMap)
181+
agentItem.Upgradable = checkAgentUpgradable(appInstall)
182+
items = append(items, agentItem)
176183
}
177184
return count, items, nil
178185
}
@@ -349,6 +356,156 @@ func (a AgentService) DeleteAccount(req dto.AgentAccountDeleteReq) error {
349356
return agentAccountRepo.DeleteByID(req.ID)
350357
}
351358

359+
func (a AgentService) GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error) {
360+
agent, install, err := a.loadAgentAndInstall(req.AgentID)
361+
if err != nil {
362+
return nil, err
363+
}
364+
_ = install
365+
conf, err := readOpenclawConfig(agent.ConfigPath)
366+
if err != nil {
367+
return nil, err
368+
}
369+
result := extractFeishuConfig(conf)
370+
return &result, nil
371+
}
372+
373+
func (a AgentService) UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error {
374+
agent, _, err := a.loadAgentAndInstall(req.AgentID)
375+
if err != nil {
376+
return err
377+
}
378+
conf, err := readOpenclawConfig(agent.ConfigPath)
379+
if err != nil {
380+
return err
381+
}
382+
if req.DmPolicy == "" {
383+
req.DmPolicy = "pairing"
384+
}
385+
setFeishuConfig(conf, dto.AgentFeishuConfig{
386+
Enabled: req.Enabled,
387+
DmPolicy: req.DmPolicy,
388+
BotName: req.BotName,
389+
AppID: req.AppID,
390+
AppSecret: req.AppSecret,
391+
})
392+
if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil {
393+
return err
394+
}
395+
return nil
396+
}
397+
398+
func (a AgentService) ApproveFeishuPairing(req dto.AgentFeishuPairingApproveReq) error {
399+
_, install, err := a.loadAgentAndInstall(req.AgentID)
400+
if err != nil {
401+
return err
402+
}
403+
if err := cmd.RunDefaultBashCf(
404+
"docker exec %s openclaw pairing approve feishu %q",
405+
install.ContainerName,
406+
strings.TrimSpace(req.PairingCode),
407+
); err != nil {
408+
return err
409+
}
410+
return nil
411+
}
412+
413+
func (a AgentService) loadAgentAndInstall(agentID uint) (*model.Agent, *model.AppInstall, error) {
414+
agent, err := agentRepo.GetFirst(repo.WithByID(agentID))
415+
if err != nil {
416+
return nil, nil, err
417+
}
418+
if agent.AppInstallID == 0 {
419+
return nil, nil, buserr.New("ErrRecordNotFound")
420+
}
421+
install, err := appInstallRepo.GetFirst(repo.WithByID(agent.AppInstallID))
422+
if err != nil {
423+
return nil, nil, err
424+
}
425+
return agent, &install, nil
426+
}
427+
428+
func readOpenclawConfig(configPath string) (map[string]interface{}, error) {
429+
if strings.TrimSpace(configPath) == "" {
430+
return nil, buserr.New("ErrRecordNotFound")
431+
}
432+
fileOp := files.NewFileOp()
433+
content, err := fileOp.GetContent(configPath)
434+
if err != nil {
435+
return nil, err
436+
}
437+
conf := map[string]interface{}{}
438+
if err := json.Unmarshal(content, &conf); err != nil {
439+
return nil, err
440+
}
441+
return conf, nil
442+
}
443+
444+
func writeOpenclawConfigRaw(configPath string, conf map[string]interface{}) error {
445+
payload, err := json.MarshalIndent(conf, "", " ")
446+
if err != nil {
447+
return err
448+
}
449+
fileOp := files.NewFileOp()
450+
return fileOp.SaveFile(configPath, string(payload), 0600)
451+
}
452+
453+
func extractFeishuConfig(conf map[string]interface{}) dto.AgentFeishuConfig {
454+
result := dto.AgentFeishuConfig{Enabled: true, DmPolicy: "pairing"}
455+
channels, ok := conf["channels"].(map[string]interface{})
456+
if !ok {
457+
return result
458+
}
459+
feishu, ok := channels["feishu"].(map[string]interface{})
460+
if !ok {
461+
return result
462+
}
463+
if enabled, ok := feishu["enabled"].(bool); ok {
464+
result.Enabled = enabled
465+
}
466+
if dmPolicy, ok := feishu["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
467+
result.DmPolicy = dmPolicy
468+
}
469+
accounts, ok := feishu["accounts"].(map[string]interface{})
470+
if !ok {
471+
return result
472+
}
473+
main, ok := accounts["main"].(map[string]interface{})
474+
if !ok {
475+
return result
476+
}
477+
if appID, ok := main["appId"].(string); ok {
478+
result.AppID = appID
479+
}
480+
if appSecret, ok := main["appSecret"].(string); ok {
481+
result.AppSecret = appSecret
482+
}
483+
if botName, ok := main["botName"].(string); ok {
484+
result.BotName = botName
485+
}
486+
return result
487+
}
488+
489+
func setFeishuConfig(conf map[string]interface{}, config dto.AgentFeishuConfig) {
490+
channels, ok := conf["channels"].(map[string]interface{})
491+
if !ok {
492+
channels = map[string]interface{}{}
493+
conf["channels"] = channels
494+
}
495+
feishu := map[string]interface{}{
496+
"enabled": config.Enabled,
497+
"dmPolicy": config.DmPolicy,
498+
"accounts": map[string]interface{}{
499+
"main": map[string]interface{}{
500+
"appId": config.AppID,
501+
"appSecret": config.AppSecret,
502+
"botName": config.BotName,
503+
},
504+
},
505+
}
506+
channels["feishu"] = feishu
507+
}
508+
352509
func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
353510
agents, err := agentRepo.List(repo.WithByAccountID(account.ID))
354511
if err != nil {
@@ -436,6 +593,39 @@ func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map
436593
return item
437594
}
438595

596+
func checkAgentUpgradable(install model.AppInstall) bool {
597+
if install.ID == 0 || install.Version == "" || install.Version == "latest" {
598+
return false
599+
}
600+
if install.App.ID == 0 {
601+
return false
602+
}
603+
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(install.App.ID))
604+
if err != nil || len(details) == 0 {
605+
return false
606+
}
607+
versions := make([]string, 0, len(details))
608+
for _, item := range details {
609+
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(item.ID), appIgnoreUpgradeRepo.WithScope("version"))
610+
if len(ignores) > 0 {
611+
continue
612+
}
613+
if common.IsCrossVersion(install.Version, item.Version) && !install.App.CrossVersionUpdate {
614+
continue
615+
}
616+
versions = append(versions, item.Version)
617+
}
618+
if len(versions) == 0 {
619+
return false
620+
}
621+
versions = common.GetSortedVersions(versions)
622+
lastVersion := versions[0]
623+
if common.IsCrossVersion(install.Version, lastVersion) {
624+
return install.App.CrossVersionUpdate
625+
}
626+
return common.CompareVersion(lastVersion, install.Version)
627+
}
628+
439629
func (a AgentService) waitAndDeleteAgent(agentID uint, appInstallID uint) {
440630
if appInstallID == 0 {
441631
_ = agentRepo.DeleteByID(agentID)

agent/router/ro_ai.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,8 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
4949
aiToolsRouter.POST("/agents/accounts/search", baseApi.PageAgentAccounts)
5050
aiToolsRouter.POST("/agents/accounts/verify", baseApi.VerifyAgentAccount)
5151
aiToolsRouter.POST("/agents/accounts/delete", baseApi.DeleteAgentAccount)
52+
aiToolsRouter.POST("/agents/channel/feishu/get", baseApi.GetAgentFeishuConfig)
53+
aiToolsRouter.POST("/agents/channel/feishu/update", baseApi.UpdateAgentFeishuConfig)
54+
aiToolsRouter.POST("/agents/channel/feishu/approve", baseApi.ApproveAgentFeishuPairing)
5255
}
5356
}

frontend/src/api/interface/ai.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ export namespace AI {
278278
bridgePort: number;
279279
path: string;
280280
configPath: string;
281+
upgradable: boolean;
281282
createdAt: string;
282283
}
283284

@@ -343,4 +344,30 @@ export namespace AI {
343344
export interface AgentAccountDeleteReq {
344345
id: number;
345346
}
347+
348+
export interface AgentFeishuConfigReq {
349+
agentId: number;
350+
}
351+
352+
export interface AgentFeishuConfig {
353+
enabled: boolean;
354+
dmPolicy: string;
355+
botName: string;
356+
appId: string;
357+
appSecret: string;
358+
}
359+
360+
export interface AgentFeishuConfigUpdateReq {
361+
agentId: number;
362+
enabled: boolean;
363+
dmPolicy: string;
364+
botName: string;
365+
appId: string;
366+
appSecret: string;
367+
}
368+
369+
export interface AgentFeishuPairingApproveReq {
370+
agentId: number;
371+
pairingCode: string;
372+
}
346373
}

frontend/src/api/modules/ai.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,15 @@ export const verifyAgentAccount = (req: AI.AgentAccountVerifyReq) => {
127127
export const deleteAgentAccount = (req: AI.AgentAccountDeleteReq) => {
128128
return http.post(`/ai/agents/accounts/delete`, req);
129129
};
130+
131+
export const getAgentFeishuConfig = (req: AI.AgentFeishuConfigReq) => {
132+
return http.post<AI.AgentFeishuConfig>(`/ai/agents/channel/feishu/get`, req);
133+
};
134+
135+
export const updateAgentFeishuConfig = (req: AI.AgentFeishuConfigUpdateReq) => {
136+
return http.post(`/ai/agents/channel/feishu/update`, req);
137+
};
138+
139+
export const approveAgentFeishuPairing = (req: AI.AgentFeishuPairingApproveReq) => {
140+
return http.post(`/ai/agents/channel/feishu/approve`, req);
141+
};

0 commit comments

Comments
 (0)