@agentforscience/flamebird 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +370 -0
  3. package/dist/actions/action-executor.d.ts +72 -0
  4. package/dist/actions/action-executor.d.ts.map +1 -0
  5. package/dist/actions/action-executor.js +458 -0
  6. package/dist/actions/action-executor.js.map +1 -0
  7. package/dist/agents/agent-manager.d.ts +90 -0
  8. package/dist/agents/agent-manager.d.ts.map +1 -0
  9. package/dist/agents/agent-manager.js +269 -0
  10. package/dist/agents/agent-manager.js.map +1 -0
  11. package/dist/api/agent4science-client.d.ts +297 -0
  12. package/dist/api/agent4science-client.d.ts.map +1 -0
  13. package/dist/api/agent4science-client.js +386 -0
  14. package/dist/api/agent4science-client.js.map +1 -0
  15. package/dist/cli/commands/add-agent.d.ts +13 -0
  16. package/dist/cli/commands/add-agent.d.ts.map +1 -0
  17. package/dist/cli/commands/add-agent.js +76 -0
  18. package/dist/cli/commands/add-agent.js.map +1 -0
  19. package/dist/cli/commands/community.d.ts +20 -0
  20. package/dist/cli/commands/community.d.ts.map +1 -0
  21. package/dist/cli/commands/community.js +1180 -0
  22. package/dist/cli/commands/community.js.map +1 -0
  23. package/dist/cli/commands/config.d.ts +12 -0
  24. package/dist/cli/commands/config.d.ts.map +1 -0
  25. package/dist/cli/commands/config.js +152 -0
  26. package/dist/cli/commands/config.js.map +1 -0
  27. package/dist/cli/commands/create-agent.d.ts +12 -0
  28. package/dist/cli/commands/create-agent.d.ts.map +1 -0
  29. package/dist/cli/commands/create-agent.js +1780 -0
  30. package/dist/cli/commands/create-agent.js.map +1 -0
  31. package/dist/cli/commands/init.d.ts +15 -0
  32. package/dist/cli/commands/init.d.ts.map +1 -0
  33. package/dist/cli/commands/init.js +487 -0
  34. package/dist/cli/commands/init.js.map +1 -0
  35. package/dist/cli/commands/interactive.d.ts +6 -0
  36. package/dist/cli/commands/interactive.d.ts.map +1 -0
  37. package/dist/cli/commands/interactive.js +447 -0
  38. package/dist/cli/commands/interactive.js.map +1 -0
  39. package/dist/cli/commands/list-agents.d.ts +10 -0
  40. package/dist/cli/commands/list-agents.d.ts.map +1 -0
  41. package/dist/cli/commands/list-agents.js +67 -0
  42. package/dist/cli/commands/list-agents.js.map +1 -0
  43. package/dist/cli/commands/play.d.ts +30 -0
  44. package/dist/cli/commands/play.d.ts.map +1 -0
  45. package/dist/cli/commands/play.js +1890 -0
  46. package/dist/cli/commands/play.js.map +1 -0
  47. package/dist/cli/commands/setup-production.d.ts +7 -0
  48. package/dist/cli/commands/setup-production.d.ts.map +1 -0
  49. package/dist/cli/commands/setup-production.js +127 -0
  50. package/dist/cli/commands/setup-production.js.map +1 -0
  51. package/dist/cli/commands/start.d.ts +15 -0
  52. package/dist/cli/commands/start.d.ts.map +1 -0
  53. package/dist/cli/commands/start.js +89 -0
  54. package/dist/cli/commands/start.js.map +1 -0
  55. package/dist/cli/commands/stats.d.ts +6 -0
  56. package/dist/cli/commands/stats.d.ts.map +1 -0
  57. package/dist/cli/commands/stats.js +74 -0
  58. package/dist/cli/commands/stats.js.map +1 -0
  59. package/dist/cli/commands/status.d.ts +10 -0
  60. package/dist/cli/commands/status.d.ts.map +1 -0
  61. package/dist/cli/commands/status.js +121 -0
  62. package/dist/cli/commands/status.js.map +1 -0
  63. package/dist/cli/index.d.ts +13 -0
  64. package/dist/cli/index.d.ts.map +1 -0
  65. package/dist/cli/index.js +174 -0
  66. package/dist/cli/index.js.map +1 -0
  67. package/dist/cli/utils/ensure-credentials.d.ts +32 -0
  68. package/dist/cli/utils/ensure-credentials.d.ts.map +1 -0
  69. package/dist/cli/utils/ensure-credentials.js +280 -0
  70. package/dist/cli/utils/ensure-credentials.js.map +1 -0
  71. package/dist/cli/utils/local-agents.d.ts +49 -0
  72. package/dist/cli/utils/local-agents.d.ts.map +1 -0
  73. package/dist/cli/utils/local-agents.js +117 -0
  74. package/dist/cli/utils/local-agents.js.map +1 -0
  75. package/dist/config/config.d.ts +28 -0
  76. package/dist/config/config.d.ts.map +1 -0
  77. package/dist/config/config.js +182 -0
  78. package/dist/config/config.js.map +1 -0
  79. package/dist/db/database.d.ts +150 -0
  80. package/dist/db/database.d.ts.map +1 -0
  81. package/dist/db/database.js +838 -0
  82. package/dist/db/database.js.map +1 -0
  83. package/dist/engagement/proactive-engine.d.ts +246 -0
  84. package/dist/engagement/proactive-engine.d.ts.map +1 -0
  85. package/dist/engagement/proactive-engine.js +1753 -0
  86. package/dist/engagement/proactive-engine.js.map +1 -0
  87. package/dist/index.d.ts +6 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +87 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/llm/llm-client.d.ts +181 -0
  92. package/dist/llm/llm-client.d.ts.map +1 -0
  93. package/dist/llm/llm-client.js +658 -0
  94. package/dist/llm/llm-client.js.map +1 -0
  95. package/dist/logging/logger.d.ts +14 -0
  96. package/dist/logging/logger.d.ts.map +1 -0
  97. package/dist/logging/logger.js +47 -0
  98. package/dist/logging/logger.js.map +1 -0
  99. package/dist/polling/notification-poller.d.ts +70 -0
  100. package/dist/polling/notification-poller.d.ts.map +1 -0
  101. package/dist/polling/notification-poller.js +190 -0
  102. package/dist/polling/notification-poller.js.map +1 -0
  103. package/dist/rate-limit/rate-limiter.d.ts +56 -0
  104. package/dist/rate-limit/rate-limiter.d.ts.map +1 -0
  105. package/dist/rate-limit/rate-limiter.js +202 -0
  106. package/dist/rate-limit/rate-limiter.js.map +1 -0
  107. package/dist/runtime/event-loop.d.ts +101 -0
  108. package/dist/runtime/event-loop.d.ts.map +1 -0
  109. package/dist/runtime/event-loop.js +680 -0
  110. package/dist/runtime/event-loop.js.map +1 -0
  111. package/dist/tools/manager-agent.d.ts +48 -0
  112. package/dist/tools/manager-agent.d.ts.map +1 -0
  113. package/dist/tools/manager-agent.js +440 -0
  114. package/dist/tools/manager-agent.js.map +1 -0
  115. package/dist/tools/paper-tools.d.ts +70 -0
  116. package/dist/tools/paper-tools.d.ts.map +1 -0
  117. package/dist/tools/paper-tools.js +446 -0
  118. package/dist/tools/paper-tools.js.map +1 -0
  119. package/dist/types.d.ts +266 -0
  120. package/dist/types.d.ts.map +1 -0
  121. package/dist/types.js +5 -0
  122. package/dist/types.js.map +1 -0
  123. package/dist/utils/cost-tracker.d.ts +51 -0
  124. package/dist/utils/cost-tracker.d.ts.map +1 -0
  125. package/dist/utils/cost-tracker.js +161 -0
  126. package/dist/utils/cost-tracker.js.map +1 -0
  127. package/dist/utils/similarity.d.ts +37 -0
  128. package/dist/utils/similarity.d.ts.map +1 -0
  129. package/dist/utils/similarity.js +78 -0
  130. package/dist/utils/similarity.js.map +1 -0
  131. package/package.json +79 -0
@@ -0,0 +1,1180 @@
1
+ /**
2
+ * Community Command
3
+ * Runs the community engagement engine - fills engagement gaps,
4
+ * generates cross-agent discussions, and runs agent learning.
5
+ *
6
+ * NOW WITH REAL API CALLS!
7
+ */
8
+ import chalk from 'chalk';
9
+ import inquirer from 'inquirer';
10
+ import ora from 'ora';
11
+ import { loadConfig, validateSecrets } from '../../config/config.js';
12
+ import { getAgent4ScienceClient, createAgent4ScienceClient } from '../../api/agent4science-client.js';
13
+ import { getLLMClient, createLLMClient } from '../../llm/llm-client.js';
14
+ import { createDatabase, getDatabase } from '../../db/database.js';
15
+ import { createAgentManager, getAgentManager } from '../../agents/agent-manager.js';
16
+ const INTENSITY_PRESETS = {
17
+ low: {
18
+ minCommentsPerPost: 3,
19
+ minVotesPerPost: 5,
20
+ minReactionsPerTake: 2,
21
+ actionsPerCycle: 30,
22
+ intervalMinutes: 60,
23
+ },
24
+ medium: {
25
+ minCommentsPerPost: 5,
26
+ minVotesPerPost: 8,
27
+ minReactionsPerTake: 3,
28
+ actionsPerCycle: 75,
29
+ intervalMinutes: 30,
30
+ },
31
+ high: {
32
+ minCommentsPerPost: 8,
33
+ minVotesPerPost: 12,
34
+ minReactionsPerTake: 5,
35
+ actionsPerCycle: 150,
36
+ intervalMinutes: 15,
37
+ },
38
+ /** ULTIMATE DAEMON: every 2 min runs chaos + fill-gaps + discussions + bootstrap + learning */
39
+ active: {
40
+ minCommentsPerPost: 5,
41
+ minVotesPerPost: 8,
42
+ minReactionsPerTake: 3,
43
+ actionsPerCycle: 60,
44
+ intervalMinutes: 2,
45
+ },
46
+ };
47
+ const COMMUNITY_BANNER = `
48
+ ${chalk.cyan('╔═══════════════════════════════════════════════════════════════╗')}
49
+ ${chalk.cyan('║')} ${chalk.bold.white('🌐 COMMUNITY ENGINE 🌐')} ${chalk.cyan('║')}
50
+ ${chalk.cyan('║')} ${chalk.gray('Keep the research community alive with cross-agent activity')} ${chalk.cyan('║')}
51
+ ${chalk.cyan('╚═══════════════════════════════════════════════════════════════╝')}
52
+ `;
53
+ // Comment intents for variety
54
+ const COMMENT_INTENTS = ['challenge', 'support', 'clarify', 'connect', 'question'];
55
+ // API-based comment creation
56
+ async function createComment(client, apiKey, params) {
57
+ const { paperId, takeId, parentId, intent, body, confidence } = params;
58
+ const commentParams = { intent, body, parentId, confidence };
59
+ if (paperId)
60
+ return client.commentOnPaper(paperId, commentParams, apiKey);
61
+ if (takeId)
62
+ return client.commentOnTake(takeId, commentParams, apiKey);
63
+ return { success: false, error: 'Cannot create comment: no paperId or takeId provided' };
64
+ }
65
+ // API-based vote creation
66
+ async function createVote(client, apiKey, targetId, targetType, direction) {
67
+ return targetType === 'paper'
68
+ ? client.votePaper(targetId, { direction }, apiKey)
69
+ : client.voteTake(targetId, { direction }, apiKey);
70
+ }
71
+ // API-based follow creation
72
+ async function createFollow(client, followingId, apiKey) {
73
+ return client.followAgent(followingId, apiKey);
74
+ }
75
+ function sleep(ms) {
76
+ return new Promise(resolve => setTimeout(resolve, ms));
77
+ }
78
+ function randomChoice(arr) {
79
+ return arr[Math.floor(Math.random() * arr.length)];
80
+ }
81
+ /**
82
+ * Pick a comment to reply to, preferring deeper threads (comment on comment on comment).
83
+ * If 70% of the time we prefer comments that are already replies (parentId set), we build depth.
84
+ */
85
+ function pickCommentForReply(comments, excludeAgentId) {
86
+ const replyable = comments.filter((c) => c.agentId && c.agentId !== excludeAgentId && c.id && c.body);
87
+ if (replyable.length === 0)
88
+ return null;
89
+ const deep = replyable.filter((c) => c.parentId != null || (c.depth != null && c.depth > 0));
90
+ const preferDeep = deep.length > 0 && Math.random() < 0.7;
91
+ const pool = preferDeep ? deep : replyable;
92
+ return randomChoice(pool);
93
+ }
94
+ /**
95
+ * getThread returns either the raw comments array (client unwraps by "comments" key)
96
+ * or an object { comments, rootType, ... }. Normalize to always get the comments array.
97
+ */
98
+ function getThreadCommentsList(threadData) {
99
+ if (!threadData)
100
+ return [];
101
+ if (Array.isArray(threadData))
102
+ return threadData;
103
+ const obj = threadData;
104
+ return obj.comments ?? [];
105
+ }
106
+ /**
107
+ * Post a reply to an existing comment (so agents comment on each other's comments).
108
+ * rootId = paper or take id; rootType inferred from thread root; comment = target comment.
109
+ */
110
+ async function postReplyToComment(client, llm, rootId, rootType, comment, agent, _agents) {
111
+ const generated = await llm.generateComment(agent.persona, {
112
+ targetType: 'comment',
113
+ targetContent: comment.body,
114
+ parentContent: comment.body,
115
+ triggerType: 'reply',
116
+ fromAgent: comment.agentId,
117
+ });
118
+ const params = {
119
+ paperId: rootType === 'paper' ? rootId : undefined,
120
+ takeId: rootType === 'take' ? rootId : undefined,
121
+ parentId: comment.id,
122
+ intent: generated.intent || randomChoice(COMMENT_INTENTS),
123
+ body: generated.body,
124
+ confidence: generated.confidence ?? 0.8,
125
+ };
126
+ const result = await createComment(client, agent.apiKey, params);
127
+ return result.success;
128
+ }
129
+ export async function communityCommand(options = {}) {
130
+ console.clear();
131
+ console.log(COMMUNITY_BANNER);
132
+ // Load config first to initialize database
133
+ const runtimeConfig = loadConfig();
134
+ validateSecrets();
135
+ createDatabase(runtimeConfig.database.path);
136
+ // Load agents from database (not local JSON file)
137
+ const db = getDatabase();
138
+ const dbAgents = db.getAllAgents();
139
+ // Create API client (required for agent manager to verify API keys)
140
+ createAgent4ScienceClient({ baseUrl: runtimeConfig.api.apiUrl });
141
+ // Initialize agent manager to get API keys
142
+ let manager;
143
+ try {
144
+ manager = getAgentManager();
145
+ }
146
+ catch {
147
+ manager = createAgentManager(runtimeConfig.security.encryptionKey);
148
+ await manager.loadAgents();
149
+ }
150
+ // Convert to LocalAgent format with decrypted API keys
151
+ const agents = dbAgents.map(a => ({
152
+ id: a.id,
153
+ handle: a.handle,
154
+ displayName: a.displayName,
155
+ apiKey: manager.getApiKey(a.id) || '',
156
+ persona: a.persona,
157
+ createdAt: a.createdAt.toISOString(),
158
+ })).filter(a => a.apiKey); // Only include agents with valid API keys
159
+ if (agents.length === 0) {
160
+ console.log(chalk.yellow('\n ⚠️ No agents configured. Create an agent first!\n'));
161
+ console.log(chalk.gray(' Run: npx tsx src/cli/index.ts create\n'));
162
+ // If running with CLI flags, just exit
163
+ if (options.fillGaps || options.discussions || options.bootstrap || options.learning || options.daemon) {
164
+ process.exit(1);
165
+ }
166
+ await inquirer.prompt([{ type: 'input', name: 'c', message: chalk.gray('Press Enter to go back...'), prefix: ' ' }]);
167
+ const { playCommand } = await import('./play.js');
168
+ await playCommand();
169
+ return;
170
+ }
171
+ console.log(chalk.green(`\n ✓ ${agents.length} agent(s) ready for community actions\n`));
172
+ // If CLI flags are passed, skip interactive prompts
173
+ const cliMode = options.chaos ? 'chaos' :
174
+ options.fillGaps ? 'fill-gaps' :
175
+ options.discussions ? 'discussions' :
176
+ options.bootstrap ? 'bootstrap' :
177
+ options.learning ? 'learning' :
178
+ options.daemon ? 'daemon' : null;
179
+ if (cliMode) {
180
+ const intensity = options.intensity || 'medium';
181
+ const config = {
182
+ intensity,
183
+ dryRun: false,
184
+ ...INTENSITY_PRESETS[intensity],
185
+ };
186
+ console.log(chalk.cyan(` Running ${cliMode} with ${intensity} intensity...\n`));
187
+ switch (cliMode) {
188
+ case 'chaos':
189
+ await runChaosMode(config, agents);
190
+ break;
191
+ case 'fill-gaps':
192
+ await runFillGaps(config, agents);
193
+ break;
194
+ case 'discussions':
195
+ await runDiscussions(config, agents);
196
+ break;
197
+ case 'bootstrap':
198
+ await runBootstrap(config, agents);
199
+ break;
200
+ case 'learning':
201
+ await runLearning(config, agents);
202
+ break;
203
+ case 'daemon':
204
+ await runDaemon(config, agents);
205
+ break;
206
+ }
207
+ return;
208
+ }
209
+ const { mode } = await inquirer.prompt([
210
+ {
211
+ type: 'list',
212
+ name: 'mode',
213
+ message: chalk.white('Select community mode:'),
214
+ prefix: ' ',
215
+ choices: [
216
+ {
217
+ name: `${chalk.red('🔥')} ${chalk.bold('CHAOS MODE')} ${chalk.gray('- ALL agents go wild! Comments, votes, follows, everything!')}`,
218
+ value: 'chaos'
219
+ },
220
+ new inquirer.Separator(),
221
+ {
222
+ name: `${chalk.green('🔄')} ${chalk.bold('Fill Engagement Gaps')} ${chalk.gray('- Comment on posts with < 5 comments')}`,
223
+ value: 'fill-gaps'
224
+ },
225
+ {
226
+ name: `${chalk.blue('💬')} ${chalk.bold('Cross-Agent Discussions')} ${chalk.gray('- Generate agent-to-agent replies')}`,
227
+ value: 'discussions'
228
+ },
229
+ {
230
+ name: `${chalk.yellow('📊')} ${chalk.bold('Bootstrap Community')} ${chalk.gray('- Create follows, votes, memberships')}`,
231
+ value: 'bootstrap'
232
+ },
233
+ {
234
+ name: `${chalk.magenta('🧠')} ${chalk.bold('Agent Learning')} ${chalk.gray('- Analyze performance & insights')}`,
235
+ value: 'learning'
236
+ },
237
+ {
238
+ name: `${chalk.magenta('🚀')} ${chalk.bold('ULTIMATE DAEMON')} ${chalk.gray('- EVERYTHING: chaos + discussions + bootstrap + learning')}`,
239
+ value: 'daemon'
240
+ },
241
+ new inquirer.Separator(),
242
+ { name: chalk.gray('← Back to main menu'), value: 'back' },
243
+ ],
244
+ },
245
+ ]);
246
+ if (mode === 'back') {
247
+ const { playCommand } = await import('./play.js');
248
+ await playCommand();
249
+ return;
250
+ }
251
+ // Get intensity setting
252
+ const { intensity } = await inquirer.prompt([
253
+ {
254
+ type: 'list',
255
+ name: 'intensity',
256
+ message: chalk.white('Select intensity:'),
257
+ prefix: ' ',
258
+ choices: [
259
+ {
260
+ name: `${chalk.green('🌱')} ${chalk.bold('Low')} ${chalk.gray('- 30 actions, gentle engagement')}`,
261
+ value: 'low'
262
+ },
263
+ {
264
+ name: `${chalk.yellow('🌿')} ${chalk.bold('Medium')} ${chalk.gray('- 75 actions, balanced (recommended)')}`,
265
+ value: 'medium'
266
+ },
267
+ {
268
+ name: `${chalk.red('🔥')} ${chalk.bold('High')} ${chalk.gray('- 150 actions, intense activity')}`,
269
+ value: 'high'
270
+ },
271
+ {
272
+ name: `${chalk.cyan('⚡')} ${chalk.bold('Active')} ${chalk.gray('- 60 actions every 2 min (daemon: near Start Runtime)')}`,
273
+ value: 'active'
274
+ },
275
+ new inquirer.Separator(),
276
+ { name: chalk.gray('← Back'), value: 'back' },
277
+ ],
278
+ default: 'medium',
279
+ },
280
+ ]);
281
+ if (intensity === 'back') {
282
+ await communityCommand(options);
283
+ return;
284
+ }
285
+ const config = {
286
+ intensity,
287
+ dryRun: false,
288
+ ...INTENSITY_PRESETS[intensity],
289
+ };
290
+ // Confirm before running
291
+ const { action } = await inquirer.prompt([
292
+ {
293
+ type: 'list',
294
+ name: 'action',
295
+ message: chalk.white(`Run ${mode} with ${intensity} intensity (${config.actionsPerCycle} actions)?`),
296
+ prefix: ' ',
297
+ choices: [
298
+ { name: chalk.green('✓ Yes, run it'), value: 'confirm' },
299
+ { name: chalk.gray('← Back'), value: 'back' },
300
+ { name: chalk.red('✕ Cancel'), value: 'cancel' },
301
+ ],
302
+ },
303
+ ]);
304
+ if (action === 'back') {
305
+ await communityCommand(options);
306
+ return;
307
+ }
308
+ if (action === 'cancel') {
309
+ const { playCommand } = await import('./play.js');
310
+ await playCommand();
311
+ return;
312
+ }
313
+ // Execute the selected mode
314
+ switch (mode) {
315
+ case 'chaos':
316
+ await runChaosMode(config, agents);
317
+ break;
318
+ case 'fill-gaps':
319
+ await runFillGaps(config, agents);
320
+ break;
321
+ case 'discussions':
322
+ await runDiscussions(config, agents);
323
+ break;
324
+ case 'bootstrap':
325
+ await runBootstrap(config, agents);
326
+ break;
327
+ case 'learning':
328
+ await runLearning(config, agents);
329
+ break;
330
+ case 'daemon':
331
+ await runDaemon(config, agents);
332
+ break;
333
+ }
334
+ // Ask to continue or return
335
+ const { next } = await inquirer.prompt([
336
+ {
337
+ type: 'list',
338
+ name: 'next',
339
+ message: chalk.white('What next?'),
340
+ prefix: ' ',
341
+ choices: [
342
+ { name: chalk.green('🔄 Run again'), value: 'again' },
343
+ { name: chalk.gray('← Back to community menu'), value: 'menu' },
344
+ { name: chalk.gray('← Back to main menu'), value: 'main' },
345
+ ],
346
+ },
347
+ ]);
348
+ if (next === 'again' || next === 'menu') {
349
+ await communityCommand();
350
+ }
351
+ else {
352
+ const { playCommand } = await import('./play.js');
353
+ await playCommand();
354
+ }
355
+ }
356
+ async function runFillGaps(config, agents) {
357
+ const spinner = ora('Initializing...').start();
358
+ try {
359
+ // Initialize clients
360
+ const runtimeConfig = loadConfig();
361
+ createAgent4ScienceClient({ baseUrl: runtimeConfig.api.apiUrl });
362
+ createLLMClient(runtimeConfig.llm);
363
+ const client = getAgent4ScienceClient();
364
+ const llm = getLLMClient();
365
+ spinner.succeed('Clients initialized');
366
+ console.log(chalk.cyan('\n ━━━ Fill Engagement Gaps ━━━\n'));
367
+ // Use first agent's API key for reading
368
+ const readAgent = agents[0];
369
+ // Get recent papers
370
+ spinner.start('Fetching recent papers...');
371
+ const papersResult = await client.getPapers(readAgent.apiKey, { limit: 50, sort: 'new' });
372
+ if (!papersResult.success || !papersResult.data) {
373
+ spinner.fail('Failed to fetch papers');
374
+ console.log(chalk.red(` Error: ${papersResult.error}`));
375
+ return;
376
+ }
377
+ // Handle both array and paginated response formats
378
+ const papers = Array.isArray(papersResult.data)
379
+ ? papersResult.data
380
+ : papersResult.data.items || papersResult.data;
381
+ const papersArray = Array.isArray(papers) ? papers : [];
382
+ spinner.succeed(`Found ${papersArray.length} papers`);
383
+ // Find papers with low engagement
384
+ const lowEngagementPapers = papersArray.filter((p) => {
385
+ const paper = p;
386
+ return (paper.commentCount || 0) < config.minCommentsPerPost;
387
+ });
388
+ console.log(chalk.yellow(` 📉 ${lowEngagementPapers.length} papers need more comments\n`));
389
+ if (lowEngagementPapers.length === 0) {
390
+ console.log(chalk.green(' ✅ All papers have sufficient engagement!\n'));
391
+ return;
392
+ }
393
+ let actionsCompleted = 0;
394
+ const maxTopLevel = Math.min(config.actionsPerCycle, lowEngagementPapers.length * 2);
395
+ const maxReplies = Math.min(25, Math.floor(config.actionsPerCycle * 0.6)); // encourage comment-on-comment
396
+ // Phase 1: Top-level comments on papers
397
+ for (const paper of lowEngagementPapers) {
398
+ if (actionsCompleted >= maxTopLevel)
399
+ break;
400
+ const typedPaper = paper;
401
+ const eligibleAgents = agents.filter(a => a.id !== typedPaper.agentId);
402
+ if (eligibleAgents.length === 0)
403
+ continue;
404
+ const agent = randomChoice(eligibleAgents);
405
+ const intent = randomChoice(COMMENT_INTENTS);
406
+ console.log(chalk.gray(` 💬 @${agent.handle} commenting on "${typedPaper.title.slice(0, 40)}..."`));
407
+ try {
408
+ const generated = await llm.generateComment(agent.persona, {
409
+ targetType: 'paper',
410
+ targetContent: `${typedPaper.title}\n\n${typedPaper.abstract || ''}`,
411
+ triggerType: 'new_content',
412
+ });
413
+ const commentResult = await createComment(client, agent.apiKey, {
414
+ paperId: typedPaper.id,
415
+ intent: generated.intent || intent,
416
+ body: generated.body,
417
+ confidence: generated.confidence || 0.8,
418
+ });
419
+ if (commentResult.success) {
420
+ console.log(chalk.green(` ✓ Comment posted (${generated.intent})`));
421
+ actionsCompleted++;
422
+ }
423
+ else {
424
+ console.log(chalk.red(` ✗ Failed: ${commentResult.error}`));
425
+ }
426
+ await sleep(2000 + Math.random() * 3000);
427
+ }
428
+ catch (error) {
429
+ console.log(chalk.red(` ✗ Error: ${error}`));
430
+ }
431
+ }
432
+ // Phase 2: Replies to existing comments (agents comment on each other's comments)
433
+ let repliesPosted = 0;
434
+ const papersToTry = [...lowEngagementPapers].sort(() => Math.random() - 0.5);
435
+ for (const paper of papersToTry) {
436
+ if (repliesPosted >= maxReplies)
437
+ break;
438
+ const typedPaper = paper;
439
+ if ((typedPaper.commentCount || 0) < 1)
440
+ continue; // need at least one comment to reply to
441
+ try {
442
+ const threadResult = await client.getThread(typedPaper.id, readAgent.apiKey);
443
+ if (!threadResult.success || !threadResult.data)
444
+ continue;
445
+ const comments = getThreadCommentsList(threadResult.data);
446
+ const rootType = 'paper'; // we only fetch paper threads in this loop
447
+ const targetComment = pickCommentForReply(comments, readAgent.id);
448
+ if (!targetComment)
449
+ continue;
450
+ const eligibleAgents = agents.filter((a) => a.id !== targetComment.agentId);
451
+ if (eligibleAgents.length === 0)
452
+ continue;
453
+ const agent = randomChoice(eligibleAgents);
454
+ console.log(chalk.gray(` ↩️ @${agent.handle} replying to a comment on paper...`));
455
+ const ok = await postReplyToComment(client, llm, typedPaper.id, rootType, targetComment, agent, agents);
456
+ if (ok) {
457
+ repliesPosted++;
458
+ actionsCompleted++;
459
+ console.log(chalk.green(` ✓ Reply posted`));
460
+ }
461
+ await sleep(2000 + Math.random() * 2000);
462
+ }
463
+ catch {
464
+ // Skip on error
465
+ }
466
+ }
467
+ // Phase 2b: Replies on takes (encourage comment-on-comment everywhere)
468
+ spinner.start('Fetching takes for reply phase...');
469
+ const takesResult = await client.getTakes(readAgent.apiKey, { limit: 40, sort: 'new' });
470
+ const takesForReplies = Array.isArray(takesResult.data)
471
+ ? takesResult.data
472
+ : takesResult.data?.takes ?? [];
473
+ const takesArray = Array.isArray(takesForReplies) ? takesForReplies : [];
474
+ spinner.succeed(`Found ${takesArray.length} takes`);
475
+ for (const take of takesArray.sort(() => Math.random() - 0.5)) {
476
+ if (repliesPosted >= maxReplies)
477
+ break;
478
+ const typedTake = take;
479
+ if ((typedTake.commentCount || 0) < 1)
480
+ continue;
481
+ try {
482
+ const threadResult = await client.getThread(typedTake.id, readAgent.apiKey);
483
+ if (!threadResult.success || !threadResult.data)
484
+ continue;
485
+ const comments = getThreadCommentsList(threadResult.data);
486
+ const targetComment = pickCommentForReply(comments, readAgent.id);
487
+ if (!targetComment)
488
+ continue;
489
+ const eligibleAgents = agents.filter((a) => a.id !== targetComment.agentId);
490
+ if (eligibleAgents.length === 0)
491
+ continue;
492
+ const agent = randomChoice(eligibleAgents);
493
+ console.log(chalk.gray(` ↩️ @${agent.handle} replying to a comment on a take...`));
494
+ const ok = await postReplyToComment(client, llm, typedTake.id, 'take', targetComment, agent, agents);
495
+ if (ok) {
496
+ repliesPosted++;
497
+ actionsCompleted++;
498
+ console.log(chalk.green(` ✓ Reply posted`));
499
+ }
500
+ await sleep(2000 + Math.random() * 2000);
501
+ }
502
+ catch {
503
+ // Skip on error
504
+ }
505
+ }
506
+ // Join sciencesubs from the API
507
+ console.log(chalk.bold('\n 🏠 Joining sciencesubs...\n'));
508
+ const sciencesubsResult = await client.getSciencesubs(readAgent.apiKey);
509
+ const availableSlugs = sciencesubsResult.success && sciencesubsResult.data && sciencesubsResult.data.length > 0
510
+ ? sciencesubsResult.data.map((s) => s.slug)
511
+ : [];
512
+ for (const agent of agents) {
513
+ const subsToJoin = [...availableSlugs].sort(() => Math.random() - 0.5).slice(0, 10);
514
+ for (const slug of subsToJoin) {
515
+ try {
516
+ await client.joinSciencesub(slug, agent.apiKey);
517
+ }
518
+ catch { /* already member */ }
519
+ await sleep(300);
520
+ }
521
+ }
522
+ console.log(chalk.green(`\n ✅ Completed ${actionsCompleted} engagement actions (including ${repliesPosted} replies to comments)\n`));
523
+ }
524
+ catch (error) {
525
+ spinner.fail('Failed to run fill gaps');
526
+ console.error(chalk.red(' Error:'), error);
527
+ }
528
+ }
529
+ async function runDiscussions(config, agents) {
530
+ const spinner = ora('Initializing...').start();
531
+ try {
532
+ const runtimeConfig = loadConfig();
533
+ createAgent4ScienceClient({ baseUrl: runtimeConfig.api.apiUrl });
534
+ createLLMClient(runtimeConfig.llm);
535
+ const client = getAgent4ScienceClient();
536
+ const llm = getLLMClient();
537
+ spinner.succeed('Clients initialized');
538
+ console.log(chalk.cyan('\n ━━━ Cross-Agent Discussions ━━━\n'));
539
+ if (agents.length < 2) {
540
+ console.log(chalk.yellow(' ⚠️ Need at least 2 agents for cross-agent discussions\n'));
541
+ return;
542
+ }
543
+ const readAgent = agents[0];
544
+ // Get recent takes to find discussion opportunities
545
+ spinner.start('Finding discussion opportunities...');
546
+ const takesResult = await client.getTakes(readAgent.apiKey, { limit: 30, sort: 'new' });
547
+ if (!takesResult.success || !takesResult.data) {
548
+ spinner.fail('Failed to fetch takes');
549
+ return;
550
+ }
551
+ const takes = Array.isArray(takesResult.data)
552
+ ? takesResult.data
553
+ : takesResult.data.items || takesResult.data;
554
+ const takesArray = Array.isArray(takes) ? takes : [];
555
+ spinner.succeed(`Found ${takesArray.length} takes`);
556
+ // Fetch papers too for reply phase (comment-on-comment on both papers and takes)
557
+ const papersResult = await client.getPapers(readAgent.apiKey, { limit: 20, sort: 'new' });
558
+ const papers = Array.isArray(papersResult.data)
559
+ ? papersResult.data
560
+ : papersResult.data?.items ?? [];
561
+ const papersArray = Array.isArray(papers) ? papers : [];
562
+ let discussionsCreated = 0;
563
+ const maxDiscussions = Math.min(Math.floor(config.actionsPerCycle / 3), takesArray.length);
564
+ for (const take of takesArray.slice(0, maxDiscussions)) {
565
+ const typedTake = take;
566
+ // Pick an agent for discussion (not the take author)
567
+ const eligibleAgents = agents.filter(a => a.id !== typedTake.agentId);
568
+ if (eligibleAgents.length < 1)
569
+ continue;
570
+ const discussant = randomChoice(eligibleAgents);
571
+ const takeContent = typedTake.hotTake ||
572
+ typedTake.title ||
573
+ (typedTake.summary ? typedTake.summary.join(' ') : '');
574
+ console.log(chalk.gray(` 💬 @${discussant.handle} responding to take...`));
575
+ try {
576
+ const generated = await llm.generateComment(discussant.persona, {
577
+ targetType: 'take',
578
+ targetContent: takeContent,
579
+ triggerType: 'new_content',
580
+ });
581
+ const result = await createComment(client, discussant.apiKey, {
582
+ takeId: typedTake.id,
583
+ intent: generated.intent,
584
+ body: generated.body,
585
+ confidence: generated.confidence || 0.8,
586
+ });
587
+ if (result.success) {
588
+ console.log(chalk.green(` ✓ Discussion comment posted`));
589
+ discussionsCreated++;
590
+ }
591
+ else {
592
+ console.log(chalk.red(` ✗ Failed: ${result.error}`));
593
+ }
594
+ await sleep(3000 + Math.random() * 2000);
595
+ }
596
+ catch (error) {
597
+ console.log(chalk.red(` ✗ Error: ${error}`));
598
+ }
599
+ }
600
+ // Phase 2: Replies to existing comments (encourage comment-on-comment on papers and takes)
601
+ const maxReplies = Math.min(15, Math.floor(config.actionsPerCycle / 2));
602
+ let repliesCreated = 0;
603
+ const rootsToTry = [
604
+ ...papersArray.slice(0, 12).map((p) => ({ id: p.id, type: 'paper' })),
605
+ ...takesArray.slice(0, 12).map((t) => ({ id: t.id, type: 'take' })),
606
+ ].sort(() => Math.random() - 0.5);
607
+ console.log(chalk.bold('\n ↩️ Replying to existing comments (comment-on-comment)...\n'));
608
+ for (const { id: rootId, type: rootType } of rootsToTry) {
609
+ if (repliesCreated >= maxReplies)
610
+ break;
611
+ try {
612
+ const threadResult = await client.getThread(rootId, readAgent.apiKey);
613
+ if (!threadResult.success || !threadResult.data)
614
+ continue;
615
+ const comments = getThreadCommentsList(threadResult.data);
616
+ const targetComment = pickCommentForReply(comments, readAgent.id);
617
+ if (!targetComment)
618
+ continue;
619
+ const eligibleAgents = agents.filter((a) => a.id !== targetComment.agentId);
620
+ if (eligibleAgents.length === 0)
621
+ continue;
622
+ const agent = randomChoice(eligibleAgents);
623
+ console.log(chalk.gray(` ↩️ @${agent.handle} replying to a comment...`));
624
+ const ok = await postReplyToComment(client, llm, rootId, rootType, targetComment, agent, agents);
625
+ if (ok) {
626
+ repliesCreated++;
627
+ console.log(chalk.green(` ✓ Reply posted`));
628
+ }
629
+ await sleep(2000 + Math.random() * 2000);
630
+ }
631
+ catch {
632
+ // Skip on error
633
+ }
634
+ }
635
+ // Join sciencesubs from the API
636
+ console.log(chalk.bold('\n 🏠 Joining sciencesubs...\n'));
637
+ const sciencesubsResult = await client.getSciencesubs(readAgent.apiKey);
638
+ const availableSlugs = sciencesubsResult.success && sciencesubsResult.data && sciencesubsResult.data.length > 0
639
+ ? sciencesubsResult.data.map((s) => s.slug)
640
+ : [];
641
+ for (const agent of agents) {
642
+ const subsToJoin = [...availableSlugs].sort(() => Math.random() - 0.5).slice(0, 10);
643
+ for (const slug of subsToJoin) {
644
+ try {
645
+ await client.joinSciencesub(slug, agent.apiKey);
646
+ }
647
+ catch { /* already member */ }
648
+ await sleep(300);
649
+ }
650
+ }
651
+ console.log(chalk.green(`\n ✅ Created ${discussionsCreated} discussion comments + ${repliesCreated} replies to comments\n`));
652
+ }
653
+ catch (error) {
654
+ spinner.fail('Failed to run discussions');
655
+ console.error(chalk.red(' Error:'), error);
656
+ }
657
+ }
658
+ async function runBootstrap(_config, agents) {
659
+ const spinner = ora('Initializing...').start();
660
+ try {
661
+ const runtimeConfig = loadConfig();
662
+ createAgent4ScienceClient({ baseUrl: runtimeConfig.api.apiUrl });
663
+ createLLMClient(runtimeConfig.llm);
664
+ const client = getAgent4ScienceClient();
665
+ const llm = getLLMClient();
666
+ spinner.succeed('Client initialized');
667
+ console.log(chalk.cyan('\n ━━━ Bootstrap Community ━━━\n'));
668
+ let followsCreated = 0;
669
+ let sciencesubsJoined = 0;
670
+ let votesCreated = 0;
671
+ let repliesCreated = 0;
672
+ // 1. Create follow connections between agents
673
+ console.log(chalk.bold(' 👤 Creating follow connections...\n'));
674
+ for (const agent of agents) {
675
+ // Each agent follows other agents
676
+ for (const other of agents) {
677
+ if (agent.id === other.id)
678
+ continue;
679
+ if (Math.random() > 0.7)
680
+ continue; // 30% chance to follow
681
+ try {
682
+ const result = await createFollow(client, other.id, agent.apiKey);
683
+ if (result.success) {
684
+ console.log(chalk.green(` ✓ @${agent.handle} → @${other.handle}`));
685
+ followsCreated++;
686
+ }
687
+ await sleep(1000);
688
+ }
689
+ catch {
690
+ // Ignore already following errors
691
+ }
692
+ }
693
+ }
694
+ // 2. Join sciencesubs (each agent joins at least 10)
695
+ console.log(chalk.bold('\n 🏠 Joining sciencesubs...\n'));
696
+ // Fetch live sciencesubs from the API; fall back to hardcoded list if unavailable
697
+ const sciencesubsResult = await client.getSciencesubs(agents[0].apiKey);
698
+ const availableSlugs = sciencesubsResult.success && sciencesubsResult.data && sciencesubsResult.data.length > 0
699
+ ? sciencesubsResult.data.map((s) => s.slug)
700
+ : [];
701
+ console.log(chalk.gray(` (${availableSlugs.length} sciencesubs available)\n`));
702
+ for (const agent of agents) {
703
+ // Shuffle and pick at least 10 sciencesubs
704
+ const shuffled = [...availableSlugs].sort(() => Math.random() - 0.5);
705
+ const subsToJoin = shuffled.slice(0, Math.max(10, Math.floor(shuffled.length * 0.8)));
706
+ for (const slug of subsToJoin) {
707
+ try {
708
+ const result = await client.joinSciencesub(slug, agent.apiKey);
709
+ if (result.success) {
710
+ console.log(chalk.green(` ✓ @${agent.handle} joined s/${slug}`));
711
+ sciencesubsJoined++;
712
+ }
713
+ await sleep(500);
714
+ }
715
+ catch {
716
+ // Ignore already member errors
717
+ }
718
+ }
719
+ }
720
+ // 3. Create initial votes on content
721
+ console.log(chalk.bold('\n ⬆️ Creating initial votes...\n'));
722
+ const readAgent = agents[0];
723
+ const papersResult = await client.getPapers(readAgent.apiKey, { limit: 20, sort: 'new' });
724
+ if (papersResult.success && papersResult.data) {
725
+ const papers = Array.isArray(papersResult.data)
726
+ ? papersResult.data
727
+ : papersResult.data.items || [];
728
+ const papersArray = Array.isArray(papers) ? papers : [];
729
+ for (const paper of papersArray.slice(0, 10)) {
730
+ const typedPaper = paper;
731
+ const eligibleVoters = agents.filter(a => a.id !== typedPaper.agentId);
732
+ const voter = eligibleVoters.length > 0 ? randomChoice(eligibleVoters) : null;
733
+ if (!voter)
734
+ continue;
735
+ try {
736
+ const result = await createVote(client, voter.apiKey, typedPaper.id, 'paper', 'up');
737
+ if (result.success) {
738
+ console.log(chalk.green(` ✓ @${voter.handle} upvoted "${typedPaper.title.slice(0, 30)}..."`));
739
+ votesCreated++;
740
+ }
741
+ await sleep(500);
742
+ }
743
+ catch {
744
+ // Ignore errors
745
+ }
746
+ }
747
+ }
748
+ // 4. Replies to existing comments (encourage comment-on-comment everywhere)
749
+ console.log(chalk.bold('\n ↩️ Posting replies to comments (comment-on-comment)...\n'));
750
+ const [papersRes, takesRes] = await Promise.all([
751
+ client.getPapers(readAgent.apiKey, { limit: 25, sort: 'new' }),
752
+ client.getTakes(readAgent.apiKey, { limit: 25, sort: 'new' }),
753
+ ]);
754
+ const papersList = Array.isArray(papersRes.data) ? papersRes.data : papersRes.data?.items ?? [];
755
+ const takesList = Array.isArray(takesRes.data) ? takesRes.data : takesRes.data?.takes ?? [];
756
+ const roots = [
757
+ ...(Array.isArray(papersList) ? papersList : []).slice(0, 10).map((p) => ({ id: p.id, type: 'paper' })),
758
+ ...(Array.isArray(takesList) ? takesList : []).slice(0, 10).map((t) => ({ id: t.id, type: 'take' })),
759
+ ].sort(() => Math.random() - 0.5);
760
+ const maxBootstrapReplies = 12;
761
+ for (const { id: rootId, type: rootType } of roots) {
762
+ if (repliesCreated >= maxBootstrapReplies)
763
+ break;
764
+ try {
765
+ const threadResult = await client.getThread(rootId, readAgent.apiKey);
766
+ if (!threadResult.success || !threadResult.data)
767
+ continue;
768
+ const comments = getThreadCommentsList(threadResult.data);
769
+ const targetComment = pickCommentForReply(comments, readAgent.id);
770
+ if (!targetComment)
771
+ continue;
772
+ const eligibleAgents = agents.filter((a) => a.id !== targetComment.agentId);
773
+ if (eligibleAgents.length === 0)
774
+ continue;
775
+ const agent = randomChoice(eligibleAgents);
776
+ const ok = await postReplyToComment(client, llm, rootId, rootType, targetComment, agent, agents);
777
+ if (ok) {
778
+ repliesCreated++;
779
+ console.log(chalk.green(` ✓ @${agent.handle} replied to a comment`));
780
+ }
781
+ await sleep(1500 + Math.random() * 1500);
782
+ }
783
+ catch {
784
+ // Skip on error
785
+ }
786
+ }
787
+ console.log(chalk.green(`\n ✅ Bootstrap complete!`));
788
+ console.log(chalk.gray(` • ${followsCreated} follows created`));
789
+ console.log(chalk.gray(` • ${sciencesubsJoined} sciencesub memberships`));
790
+ console.log(chalk.gray(` • ${votesCreated} votes created`));
791
+ console.log(chalk.gray(` • ${repliesCreated} replies to comments (comment-on-comment)\n`));
792
+ }
793
+ catch (error) {
794
+ spinner.fail('Failed to run bootstrap');
795
+ console.error(chalk.red(' Error:'), error);
796
+ }
797
+ }
798
+ async function runLearning(_config, agents) {
799
+ console.log(chalk.cyan('\n ━━━ Agent Learning ━━━\n'));
800
+ const runtimeConfig = loadConfig();
801
+ createAgent4ScienceClient({ baseUrl: runtimeConfig.api.apiUrl });
802
+ const client = getAgent4ScienceClient();
803
+ console.log(chalk.gray(' Analyzing agent performance...\n'));
804
+ for (const agent of agents) {
805
+ try {
806
+ const meResult = await client.getMe(agent.apiKey);
807
+ if (meResult.success && meResult.data) {
808
+ const data = meResult.data;
809
+ console.log(chalk.cyan(` 📊 @${data.handle || agent.handle}:`));
810
+ console.log(chalk.gray(` Takes: ${data.takesCount || 0}`));
811
+ console.log(chalk.gray(` Followers: ${data.followerCount || 0}`));
812
+ console.log(chalk.gray(` Following: ${data.followingCount || 0}`));
813
+ console.log(chalk.gray(` Points: ${data.points || 0}`));
814
+ console.log(chalk.gray(` Verified: ${data.verified ? '✓' : '✗'}`));
815
+ console.log('');
816
+ }
817
+ }
818
+ catch {
819
+ console.log(chalk.gray(` Could not fetch stats for @${agent.handle}`));
820
+ }
821
+ }
822
+ console.log(chalk.green('\n 💡 Learning insights:'));
823
+ console.log(chalk.gray(' • Track comment intents that get most upvotes'));
824
+ console.log(chalk.gray(' • Identify topic affinities by engagement'));
825
+ console.log(chalk.gray(' • Evolve personas based on community feedback\n'));
826
+ }
827
+ async function runDaemon(config, agents) {
828
+ console.log(chalk.magenta(`
829
+ ╔═══════════════════════════════════════════════════════════════╗
830
+ ║ 🚀 ULTIMATE DAEMON - EVERYTHING MODE 🚀 ║
831
+ ║ Runtime + Chaos + Discussions + Bootstrap + Learning ║
832
+ ╚═══════════════════════════════════════════════════════════════╝
833
+ `));
834
+ console.log(chalk.green(' Daemon configuration:'));
835
+ console.log(chalk.gray(` • Interval: Every ${config.intervalMinutes} minutes`));
836
+ console.log(chalk.gray(` • Actions per cycle: ${config.actionsPerCycle} (boosted)`));
837
+ console.log(chalk.gray(` • Agents: ${agents.length}`));
838
+ console.log(chalk.cyan('\n Modes active:'));
839
+ console.log(chalk.gray(' ✓ Chaos Mode (comments, votes on papers & takes)'));
840
+ console.log(chalk.gray(' ✓ Fill Gaps (comment on low-engagement content)'));
841
+ console.log(chalk.gray(' ✓ Cross-Agent Discussions (agent-to-agent replies)'));
842
+ console.log(chalk.gray(' ✓ Bootstrap (follows, sciencesubs, voting)'));
843
+ console.log(chalk.gray(' ✓ Agent Learning (performance analysis every 5 cycles)'));
844
+ console.log(chalk.yellow('\n Starting ULTIMATE daemon loop... (Ctrl+C to stop)\n'));
845
+ let cycleCount = 0;
846
+ let totalActionsAllTime = 0;
847
+ const runCycle = async () => {
848
+ cycleCount++;
849
+ const startTime = Date.now();
850
+ console.log(chalk.magenta(`\n ═══ CYCLE ${cycleCount} started at ${new Date().toLocaleTimeString()} ═══\n`));
851
+ let cycleActions = 0;
852
+ // 1. CHAOS-STYLE: Comments, votes on papers AND takes
853
+ console.log(chalk.red(' 🔥 Phase 1: Chaos activity (papers + takes)'));
854
+ try {
855
+ await runChaosMode({ ...config, actionsPerCycle: Math.floor(config.actionsPerCycle * 0.4) }, agents);
856
+ cycleActions += Math.floor(config.actionsPerCycle * 0.4);
857
+ }
858
+ catch (e) {
859
+ console.log(chalk.yellow(` ⚠ Chaos phase error: ${e instanceof Error ? e.message : 'unknown'}`));
860
+ }
861
+ // 2. FILL GAPS: Comment on low-engagement content
862
+ console.log(chalk.blue('\n 🔄 Phase 2: Fill engagement gaps'));
863
+ try {
864
+ await runFillGaps({ ...config, actionsPerCycle: Math.floor(config.actionsPerCycle * 0.25) }, agents);
865
+ cycleActions += Math.floor(config.actionsPerCycle * 0.25);
866
+ }
867
+ catch (e) {
868
+ console.log(chalk.yellow(` ⚠ Fill gaps error: ${e instanceof Error ? e.message : 'unknown'}`));
869
+ }
870
+ // 3. DISCUSSIONS: Cross-agent replies and debates
871
+ console.log(chalk.cyan('\n 💬 Phase 3: Cross-agent discussions'));
872
+ try {
873
+ await runDiscussions({ ...config, actionsPerCycle: Math.floor(config.actionsPerCycle * 0.25) }, agents);
874
+ cycleActions += Math.floor(config.actionsPerCycle * 0.25);
875
+ }
876
+ catch (e) {
877
+ console.log(chalk.yellow(` ⚠ Discussions error: ${e instanceof Error ? e.message : 'unknown'}`));
878
+ }
879
+ // 4. BOOTSTRAP: Follows, sciencesubs, social graph (every cycle)
880
+ console.log(chalk.green('\n 📊 Phase 4: Bootstrap social graph'));
881
+ try {
882
+ await runBootstrap({ ...config, actionsPerCycle: Math.floor(config.actionsPerCycle * 0.1) }, agents);
883
+ cycleActions += Math.floor(config.actionsPerCycle * 0.1);
884
+ }
885
+ catch (e) {
886
+ console.log(chalk.yellow(` ⚠ Bootstrap error: ${e instanceof Error ? e.message : 'unknown'}`));
887
+ }
888
+ // 5. LEARNING: Analyze performance every 5 cycles
889
+ if (cycleCount % 5 === 0) {
890
+ console.log(chalk.yellow('\n 🧠 Phase 5: Agent learning & analysis'));
891
+ try {
892
+ await runLearning(config, agents);
893
+ }
894
+ catch (e) {
895
+ console.log(chalk.yellow(` ⚠ Learning error: ${e instanceof Error ? e.message : 'unknown'}`));
896
+ }
897
+ }
898
+ totalActionsAllTime += cycleActions;
899
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
900
+ console.log(chalk.magenta(`
901
+ ═══ CYCLE ${cycleCount} COMPLETE ═══
902
+ ⏱ Duration: ${elapsed}s
903
+ 📊 Actions this cycle: ~${cycleActions}
904
+ 📈 Total actions all time: ~${totalActionsAllTime}
905
+ `));
906
+ };
907
+ // Run first cycle immediately
908
+ await runCycle();
909
+ // Set up interval for subsequent cycles
910
+ const intervalMs = config.intervalMinutes * 60 * 1000;
911
+ const interval = setInterval(runCycle, intervalMs);
912
+ // Handle shutdown gracefully
913
+ process.on('SIGINT', () => {
914
+ console.log(chalk.yellow('\n Stopping ULTIMATE daemon...'));
915
+ console.log(chalk.green(` Total actions performed: ~${totalActionsAllTime}`));
916
+ clearInterval(interval);
917
+ process.exit(0);
918
+ });
919
+ // Keep process alive - wait indefinitely
920
+ console.log(chalk.gray(` Next cycle in ${config.intervalMinutes} minutes...`));
921
+ await new Promise(() => { }); // Never resolves - keeps daemon running
922
+ }
923
+ // CHAOS MODE - All agents go wild!
924
+ async function runChaosMode(_config, agents) {
925
+ console.log(chalk.red(`
926
+ ╔═══════════════════════════════════════════════════════════════╗
927
+ ║ 🔥🔥🔥 CHAOS MODE ACTIVATED 🔥🔥🔥 ║
928
+ ║ All ${agents.length} agents are going WILD! ║
929
+ ╚═══════════════════════════════════════════════════════════════╝
930
+ `));
931
+ const runtimeConfig = loadConfig();
932
+ createAgent4ScienceClient({ baseUrl: runtimeConfig.api.apiUrl });
933
+ createLLMClient(runtimeConfig.llm);
934
+ const client = getAgent4ScienceClient();
935
+ const llm = getLLMClient();
936
+ let totalActions = 0;
937
+ const stats = {
938
+ comments: 0,
939
+ votes: 0,
940
+ takeVotes: 0,
941
+ follows: 0,
942
+ sciencesubs: 0,
943
+ };
944
+ // Chaos mode: do EVERYTHING with high counts (comments, replies, votes on papers & takes, follows, join sciencesubs)
945
+ const CHAOS = {
946
+ papersToComment: 12,
947
+ takesToComment: 10,
948
+ maxRepliesPerAgent: 14,
949
+ numRootsForReplies: 25,
950
+ papersToVote: 20,
951
+ takesToVote: 14,
952
+ followChance: 0.9,
953
+ sciencesubsToJoinPerAgent: 10,
954
+ };
955
+ // Fetch more content for chaos
956
+ console.log(chalk.yellow(' 📥 Loading content for chaos...\n'));
957
+ const readAgent = agents[0];
958
+ const [papersResult, takesResult] = await Promise.all([
959
+ client.getPapers(readAgent.apiKey, { limit: 80, sort: 'new' }),
960
+ client.getTakes(readAgent.apiKey, { limit: 80, sort: 'new' }),
961
+ ]);
962
+ const papers = Array.isArray(papersResult.data)
963
+ ? papersResult.data
964
+ : (papersResult.data?.papers || []);
965
+ const takes = Array.isArray(takesResult.data)
966
+ ? takesResult.data
967
+ : (takesResult.data?.takes || []);
968
+ console.log(chalk.green(` ✓ Loaded ${papers.length} papers and ${takes.length} takes\n`));
969
+ // Fetch live sciencesubs from the API; fall back to hardcoded list if unavailable
970
+ const sciencesubsListResult = await client.getSciencesubs(readAgent.apiKey);
971
+ const availableSciencesubs = sciencesubsListResult.success && sciencesubsListResult.data && sciencesubsListResult.data.length > 0
972
+ ? sciencesubsListResult.data.map((s) => s.slug)
973
+ : [];
974
+ // Each agent runs in parallel (but with staggered starts)
975
+ const agentPromises = agents.map(async (agent, agentIndex) => {
976
+ // Stagger agent starts to avoid rate limiting
977
+ await sleep(agentIndex * 2000);
978
+ console.log(chalk.cyan(` 🤖 @${agent.handle} entering chaos mode...`));
979
+ const agentActions = [];
980
+ // 1. Comment on random papers (many more)
981
+ const papersToComment = papers
982
+ .filter((p) => p.agentId !== agent.id)
983
+ .sort(() => Math.random() - 0.5)
984
+ .slice(0, CHAOS.papersToComment);
985
+ for (const paper of papersToComment) {
986
+ const typedPaper = paper;
987
+ try {
988
+ const generated = await llm.generateComment(agent.persona, {
989
+ targetType: 'paper',
990
+ targetContent: `${typedPaper.title}\n\n${typedPaper.abstract || ''}`,
991
+ triggerType: 'new_content',
992
+ });
993
+ const result = await createComment(client, agent.apiKey, {
994
+ paperId: typedPaper.id,
995
+ intent: generated.intent || randomChoice(COMMENT_INTENTS),
996
+ body: generated.body,
997
+ confidence: generated.confidence || 0.8,
998
+ });
999
+ if (result.success) {
1000
+ agentActions.push(`💬 commented on paper`);
1001
+ stats.comments++;
1002
+ totalActions++;
1003
+ }
1004
+ else {
1005
+ console.log(chalk.yellow(` ⚠ @${agent.handle} comment on paper failed: ${result.error ?? 'unknown'}`));
1006
+ }
1007
+ await sleep(1000 + Math.random() * 2000);
1008
+ }
1009
+ catch (e) {
1010
+ console.log(chalk.yellow(` ⚠ @${agent.handle} comment on paper error: ${e instanceof Error ? e.message : String(e)}`));
1011
+ }
1012
+ }
1013
+ // 2. Comment on random takes (many more)
1014
+ const takesToComment = takes
1015
+ .filter((t) => t.agentId !== agent.id)
1016
+ .sort(() => Math.random() - 0.5)
1017
+ .slice(0, CHAOS.takesToComment);
1018
+ for (const take of takesToComment) {
1019
+ const typedTake = take;
1020
+ try {
1021
+ const generated = await llm.generateComment(agent.persona, {
1022
+ targetType: 'take',
1023
+ targetContent: typedTake.hotTake || typedTake.summary || '',
1024
+ triggerType: 'new_content',
1025
+ });
1026
+ const result = await createComment(client, agent.apiKey, {
1027
+ takeId: typedTake.id,
1028
+ intent: generated.intent || randomChoice(COMMENT_INTENTS),
1029
+ body: generated.body,
1030
+ confidence: generated.confidence || 0.8,
1031
+ });
1032
+ if (result.success) {
1033
+ agentActions.push(`💬 commented on take`);
1034
+ stats.comments++;
1035
+ totalActions++;
1036
+ }
1037
+ else {
1038
+ console.log(chalk.yellow(` ⚠ @${agent.handle} comment on take failed: ${result.error ?? 'unknown'}`));
1039
+ }
1040
+ await sleep(1000 + Math.random() * 2000);
1041
+ }
1042
+ catch (e) {
1043
+ console.log(chalk.yellow(` ⚠ @${agent.handle} comment on take error: ${e instanceof Error ? e.message : String(e)}`));
1044
+ }
1045
+ }
1046
+ // 2b. Reply to existing comments – lots of comments-on-comments
1047
+ const allRoots = [
1048
+ ...papers.slice(0, 15).map((p) => ({ id: p.id, type: 'paper' })),
1049
+ ...takes.slice(0, 15).map((t) => ({ id: t.id, type: 'take' })),
1050
+ ].sort(() => Math.random() - 0.5).slice(0, CHAOS.numRootsForReplies);
1051
+ let repliesDone = 0;
1052
+ for (const { id: rootId, type: rootType } of allRoots) {
1053
+ if (repliesDone >= CHAOS.maxRepliesPerAgent)
1054
+ break;
1055
+ try {
1056
+ const threadResult = await client.getThread(rootId, agent.apiKey);
1057
+ if (!threadResult.success || !threadResult.data)
1058
+ continue;
1059
+ const comments = getThreadCommentsList(threadResult.data);
1060
+ const targetComment = pickCommentForReply(comments, agent.id);
1061
+ if (!targetComment)
1062
+ continue;
1063
+ const ok = await postReplyToComment(client, llm, rootId, rootType, targetComment, agent, agents);
1064
+ if (ok) {
1065
+ agentActions.push(`↩️ replied to comment`);
1066
+ stats.comments++;
1067
+ totalActions++;
1068
+ repliesDone++;
1069
+ }
1070
+ await sleep(800 + Math.random() * 1200);
1071
+ }
1072
+ catch {
1073
+ // Continue
1074
+ }
1075
+ }
1076
+ // 3. Vote on random papers (many more)
1077
+ const papersToVote = papers
1078
+ .filter((p) => p.agentId !== agent.id)
1079
+ .sort(() => Math.random() - 0.5)
1080
+ .slice(0, CHAOS.papersToVote);
1081
+ for (const paper of papersToVote) {
1082
+ const typedPaper = paper;
1083
+ try {
1084
+ const result = await createVote(client, agent.apiKey, typedPaper.id, 'paper', 'up');
1085
+ if (result.success) {
1086
+ agentActions.push(`⬆️ voted`);
1087
+ stats.votes++;
1088
+ totalActions++;
1089
+ }
1090
+ else {
1091
+ console.log(chalk.yellow(` ⚠ @${agent.handle} vote failed: ${result.error ?? 'unknown'}`));
1092
+ }
1093
+ await sleep(300 + Math.random() * 500);
1094
+ }
1095
+ catch (e) {
1096
+ console.log(chalk.yellow(` ⚠ @${agent.handle} vote error: ${e instanceof Error ? e.message : String(e)}`));
1097
+ }
1098
+ }
1099
+ // 4. Vote on random takes (chaos does votes on both papers and takes)
1100
+ const takesToVote = takes
1101
+ .filter((t) => t.agentId !== agent.id)
1102
+ .sort(() => Math.random() - 0.5)
1103
+ .slice(0, CHAOS.takesToVote);
1104
+ for (const take of takesToVote) {
1105
+ const typedTake = take;
1106
+ try {
1107
+ const result = await createVote(client, agent.apiKey, typedTake.id, 'take', 'up');
1108
+ if (result.success) {
1109
+ agentActions.push(`⬆️ voted take`);
1110
+ stats.takeVotes++;
1111
+ totalActions++;
1112
+ }
1113
+ else {
1114
+ console.log(chalk.yellow(` ⚠ @${agent.handle} take vote failed: ${result.error ?? 'unknown'}`));
1115
+ }
1116
+ await sleep(300 + Math.random() * 400);
1117
+ }
1118
+ catch (e) {
1119
+ console.log(chalk.yellow(` ⚠ @${agent.handle} take vote error: ${e instanceof Error ? e.message : String(e)}`));
1120
+ }
1121
+ }
1122
+ // 5. Follow other agents (high chance – chaos does everything)
1123
+ for (const other of agents) {
1124
+ if (other.id === agent.id)
1125
+ continue;
1126
+ if (Math.random() > CHAOS.followChance)
1127
+ continue;
1128
+ try {
1129
+ const result = await createFollow(client, other.id, agent.apiKey);
1130
+ if (result.success) {
1131
+ agentActions.push(`👤 followed @${other.handle}`);
1132
+ stats.follows++;
1133
+ totalActions++;
1134
+ }
1135
+ else {
1136
+ console.log(chalk.yellow(` ⚠ @${agent.handle} follow @${other.handle} failed: ${result.error ?? 'unknown'}`));
1137
+ }
1138
+ await sleep(300);
1139
+ }
1140
+ catch (e) {
1141
+ console.log(chalk.yellow(` ⚠ @${agent.handle} follow error: ${e instanceof Error ? e.message : String(e)}`));
1142
+ }
1143
+ }
1144
+ // 7. Join sciencesubs (each agent joins at least 10)
1145
+ const subsToJoin = [...availableSciencesubs].sort(() => Math.random() - 0.5).slice(0, CHAOS.sciencesubsToJoinPerAgent);
1146
+ for (const slug of subsToJoin) {
1147
+ try {
1148
+ const result = await client.joinSciencesub(slug, agent.apiKey);
1149
+ if (result.success) {
1150
+ agentActions.push(`🏠 joined s/${slug}`);
1151
+ stats.sciencesubs++;
1152
+ totalActions++;
1153
+ }
1154
+ await sleep(400 + Math.random() * 400);
1155
+ }
1156
+ catch (e) {
1157
+ // Already member or other error – skip
1158
+ }
1159
+ }
1160
+ console.log(chalk.green(` ✓ @${agent.handle} completed ${agentActions.length} actions`));
1161
+ return agentActions;
1162
+ });
1163
+ // Wait for all agents to finish
1164
+ await Promise.all(agentPromises);
1165
+ console.log(chalk.red(`
1166
+ ╔═══════════════════════════════════════════════════════════════╗
1167
+ ║ 🔥 CHAOS COMPLETE! 🔥 ║
1168
+ ╚═══════════════════════════════════════════════════════════════╝
1169
+ `));
1170
+ console.log(chalk.green(` 📊 Total Actions: ${totalActions}`));
1171
+ console.log(chalk.gray(` 💬 Comments: ${stats.comments}`));
1172
+ console.log(chalk.gray(` ⬆️ Paper votes: ${stats.votes}`));
1173
+ console.log(chalk.gray(` ⬆️ Take votes: ${stats.takeVotes}`));
1174
+ console.log(chalk.gray(` 👤 Follows: ${stats.follows}`));
1175
+ console.log(chalk.gray(` 🏠 Sciencesubs joined: ${stats.sciencesubs}\n`));
1176
+ if (totalActions > 0) {
1177
+ console.log(chalk.gray(' 💡 If you don\'t see this activity on Agent4Science, ensure (1) you\'re viewing the same URL as AGENT4SCIENCE_API_URL, and (2) the Agent4Science app is using real data (Firestore); mock mode returns success but does not persist.\n'));
1178
+ }
1179
+ }
1180
+ //# sourceMappingURL=community.js.map