@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,838 @@
1
+ /**
2
+ * SQLite Database Layer
3
+ * Persistent storage for agents, actions, and state
4
+ */
5
+ import Database from 'better-sqlite3';
6
+ import { mkdirSync, existsSync } from 'fs';
7
+ import { dirname } from 'path';
8
+ function rowToAgentConfig(row) {
9
+ return {
10
+ id: row.id,
11
+ handle: row.handle,
12
+ displayName: row.display_name,
13
+ persona: JSON.parse(row.persona),
14
+ capability: (row.capability || 'base'),
15
+ researchDomain: row.research_domain || undefined,
16
+ enabled: row.enabled === 1,
17
+ createdAt: new Date(row.created_at),
18
+ apiKeyEncrypted: row.api_key_encrypted,
19
+ };
20
+ }
21
+ export class RuntimeDatabase {
22
+ db;
23
+ constructor(dbPath) {
24
+ // Ensure directory exists
25
+ const dir = dirname(dbPath);
26
+ if (!existsSync(dir)) {
27
+ mkdirSync(dir, { recursive: true });
28
+ }
29
+ this.db = new Database(dbPath);
30
+ this.db.pragma('journal_mode = WAL');
31
+ this.db.pragma('foreign_keys = ON');
32
+ this.initialize();
33
+ }
34
+ initialize() {
35
+ // Agents table
36
+ this.db.exec(`
37
+ CREATE TABLE IF NOT EXISTS agents (
38
+ id TEXT PRIMARY KEY,
39
+ handle TEXT UNIQUE NOT NULL,
40
+ display_name TEXT NOT NULL,
41
+ persona TEXT NOT NULL,
42
+ capability TEXT NOT NULL DEFAULT 'base',
43
+ api_key_encrypted TEXT NOT NULL,
44
+ enabled INTEGER DEFAULT 1,
45
+ created_at TEXT NOT NULL,
46
+ updated_at TEXT NOT NULL
47
+ )
48
+ `);
49
+ // Migration: add capability column to existing databases
50
+ this.migrate();
51
+ // Action queue table
52
+ this.db.exec(`
53
+ CREATE TABLE IF NOT EXISTS action_queue (
54
+ id TEXT PRIMARY KEY,
55
+ agent_id TEXT NOT NULL,
56
+ type TEXT NOT NULL,
57
+ target_id TEXT NOT NULL,
58
+ target_type TEXT NOT NULL,
59
+ priority TEXT NOT NULL,
60
+ payload TEXT NOT NULL,
61
+ created_at TEXT NOT NULL,
62
+ execute_after TEXT NOT NULL,
63
+ attempts INTEGER DEFAULT 0,
64
+ max_attempts INTEGER DEFAULT 3,
65
+ last_error TEXT,
66
+ status TEXT DEFAULT 'pending',
67
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
68
+ )
69
+ `);
70
+ // Create index for efficient queue queries
71
+ this.db.exec(`
72
+ CREATE INDEX IF NOT EXISTS idx_action_queue_status
73
+ ON action_queue(status, execute_after)
74
+ `);
75
+ this.db.exec(`
76
+ CREATE INDEX IF NOT EXISTS idx_action_queue_agent
77
+ ON action_queue(agent_id, status)
78
+ `);
79
+ // Agent state table (for tracking runtime state)
80
+ this.db.exec(`
81
+ CREATE TABLE IF NOT EXISTS agent_state (
82
+ agent_id TEXT PRIMARY KEY,
83
+ state TEXT NOT NULL,
84
+ last_poll_time TEXT,
85
+ last_action_time TEXT,
86
+ error_count INTEGER DEFAULT 0,
87
+ last_error TEXT,
88
+ updated_at TEXT NOT NULL,
89
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
90
+ )
91
+ `);
92
+ // Rate limit tracking (persisted for daily limits)
93
+ this.db.exec(`
94
+ CREATE TABLE IF NOT EXISTS rate_limits (
95
+ agent_id TEXT NOT NULL,
96
+ action TEXT NOT NULL,
97
+ count INTEGER DEFAULT 0,
98
+ window_start TEXT NOT NULL,
99
+ last_action_time TEXT,
100
+ PRIMARY KEY (agent_id, action),
101
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
102
+ )
103
+ `);
104
+ // Audit log
105
+ this.db.exec(`
106
+ CREATE TABLE IF NOT EXISTS audit_log (
107
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
108
+ agent_id TEXT,
109
+ action TEXT NOT NULL,
110
+ target_id TEXT,
111
+ target_type TEXT,
112
+ success INTEGER,
113
+ error TEXT,
114
+ metadata TEXT,
115
+ created_at TEXT NOT NULL
116
+ )
117
+ `);
118
+ this.db.exec(`
119
+ CREATE INDEX IF NOT EXISTS idx_audit_log_agent
120
+ ON audit_log(agent_id, created_at)
121
+ `);
122
+ // Processed notifications (to avoid duplicates)
123
+ this.db.exec(`
124
+ CREATE TABLE IF NOT EXISTS processed_notifications (
125
+ notification_id TEXT PRIMARY KEY,
126
+ agent_id TEXT NOT NULL,
127
+ processed_at TEXT NOT NULL,
128
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
129
+ )
130
+ `);
131
+ // Content engagements (Moltbook-style activity tracking)
132
+ this.db.exec(`
133
+ CREATE TABLE IF NOT EXISTS content_engagements (
134
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
135
+ agent_id TEXT NOT NULL,
136
+ content_id TEXT NOT NULL,
137
+ content_type TEXT NOT NULL,
138
+ action_type TEXT NOT NULL,
139
+ engaged_at TEXT NOT NULL,
140
+ UNIQUE(agent_id, content_id, action_type),
141
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
142
+ )
143
+ `);
144
+ this.db.exec(`
145
+ CREATE INDEX IF NOT EXISTS idx_content_engagements_agent
146
+ ON content_engagements(agent_id, content_id)
147
+ `);
148
+ // Agent follows (who this agent follows)
149
+ this.db.exec(`
150
+ CREATE TABLE IF NOT EXISTS agent_follows (
151
+ follower_id TEXT NOT NULL,
152
+ following_id TEXT NOT NULL,
153
+ followed_at TEXT NOT NULL,
154
+ PRIMARY KEY (follower_id, following_id),
155
+ FOREIGN KEY (follower_id) REFERENCES agents(id)
156
+ )
157
+ `);
158
+ // Sciencesub memberships
159
+ this.db.exec(`
160
+ CREATE TABLE IF NOT EXISTS sciencesub_memberships (
161
+ agent_id TEXT NOT NULL,
162
+ sciencesub_slug TEXT NOT NULL,
163
+ joined_at TEXT NOT NULL,
164
+ PRIMARY KEY (agent_id, sciencesub_slug),
165
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
166
+ )
167
+ `);
168
+ // Agent-to-agent interactions (for reciprocity tracking)
169
+ this.db.exec(`
170
+ CREATE TABLE IF NOT EXISTS agent_interactions (
171
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
172
+ agent_id TEXT NOT NULL,
173
+ other_agent_id TEXT NOT NULL,
174
+ interaction_type TEXT NOT NULL,
175
+ interaction_count INTEGER DEFAULT 1,
176
+ last_interaction TEXT NOT NULL,
177
+ first_interaction TEXT NOT NULL,
178
+ UNIQUE(agent_id, other_agent_id, interaction_type),
179
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
180
+ )
181
+ `);
182
+ this.db.exec(`
183
+ CREATE INDEX IF NOT EXISTS idx_agent_interactions
184
+ ON agent_interactions(agent_id, other_agent_id)
185
+ `);
186
+ }
187
+ /** Run schema migrations for existing databases. */
188
+ migrate() {
189
+ // Add capability column if missing (pre-v0.2 databases)
190
+ const cols = this.db.pragma('table_info(agents)');
191
+ if (!cols.some(c => c.name === 'capability')) {
192
+ this.db.exec(`ALTER TABLE agents ADD COLUMN capability TEXT NOT NULL DEFAULT 'base'`);
193
+ }
194
+ // Add paper_generation_interval_ms and last_generation_time columns
195
+ if (!cols.some(c => c.name === 'paper_generation_interval_ms')) {
196
+ this.db.exec(`ALTER TABLE agents ADD COLUMN paper_generation_interval_ms INTEGER NOT NULL DEFAULT 86400000`);
197
+ }
198
+ if (!cols.some(c => c.name === 'last_generation_time')) {
199
+ this.db.exec(`ALTER TABLE agents ADD COLUMN last_generation_time TEXT`);
200
+ }
201
+ // Add research_domain column and migrate math-agent → idea-explorer
202
+ if (!cols.some(c => c.name === 'research_domain')) {
203
+ this.db.exec(`ALTER TABLE agents ADD COLUMN research_domain TEXT DEFAULT NULL`);
204
+ this.db.exec(`UPDATE agents SET capability = 'idea-explorer', research_domain = 'mathematics' WHERE capability = 'math-agent'`);
205
+ }
206
+ }
207
+ // ============================================================================
208
+ // Agent Operations
209
+ // ============================================================================
210
+ addAgent(config, apiKeyEncrypted, paperIntervalMs) {
211
+ const stmt = this.db.prepare(`
212
+ INSERT INTO agents (id, handle, display_name, persona, capability, research_domain, api_key_encrypted, enabled, created_at, updated_at, paper_generation_interval_ms)
213
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
214
+ `);
215
+ const now = new Date().toISOString();
216
+ stmt.run(config.id, config.handle, config.displayName, JSON.stringify(config.persona), config.capability || 'base', config.researchDomain || null, apiKeyEncrypted, config.enabled ? 1 : 0, now, now, paperIntervalMs ?? 86400000);
217
+ // Initialize state
218
+ const stateStmt = this.db.prepare(`
219
+ INSERT INTO agent_state (agent_id, state, updated_at)
220
+ VALUES (?, 'idle', ?)
221
+ `);
222
+ stateStmt.run(config.id, now);
223
+ }
224
+ getAgent(id) {
225
+ const stmt = this.db.prepare(`
226
+ SELECT * FROM agents WHERE id = ?
227
+ `);
228
+ const row = stmt.get(id);
229
+ if (!row)
230
+ return null;
231
+ return rowToAgentConfig(row);
232
+ }
233
+ getAgentByHandle(handle) {
234
+ const stmt = this.db.prepare(`
235
+ SELECT * FROM agents WHERE handle = ?
236
+ `);
237
+ const row = stmt.get(handle);
238
+ if (!row)
239
+ return null;
240
+ return rowToAgentConfig(row);
241
+ }
242
+ getAllAgents() {
243
+ const stmt = this.db.prepare(`
244
+ SELECT * FROM agents WHERE enabled = 1
245
+ `);
246
+ const rows = stmt.all();
247
+ return rows.map(rowToAgentConfig);
248
+ }
249
+ /**
250
+ * Update agent details (handle, displayName)
251
+ */
252
+ updateAgent(id, updates) {
253
+ const fields = ['updated_at = ?'];
254
+ const values = [new Date().toISOString()];
255
+ if (updates.handle !== undefined) {
256
+ fields.push('handle = ?');
257
+ values.push(updates.handle);
258
+ }
259
+ if (updates.displayName !== undefined) {
260
+ fields.push('display_name = ?');
261
+ values.push(updates.displayName);
262
+ }
263
+ values.push(id);
264
+ const stmt = this.db.prepare(`
265
+ UPDATE agents SET ${fields.join(', ')} WHERE id = ?
266
+ `);
267
+ stmt.run(...values);
268
+ }
269
+ updateAgentEnabled(id, enabled) {
270
+ const stmt = this.db.prepare(`
271
+ UPDATE agents SET enabled = ?, updated_at = ? WHERE id = ?
272
+ `);
273
+ stmt.run(enabled ? 1 : 0, new Date().toISOString(), id);
274
+ }
275
+ deleteAgent(id) {
276
+ this.db.exec('BEGIN TRANSACTION');
277
+ try {
278
+ this.db.prepare('DELETE FROM agent_state WHERE agent_id = ?').run(id);
279
+ this.db.prepare('DELETE FROM rate_limits WHERE agent_id = ?').run(id);
280
+ this.db.prepare('DELETE FROM action_queue WHERE agent_id = ?').run(id);
281
+ this.db.prepare('DELETE FROM processed_notifications WHERE agent_id = ?').run(id);
282
+ this.db.prepare('DELETE FROM content_engagements WHERE agent_id = ?').run(id);
283
+ this.db.prepare('DELETE FROM agent_follows WHERE follower_id = ?').run(id);
284
+ this.db.prepare('DELETE FROM sciencesub_memberships WHERE agent_id = ?').run(id);
285
+ this.db.prepare('DELETE FROM agent_interactions WHERE agent_id = ?').run(id);
286
+ this.db.prepare('DELETE FROM agents WHERE id = ?').run(id);
287
+ this.db.exec('COMMIT');
288
+ }
289
+ catch (error) {
290
+ this.db.exec('ROLLBACK');
291
+ throw error;
292
+ }
293
+ }
294
+ updateAgentCapability(id, capability) {
295
+ const stmt = this.db.prepare(`
296
+ UPDATE agents SET capability = ?, updated_at = ? WHERE id = ?
297
+ `);
298
+ stmt.run(capability, new Date().toISOString(), id);
299
+ }
300
+ /** Get the paper generation schedule for an agent. */
301
+ getPaperGenerationConfig(agentId) {
302
+ const stmt = this.db.prepare(`
303
+ SELECT paper_generation_interval_ms, last_generation_time FROM agents WHERE id = ?
304
+ `);
305
+ const row = stmt.get(agentId);
306
+ return {
307
+ intervalMs: row?.paper_generation_interval_ms ?? 86400000,
308
+ lastGenerationTime: row?.last_generation_time ? new Date(row.last_generation_time) : null,
309
+ };
310
+ }
311
+ /** Record that an agent just generated a paper. */
312
+ recordPaperGeneration(agentId) {
313
+ const stmt = this.db.prepare(`
314
+ UPDATE agents SET last_generation_time = ?, updated_at = ? WHERE id = ?
315
+ `);
316
+ const now = new Date().toISOString();
317
+ stmt.run(now, now, agentId);
318
+ }
319
+ /** Update how often an agent generates papers (in ms). */
320
+ setPaperGenerationInterval(agentId, intervalMs) {
321
+ const stmt = this.db.prepare(`
322
+ UPDATE agents SET paper_generation_interval_ms = ?, updated_at = ? WHERE id = ?
323
+ `);
324
+ stmt.run(intervalMs, new Date().toISOString(), agentId);
325
+ }
326
+ // ============================================================================
327
+ // Action Queue Operations
328
+ // ============================================================================
329
+ queueAction(action) {
330
+ const stmt = this.db.prepare(`
331
+ INSERT INTO action_queue (
332
+ id, agent_id, type, target_id, target_type, priority, payload,
333
+ created_at, execute_after, attempts, max_attempts, status
334
+ )
335
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')
336
+ `);
337
+ stmt.run(action.id, action.agentId, action.type, action.targetId, action.targetType, action.priority, JSON.stringify(action.payload), action.createdAt.toISOString(), action.executeAfter.toISOString(), action.attempts, action.maxAttempts);
338
+ }
339
+ getNextAction() {
340
+ const stmt = this.db.prepare(`
341
+ SELECT * FROM action_queue
342
+ WHERE status = 'pending' AND execute_after <= ?
343
+ ORDER BY
344
+ CASE priority
345
+ WHEN 'critical' THEN 1
346
+ WHEN 'high' THEN 2
347
+ WHEN 'normal' THEN 3
348
+ WHEN 'low' THEN 4
349
+ END,
350
+ created_at ASC
351
+ LIMIT 1
352
+ `);
353
+ const row = stmt.get(new Date().toISOString());
354
+ if (!row)
355
+ return null;
356
+ return {
357
+ id: row.id,
358
+ agentId: row.agent_id,
359
+ type: row.type,
360
+ targetId: row.target_id,
361
+ targetType: row.target_type,
362
+ priority: row.priority,
363
+ payload: JSON.parse(row.payload),
364
+ createdAt: new Date(row.created_at),
365
+ executeAfter: new Date(row.execute_after),
366
+ attempts: row.attempts,
367
+ maxAttempts: row.max_attempts,
368
+ lastError: row.last_error ?? undefined,
369
+ };
370
+ }
371
+ markActionComplete(id) {
372
+ const stmt = this.db.prepare(`
373
+ UPDATE action_queue SET status = 'completed' WHERE id = ?
374
+ `);
375
+ stmt.run(id);
376
+ }
377
+ markActionFailed(id, error, retryAfter) {
378
+ if (retryAfter) {
379
+ const stmt = this.db.prepare(`
380
+ UPDATE action_queue
381
+ SET attempts = attempts + 1, last_error = ?, execute_after = ?
382
+ WHERE id = ?
383
+ `);
384
+ stmt.run(error, retryAfter.toISOString(), id);
385
+ }
386
+ else {
387
+ const stmt = this.db.prepare(`
388
+ UPDATE action_queue
389
+ SET status = 'failed', attempts = attempts + 1, last_error = ?
390
+ WHERE id = ?
391
+ `);
392
+ stmt.run(error, id);
393
+ }
394
+ }
395
+ /**
396
+ * Check if an agent already has a pending action of a given type
397
+ */
398
+ hasPendingAction(agentId, type) {
399
+ const stmt = this.db.prepare(`
400
+ SELECT 1 FROM action_queue
401
+ WHERE agent_id = ? AND type = ? AND status = 'pending'
402
+ LIMIT 1
403
+ `);
404
+ return !!stmt.get(agentId, type);
405
+ }
406
+ /**
407
+ * Reschedule a rate-limited action WITHOUT incrementing attempts
408
+ */
409
+ rescheduleAction(id, reason, executeAfter) {
410
+ const stmt = this.db.prepare(`
411
+ UPDATE action_queue
412
+ SET last_error = ?, execute_after = ?
413
+ WHERE id = ?
414
+ `);
415
+ stmt.run(reason, executeAfter.toISOString(), id);
416
+ }
417
+ getQueueStats() {
418
+ const stmt = this.db.prepare(`
419
+ SELECT status, COUNT(*) as count FROM action_queue GROUP BY status
420
+ `);
421
+ const rows = stmt.all();
422
+ const stats = { pending: 0, completed: 0, failed: 0 };
423
+ for (const row of rows) {
424
+ if (row.status === 'pending')
425
+ stats.pending = row.count;
426
+ else if (row.status === 'completed')
427
+ stats.completed = row.count;
428
+ else if (row.status === 'failed')
429
+ stats.failed = row.count;
430
+ }
431
+ return stats;
432
+ }
433
+ // ============================================================================
434
+ // State Operations
435
+ // ============================================================================
436
+ updateAgentState(agentId, state, updates) {
437
+ const fields = ['state = ?', 'updated_at = ?'];
438
+ const values = [state, new Date().toISOString()];
439
+ if (updates?.lastPollTime) {
440
+ fields.push('last_poll_time = ?');
441
+ values.push(updates.lastPollTime.toISOString());
442
+ }
443
+ if (updates?.lastActionTime) {
444
+ fields.push('last_action_time = ?');
445
+ values.push(updates.lastActionTime.toISOString());
446
+ }
447
+ if (updates?.errorCount !== undefined) {
448
+ fields.push('error_count = ?');
449
+ values.push(updates.errorCount);
450
+ }
451
+ if (updates?.lastError !== undefined) {
452
+ fields.push('last_error = ?');
453
+ values.push(updates.lastError);
454
+ }
455
+ values.push(agentId);
456
+ const stmt = this.db.prepare(`
457
+ UPDATE agent_state SET ${fields.join(', ')} WHERE agent_id = ?
458
+ `);
459
+ stmt.run(...values);
460
+ }
461
+ // ============================================================================
462
+ // Notification Tracking
463
+ // ============================================================================
464
+ isNotificationProcessed(notificationId) {
465
+ const stmt = this.db.prepare(`
466
+ SELECT 1 FROM processed_notifications WHERE notification_id = ?
467
+ `);
468
+ return stmt.get(notificationId) !== undefined;
469
+ }
470
+ markNotificationProcessed(notificationId, agentId) {
471
+ const stmt = this.db.prepare(`
472
+ INSERT OR IGNORE INTO processed_notifications (notification_id, agent_id, processed_at)
473
+ VALUES (?, ?, ?)
474
+ `);
475
+ stmt.run(notificationId, agentId, new Date().toISOString());
476
+ }
477
+ // ============================================================================
478
+ // Audit Log
479
+ // ============================================================================
480
+ logAction(agentId, action, targetId, targetType, success, error, metadata) {
481
+ const stmt = this.db.prepare(`
482
+ INSERT INTO audit_log (agent_id, action, target_id, target_type, success, error, metadata, created_at)
483
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
484
+ `);
485
+ stmt.run(agentId, action, targetId, targetType, success ? 1 : 0, error ?? null, metadata ? JSON.stringify(metadata) : null, new Date().toISOString());
486
+ }
487
+ // ============================================================================
488
+ // Cleanup
489
+ // ============================================================================
490
+ cleanupOldData(daysToKeep = 30) {
491
+ const cutoff = new Date();
492
+ cutoff.setDate(cutoff.getDate() - daysToKeep);
493
+ const cutoffStr = cutoff.toISOString();
494
+ this.db.prepare(`
495
+ DELETE FROM action_queue WHERE status IN ('completed', 'failed') AND created_at < ?
496
+ `).run(cutoffStr);
497
+ this.db.prepare(`
498
+ DELETE FROM processed_notifications WHERE processed_at < ?
499
+ `).run(cutoffStr);
500
+ this.db.prepare(`
501
+ DELETE FROM audit_log WHERE created_at < ?
502
+ `).run(cutoffStr);
503
+ }
504
+ // ============================================================================
505
+ // Agent Interaction Tracking (Reciprocity)
506
+ // ============================================================================
507
+ /**
508
+ * Record an interaction between two agents (for reciprocity tracking)
509
+ */
510
+ recordInteraction(agentId, otherAgentId, interactionType) {
511
+ const now = new Date().toISOString();
512
+ const stmt = this.db.prepare(`
513
+ INSERT INTO agent_interactions (agent_id, other_agent_id, interaction_type, interaction_count, first_interaction, last_interaction)
514
+ VALUES (?, ?, ?, 1, ?, ?)
515
+ ON CONFLICT(agent_id, other_agent_id, interaction_type) DO UPDATE SET
516
+ interaction_count = interaction_count + 1,
517
+ last_interaction = excluded.last_interaction
518
+ `);
519
+ stmt.run(agentId, otherAgentId, interactionType, now, now);
520
+ }
521
+ /**
522
+ * Get total interaction count between two agents (all types)
523
+ */
524
+ getInteractionCount(agentId, otherAgentId) {
525
+ const stmt = this.db.prepare(`
526
+ SELECT SUM(interaction_count) as total
527
+ FROM agent_interactions
528
+ WHERE agent_id = ? AND other_agent_id = ?
529
+ `);
530
+ const result = stmt.get(agentId, otherAgentId);
531
+ return result?.total || 0;
532
+ }
533
+ /**
534
+ * Get interactions from another agent towards this agent (for reciprocity)
535
+ * Returns how many times otherAgent has engaged with this agent
536
+ */
537
+ getIncomingInteractions(agentId, otherAgentId) {
538
+ const stmt = this.db.prepare(`
539
+ SELECT SUM(interaction_count) as total
540
+ FROM agent_interactions
541
+ WHERE agent_id = ? AND other_agent_id = ?
542
+ `);
543
+ // Note: Reversed - check how many times OTHER agent engaged with US
544
+ const result = stmt.get(otherAgentId, agentId);
545
+ return result?.total || 0;
546
+ }
547
+ /**
548
+ * Get most frequently interacted agents (social circle)
549
+ */
550
+ getTopInteractedAgents(agentId, limit = 10) {
551
+ const stmt = this.db.prepare(`
552
+ SELECT other_agent_id as agentId, SUM(interaction_count) as count
553
+ FROM agent_interactions
554
+ WHERE agent_id = ?
555
+ GROUP BY other_agent_id
556
+ ORDER BY count DESC
557
+ LIMIT ?
558
+ `);
559
+ return stmt.all(agentId, limit);
560
+ }
561
+ /**
562
+ * Get recent comments by an agent (for similarity checking)
563
+ */
564
+ getRecentCommentsByAgent(agentId, limit = 10) {
565
+ const stmt = this.db.prepare(`
566
+ SELECT metadata, created_at as createdAt
567
+ FROM audit_log
568
+ WHERE agent_id = ?
569
+ AND action = 'comment'
570
+ AND success = 1
571
+ AND metadata IS NOT NULL
572
+ ORDER BY created_at DESC
573
+ LIMIT ?
574
+ `);
575
+ const rows = stmt.all(agentId, limit);
576
+ return rows.map(row => {
577
+ try {
578
+ const meta = JSON.parse(row.metadata);
579
+ return {
580
+ body: meta.body || '',
581
+ createdAt: row.createdAt,
582
+ };
583
+ }
584
+ catch {
585
+ return { body: '', createdAt: row.createdAt };
586
+ }
587
+ }).filter(c => c.body); // Filter out empty bodies
588
+ }
589
+ close() {
590
+ this.db.close();
591
+ }
592
+ // ============================================================================
593
+ // Additional Methods
594
+ // ============================================================================
595
+ getAgentState(agentId) {
596
+ const stmt = this.db.prepare(`
597
+ SELECT * FROM agent_state WHERE agent_id = ?
598
+ `);
599
+ const row = stmt.get(agentId);
600
+ if (!row)
601
+ return null;
602
+ return {
603
+ state: row.state,
604
+ lastPollTime: row.last_poll_time ? new Date(row.last_poll_time) : null,
605
+ lastActionTime: row.last_action_time ? new Date(row.last_action_time) : null,
606
+ errorCount: row.error_count,
607
+ lastError: row.last_error,
608
+ };
609
+ }
610
+ getRecentLogs(limit = 10) {
611
+ const stmt = this.db.prepare(`
612
+ SELECT agent_id, action, target_id, success, error, created_at
613
+ FROM audit_log
614
+ ORDER BY created_at DESC
615
+ LIMIT ?
616
+ `);
617
+ const rows = stmt.all(limit);
618
+ return rows.map(row => ({
619
+ agentId: row.agent_id,
620
+ actionType: row.action,
621
+ targetId: row.target_id,
622
+ success: row.success === 1,
623
+ error: row.error,
624
+ timestamp: row.created_at,
625
+ }));
626
+ }
627
+ // ============================================================================
628
+ // Content Engagement Tracking (Moltbook-style)
629
+ // ============================================================================
630
+ hasEngaged(agentId, contentId, actionType) {
631
+ if (actionType) {
632
+ const stmt = this.db.prepare(`
633
+ SELECT 1 FROM content_engagements
634
+ WHERE agent_id = ? AND content_id = ? AND action_type = ?
635
+ `);
636
+ return stmt.get(agentId, contentId, actionType) !== undefined;
637
+ }
638
+ const stmt = this.db.prepare(`
639
+ SELECT 1 FROM content_engagements WHERE agent_id = ? AND content_id = ?
640
+ `);
641
+ return stmt.get(agentId, contentId) !== undefined;
642
+ }
643
+ recordEngagement(agentId, contentId, contentType, actionType) {
644
+ const stmt = this.db.prepare(`
645
+ INSERT OR IGNORE INTO content_engagements (agent_id, content_id, content_type, action_type, engaged_at)
646
+ VALUES (?, ?, ?, ?, ?)
647
+ `);
648
+ stmt.run(agentId, contentId, contentType, actionType, new Date().toISOString());
649
+ }
650
+ getEngagementCount(agentId) {
651
+ const stmt = this.db.prepare(`
652
+ SELECT COUNT(*) as count FROM content_engagements WHERE agent_id = ?
653
+ `);
654
+ const row = stmt.get(agentId);
655
+ return row.count;
656
+ }
657
+ getRecentEngagements(agentId, limit = 50) {
658
+ const stmt = this.db.prepare(`
659
+ SELECT content_id, content_type, action_type, engaged_at
660
+ FROM content_engagements
661
+ WHERE agent_id = ?
662
+ ORDER BY engaged_at DESC
663
+ LIMIT ?
664
+ `);
665
+ const rows = stmt.all(agentId, limit);
666
+ return rows.map(row => ({
667
+ contentId: row.content_id,
668
+ contentType: row.content_type,
669
+ actionType: row.action_type,
670
+ engagedAt: new Date(row.engaged_at),
671
+ }));
672
+ }
673
+ // ============================================================================
674
+ // Agent Follows
675
+ // ============================================================================
676
+ hasFollowed(followerId, followingId) {
677
+ const stmt = this.db.prepare(`
678
+ SELECT 1 FROM agent_follows WHERE follower_id = ? AND following_id = ?
679
+ `);
680
+ return stmt.get(followerId, followingId) !== undefined;
681
+ }
682
+ recordFollow(followerId, followingId) {
683
+ const stmt = this.db.prepare(`
684
+ INSERT OR IGNORE INTO agent_follows (follower_id, following_id, followed_at)
685
+ VALUES (?, ?, ?)
686
+ `);
687
+ stmt.run(followerId, followingId, new Date().toISOString());
688
+ }
689
+ getFollowingCount(agentId) {
690
+ const stmt = this.db.prepare(`
691
+ SELECT COUNT(*) as count FROM agent_follows WHERE follower_id = ?
692
+ `);
693
+ const row = stmt.get(agentId);
694
+ return row.count;
695
+ }
696
+ getFollowing(agentId) {
697
+ const stmt = this.db.prepare(`
698
+ SELECT following_id FROM agent_follows WHERE follower_id = ?
699
+ `);
700
+ const rows = stmt.all(agentId);
701
+ return rows.map(r => r.following_id);
702
+ }
703
+ // ============================================================================
704
+ // Sciencesub Memberships
705
+ // ============================================================================
706
+ hasJoinedSciencesub(agentId, slug) {
707
+ const stmt = this.db.prepare(`
708
+ SELECT 1 FROM sciencesub_memberships WHERE agent_id = ? AND sciencesub_slug = ?
709
+ `);
710
+ return stmt.get(agentId, slug) !== undefined;
711
+ }
712
+ recordSciencesubJoin(agentId, slug) {
713
+ const stmt = this.db.prepare(`
714
+ INSERT OR IGNORE INTO sciencesub_memberships (agent_id, sciencesub_slug, joined_at)
715
+ VALUES (?, ?, ?)
716
+ `);
717
+ stmt.run(agentId, slug, new Date().toISOString());
718
+ }
719
+ getJoinedSciencesubs(agentId) {
720
+ const stmt = this.db.prepare(`
721
+ SELECT sciencesub_slug FROM sciencesub_memberships WHERE agent_id = ?
722
+ `);
723
+ const rows = stmt.all(agentId);
724
+ return rows.map(r => r.sciencesub_slug);
725
+ }
726
+ getMembershipCount(agentId) {
727
+ const stmt = this.db.prepare(`
728
+ SELECT COUNT(*) as count FROM sciencesub_memberships WHERE agent_id = ?
729
+ `);
730
+ const row = stmt.get(agentId);
731
+ return row.count;
732
+ }
733
+ // ============================================================================
734
+ // Agent Activity Summary
735
+ // ============================================================================
736
+ getAgentActivitySummary(agentId) {
737
+ const stmt = this.db.prepare(`
738
+ SELECT action, COUNT(*) as count
739
+ FROM audit_log
740
+ WHERE agent_id = ? AND success = 1
741
+ GROUP BY action
742
+ `);
743
+ const rows = stmt.all(agentId);
744
+ const summary = {
745
+ papers: 0,
746
+ takes: 0,
747
+ comments: 0,
748
+ votes: 0,
749
+ follows: 0,
750
+ total: 0,
751
+ };
752
+ for (const row of rows) {
753
+ const action = row.action.toLowerCase();
754
+ if (action === 'paper' || action === 'create_paper') {
755
+ summary.papers += row.count;
756
+ }
757
+ else if (action === 'take' || action === 'create_take') {
758
+ summary.takes += row.count;
759
+ }
760
+ else if (action === 'comment' || action === 'create_comment') {
761
+ summary.comments += row.count;
762
+ }
763
+ else if (action === 'vote') {
764
+ summary.votes += row.count;
765
+ }
766
+ else if (action === 'follow') {
767
+ summary.follows += row.count;
768
+ }
769
+ summary.total += row.count;
770
+ }
771
+ return summary;
772
+ }
773
+ getAllAgentsActivitySummary() {
774
+ const stmt = this.db.prepare(`
775
+ SELECT a.id, a.handle, al.action, COUNT(*) as count
776
+ FROM agents a
777
+ LEFT JOIN audit_log al ON a.id = al.agent_id AND al.success = 1
778
+ GROUP BY a.id, a.handle, al.action
779
+ `);
780
+ const rows = stmt.all();
781
+ // Group by agent
782
+ const agentMap = new Map();
783
+ for (const row of rows) {
784
+ if (!agentMap.has(row.id)) {
785
+ agentMap.set(row.id, {
786
+ agentId: row.id,
787
+ handle: row.handle,
788
+ papers: 0,
789
+ takes: 0,
790
+ comments: 0,
791
+ votes: 0,
792
+ follows: 0,
793
+ total: 0,
794
+ });
795
+ }
796
+ const agent = agentMap.get(row.id);
797
+ const action = (row.action || '').toLowerCase();
798
+ if (action === 'paper' || action === 'create_paper') {
799
+ agent.papers += row.count;
800
+ }
801
+ else if (action === 'take' || action === 'create_take') {
802
+ agent.takes += row.count;
803
+ }
804
+ else if (action === 'comment' || action === 'create_comment') {
805
+ agent.comments += row.count;
806
+ }
807
+ else if (action === 'vote') {
808
+ agent.votes += row.count;
809
+ }
810
+ else if (action === 'follow') {
811
+ agent.follows += row.count;
812
+ }
813
+ if (row.action) {
814
+ agent.total += row.count;
815
+ }
816
+ }
817
+ return Array.from(agentMap.values());
818
+ }
819
+ }
820
+ // Singleton
821
+ let dbInstance = null;
822
+ export function createDatabase(dbPath) {
823
+ dbInstance = new RuntimeDatabase(dbPath);
824
+ return dbInstance;
825
+ }
826
+ export function getDatabase() {
827
+ if (!dbInstance) {
828
+ throw new Error('Database not initialized. Call createDatabase first.');
829
+ }
830
+ return dbInstance;
831
+ }
832
+ export function closeDatabase() {
833
+ if (dbInstance) {
834
+ dbInstance.close();
835
+ dbInstance = null;
836
+ }
837
+ }
838
+ //# sourceMappingURL=database.js.map