obol-ai 0.3.35 → 0.3.37
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
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 0.3.37
|
|
2
|
+
- changelog
|
|
3
|
+
- fix media group race condition — buffer before download
|
|
4
|
+
|
|
5
|
+
## 0.3.36
|
|
6
|
+
- save media assets instantly in date-based folders
|
|
7
|
+
- fix agentic event raw JSON output and update_event wiping instructions
|
|
8
|
+
|
|
1
9
|
## 0.3.35
|
|
2
10
|
- add order param to memory, self-memory, and events queries
|
|
3
11
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.37",
|
|
4
4
|
"description": "Self-evolving AI assistant that learns, remembers, and acts on its own. Persistent vector memory, self-rewriting personality, proactive heartbeats.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -59,7 +59,8 @@ Always search memory first for the user's timezone/location.`,
|
|
|
59
59
|
event_id: { type: 'string', description: 'UUID of the event to update' },
|
|
60
60
|
title: { type: 'string' },
|
|
61
61
|
description: { type: 'string' },
|
|
62
|
-
instructions: { type: 'string', description: 'LLM instructions to execute when the event fires.
|
|
62
|
+
instructions: { type: 'string', description: 'New LLM instructions to execute when the event fires. Only provide if the user wants to change them.' },
|
|
63
|
+
clear_instructions: { type: 'boolean', description: 'Set to true to remove instructions and convert an agentic event to a plain reminder.' },
|
|
63
64
|
timezone: { type: 'string' },
|
|
64
65
|
cron_expr: { type: 'string' },
|
|
65
66
|
max_runs: { type: 'number' },
|
|
@@ -146,7 +147,8 @@ const handlers = {
|
|
|
146
147
|
const fields = {};
|
|
147
148
|
if (rest.title !== undefined) fields.title = rest.title;
|
|
148
149
|
if (rest.description !== undefined) fields.description = rest.description;
|
|
149
|
-
if (
|
|
150
|
+
if (rest.clear_instructions === true) fields.instructions = null;
|
|
151
|
+
else if (typeof rest.instructions === 'string' && rest.instructions.length > 0) fields.instructions = rest.instructions;
|
|
150
152
|
if (rest.timezone !== undefined) fields.timezone = rest.timezone;
|
|
151
153
|
if (rest.cron_expr !== undefined) fields.cron_expr = rest.cron_expr;
|
|
152
154
|
if (rest.max_runs !== undefined) fields.max_runs = rest.max_runs;
|
package/src/runtime/heartbeat.js
CHANGED
|
@@ -363,6 +363,10 @@ async function runAgenticEvent(bot, config, event) {
|
|
|
363
363
|
if (tenant.personality?.soul) systemParts.push(tenant.personality.soul);
|
|
364
364
|
if (tenant.personality?.user) systemParts.push(`About this user:\n${tenant.personality.user}`);
|
|
365
365
|
if (context) systemParts.push(`Current context:\n${context}`);
|
|
366
|
+
systemParts.push(
|
|
367
|
+
'You are executing a scheduled task. Respond ONLY with a clean, natural-language message for the user. ' +
|
|
368
|
+
'Do NOT output JSON, code blocks, tool calls, or structured data. Write as a direct Telegram message.'
|
|
369
|
+
);
|
|
366
370
|
|
|
367
371
|
const response = await tenant.claude.client.messages.create({
|
|
368
372
|
model: 'claude-sonnet-4-6',
|
|
@@ -9,7 +9,7 @@ const TEXT_BUFFER_GAP_MS = 1500;
|
|
|
9
9
|
const TEXT_BUFFER_MAX_PARTS = 12;
|
|
10
10
|
const TEXT_BUFFER_MAX_CHARS = 50000;
|
|
11
11
|
const TEXT_BUFFER_THRESHOLD = 4000;
|
|
12
|
-
const MEDIA_GROUP_DELAY_MS =
|
|
12
|
+
const MEDIA_GROUP_DELAY_MS = 5000;
|
|
13
13
|
const MAX_MEDIA_SIZE = 50 * 1024 * 1024;
|
|
14
14
|
const TERM_SEP = '━'.repeat(TERM_WIDTH);
|
|
15
15
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const { getTenant } = require('../../tenant');
|
|
3
|
+
const { ensureUserDir, getUserTimezone } = require('../../config');
|
|
3
4
|
const { buildStatusHtml, formatToolCall, formatTokenStats } = require('../../status');
|
|
4
5
|
const media = require('../../media');
|
|
5
6
|
const { sendHtml, startTyping, splitMessage } = require('../utils');
|
|
@@ -15,6 +16,29 @@ async function downloadMediaItem(ctx, fileInfo, telegramToken) {
|
|
|
15
16
|
return { buffer, filename, fileInfo, caption: ctx.message.caption || '' };
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
async function downloadAndProcess(ctx, entries, deps, token) {
|
|
20
|
+
const userId = ctx.from.id;
|
|
21
|
+
const userDir = ensureUserDir(userId);
|
|
22
|
+
const tz = getUserTimezone(deps.config, userId);
|
|
23
|
+
const today = new Date().toLocaleDateString('en-CA', { timeZone: tz });
|
|
24
|
+
const assetsDir = path.join(userDir, 'assets', today);
|
|
25
|
+
|
|
26
|
+
const items = (await Promise.all(
|
|
27
|
+
entries.map(({ ctx: entryCtx, fileInfo }) =>
|
|
28
|
+
downloadMediaItem(entryCtx, fileInfo, token).then(item => {
|
|
29
|
+
if (item) item.savedPath = media.saveFile(item.buffer, assetsDir, item.filename);
|
|
30
|
+
return item;
|
|
31
|
+
}).catch(e => {
|
|
32
|
+
console.error('Media download error:', e.message);
|
|
33
|
+
return null;
|
|
34
|
+
})
|
|
35
|
+
)
|
|
36
|
+
)).filter(Boolean);
|
|
37
|
+
|
|
38
|
+
if (items.length === 0) return;
|
|
39
|
+
await processMediaItems(ctx, items, deps);
|
|
40
|
+
}
|
|
41
|
+
|
|
18
42
|
async function processMediaItems(ctx, items, { config, allowedUsers, bot, createAsk }) {
|
|
19
43
|
if (!ctx.from) return;
|
|
20
44
|
const userId = ctx.from.id;
|
|
@@ -23,13 +47,12 @@ async function processMediaItems(ctx, items, { config, allowedUsers, bot, create
|
|
|
23
47
|
|
|
24
48
|
try {
|
|
25
49
|
const tenant = await getTenant(userId, config);
|
|
26
|
-
const assetsDir = path.join(tenant.userDir, 'assets');
|
|
27
50
|
const imageBlocks = [];
|
|
28
51
|
const nonImageParts = [];
|
|
29
52
|
const caption = items.map(i => i.caption).filter(Boolean).join('\n') || '';
|
|
30
53
|
|
|
31
54
|
for (const item of items) {
|
|
32
|
-
const savedPath =
|
|
55
|
+
const savedPath = item.savedPath;
|
|
33
56
|
|
|
34
57
|
if (tenant.memory && !media.isImage(item.fileInfo)) {
|
|
35
58
|
const memContent = media.buildMemoryContent(item.fileInfo, item.filename, savedPath, item.caption);
|
|
@@ -57,6 +80,9 @@ async function processMediaItems(ctx, items, { config, allowedUsers, bot, create
|
|
|
57
80
|
}
|
|
58
81
|
}
|
|
59
82
|
|
|
83
|
+
const fileList = items.map(i => `- ${i.savedPath}`).join('\n');
|
|
84
|
+
const fileRef = `Files saved:\n${fileList}`;
|
|
85
|
+
|
|
60
86
|
let prompt, chatImages;
|
|
61
87
|
if (imageBlocks.length > 0) {
|
|
62
88
|
prompt = caption || `The user sent ${imageBlocks.length} image(s). Describe what you see and respond naturally.`;
|
|
@@ -65,6 +91,7 @@ async function processMediaItems(ctx, items, { config, allowedUsers, bot, create
|
|
|
65
91
|
} else {
|
|
66
92
|
prompt = nonImageParts.join('\n');
|
|
67
93
|
}
|
|
94
|
+
prompt = fileRef + '\n\n' + prompt;
|
|
68
95
|
|
|
69
96
|
const mediaChatCtx = createChatContext(ctx, tenant, config, { allowedUsers, bot, createAsk });
|
|
70
97
|
if (chatImages) mediaChatCtx.images = chatImages;
|
|
@@ -150,32 +177,26 @@ function registerMediaHandler(bot, telegramConfig, deps) {
|
|
|
150
177
|
return;
|
|
151
178
|
}
|
|
152
179
|
|
|
153
|
-
const item = await downloadMediaItem(ctx, fileInfo, telegramConfig.token).catch(e => {
|
|
154
|
-
console.error('Media download error:', e.message);
|
|
155
|
-
return null;
|
|
156
|
-
});
|
|
157
|
-
if (!item) return;
|
|
158
|
-
|
|
159
180
|
const groupId = ctx.message.media_group_id;
|
|
160
181
|
if (groupId) {
|
|
161
182
|
const existing = mediaGroups.get(groupId);
|
|
162
183
|
if (existing) {
|
|
163
184
|
clearTimeout(existing.timer);
|
|
164
|
-
existing.
|
|
165
|
-
existing.
|
|
185
|
+
existing.entries.push({ ctx, fileInfo });
|
|
186
|
+
existing.latestCtx = ctx;
|
|
166
187
|
existing.timer = setTimeout(() => {
|
|
167
188
|
mediaGroups.delete(groupId);
|
|
168
|
-
|
|
189
|
+
downloadAndProcess(existing.latestCtx, existing.entries, deps, telegramConfig.token).catch(e =>
|
|
169
190
|
console.error('Media group error:', e.message)
|
|
170
191
|
);
|
|
171
192
|
}, MEDIA_GROUP_DELAY_MS);
|
|
172
193
|
} else {
|
|
173
194
|
const group = {
|
|
174
|
-
|
|
175
|
-
ctx,
|
|
195
|
+
entries: [{ ctx, fileInfo }],
|
|
196
|
+
latestCtx: ctx,
|
|
176
197
|
timer: setTimeout(() => {
|
|
177
198
|
mediaGroups.delete(groupId);
|
|
178
|
-
|
|
199
|
+
downloadAndProcess(ctx, [{ ctx, fileInfo }], deps, telegramConfig.token).catch(e =>
|
|
179
200
|
console.error('Media group error:', e.message)
|
|
180
201
|
);
|
|
181
202
|
}, MEDIA_GROUP_DELAY_MS),
|
|
@@ -185,6 +206,18 @@ function registerMediaHandler(bot, telegramConfig, deps) {
|
|
|
185
206
|
return;
|
|
186
207
|
}
|
|
187
208
|
|
|
209
|
+
const item = await downloadMediaItem(ctx, fileInfo, telegramConfig.token).catch(e => {
|
|
210
|
+
console.error('Media download error:', e.message);
|
|
211
|
+
return null;
|
|
212
|
+
});
|
|
213
|
+
if (!item) return;
|
|
214
|
+
|
|
215
|
+
const userDir = ensureUserDir(userId);
|
|
216
|
+
const tz = getUserTimezone(deps.config, userId);
|
|
217
|
+
const today = new Date().toLocaleDateString('en-CA', { timeZone: tz });
|
|
218
|
+
const assetsDir = path.join(userDir, 'assets', today);
|
|
219
|
+
item.savedPath = media.saveFile(item.buffer, assetsDir, item.filename);
|
|
220
|
+
|
|
188
221
|
await processMediaItems(ctx, [item], deps);
|
|
189
222
|
}
|
|
190
223
|
|