diff --git a/pkg/tools/sessions_tool.go b/pkg/tools/sessions_tool.go index d7e8246..4924686 100644 --- a/pkg/tools/sessions_tool.go +++ b/pkg/tools/sessions_tool.go @@ -41,6 +41,7 @@ func (t *SessionsTool) Parameters() map[string]interface{} { "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"}, + "query": map[string]interface{}{"type": "string", "description": "optional text query for list or history"}, "include_tools": map[string]interface{}{"type": "boolean", "description": "include tool role messages in history", "default": false}, "around": map[string]interface{}{"type": "integer", "description": "1-indexed message index center for history window"}, "before": map[string]interface{}{"type": "integer", "description": "1-indexed message index upper bound (exclusive)"}, @@ -78,6 +79,8 @@ func (t *SessionsTool) Execute(ctx context.Context, args map[string]interface{}) if v, ok := args["active_minutes"].(float64); ok && int(v) > 0 { activeMinutes = int(v) } + query, _ := args["query"].(string) + query = strings.ToLower(strings.TrimSpace(query)) kindFilter := map[string]struct{}{} if rawKinds, ok := args["kinds"].([]interface{}); ok { for _, it := range rawKinds { @@ -119,6 +122,16 @@ func (t *SessionsTool) Execute(ctx context.Context, args map[string]interface{}) } items = filtered } + if query != "" { + filtered := make([]SessionInfo, 0, len(items)) + for _, s := range items { + blob := strings.ToLower(strings.TrimSpace(s.Key + "\n" + s.Kind + "\n" + s.Summary)) + if strings.Contains(blob, query) { + filtered = append(filtered, s) + } + } + items = filtered + } if len(items) == 0 { return "No sessions (after filters).", nil } @@ -202,6 +215,16 @@ func (t *SessionsTool) Execute(ctx context.Context, args map[string]interface{}) } h = filtered } + if query != "" { + filtered := make([]providers.Message, 0, len(h)) + for _, m := range h { + blob := strings.ToLower(strings.TrimSpace(m.Role + "\n" + m.Content)) + if strings.Contains(blob, query) { + filtered = append(filtered, m) + } + } + h = filtered + } if len(h) == 0 { return "No history (after filters).", nil } diff --git a/pkg/tools/sessions_tool_test.go b/pkg/tools/sessions_tool_test.go new file mode 100644 index 0000000..b43f581 --- /dev/null +++ b/pkg/tools/sessions_tool_test.go @@ -0,0 +1,52 @@ +package tools + +import ( + "context" + "strings" + "testing" + "time" + + "clawgo/pkg/providers" +) + +func TestSessionsToolListWithKindsAndQuery(t *testing.T) { + tool := NewSessionsTool(func(limit int) []SessionInfo { + return []SessionInfo{ + {Key: "telegram:1", Kind: "main", Summary: "project alpha", UpdatedAt: time.Now()}, + {Key: "cron:1", Kind: "cron", Summary: "nightly sync", UpdatedAt: time.Now()}, + } + }, nil) + + out, err := tool.Execute(context.Background(), map[string]interface{}{ + "action": "list", + "kinds": []interface{}{"main"}, + "query": "alpha", + }) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(out, "telegram:1") || strings.Contains(out, "cron:1") { + t.Fatalf("unexpected output: %s", out) + } +} + +func TestSessionsToolHistoryWithoutTools(t *testing.T) { + tool := NewSessionsTool(nil, func(key string, limit int) []providers.Message { + return []providers.Message{ + {Role: "user", Content: "hello"}, + {Role: "tool", Content: "tool output"}, + {Role: "assistant", Content: "ok"}, + } + }) + + out, err := tool.Execute(context.Background(), map[string]interface{}{ + "action": "history", + "key": "telegram:1", + }) + if err != nil { + t.Fatal(err) + } + if strings.Contains(strings.ToLower(out), "tool output") { + t.Fatalf("tool message should be filtered: %s", out) + } +} diff --git a/pkg/tools/subagents_tool.go b/pkg/tools/subagents_tool.go index 0a1bcf7..2ec0208 100644 --- a/pkg/tools/subagents_tool.go +++ b/pkg/tools/subagents_tool.go @@ -60,6 +60,19 @@ func (t *SubagentsTool) Execute(ctx context.Context, args map[string]interface{} } return strings.TrimSpace(sb.String()), nil case "info": + if strings.EqualFold(strings.TrimSpace(id), "all") { + tasks := t.manager.ListTasks() + if len(tasks) == 0 { + return "No subagents.", nil + } + sort.Slice(tasks, func(i, j int) bool { return tasks[i].Created > tasks[j].Created }) + var sb strings.Builder + sb.WriteString("Subagents Summary:\n") + for i, task := range tasks { + sb.WriteString(fmt.Sprintf("- #%d %s [%s] label=%s steering=%d\n", i+1, task.ID, task.Status, task.Label, len(task.Steering))) + } + return strings.TrimSpace(sb.String()), nil + } resolvedID, err := t.resolveTaskID(id) if err != nil { return err.Error(), nil diff --git a/pkg/tools/subagents_tool_test.go b/pkg/tools/subagents_tool_test.go new file mode 100644 index 0000000..b2e7b82 --- /dev/null +++ b/pkg/tools/subagents_tool_test.go @@ -0,0 +1,22 @@ +package tools + +import ( + "context" + "strings" + "testing" +) + +func TestSubagentsInfoAll(t *testing.T) { + m := NewSubagentManager(nil, ".", nil, nil) + m.tasks["subagent-1"] = &SubagentTask{ID: "subagent-1", Status: "completed", Label: "a", Created: 2} + m.tasks["subagent-2"] = &SubagentTask{ID: "subagent-2", Status: "running", Label: "b", Created: 3} + + tool := NewSubagentsTool(m) + out, err := tool.Execute(context.Background(), map[string]interface{}{"action": "info", "id": "all"}) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(out, "Subagents Summary") || !strings.Contains(out, "subagent-2") { + t.Fatalf("unexpected output: %s", out) + } +}