@moltium/core 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/dist/index.d.cts +564 -0
- package/dist/index.d.ts +564 -0
- package/dist/index.js +1692 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1631 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +58 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1631 @@
|
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
import winston from "winston";
|
|
3
|
+
var logLevel = process.env.LOG_LEVEL || "info";
|
|
4
|
+
function createLogger(label) {
|
|
5
|
+
return winston.createLogger({
|
|
6
|
+
level: logLevel,
|
|
7
|
+
format: winston.format.combine(
|
|
8
|
+
winston.format.timestamp(),
|
|
9
|
+
winston.format.label({ label }),
|
|
10
|
+
winston.format.printf(({ timestamp, level, label: label2, message, ...meta }) => {
|
|
11
|
+
const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : "";
|
|
12
|
+
return `${timestamp} [${label2}] ${level}: ${message}${metaStr}`;
|
|
13
|
+
})
|
|
14
|
+
),
|
|
15
|
+
transports: [new winston.transports.Console()]
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/agent/scheduler.ts
|
|
20
|
+
var logger = createLogger("Scheduler");
|
|
21
|
+
var Scheduler = class {
|
|
22
|
+
jobs = [];
|
|
23
|
+
timers = /* @__PURE__ */ new Map();
|
|
24
|
+
running = false;
|
|
25
|
+
addJob(job) {
|
|
26
|
+
this.jobs.push(job);
|
|
27
|
+
logger.debug(`Job registered: ${job.name} (every ${job.intervalMs}ms)`);
|
|
28
|
+
}
|
|
29
|
+
removeJob(name) {
|
|
30
|
+
this.jobs = this.jobs.filter((j) => j.name !== name);
|
|
31
|
+
const timer = this.timers.get(name);
|
|
32
|
+
if (timer) {
|
|
33
|
+
clearInterval(timer);
|
|
34
|
+
this.timers.delete(name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
start() {
|
|
38
|
+
if (this.running) return;
|
|
39
|
+
this.running = true;
|
|
40
|
+
for (const job of this.jobs) {
|
|
41
|
+
const timer = setInterval(async () => {
|
|
42
|
+
try {
|
|
43
|
+
logger.debug(`Running job: ${job.name}`);
|
|
44
|
+
await job.handler();
|
|
45
|
+
job.lastRun = Date.now();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.error(`Job failed: ${job.name}`, {
|
|
48
|
+
error: error instanceof Error ? error.message : String(error)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}, job.intervalMs);
|
|
52
|
+
this.timers.set(job.name, timer);
|
|
53
|
+
}
|
|
54
|
+
logger.info(`Scheduler started with ${this.jobs.length} jobs`);
|
|
55
|
+
}
|
|
56
|
+
stop() {
|
|
57
|
+
this.running = false;
|
|
58
|
+
for (const [name, timer] of this.timers) {
|
|
59
|
+
clearInterval(timer);
|
|
60
|
+
}
|
|
61
|
+
this.timers.clear();
|
|
62
|
+
logger.info("Scheduler stopped");
|
|
63
|
+
}
|
|
64
|
+
isRunning() {
|
|
65
|
+
return this.running;
|
|
66
|
+
}
|
|
67
|
+
getJobs() {
|
|
68
|
+
return [...this.jobs];
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/actions/registry.ts
|
|
73
|
+
var ActionRegistry = class {
|
|
74
|
+
actions = /* @__PURE__ */ new Map();
|
|
75
|
+
register(action) {
|
|
76
|
+
if (this.actions.has(action.name)) {
|
|
77
|
+
throw new Error(`Action "${action.name}" is already registered`);
|
|
78
|
+
}
|
|
79
|
+
this.actions.set(action.name, action);
|
|
80
|
+
}
|
|
81
|
+
unregister(name) {
|
|
82
|
+
this.actions.delete(name);
|
|
83
|
+
}
|
|
84
|
+
get(name) {
|
|
85
|
+
return this.actions.get(name);
|
|
86
|
+
}
|
|
87
|
+
has(name) {
|
|
88
|
+
return this.actions.has(name);
|
|
89
|
+
}
|
|
90
|
+
list() {
|
|
91
|
+
return Array.from(this.actions.values());
|
|
92
|
+
}
|
|
93
|
+
listNames() {
|
|
94
|
+
return Array.from(this.actions.keys());
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/actions/ActionHandler.ts
|
|
99
|
+
var logger2 = createLogger("ActionHandler");
|
|
100
|
+
var ActionExecutionError = class extends Error {
|
|
101
|
+
constructor(message, actionName, recoverable = true) {
|
|
102
|
+
super(message);
|
|
103
|
+
this.actionName = actionName;
|
|
104
|
+
this.recoverable = recoverable;
|
|
105
|
+
this.name = "ActionExecutionError";
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var ActionHandler = class {
|
|
109
|
+
registry;
|
|
110
|
+
constructor(registry) {
|
|
111
|
+
this.registry = registry;
|
|
112
|
+
}
|
|
113
|
+
async execute(actionName, context) {
|
|
114
|
+
const action = this.registry.get(actionName);
|
|
115
|
+
if (!action) {
|
|
116
|
+
throw new ActionExecutionError(
|
|
117
|
+
`Action "${actionName}" not found in registry`,
|
|
118
|
+
actionName,
|
|
119
|
+
false
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
logger2.info(`Executing action: ${actionName}`);
|
|
123
|
+
try {
|
|
124
|
+
const result = await action.execute(context);
|
|
125
|
+
logger2.info(`Action completed: ${actionName}`, { success: result.success });
|
|
126
|
+
return result;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
129
|
+
logger2.error(`Action failed: ${actionName}`, { error: message });
|
|
130
|
+
throw new ActionExecutionError(
|
|
131
|
+
`Action "${actionName}" failed: ${message}`,
|
|
132
|
+
actionName
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
getAvailableActions() {
|
|
137
|
+
return this.registry.list().map((action) => ({
|
|
138
|
+
name: action.name,
|
|
139
|
+
description: action.description
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// src/actions/built-in/social.ts
|
|
145
|
+
var postSocialUpdateAction = {
|
|
146
|
+
name: "post_social_update",
|
|
147
|
+
description: "Post a status update to configured social platforms",
|
|
148
|
+
async execute(context) {
|
|
149
|
+
const { content, platforms } = context.parameters;
|
|
150
|
+
if (!content) {
|
|
151
|
+
return { success: false, error: "Missing required parameter: content" };
|
|
152
|
+
}
|
|
153
|
+
const results = {};
|
|
154
|
+
const socialAdapters = context.agent?.socialAdapters || {};
|
|
155
|
+
const targetPlatforms = platforms || Object.keys(socialAdapters);
|
|
156
|
+
for (const platform of targetPlatforms) {
|
|
157
|
+
const adapter = socialAdapters[platform];
|
|
158
|
+
if (!adapter) continue;
|
|
159
|
+
try {
|
|
160
|
+
const result = await adapter.post(content);
|
|
161
|
+
results[platform] = result;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
results[platform] = {
|
|
164
|
+
error: error instanceof Error ? error.message : String(error)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { success: true, data: results };
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var respondToMentionAction = {
|
|
172
|
+
name: "respond_to_mention",
|
|
173
|
+
description: "Respond to a social platform mention",
|
|
174
|
+
async execute(context) {
|
|
175
|
+
const { mentionId, content, platform } = context.parameters;
|
|
176
|
+
if (!mentionId || !content || !platform) {
|
|
177
|
+
return { success: false, error: "Missing required parameters: mentionId, content, platform" };
|
|
178
|
+
}
|
|
179
|
+
const adapter = context.agent?.socialAdapters?.[platform];
|
|
180
|
+
if (!adapter) {
|
|
181
|
+
return { success: false, error: `No adapter configured for platform: ${platform}` };
|
|
182
|
+
}
|
|
183
|
+
const result = await adapter.reply(mentionId, content);
|
|
184
|
+
return { success: true, data: result };
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
var dmUserAction = {
|
|
188
|
+
name: "dm_user",
|
|
189
|
+
description: "Send a direct message to a user on a social platform",
|
|
190
|
+
async execute(context) {
|
|
191
|
+
const { userId, content, platform } = context.parameters;
|
|
192
|
+
if (!userId || !content || !platform) {
|
|
193
|
+
return { success: false, error: "Missing required parameters: userId, content, platform" };
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
success: false,
|
|
197
|
+
error: `DM not yet implemented for platform: ${platform}`
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var scheduleTaskAction = {
|
|
202
|
+
name: "schedule_task",
|
|
203
|
+
description: "Schedule a task for later execution",
|
|
204
|
+
async execute(context) {
|
|
205
|
+
const { taskName, delay, parameters } = context.parameters;
|
|
206
|
+
if (!taskName || delay === void 0) {
|
|
207
|
+
return { success: false, error: "Missing required parameters: taskName, delay" };
|
|
208
|
+
}
|
|
209
|
+
const scheduledTask = {
|
|
210
|
+
taskName,
|
|
211
|
+
executeAt: Date.now() + delay * 1e3,
|
|
212
|
+
parameters: parameters || {}
|
|
213
|
+
};
|
|
214
|
+
await context.memory.store(`scheduled:${taskName}:${Date.now()}`, scheduledTask);
|
|
215
|
+
return { success: true, data: scheduledTask };
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// src/actions/built-in/index.ts
|
|
220
|
+
var builtInActions = [
|
|
221
|
+
postSocialUpdateAction,
|
|
222
|
+
respondToMentionAction,
|
|
223
|
+
dmUserAction,
|
|
224
|
+
scheduleTaskAction
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
// src/llm/prompt.ts
|
|
228
|
+
function buildSystemPrompt(config) {
|
|
229
|
+
const parts = [];
|
|
230
|
+
parts.push(`You are ${config.name}, an autonomous AI agent.`);
|
|
231
|
+
if (config.type) {
|
|
232
|
+
parts.push(`Your role: ${config.type}.`);
|
|
233
|
+
}
|
|
234
|
+
if (config.personality.bio) {
|
|
235
|
+
parts.push(`
|
|
236
|
+
Background:
|
|
237
|
+
${config.personality.bio}`);
|
|
238
|
+
}
|
|
239
|
+
if (config.personality.traits.length > 0) {
|
|
240
|
+
parts.push(`
|
|
241
|
+
Personality traits: ${config.personality.traits.join(", ")}`);
|
|
242
|
+
}
|
|
243
|
+
if (config.llm.systemPrompt) {
|
|
244
|
+
parts.push(`
|
|
245
|
+
${config.llm.systemPrompt}`);
|
|
246
|
+
}
|
|
247
|
+
return parts.join("\n");
|
|
248
|
+
}
|
|
249
|
+
function buildDecisionPrompt(context, availableActions) {
|
|
250
|
+
const actionList = availableActions.map((a) => `- ${a.name}: ${a.description}`).join("\n");
|
|
251
|
+
return `Given the current context, decide what action to take next.
|
|
252
|
+
|
|
253
|
+
Available actions:
|
|
254
|
+
${actionList}
|
|
255
|
+
|
|
256
|
+
Current context:
|
|
257
|
+
${JSON.stringify(context, null, 2)}
|
|
258
|
+
|
|
259
|
+
Respond with a JSON object:
|
|
260
|
+
{
|
|
261
|
+
"action": "<action_name>",
|
|
262
|
+
"reasoning": "<brief explanation>",
|
|
263
|
+
"parameters": { <any parameters for the action> }
|
|
264
|
+
}`;
|
|
265
|
+
}
|
|
266
|
+
function buildSkillPrompt(skillName, skillDescription, parameters) {
|
|
267
|
+
return `Execute the following skill: ${skillName}
|
|
268
|
+
|
|
269
|
+
Instructions:
|
|
270
|
+
${skillDescription}
|
|
271
|
+
|
|
272
|
+
Parameters:
|
|
273
|
+
${JSON.stringify(parameters, null, 2)}
|
|
274
|
+
|
|
275
|
+
Execute these instructions step by step and return the result as a JSON object:
|
|
276
|
+
{
|
|
277
|
+
"success": true/false,
|
|
278
|
+
"data": { <result data> },
|
|
279
|
+
"reasoning": "<what you did and why>"
|
|
280
|
+
}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/actions/interpreter.ts
|
|
284
|
+
var logger3 = createLogger("MarkdownInterpreter");
|
|
285
|
+
var LLMInterpretationError = class extends Error {
|
|
286
|
+
constructor(message) {
|
|
287
|
+
super(message);
|
|
288
|
+
this.name = "LLMInterpretationError";
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
function createMarkdownAction(name, description, llmProvider) {
|
|
292
|
+
return {
|
|
293
|
+
name,
|
|
294
|
+
description,
|
|
295
|
+
execute: async (context) => {
|
|
296
|
+
logger3.info(`Interpreting markdown skill: ${name}`);
|
|
297
|
+
const prompt = buildSkillPrompt(name, description, context.parameters);
|
|
298
|
+
try {
|
|
299
|
+
const response = await llmProvider.generateText(prompt, {
|
|
300
|
+
temperature: 0.7,
|
|
301
|
+
maxTokens: 2048
|
|
302
|
+
});
|
|
303
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
304
|
+
if (!jsonMatch) {
|
|
305
|
+
throw new LLMInterpretationError(
|
|
306
|
+
`LLM did not return valid JSON for skill "${name}"`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
const result = JSON.parse(jsonMatch[0]);
|
|
310
|
+
return {
|
|
311
|
+
success: result.success ?? true,
|
|
312
|
+
data: result.data || result
|
|
313
|
+
};
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (error instanceof LLMInterpretationError) throw error;
|
|
316
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
317
|
+
logger3.error(`Markdown skill interpretation failed: ${name}`, { error: message });
|
|
318
|
+
throw new LLMInterpretationError(
|
|
319
|
+
`Failed to interpret skill "${name}": ${message}`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/memory/Memory.ts
|
|
327
|
+
var Memory = class {
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// src/memory/shortterm.ts
|
|
331
|
+
var ShortTermMemory = class extends Memory {
|
|
332
|
+
cache = /* @__PURE__ */ new Map();
|
|
333
|
+
store_ = /* @__PURE__ */ new Map();
|
|
334
|
+
experiences = [];
|
|
335
|
+
async remember(key, value, ttl) {
|
|
336
|
+
this.cache.set(key, {
|
|
337
|
+
value,
|
|
338
|
+
expiresAt: ttl ? Date.now() + ttl * 1e3 : void 0
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
async recall(key) {
|
|
342
|
+
const entry = this.cache.get(key);
|
|
343
|
+
if (!entry) return void 0;
|
|
344
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
345
|
+
this.cache.delete(key);
|
|
346
|
+
return void 0;
|
|
347
|
+
}
|
|
348
|
+
return entry.value;
|
|
349
|
+
}
|
|
350
|
+
async forget(key) {
|
|
351
|
+
this.cache.delete(key);
|
|
352
|
+
}
|
|
353
|
+
async store(key, value) {
|
|
354
|
+
this.store_.set(key, value);
|
|
355
|
+
}
|
|
356
|
+
async retrieve(key) {
|
|
357
|
+
return this.store_.get(key);
|
|
358
|
+
}
|
|
359
|
+
async delete(key) {
|
|
360
|
+
this.store_.delete(key);
|
|
361
|
+
}
|
|
362
|
+
async addExperience(experience) {
|
|
363
|
+
this.experiences.push(experience);
|
|
364
|
+
}
|
|
365
|
+
async queryExperiences(query, limit = 10) {
|
|
366
|
+
const queryLower = query.toLowerCase();
|
|
367
|
+
return this.experiences.filter(
|
|
368
|
+
(exp) => exp.type.toLowerCase().includes(queryLower) || exp.action.toLowerCase().includes(queryLower)
|
|
369
|
+
).slice(-limit);
|
|
370
|
+
}
|
|
371
|
+
async clear() {
|
|
372
|
+
this.cache.clear();
|
|
373
|
+
this.store_.clear();
|
|
374
|
+
this.experiences = [];
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// src/llm/anthropic.ts
|
|
379
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
380
|
+
|
|
381
|
+
// src/llm/Provider.ts
|
|
382
|
+
var LLMProvider = class {
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// src/llm/anthropic.ts
|
|
386
|
+
var AnthropicProvider = class extends LLMProvider {
|
|
387
|
+
name = "anthropic";
|
|
388
|
+
client;
|
|
389
|
+
model;
|
|
390
|
+
constructor(apiKey, model = "claude-sonnet-4-20250514") {
|
|
391
|
+
super();
|
|
392
|
+
this.client = new Anthropic({ apiKey });
|
|
393
|
+
this.model = model;
|
|
394
|
+
}
|
|
395
|
+
async generateText(prompt, options) {
|
|
396
|
+
const response = await this.client.messages.create({
|
|
397
|
+
model: this.model,
|
|
398
|
+
max_tokens: options?.maxTokens || 2048,
|
|
399
|
+
messages: [{ role: "user", content: prompt }],
|
|
400
|
+
...options?.systemPrompt ? { system: options.systemPrompt } : {},
|
|
401
|
+
...options?.temperature !== void 0 ? { temperature: options.temperature } : {},
|
|
402
|
+
...options?.stopSequences ? { stop_sequences: options.stopSequences } : {}
|
|
403
|
+
});
|
|
404
|
+
const textBlock = response.content.find((block) => block.type === "text");
|
|
405
|
+
return textBlock ? textBlock.text : "";
|
|
406
|
+
}
|
|
407
|
+
async generateStructured(prompt, schema, options) {
|
|
408
|
+
const structuredPrompt = `${prompt}
|
|
409
|
+
|
|
410
|
+
Respond with a JSON object matching this schema:
|
|
411
|
+
${JSON.stringify(schema, null, 2)}
|
|
412
|
+
|
|
413
|
+
Respond with valid JSON only, no other text.`;
|
|
414
|
+
const text = await this.generateText(structuredPrompt, options);
|
|
415
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
416
|
+
if (!jsonMatch) {
|
|
417
|
+
throw new Error("LLM did not return valid JSON");
|
|
418
|
+
}
|
|
419
|
+
return JSON.parse(jsonMatch[0]);
|
|
420
|
+
}
|
|
421
|
+
async chat(messages, options) {
|
|
422
|
+
const systemMessages = messages.filter((m) => m.role === "system");
|
|
423
|
+
const chatMessages = messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }));
|
|
424
|
+
const response = await this.client.messages.create({
|
|
425
|
+
model: this.model,
|
|
426
|
+
max_tokens: options?.maxTokens || 2048,
|
|
427
|
+
messages: chatMessages,
|
|
428
|
+
...systemMessages.length > 0 ? { system: systemMessages.map((m) => m.content).join("\n") } : {},
|
|
429
|
+
...options?.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
430
|
+
});
|
|
431
|
+
const textBlock = response.content.find((block) => block.type === "text");
|
|
432
|
+
return { role: "assistant", content: textBlock ? textBlock.text : "" };
|
|
433
|
+
}
|
|
434
|
+
async *streamGenerate(prompt, options) {
|
|
435
|
+
const stream = this.client.messages.stream({
|
|
436
|
+
model: this.model,
|
|
437
|
+
max_tokens: options?.maxTokens || 2048,
|
|
438
|
+
messages: [{ role: "user", content: prompt }],
|
|
439
|
+
...options?.systemPrompt ? { system: options.systemPrompt } : {},
|
|
440
|
+
...options?.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
441
|
+
});
|
|
442
|
+
for await (const event of stream) {
|
|
443
|
+
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
444
|
+
yield event.delta.text;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// src/llm/openai.ts
|
|
451
|
+
import OpenAI from "openai";
|
|
452
|
+
var OpenAIProvider = class extends LLMProvider {
|
|
453
|
+
name = "openai";
|
|
454
|
+
client;
|
|
455
|
+
model;
|
|
456
|
+
constructor(apiKey, model = "gpt-4o") {
|
|
457
|
+
super();
|
|
458
|
+
this.client = new OpenAI({ apiKey });
|
|
459
|
+
this.model = model;
|
|
460
|
+
}
|
|
461
|
+
async generateText(prompt, options) {
|
|
462
|
+
const messages = [];
|
|
463
|
+
if (options?.systemPrompt) {
|
|
464
|
+
messages.push({ role: "system", content: options.systemPrompt });
|
|
465
|
+
}
|
|
466
|
+
messages.push({ role: "user", content: prompt });
|
|
467
|
+
const response = await this.client.chat.completions.create({
|
|
468
|
+
model: this.model,
|
|
469
|
+
messages,
|
|
470
|
+
max_tokens: options?.maxTokens || 2048,
|
|
471
|
+
...options?.temperature !== void 0 ? { temperature: options.temperature } : {},
|
|
472
|
+
...options?.stopSequences ? { stop: options.stopSequences } : {}
|
|
473
|
+
});
|
|
474
|
+
return response.choices[0]?.message?.content || "";
|
|
475
|
+
}
|
|
476
|
+
async generateStructured(prompt, schema, options) {
|
|
477
|
+
const structuredPrompt = `${prompt}
|
|
478
|
+
|
|
479
|
+
Respond with a JSON object matching this schema:
|
|
480
|
+
${JSON.stringify(schema, null, 2)}
|
|
481
|
+
|
|
482
|
+
Respond with valid JSON only, no other text.`;
|
|
483
|
+
const text = await this.generateText(structuredPrompt, options);
|
|
484
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
485
|
+
if (!jsonMatch) {
|
|
486
|
+
throw new Error("LLM did not return valid JSON");
|
|
487
|
+
}
|
|
488
|
+
return JSON.parse(jsonMatch[0]);
|
|
489
|
+
}
|
|
490
|
+
async chat(messages, options) {
|
|
491
|
+
const openaiMessages = messages.map((m) => ({
|
|
492
|
+
role: m.role,
|
|
493
|
+
content: m.content
|
|
494
|
+
}));
|
|
495
|
+
const response = await this.client.chat.completions.create({
|
|
496
|
+
model: this.model,
|
|
497
|
+
messages: openaiMessages,
|
|
498
|
+
max_tokens: options?.maxTokens || 2048,
|
|
499
|
+
...options?.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
500
|
+
});
|
|
501
|
+
return { role: "assistant", content: response.choices[0]?.message?.content || "" };
|
|
502
|
+
}
|
|
503
|
+
async *streamGenerate(prompt, options) {
|
|
504
|
+
const messages = [];
|
|
505
|
+
if (options?.systemPrompt) {
|
|
506
|
+
messages.push({ role: "system", content: options.systemPrompt });
|
|
507
|
+
}
|
|
508
|
+
messages.push({ role: "user", content: prompt });
|
|
509
|
+
const stream = await this.client.chat.completions.create({
|
|
510
|
+
model: this.model,
|
|
511
|
+
messages,
|
|
512
|
+
max_tokens: options?.maxTokens || 2048,
|
|
513
|
+
stream: true,
|
|
514
|
+
...options?.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
515
|
+
});
|
|
516
|
+
for await (const chunk of stream) {
|
|
517
|
+
const delta = chunk.choices[0]?.delta?.content;
|
|
518
|
+
if (delta) yield delta;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// src/social/moltbook.ts
|
|
524
|
+
import axios from "axios";
|
|
525
|
+
|
|
526
|
+
// src/social/SocialAdapter.ts
|
|
527
|
+
var SocialAdapter = class {
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// src/social/moltbook.ts
|
|
531
|
+
var MoltbookAdapter = class extends SocialAdapter {
|
|
532
|
+
platform = "moltbook";
|
|
533
|
+
client;
|
|
534
|
+
config;
|
|
535
|
+
constructor(config) {
|
|
536
|
+
super();
|
|
537
|
+
this.config = config;
|
|
538
|
+
this.client = axios.create({
|
|
539
|
+
baseURL: config.baseUrl || "https://api.moltbook.com",
|
|
540
|
+
headers: {
|
|
541
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
542
|
+
"Content-Type": "application/json"
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
async connect() {
|
|
547
|
+
await this.client.get("/me");
|
|
548
|
+
}
|
|
549
|
+
async disconnect() {
|
|
550
|
+
}
|
|
551
|
+
async post(content) {
|
|
552
|
+
const response = await this.client.post("/posts", { content });
|
|
553
|
+
return {
|
|
554
|
+
id: response.data.id,
|
|
555
|
+
url: response.data.url,
|
|
556
|
+
timestamp: new Date(response.data.created_at)
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
async reply(postId, content) {
|
|
560
|
+
const response = await this.client.post(`/posts/${postId}/replies`, { content });
|
|
561
|
+
return {
|
|
562
|
+
id: response.data.id,
|
|
563
|
+
parentId: postId,
|
|
564
|
+
url: response.data.url,
|
|
565
|
+
timestamp: new Date(response.data.created_at)
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
async like(postId) {
|
|
569
|
+
await this.client.post(`/posts/${postId}/like`);
|
|
570
|
+
}
|
|
571
|
+
async follow(userId) {
|
|
572
|
+
await this.client.post(`/users/${userId}/follow`);
|
|
573
|
+
}
|
|
574
|
+
async getMentions() {
|
|
575
|
+
const response = await this.client.get("/mentions");
|
|
576
|
+
return response.data.mentions.map((m) => ({
|
|
577
|
+
id: m.id,
|
|
578
|
+
authorId: m.author_id,
|
|
579
|
+
authorName: m.author_name,
|
|
580
|
+
content: m.content,
|
|
581
|
+
timestamp: new Date(m.created_at),
|
|
582
|
+
platform: "moltbook"
|
|
583
|
+
}));
|
|
584
|
+
}
|
|
585
|
+
async getFeed(options) {
|
|
586
|
+
const params = {};
|
|
587
|
+
if (options?.limit) params.limit = options.limit;
|
|
588
|
+
if (options?.cursor) params.cursor = options.cursor;
|
|
589
|
+
if (options?.since) params.since = options.since.toISOString();
|
|
590
|
+
const response = await this.client.get("/feed", { params });
|
|
591
|
+
return response.data.posts.map((p) => ({
|
|
592
|
+
id: p.id,
|
|
593
|
+
authorId: p.author_id,
|
|
594
|
+
authorName: p.author_name,
|
|
595
|
+
content: p.content,
|
|
596
|
+
timestamp: new Date(p.created_at),
|
|
597
|
+
platform: "moltbook",
|
|
598
|
+
likes: p.likes_count,
|
|
599
|
+
replies: p.replies_count,
|
|
600
|
+
reposts: p.reposts_count,
|
|
601
|
+
mentions: p.mentions || [],
|
|
602
|
+
url: p.url
|
|
603
|
+
}));
|
|
604
|
+
}
|
|
605
|
+
async getProfile(userId) {
|
|
606
|
+
const response = await this.client.get(`/users/${userId}`);
|
|
607
|
+
return {
|
|
608
|
+
id: response.data.id,
|
|
609
|
+
name: response.data.name,
|
|
610
|
+
bio: response.data.bio,
|
|
611
|
+
followers: response.data.followers_count,
|
|
612
|
+
following: response.data.following_count,
|
|
613
|
+
platform: "moltbook"
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// src/social/twitter.ts
|
|
619
|
+
var TwitterAdapter = class extends SocialAdapter {
|
|
620
|
+
platform = "twitter";
|
|
621
|
+
config;
|
|
622
|
+
client;
|
|
623
|
+
// twitter-api-v2 client
|
|
624
|
+
constructor(config) {
|
|
625
|
+
super();
|
|
626
|
+
this.config = config;
|
|
627
|
+
}
|
|
628
|
+
async connect() {
|
|
629
|
+
const { TwitterApi } = await import("twitter-api-v2");
|
|
630
|
+
this.client = new TwitterApi({
|
|
631
|
+
appKey: this.config.credentials.apiKey,
|
|
632
|
+
appSecret: this.config.credentials.apiSecret,
|
|
633
|
+
accessToken: this.config.credentials.accessToken,
|
|
634
|
+
accessSecret: this.config.credentials.accessSecret
|
|
635
|
+
});
|
|
636
|
+
await this.client.v2.me();
|
|
637
|
+
}
|
|
638
|
+
async disconnect() {
|
|
639
|
+
this.client = null;
|
|
640
|
+
}
|
|
641
|
+
async post(content) {
|
|
642
|
+
const tweet = await this.client.v2.tweet(content);
|
|
643
|
+
return {
|
|
644
|
+
id: tweet.data.id,
|
|
645
|
+
url: `https://twitter.com/i/status/${tweet.data.id}`,
|
|
646
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
async reply(tweetId, content) {
|
|
650
|
+
const tweet = await this.client.v2.reply(content, tweetId);
|
|
651
|
+
return {
|
|
652
|
+
id: tweet.data.id,
|
|
653
|
+
parentId: tweetId,
|
|
654
|
+
url: `https://twitter.com/i/status/${tweet.data.id}`,
|
|
655
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
async like(tweetId) {
|
|
659
|
+
const me = await this.client.v2.me();
|
|
660
|
+
await this.client.v2.like(me.data.id, tweetId);
|
|
661
|
+
}
|
|
662
|
+
async follow(userId) {
|
|
663
|
+
const me = await this.client.v2.me();
|
|
664
|
+
await this.client.v2.follow(me.data.id, userId);
|
|
665
|
+
}
|
|
666
|
+
async getMentions() {
|
|
667
|
+
const me = await this.client.v2.me();
|
|
668
|
+
const mentions = await this.client.v2.userMentionTimeline(me.data.id, {
|
|
669
|
+
max_results: 20,
|
|
670
|
+
expansions: ["author_id"],
|
|
671
|
+
"tweet.fields": ["created_at"],
|
|
672
|
+
"user.fields": ["name", "username"]
|
|
673
|
+
});
|
|
674
|
+
const users = /* @__PURE__ */ new Map();
|
|
675
|
+
if (mentions.includes?.users) {
|
|
676
|
+
for (const user of mentions.includes.users) {
|
|
677
|
+
users.set(user.id, user.username);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return (mentions.data?.data || []).map((tweet) => ({
|
|
681
|
+
id: tweet.id,
|
|
682
|
+
authorId: tweet.author_id,
|
|
683
|
+
authorName: users.get(tweet.author_id) || "unknown",
|
|
684
|
+
content: tweet.text,
|
|
685
|
+
timestamp: new Date(tweet.created_at),
|
|
686
|
+
platform: "twitter"
|
|
687
|
+
}));
|
|
688
|
+
}
|
|
689
|
+
async getFeed(options) {
|
|
690
|
+
const me = await this.client.v2.me();
|
|
691
|
+
const timeline = await this.client.v2.userTimeline(me.data.id, {
|
|
692
|
+
max_results: options?.limit || 20,
|
|
693
|
+
"tweet.fields": ["created_at", "public_metrics"]
|
|
694
|
+
});
|
|
695
|
+
return (timeline.data?.data || []).map((tweet) => ({
|
|
696
|
+
id: tweet.id,
|
|
697
|
+
authorId: me.data.id,
|
|
698
|
+
authorName: me.data.username,
|
|
699
|
+
content: tweet.text,
|
|
700
|
+
timestamp: new Date(tweet.created_at),
|
|
701
|
+
platform: "twitter",
|
|
702
|
+
likes: tweet.public_metrics?.like_count,
|
|
703
|
+
replies: tweet.public_metrics?.reply_count,
|
|
704
|
+
reposts: tweet.public_metrics?.retweet_count
|
|
705
|
+
}));
|
|
706
|
+
}
|
|
707
|
+
async getProfile(userId) {
|
|
708
|
+
const user = await this.client.v2.user(userId, {
|
|
709
|
+
"user.fields": ["description", "public_metrics"]
|
|
710
|
+
});
|
|
711
|
+
return {
|
|
712
|
+
id: user.data.id,
|
|
713
|
+
name: user.data.name,
|
|
714
|
+
bio: user.data.description,
|
|
715
|
+
followers: user.data.public_metrics?.followers_count,
|
|
716
|
+
following: user.data.public_metrics?.following_count,
|
|
717
|
+
platform: "twitter"
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
// src/agent/Agent.ts
|
|
723
|
+
var logger4 = createLogger("Agent");
|
|
724
|
+
var Agent = class {
|
|
725
|
+
config;
|
|
726
|
+
state = "idle";
|
|
727
|
+
llm;
|
|
728
|
+
memory_;
|
|
729
|
+
scheduler;
|
|
730
|
+
actionRegistry;
|
|
731
|
+
actionHandler;
|
|
732
|
+
hooks = {};
|
|
733
|
+
tickTimer;
|
|
734
|
+
systemPrompt;
|
|
735
|
+
socialAdapters = {};
|
|
736
|
+
constructor(config, hooks) {
|
|
737
|
+
this.config = config;
|
|
738
|
+
this.hooks = hooks || {};
|
|
739
|
+
this.memory_ = new ShortTermMemory();
|
|
740
|
+
this.scheduler = new Scheduler();
|
|
741
|
+
this.actionRegistry = new ActionRegistry();
|
|
742
|
+
this.actionHandler = new ActionHandler(this.actionRegistry);
|
|
743
|
+
this.systemPrompt = buildSystemPrompt(config);
|
|
744
|
+
}
|
|
745
|
+
get name() {
|
|
746
|
+
return this.config.name;
|
|
747
|
+
}
|
|
748
|
+
getState() {
|
|
749
|
+
return this.state;
|
|
750
|
+
}
|
|
751
|
+
getMemory() {
|
|
752
|
+
return this.memory_;
|
|
753
|
+
}
|
|
754
|
+
getLLM() {
|
|
755
|
+
return this.llm;
|
|
756
|
+
}
|
|
757
|
+
getActionRegistry() {
|
|
758
|
+
return this.actionRegistry;
|
|
759
|
+
}
|
|
760
|
+
// ── Configuration Methods ──
|
|
761
|
+
setSystemPrompt(prompt) {
|
|
762
|
+
this.systemPrompt = prompt;
|
|
763
|
+
}
|
|
764
|
+
appendSystemPrompt(additional) {
|
|
765
|
+
this.systemPrompt += "\n\n" + additional;
|
|
766
|
+
}
|
|
767
|
+
getSystemPrompt() {
|
|
768
|
+
return this.systemPrompt;
|
|
769
|
+
}
|
|
770
|
+
setHooks(hooks) {
|
|
771
|
+
this.hooks = hooks;
|
|
772
|
+
}
|
|
773
|
+
mergeHooks(hooks) {
|
|
774
|
+
this.hooks = { ...this.hooks, ...hooks };
|
|
775
|
+
}
|
|
776
|
+
setMemory(memory) {
|
|
777
|
+
this.memory_ = memory;
|
|
778
|
+
}
|
|
779
|
+
async preloadMemory(entries) {
|
|
780
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
781
|
+
await this.memory_.remember(key, value);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
registerAction(action) {
|
|
785
|
+
this.actionRegistry.register(action);
|
|
786
|
+
}
|
|
787
|
+
// ── Lifecycle ──
|
|
788
|
+
async init() {
|
|
789
|
+
this.state = "initializing";
|
|
790
|
+
logger4.info(`Initializing agent: ${this.config.name}`);
|
|
791
|
+
this.initLLM();
|
|
792
|
+
this.registerActions();
|
|
793
|
+
await this.initSocialAdapters();
|
|
794
|
+
if (this.hooks.onInit) {
|
|
795
|
+
await this.hooks.onInit(this);
|
|
796
|
+
}
|
|
797
|
+
logger4.info(`Agent initialized: ${this.config.name}`);
|
|
798
|
+
}
|
|
799
|
+
async start() {
|
|
800
|
+
if (this.state !== "initializing" && this.state !== "idle") {
|
|
801
|
+
throw new Error(`Cannot start agent in state: ${this.state}`);
|
|
802
|
+
}
|
|
803
|
+
if (this.state === "idle") {
|
|
804
|
+
await this.init();
|
|
805
|
+
}
|
|
806
|
+
this.state = "running";
|
|
807
|
+
logger4.info(`Agent started: ${this.config.name}`);
|
|
808
|
+
if (this.hooks.onStart) {
|
|
809
|
+
await this.hooks.onStart(this);
|
|
810
|
+
}
|
|
811
|
+
this.scheduler.start();
|
|
812
|
+
if (this.config.behaviors.autonomous) {
|
|
813
|
+
this.startAutonomousLoop();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
async stop() {
|
|
817
|
+
this.state = "stopping";
|
|
818
|
+
logger4.info(`Stopping agent: ${this.config.name}`);
|
|
819
|
+
if (this.tickTimer) {
|
|
820
|
+
clearInterval(this.tickTimer);
|
|
821
|
+
this.tickTimer = void 0;
|
|
822
|
+
}
|
|
823
|
+
this.scheduler.stop();
|
|
824
|
+
for (const adapter of Object.values(this.socialAdapters)) {
|
|
825
|
+
await adapter.disconnect();
|
|
826
|
+
}
|
|
827
|
+
if (this.hooks.onShutdown) {
|
|
828
|
+
await this.hooks.onShutdown(this);
|
|
829
|
+
}
|
|
830
|
+
this.state = "stopped";
|
|
831
|
+
logger4.info(`Agent stopped: ${this.config.name}`);
|
|
832
|
+
}
|
|
833
|
+
// ── Decision Loop ──
|
|
834
|
+
async think() {
|
|
835
|
+
const context = await this.gatherContext();
|
|
836
|
+
const availableActions = this.actionHandler.getAvailableActions();
|
|
837
|
+
const prompt = buildDecisionPrompt(context, availableActions);
|
|
838
|
+
const decision = await this.llm.generateStructured(
|
|
839
|
+
prompt,
|
|
840
|
+
{
|
|
841
|
+
type: "object",
|
|
842
|
+
properties: {
|
|
843
|
+
action: { type: "string" },
|
|
844
|
+
reasoning: { type: "string" },
|
|
845
|
+
parameters: { type: "object" }
|
|
846
|
+
},
|
|
847
|
+
required: ["action", "reasoning", "parameters"]
|
|
848
|
+
},
|
|
849
|
+
{ systemPrompt: this.systemPrompt, temperature: this.config.llm.temperature }
|
|
850
|
+
);
|
|
851
|
+
logger4.debug(`Decision: ${decision.action} \u2014 ${decision.reasoning}`);
|
|
852
|
+
return decision;
|
|
853
|
+
}
|
|
854
|
+
async act(actionName, parameters = {}) {
|
|
855
|
+
const context = {
|
|
856
|
+
agent: this,
|
|
857
|
+
memory: this.memory_,
|
|
858
|
+
llm: this.llm,
|
|
859
|
+
parameters
|
|
860
|
+
};
|
|
861
|
+
return this.actionHandler.execute(actionName, context);
|
|
862
|
+
}
|
|
863
|
+
async learn(experience) {
|
|
864
|
+
await this.memory_.addExperience(experience);
|
|
865
|
+
}
|
|
866
|
+
async tick() {
|
|
867
|
+
if (this.state !== "running") return;
|
|
868
|
+
try {
|
|
869
|
+
if (this.hooks.onTick) {
|
|
870
|
+
await this.hooks.onTick(this);
|
|
871
|
+
}
|
|
872
|
+
if (this.isSleeping()) return;
|
|
873
|
+
const decision = await this.think();
|
|
874
|
+
const result = await this.act(decision.action, decision.parameters);
|
|
875
|
+
await this.learn({
|
|
876
|
+
type: "action",
|
|
877
|
+
action: decision.action,
|
|
878
|
+
result,
|
|
879
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
880
|
+
context: { reasoning: decision.reasoning }
|
|
881
|
+
});
|
|
882
|
+
if (this.config.webhooks?.onAction) {
|
|
883
|
+
await this.fireWebhook("onAction", decision, result);
|
|
884
|
+
}
|
|
885
|
+
} catch (error) {
|
|
886
|
+
logger4.error(`Tick error: ${error instanceof Error ? error.message : error}`);
|
|
887
|
+
if (this.hooks.onError && error instanceof Error) {
|
|
888
|
+
await this.hooks.onError(error, this);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
// ── Markdown skill registration ──
|
|
893
|
+
registerMarkdownSkills(skills) {
|
|
894
|
+
for (const skill of skills) {
|
|
895
|
+
const action = createMarkdownAction(skill.name, skill.description, this.llm);
|
|
896
|
+
this.actionRegistry.register(action);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
// ── Internal ──
|
|
900
|
+
initLLM() {
|
|
901
|
+
const { provider, apiKey, model } = this.config.llm;
|
|
902
|
+
switch (provider) {
|
|
903
|
+
case "anthropic":
|
|
904
|
+
this.llm = new AnthropicProvider(apiKey, model);
|
|
905
|
+
break;
|
|
906
|
+
case "openai":
|
|
907
|
+
this.llm = new OpenAIProvider(apiKey, model);
|
|
908
|
+
break;
|
|
909
|
+
default:
|
|
910
|
+
throw new Error(`Unsupported LLM provider: ${provider}`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
registerActions() {
|
|
914
|
+
for (const action of builtInActions) {
|
|
915
|
+
if (this.config.actions.includes(action.name)) {
|
|
916
|
+
this.actionRegistry.register(action);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (this.config.customActions) {
|
|
920
|
+
for (const action of this.config.customActions) {
|
|
921
|
+
this.actionRegistry.register(action);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
async initSocialAdapters() {
|
|
926
|
+
const { social } = this.config;
|
|
927
|
+
if (social.moltbook?.enabled) {
|
|
928
|
+
const adapter = new MoltbookAdapter(social.moltbook);
|
|
929
|
+
try {
|
|
930
|
+
await adapter.connect();
|
|
931
|
+
this.socialAdapters["moltbook"] = adapter;
|
|
932
|
+
logger4.info("Moltbook adapter connected");
|
|
933
|
+
} catch (error) {
|
|
934
|
+
logger4.warn(`Failed to connect Moltbook adapter: ${error}`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (social.twitter?.enabled) {
|
|
938
|
+
const adapter = new TwitterAdapter(social.twitter);
|
|
939
|
+
try {
|
|
940
|
+
await adapter.connect();
|
|
941
|
+
this.socialAdapters["twitter"] = adapter;
|
|
942
|
+
logger4.info("Twitter adapter connected");
|
|
943
|
+
} catch (error) {
|
|
944
|
+
logger4.warn(`Failed to connect Twitter adapter: ${error}`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
startAutonomousLoop() {
|
|
949
|
+
const actionsPerHour = typeof this.config.behaviors.actionsPerHour === "function" ? this.config.behaviors.actionsPerHour({}) : this.config.behaviors.actionsPerHour || 5;
|
|
950
|
+
const intervalMs = Math.floor(36e5 / actionsPerHour);
|
|
951
|
+
this.tickTimer = setInterval(() => this.tick(), intervalMs);
|
|
952
|
+
logger4.info(`Autonomous loop started: ~${actionsPerHour} actions/hour (every ${intervalMs}ms)`);
|
|
953
|
+
}
|
|
954
|
+
isSleeping() {
|
|
955
|
+
const schedule = this.config.behaviors.sleepSchedule;
|
|
956
|
+
if (!schedule) return false;
|
|
957
|
+
const now = /* @__PURE__ */ new Date();
|
|
958
|
+
const hour = now.getHours();
|
|
959
|
+
if (schedule.start > schedule.end) {
|
|
960
|
+
return hour >= schedule.start || hour < schedule.end;
|
|
961
|
+
}
|
|
962
|
+
return hour >= schedule.start && hour < schedule.end;
|
|
963
|
+
}
|
|
964
|
+
async gatherContext() {
|
|
965
|
+
return {
|
|
966
|
+
agentName: this.config.name,
|
|
967
|
+
agentType: this.config.type,
|
|
968
|
+
currentTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
969
|
+
state: this.state,
|
|
970
|
+
personality: this.config.personality.traits
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
async fireWebhook(event, ...args) {
|
|
974
|
+
try {
|
|
975
|
+
const hook = this.config.webhooks?.[event];
|
|
976
|
+
if (typeof hook === "function") {
|
|
977
|
+
await hook(...args);
|
|
978
|
+
} else if (typeof hook === "string") {
|
|
979
|
+
const { default: axios2 } = await import("axios");
|
|
980
|
+
await axios2.post(hook, { event, data: args });
|
|
981
|
+
}
|
|
982
|
+
} catch (error) {
|
|
983
|
+
logger4.error(`Webhook ${event} failed: ${error}`);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
// src/config/ConfigLoader.ts
|
|
989
|
+
import { readFileSync, existsSync } from "fs";
|
|
990
|
+
import { resolve } from "path";
|
|
991
|
+
import { pathToFileURL } from "url";
|
|
992
|
+
|
|
993
|
+
// src/config/MarkdownParser.ts
|
|
994
|
+
import matter from "gray-matter";
|
|
995
|
+
var MarkdownParser = class {
|
|
996
|
+
parse(markdown) {
|
|
997
|
+
const { data: frontmatter, content } = matter(markdown);
|
|
998
|
+
const sections = this.parseSections(content);
|
|
999
|
+
const identity = this.findSection(sections, "Identity");
|
|
1000
|
+
const bio = this.findSection(sections, "Bio");
|
|
1001
|
+
const social = this.findSection(sections, "Social Platforms");
|
|
1002
|
+
const behaviors = this.findSection(sections, "Behaviors");
|
|
1003
|
+
const memory = this.findSection(sections, "Memory");
|
|
1004
|
+
const skills = this.findSection(sections, "Skills");
|
|
1005
|
+
const scheduling = this.findSection(sections, "Scheduling");
|
|
1006
|
+
const identityData = this.parseIdentity(identity);
|
|
1007
|
+
const bioText = bio ? bio.content.trim() : identityData?.personality?.bio || "";
|
|
1008
|
+
const config = {
|
|
1009
|
+
...identityData,
|
|
1010
|
+
personality: {
|
|
1011
|
+
traits: identityData?.personality?.traits || [],
|
|
1012
|
+
bio: bioText
|
|
1013
|
+
},
|
|
1014
|
+
...social ? { social: this.parseSocial(social) } : {},
|
|
1015
|
+
...behaviors ? { behaviors: this.parseBehaviors(behaviors) } : {},
|
|
1016
|
+
...memory ? { memory: this.parseMemory(memory) } : {},
|
|
1017
|
+
actions: [],
|
|
1018
|
+
...frontmatter
|
|
1019
|
+
};
|
|
1020
|
+
if (skills) {
|
|
1021
|
+
config.actions = skills.children.map((s) => this.toSnakeCase(s.title));
|
|
1022
|
+
}
|
|
1023
|
+
return config;
|
|
1024
|
+
}
|
|
1025
|
+
parseSkills(markdown) {
|
|
1026
|
+
const { content } = matter(markdown);
|
|
1027
|
+
const sections = this.parseSections(content);
|
|
1028
|
+
const skills = this.findSection(sections, "Skills");
|
|
1029
|
+
if (!skills) return [];
|
|
1030
|
+
return skills.children.map((child) => ({
|
|
1031
|
+
name: this.toSnakeCase(child.title),
|
|
1032
|
+
description: child.content.trim()
|
|
1033
|
+
}));
|
|
1034
|
+
}
|
|
1035
|
+
parseSections(content) {
|
|
1036
|
+
const lines = content.split("\n");
|
|
1037
|
+
const root = [];
|
|
1038
|
+
const stack = [];
|
|
1039
|
+
for (const line of lines) {
|
|
1040
|
+
const match = line.match(/^(#{1,6})\s+(.+)/);
|
|
1041
|
+
if (match) {
|
|
1042
|
+
const level = match[1].length;
|
|
1043
|
+
const title = match[2].trim();
|
|
1044
|
+
const section = { title, level, content: "", children: [] };
|
|
1045
|
+
while (stack.length > 0 && stack[stack.length - 1].level >= level) {
|
|
1046
|
+
stack.pop();
|
|
1047
|
+
}
|
|
1048
|
+
if (stack.length === 0) {
|
|
1049
|
+
root.push(section);
|
|
1050
|
+
} else {
|
|
1051
|
+
stack[stack.length - 1].section.children.push(section);
|
|
1052
|
+
}
|
|
1053
|
+
stack.push({ level, section });
|
|
1054
|
+
} else if (stack.length > 0) {
|
|
1055
|
+
stack[stack.length - 1].section.content += line + "\n";
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return root;
|
|
1059
|
+
}
|
|
1060
|
+
findSection(sections, title) {
|
|
1061
|
+
for (const section of sections) {
|
|
1062
|
+
if (section.title.toLowerCase() === title.toLowerCase()) return section;
|
|
1063
|
+
const found = this.findSection(section.children, title);
|
|
1064
|
+
if (found) return found;
|
|
1065
|
+
}
|
|
1066
|
+
return void 0;
|
|
1067
|
+
}
|
|
1068
|
+
parseIdentity(section) {
|
|
1069
|
+
if (!section) return void 0;
|
|
1070
|
+
const fields = this.parseKeyValueLines(section.content);
|
|
1071
|
+
return {
|
|
1072
|
+
name: fields["name"] || "Unnamed Agent",
|
|
1073
|
+
type: fields["type"],
|
|
1074
|
+
personality: {
|
|
1075
|
+
traits: (fields["personality"] || "").split(",").map((t) => t.trim()).filter(Boolean),
|
|
1076
|
+
bio: ""
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
parseSocial(section) {
|
|
1081
|
+
const social = {};
|
|
1082
|
+
for (const child of section.children) {
|
|
1083
|
+
const platform = child.title.toLowerCase();
|
|
1084
|
+
const fields = this.parseKeyValueLines(child.content);
|
|
1085
|
+
social[platform] = {
|
|
1086
|
+
enabled: fields["enabled"] === "true",
|
|
1087
|
+
...fields
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
return social;
|
|
1091
|
+
}
|
|
1092
|
+
parseBehaviors(section) {
|
|
1093
|
+
const fields = this.parseKeyValueLines(section.content);
|
|
1094
|
+
return {
|
|
1095
|
+
autonomous: fields["autonomous"] === "true",
|
|
1096
|
+
decisionMaking: fields["decision_making"] || "llm-driven",
|
|
1097
|
+
actionsPerHour: fields["actions_per_hour"] ? parseInt(fields["actions_per_hour"], 10) : void 0,
|
|
1098
|
+
sleepSchedule: fields["sleep_schedule"] ? this.parseSleepSchedule(fields["sleep_schedule"]) : void 0
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
parseMemory(section) {
|
|
1102
|
+
const fields = this.parseKeyValueLines(section.content);
|
|
1103
|
+
return {
|
|
1104
|
+
type: fields["type"] || "memory",
|
|
1105
|
+
retention: fields["retention"]
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
parseSleepSchedule(value) {
|
|
1109
|
+
const match = value.match(/(\d{1,2}):?\d{0,2}\s*-\s*(\d{1,2}):?\d{0,2}\s*(.*)?/);
|
|
1110
|
+
if (!match) return { start: 22, end: 6 };
|
|
1111
|
+
return {
|
|
1112
|
+
start: parseInt(match[1], 10),
|
|
1113
|
+
end: parseInt(match[2], 10),
|
|
1114
|
+
timezone: match[3]?.trim() || void 0
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
parseKeyValueLines(content) {
|
|
1118
|
+
const result = {};
|
|
1119
|
+
for (const line of content.split("\n")) {
|
|
1120
|
+
const match = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*):\s*(.+)/);
|
|
1121
|
+
if (match) {
|
|
1122
|
+
result[match[1].trim()] = match[2].trim();
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return result;
|
|
1126
|
+
}
|
|
1127
|
+
toSnakeCase(str) {
|
|
1128
|
+
return str.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
// src/config/types.ts
|
|
1133
|
+
import { z } from "zod";
|
|
1134
|
+
var AgentConfigSchema = z.object({
|
|
1135
|
+
name: z.string().min(1),
|
|
1136
|
+
type: z.string().optional(),
|
|
1137
|
+
personality: z.object({
|
|
1138
|
+
traits: z.array(z.string()),
|
|
1139
|
+
bio: z.string()
|
|
1140
|
+
}).passthrough(),
|
|
1141
|
+
llm: z.object({
|
|
1142
|
+
provider: z.enum(["anthropic", "openai"]),
|
|
1143
|
+
model: z.string(),
|
|
1144
|
+
apiKey: z.string(),
|
|
1145
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
1146
|
+
maxTokens: z.number().positive().optional(),
|
|
1147
|
+
systemPrompt: z.string().optional()
|
|
1148
|
+
}),
|
|
1149
|
+
social: z.record(z.unknown()).optional(),
|
|
1150
|
+
behaviors: z.object({
|
|
1151
|
+
autonomous: z.boolean(),
|
|
1152
|
+
decisionMaking: z.enum(["llm-driven", "rule-based", "hybrid"]),
|
|
1153
|
+
actionsPerHour: z.number().optional(),
|
|
1154
|
+
sleepSchedule: z.object({
|
|
1155
|
+
start: z.number(),
|
|
1156
|
+
end: z.number(),
|
|
1157
|
+
timezone: z.string().optional()
|
|
1158
|
+
}).optional()
|
|
1159
|
+
}),
|
|
1160
|
+
memory: z.object({
|
|
1161
|
+
type: z.enum(["memory", "redis", "postgres", "hybrid"]),
|
|
1162
|
+
retention: z.string().optional()
|
|
1163
|
+
}).passthrough(),
|
|
1164
|
+
actions: z.array(z.string())
|
|
1165
|
+
}).passthrough();
|
|
1166
|
+
|
|
1167
|
+
// src/config/validator.ts
|
|
1168
|
+
var ConfigValidationError = class extends Error {
|
|
1169
|
+
constructor(message, issues) {
|
|
1170
|
+
super(message);
|
|
1171
|
+
this.issues = issues;
|
|
1172
|
+
this.name = "ConfigValidationError";
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
function validateConfig(config) {
|
|
1176
|
+
const result = AgentConfigSchema.safeParse(config);
|
|
1177
|
+
if (!result.success) {
|
|
1178
|
+
const issues = result.error.issues.map((issue) => ({
|
|
1179
|
+
path: issue.path.join("."),
|
|
1180
|
+
message: issue.message
|
|
1181
|
+
}));
|
|
1182
|
+
throw new ConfigValidationError(
|
|
1183
|
+
`Invalid agent configuration:
|
|
1184
|
+
${issues.map((i) => ` - ${i.path}: ${i.message}`).join("\n")}`,
|
|
1185
|
+
issues
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
return result.data;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// src/config/ConfigLoader.ts
|
|
1192
|
+
var ConfigurationError = class extends Error {
|
|
1193
|
+
constructor(message) {
|
|
1194
|
+
super(message);
|
|
1195
|
+
this.name = "ConfigurationError";
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
var ConfigLoader = class {
|
|
1199
|
+
markdownParser = new MarkdownParser();
|
|
1200
|
+
async load(directory) {
|
|
1201
|
+
const codeConfigPath = resolve(directory, "agent.config.ts");
|
|
1202
|
+
const mdConfigPath = resolve(directory, "agent.md");
|
|
1203
|
+
if (existsSync(codeConfigPath)) {
|
|
1204
|
+
return { config: await this.loadCode(codeConfigPath), type: "code" };
|
|
1205
|
+
}
|
|
1206
|
+
if (existsSync(mdConfigPath)) {
|
|
1207
|
+
return { config: await this.loadMarkdown(mdConfigPath), type: "markdown" };
|
|
1208
|
+
}
|
|
1209
|
+
throw new ConfigurationError(
|
|
1210
|
+
`No agent configuration found in ${directory}. Expected agent.config.ts or agent.md`
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
async loadCode(filePath) {
|
|
1214
|
+
try {
|
|
1215
|
+
const { createJiti } = await import("jiti");
|
|
1216
|
+
const jiti = createJiti(pathToFileURL(filePath).href, {
|
|
1217
|
+
interopDefault: true,
|
|
1218
|
+
moduleCache: false
|
|
1219
|
+
});
|
|
1220
|
+
const module = await jiti.import(filePath);
|
|
1221
|
+
const config = module.default || module;
|
|
1222
|
+
return validateConfig(config);
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
if (error instanceof Error && error.name === "ConfigValidationError") throw error;
|
|
1225
|
+
throw new ConfigurationError(`Failed to load code config from ${filePath}: ${error}`);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async loadMarkdown(filePath) {
|
|
1229
|
+
try {
|
|
1230
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1231
|
+
const partialConfig = this.markdownParser.parse(content);
|
|
1232
|
+
const config = {
|
|
1233
|
+
...partialConfig,
|
|
1234
|
+
llm: partialConfig.llm || {
|
|
1235
|
+
provider: process.env.LLM_PROVIDER || "anthropic",
|
|
1236
|
+
model: process.env.LLM_MODEL || "claude-sonnet-4-20250514",
|
|
1237
|
+
apiKey: process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || ""
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
return validateConfig(config);
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
if (error instanceof Error && error.name === "ConfigValidationError") throw error;
|
|
1243
|
+
throw new ConfigurationError(`Failed to load markdown config from ${filePath}: ${error}`);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
|
|
1248
|
+
// src/memory/longterm.ts
|
|
1249
|
+
var logger5 = createLogger("LongTermMemory");
|
|
1250
|
+
var LongTermMemory = class extends Memory {
|
|
1251
|
+
redisConfig;
|
|
1252
|
+
postgresConfig;
|
|
1253
|
+
redis = null;
|
|
1254
|
+
pg = null;
|
|
1255
|
+
initialized = false;
|
|
1256
|
+
constructor(options = {}) {
|
|
1257
|
+
super();
|
|
1258
|
+
this.redisConfig = options.redis;
|
|
1259
|
+
this.postgresConfig = options.postgres;
|
|
1260
|
+
}
|
|
1261
|
+
async init() {
|
|
1262
|
+
if (this.initialized) return;
|
|
1263
|
+
if (this.redisConfig) {
|
|
1264
|
+
await this.initRedis();
|
|
1265
|
+
}
|
|
1266
|
+
if (this.postgresConfig) {
|
|
1267
|
+
await this.initPostgres();
|
|
1268
|
+
}
|
|
1269
|
+
this.initialized = true;
|
|
1270
|
+
}
|
|
1271
|
+
// ── Redis: Short-term cache operations ──
|
|
1272
|
+
async remember(key, value, ttl) {
|
|
1273
|
+
await this.ensureRedis();
|
|
1274
|
+
const serialized = JSON.stringify(value);
|
|
1275
|
+
const prefixedKey = this.redisKey(key);
|
|
1276
|
+
if (ttl) {
|
|
1277
|
+
await this.redis.setex(prefixedKey, ttl, serialized);
|
|
1278
|
+
} else {
|
|
1279
|
+
const defaultTtl = this.redisConfig?.ttl || 86400;
|
|
1280
|
+
await this.redis.setex(prefixedKey, defaultTtl, serialized);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
async recall(key) {
|
|
1284
|
+
await this.ensureRedis();
|
|
1285
|
+
const data = await this.redis.get(this.redisKey(key));
|
|
1286
|
+
if (data === null) return void 0;
|
|
1287
|
+
try {
|
|
1288
|
+
return JSON.parse(data);
|
|
1289
|
+
} catch {
|
|
1290
|
+
return data;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
async forget(key) {
|
|
1294
|
+
await this.ensureRedis();
|
|
1295
|
+
await this.redis.del(this.redisKey(key));
|
|
1296
|
+
}
|
|
1297
|
+
// ── Postgres: Persistent storage operations ──
|
|
1298
|
+
async store(key, value) {
|
|
1299
|
+
await this.ensurePostgres();
|
|
1300
|
+
const table = this.pgTable();
|
|
1301
|
+
await this.pg.query(
|
|
1302
|
+
`INSERT INTO ${table} (key, value, updated_at)
|
|
1303
|
+
VALUES ($1, $2, NOW())
|
|
1304
|
+
ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()`,
|
|
1305
|
+
[key, JSON.stringify(value)]
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
async retrieve(key) {
|
|
1309
|
+
await this.ensurePostgres();
|
|
1310
|
+
const table = this.pgTable();
|
|
1311
|
+
const result = await this.pg.query(
|
|
1312
|
+
`SELECT value FROM ${table} WHERE key = $1`,
|
|
1313
|
+
[key]
|
|
1314
|
+
);
|
|
1315
|
+
if (result.rows.length === 0) return void 0;
|
|
1316
|
+
try {
|
|
1317
|
+
return JSON.parse(result.rows[0].value);
|
|
1318
|
+
} catch {
|
|
1319
|
+
return result.rows[0].value;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
async delete(key) {
|
|
1323
|
+
await this.ensurePostgres();
|
|
1324
|
+
const table = this.pgTable();
|
|
1325
|
+
await this.pg.query(`DELETE FROM ${table} WHERE key = $1`, [key]);
|
|
1326
|
+
}
|
|
1327
|
+
// ── Postgres: Experience/semantic storage ──
|
|
1328
|
+
async addExperience(experience) {
|
|
1329
|
+
await this.ensurePostgres();
|
|
1330
|
+
const table = this.pgExperienceTable();
|
|
1331
|
+
await this.pg.query(
|
|
1332
|
+
`INSERT INTO ${table} (type, action, result, context, timestamp)
|
|
1333
|
+
VALUES ($1, $2, $3, $4, $5)`,
|
|
1334
|
+
[
|
|
1335
|
+
experience.type,
|
|
1336
|
+
experience.action,
|
|
1337
|
+
JSON.stringify(experience.result),
|
|
1338
|
+
JSON.stringify(experience.context || {}),
|
|
1339
|
+
experience.timestamp
|
|
1340
|
+
]
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
async queryExperiences(query, limit = 20) {
|
|
1344
|
+
await this.ensurePostgres();
|
|
1345
|
+
const table = this.pgExperienceTable();
|
|
1346
|
+
const result = await this.pg.query(
|
|
1347
|
+
`SELECT type, action, result, context, timestamp FROM ${table}
|
|
1348
|
+
WHERE action ILIKE $1 OR context::text ILIKE $1
|
|
1349
|
+
ORDER BY timestamp DESC
|
|
1350
|
+
LIMIT $2`,
|
|
1351
|
+
[`%${query}%`, limit]
|
|
1352
|
+
);
|
|
1353
|
+
return result.rows.map((row) => ({
|
|
1354
|
+
type: row.type,
|
|
1355
|
+
action: row.action,
|
|
1356
|
+
result: typeof row.result === "string" ? JSON.parse(row.result) : row.result,
|
|
1357
|
+
context: typeof row.context === "string" ? JSON.parse(row.context) : row.context,
|
|
1358
|
+
timestamp: new Date(row.timestamp)
|
|
1359
|
+
}));
|
|
1360
|
+
}
|
|
1361
|
+
async clear() {
|
|
1362
|
+
if (this.redis) {
|
|
1363
|
+
const prefix = this.redisConfig?.keyPrefix || "moltium:";
|
|
1364
|
+
const keys = await this.redis.keys(`${prefix}*`);
|
|
1365
|
+
if (keys.length > 0) {
|
|
1366
|
+
await this.redis.del(...keys);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
if (this.pg) {
|
|
1370
|
+
await this.pg.query(`DELETE FROM ${this.pgTable()}`);
|
|
1371
|
+
await this.pg.query(`DELETE FROM ${this.pgExperienceTable()}`);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
async disconnect() {
|
|
1375
|
+
if (this.redis) {
|
|
1376
|
+
await this.redis.quit();
|
|
1377
|
+
this.redis = null;
|
|
1378
|
+
}
|
|
1379
|
+
if (this.pg) {
|
|
1380
|
+
await this.pg.end();
|
|
1381
|
+
this.pg = null;
|
|
1382
|
+
}
|
|
1383
|
+
this.initialized = false;
|
|
1384
|
+
}
|
|
1385
|
+
// ── Schema setup (call once to create tables) ──
|
|
1386
|
+
async ensureSchema() {
|
|
1387
|
+
await this.ensurePostgres();
|
|
1388
|
+
const table = this.pgTable();
|
|
1389
|
+
const expTable = this.pgExperienceTable();
|
|
1390
|
+
const schema = this.postgresConfig?.schema || "public";
|
|
1391
|
+
await this.pg.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
|
|
1392
|
+
await this.pg.query(`
|
|
1393
|
+
CREATE TABLE IF NOT EXISTS ${table} (
|
|
1394
|
+
key TEXT PRIMARY KEY,
|
|
1395
|
+
value JSONB NOT NULL,
|
|
1396
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
1397
|
+
)
|
|
1398
|
+
`);
|
|
1399
|
+
await this.pg.query(`
|
|
1400
|
+
CREATE TABLE IF NOT EXISTS ${expTable} (
|
|
1401
|
+
id SERIAL PRIMARY KEY,
|
|
1402
|
+
type TEXT NOT NULL,
|
|
1403
|
+
action TEXT NOT NULL,
|
|
1404
|
+
result JSONB NOT NULL,
|
|
1405
|
+
context JSONB DEFAULT '{}',
|
|
1406
|
+
timestamp TIMESTAMPTZ NOT NULL,
|
|
1407
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
1408
|
+
)
|
|
1409
|
+
`);
|
|
1410
|
+
await this.pg.query(`
|
|
1411
|
+
CREATE INDEX IF NOT EXISTS idx_${this.sanitizeName()}_experiences_action
|
|
1412
|
+
ON ${expTable} (action)
|
|
1413
|
+
`);
|
|
1414
|
+
await this.pg.query(`
|
|
1415
|
+
CREATE INDEX IF NOT EXISTS idx_${this.sanitizeName()}_experiences_timestamp
|
|
1416
|
+
ON ${expTable} (timestamp DESC)
|
|
1417
|
+
`);
|
|
1418
|
+
}
|
|
1419
|
+
// ── Internal helpers ──
|
|
1420
|
+
async initRedis() {
|
|
1421
|
+
if (!this.redisConfig) throw new Error("Redis config not provided");
|
|
1422
|
+
try {
|
|
1423
|
+
const { default: Redis } = await import("ioredis");
|
|
1424
|
+
this.redis = new Redis(this.redisConfig.url);
|
|
1425
|
+
logger5.info("Redis connection established");
|
|
1426
|
+
} catch (error) {
|
|
1427
|
+
logger5.error(`Failed to connect to Redis: ${error}`);
|
|
1428
|
+
throw error;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
async initPostgres() {
|
|
1432
|
+
if (!this.postgresConfig) throw new Error("Postgres config not provided");
|
|
1433
|
+
try {
|
|
1434
|
+
const pg = await import("pg");
|
|
1435
|
+
const Pool = pg.default?.Pool || pg.Pool;
|
|
1436
|
+
this.pg = new Pool({
|
|
1437
|
+
connectionString: this.postgresConfig.url
|
|
1438
|
+
});
|
|
1439
|
+
await this.pg.query("SELECT 1");
|
|
1440
|
+
logger5.info("Postgres connection established");
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
logger5.error(`Failed to connect to Postgres: ${error}`);
|
|
1443
|
+
throw error;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
async ensureRedis() {
|
|
1447
|
+
if (!this.redis) {
|
|
1448
|
+
if (!this.redisConfig) {
|
|
1449
|
+
throw new Error("Redis not configured. Provide redis config in memory settings.");
|
|
1450
|
+
}
|
|
1451
|
+
await this.initRedis();
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
async ensurePostgres() {
|
|
1455
|
+
if (!this.pg) {
|
|
1456
|
+
if (!this.postgresConfig) {
|
|
1457
|
+
throw new Error("Postgres not configured. Provide postgres config in memory settings.");
|
|
1458
|
+
}
|
|
1459
|
+
await this.initPostgres();
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
redisKey(key) {
|
|
1463
|
+
const prefix = this.redisConfig?.keyPrefix || "moltium:";
|
|
1464
|
+
return `${prefix}${key}`;
|
|
1465
|
+
}
|
|
1466
|
+
pgTable() {
|
|
1467
|
+
const schema = this.postgresConfig?.schema || "public";
|
|
1468
|
+
const table = this.postgresConfig?.tableName || "moltium_memory";
|
|
1469
|
+
return `${schema}.${table}`;
|
|
1470
|
+
}
|
|
1471
|
+
pgExperienceTable() {
|
|
1472
|
+
const schema = this.postgresConfig?.schema || "public";
|
|
1473
|
+
const table = this.postgresConfig?.tableName || "moltium_memory";
|
|
1474
|
+
return `${schema}.${table}_experiences`;
|
|
1475
|
+
}
|
|
1476
|
+
sanitizeName() {
|
|
1477
|
+
return (this.postgresConfig?.tableName || "moltium_memory").replace(/[^a-z0-9_]/g, "_");
|
|
1478
|
+
}
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1481
|
+
// src/server/app.ts
|
|
1482
|
+
import express from "express";
|
|
1483
|
+
|
|
1484
|
+
// src/server/routes.ts
|
|
1485
|
+
import { Router } from "express";
|
|
1486
|
+
function createRoutes(agent) {
|
|
1487
|
+
const router = Router();
|
|
1488
|
+
router.get("/health", (_req, res) => {
|
|
1489
|
+
res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1490
|
+
});
|
|
1491
|
+
router.get("/status", (_req, res) => {
|
|
1492
|
+
res.json({
|
|
1493
|
+
name: agent.name,
|
|
1494
|
+
state: agent.getState(),
|
|
1495
|
+
type: agent.config.type || "general",
|
|
1496
|
+
uptime: process.uptime(),
|
|
1497
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1498
|
+
});
|
|
1499
|
+
});
|
|
1500
|
+
router.post("/message", async (req, res) => {
|
|
1501
|
+
try {
|
|
1502
|
+
const { message } = req.body;
|
|
1503
|
+
if (!message) {
|
|
1504
|
+
res.status(400).json({ error: "Missing message field" });
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
const response = await agent.getLLM().generateText(message, {
|
|
1508
|
+
systemPrompt: `You are ${agent.name}. ${agent.config.personality.bio}`,
|
|
1509
|
+
temperature: agent.config.llm.temperature
|
|
1510
|
+
});
|
|
1511
|
+
res.json({ response });
|
|
1512
|
+
} catch (error) {
|
|
1513
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Unknown error" });
|
|
1514
|
+
}
|
|
1515
|
+
});
|
|
1516
|
+
router.post("/action", async (req, res) => {
|
|
1517
|
+
try {
|
|
1518
|
+
const { action, parameters } = req.body;
|
|
1519
|
+
if (!action) {
|
|
1520
|
+
res.status(400).json({ error: "Missing action field" });
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
const result = await agent.act(action, parameters || {});
|
|
1524
|
+
res.json(result);
|
|
1525
|
+
} catch (error) {
|
|
1526
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Unknown error" });
|
|
1527
|
+
}
|
|
1528
|
+
});
|
|
1529
|
+
router.get("/config", (_req, res) => {
|
|
1530
|
+
const safeConfig = {
|
|
1531
|
+
name: agent.config.name,
|
|
1532
|
+
type: agent.config.type,
|
|
1533
|
+
personality: agent.config.personality,
|
|
1534
|
+
behaviors: {
|
|
1535
|
+
autonomous: agent.config.behaviors.autonomous,
|
|
1536
|
+
decisionMaking: agent.config.behaviors.decisionMaking,
|
|
1537
|
+
actionsPerHour: typeof agent.config.behaviors.actionsPerHour === "function" ? "(dynamic)" : agent.config.behaviors.actionsPerHour,
|
|
1538
|
+
sleepSchedule: agent.config.behaviors.sleepSchedule
|
|
1539
|
+
},
|
|
1540
|
+
memory: { type: agent.config.memory.type },
|
|
1541
|
+
actions: agent.config.actions
|
|
1542
|
+
};
|
|
1543
|
+
res.json(safeConfig);
|
|
1544
|
+
});
|
|
1545
|
+
router.post("/shutdown", async (_req, res) => {
|
|
1546
|
+
res.json({ message: "Shutting down..." });
|
|
1547
|
+
await agent.stop();
|
|
1548
|
+
process.exit(0);
|
|
1549
|
+
});
|
|
1550
|
+
return router;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// src/server/middleware.ts
|
|
1554
|
+
var logger6 = createLogger("Server");
|
|
1555
|
+
function requestLogger(req, _res, next) {
|
|
1556
|
+
logger6.debug(`${req.method} ${req.path}`);
|
|
1557
|
+
next();
|
|
1558
|
+
}
|
|
1559
|
+
function errorHandler(err, _req, res, _next) {
|
|
1560
|
+
logger6.error(`Server error: ${err.message}`);
|
|
1561
|
+
res.status(500).json({ error: err.message });
|
|
1562
|
+
}
|
|
1563
|
+
function authMiddleware(req, res, next) {
|
|
1564
|
+
const authToken = process.env.AGENT_AUTH_TOKEN;
|
|
1565
|
+
if (!authToken) {
|
|
1566
|
+
next();
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
const provided = req.headers.authorization?.replace("Bearer ", "");
|
|
1570
|
+
if (provided !== authToken) {
|
|
1571
|
+
res.status(401).json({ error: "Unauthorized" });
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
next();
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// src/server/app.ts
|
|
1578
|
+
var logger7 = createLogger("Server");
|
|
1579
|
+
function createApp(agent) {
|
|
1580
|
+
const app = express();
|
|
1581
|
+
app.use(express.json());
|
|
1582
|
+
app.use(requestLogger);
|
|
1583
|
+
app.use(authMiddleware);
|
|
1584
|
+
app.use(createRoutes(agent));
|
|
1585
|
+
app.use(errorHandler);
|
|
1586
|
+
return app;
|
|
1587
|
+
}
|
|
1588
|
+
async function startServer(agent, options = {}) {
|
|
1589
|
+
const port = options.port || parseInt(process.env.PORT || "3000", 10);
|
|
1590
|
+
const host = options.host || "0.0.0.0";
|
|
1591
|
+
const app = createApp(agent);
|
|
1592
|
+
await agent.start();
|
|
1593
|
+
app.listen(port, host, () => {
|
|
1594
|
+
logger7.info(`Agent "${agent.name}" running at http://${host}:${port}`);
|
|
1595
|
+
});
|
|
1596
|
+
const shutdown = async () => {
|
|
1597
|
+
logger7.info("Received shutdown signal");
|
|
1598
|
+
await agent.stop();
|
|
1599
|
+
process.exit(0);
|
|
1600
|
+
};
|
|
1601
|
+
process.on("SIGINT", shutdown);
|
|
1602
|
+
process.on("SIGTERM", shutdown);
|
|
1603
|
+
}
|
|
1604
|
+
export {
|
|
1605
|
+
ActionHandler,
|
|
1606
|
+
ActionRegistry,
|
|
1607
|
+
Agent,
|
|
1608
|
+
AnthropicProvider,
|
|
1609
|
+
ConfigLoader,
|
|
1610
|
+
LLMProvider,
|
|
1611
|
+
LongTermMemory,
|
|
1612
|
+
MarkdownParser,
|
|
1613
|
+
Memory,
|
|
1614
|
+
MoltbookAdapter,
|
|
1615
|
+
OpenAIProvider,
|
|
1616
|
+
Scheduler,
|
|
1617
|
+
ShortTermMemory,
|
|
1618
|
+
SocialAdapter,
|
|
1619
|
+
TwitterAdapter,
|
|
1620
|
+
buildDecisionPrompt,
|
|
1621
|
+
buildSkillPrompt,
|
|
1622
|
+
buildSystemPrompt,
|
|
1623
|
+
builtInActions,
|
|
1624
|
+
createApp,
|
|
1625
|
+
createLogger,
|
|
1626
|
+
createMarkdownAction,
|
|
1627
|
+
createRoutes,
|
|
1628
|
+
startServer,
|
|
1629
|
+
validateConfig
|
|
1630
|
+
};
|
|
1631
|
+
//# sourceMappingURL=index.mjs.map
|