← back to Norma
agents/moveon-agent/skills/discover.js
93 lines
/**
* MoveOn Discover Skill
*
* Searches MoveOn.org for existing petitions related to advocacy topics.
* Rate limit: 2/hour
*/
const { RateLimiter } = require('../../shared/rate-limiter');
const { logAction } = require('../../shared/audit-logger');
const { searchPetitions, getPetitionDetails } = require('../lib/moveon');
const AGENT = 'moveon-agent';
const PLATFORM = 'moveon';
const rateLimiter = new RateLimiter(AGENT, PLATFORM);
/**
* @param {Object} body - Request body
* @param {string} [body.query='nonprofit advocacy'] - Search query
* @param {number} [body.max_results=10] - Max results to return
* @param {boolean} [body.fetch_details=false] - Fetch full details for each result
* @returns {Promise<Object>}
*/
module.exports = async function discover(body) {
const query = body.query || 'nonprofit advocacy';
const maxResults = Math.min(body.max_results || 10, 25);
const fetchDetails = body.fetch_details || false;
// Check rate limit (uses 'search' action type — falls back to default 10/hour)
const limit = await rateLimiter.checkLimit('monitor');
if (!limit.allowed) {
await logAction({
agent: AGENT,
actionType: 'discover',
platform: PLATFORM,
content: query,
status: 'rate_limited',
errorMessage: `Rate limited. Retry after ${Math.ceil(limit.retryAfterMs / 1000)}s`,
});
return {
rate_limited: true,
retry_after_seconds: Math.ceil(limit.retryAfterMs / 1000),
current: limit.current,
max: limit.max,
};
}
// Search MoveOn for petitions
const results = await searchPetitions(query, maxResults);
// Optionally fetch full details for each petition
let enrichedResults = results;
if (fetchDetails && results.length > 0) {
enrichedResults = [];
for (const result of results.slice(0, 5)) {
// limit detail fetches to 5
try {
const details = await getPetitionDetails(result.url);
enrichedResults.push({ ...result, ...details });
} catch (err) {
enrichedResults.push({ ...result, detailError: err.message });
}
}
// Add remaining without details
if (results.length > 5) {
enrichedResults.push(...results.slice(5));
}
}
// Record the action
await rateLimiter.recordAction('monitor');
await logAction({
agent: AGENT,
actionType: 'discover',
platform: PLATFORM,
content: query,
responseData: {
query,
resultCount: enrichedResults.length,
fetchedDetails: fetchDetails,
},
status: 'success',
});
return {
query,
count: enrichedResults.length,
petitions: enrichedResults,
fetched_at: new Date().toISOString(),
};
};