@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,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
|