diff --git a/internal/actions/registry.go b/internal/actions/registry.go index a0fe7db..f17c8ef 100644 --- a/internal/actions/registry.go +++ b/internal/actions/registry.go @@ -512,21 +512,24 @@ func allActions() []Action { { Name: "list_by_state", Category: "reactions", - Description: "List messages in a channel filtered by workflow state. Use to find actionable work: list_by_state with state='approved' finds work ready to be claimed. States: proposed (new, awaiting approval), approved (ready for work), in_progress (claimed by agent), rejected, done, published.", + Description: "List messages in a channel filtered by workflow state. Paginated — use limit and offset for large channels. States: proposed (new), approved (ready for work), in_progress (claimed), rejected, done, published.", Params: []Param{ {Name: "channel", Type: "string", Description: "Channel name", Required: true}, {Name: "state", Type: "string", Description: "Workflow state to filter by: proposed, approved, in_progress, rejected, done, published", Required: true}, - {Name: "include_messages", Type: "boolean", Description: "If true, include full message bodies in the response (default: false)"}, + {Name: "limit", Type: "number", Description: "Max messages to return (default 20, max 100)"}, + {Name: "offset", Type: "number", Description: "Skip first N messages for pagination (default 0)"}, + {Name: "include_messages", Type: "boolean", Description: "Include message bodies (default false). Bodies truncated to max_body_length chars."}, + {Name: "max_body_length", Type: "number", Description: "Max chars per message body when include_messages=true (default 500). Use lower values for channels with long messages."}, }, - Returns: "JSON with message_ids array, count, and optionally messages array with id, from_agent, body, priority, created_at, reply_to", + Returns: "JSON with message_ids, count (this page), total (all matching), limit, offset, and optionally messages array", Examples: []Example{ { - Description: "List approved messages in a channel", - Code: `call("list_by_state", {"channel": "approvals", "state": "approved"})`, + Description: "List first 10 approved messages with content", + Code: `call("list_by_state", {"channel": "approvals", "state": "approved", "limit": 10, "include_messages": true})`, }, { - Description: "List approved messages with full content", - Code: `call("list_by_state", {"channel": "approvals", "state": "approved", "include_messages": true})`, + Description: "Paginate — get next page", + Code: `call("list_by_state", {"channel": "approvals", "state": "proposed", "limit": 10, "offset": 10})`, }, }, }, diff --git a/internal/mcp/bridge.go b/internal/mcp/bridge.go index 3063c4c..2683aa7 100644 --- a/internal/mcp/bridge.go +++ b/internal/mcp/bridge.go @@ -1080,25 +1080,59 @@ func (b *ServiceBridge) callListByState(ctx context.Context, args map[string]any messageIDs = []int64{} } + totalCount := len(messageIDs) + + // Apply limit and offset for pagination + limit := getInt(args, "limit", 20) + if limit <= 0 { + limit = 20 + } + if limit > 100 { + limit = 100 + } + offset := getInt(args, "offset", 0) + if offset < 0 { + offset = 0 + } + if offset > len(messageIDs) { + offset = len(messageIDs) + } + end := offset + limit + if end > len(messageIDs) { + end = len(messageIDs) + } + pageIDs := messageIDs[offset:end] + resp := map[string]any{ - "message_ids": messageIDs, - "count": len(messageIDs), + "message_ids": pageIDs, + "count": len(pageIDs), + "total": totalCount, "channel": channelName, "state": state, + "limit": limit, + "offset": offset, } includeMessages := getBool(args, "include_messages", false) - if includeMessages && len(messageIDs) > 0 && b.msgService != nil { + if includeMessages && len(pageIDs) > 0 && b.msgService != nil { + maxBodyLen := getInt(args, "max_body_length", 500) + if maxBodyLen <= 0 { + maxBodyLen = 500 + } var messages []map[string]any - for _, id := range messageIDs { + for _, id := range pageIDs { msg, err := b.msgService.GetMessageByID(ctx, id) if err != nil { - continue // skip messages that can't be fetched + continue + } + body := msg.Body + if len(body) > maxBodyLen { + body = body[:maxBodyLen] + "..." } messages = append(messages, map[string]any{ "id": msg.ID, "from_agent": msg.FromAgent, - "body": msg.Body, + "body": body, "priority": msg.Priority, "created_at": msg.CreatedAt, "reply_to": msg.ReplyTo,