diff --git a/package-lock.json b/package-lock.json index 9358015..ced4e7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -396,6 +396,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -600,6 +601,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -1170,6 +1172,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/index.ts b/src/index.ts index 064a6c8..a12c45d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ 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 - console.log = (...args: any[]) => console.error(...args); + console.log = (...args: unknown[]) => console.error(...args); console.log('═══════════════════════════════════════════════════'); console.log(' MyXstack - Autonomous AI Agent on X (Twitter)'); diff --git a/src/services/agent.ts b/src/services/agent.ts index de10ae7..bbe7556 100644 --- a/src/services/agent.ts +++ b/src/services/agent.ts @@ -91,8 +91,9 @@ export class AutonomousAgent { console.log(`\n📬 [${new Date().toLocaleTimeString()}] Found ${newMentions.length} new mention(s)!\n`); - // Process each mention - for (const mention of newMentions) { + // Process mentions oldest-first (API returns newest-first) so Set + // insertion order is chronological and oldest entries are pruned first + for (const mention of [...newMentions].reverse()) { await this.processMention(mention); this.processedMentions.add(mention.post.id); } @@ -102,7 +103,11 @@ export class AutonomousAgent { 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); + const { value, done } = iter.next(); + if (done) { + break; + } + this.processedMentions.delete(value); } } } catch (error) { diff --git a/src/services/grok.ts b/src/services/grok.ts index d71ffdc..52c239c 100644 --- a/src/services/grok.ts +++ b/src/services/grok.ts @@ -59,7 +59,7 @@ export class GrokService { throw new Error(`Grok API error: ${response.status}`); } - const data: any = await response.json(); + const data = await response.json() as { choices: { message?: { content?: string } }[] }; const analysisText = data.choices[0]?.message?.content || ''; // Use the mention post ID so replies target the specific post where the agent was mentioned diff --git a/src/services/xapi.ts b/src/services/xapi.ts index ab6c4b5..968d3db 100644 --- a/src/services/xapi.ts +++ b/src/services/xapi.ts @@ -44,10 +44,15 @@ export class XAPIClient { throw new Error('Failed to get user ID from response'); } - 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`; + const params = new URLSearchParams({ + 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}`; + params.set('since_id', this.lastMentionId); } + const mentionsUrl = `https://api.twitter.com/2/users/${userId}/mentions?${params.toString()}`; const mentionsResponse = await this.makeXAPIRequest(mentionsUrl, 'GET'); @@ -91,6 +96,11 @@ export class XAPIClient { return null; } + if (!Array.isArray(response.data)) { + console.warn('Unexpected response shape from X API (thread): data is not an array'); + return null; + } + return this.parseThread(response.data); } catch (error) { console.error('Error fetching thread:', error);