← back to Norma
agents/discord-agent/skills/discover.js
174 lines
/**
* Discord Discover Skill
*
* Searches configured Discord channels for relevant advocacy discussions.
* Queries channels from the sdcc_agent_monitors table.
*
* Rate limit: 5 per 5sec per channel
*/
const { RateLimiter } = require('../../shared/rate-limiter');
const { logAction } = require('../../shared/audit-logger');
const { pushSocialFeed } = require('../../shared/pulse-reporter');
const { getRecentMessages, scoreRelevance, hasCredentials, KEYWORDS } = require('../lib/discord');
const { query: dbQuery } = require('../../shared/db');
const AGENT = 'discord-agent';
const PLATFORM = 'discord';
const rateLimiter = new RateLimiter(AGENT, PLATFORM);
/**
* @param {Object} body - Request body
* @param {string[]} [body.channel_ids] - Specific channel IDs to search
* @param {string[]} [body.keywords] - Keywords to filter by (default: advocacy keywords)
* @param {number} [body.limit=50] - Max messages per channel
* @param {boolean} [body.push_to_pulse=true] - Push results to Pulse
* @returns {Promise<Object>}
*/
module.exports = async function discover(body) {
const limit = Math.min(body.limit || 50, 100);
const keywords = body.keywords || KEYWORDS;
const pushToPulse = body.push_to_pulse !== false;
// Check rate limit
const rateCheck = await rateLimiter.checkLimit('post');
if (!rateCheck.allowed) {
await logAction({
agent: AGENT,
actionType: 'discover',
platform: PLATFORM,
status: 'rate_limited',
errorMessage: `Rate limited. Retry after ${Math.ceil(rateCheck.retryAfterMs / 1000)}s`,
});
return {
rate_limited: true,
retry_after_seconds: Math.ceil(rateCheck.retryAfterMs / 1000),
current: rateCheck.current,
max: rateCheck.max,
};
}
// Get channel IDs from body or database monitors
const channelIds = new Set();
if (body.channel_ids && body.channel_ids.length > 0) {
body.channel_ids.forEach((id) => channelIds.add(id));
}
// Query database for configured monitors
try {
const { rows } = await dbQuery(
`SELECT id, target, keywords
FROM sdcc_agent_monitors
WHERE agent = $1 AND platform = $2 AND is_active = true`,
[AGENT, PLATFORM]
);
for (const row of rows) {
if (row.target) {
channelIds.add(row.target);
}
}
} catch (err) {
console.error(`[${AGENT}] Failed to fetch monitors:`, err.message);
}
if (channelIds.size === 0) {
return {
channels_searched: 0,
total_messages: 0,
relevant_count: 0,
results: [],
note: 'No channels configured. Add channel IDs via body.channel_ids or sdcc_agent_monitors table.',
};
}
const allResults = [];
for (const channelId of channelIds) {
try {
const messages = await getRecentMessages(channelId, limit);
await rateLimiter.recordAction('post');
// Score each message for relevance
const scored = messages
.filter((msg) => !msg.isBot) // Skip bot messages
.map((msg) => ({
...msg,
relevance_score: scoreRelevance(msg.content),
}));
// Filter to relevant messages
const relevant = scored.filter((m) => m.relevance_score > 10);
relevant.sort((a, b) => b.relevance_score - a.relevance_score);
allResults.push({
channelId,
total_messages: messages.length,
relevant_count: relevant.length,
messages: relevant.slice(0, 20), // Top 20 relevant messages
});
} catch (err) {
console.error(`[${AGENT}] Discover failed for channel ${channelId}:`, err.message);
allResults.push({
channelId,
total_messages: 0,
relevant_count: 0,
messages: [],
error: err.message,
});
}
}
const totalMessages = allResults.reduce((sum, r) => sum + r.total_messages, 0);
const totalRelevant = allResults.reduce((sum, r) => sum + r.relevant_count, 0);
// Push to Pulse if enabled
if (pushToPulse) {
const topMessages = allResults
.flatMap((r) => r.messages)
.sort((a, b) => b.relevance_score - a.relevance_score)
.slice(0, 5);
for (const msg of topMessages) {
await pushSocialFeed({
agent: AGENT,
platform: PLATFORM,
type: 'discussion',
title: `${msg.author}: ${msg.content.substring(0, 80)}`,
content: msg.content.substring(0, 300),
metadata: {
message_id: msg.id,
author: msg.author,
relevance_score: msg.relevance_score,
created_at: msg.createdAt,
},
});
}
}
// Log the action
await logAction({
agent: AGENT,
actionType: 'discover',
platform: PLATFORM,
responseData: {
channels_searched: channelIds.size,
total_messages: totalMessages,
total_relevant: totalRelevant,
bot_connected: hasCredentials(),
},
status: 'success',
});
return {
channels_searched: channelIds.size,
total_messages: totalMessages,
relevant_count: totalRelevant,
results: allResults,
bot_connected: hasCredentials(),
fetched_at: new Date().toISOString(),
};
};