@@ -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
4045func 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+
352509func (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+
439629func (a AgentService ) waitAndDeleteAgent (agentID uint , appInstallID uint ) {
440630 if appInstallID == 0 {
441631 _ = agentRepo .DeleteByID (agentID )
0 commit comments