diff --git a/src/index.ts b/src/index.ts index c29abb7..2892648 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,11 @@ import { AutonomousAgent } from './services/agent.js'; import { XMCPServer } from './mcp/server.js'; async function main() { + // Redirect console.log to stderr so it doesn't conflict with + // MCP StdioServerTransport which uses stdout for protocol messages + const origLog = console.log; + console.log = (...args: any[]) => console.error(...args); + console.log('═══════════════════════════════════════════════════'); console.log(' MyXstack - Autonomous AI Agent on X (Twitter)'); console.log('═══════════════════════════════════════════════════\n'); diff --git a/src/services/agent.ts b/src/services/agent.ts index 326fb38..de10ae7 100644 --- a/src/services/agent.ts +++ b/src/services/agent.ts @@ -11,6 +11,7 @@ export class AutonomousAgent { private grokService: GrokService; private config: AgentConfig; private processedMentions: Set = new Set(); + private static readonly MAX_PROCESSED_MENTIONS = 10000; private isRunning: boolean = false; private pollingIntervalId: NodeJS.Timeout | null = null; private isProcessing: boolean = false; @@ -95,6 +96,15 @@ export class AutonomousAgent { await this.processMention(mention); this.processedMentions.add(mention.post.id); } + + // Prune oldest entries to prevent unbounded memory growth + if (this.processedMentions.size > AutonomousAgent.MAX_PROCESSED_MENTIONS) { + const excess = this.processedMentions.size - AutonomousAgent.MAX_PROCESSED_MENTIONS; + const iter = this.processedMentions.values(); + for (let i = 0; i < excess; i++) { + this.processedMentions.delete(iter.next().value as string); + } + } } catch (error) { console.error('❌ Error in processing loop:', error); } finally { diff --git a/src/services/xapi.ts b/src/services/xapi.ts index f80be1e..13e92a7 100644 --- a/src/services/xapi.ts +++ b/src/services/xapi.ts @@ -44,17 +44,26 @@ export class XAPIClient { throw new Error('Failed to get user ID from response'); } - const mentionsResponse = await this.makeXAPIRequest( - `https://api.twitter.com/2/users/${userId}/mentions?max_results=10&expansions=author_id&tweet.fields=created_at,conversation_id,in_reply_to_user_id,referenced_tweets`, - 'GET' - ); + let mentionsUrl = `https://api.twitter.com/2/users/${userId}/mentions?max_results=10&expansions=author_id&tweet.fields=created_at,conversation_id,in_reply_to_user_id,referenced_tweets`; + if (this.lastMentionId) { + mentionsUrl += `&since_id=${this.lastMentionId}`; + } + + const mentionsResponse = await this.makeXAPIRequest(mentionsUrl, 'GET'); if (!mentionsResponse || !Array.isArray(mentionsResponse.data)) { console.warn('Invalid response from X API (mentions)'); return []; } - return this.parseMentions(mentionsResponse.data); + const mentions = this.parseMentions(mentionsResponse.data); + + // Track the newest mention ID for pagination on the next poll + if (mentionsResponse.data.length > 0) { + this.lastMentionId = mentionsResponse.data[0].id; + } + + return mentions; } catch (error) { console.error('Error fetching mentions:', error); return []; @@ -77,7 +86,12 @@ export class XAPIClient { 'GET' ); - return this.parseThread(response.data || []); + if (!response || !response.data) { + console.warn('Invalid response from X API (thread)'); + return null; + } + + return this.parseThread(response.data); } catch (error) { console.error('Error fetching thread:', error); return null; @@ -184,7 +198,7 @@ export class XAPIClient { private parseThread(tweets: { created_at: string; [key: string]: any }[]): XThread | null { if (tweets.length === 0) return null; - const sorted = tweets.sort((a, b) => + const sorted = [...tweets].sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime() );