obol-ai 0.3.23 → 0.3.25
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/package.json +1 -1
- package/src/claude/prompt.js +1 -0
- package/src/claude/router.js +2 -0
- package/src/runtime/background.js +29 -9
- package/src/status.js +16 -1
- package/src/telegram/handlers/media.js +3 -12
- package/src/telegram/handlers/special.js +3 -12
- package/src/telegram/handlers/text.js +15 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
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": {
|
package/src/claude/prompt.js
CHANGED
|
@@ -133,6 +133,7 @@ Tool descriptions and parameters are in the tool definitions — refer to those.
|
|
|
133
133
|
- \`knowledge_add\`/\`interests_add\`: when a topic comes up you want to know more about, add it as an interest — your curiosity cycle researches it automatically
|
|
134
134
|
- \`background_task\`: after spawning, reply with a brief acknowledgment
|
|
135
135
|
- \`schedule_event\`: always search memory first for the user's timezone. For recurring events, use \`cron_expr\` — never chain one-time events manually
|
|
136
|
+
- When the user sends an image or message containing time-sensitive details — meetings, flights, appointments, deadlines, bookings, reservations, calendar entries — automatically extract the event info and schedule it using \`schedule_event\` with a 30-minute reminder (set \`due_at\` to 30 minutes before the event). Then ask the user if they'd like to change the reminder time. Don't ask for confirmation before scheduling — just do it and offer to adjust.
|
|
136
137
|
- \`create_pdf\`: uses Typst markup. Quick ref: \`= Heading\`, \`*bold*\`, \`_italic_\`, \`- list\`, \`#table(columns: N, [...])\`, \`#set page(paper: "a4")\`, \`#set text(font: "New Computer Modern", size: 11pt)\`. After creating, use \`send_file\` to deliver
|
|
137
138
|
- \`store_secret\`/\`read_secret\`: use these instead of \`exec\` for credentials — they bypass \`bash -c\` restriction
|
|
138
139
|
- \`telegram_ask\`: use for human-in-the-loop decisions before acting (confirmations, approvals, selections)
|
package/src/claude/router.js
CHANGED
|
@@ -34,6 +34,8 @@ const TOOL_PATTERNS = [
|
|
|
34
34
|
/\b(?:create|generate|make)\b.*\b(?:pdf|chart|diagram|flowchart|image)\b/,
|
|
35
35
|
/\b(?:what|which)\b.*\b(?:api keys?|secrets?|credentials?|reminders?|events?)\b.*\b(?:stored|have|set|coming)\b/,
|
|
36
36
|
/\b(?:what have you been|what are you)\b.*\b(?:research|learn|curious|explor)\b/,
|
|
37
|
+
/\b(?:email|e-mail|inbox|gmail|outlook|mail)\b/,
|
|
38
|
+
/\b(?:meeting|flight|appointment|deadline|booking|reservation|calendar|event|conference|itinerary)\b/,
|
|
37
39
|
];
|
|
38
40
|
|
|
39
41
|
function likelyNeedsTools(message) {
|
|
@@ -31,7 +31,20 @@ class BackgroundRunner {
|
|
|
31
31
|
const verbose = parentContext?.verbose || false;
|
|
32
32
|
const verboseNotify = parentContext?._verboseNotify;
|
|
33
33
|
|
|
34
|
-
const
|
|
34
|
+
const inherited = parentContext ? {
|
|
35
|
+
toolPrefs: parentContext.toolPrefs,
|
|
36
|
+
config: parentContext.config,
|
|
37
|
+
scheduler: parentContext.scheduler,
|
|
38
|
+
messageLog: parentContext.messageLog,
|
|
39
|
+
userId: parentContext.userId,
|
|
40
|
+
userDir: parentContext.userDir,
|
|
41
|
+
telegramAsk: parentContext.telegramAsk,
|
|
42
|
+
_notifyFn: parentContext._notifyFn,
|
|
43
|
+
} : {};
|
|
44
|
+
|
|
45
|
+
const mergedExtra = { ...inherited, ...(opts.extraContext || extraContext) };
|
|
46
|
+
|
|
47
|
+
const promise = this._runTask(claude, task, taskState, ctx, memory, verbose, verboseNotify, opts.model, mergedExtra, opts.silent || false);
|
|
35
48
|
taskState.promise = promise;
|
|
36
49
|
|
|
37
50
|
return taskId;
|
|
@@ -94,18 +107,25 @@ TASK: ${task}`;
|
|
|
94
107
|
},
|
|
95
108
|
});
|
|
96
109
|
|
|
97
|
-
taskState.status = 'done';
|
|
98
|
-
taskState.result = result;
|
|
99
110
|
claude.clearHistory(`bg-${taskState.id}`);
|
|
100
|
-
|
|
101
111
|
clearStatus();
|
|
102
112
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
if (!result?.trim()) {
|
|
114
|
+
taskState.status = 'error';
|
|
115
|
+
taskState.error = 'No result returned';
|
|
116
|
+
if (!silent) {
|
|
117
|
+
await ctx.reply(`⚠️ BG #${taskState.id} finished but produced no result.`).catch(() => {});
|
|
118
|
+
}
|
|
106
119
|
} else {
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
taskState.status = 'done';
|
|
121
|
+
taskState.result = result;
|
|
122
|
+
const elapsed = Math.floor((Date.now() - taskState.startedAt) / 1000);
|
|
123
|
+
if (silent) {
|
|
124
|
+
await sendLong(ctx, result);
|
|
125
|
+
} else {
|
|
126
|
+
const header = `✅ <b>BG #${taskState.id}</b> done (${formatDuration(elapsed)})\n\n`;
|
|
127
|
+
await sendLong(ctx, header + result);
|
|
128
|
+
}
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
if (memory) {
|
package/src/status.js
CHANGED
|
@@ -33,4 +33,19 @@ function formatToolCall(toolName, inputSummary) {
|
|
|
33
33
|
return `${toolName} "${truncated}"`;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* @param {{ model: string, usage: { input_tokens: number, output_tokens: number }, startTime: number | null }} params
|
|
38
|
+
* @returns {string | null}
|
|
39
|
+
*/
|
|
40
|
+
function formatTokenStats({ model, usage, startTime }) {
|
|
41
|
+
if (!usage || !model) return null;
|
|
42
|
+
const tag = model.includes('opus') ? 'opus' : model.includes('haiku') ? 'haiku' : 'sonnet';
|
|
43
|
+
const tokIn = usage.input_tokens >= 1000 ? `${(usage.input_tokens / 1000).toFixed(1)}k` : usage.input_tokens;
|
|
44
|
+
const tokOut = usage.output_tokens >= 1000 ? `${(usage.output_tokens / 1000).toFixed(1)}k` : usage.output_tokens;
|
|
45
|
+
const dur = startTime ? ((Date.now() - startTime) / 1000).toFixed(1) : null;
|
|
46
|
+
const parts = [`◈ ${tag}`, `${tokIn} in`, `${tokOut} out`];
|
|
47
|
+
if (dur) parts.push(`${dur}s`);
|
|
48
|
+
return `<code>${parts.join(' ▪ ')}</code>`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { buildStatusHtml, formatToolCall, formatTokenStats, TERM_WIDTH };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const { getTenant } = require('../../tenant');
|
|
3
|
-
const { buildStatusHtml, formatToolCall } = require('../../status');
|
|
3
|
+
const { buildStatusHtml, formatToolCall, formatTokenStats } = require('../../status');
|
|
4
4
|
const media = require('../../media');
|
|
5
5
|
const { sendHtml, startTyping, splitMessage } = require('../utils');
|
|
6
6
|
const { MAX_MEDIA_SIZE, MEDIA_GROUP_DELAY_MS } = require('../constants');
|
|
@@ -124,17 +124,8 @@ async function processMediaItems(ctx, items, { config, allowedUsers, bot, create
|
|
|
124
124
|
|
|
125
125
|
const statsPref = tenant.toolPrefs?.get('model_stats');
|
|
126
126
|
const showStats = statsPref ? statsPref.enabled : true;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const tokIn = usage.input_tokens >= 1000 ? `${(usage.input_tokens/1000).toFixed(1)}k` : usage.input_tokens;
|
|
130
|
-
const tokOut = usage.output_tokens >= 1000 ? `${(usage.output_tokens/1000).toFixed(1)}k` : usage.output_tokens;
|
|
131
|
-
const dur = status.statusStart ? ((Date.now() - status.statusStart)/1000).toFixed(1) : null;
|
|
132
|
-
const parts = [`◈ ${tag}`, `${tokIn} in`, `${tokOut} out`];
|
|
133
|
-
if (dur) parts.push(`${dur}s`);
|
|
134
|
-
await ctx.reply(`<code>${parts.join(' ▪ ')}</code>`, { parse_mode: 'HTML' }).catch(() => {});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
status.deleteMsg();
|
|
127
|
+
const statsHtml = showStats ? formatTokenStats({ model, usage, startTime: status.statusStart }) : null;
|
|
128
|
+
status.finalize(statsHtml);
|
|
138
129
|
} catch (e) {
|
|
139
130
|
status.clear();
|
|
140
131
|
stopTyping();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { getTenant } = require('../../tenant');
|
|
2
|
-
const { formatToolCall } = require('../../status');
|
|
2
|
+
const { formatToolCall, formatTokenStats } = require('../../status');
|
|
3
3
|
const { sendHtml, startTyping, splitMessage } = require('../utils');
|
|
4
4
|
const { createChatContext, createStatusTracker } = require('./text');
|
|
5
5
|
|
|
@@ -120,17 +120,8 @@ async function processSpecial(ctx, prompt, deps) {
|
|
|
120
120
|
|
|
121
121
|
const statsPref = tenant.toolPrefs?.get('model_stats');
|
|
122
122
|
const showStats = statsPref ? statsPref.enabled : true;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const tokIn = usage.input_tokens >= 1000 ? `${(usage.input_tokens / 1000).toFixed(1)}k` : usage.input_tokens;
|
|
126
|
-
const tokOut = usage.output_tokens >= 1000 ? `${(usage.output_tokens / 1000).toFixed(1)}k` : usage.output_tokens;
|
|
127
|
-
const dur = status.statusStart ? ((Date.now() - status.statusStart) / 1000).toFixed(1) : null;
|
|
128
|
-
const parts = [`◈ ${tag}`, `${tokIn} in`, `${tokOut} out`];
|
|
129
|
-
if (dur) parts.push(`${dur}s`);
|
|
130
|
-
await ctx.reply(`<code>${parts.join(' ▪ ')}</code>`, { parse_mode: 'HTML' }).catch(() => {});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
status.deleteMsg();
|
|
123
|
+
const statsHtml = showStats ? formatTokenStats({ model, usage, startTime: status.statusStart }) : null;
|
|
124
|
+
status.finalize(statsHtml);
|
|
134
125
|
} catch (e) {
|
|
135
126
|
status.clear();
|
|
136
127
|
stopTyping();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { InlineKeyboard } = require('grammy');
|
|
2
2
|
const { getTenant } = require('../../tenant');
|
|
3
|
-
const { buildStatusHtml, formatToolCall } = require('../../status');
|
|
3
|
+
const { buildStatusHtml, formatToolCall, formatTokenStats } = require('../../status');
|
|
4
4
|
const { sendHtml, startTyping, splitMessage } = require('../utils');
|
|
5
5
|
const { TEXT_BUFFER_GAP_MS, TEXT_BUFFER_MAX_PARTS, TEXT_BUFFER_MAX_CHARS, TEXT_BUFFER_THRESHOLD } = require('../constants');
|
|
6
6
|
|
|
@@ -142,6 +142,18 @@ function createStatusTracker(ctx, botName) {
|
|
|
142
142
|
deleteMsg() {
|
|
143
143
|
if (statusMsgId) ctx.api.deleteMessage(ctx.chat.id, statusMsgId).catch(() => {});
|
|
144
144
|
},
|
|
145
|
+
finalize(statsHtml) {
|
|
146
|
+
if (statusTimer) { clearInterval(statusTimer); statusTimer = null; }
|
|
147
|
+
if (statsHtml && statusMsgId) {
|
|
148
|
+
ctx.api.editMessageText(ctx.chat.id, statusMsgId, statsHtml, { parse_mode: 'HTML' }).catch(() => {
|
|
149
|
+
ctx.api.deleteMessage(ctx.chat.id, statusMsgId).catch(() => {});
|
|
150
|
+
});
|
|
151
|
+
statusMsgId = null;
|
|
152
|
+
} else {
|
|
153
|
+
if (statusMsgId) { ctx.api.deleteMessage(ctx.chat.id, statusMsgId).catch(() => {}); statusMsgId = null; }
|
|
154
|
+
if (statsHtml) ctx.reply(statsHtml, { parse_mode: 'HTML' }).catch(() => {});
|
|
155
|
+
}
|
|
156
|
+
},
|
|
145
157
|
};
|
|
146
158
|
}
|
|
147
159
|
|
|
@@ -233,17 +245,8 @@ async function processTextMessage(ctx, fullMessage, { config, allowedUsers, bot,
|
|
|
233
245
|
|
|
234
246
|
const statsPref = tenant.toolPrefs?.get('model_stats');
|
|
235
247
|
const showStats = statsPref ? statsPref.enabled : true;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const tokIn = usage.input_tokens >= 1000 ? `${(usage.input_tokens/1000).toFixed(1)}k` : usage.input_tokens;
|
|
239
|
-
const tokOut = usage.output_tokens >= 1000 ? `${(usage.output_tokens/1000).toFixed(1)}k` : usage.output_tokens;
|
|
240
|
-
const dur = status.statusStart ? ((Date.now() - status.statusStart)/1000).toFixed(1) : null;
|
|
241
|
-
const parts = [`◈ ${tag}`, `${tokIn} in`, `${tokOut} out`];
|
|
242
|
-
if (dur) parts.push(`${dur}s`);
|
|
243
|
-
await ctx.reply(`<code>${parts.join(' ▪ ')}</code>`, { parse_mode: 'HTML' }).catch(() => {});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
status.deleteMsg();
|
|
248
|
+
const statsHtml = showStats ? formatTokenStats({ model, usage, startTime: status.statusStart }) : null;
|
|
249
|
+
status.finalize(statsHtml);
|
|
247
250
|
} catch (e) {
|
|
248
251
|
batcher?.flush();
|
|
249
252
|
status.clear();
|