Skip to content

Commit 86937c5

Browse files
fix: consolidate actionable review feedback from PRs #4, #6, #13, #26
- Add Array.isArray guard in fetchThread (xapi.ts) - Use URLSearchParams for mentions URL construction (xapi.ts) - Add since_id pagination tracking for mentions (xapi.ts) - Add console.log→stderr redirect for MCP transport (index.ts) - Add memory pruning with safe iterator for processedMentions (agent.ts) - Process mentions oldest-first for chronological pruning (agent.ts) - Replace any with unknown/proper types (xapi.ts, grok.ts) - Fix CODEOWNERS invalid usernames to @groupthinking - Add permissions blocks to all workflow files - Add label existence check in issue-triage workflow Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com>
1 parent 183453f commit 86937c5

File tree

8 files changed

+99
-27
lines changed

8 files changed

+99
-27
lines changed

.github/CODEOWNERS

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# CODEOWNERS - Automatically assigns reviewers based on file paths
22

33
# Default owner for everything
4-
* @codex
4+
* @groupthinking
55

66
# Frontend
7-
*.tsx @Vercel
8-
*.jsx @Vercel
9-
*.css @Vercel
10-
/frontend/ @Vercel
7+
*.tsx @groupthinking
8+
*.jsx @groupthinking
9+
*.css @groupthinking
10+
/frontend/ @groupthinking
1111

1212
# Backend
1313
*.py @groupthinking
@@ -16,11 +16,11 @@
1616
/api/ @groupthinking
1717

1818
# Infrastructure
19-
/.github/ @Claude
20-
*.yml @Claude
21-
*.yaml @Claude
22-
Dockerfile @Claude
19+
/.github/ @groupthinking
20+
*.yml @groupthinking
21+
*.yaml @groupthinking
22+
Dockerfile @groupthinking
2323

2424
# Documentation
25-
*.md @Copilot
26-
/docs/ @Copilot
25+
*.md @groupthinking
26+
/docs/ @groupthinking

.github/workflows/auto-label.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ name: Auto Label
22
on:
33
pull_request:
44
types: [opened, reopened, synchronized]
5+
6+
permissions:
7+
contents: read
8+
pull-requests: write
9+
510
jobs:
611
label:
712
runs-on: ubuntu-latest

.github/workflows/issue-triage.yml

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ name: Issue Triage
22
on:
33
issues:
44
types: [opened]
5+
6+
permissions:
7+
issues: write
8+
59
jobs:
610
triage:
711
runs-on: ubuntu-latest
@@ -22,12 +26,23 @@ jobs:
2226
labels.push('needs-triage');
2327
2428
if (labels.length > 0) {
25-
await github.rest.issues.addLabels({
29+
// Fetch existing labels to avoid "Label does not exist" errors
30+
const { data: repoLabels } = await github.rest.issues.listLabelsForRepo({
2631
owner: context.repo.owner,
2732
repo: context.repo.repo,
28-
issue_number: issue.number,
29-
labels: labels
33+
per_page: 100,
3034
});
35+
const existingLabelNames = new Set(repoLabels.map(l => l.name));
36+
const labelsToAdd = labels.filter(l => existingLabelNames.has(l));
37+
38+
if (labelsToAdd.length > 0) {
39+
await github.rest.issues.addLabels({
40+
owner: context.repo.owner,
41+
repo: context.repo.repo,
42+
issue_number: issue.number,
43+
labels: labelsToAdd
44+
});
45+
}
3146
}
3247
3348
await github.rest.issues.createComment({

.github/workflows/pr-checks.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ name: PR Checks
22
on:
33
pull_request:
44
types: [opened, reopened, synchronize, edited]
5+
6+
permissions:
7+
contents: read
8+
pull-requests: write
9+
510
jobs:
611
validate:
712
runs-on: ubuntu-latest

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import { AutonomousAgent } from './services/agent.js';
88
import { XMCPServer } from './mcp/server.js';
99

1010
async function main() {
11+
// Redirect console.log to stderr so it doesn't conflict with
12+
// MCP StdioServerTransport which uses stdout for protocol messages
13+
console.log = (...args: unknown[]) => console.error(...args);
14+
1115
console.log('═══════════════════════════════════════════════════');
1216
console.log(' MyXstack - Autonomous AI Agent on X (Twitter)');
1317
console.log('═══════════════════════════════════════════════════\n');

src/services/agent.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export class AutonomousAgent {
1111
private grokService: GrokService;
1212
private config: AgentConfig;
1313
private processedMentions: Set<string> = new Set();
14+
private static readonly MAX_PROCESSED_MENTIONS = 10000;
1415
private isRunning: boolean = false;
1516
private pollingIntervalId: NodeJS.Timeout | null = null;
1617
private isProcessing: boolean = false;
@@ -90,10 +91,19 @@ export class AutonomousAgent {
9091

9192
console.log(`\n📬 [${new Date().toLocaleTimeString()}] Found ${newMentions.length} new mention(s)!\n`);
9293

93-
// Process each mention
94-
for (const mention of newMentions) {
95-
await this.processMention(mention);
96-
this.processedMentions.add(mention.post.id);
94+
// Process mentions oldest-first (API returns newest first) to maintain
95+
// chronological insertion order in the Set for proper pruning behavior
96+
for (let i = newMentions.length - 1; i >= 0; i--) {
97+
await this.processMention(newMentions[i]);
98+
this.processedMentions.add(newMentions[i].post.id);
99+
}
100+
101+
// Prune oldest entries (from beginning of Set) to prevent unbounded memory growth
102+
// Sets maintain insertion order, so slice(0, excess) removes oldest entries
103+
if (this.processedMentions.size > AutonomousAgent.MAX_PROCESSED_MENTIONS) {
104+
const excess = this.processedMentions.size - AutonomousAgent.MAX_PROCESSED_MENTIONS;
105+
const toDelete = Array.from(this.processedMentions).slice(0, excess);
106+
toDelete.forEach(id => this.processedMentions.delete(id));
97107
}
98108
} catch (error) {
99109
console.error('❌ Error in processing loop:', error);

src/services/grok.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44
*/
55
import { XThread, GrokAnalysis, AgentAction } from '../types/index.js';
66

7+
interface GrokApiResponse {
8+
choices: Array<{
9+
message?: {
10+
content?: string;
11+
};
12+
}>;
13+
}
14+
715
export class GrokService {
816
private apiKey: string;
917
private simulationMode: boolean = false;
@@ -59,7 +67,7 @@ export class GrokService {
5967
throw new Error(`Grok API error: ${response.status}`);
6068
}
6169

62-
const data: any = await response.json();
70+
const data = await response.json() as GrokApiResponse;
6371
const analysisText = data.choices[0]?.message?.content || '';
6472

6573
// Use the mention post ID so replies target the specific post where the agent was mentioned

src/services/xapi.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { XPost, XThread, Mention, XAPIConfig } from '../types/index.js';
66

77
export class XAPIClient {
88
private config: XAPIConfig;
9+
// Track the most recent mention ID to enable pagination (avoid re-fetching)
910
private lastMentionId: string | null = null;
1011
private simulationMode: boolean = false;
1112

@@ -44,17 +45,31 @@ export class XAPIClient {
4445
throw new Error('Failed to get user ID from response');
4546
}
4647

47-
const mentionsResponse = await this.makeXAPIRequest(
48-
`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`,
49-
'GET'
50-
);
48+
const params = new URLSearchParams({
49+
max_results: '10',
50+
expansions: 'author_id',
51+
'tweet.fields': 'created_at,conversation_id,in_reply_to_user_id,referenced_tweets',
52+
});
53+
if (this.lastMentionId) {
54+
params.set('since_id', this.lastMentionId);
55+
}
56+
const mentionsUrl = `https://api.twitter.com/2/users/${userId}/mentions?${params.toString()}`;
57+
58+
const mentionsResponse = await this.makeXAPIRequest(mentionsUrl, 'GET');
5159

5260
if (!mentionsResponse || !Array.isArray(mentionsResponse.data)) {
5361
console.warn('Invalid response from X API (mentions)');
5462
return [];
5563
}
5664

57-
return this.parseMentions(mentionsResponse.data);
65+
const mentions = this.parseMentions(mentionsResponse.data);
66+
67+
// Track the newest mention ID for pagination on the next poll
68+
if (mentionsResponse.data.length > 0) {
69+
this.lastMentionId = mentionsResponse.data[0].id;
70+
}
71+
72+
return mentions;
5873
} catch (error) {
5974
console.error('Error fetching mentions:', error);
6075
return [];
@@ -77,7 +92,17 @@ export class XAPIClient {
7792
'GET'
7893
);
7994

80-
return this.parseThread(response.data || []);
95+
if (!response || !response.data) {
96+
console.warn('Invalid response from X API (thread)');
97+
return null;
98+
}
99+
100+
if (!Array.isArray(response.data)) {
101+
console.warn('Unexpected response shape from X API (thread): data is not an array');
102+
return null;
103+
}
104+
105+
return this.parseThread(response.data);
81106
} catch (error) {
82107
console.error('Error fetching thread:', error);
83108
return null;
@@ -181,10 +206,10 @@ export class XAPIClient {
181206
};
182207
}
183208

184-
private parseThread(tweets: { created_at: string; [key: string]: any }[]): XThread | null {
209+
private parseThread(tweets: { created_at: string; [key: string]: unknown }[]): XThread | null {
185210
if (tweets.length === 0) return null;
186211

187-
const sorted = tweets.sort((a, b) =>
212+
const sorted = [...tweets].sort((a, b) =>
188213
new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
189214
);
190215

0 commit comments

Comments
 (0)