From f252afab64c7beb7ccd2d8b1773963c08eaf74ce Mon Sep 17 00:00:00 2001 From: DBT Date: Mon, 23 Feb 2026 13:45:02 +0000 Subject: [PATCH] add subagent index selectors and session active filters --- cmd/clawgo/cmd_status.go | 54 ++++++++++++++++++++++++++++++++ pkg/tools/sessions_tool.go | 25 ++++++++++++--- pkg/tools/subagents_tool.go | 61 ++++++++++++++++++++++++++++--------- 3 files changed, 121 insertions(+), 19 deletions(-) diff --git a/cmd/clawgo/cmd_status.go b/cmd/clawgo/cmd_status.go index 250bc38..0a32a13 100644 --- a/cmd/clawgo/cmd_status.go +++ b/cmd/clawgo/cmd_status.go @@ -5,7 +5,9 @@ import ( "fmt" "os" "path/filepath" + "sort" "strings" + "time" "clawgo/pkg/providers" ) @@ -101,6 +103,12 @@ func statusCmd() { } } } + if recent, err := collectRecentSubagentSessions(sessionsDir, 5); err == nil && len(recent) > 0 { + fmt.Println("Recent Subagent Sessions:") + for _, key := range recent { + fmt.Printf(" - %s\n", key) + } + } } } @@ -133,3 +141,49 @@ func collectSessionKindCounts(sessionsDir string) (map[string]int, error) { } return counts, nil } + +func collectRecentSubagentSessions(sessionsDir string, limit int) ([]string, error) { + entries, err := os.ReadDir(sessionsDir) + if err != nil { + return nil, err + } + type item struct { + key string + updated int64 + } + items := make([]item, 0) + for _, e := range entries { + if e.IsDir() || !strings.HasSuffix(e.Name(), ".meta") { + continue + } + metaPath := filepath.Join(sessionsDir, e.Name()) + data, err := os.ReadFile(metaPath) + if err != nil { + continue + } + var meta struct { + Kind string `json:"kind"` + Updated string `json:"updated"` + } + if err := json.Unmarshal(data, &meta); err != nil { + continue + } + if strings.ToLower(strings.TrimSpace(meta.Kind)) != "subagent" { + continue + } + t, err := time.Parse(time.RFC3339Nano, meta.Updated) + if err != nil { + t, _ = time.Parse(time.RFC3339, meta.Updated) + } + items = append(items, item{key: strings.TrimSuffix(e.Name(), ".meta"), updated: t.UnixMilli()}) + } + sort.Slice(items, func(i, j int) bool { return items[i].updated > items[j].updated }) + if limit > 0 && len(items) > limit { + items = items[:limit] + } + out := make([]string, 0, len(items)) + for _, it := range items { + out = append(out, it.key) + } + return out, nil +} diff --git a/pkg/tools/sessions_tool.go b/pkg/tools/sessions_tool.go index 74b093a..6187c27 100644 --- a/pkg/tools/sessions_tool.go +++ b/pkg/tools/sessions_tool.go @@ -36,11 +36,12 @@ 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}, - "kinds": map[string]interface{}{"type": "array", "items": map[string]interface{}{"type": "string"}, "description": "optional session kinds filter for list"}, - "include_tools": map[string]interface{}{"type": "boolean", "description": "include tool role messages in history", "default": false}, + "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}, + "active_minutes": map[string]interface{}{"type": "integer", "description": "only sessions updated in recent N minutes (list action)"}, + "kinds": map[string]interface{}{"type": "array", "items": map[string]interface{}{"type": "string"}, "description": "optional session kinds filter for list"}, + "include_tools": map[string]interface{}{"type": "boolean", "description": "include tool role messages in history", "default": false}, }, "required": []string{"action"}, } @@ -58,6 +59,10 @@ func (t *SessionsTool) Execute(ctx context.Context, args map[string]interface{}) if v, ok := args["include_tools"].(bool); ok { includeTools = v } + activeMinutes := 0 + if v, ok := args["active_minutes"].(float64); ok && int(v) > 0 { + activeMinutes = int(v) + } kindFilter := map[string]struct{}{} if rawKinds, ok := args["kinds"].([]interface{}); ok { for _, it := range rawKinds { @@ -89,6 +94,16 @@ func (t *SessionsTool) Execute(ctx context.Context, args map[string]interface{}) } items = filtered } + if activeMinutes > 0 { + cutoff := time.Now().Add(-time.Duration(activeMinutes) * time.Minute) + filtered := make([]SessionInfo, 0, len(items)) + for _, s := range items { + if s.UpdatedAt.After(cutoff) { + filtered = append(filtered, s) + } + } + items = filtered + } if len(items) == 0 { return "No sessions (after filters).", nil } diff --git a/pkg/tools/subagents_tool.go b/pkg/tools/subagents_tool.go index 77a7a82..7f9764a 100644 --- a/pkg/tools/subagents_tool.go +++ b/pkg/tools/subagents_tool.go @@ -3,6 +3,8 @@ package tools import ( "context" "fmt" + "sort" + "strconv" "strings" ) @@ -52,40 +54,48 @@ func (t *SubagentsTool) Execute(ctx context.Context, args map[string]interface{} } var sb strings.Builder sb.WriteString("Subagents:\n") - for _, task := range tasks { - sb.WriteString(fmt.Sprintf("- %s [%s] label=%s\n", task.ID, task.Status, task.Label)) + sort.Slice(tasks, func(i, j int) bool { return tasks[i].Created > tasks[j].Created }) + for i, task := range tasks { + sb.WriteString(fmt.Sprintf("- #%d %s [%s] label=%s\n", i+1, task.ID, task.Status, task.Label)) } return strings.TrimSpace(sb.String()), nil case "info": - if id == "" { - return "id is required for info", nil + resolvedID, err := t.resolveTaskID(id) + if err != nil { + return err.Error(), nil } - task, ok := t.manager.GetTask(id) + task, ok := t.manager.GetTask(resolvedID) if !ok { return "subagent not found", nil } return fmt.Sprintf("ID: %s\nStatus: %s\nLabel: %s\nCreated: %d\nUpdated: %d\nSteering Count: %d\nTask: %s\nResult:\n%s", task.ID, task.Status, task.Label, task.Created, task.Updated, len(task.Steering), task.Task, task.Result), nil case "kill": - if id == "" { - return "id is required for kill", nil + resolvedID, err := t.resolveTaskID(id) + if err != nil { + return err.Error(), nil } - if !t.manager.KillTask(id) { + if !t.manager.KillTask(resolvedID) { return "subagent not found", nil } return "subagent kill requested", nil case "steer", "send": - if id == "" || message == "" { - return "id and message are required for steer/send", nil + if message == "" { + return "message is required for steer/send", nil } - if !t.manager.SteerTask(id, message) { + resolvedID, err := t.resolveTaskID(id) + if err != nil { + return err.Error(), nil + } + if !t.manager.SteerTask(resolvedID, message) { return "subagent not found", nil } return "steering message accepted", nil case "log": - if id == "" { - return "id is required for log", nil + resolvedID, err := t.resolveTaskID(id) + if err != nil { + return err.Error(), nil } - task, ok := t.manager.GetTask(id) + task, ok := t.manager.GetTask(resolvedID) if !ok { return "subagent not found", nil } @@ -110,3 +120,26 @@ func (t *SubagentsTool) Execute(ctx context.Context, args map[string]interface{} return "unsupported action", nil } } + +func (t *SubagentsTool) resolveTaskID(idOrIndex string) (string, error) { + idOrIndex = strings.TrimSpace(idOrIndex) + if idOrIndex == "" { + return "", fmt.Errorf("id is required") + } + if strings.HasPrefix(idOrIndex, "#") { + n, err := strconv.Atoi(strings.TrimPrefix(idOrIndex, "#")) + if err != nil || n <= 0 { + return "", fmt.Errorf("invalid subagent index") + } + tasks := t.manager.ListTasks() + if len(tasks) == 0 { + return "", fmt.Errorf("no subagents") + } + sort.Slice(tasks, func(i, j int) bool { return tasks[i].Created > tasks[j].Created }) + if n > len(tasks) { + return "", fmt.Errorf("subagent index out of range") + } + return tasks[n-1].ID, nil + } + return idOrIndex, nil +}