obol-ai 0.2.39 ā 0.3.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/CHANGELOG.md +15 -0
- package/package.json +1 -1
- package/src/analysis.js +191 -0
- package/src/background.js +15 -7
- package/src/claude/chat.js +3 -2
- package/src/claude/prompt.js +63 -6
- package/src/claude/tool-registry.js +10 -0
- package/src/claude/tools/knowledge.js +107 -0
- package/src/curiosity-dispatch.js +136 -0
- package/src/curiosity.js +112 -0
- package/src/db/migrate.js +98 -0
- package/src/evolve/check.js +13 -21
- package/src/evolve/evolve.js +49 -20
- package/src/evolve/index.js +2 -2
- package/src/heartbeat.js +222 -2
- package/src/index.js +11 -0
- package/src/memory-self.js +123 -0
- package/src/messages.js +45 -48
- package/src/patterns.js +111 -0
- package/src/personality.js +9 -10
- package/src/soul.js +53 -0
- package/src/telegram/constants.js +0 -2
- package/src/telegram/handlers/text.js +1 -58
- package/src/tenant.js +10 -7
package/src/personality.js
CHANGED
|
@@ -4,17 +4,16 @@ const { OBOL_DIR } = require('./config');
|
|
|
4
4
|
|
|
5
5
|
const DEFAULT_TRAITS = require('./defaults/traits.json');
|
|
6
6
|
|
|
7
|
-
function loadPersonality(
|
|
8
|
-
|
|
7
|
+
function loadPersonality(sharedDir, userDir) {
|
|
8
|
+
sharedDir = sharedDir || path.join(OBOL_DIR, 'personality');
|
|
9
|
+
userDir = userDir || sharedDir;
|
|
9
10
|
const personality = {};
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
-
soul
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
for (const [key, filename] of Object.entries(files)) {
|
|
12
|
+
for (const [key, filename, dir] of [
|
|
13
|
+
['soul', 'SOUL.md', sharedDir],
|
|
14
|
+
['agents', 'AGENTS.md', userDir],
|
|
15
|
+
['user', 'USER.md', userDir],
|
|
16
|
+
]) {
|
|
18
17
|
const filepath = path.join(dir, filename);
|
|
19
18
|
try {
|
|
20
19
|
personality[key] = fs.readFileSync(filepath, 'utf-8');
|
|
@@ -23,7 +22,7 @@ function loadPersonality(dir) {
|
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
personality.traits = loadTraits(
|
|
25
|
+
personality.traits = loadTraits(userDir);
|
|
27
26
|
|
|
28
27
|
return personality;
|
|
29
28
|
}
|
package/src/soul.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { OBOL_DIR } = require('./config');
|
|
4
|
+
|
|
5
|
+
const PERSONALITY_DIR = path.join(OBOL_DIR, 'personality');
|
|
6
|
+
|
|
7
|
+
function makeHeaders(supabaseConfig) {
|
|
8
|
+
return {
|
|
9
|
+
'apikey': supabaseConfig.serviceKey,
|
|
10
|
+
'Authorization': `Bearer ${supabaseConfig.serviceKey}`,
|
|
11
|
+
'Content-Type': 'application/json',
|
|
12
|
+
'Prefer': 'return=minimal',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function backup(supabaseConfig, key, content) {
|
|
17
|
+
if (!supabaseConfig?.url || !supabaseConfig?.serviceKey) return;
|
|
18
|
+
await fetch(`${supabaseConfig.url}/rest/v1/obol_soul`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { ...makeHeaders(supabaseConfig), 'Prefer': 'resolution=merge-duplicates,return=minimal' },
|
|
21
|
+
body: JSON.stringify({ id: key, content, updated_at: new Date().toISOString() }),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function restore(supabaseConfig, key) {
|
|
26
|
+
if (!supabaseConfig?.url || !supabaseConfig?.serviceKey) return null;
|
|
27
|
+
const res = await fetch(`${supabaseConfig.url}/rest/v1/obol_soul?id=eq.${key}&select=content`, {
|
|
28
|
+
headers: makeHeaders(supabaseConfig),
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) return null;
|
|
31
|
+
const rows = await res.json();
|
|
32
|
+
return rows?.[0]?.content || null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function restoreIfMissing(supabaseConfig) {
|
|
36
|
+
if (!supabaseConfig?.url || !supabaseConfig?.serviceKey) return;
|
|
37
|
+
fs.mkdirSync(PERSONALITY_DIR, { recursive: true });
|
|
38
|
+
|
|
39
|
+
const soulPath = path.join(PERSONALITY_DIR, 'SOUL.md');
|
|
40
|
+
if (!fs.existsSync(soulPath)) {
|
|
41
|
+
try {
|
|
42
|
+
const content = await restore(supabaseConfig, 'soul');
|
|
43
|
+
if (content) {
|
|
44
|
+
fs.writeFileSync(soulPath, content);
|
|
45
|
+
console.log(' [soul] Restored SOUL.md from Supabase');
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.error(` [soul] Failed to restore SOUL.md: ${e.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { backup, restore, restoreIfMissing, PERSONALITY_DIR };
|
|
@@ -3,7 +3,6 @@ const { TERM_WIDTH } = require('../status');
|
|
|
3
3
|
const RATE_LIMIT_MS = 3000;
|
|
4
4
|
const SPAM_THRESHOLD = 5;
|
|
5
5
|
const SPAM_COOLDOWN_MS = 30000;
|
|
6
|
-
const EVOLUTION_IDLE_MS = 15 * 60 * 1000;
|
|
7
6
|
const DEDUP_TTL_MS = 5 * 60 * 1000;
|
|
8
7
|
const DEDUP_MAX_SIZE = 2000;
|
|
9
8
|
const TEXT_BUFFER_GAP_MS = 1500;
|
|
@@ -18,7 +17,6 @@ module.exports = {
|
|
|
18
17
|
RATE_LIMIT_MS,
|
|
19
18
|
SPAM_THRESHOLD,
|
|
20
19
|
SPAM_COOLDOWN_MS,
|
|
21
|
-
EVOLUTION_IDLE_MS,
|
|
22
20
|
DEDUP_TTL_MS,
|
|
23
21
|
DEDUP_MAX_SIZE,
|
|
24
22
|
TEXT_BUFFER_GAP_MS,
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
const { InlineKeyboard } = require('grammy');
|
|
2
2
|
const { getTenant } = require('../../tenant');
|
|
3
|
-
const { evolve, loadEvolutionState } = require('../../evolve');
|
|
4
3
|
const { buildStatusHtml, describeToolCall } = require('../../status');
|
|
5
4
|
const { sendHtml, startTyping, splitMessage } = require('../utils');
|
|
6
|
-
const {
|
|
5
|
+
const { TEXT_BUFFER_GAP_MS, TEXT_BUFFER_MAX_PARTS, TEXT_BUFFER_MAX_CHARS, TEXT_BUFFER_THRESHOLD } = require('../constants');
|
|
7
6
|
|
|
8
|
-
const _evolutionTimers = new Map();
|
|
9
7
|
const textBuffers = new Map();
|
|
10
8
|
const VERBOSE_FLUSH_MS = 2000;
|
|
11
9
|
|
|
@@ -141,12 +139,6 @@ async function processTextMessage(ctx, fullMessage, { config, allowedUsers, bot,
|
|
|
141
139
|
const userId = ctx.from.id;
|
|
142
140
|
const tenant = await getTenant(userId, config);
|
|
143
141
|
|
|
144
|
-
if (_evolutionTimers.has(userId)) {
|
|
145
|
-
clearTimeout(_evolutionTimers.get(userId));
|
|
146
|
-
_evolutionTimers.delete(userId);
|
|
147
|
-
if (tenant.messageLog) tenant.messageLog._evolutionPending = false;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
142
|
let replyContext = '';
|
|
151
143
|
const reply = ctx.message?.reply_to_message;
|
|
152
144
|
if (reply) {
|
|
@@ -214,55 +206,6 @@ async function processTextMessage(ctx, fullMessage, { config, allowedUsers, bot,
|
|
|
214
206
|
|
|
215
207
|
tenant.messageLog?.log(ctx.chat.id, 'assistant', response, { model, tokensIn: usage?.input_tokens, tokensOut: usage?.output_tokens });
|
|
216
208
|
|
|
217
|
-
if (tenant.messageLog?._evolutionReady && !_evolutionTimers.has(userId)) {
|
|
218
|
-
tenant.messageLog._evolutionReady = false;
|
|
219
|
-
tenant.messageLog._evolutionPending = true;
|
|
220
|
-
const timer = setTimeout(async () => {
|
|
221
|
-
_evolutionTimers.delete(userId);
|
|
222
|
-
try {
|
|
223
|
-
const result = await evolve(tenant.claude.client, tenant.messageLog, tenant.memory, tenant.userDir);
|
|
224
|
-
tenant.claude.reloadPersonality?.();
|
|
225
|
-
let msg = `šŖ Evolution #${result.evolutionNumber} complete.`;
|
|
226
|
-
|
|
227
|
-
if (result.scriptsFixed) {
|
|
228
|
-
msg += '\nš§ Fixed a test regression automatically.';
|
|
229
|
-
} else if (result.scriptsRolledBack) {
|
|
230
|
-
msg += '\nā ļø Rolled back a script refactor ā tests couldn\'t be fixed.';
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (result.upgrades && result.upgrades.length > 0) {
|
|
234
|
-
msg += '\n\nš **New capabilities:**';
|
|
235
|
-
for (const u of result.upgrades) {
|
|
236
|
-
msg += `\n⢠**${u.name}** ā ${u.description}`;
|
|
237
|
-
if (u.command) msg += ` ā \`${u.command}\``;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (result.deployedApps && result.deployedApps.length > 0) {
|
|
242
|
-
msg += '\n\nš **Deployed:**';
|
|
243
|
-
for (const app of result.deployedApps) {
|
|
244
|
-
if (app.url) {
|
|
245
|
-
msg += `\n⢠${app.name} ā ${app.url}`;
|
|
246
|
-
} else if (app.error) {
|
|
247
|
-
msg += `\n⢠${app.name} ā deploy failed: ${app.error.substring(0, 100)}`;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (result.changelog) {
|
|
253
|
-
msg += `\n\n_${result.changelog}_`;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
await sendHtml(ctx, msg).catch(() => {});
|
|
257
|
-
} catch (e) {
|
|
258
|
-
console.error('Evolution failed:', e.message);
|
|
259
|
-
} finally {
|
|
260
|
-
tenant.messageLog._evolutionPending = false;
|
|
261
|
-
}
|
|
262
|
-
}, EVOLUTION_IDLE_MS);
|
|
263
|
-
_evolutionTimers.set(userId, timer);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
209
|
stopTyping();
|
|
267
210
|
|
|
268
211
|
if (response.length > 4096) {
|
package/src/tenant.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const { ensureUserDir } = require('./config');
|
|
2
|
+
const { PERSONALITY_DIR } = require('./soul');
|
|
2
3
|
const { loadPersonality } = require('./personality');
|
|
3
4
|
const { createMemory } = require('./memory');
|
|
5
|
+
const { createSelfMemory } = require('./memory-self');
|
|
6
|
+
const { createPatterns } = require('./patterns');
|
|
4
7
|
const { createClaude } = require('./claude');
|
|
5
8
|
const { createMessageLog } = require('./messages');
|
|
6
9
|
const { BackgroundRunner } = require('./background');
|
|
@@ -30,13 +33,14 @@ _tenantCleanup.unref();
|
|
|
30
33
|
|
|
31
34
|
async function createTenant(userId, config) {
|
|
32
35
|
const userDir = ensureUserDir(userId);
|
|
33
|
-
const
|
|
34
|
-
const personality = loadPersonality(personalityDir);
|
|
36
|
+
const personality = loadPersonality(PERSONALITY_DIR, path.join(userDir, 'personality'));
|
|
35
37
|
const memory = config.supabase ? await createMemory(config.supabase, userId) : null;
|
|
38
|
+
const selfMemory = config.supabase ? await createSelfMemory(config.supabase, userId) : null;
|
|
39
|
+
const patterns = config.supabase ? await createPatterns(config.supabase, userId) : null;
|
|
36
40
|
const bridgeEnabled = isBridgeEnabled(config) && (config.telegram?.allowedUsers?.length || 0) >= 2;
|
|
37
|
-
const claude = createClaude(config.anthropic, { personality, memory, userDir, bridgeEnabled, botName: config.bot?.name });
|
|
38
|
-
const messageLog = config.supabase ? createMessageLog(config.supabase, memory, config.anthropic, userId, userDir) : null;
|
|
41
|
+
const claude = createClaude(config.anthropic, { personality, memory, selfMemory, userDir, bridgeEnabled, botName: config.bot?.name });
|
|
39
42
|
const scheduler = config.supabase ? createScheduler(config.supabase, userId) : null;
|
|
43
|
+
const messageLog = config.supabase ? createMessageLog(config.supabase, memory, config.anthropic, userId, userDir) : null;
|
|
40
44
|
const toolPrefsApi = config.supabase ? createToolPrefs(config.supabase, userId) : null;
|
|
41
45
|
const bg = new BackgroundRunner();
|
|
42
46
|
|
|
@@ -60,7 +64,7 @@ async function createTenant(userId, config) {
|
|
|
60
64
|
} catch {}
|
|
61
65
|
|
|
62
66
|
return {
|
|
63
|
-
claude, memory, messageLog, personality, scheduler, bg, userDir, userId,
|
|
67
|
+
claude, memory, selfMemory, patterns, messageLog, personality, scheduler, bg, userDir, userId,
|
|
64
68
|
toolPrefs,
|
|
65
69
|
toolPrefsApi,
|
|
66
70
|
async reloadToolPrefs() {
|
|
@@ -78,9 +82,8 @@ async function getTenant(userId, config) {
|
|
|
78
82
|
if (tenants.has(userId)) {
|
|
79
83
|
const tenant = tenants.get(userId);
|
|
80
84
|
if (Date.now() - (tenant._personalityLoadedAt || 0) > PERSONALITY_CACHE_TTL) {
|
|
81
|
-
const personalityDir = path.join(tenant.userDir, 'personality');
|
|
82
85
|
try {
|
|
83
|
-
const soulPath = path.join(
|
|
86
|
+
const soulPath = path.join(PERSONALITY_DIR, 'SOUL.md');
|
|
84
87
|
const mtime = fs.statSync(soulPath).mtimeMs;
|
|
85
88
|
if (mtime > (tenant._personalityMtime || 0)) {
|
|
86
89
|
tenant.claude.reloadPersonality();
|