mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-08-01 02:21:06 +00:00
Multi-agent AI gateway with WebSocket RPC, HTTP API, and messaging channel integrations. Go port of OpenClaw with multi-tenant PostgreSQL, per-user isolation, security hardening, and production observability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
191 lines
4.5 KiB
Go
191 lines
4.5 KiB
Go
package cmd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"text/tabwriter"
|
|
"time"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/config"
|
|
"github.com/nextlevelbuilder/goclaw/internal/sessions"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store/file"
|
|
"github.com/nextlevelbuilder/goclaw/pkg/protocol"
|
|
)
|
|
|
|
func sessionsCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "sessions",
|
|
Short: "View and manage chat sessions",
|
|
}
|
|
cmd.AddCommand(sessionsListCmd())
|
|
cmd.AddCommand(sessionsDeleteCmd())
|
|
cmd.AddCommand(sessionsResetCmd())
|
|
return cmd
|
|
}
|
|
|
|
func sessionsListCmd() *cobra.Command {
|
|
var jsonOutput bool
|
|
var agentFilter string
|
|
cmd := &cobra.Command{
|
|
Use: "list",
|
|
Short: "List all sessions",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
if isManagedMode() {
|
|
sessionsListRPC(agentFilter, jsonOutput)
|
|
return
|
|
}
|
|
mgr := loadSessionsStore()
|
|
infos := mgr.List(agentFilter)
|
|
printSessionInfos(infos, jsonOutput)
|
|
},
|
|
}
|
|
cmd.Flags().BoolVar(&jsonOutput, "json", false, "output as JSON")
|
|
cmd.Flags().StringVar(&agentFilter, "agent", "", "filter by agent ID")
|
|
return cmd
|
|
}
|
|
|
|
func sessionsDeleteCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "delete [key]",
|
|
Short: "Delete a session",
|
|
Args: cobra.ExactArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
if isManagedMode() {
|
|
sessionsDeleteRPC(args[0])
|
|
return
|
|
}
|
|
mgr := loadSessionsStore()
|
|
if err := mgr.Delete(args[0]); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Printf("Deleted session: %s\n", args[0])
|
|
},
|
|
}
|
|
}
|
|
|
|
func sessionsResetCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "reset [key]",
|
|
Short: "Clear session history (keep session)",
|
|
Args: cobra.ExactArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
if isManagedMode() {
|
|
sessionsResetRPC(args[0])
|
|
return
|
|
}
|
|
mgr := loadSessionsStore()
|
|
mgr.Reset(args[0])
|
|
fmt.Printf("Reset session: %s\n", args[0])
|
|
},
|
|
}
|
|
}
|
|
|
|
// --- RPC implementations (managed mode) ---
|
|
|
|
func sessionsListRPC(agentFilter string, jsonOutput bool) {
|
|
requireGatewayForManaged()
|
|
|
|
params, _ := json.Marshal(map[string]string{"agentId": agentFilter})
|
|
resp, err := gatewayRPC(protocol.MethodSessionsList, params)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
if !resp.OK {
|
|
fmt.Fprintf(os.Stderr, "Failed: %s\n", resp.Error.Message)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Parse response payload → []store.SessionInfo
|
|
raw, _ := json.Marshal(resp.Payload)
|
|
var result struct {
|
|
Sessions []store.SessionInfo `json:"sessions"`
|
|
}
|
|
if err := json.Unmarshal(raw, &result); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
printSessionInfos(result.Sessions, jsonOutput)
|
|
}
|
|
|
|
func sessionsDeleteRPC(key string) {
|
|
requireGatewayForManaged()
|
|
|
|
params, _ := json.Marshal(map[string]string{"key": key})
|
|
resp, err := gatewayRPC(protocol.MethodSessionsDelete, params)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
if !resp.OK {
|
|
fmt.Fprintf(os.Stderr, "Failed: %s\n", resp.Error.Message)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Printf("Deleted session: %s\n", key)
|
|
}
|
|
|
|
func sessionsResetRPC(key string) {
|
|
requireGatewayForManaged()
|
|
|
|
params, _ := json.Marshal(map[string]string{"key": key})
|
|
resp, err := gatewayRPC(protocol.MethodSessionsReset, params)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
if !resp.OK {
|
|
fmt.Fprintf(os.Stderr, "Failed: %s\n", resp.Error.Message)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Printf("Reset session: %s\n", key)
|
|
}
|
|
|
|
// --- Shared display ---
|
|
|
|
func printSessionInfos(infos []store.SessionInfo, jsonOutput bool) {
|
|
if jsonOutput {
|
|
data, _ := json.MarshalIndent(infos, "", " ")
|
|
fmt.Println(string(data))
|
|
return
|
|
}
|
|
|
|
if len(infos) == 0 {
|
|
fmt.Println("No sessions found.")
|
|
return
|
|
}
|
|
|
|
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
fmt.Fprintf(tw, "KEY\tMESSAGES\tCREATED\tUPDATED\n")
|
|
for _, s := range infos {
|
|
fmt.Fprintf(tw, "%s\t%d\t%s\t%s\n",
|
|
truncateStr(s.Key, 50),
|
|
s.MessageCount,
|
|
s.Created.Format(time.DateTime),
|
|
s.Updated.Format(time.DateTime),
|
|
)
|
|
}
|
|
tw.Flush()
|
|
}
|
|
|
|
// --- File-based helpers (standalone mode) ---
|
|
|
|
func loadSessionsStore() store.SessionStore {
|
|
cfgPath := resolveConfigPath()
|
|
cfg, _ := config.Load(cfgPath)
|
|
storage := config.ExpandHome(cfg.Sessions.Storage)
|
|
return file.NewFileSessionStore(sessions.NewManager(storage))
|
|
}
|
|
|
|
func truncateStr(s string, max int) string {
|
|
if len(s) <= max {
|
|
return s
|
|
}
|
|
return s[:max-3] + "..."
|
|
}
|