feat: add pagination + body truncation to list_by_state

Prevents 181K+ responses when channels have many messages with long
bodies. New params: limit (default 20, max 100), offset (default 0),
max_body_length (default 500 chars when include_messages=true).

Response now includes total count alongside paginated results.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Algis Dumbris
2026-03-22 20:09:38 +02:00
co-authored by Claude Opus 4.6
parent faab0f7f17
commit 91a1f2adcb
2 changed files with 50 additions and 13 deletions
+10 -7
View File
@@ -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})`,
},
},
},
+40 -6
View File
@@ -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,