package service import ( "database/sql" "fmt" "math" "path/filepath" "strings" "time" "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/files" ) const hermesSessionListLimit = 100 func (a AgentService) GetHermesChatSessions(req dto.AgentIDReq) ([]dto.AgentHermesChatSessionItem, error) { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } if agent.AgentType != constant.AppHermesAgent { return nil, fmt.Errorf("%s does not support", agent.AgentType) } return listHermesChatSessionsFromStateDB(filepath.Join(install.GetPath(), "data", "state.db")) } func (a AgentService) RenameHermesChatSession(req dto.AgentHermesChatSessionRenameReq) 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", "rename", req.ID, req.Title)..., ) 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 } db, err := sql.Open("sqlite", stateDBPath) if err != nil { return nil, err } defer db.Close() rows, err := db.Query(` SELECT s.id, COALESCE(NULLIF(TRIM(s.title), ''), s.id) AS title, COALESCE(s.model, '') AS model, COALESCE(s.message_count, 0) AS message_count, s.started_at, COALESCE(MAX(m.timestamp), s.started_at) AS last_active FROM sessions s LEFT JOIN messages m ON m.session_id = s.id WHERE s.source = 'cli' GROUP BY s.id, title, model, s.message_count, s.started_at ORDER BY last_active DESC LIMIT ? `, hermesSessionListLimit) if err != nil { return nil, err } defer rows.Close() items := make([]dto.AgentHermesChatSessionItem, 0, 8) for rows.Next() { var item dto.AgentHermesChatSessionItem var title sql.NullString var model sql.NullString var startedAt sql.NullFloat64 var lastActive sql.NullFloat64 if err := rows.Scan(&item.ID, &title, &model, &item.MessageCount, &startedAt, &lastActive); err != nil { return nil, err } item.Title = strings.TrimSpace(title.String) if item.Title == "" { item.Title = item.ID } item.Model = strings.TrimSpace(model.String) item.StartedAt = formatHermesSessionTimestamp(startedAt) item.LastActive = formatHermesSessionTimestamp(lastActive) items = append(items, item) } if err := rows.Err(); err != nil { return nil, err } return items, nil } func formatHermesSessionTimestamp(value sql.NullFloat64) string { if !value.Valid || value.Float64 <= 0 { return "" } seconds, fraction := math.Modf(value.Float64) return time.Unix(int64(seconds), int64(fraction*float64(time.Second))).UTC().Format(time.RFC3339) }