mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-13 20:47:49 +08:00
add sessions tool and subagents steer control
This commit is contained in:
@@ -51,6 +51,8 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
|
||||
workspace := cfg.WorkspacePath()
|
||||
os.MkdirAll(workspace, 0755)
|
||||
|
||||
sessionsManager := session.NewSessionManager(filepath.Join(filepath.Dir(cfg.WorkspacePath()), "sessions"))
|
||||
|
||||
toolsRegistry := tools.NewToolRegistry()
|
||||
toolsRegistry.Register(&tools.ReadFileTool{})
|
||||
toolsRegistry.Register(&tools.WriteFileTool{})
|
||||
@@ -98,6 +100,19 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
|
||||
spawnTool := tools.NewSpawnTool(subagentManager)
|
||||
toolsRegistry.Register(spawnTool)
|
||||
toolsRegistry.Register(tools.NewSubagentsTool(subagentManager))
|
||||
toolsRegistry.Register(tools.NewSessionsTool(
|
||||
func(limit int) []tools.SessionInfo {
|
||||
sessions := alSessionListForTool(sessionsManager, limit)
|
||||
return sessions
|
||||
},
|
||||
func(key string, limit int) []providers.Message {
|
||||
h := sessionsManager.GetHistory(key)
|
||||
if limit > 0 && len(h) > limit {
|
||||
return h[len(h)-limit:]
|
||||
}
|
||||
return h
|
||||
},
|
||||
))
|
||||
|
||||
// Register edit file tool
|
||||
editFileTool := tools.NewEditFileTool(workspace)
|
||||
@@ -120,8 +135,6 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
|
||||
// Register system info tool
|
||||
toolsRegistry.Register(tools.NewSystemInfoTool())
|
||||
|
||||
sessionsManager := session.NewSessionManager(filepath.Join(filepath.Dir(cfg.WorkspacePath()), "sessions"))
|
||||
|
||||
loop := &AgentLoop{
|
||||
bus: msgBus,
|
||||
provider: provider,
|
||||
@@ -792,3 +805,17 @@ func truncateString(s string, maxLen int) string {
|
||||
}
|
||||
return s[:maxLen-3] + "..."
|
||||
}
|
||||
|
||||
func alSessionListForTool(sm *session.SessionManager, limit int) []tools.SessionInfo {
|
||||
items := sm.List(limit)
|
||||
out := make([]tools.SessionInfo, 0, len(items))
|
||||
for _, s := range items {
|
||||
out = append(out, tools.SessionInfo{
|
||||
Key: s.Key,
|
||||
Kind: s.Kind,
|
||||
Summary: s.Summary,
|
||||
UpdatedAt: s.Updated,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -250,6 +250,29 @@ func (sm *SessionManager) Keys() []string {
|
||||
return keys
|
||||
}
|
||||
|
||||
func (sm *SessionManager) List(limit int) []Session {
|
||||
sm.mu.RLock()
|
||||
defer sm.mu.RUnlock()
|
||||
items := make([]Session, 0, len(sm.sessions))
|
||||
for _, s := range sm.sessions {
|
||||
s.mu.RLock()
|
||||
items = append(items, Session{
|
||||
Key: s.Key,
|
||||
Kind: s.Kind,
|
||||
Summary: s.Summary,
|
||||
LastLanguage: s.LastLanguage,
|
||||
PreferredLanguage: s.PreferredLanguage,
|
||||
Created: s.Created,
|
||||
Updated: s.Updated,
|
||||
})
|
||||
s.mu.RUnlock()
|
||||
}
|
||||
if limit > 0 && len(items) > limit {
|
||||
return items[:limit]
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func detectSessionKind(key string) string {
|
||||
k := strings.TrimSpace(strings.ToLower(key))
|
||||
switch {
|
||||
|
||||
101
pkg/tools/sessions_tool.go
Normal file
101
pkg/tools/sessions_tool.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/providers"
|
||||
)
|
||||
|
||||
type SessionInfo struct {
|
||||
Key string
|
||||
Kind string
|
||||
Summary string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type SessionsTool struct {
|
||||
listFn func(limit int) []SessionInfo
|
||||
historyFn func(key string, limit int) []providers.Message
|
||||
}
|
||||
|
||||
func NewSessionsTool(listFn func(limit int) []SessionInfo, historyFn func(key string, limit int) []providers.Message) *SessionsTool {
|
||||
return &SessionsTool{listFn: listFn, historyFn: historyFn}
|
||||
}
|
||||
|
||||
func (t *SessionsTool) Name() string { return "sessions" }
|
||||
|
||||
func (t *SessionsTool) Description() string {
|
||||
return "Inspect sessions in current runtime: list or history"
|
||||
}
|
||||
|
||||
func (t *SessionsTool) Parameters() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"action": map[string]interface{}{"type": "string", "description": "list|history"},
|
||||
"key": map[string]interface{}{"type": "string", "description": "session key for history"},
|
||||
"limit": map[string]interface{}{"type": "integer", "description": "max items", "default": 20},
|
||||
},
|
||||
"required": []string{"action"},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *SessionsTool) Execute(ctx context.Context, args map[string]interface{}) (string, error) {
|
||||
_ = ctx
|
||||
action, _ := args["action"].(string)
|
||||
action = strings.ToLower(strings.TrimSpace(action))
|
||||
limit := 20
|
||||
if v, ok := args["limit"].(float64); ok && int(v) > 0 {
|
||||
limit = int(v)
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "list":
|
||||
if t.listFn == nil {
|
||||
return "sessions list unavailable", nil
|
||||
}
|
||||
items := t.listFn(limit)
|
||||
if len(items) == 0 {
|
||||
return "No sessions.", nil
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool { return items[i].UpdatedAt.After(items[j].UpdatedAt) })
|
||||
var sb strings.Builder
|
||||
sb.WriteString("Sessions:\n")
|
||||
for _, s := range items {
|
||||
sb.WriteString(fmt.Sprintf("- %s kind=%s updated=%s\n", s.Key, s.Kind, s.UpdatedAt.Format(time.RFC3339)))
|
||||
}
|
||||
return strings.TrimSpace(sb.String()), nil
|
||||
case "history":
|
||||
if t.historyFn == nil {
|
||||
return "sessions history unavailable", nil
|
||||
}
|
||||
key, _ := args["key"].(string)
|
||||
key = strings.TrimSpace(key)
|
||||
if key == "" {
|
||||
return "key is required for history", nil
|
||||
}
|
||||
h := t.historyFn(key, limit)
|
||||
if len(h) == 0 {
|
||||
return "No history.", nil
|
||||
}
|
||||
if len(h) > limit {
|
||||
h = h[len(h)-limit:]
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("History for %s:\n", key))
|
||||
for _, m := range h {
|
||||
content := strings.TrimSpace(m.Content)
|
||||
if len(content) > 180 {
|
||||
content = content[:180] + "..."
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("- [%s] %s\n", m.Role, content))
|
||||
}
|
||||
return strings.TrimSpace(sb.String()), nil
|
||||
default:
|
||||
return "unsupported action", nil
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package tools
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -22,6 +23,7 @@ type SubagentTask struct {
|
||||
OriginChatID string
|
||||
Status string
|
||||
Result string
|
||||
Steering []string
|
||||
Created int64
|
||||
Updated int64
|
||||
}
|
||||
@@ -230,3 +232,19 @@ func (sm *SubagentManager) KillTask(taskID string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (sm *SubagentManager) SteerTask(taskID, message string) bool {
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
t, ok := sm.tasks[taskID]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
message = strings.TrimSpace(message)
|
||||
if message == "" {
|
||||
return false
|
||||
}
|
||||
t.Steering = append(t.Steering, message)
|
||||
t.Updated = time.Now().UnixMilli()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -17,15 +17,16 @@ func NewSubagentsTool(m *SubagentManager) *SubagentsTool {
|
||||
func (t *SubagentsTool) Name() string { return "subagents" }
|
||||
|
||||
func (t *SubagentsTool) Description() string {
|
||||
return "Manage subagent runs in current process: list, info, kill"
|
||||
return "Manage subagent runs in current process: list, info, kill, steer"
|
||||
}
|
||||
|
||||
func (t *SubagentsTool) Parameters() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"action": map[string]interface{}{"type": "string", "description": "list|info|kill"},
|
||||
"id": map[string]interface{}{"type": "string", "description": "subagent id for info/kill"},
|
||||
"action": map[string]interface{}{"type": "string", "description": "list|info|kill|steer"},
|
||||
"id": map[string]interface{}{"type": "string", "description": "subagent id for info/kill/steer"},
|
||||
"message": map[string]interface{}{"type": "string", "description": "steering message for steer action"},
|
||||
},
|
||||
"required": []string{"action"},
|
||||
}
|
||||
@@ -40,6 +41,8 @@ func (t *SubagentsTool) Execute(ctx context.Context, args map[string]interface{}
|
||||
action = strings.ToLower(strings.TrimSpace(action))
|
||||
id, _ := args["id"].(string)
|
||||
id = strings.TrimSpace(id)
|
||||
message, _ := args["message"].(string)
|
||||
message = strings.TrimSpace(message)
|
||||
|
||||
switch action {
|
||||
case "list":
|
||||
@@ -70,6 +73,14 @@ func (t *SubagentsTool) Execute(ctx context.Context, args map[string]interface{}
|
||||
return "subagent not found", nil
|
||||
}
|
||||
return "subagent kill requested", nil
|
||||
case "steer":
|
||||
if id == "" || message == "" {
|
||||
return "id and message are required for steer", nil
|
||||
}
|
||||
if !t.manager.SteerTask(id, message) {
|
||||
return "subagent not found", nil
|
||||
}
|
||||
return "steering message accepted", nil
|
||||
default:
|
||||
return "unsupported action", nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user