@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.
- package/LICENSE +21 -0
- package/README.md +370 -0
- package/dist/actions/action-executor.d.ts +72 -0
- package/dist/actions/action-executor.d.ts.map +1 -0
- package/dist/actions/action-executor.js +458 -0
- package/dist/actions/action-executor.js.map +1 -0
- package/dist/agents/agent-manager.d.ts +90 -0
- package/dist/agents/agent-manager.d.ts.map +1 -0
- package/dist/agents/agent-manager.js +269 -0
- package/dist/agents/agent-manager.js.map +1 -0
- package/dist/api/agent4science-client.d.ts +297 -0
- package/dist/api/agent4science-client.d.ts.map +1 -0
- package/dist/api/agent4science-client.js +386 -0
- package/dist/api/agent4science-client.js.map +1 -0
- package/dist/cli/commands/add-agent.d.ts +13 -0
- package/dist/cli/commands/add-agent.d.ts.map +1 -0
- package/dist/cli/commands/add-agent.js +76 -0
- package/dist/cli/commands/add-agent.js.map +1 -0
- package/dist/cli/commands/community.d.ts +20 -0
- package/dist/cli/commands/community.d.ts.map +1 -0
- package/dist/cli/commands/community.js +1180 -0
- package/dist/cli/commands/community.js.map +1 -0
- package/dist/cli/commands/config.d.ts +12 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +152 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/create-agent.d.ts +12 -0
- package/dist/cli/commands/create-agent.d.ts.map +1 -0
- package/dist/cli/commands/create-agent.js +1780 -0
- package/dist/cli/commands/create-agent.js.map +1 -0
- package/dist/cli/commands/init.d.ts +15 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +487 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/interactive.d.ts +6 -0
- package/dist/cli/commands/interactive.d.ts.map +1 -0
- package/dist/cli/commands/interactive.js +447 -0
- package/dist/cli/commands/interactive.js.map +1 -0
- package/dist/cli/commands/list-agents.d.ts +10 -0
- package/dist/cli/commands/list-agents.d.ts.map +1 -0
- package/dist/cli/commands/list-agents.js +67 -0
- package/dist/cli/commands/list-agents.js.map +1 -0
- package/dist/cli/commands/play.d.ts +30 -0
- package/dist/cli/commands/play.d.ts.map +1 -0
- package/dist/cli/commands/play.js +1890 -0
- package/dist/cli/commands/play.js.map +1 -0
- package/dist/cli/commands/setup-production.d.ts +7 -0
- package/dist/cli/commands/setup-production.d.ts.map +1 -0
- package/dist/cli/commands/setup-production.js +127 -0
- package/dist/cli/commands/setup-production.js.map +1 -0
- package/dist/cli/commands/start.d.ts +15 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +89 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +6 -0
- package/dist/cli/commands/stats.d.ts.map +1 -0
- package/dist/cli/commands/stats.js +74 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/status.d.ts +10 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +121 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +174 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/ensure-credentials.d.ts +32 -0
- package/dist/cli/utils/ensure-credentials.d.ts.map +1 -0
- package/dist/cli/utils/ensure-credentials.js +280 -0
- package/dist/cli/utils/ensure-credentials.js.map +1 -0
- package/dist/cli/utils/local-agents.d.ts +49 -0
- package/dist/cli/utils/local-agents.d.ts.map +1 -0
- package/dist/cli/utils/local-agents.js +117 -0
- package/dist/cli/utils/local-agents.js.map +1 -0
- package/dist/config/config.d.ts +28 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +182 -0
- package/dist/config/config.js.map +1 -0
- package/dist/db/database.d.ts +150 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +838 -0
- package/dist/db/database.js.map +1 -0
- package/dist/engagement/proactive-engine.d.ts +246 -0
- package/dist/engagement/proactive-engine.d.ts.map +1 -0
- package/dist/engagement/proactive-engine.js +1753 -0
- package/dist/engagement/proactive-engine.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/llm-client.d.ts +181 -0
- package/dist/llm/llm-client.d.ts.map +1 -0
- package/dist/llm/llm-client.js +658 -0
- package/dist/llm/llm-client.js.map +1 -0
- package/dist/logging/logger.d.ts +14 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +47 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/polling/notification-poller.d.ts +70 -0
- package/dist/polling/notification-poller.d.ts.map +1 -0
- package/dist/polling/notification-poller.js +190 -0
- package/dist/polling/notification-poller.js.map +1 -0
- package/dist/rate-limit/rate-limiter.d.ts +56 -0
- package/dist/rate-limit/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limit/rate-limiter.js +202 -0
- package/dist/rate-limit/rate-limiter.js.map +1 -0
- package/dist/runtime/event-loop.d.ts +101 -0
- package/dist/runtime/event-loop.d.ts.map +1 -0
- package/dist/runtime/event-loop.js +680 -0
- package/dist/runtime/event-loop.js.map +1 -0
- package/dist/tools/manager-agent.d.ts +48 -0
- package/dist/tools/manager-agent.d.ts.map +1 -0
- package/dist/tools/manager-agent.js +440 -0
- package/dist/tools/manager-agent.js.map +1 -0
- package/dist/tools/paper-tools.d.ts +70 -0
- package/dist/tools/paper-tools.d.ts.map +1 -0
- package/dist/tools/paper-tools.js +446 -0
- package/dist/tools/paper-tools.js.map +1 -0
- package/dist/types.d.ts +266 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/cost-tracker.d.ts +51 -0
- package/dist/utils/cost-tracker.d.ts.map +1 -0
- package/dist/utils/cost-tracker.js +161 -0
- package/dist/utils/cost-tracker.js.map +1 -0
- package/dist/utils/similarity.d.ts +37 -0
- package/dist/utils/similarity.d.ts.map +1 -0
- package/dist/utils/similarity.js +78 -0
- package/dist/utils/similarity.js.map +1 -0
- 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
|