From 4f81675b6ccdc27ecfa801d06a7da66798118fe1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 17:51:04 +0000 Subject: [PATCH 1/3] Initial plan From 1e5708c238c8f0f00d6d72adcae5d2aff27448f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 18:11:30 +0000 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20consolidate=20review=20feedback=20fr?= =?UTF-8?q?om=20PRs=20#26=20and=20#6=20=E2=80=94=20type=20safety,=20URLSea?= =?UTF-8?q?rchParams,=20Array.isArray=20guard,=20safe=20iterators,=20reply?= =?UTF-8?q?=20targeting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- CHANGES_SUMMARY.md | 33 +++++++++++++++ FINAL_CHANGES_SUMMARY.md | 91 ++++++++++++++++++++++++++++++++++++++++ src/examples.ts | 4 +- src/index.ts | 4 ++ src/services/agent.ts | 15 +++++-- src/services/grok.ts | 22 +++++----- src/services/xapi.ts | 44 ++++++++++++++----- 7 files changed, 188 insertions(+), 25 deletions(-) create mode 100644 CHANGES_SUMMARY.md create mode 100644 FINAL_CHANGES_SUMMARY.md diff --git a/CHANGES_SUMMARY.md b/CHANGES_SUMMARY.md new file mode 100644 index 0000000..cd2072c --- /dev/null +++ b/CHANGES_SUMMARY.md @@ -0,0 +1,33 @@ +# Code Changes Summary + +All requested changes from PR #26 and PR #6 review feedback have been successfully applied: + +## ✅ 1. src/index.ts +- Added console.log redirect to stderr before first console.log (line 13) +- Uses `unknown[]` type (not `any[]`) as requested + +## ✅ 2. src/services/xapi.ts +- **2a.** fetchMentions() now uses URLSearchParams and wires up lastMentionId (lines 47-57) +- **2b.** Added lastMentionId tracking after Array.isArray check (lines 64-69) +- **2c.** Added Array.isArray guard in fetchThread() (lines 79-88) +- **2d.** Fixed parseThread() to copy array before sorting and improved type (line 194) + +## ✅ 3. src/services/agent.ts +- **3a.** Added MAX_PROCESSED_MENTIONS constant (line 14) +- **3b.** Process mentions in reverse order and added pruning logic (lines 94-110) +- **3c.** Added mentionPostId parameter to analyzeAndDecide call (line 133) + +## ✅ 4. src/services/grok.ts +- **4a.** Updated analyzeAndDecide signature with mentionPostId parameter and updated JSDoc (lines 20-29) +- **4b.** Fixed `any` type to `unknown` with proper type casting and use mentionPostId (lines 62-67) +- **4c.** Updated fallback call to pass mentionPostId (line 71) +- **4d.** Updated simulateAnalysis signature (line 148) +- **4e.** Updated both target_post_id references to use mentionPostId (lines 164 and 179) + +## ✅ 5. src/examples.ts +- **5a.** Line 36 - Pass mentionPostId to analyzeAndDecide +- **5b.** Line 142 - Pass mentionPostId to analyzeAndDecide + +## ✅ Build Verification +- Build completed successfully with no errors +- All TypeScript types are correct diff --git a/FINAL_CHANGES_SUMMARY.md b/FINAL_CHANGES_SUMMARY.md new file mode 100644 index 0000000..ab0748b --- /dev/null +++ b/FINAL_CHANGES_SUMMARY.md @@ -0,0 +1,91 @@ +# Final Changes Summary + +All requested code changes from PR #26 and PR #6 have been successfully implemented and verified. + +## Changes Made + +### 1. ✅ src/index.ts - Console Logging +- Added console.log redirect to stderr to avoid MCP protocol conflicts +- Uses `unknown[]` type instead of `any[]` (review feedback) +- Location: Line 13 + +### 2. ✅ src/services/xapi.ts - X API Improvements +**2a. URLSearchParams in fetchMentions() (lines 47-57)** +- Replaced string concatenation with URLSearchParams +- Properly wires up lastMentionId for pagination + +**2b. lastMentionId tracking (lines 64-69)** +- Tracks newest mention ID after fetching +- Includes proper type guard (mentionsResponse.data[0]?.id) +- Enhanced comment clarity about API ordering + +**2c. Array.isArray guard in fetchThread() (lines 79-88)** +- Added proper response validation +- Guards against non-array data +- Returns null for invalid responses + +**2d. parseThread() improvements (lines 208-218)** +- Copies array before sorting to avoid mutations +- Improved type signature: `{ created_at: string; [key: string]: unknown }[]` +- Better variable naming: `sortedTweets` + +### 3. ✅ src/services/agent.ts - Memory Management +**3a. MAX_PROCESSED_MENTIONS constant (line 14)** +- Set to 10,000 items +- Prevents unbounded memory growth + +**3b. Process mentions in reverse (line 95)** +- Uses `[...newMentions].reverse()` to avoid mutating original +- Processes oldest-first (API returns newest-first) + +**3c. Pruning logic (lines 101-104)** +- Simplified array-based approach for better readability +- Deletes oldest entries when exceeding MAX_PROCESSED_MENTIONS + +**3d. mentionPostId parameter (line 133)** +- Added to analyzeAndDecide call + +### 4. ✅ src/services/grok.ts - Type Safety & Correct Reply Targeting +**4a. Updated analyzeAndDecide signature (lines 20-29)** +- Added mentionPostId parameter +- Updated JSDoc documentation + +**4b. Type safety improvements (lines 62-67)** +- Changed `any` to `unknown` +- Proper type casting with explicit type definition +- Uses mentionPostId for replies + +**4c. Fallback call updated (line 71)** +- Passes mentionPostId to simulateAnalysis + +**4d. simulateAnalysis signature (line 148)** +- Added mentionPostId parameter + +**4e. target_post_id usage (lines 164, 179)** +- Both branches now use mentionPostId +- Ensures replies target the correct post + +### 5. ✅ src/examples.ts - Parameter Updates +- Line 36: Added mention.post.id to analyzeAndDecide call +- Line 142: Added mention.post.id to analyzeAndDecide call + +## Verification + +✅ **Build Status**: SUCCESS (TypeScript compilation with no errors) +✅ **Code Review**: All feedback addressed +✅ **Security Scan**: No vulnerabilities found (CodeQL) +✅ **Type Safety**: All `any` types replaced with proper types +✅ **Memory Safety**: Implemented capping and pruning +✅ **Code Quality**: Improved readability and maintainability + +## Key Improvements + +1. **Type Safety**: Eliminated `any` types in favor of `unknown` with proper type guards +2. **Memory Management**: Added bounded memory with automatic pruning +3. **Correct Behavior**: Replies now target the mention post, not the thread root +4. **Pagination**: lastMentionId properly tracked for incremental fetching +5. **Robustness**: Added validation guards for API responses +6. **Immutability**: Arrays are copied before mutation operations +7. **Readability**: Simplified complex iterator patterns and improved variable naming + +All changes follow TypeScript best practices and have been validated through builds and security scanning. diff --git a/src/examples.ts b/src/examples.ts index aaf1598..3da6efe 100644 --- a/src/examples.ts +++ b/src/examples.ts @@ -33,7 +33,7 @@ async function example1_fetchAndAnalyzeMention() { console.log(`\nThread has ${thread.replies.length + 1} posts`); // Analyze with Grok - const analysis = await grok.analyzeAndDecide(mention.post.text, thread); + const analysis = await grok.analyzeAndDecide(mention.post.text, thread, mention.post.id); console.log(`\nGrok's Decision:`); console.log(` Action: ${analysis.action.type}`); @@ -139,7 +139,7 @@ async function example5_batchProcessMentions() { const thread = await xClient.fetchThread(conversationId); if (thread) { - const analysis = await grok.analyzeAndDecide(mention.post.text, thread); + const analysis = await grok.analyzeAndDecide(mention.post.text, thread, mention.post.id); console.log(` → Action: ${analysis.action.type} (${(analysis.confidence * 100).toFixed(0)}% confidence)`); // In a real scenario, you might execute the action here diff --git a/src/index.ts b/src/index.ts index 6dd2dfe..9ae06a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,10 @@ 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 + console.log = (...args: unknown[]) => 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 ab40033..5d38062 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; @@ -90,11 +91,18 @@ 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) + for (const mention of [...newMentions].reverse()) { 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 toDelete = Array.from(this.processedMentions).slice(0, excess); + toDelete.forEach(id => this.processedMentions.delete(id)); + } } catch (error) { console.error('❌ Error in processing loop:', error); } finally { @@ -129,7 +137,8 @@ export class AutonomousAgent { console.log('\n🤖 Analyzing with Grok AI...'); const analysis = await this.grokService.analyzeAndDecide( mention.post.text, - thread + thread, + mention.post.id ); console.log(` Action: ${analysis.action.type.toUpperCase()}`); diff --git a/src/services/grok.ts b/src/services/grok.ts index 0fd6b2c..b97dc46 100644 --- a/src/services/grok.ts +++ b/src/services/grok.ts @@ -21,11 +21,12 @@ export class GrokService { * Analyze a mention and thread context to determine appropriate action * @param mention - The text content of the mention to analyze * @param thread - The thread context including root post and replies + * @param mentionPostId - The ID of the post where the agent was mentioned * @returns Analysis with recommended action */ - async analyzeAndDecide(mention: string, thread: XThread): Promise { + async analyzeAndDecide(mention: string, thread: XThread, mentionPostId: string): Promise { if (this.simulationMode) { - return this.simulateAnalysis(mention, thread); + return this.simulateAnalysis(mention, thread, mentionPostId); } try { @@ -58,15 +59,16 @@ export class GrokService { throw new Error(`Grok API error: ${response.status}`); } - const data: any = await response.json(); - const analysisText = data.choices[0]?.message?.content || ''; + const data: unknown = await response.json(); + const parsed = data as { choices?: Array<{ message?: { content?: string } }> }; + const analysisText = parsed.choices?.[0]?.message?.content || ''; - // Use the root post ID from the thread, not the mention text - return this.parseGrokResponse(analysisText, thread.root_post.id); + // Use the mention post ID to reply to the specific post that mentioned the agent + return this.parseGrokResponse(analysisText, mentionPostId); } catch (error) { console.error('Error calling Grok API:', error); // Fallback to simulation - return this.simulateAnalysis(mention, thread); + return this.simulateAnalysis(mention, thread, mentionPostId); } } @@ -145,7 +147,7 @@ export class GrokService { /** * Simulate Grok analysis for testing */ - private simulateAnalysis(mention: string, thread: XThread): GrokAnalysis { + private simulateAnalysis(mention: string, thread: XThread, mentionPostId: string): GrokAnalysis { console.log('🤖 Simulated Grok Analysis:'); console.log(` Analyzing: "${mention}"`); @@ -159,7 +161,7 @@ export class GrokService { const analysis: GrokAnalysis = { action: { type: 'reply', - target_post_id: thread.root_post.id, + target_post_id: mentionPostId, content: 'Thanks for reaching out! I\'ve analyzed your question and here\'s my insight: Based on the context, I\'d recommend exploring this topic further. Let me know if you need more specific information!', reasoning: 'Detected a question, providing helpful response', }, @@ -174,7 +176,7 @@ export class GrokService { const analysis: GrokAnalysis = { action: { type: 'analyze', - target_post_id: thread.root_post.id, + target_post_id: mentionPostId, reasoning: 'No clear action needed, just acknowledgment', }, confidence: 0.7, diff --git a/src/services/xapi.ts b/src/services/xapi.ts index 1f62056..6fc3c67 100644 --- a/src/services/xapi.ts +++ b/src/services/xapi.ts @@ -44,17 +44,31 @@ 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' - ); + 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) { + 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'); 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 (API returns newest-first, so ID is at index 0) + if (mentionsResponse.data.length > 0 && mentionsResponse.data[0]?.id) { + this.lastMentionId = mentionsResponse.data[0].id; + } + + return mentions; } catch (error) { console.error('Error fetching mentions:', error); return []; @@ -77,7 +91,17 @@ export class XAPIClient { 'GET' ); - return this.parseThread(response.data || []); + if (!response || !response.data) { + console.warn('Invalid response from X API (thread)'); + 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); return null; @@ -181,16 +205,16 @@ export class XAPIClient { }; } - private parseThread(tweets: any[]): XThread | null { + private parseThread(tweets: { created_at: string; [key: string]: unknown }[]): XThread | null { if (tweets.length === 0) return null; - const sorted = tweets.sort((a, b) => + const sortedTweets = [...tweets].sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime() ); return { - root_post: this.parsePost(sorted[0]), - replies: sorted.slice(1).map((t) => this.parsePost(t)), + root_post: this.parsePost(sortedTweets[0]), + replies: sortedTweets.slice(1).map((t) => this.parsePost(t)), }; } From af23a7949bf047cd0f94ec6ce3c68b194f87635a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 18:13:10 +0000 Subject: [PATCH 3/3] fix: use iterator pruning pattern and add string type guard for lastMentionId Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- CHANGES_SUMMARY.md | 33 --------------- FINAL_CHANGES_SUMMARY.md | 91 ---------------------------------------- src/services/agent.ts | 10 ++++- src/services/xapi.ts | 2 +- 4 files changed, 9 insertions(+), 127 deletions(-) delete mode 100644 CHANGES_SUMMARY.md delete mode 100644 FINAL_CHANGES_SUMMARY.md diff --git a/CHANGES_SUMMARY.md b/CHANGES_SUMMARY.md deleted file mode 100644 index cd2072c..0000000 --- a/CHANGES_SUMMARY.md +++ /dev/null @@ -1,33 +0,0 @@ -# Code Changes Summary - -All requested changes from PR #26 and PR #6 review feedback have been successfully applied: - -## ✅ 1. src/index.ts -- Added console.log redirect to stderr before first console.log (line 13) -- Uses `unknown[]` type (not `any[]`) as requested - -## ✅ 2. src/services/xapi.ts -- **2a.** fetchMentions() now uses URLSearchParams and wires up lastMentionId (lines 47-57) -- **2b.** Added lastMentionId tracking after Array.isArray check (lines 64-69) -- **2c.** Added Array.isArray guard in fetchThread() (lines 79-88) -- **2d.** Fixed parseThread() to copy array before sorting and improved type (line 194) - -## ✅ 3. src/services/agent.ts -- **3a.** Added MAX_PROCESSED_MENTIONS constant (line 14) -- **3b.** Process mentions in reverse order and added pruning logic (lines 94-110) -- **3c.** Added mentionPostId parameter to analyzeAndDecide call (line 133) - -## ✅ 4. src/services/grok.ts -- **4a.** Updated analyzeAndDecide signature with mentionPostId parameter and updated JSDoc (lines 20-29) -- **4b.** Fixed `any` type to `unknown` with proper type casting and use mentionPostId (lines 62-67) -- **4c.** Updated fallback call to pass mentionPostId (line 71) -- **4d.** Updated simulateAnalysis signature (line 148) -- **4e.** Updated both target_post_id references to use mentionPostId (lines 164 and 179) - -## ✅ 5. src/examples.ts -- **5a.** Line 36 - Pass mentionPostId to analyzeAndDecide -- **5b.** Line 142 - Pass mentionPostId to analyzeAndDecide - -## ✅ Build Verification -- Build completed successfully with no errors -- All TypeScript types are correct diff --git a/FINAL_CHANGES_SUMMARY.md b/FINAL_CHANGES_SUMMARY.md deleted file mode 100644 index ab0748b..0000000 --- a/FINAL_CHANGES_SUMMARY.md +++ /dev/null @@ -1,91 +0,0 @@ -# Final Changes Summary - -All requested code changes from PR #26 and PR #6 have been successfully implemented and verified. - -## Changes Made - -### 1. ✅ src/index.ts - Console Logging -- Added console.log redirect to stderr to avoid MCP protocol conflicts -- Uses `unknown[]` type instead of `any[]` (review feedback) -- Location: Line 13 - -### 2. ✅ src/services/xapi.ts - X API Improvements -**2a. URLSearchParams in fetchMentions() (lines 47-57)** -- Replaced string concatenation with URLSearchParams -- Properly wires up lastMentionId for pagination - -**2b. lastMentionId tracking (lines 64-69)** -- Tracks newest mention ID after fetching -- Includes proper type guard (mentionsResponse.data[0]?.id) -- Enhanced comment clarity about API ordering - -**2c. Array.isArray guard in fetchThread() (lines 79-88)** -- Added proper response validation -- Guards against non-array data -- Returns null for invalid responses - -**2d. parseThread() improvements (lines 208-218)** -- Copies array before sorting to avoid mutations -- Improved type signature: `{ created_at: string; [key: string]: unknown }[]` -- Better variable naming: `sortedTweets` - -### 3. ✅ src/services/agent.ts - Memory Management -**3a. MAX_PROCESSED_MENTIONS constant (line 14)** -- Set to 10,000 items -- Prevents unbounded memory growth - -**3b. Process mentions in reverse (line 95)** -- Uses `[...newMentions].reverse()` to avoid mutating original -- Processes oldest-first (API returns newest-first) - -**3c. Pruning logic (lines 101-104)** -- Simplified array-based approach for better readability -- Deletes oldest entries when exceeding MAX_PROCESSED_MENTIONS - -**3d. mentionPostId parameter (line 133)** -- Added to analyzeAndDecide call - -### 4. ✅ src/services/grok.ts - Type Safety & Correct Reply Targeting -**4a. Updated analyzeAndDecide signature (lines 20-29)** -- Added mentionPostId parameter -- Updated JSDoc documentation - -**4b. Type safety improvements (lines 62-67)** -- Changed `any` to `unknown` -- Proper type casting with explicit type definition -- Uses mentionPostId for replies - -**4c. Fallback call updated (line 71)** -- Passes mentionPostId to simulateAnalysis - -**4d. simulateAnalysis signature (line 148)** -- Added mentionPostId parameter - -**4e. target_post_id usage (lines 164, 179)** -- Both branches now use mentionPostId -- Ensures replies target the correct post - -### 5. ✅ src/examples.ts - Parameter Updates -- Line 36: Added mention.post.id to analyzeAndDecide call -- Line 142: Added mention.post.id to analyzeAndDecide call - -## Verification - -✅ **Build Status**: SUCCESS (TypeScript compilation with no errors) -✅ **Code Review**: All feedback addressed -✅ **Security Scan**: No vulnerabilities found (CodeQL) -✅ **Type Safety**: All `any` types replaced with proper types -✅ **Memory Safety**: Implemented capping and pruning -✅ **Code Quality**: Improved readability and maintainability - -## Key Improvements - -1. **Type Safety**: Eliminated `any` types in favor of `unknown` with proper type guards -2. **Memory Management**: Added bounded memory with automatic pruning -3. **Correct Behavior**: Replies now target the mention post, not the thread root -4. **Pagination**: lastMentionId properly tracked for incremental fetching -5. **Robustness**: Added validation guards for API responses -6. **Immutability**: Arrays are copied before mutation operations -7. **Readability**: Simplified complex iterator patterns and improved variable naming - -All changes follow TypeScript best practices and have been validated through builds and security scanning. diff --git a/src/services/agent.ts b/src/services/agent.ts index 5d38062..9b2e2af 100644 --- a/src/services/agent.ts +++ b/src/services/agent.ts @@ -100,8 +100,14 @@ export class AutonomousAgent { // 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 toDelete = Array.from(this.processedMentions).slice(0, excess); - toDelete.forEach(id => this.processedMentions.delete(id)); + const iter = this.processedMentions.values(); + for (let i = 0; i < excess; i++) { + const { value, done } = iter.next(); + if (done) { + break; + } + this.processedMentions.delete(value); + } } } catch (error) { console.error('❌ Error in processing loop:', error); diff --git a/src/services/xapi.ts b/src/services/xapi.ts index 6fc3c67..6b86614 100644 --- a/src/services/xapi.ts +++ b/src/services/xapi.ts @@ -64,7 +64,7 @@ export class XAPIClient { const mentions = this.parseMentions(mentionsResponse.data); // Track the newest mention ID for pagination (API returns newest-first, so ID is at index 0) - if (mentionsResponse.data.length > 0 && mentionsResponse.data[0]?.id) { + if (mentionsResponse.data.length > 0 && typeof mentionsResponse.data[0]?.id === 'string') { this.lastMentionId = mentionsResponse.data[0].id; }