From ff9fdf774fb97b85e40f15ec8d112f21ca3d9356 Mon Sep 17 00:00:00 2001 From: Anton Bogdanovich <27antonb@gmail.com> Date: Sun, 3 May 2026 21:10:01 -0700 Subject: [PATCH 1/3] scope seahorse retrieval tools to current session --- pkg/seahorse/short_retrieval.go | 40 ++++++++++++++++++++++ pkg/seahorse/tool_expand.go | 7 +++- pkg/seahorse/tool_expand_test.go | 38 +++++++++++++++++++++ pkg/seahorse/tool_grep.go | 7 ++++ pkg/seahorse/tool_grep_test.go | 58 ++++++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 1 deletion(-) diff --git a/pkg/seahorse/short_retrieval.go b/pkg/seahorse/short_retrieval.go index 3e94eec14..8f5b7ecb7 100644 --- a/pkg/seahorse/short_retrieval.go +++ b/pkg/seahorse/short_retrieval.go @@ -44,6 +44,7 @@ type GrepInput struct { Pattern string `json:"pattern"` Scope string `json:"scope,omitempty"` // "both" (default), "summary", or "message" Role string `json:"role,omitempty"` // "user", "assistant", or "" (all) + ConversationID int64 `json:"conversationId,omitempty"` AllConversations bool `json:"allConversations,omitempty"` Since *time.Time `json:"since,omitempty"` Before *time.Time `json:"before,omitempty"` @@ -120,6 +121,7 @@ func (r *RetrievalEngine) Grep(ctx context.Context, input GrepInput) (*GrepResul Pattern: input.Pattern, Mode: mode, Role: input.Role, + ConversationID: input.ConversationID, AllConversations: input.AllConversations, Since: since, Before: input.Before, @@ -193,8 +195,43 @@ func (r *RetrievalEngine) Grep(ctx context.Context, input GrepInput) (*GrepResul return result, nil } +// ConversationIDForSession returns the storage conversation ID for a session key. +func (r *RetrievalEngine) ConversationIDForSession(ctx context.Context, sessionKey string) (int64, error) { + if strings.TrimSpace(sessionKey) == "" { + return 0, nil + } + conv, err := r.store.GetConversationBySessionKey(ctx, sessionKey) + if err != nil { + return 0, err + } + if conv == nil { + return 0, nil + } + return conv.ConversationID, nil +} + // ExpandMessages retrieves full message content by IDs. func (r *RetrievalEngine) ExpandMessages(ctx context.Context, messageIDs []int64) (*ExpandMessagesResult, error) { + return r.expandMessages(ctx, messageIDs, 0, false) +} + +// ExpandMessagesScoped retrieves full message content by IDs, restricted to a conversation +// unless allConversations is true. +func (r *RetrievalEngine) ExpandMessagesScoped( + ctx context.Context, + messageIDs []int64, + conversationID int64, + allConversations bool, +) (*ExpandMessagesResult, error) { + return r.expandMessages(ctx, messageIDs, conversationID, allConversations) +} + +func (r *RetrievalEngine) expandMessages( + ctx context.Context, + messageIDs []int64, + conversationID int64, + allConversations bool, +) (*ExpandMessagesResult, error) { result := &ExpandMessagesResult{ Messages: make([]Message, 0, len(messageIDs)), } @@ -204,6 +241,9 @@ func (r *RetrievalEngine) ExpandMessages(ctx context.Context, messageIDs []int64 if err != nil { continue } + if !allConversations && conversationID > 0 && msg.ConversationID != conversationID { + continue + } result.Messages = append(result.Messages, *msg) result.TokenCount += msg.TokenCount } diff --git a/pkg/seahorse/tool_expand.go b/pkg/seahorse/tool_expand.go index 749c9cd6c..b37691071 100644 --- a/pkg/seahorse/tool_expand.go +++ b/pkg/seahorse/tool_expand.go @@ -82,7 +82,12 @@ func (t *ExpandTool) Execute(ctx context.Context, args map[string]any) *tools.To } } - result, err := t.engine.ExpandMessages(ctx, messageIDs) + conversationID, err := t.engine.ConversationIDForSession(ctx, tools.ToolSessionKey(ctx)) + if err != nil { + return tools.ErrorResult("Expand failed: resolve current conversation: " + err.Error()) + } + + result, err := t.engine.ExpandMessagesScoped(ctx, messageIDs, conversationID, false) if err != nil { return tools.ErrorResult("Expand failed: " + err.Error()) } diff --git a/pkg/seahorse/tool_expand_test.go b/pkg/seahorse/tool_expand_test.go index fc726a7a0..bd303df69 100644 --- a/pkg/seahorse/tool_expand_test.go +++ b/pkg/seahorse/tool_expand_test.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "testing" + + "github.com/sipeed/picoclaw/pkg/tools" ) func TestExpandToolByMessageIDs(t *testing.T) { @@ -134,3 +136,39 @@ func TestExpandToolWithParts(t *testing.T) { t.Error("missing tool_result part") } } + +func TestExpandToolScopesToCurrentSession(t *testing.T) { + s := openTestStore(t) + ctx := context.Background() + current, _ := s.GetOrCreateConversation(ctx, "session:current") + other, _ := s.GetOrCreateConversation(ctx, "session:other") + currentMsg, _ := s.AddMessage(ctx, current.ConversationID, "user", "current message", 5) + otherMsg, _ := s.AddMessage(ctx, other.ConversationID, "user", "other message", 5) + + tool := NewExpandTool(&RetrievalEngine{store: s}) + toolCtx := tools.WithToolSessionContext(ctx, "agent", "session:current", nil) + result := tool.Execute(toolCtx, map[string]any{ + "message_ids": []any{ + float64(currentMsg.ID), + float64(otherMsg.ID), + }, + }) + if result.IsError { + t.Fatalf("Execute returned error: %s", result.ContentForLLM()) + } + + var output struct { + Messages []struct { + Content string `json:"content"` + } `json:"messages"` + } + if err := json.Unmarshal([]byte(result.ContentForLLM()), &output); err != nil { + t.Fatalf("unmarshal result: %v", err) + } + if len(output.Messages) != 1 { + t.Fatalf("messages = %d, want 1: %#v", len(output.Messages), output.Messages) + } + if output.Messages[0].Content != "current message" { + t.Fatalf("content = %q, want current message", output.Messages[0].Content) + } +} diff --git a/pkg/seahorse/tool_grep.go b/pkg/seahorse/tool_grep.go index 9671d2a7f..88fff137a 100644 --- a/pkg/seahorse/tool_grep.go +++ b/pkg/seahorse/tool_grep.go @@ -131,6 +131,13 @@ func (t *GrepTool) Execute(ctx context.Context, args map[string]any) *tools.Tool if allConv, ok := args["all_conversations"].(bool); ok { input.AllConversations = allConv } + if !input.AllConversations { + conversationID, err := t.engine.ConversationIDForSession(ctx, tools.ToolSessionKey(ctx)) + if err != nil { + return tools.ErrorResult("Grep failed: resolve current conversation: " + err.Error()) + } + input.ConversationID = conversationID + } if limit, ok := args["limit"].(float64); ok { input.Limit = int(limit) } diff --git a/pkg/seahorse/tool_grep_test.go b/pkg/seahorse/tool_grep_test.go index 050d9deeb..999a2f3d5 100644 --- a/pkg/seahorse/tool_grep_test.go +++ b/pkg/seahorse/tool_grep_test.go @@ -2,7 +2,10 @@ package seahorse import ( "context" + "encoding/json" "testing" + + "github.com/sipeed/picoclaw/pkg/tools" ) func TestGrepSearchSummaries(t *testing.T) { @@ -70,3 +73,58 @@ func TestGrepToolSupportsAllConversations(t *testing.T) { t.Error("Parameters missing 'all_conversations' field") } } + +func TestGrepToolScopesToCurrentSessionByDefault(t *testing.T) { + s := openTestStore(t) + ctx := context.Background() + current, _ := s.GetOrCreateConversation(ctx, "session:current") + other, _ := s.GetOrCreateConversation(ctx, "session:other") + s.AddMessage(ctx, current.ConversationID, "user", "shared needle from current topic", 5) + s.AddMessage(ctx, other.ConversationID, "user", "shared needle from other topic", 5) + + tool := NewGrepTool(&RetrievalEngine{store: s}) + toolCtx := tools.WithToolSessionContext(ctx, "agent", "session:current", nil) + result := tool.Execute(toolCtx, map[string]any{"pattern": "needle"}) + if result.IsError { + t.Fatalf("Execute returned error: %s", result.ContentForLLM()) + } + + var output struct { + Messages []GrepMessageResult `json:"messages"` + } + if err := json.Unmarshal([]byte(result.ContentForLLM()), &output); err != nil { + t.Fatalf("unmarshal result: %v", err) + } + if len(output.Messages) != 1 { + t.Fatalf("messages = %d, want 1: %#v", len(output.Messages), output.Messages) + } + if output.Messages[0].ConversationID != current.ConversationID { + t.Fatalf("conversation id = %d, want %d", output.Messages[0].ConversationID, current.ConversationID) + } +} + +func TestGrepToolCanSearchAllConversations(t *testing.T) { + s := openTestStore(t) + ctx := context.Background() + current, _ := s.GetOrCreateConversation(ctx, "session:current") + other, _ := s.GetOrCreateConversation(ctx, "session:other") + s.AddMessage(ctx, current.ConversationID, "user", "shared needle from current topic", 5) + s.AddMessage(ctx, other.ConversationID, "user", "shared needle from other topic", 5) + + tool := NewGrepTool(&RetrievalEngine{store: s}) + toolCtx := tools.WithToolSessionContext(ctx, "agent", "session:current", nil) + result := tool.Execute(toolCtx, map[string]any{"pattern": "needle", "all_conversations": true}) + if result.IsError { + t.Fatalf("Execute returned error: %s", result.ContentForLLM()) + } + + var output struct { + Messages []GrepMessageResult `json:"messages"` + } + if err := json.Unmarshal([]byte(result.ContentForLLM()), &output); err != nil { + t.Fatalf("unmarshal result: %v", err) + } + if len(output.Messages) != 2 { + t.Fatalf("messages = %d, want 2: %#v", len(output.Messages), output.Messages) + } +} From 04b08969b47d8edd1033fe8acb4fe7436ac7eaeb Mon Sep 17 00:00:00 2001 From: Anton Bogdanovich <27antonb@gmail.com> Date: Tue, 5 May 2026 13:13:06 -0700 Subject: [PATCH 2/3] Address Seahorse scoped retrieval review --- pkg/seahorse/short_retrieval.go | 21 +++++---- pkg/seahorse/tool_expand.go | 34 +++++++++++---- pkg/seahorse/tool_expand_test.go | 75 +++++++++++++++++++++++++++++++- pkg/seahorse/tool_grep.go | 15 ++++++- pkg/seahorse/tool_grep_test.go | 53 ++++++++++++++++++++++ 5 files changed, 177 insertions(+), 21 deletions(-) diff --git a/pkg/seahorse/short_retrieval.go b/pkg/seahorse/short_retrieval.go index 8f5b7ecb7..c431efa0b 100644 --- a/pkg/seahorse/short_retrieval.go +++ b/pkg/seahorse/short_retrieval.go @@ -85,8 +85,9 @@ type GrepMessageResult struct { // ExpandMessagesResult contains expanded messages. type ExpandMessagesResult struct { - Messages []Message `json:"messages"` - TokenCount int `json:"tokenCount"` + Messages []Message `json:"messages"` + RejectedMessageIDs []int64 `json:"rejectedMessageIds,omitempty"` + TokenCount int `json:"tokenCount"` } // Grep searches summaries and messages for matching content. @@ -196,23 +197,23 @@ func (r *RetrievalEngine) Grep(ctx context.Context, input GrepInput) (*GrepResul } // ConversationIDForSession returns the storage conversation ID for a session key. -func (r *RetrievalEngine) ConversationIDForSession(ctx context.Context, sessionKey string) (int64, error) { +func (r *RetrievalEngine) ConversationIDForSession(ctx context.Context, sessionKey string) (int64, bool, error) { if strings.TrimSpace(sessionKey) == "" { - return 0, nil + return 0, false, nil } conv, err := r.store.GetConversationBySessionKey(ctx, sessionKey) if err != nil { - return 0, err + return 0, false, err } if conv == nil { - return 0, nil + return 0, false, nil } - return conv.ConversationID, nil + return conv.ConversationID, true, nil } // ExpandMessages retrieves full message content by IDs. func (r *RetrievalEngine) ExpandMessages(ctx context.Context, messageIDs []int64) (*ExpandMessagesResult, error) { - return r.expandMessages(ctx, messageIDs, 0, false) + return r.expandMessages(ctx, messageIDs, 0, true) } // ExpandMessagesScoped retrieves full message content by IDs, restricted to a conversation @@ -239,9 +240,11 @@ func (r *RetrievalEngine) expandMessages( for _, msgID := range messageIDs { msg, err := r.store.GetMessageByID(ctx, msgID) if err != nil { + result.RejectedMessageIDs = append(result.RejectedMessageIDs, msgID) continue } - if !allConversations && conversationID > 0 && msg.ConversationID != conversationID { + if !allConversations && (conversationID <= 0 || msg.ConversationID != conversationID) { + result.RejectedMessageIDs = append(result.RejectedMessageIDs, msgID) continue } result.Messages = append(result.Messages, *msg) diff --git a/pkg/seahorse/tool_expand.go b/pkg/seahorse/tool_expand.go index b37691071..9589b3237 100644 --- a/pkg/seahorse/tool_expand.go +++ b/pkg/seahorse/tool_expand.go @@ -28,6 +28,7 @@ Use when short_grep returns messages and you need complete content (not just sni Parameters: - message_ids (required): Array of message ID strings (from short_grep results) +- all_conversations: Expand IDs from any conversation (default: current conversation only) Returns message with: - content: Full text content @@ -40,9 +41,12 @@ Returns message with: Notes: - tool_result content is not returned (can be large). Re-run the tool if you need the result. - Media files are stored on disk at mediaUri path, use bash to access. +- By default, IDs outside the current conversation are rejected and reported in rejectedMessageIds. +- If short_grep used all_conversations: true, pass all_conversations: true to expand those IDs. Example: - {"message_ids": ["10", "25"]}` + {"message_ids": ["10", "25"]} + {"message_ids": ["10", "25"], "all_conversations": true}` } func (t *ExpandTool) Parameters() map[string]any { @@ -54,6 +58,10 @@ func (t *ExpandTool) Parameters() map[string]any { "items": map[string]any{"type": "string"}, "description": "Message IDs to expand (from short_grep results, e.g., [\"10\", \"25\"])", }, + "all_conversations": map[string]any{ + "type": "boolean", + "description": "Expand IDs across all conversations (default: current conversation only)", + }, }, "required": []string{"message_ids"}, } @@ -82,12 +90,21 @@ func (t *ExpandTool) Execute(ctx context.Context, args map[string]any) *tools.To } } - conversationID, err := t.engine.ConversationIDForSession(ctx, tools.ToolSessionKey(ctx)) - if err != nil { - return tools.ErrorResult("Expand failed: resolve current conversation: " + err.Error()) + allConversations, _ := args["all_conversations"].(bool) + var conversationID int64 + if !allConversations { + var found bool + var err error + conversationID, found, err = t.engine.ConversationIDForSession(ctx, tools.ToolSessionKey(ctx)) + if err != nil { + return tools.ErrorResult("Expand failed: resolve current conversation: " + err.Error()) + } + if !found { + return tools.ErrorResult("Expand failed: no current conversation found for this session. Use all_conversations: true to expand across conversations.") + } } - result, err := t.engine.ExpandMessagesScoped(ctx, messageIDs, conversationID, false) + result, err := t.engine.ExpandMessagesScoped(ctx, messageIDs, conversationID, allConversations) if err != nil { return tools.ErrorResult("Expand failed: " + err.Error()) } @@ -125,9 +142,10 @@ func (t *ExpandTool) Execute(ctx context.Context, args map[string]any) *tools.To } output := map[string]any{ - "success": true, - "tokenCount": result.TokenCount, - "messages": messages, + "success": true, + "tokenCount": result.TokenCount, + "messages": messages, + "rejectedMessageIds": result.RejectedMessageIDs, } data, _ := json.Marshal(output) return tools.NewToolResult(string(data)) diff --git a/pkg/seahorse/tool_expand_test.go b/pkg/seahorse/tool_expand_test.go index bd303df69..9e18b5f10 100644 --- a/pkg/seahorse/tool_expand_test.go +++ b/pkg/seahorse/tool_expand_test.go @@ -21,7 +21,8 @@ func TestExpandToolByMessageIDs(t *testing.T) { tool := NewExpandTool(re) result := tool.Execute(ctx, map[string]any{ - "message_ids": []any{fmt.Sprintf("%d", msg1.ID), fmt.Sprintf("%d", msg2.ID)}, + "message_ids": []any{fmt.Sprintf("%d", msg1.ID), fmt.Sprintf("%d", msg2.ID)}, + "all_conversations": true, }) if result.IsError { @@ -78,7 +79,8 @@ func TestExpandToolWithParts(t *testing.T) { tool := NewExpandTool(re) result := tool.Execute(ctx, map[string]any{ - "message_ids": []any{fmt.Sprintf("%d", msg.ID)}, + "message_ids": []any{fmt.Sprintf("%d", msg.ID)}, + "all_conversations": true, }) if result.IsError { @@ -161,6 +163,7 @@ func TestExpandToolScopesToCurrentSession(t *testing.T) { Messages []struct { Content string `json:"content"` } `json:"messages"` + RejectedMessageIDs []int64 `json:"rejectedMessageIds"` } if err := json.Unmarshal([]byte(result.ContentForLLM()), &output); err != nil { t.Fatalf("unmarshal result: %v", err) @@ -171,4 +174,72 @@ func TestExpandToolScopesToCurrentSession(t *testing.T) { if output.Messages[0].Content != "current message" { t.Fatalf("content = %q, want current message", output.Messages[0].Content) } + if len(output.RejectedMessageIDs) != 1 || output.RejectedMessageIDs[0] != otherMsg.ID { + t.Fatalf("rejectedMessageIds = %#v, want [%d]", output.RejectedMessageIDs, otherMsg.ID) + } +} + +func TestExpandToolCanExpandAllConversations(t *testing.T) { + s := openTestStore(t) + ctx := context.Background() + current, _ := s.GetOrCreateConversation(ctx, "session:current") + other, _ := s.GetOrCreateConversation(ctx, "session:other") + currentMsg, _ := s.AddMessage(ctx, current.ConversationID, "user", "current message", 5) + otherMsg, _ := s.AddMessage(ctx, other.ConversationID, "user", "other message", 5) + + tool := NewExpandTool(&RetrievalEngine{store: s}) + toolCtx := tools.WithToolSessionContext(ctx, "agent", "session:current", nil) + result := tool.Execute(toolCtx, map[string]any{ + "message_ids": []any{ + float64(currentMsg.ID), + float64(otherMsg.ID), + }, + "all_conversations": true, + }) + if result.IsError { + t.Fatalf("Execute returned error: %s", result.ContentForLLM()) + } + + var output struct { + Messages []struct { + Content string `json:"content"` + } `json:"messages"` + RejectedMessageIDs []int64 `json:"rejectedMessageIds"` + } + if err := json.Unmarshal([]byte(result.ContentForLLM()), &output); err != nil { + t.Fatalf("unmarshal result: %v", err) + } + if len(output.Messages) != 2 { + t.Fatalf("messages = %d, want 2: %#v", len(output.Messages), output.Messages) + } + if len(output.RejectedMessageIDs) != 0 { + t.Fatalf("rejectedMessageIds = %#v, want none", output.RejectedMessageIDs) + } +} + +func TestExpandToolUnknownSessionErrors(t *testing.T) { + s := openTestStore(t) + ctx := context.Background() + conv, _ := s.GetOrCreateConversation(ctx, "session:current") + msg, _ := s.AddMessage(ctx, conv.ConversationID, "user", "current message", 5) + + tool := NewExpandTool(&RetrievalEngine{store: s}) + toolCtx := tools.WithToolSessionContext(ctx, "agent", "session:missing", nil) + result := tool.Execute(toolCtx, map[string]any{ + "message_ids": []any{float64(msg.ID)}, + }) + if !result.IsError { + t.Fatal("expected error for unknown current session") + } +} + +func TestExpandToolSupportsAllConversationsParameter(t *testing.T) { + s := openTestStore(t) + tool := NewExpandTool(&RetrievalEngine{store: s}) + params := tool.Parameters() + props := params["properties"].(map[string]any) + + if _, ok := props["all_conversations"]; !ok { + t.Error("Parameters missing 'all_conversations' field") + } } diff --git a/pkg/seahorse/tool_grep.go b/pkg/seahorse/tool_grep.go index 88fff137a..31cfdc39f 100644 --- a/pkg/seahorse/tool_grep.go +++ b/pkg/seahorse/tool_grep.go @@ -132,10 +132,18 @@ func (t *GrepTool) Execute(ctx context.Context, args map[string]any) *tools.Tool input.AllConversations = allConv } if !input.AllConversations { - conversationID, err := t.engine.ConversationIDForSession(ctx, tools.ToolSessionKey(ctx)) + conversationID, found, err := t.engine.ConversationIDForSession(ctx, tools.ToolSessionKey(ctx)) if err != nil { return tools.ErrorResult("Grep failed: resolve current conversation: " + err.Error()) } + if !found { + return grepJSONResult(&GrepResult{ + Success: true, + Summaries: make([]GrepSummaryResult, 0), + Messages: make([]GrepMessageResult, 0), + Hint: "No current conversation found for this session. Use all_conversations: true to search across conversations.", + }) + } input.ConversationID = conversationID } if limit, ok := args["limit"].(float64); ok { @@ -162,7 +170,10 @@ func (t *GrepTool) Execute(ctx context.Context, args map[string]any) *tools.Tool return tools.ErrorResult("Grep failed: " + err.Error()) } - // Build response + return grepJSONResult(result) +} + +func grepJSONResult(result *GrepResult) *tools.ToolResult { output := map[string]any{ "success": result.Success, "summaries": result.Summaries, diff --git a/pkg/seahorse/tool_grep_test.go b/pkg/seahorse/tool_grep_test.go index 999a2f3d5..ecb5f61f8 100644 --- a/pkg/seahorse/tool_grep_test.go +++ b/pkg/seahorse/tool_grep_test.go @@ -128,3 +128,56 @@ func TestGrepToolCanSearchAllConversations(t *testing.T) { t.Fatalf("messages = %d, want 2: %#v", len(output.Messages), output.Messages) } } + +func TestGrepToolUnknownSessionDoesNotSearchAllConversations(t *testing.T) { + s := openTestStore(t) + ctx := context.Background() + current, _ := s.GetOrCreateConversation(ctx, "session:current") + other, _ := s.GetOrCreateConversation(ctx, "session:other") + s.AddMessage(ctx, current.ConversationID, "user", "shared needle from current topic", 5) + s.AddMessage(ctx, other.ConversationID, "user", "shared needle from other topic", 5) + + tool := NewGrepTool(&RetrievalEngine{store: s}) + toolCtx := tools.WithToolSessionContext(ctx, "agent", "session:missing", nil) + result := tool.Execute(toolCtx, map[string]any{"pattern": "needle"}) + if result.IsError { + t.Fatalf("Execute returned error: %s", result.ContentForLLM()) + } + + var output struct { + Messages []GrepMessageResult `json:"messages"` + Hint string `json:"hint"` + } + if err := json.Unmarshal([]byte(result.ContentForLLM()), &output); err != nil { + t.Fatalf("unmarshal result: %v", err) + } + if len(output.Messages) != 0 { + t.Fatalf("messages = %d, want 0: %#v", len(output.Messages), output.Messages) + } + if output.Hint == "" { + t.Fatal("expected hint for missing current conversation") + } +} + +func TestGrepToolEmptySessionDoesNotSearchAllConversations(t *testing.T) { + s := openTestStore(t) + ctx := context.Background() + conv, _ := s.GetOrCreateConversation(ctx, "session:current") + s.AddMessage(ctx, conv.ConversationID, "user", "shared needle from current topic", 5) + + tool := NewGrepTool(&RetrievalEngine{store: s}) + result := tool.Execute(ctx, map[string]any{"pattern": "needle"}) + if result.IsError { + t.Fatalf("Execute returned error: %s", result.ContentForLLM()) + } + + var output struct { + Messages []GrepMessageResult `json:"messages"` + } + if err := json.Unmarshal([]byte(result.ContentForLLM()), &output); err != nil { + t.Fatalf("unmarshal result: %v", err) + } + if len(output.Messages) != 0 { + t.Fatalf("messages = %d, want 0: %#v", len(output.Messages), output.Messages) + } +} From 95393d9b6837707bfa9ad1b5f1bdeac9066c3bdf Mon Sep 17 00:00:00 2001 From: Anton Bogdanovich <27antonb@gmail.com> Date: Tue, 5 May 2026 17:18:08 -0700 Subject: [PATCH 3/3] fix(seahorse): format scoped expand error --- pkg/seahorse/tool_expand.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/seahorse/tool_expand.go b/pkg/seahorse/tool_expand.go index 9589b3237..fbc4305f1 100644 --- a/pkg/seahorse/tool_expand.go +++ b/pkg/seahorse/tool_expand.go @@ -100,7 +100,9 @@ func (t *ExpandTool) Execute(ctx context.Context, args map[string]any) *tools.To return tools.ErrorResult("Expand failed: resolve current conversation: " + err.Error()) } if !found { - return tools.ErrorResult("Expand failed: no current conversation found for this session. Use all_conversations: true to expand across conversations.") + return tools.ErrorResult( + "Expand failed: no current conversation found for this session. Use all_conversations: true to expand across conversations.", + ) } }