Files
synapbus/internal/actions/registry.go
T
Algis DumbrisandClaude Opus 4.6 bd1bccc692 feat(015): SQL query interface for agents + split read/write pools
Split Connection Pools:
- writeDB: MaxOpenConns=1, serializes all writes (no SQLITE_BUSY)
- readDB: MaxOpenConns=8, query_only=ON, for all SELECTs
- QueryDB() helper returns read pool when available

SQL Query Interface:
- New 'query' action via execute MCP tool
- Read-only enforcement (PRAGMA query_only=ON + SQL validation)
- Curated views: my_messages, my_channels, channel_messages
- Per-agent access control via CTE injection
- Auto LIMIT 100, 5s timeout, SELECT-only validation
- Blocks: INSERT, UPDATE, DELETE, DROP, PRAGMA, etc.
- 12 new tests (access control, validation, limits, CTEs)

Migration 016: agent query views (v_agent_messages, etc.)
Action registry: 30 actions (was 29, added 'query')
All 29 test packages pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:17:23 +02:00

604 lines
29 KiB
Go

package actions
// Registry holds all action definitions and supports lookup.
type Registry struct {
actions map[string]Action
ordered []Action // maintains insertion order
}
// NewRegistry creates a registry pre-populated with all 28 agent-callable actions.
func NewRegistry() *Registry {
r := &Registry{
actions: make(map[string]Action, 28),
}
for _, a := range allActions() {
r.actions[a.Name] = a
r.ordered = append(r.ordered, a)
}
return r
}
// Get returns an action by name.
func (r *Registry) Get(name string) (Action, bool) {
a, ok := r.actions[name]
return a, ok
}
// List returns all registered actions.
func (r *Registry) List() []Action {
out := make([]Action, len(r.ordered))
copy(out, r.ordered)
return out
}
// ListByCategory returns actions in the given category.
func (r *Registry) ListByCategory(category string) []Action {
var out []Action
for _, a := range r.ordered {
if a.Category == category {
out = append(out, a)
}
}
return out
}
// allActions returns the canonical list of all 28 agent-callable actions.
func allActions() []Action {
return []Action{
// ── Messaging (7 actions) ──────────────────────────────────────
{
Name: "my_status",
Category: "messaging",
Description: "Get your complete status overview — identity, pending messages, channel mentions, system notifications, and statistics. Call this first when connecting to SynapBus.",
Params: []Param{},
Returns: "JSON with agent identity, direct_messages, mentions, system_notifications, channels, and stats",
Examples: []Example{
{
Description: "Check your full status on connect",
Code: `call("my_status", {})`,
},
},
},
{
Name: "send_message",
Category: "messaging",
Description: "Send a direct message to another agent. Use discover_agents first to find available agents you can communicate with. For channel messages, use send_channel_message instead.",
Params: []Param{
{Name: "to", Type: "string", Description: "Name of the recipient agent (required for DMs, omit for channel messages)"},
{Name: "body", Type: "string", Description: "Message body text", Required: true},
{Name: "subject", Type: "string", Description: "Conversation subject (optional)"},
{Name: "priority", Type: "number", Description: "Message priority (1-10, default 5)", Default: "5"},
{Name: "metadata", Type: "string", Description: "JSON metadata object (optional)"},
{Name: "channel_id", Type: "number", Description: "Channel ID for channel messages (optional)"},
{Name: "reply_to", Type: "number", Description: "ID of the message to reply to (optional, for threading)"},
},
Returns: "JSON with message_id, conversation_id, and status",
Examples: []Example{
{
Description: "Send a direct message to another agent",
Code: `call("send_message", {"to": "data-processor", "body": "Please analyze the Q4 sales data", "subject": "Q4 Analysis", "priority": 7})`,
},
},
},
{
Name: "read_inbox",
Category: "messaging",
Description: "Check your message inbox for pending messages. Call this first when connecting to see if other agents have sent you messages. Returns unread/pending direct messages addressed to you.",
Params: []Param{
{Name: "limit", Type: "number", Description: "Maximum number of messages to return (default 50)", Default: "50"},
{Name: "status_filter", Type: "string", Description: "Filter by message status: pending, processing, done, failed"},
{Name: "include_read", Type: "boolean", Description: "Include previously read messages (default false)", Default: "false"},
{Name: "min_priority", Type: "number", Description: "Minimum priority filter (1-10)"},
{Name: "from_agent", Type: "string", Description: "Filter by sender agent name"},
},
Returns: "JSON with messages array and count",
Examples: []Example{
{
Description: "Check for new messages",
Code: `call("read_inbox", {})`,
},
{
Description: "Read high-priority messages from a specific agent",
Code: `call("read_inbox", {"min_priority": 8, "from_agent": "coordinator"})`,
},
},
},
{
Name: "claim_messages",
Category: "messaging",
Description: "Atomically claim pending messages for processing",
Params: []Param{
{Name: "limit", Type: "number", Description: "Maximum number of messages to claim (default 10)", Default: "10"},
},
Returns: "JSON with claimed messages array and count",
Examples: []Example{
{
Description: "Claim up to 5 messages for processing",
Code: `call("claim_messages", {"limit": 5})`,
},
},
},
{
Name: "mark_done",
Category: "messaging",
Description: "Mark a claimed message as done or failed",
Params: []Param{
{Name: "message_id", Type: "number", Description: "ID of the message to mark", Required: true},
{Name: "status", Type: "string", Description: "New status: 'done' or 'failed' (default 'done')", Default: "done"},
{Name: "reason", Type: "string", Description: "Failure reason (only for status='failed')"},
},
Returns: "JSON with message_id and status",
Examples: []Example{
{
Description: "Mark a message as successfully processed",
Code: `call("mark_done", {"message_id": 42})`,
},
{
Description: "Mark a message as failed with reason",
Code: `call("mark_done", {"message_id": 42, "status": "failed", "reason": "invalid data format"})`,
},
},
},
{
Name: "search_messages",
Category: "messaging",
Description: "Search for messages across your inbox and channels you are a member of. Supports full-text and semantic search (if configured). Use with an empty query to browse recent messages, or provide a natural-language query to find relevant conversations.",
Params: []Param{
{Name: "query", Type: "string", Description: "Search query string — supports natural language for semantic search"},
{Name: "limit", Type: "number", Description: "Maximum results to return (default 10, max 100)", Default: "10"},
{Name: "min_priority", Type: "number", Description: "Minimum priority filter (1-10)"},
{Name: "from_agent", Type: "string", Description: "Filter by sender agent name"},
{Name: "status", Type: "string", Description: "Filter by message status"},
{Name: "search_mode", Type: "string", Description: "Search mode: 'auto' (default), 'semantic', or 'fulltext'", Default: "auto"},
{Name: "semantic", Type: "boolean", Description: "Force semantic search (shorthand for search_mode='semantic')"},
},
Returns: "JSON with results array, count, and search_mode used",
Examples: []Example{
{
Description: "Search for messages about deployment",
Code: `call("search_messages", {"query": "deployment status update", "limit": 5})`,
},
},
},
{
Name: "discover_agents",
Category: "messaging",
Description: "Discover other agents on the bus. Call this to find agents you can communicate with. Optionally filter by capability keywords, or omit the query to list all registered agents.",
Params: []Param{
{Name: "query", Type: "string", Description: "Capability keyword to search for"},
},
Returns: "JSON with agents array (name, display_name, type, capabilities, status) and count",
Examples: []Example{
{
Description: "List all available agents",
Code: `call("discover_agents", {})`,
},
{
Description: "Find agents with data analysis capabilities",
Code: `call("discover_agents", {"query": "data analysis"})`,
},
},
},
// ── Channels (9 actions) ──────────────────────────────────────
{
Name: "create_channel",
Category: "channels",
Description: "Create a new channel for group communication",
Params: []Param{
{Name: "name", Type: "string", Description: "Unique channel name (alphanumeric, hyphens, underscores, max 64 chars)", Required: true},
{Name: "description", Type: "string", Description: "Channel description"},
{Name: "topic", Type: "string", Description: "Current channel topic"},
{Name: "type", Type: "string", Description: "Channel type: 'standard', 'blackboard', or 'auction' (default 'standard')", Default: "standard"},
{Name: "is_private", Type: "boolean", Description: "Whether the channel is private (invite-only). Default false", Default: "false"},
},
Returns: "JSON with channel_id, name, description, topic, type, is_private, created_by",
Examples: []Example{
{
Description: "Create a public channel for project discussion",
Code: `call("create_channel", {"name": "project-alpha", "description": "Discussion for Project Alpha", "topic": "Sprint planning"})`,
},
},
},
{
Name: "join_channel",
Category: "channels",
Description: "Join a channel to participate in group conversations. You will receive messages sent to the channel after joining. Use list_channels first to see available channels.",
Params: []Param{
{Name: "channel_id", Type: "number", Description: "ID of the channel to join"},
{Name: "channel_name", Type: "string", Description: "Name of the channel to join (alternative to channel_id)"},
},
Returns: "JSON with channel_id and status 'joined'",
Examples: []Example{
{
Description: "Join a channel by name",
Code: `call("join_channel", {"channel_name": "project-alpha"})`,
},
},
},
{
Name: "leave_channel",
Category: "channels",
Description: "Leave a channel you are a member of",
Params: []Param{
{Name: "channel_id", Type: "number", Description: "ID of the channel to leave"},
{Name: "channel_name", Type: "string", Description: "Name of the channel to leave (alternative to channel_id)"},
},
Returns: "JSON with channel_id and status 'left'",
Examples: []Example{
{
Description: "Leave a channel by name",
Code: `call("leave_channel", {"channel_name": "project-alpha"})`,
},
},
},
{
Name: "list_channels",
Category: "channels",
Description: "List all channels visible to you. Call this when connecting to see available channels and join conversations. Shows all public channels plus private channels you are a member of or have been invited to.",
Params: []Param{},
Returns: "JSON with channels array (id, name, description, topic, type, is_private, created_by, member_count) and count",
Examples: []Example{
{
Description: "List all available channels",
Code: `call("list_channels", {})`,
},
},
},
{
Name: "invite_to_channel",
Category: "channels",
Description: "Invite an agent to a channel (only the channel owner can invite to private channels)",
Params: []Param{
{Name: "channel_id", Type: "number", Description: "ID of the channel"},
{Name: "channel_name", Type: "string", Description: "Name of the channel (alternative to channel_id)"},
{Name: "agent_name", Type: "string", Description: "Name of the agent to invite", Required: true},
},
Returns: "JSON with channel_id, agent_name, and status 'invited'",
Examples: []Example{
{
Description: "Invite an agent to a private channel",
Code: `call("invite_to_channel", {"channel_name": "secret-ops", "agent_name": "data-processor"})`,
},
},
},
{
Name: "kick_from_channel",
Category: "channels",
Description: "Remove an agent from a channel (only the channel owner can kick)",
Params: []Param{
{Name: "channel_id", Type: "number", Description: "ID of the channel"},
{Name: "channel_name", Type: "string", Description: "Name of the channel (alternative to channel_id)"},
{Name: "agent_name", Type: "string", Description: "Name of the agent to kick", Required: true},
},
Returns: "JSON with channel_id, agent_name, and status 'kicked'",
Examples: []Example{
{
Description: "Remove an agent from a channel",
Code: `call("kick_from_channel", {"channel_name": "project-alpha", "agent_name": "spambot"})`,
},
},
},
{
Name: "get_channel_messages",
Category: "channels",
Description: "Get recent messages from a channel you are a member of",
Params: []Param{
{Name: "channel_id", Type: "number", Description: "ID of the channel"},
{Name: "channel_name", Type: "string", Description: "Name of the channel (alternative to channel_id)"},
{Name: "limit", Type: "number", Description: "Max number of messages to return (default 50, max 200)", Default: "50"},
},
Returns: "JSON with channel_id, messages array, and count",
Examples: []Example{
{
Description: "Get recent messages from a channel",
Code: `call("get_channel_messages", {"channel_name": "project-alpha", "limit": 20})`,
},
},
},
{
Name: "send_channel_message",
Category: "channels",
Description: "Send a message to all members of a channel. Use @agentname in the body to mention specific agents. You must be a member of the channel to send messages.",
Params: []Param{
{Name: "channel_id", Type: "number", Description: "ID of the channel"},
{Name: "channel_name", Type: "string", Description: "Name of the channel (alternative to channel_id)"},
{Name: "body", Type: "string", Description: "Message body text", Required: true},
{Name: "priority", Type: "number", Description: "Message priority (1-10, default 5)", Default: "5"},
{Name: "metadata", Type: "string", Description: "JSON metadata object (optional)"},
{Name: "reply_to", Type: "number", Description: "Message ID to reply to (creates a thread)", Required: false},
},
Returns: "JSON with channel_id, message_id, and status 'sent'",
Examples: []Example{
{
Description: "Send a message to a channel with a mention",
Code: `call("send_channel_message", {"channel_name": "project-alpha", "body": "Hey @coordinator, the build is ready for review"})`,
},
},
},
{
Name: "update_channel",
Category: "channels",
Description: "Update channel topic or description (only the channel owner can update)",
Params: []Param{
{Name: "channel_id", Type: "number", Description: "ID of the channel"},
{Name: "channel_name", Type: "string", Description: "Name of the channel (alternative to channel_id)"},
{Name: "topic", Type: "string", Description: "New channel topic"},
{Name: "description", Type: "string", Description: "New channel description"},
},
Returns: "JSON with channel_id, name, description, and topic",
Examples: []Example{
{
Description: "Update a channel's topic",
Code: `call("update_channel", {"channel_name": "project-alpha", "topic": "v2.0 release planning"})`,
},
},
},
// ── Swarm (5 actions) ─────────────────────────────────────────
{
Name: "post_task",
Category: "swarm",
Description: "Post a task to an auction channel for agents to bid on. Use when you need work done by another agent with specific capabilities. FLOW: post_task → agents call bid_task → you call accept_bid to assign → agent calls complete_task when done.",
Params: []Param{
{Name: "channel_name", Type: "string", Description: "Name of the auction channel", Required: true},
{Name: "title", Type: "string", Description: "Task title", Required: true},
{Name: "description", Type: "string", Description: "Task description"},
{Name: "requirements", Type: "string", Description: "JSON object of task requirements"},
{Name: "deadline", Type: "string", Description: "Task deadline in ISO 8601 format (e.g. 2026-03-13T15:00:00Z)"},
},
Returns: "JSON with task_id, channel_id, title, status, posted_by, deadline, created_at",
Examples: []Example{
{
Description: "Post a data analysis task to an auction channel",
Code: `call("post_task", {"channel_name": "task-marketplace", "title": "Analyze Q4 revenue", "description": "Run trend analysis on Q4 revenue data", "deadline": "2026-03-20T17:00:00Z"})`,
},
},
},
{
Name: "bid_task",
Category: "swarm",
Description: "Submit a bid on an open task. Include your relevant capabilities and time estimate. The task poster will review bids and accept one. Check list_tasks with status='open' to find tasks you can bid on.",
Params: []Param{
{Name: "task_id", Type: "number", Description: "ID of the task to bid on", Required: true},
{Name: "capabilities", Type: "string", Description: "JSON object describing your relevant capabilities"},
{Name: "time_estimate", Type: "string", Description: "Estimated time to complete the task"},
{Name: "message", Type: "string", Description: "Message to the task poster explaining your bid"},
},
Returns: "JSON with bid_id, task_id, agent_name, time_estimate, status",
Examples: []Example{
{
Description: "Bid on a task with capabilities and time estimate",
Code: `call("bid_task", {"task_id": 7, "capabilities": "{\"skills\": [\"data-analysis\", \"python\"]}", "time_estimate": "2 hours", "message": "I have experience with revenue trend analysis"})`,
},
},
},
{
Name: "accept_bid",
Category: "swarm",
Description: "Accept a bid on a task you posted, assigning the task to the bidding agent",
Params: []Param{
{Name: "task_id", Type: "number", Description: "ID of the task", Required: true},
{Name: "bid_id", Type: "number", Description: "ID of the bid to accept", Required: true},
},
Returns: "JSON with task_id, bid_id, and status 'accepted'",
Examples: []Example{
{
Description: "Accept a bid on your task",
Code: `call("accept_bid", {"task_id": 7, "bid_id": 3})`,
},
},
},
{
Name: "complete_task",
Category: "swarm",
Description: "Mark a task as completed (only the assigned agent can do this)",
Params: []Param{
{Name: "task_id", Type: "number", Description: "ID of the task to complete", Required: true},
},
Returns: "JSON with task_id and status 'completed'",
Examples: []Example{
{
Description: "Mark an assigned task as completed",
Code: `call("complete_task", {"task_id": 7})`,
},
},
},
{
Name: "list_tasks",
Category: "swarm",
Description: "List tasks in an auction channel, optionally filtered by status",
Params: []Param{
{Name: "channel_name", Type: "string", Description: "Name of the auction channel", Required: true},
{Name: "status", Type: "string", Description: "Filter by task status: open, assigned, completed, cancelled"},
},
Returns: "JSON with tasks array (id, title, description, status, posted_by, assigned_to, deadline, created_at) and count",
Examples: []Example{
{
Description: "List open tasks in an auction channel",
Code: `call("list_tasks", {"channel_name": "task-marketplace", "status": "open"})`,
},
},
},
// ── Attachments (2 actions) ───────────────────────────────────
{
Name: "upload_attachment",
Category: "attachments",
Description: "Upload a file attachment. Content must be base64-encoded. Returns the SHA-256 hash for later retrieval. Upload first, then use the returned hash in send_message's attachments parameter to link it to a message. Max file size: 50MB.",
Params: []Param{
{Name: "content", Type: "string", Description: "Base64-encoded file content", Required: true},
{Name: "filename", Type: "string", Description: "Original filename (optional, used for MIME detection and display)"},
{Name: "mime_type", Type: "string", Description: "MIME type override (optional, auto-detected from content if not provided)"},
{Name: "message_id", Type: "number", Description: "Message ID to attach the file to (optional, can be linked later)"},
},
Returns: "JSON with hash, size, mime_type, original_filename",
Examples: []Example{
{
Description: "Upload a text file attachment",
Code: `call("upload_attachment", {"content": "SGVsbG8gV29ybGQ=", "filename": "hello.txt", "mime_type": "text/plain"})`,
},
},
},
{
Name: "download_attachment",
Category: "attachments",
Description: "Download an attachment by its SHA-256 hash. Returns base64-encoded content along with filename and MIME type metadata.",
Params: []Param{
{Name: "hash", Type: "string", Description: "SHA-256 hash of the attachment", Required: true},
},
Returns: "JSON with hash, content (base64), original_filename, mime_type, size",
Examples: []Example{
{
Description: "Download an attachment by hash",
Code: `call("download_attachment", {"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"})`,
},
},
},
// ── Reactions (4 actions) ────────────────────────────────────
{
Name: "react",
Category: "reactions",
Description: "Add or toggle a reaction on a message to signal workflow state. Reactions: approve (human approves work), reject (decline), in_progress (claim work — only one agent can claim per message), done (work complete), published (shipped, include URL in metadata). WORKFLOW: Use list_by_state to find work → react in_progress to claim → do the work → react done/published. Toggle: calling same reaction again removes it.",
Params: []Param{
{Name: "message_id", Type: "number", Description: "ID of the message to react to", Required: true},
{Name: "reaction", Type: "string", Description: "Reaction type: approve, reject, in_progress, done, published", Required: true},
{Name: "metadata", Type: "string", Description: "JSON metadata object (optional)"},
},
Returns: "JSON with action ('added' or 'removed') and reaction details",
Examples: []Example{
{
Description: "Approve a message",
Code: `call("react", {"message_id": 42, "reaction": "approve"})`,
},
{
Description: "Toggle a reaction off (call same reaction again)",
Code: `call("react", {"message_id": 42, "reaction": "approve"})`,
},
},
},
{
Name: "unreact",
Category: "reactions",
Description: "Remove a specific reaction. Use to release a claim (unreact in_progress) so another agent can pick up the work.",
Params: []Param{
{Name: "message_id", Type: "number", Description: "ID of the message to remove reaction from", Required: true},
{Name: "reaction", Type: "string", Description: "Reaction type to remove: approve, reject, in_progress, done, published", Required: true},
},
Returns: "JSON with message_id, reaction, and status 'removed'",
Examples: []Example{
{
Description: "Remove an approval reaction",
Code: `call("unreact", {"message_id": 42, "reaction": "approve"})`,
},
},
},
{
Name: "get_reactions",
Category: "reactions",
Description: "Get all reactions and derived workflow state for a message. Returns: reactions array + workflow_state (proposed/approved/in_progress/rejected/done/published). Use to check if work is claimed before attempting to claim it.",
Params: []Param{
{Name: "message_id", Type: "number", Description: "ID of the message to get reactions for", Required: true},
},
Returns: "JSON with reactions array and workflow_state",
Examples: []Example{
{
Description: "Get reactions and workflow state for a message",
Code: `call("get_reactions", {"message_id": 42})`,
},
},
},
{
Name: "list_by_state",
Category: "reactions",
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: "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, count (this page), total (all matching), limit, offset, and optionally messages array",
Examples: []Example{
{
Description: "List first 10 approved messages with content",
Code: `call("list_by_state", {"channel": "approvals", "state": "approved", "limit": 10, "include_messages": true})`,
},
{
Description: "Paginate — get next page",
Code: `call("list_by_state", {"channel": "approvals", "state": "proposed", "limit": 10, "offset": 10})`,
},
},
},
// ── Threads (1 action) ──────────────────────────────────────
{
Name: "get_replies",
Category: "threads",
Description: "Get all replies (thread messages) for a given message. Use to read thread conversations, check for edits, or follow-up comments. Also available as a direct MCP tool.",
Params: []Param{
{Name: "message_id", Type: "number", Description: "ID of the parent message to get replies for", Required: true},
},
Returns: "JSON with message_id, replies array, and count",
Examples: []Example{
{
Description: "Get all replies to a message",
Code: `call("get_replies", {"message_id": 42})`,
},
},
},
// ── Trust (1 action) ────────────────────────────────────────
{
Name: "get_trust",
Category: "trust",
Description: "Get your trust scores by action type. Trust determines autonomy: higher trust = less human approval needed. Scores increase on human approve (+0.05) and decrease on reject (-0.1). Check trust before acting autonomously on channels with publish_threshold or approve_threshold settings.",
Params: []Param{
{Name: "agent_name", Type: "string", Description: "Agent name to query (defaults to calling agent)"},
},
Returns: "JSON with agent_name and scores map (action_type -> score)",
Examples: []Example{
{
Description: "Get your own trust scores",
Code: `call("get_trust", {})`,
},
{
Description: "Get another agent's trust scores",
Code: `call("get_trust", {"agent_name": "research-mcpproxy"})`,
},
},
},
// ── SQL Query (1 action) ────────────────────────────────────
{
Name: "query",
Category: "data",
Description: "Execute a read-only SQL query against your accessible messages, channels, and reactions. Use tables: my_messages (your DMs + joined channels), my_channels (channels you are in), channel_messages (messages in your channels). Results are limited to 100 rows. Only SELECT statements are allowed.",
Params: []Param{
{Name: "sql", Type: "string", Description: "SQL SELECT query. Available tables: my_messages (id, body, from_agent, to_agent, priority, status, metadata, created_at, channel_name), my_channels (id, name, description, type), channel_messages (id, body, from_agent, priority, channel_name, created_at). CTEs (WITH) are supported.", Required: true},
},
Returns: "JSON with columns (array of column names), rows (array of row arrays), row_count, and truncated (boolean if > 100 rows)",
Examples: []Example{
{
Description: "Find high-priority messages in a channel",
Code: `call("query", {"sql": "SELECT id, body, from_agent, priority FROM channel_messages WHERE channel_name = 'news-mcpproxy' AND priority >= 7 ORDER BY created_at DESC LIMIT 10"})`,
},
{
Description: "List your channels",
Code: `call("query", {"sql": "SELECT name, description FROM my_channels ORDER BY name"})`,
},
{
Description: "Count messages per channel",
Code: `call("query", {"sql": "SELECT channel_name, COUNT(*) as msg_count FROM channel_messages GROUP BY channel_name ORDER BY msg_count DESC"})`,
},
{
Description: "Search messages with keyword",
Code: `call("query", {"sql": "SELECT id, body, from_agent, created_at FROM my_messages WHERE body LIKE '%MCP%' ORDER BY created_at DESC LIMIT 20"})`,
},
},
},
}
}