obol-ai 0.3.3 → 0.3.4
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/chat.js +2 -1
- package/src/claude/router.js +3 -2
- package/src/claude/tools/files.js +18 -4
- package/src/cli/config.js +22 -20
- package/src/cli/init.js +12 -7
- package/src/cli/prompt-debug.js +39 -4
- package/src/messages.js +14 -6
- package/src/telegram/commands/admin.js +2 -2
- package/src/telegram/handlers/media.js +1 -1
- package/src/telegram/handlers/special.js +1 -1
- package/src/telegram/handlers/text.js +6 -5
- package/src/tenant.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
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/chat.js
CHANGED
|
@@ -267,8 +267,9 @@ function createClaude(anthropicConfig, { personality, memory, selfMemory, userDi
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
function reloadPersonality() {
|
|
270
|
+
const { PERSONALITY_DIR } = require('../soul');
|
|
270
271
|
const pDir = userDir ? path.join(userDir, 'personality') : undefined;
|
|
271
|
-
const newPersonality = require('../personality').loadPersonality(pDir);
|
|
272
|
+
const newPersonality = require('../personality').loadPersonality(PERSONALITY_DIR, pDir);
|
|
272
273
|
for (const key of Object.keys(personality)) delete personality[key];
|
|
273
274
|
Object.assign(personality, newPersonality);
|
|
274
275
|
baseSystemPrompt = buildSystemPrompt(personality, userDir, { bridgeEnabled, botName });
|
package/src/claude/router.js
CHANGED
|
@@ -36,7 +36,7 @@ async function routeMessage(client, memory, userMessage, { vlog, onRouteDecision
|
|
|
36
36
|
Reply with ONLY a JSON object:
|
|
37
37
|
{"need_memory": true/false, "search_queries": ["query1", "query2"], "model": "haiku|sonnet|opus"}
|
|
38
38
|
|
|
39
|
-
search_queries: 1-
|
|
39
|
+
search_queries: 1-5 optimized search queries based on the full conversation context. Cover distinct topics, people, entities, time periods, or projects referenced. Single-topic messages need just one query. Use more queries when the message references multiple people, projects, or threads.
|
|
40
40
|
|
|
41
41
|
Memory: casual messages (greetings, jokes, simple questions) → false. References to past, people, projects, preferences → true.
|
|
42
42
|
|
|
@@ -73,12 +73,13 @@ If recent context shows an ongoing task (sonnet/opus was just used, multi-step w
|
|
|
73
73
|
|
|
74
74
|
if (decision.need_memory && memory) {
|
|
75
75
|
const budget = decision.model === 'opus' ? 40 : decision.model === 'haiku' ? 15 : 25;
|
|
76
|
+
const poolPerQuery = decision.model === 'opus' ? 20 : decision.model === 'haiku' ? 10 : 15;
|
|
76
77
|
const searchQueries = queries.length > 0 ? queries : [userMessage];
|
|
77
78
|
|
|
78
79
|
const recentMemories = await memory.byDate('7d', { limit: Math.ceil(budget / 3) });
|
|
79
80
|
|
|
80
81
|
const semanticResults = await Promise.all(
|
|
81
|
-
searchQueries.map(q => memory.search(q, { limit:
|
|
82
|
+
searchQueries.map(q => memory.search(q, { limit: poolPerQuery, threshold: 0.4 }))
|
|
82
83
|
);
|
|
83
84
|
const semanticMemories = semanticResults.flat();
|
|
84
85
|
|
|
@@ -96,7 +96,10 @@ const definitions = [
|
|
|
96
96
|
const handlers = {
|
|
97
97
|
async read_file(input, memory, context) {
|
|
98
98
|
const { userDir } = context;
|
|
99
|
-
const
|
|
99
|
+
const { PERSONALITY_DIR } = require('../../soul');
|
|
100
|
+
const filePath = path.basename(input.path) === 'SOUL.md'
|
|
101
|
+
? path.join(PERSONALITY_DIR, 'SOUL.md')
|
|
102
|
+
: (userDir ? resolveUserPath(input.path, userDir) : input.path);
|
|
100
103
|
if (filePath.toLowerCase().endsWith('.pdf')) {
|
|
101
104
|
const pdfParse = require('pdf-parse');
|
|
102
105
|
const { text } = await pdfParse(fs.readFileSync(filePath));
|
|
@@ -119,25 +122,36 @@ const handlers = {
|
|
|
119
122
|
|
|
120
123
|
async write_file(input, memory, context) {
|
|
121
124
|
const { userDir } = context;
|
|
122
|
-
const
|
|
125
|
+
const { PERSONALITY_DIR, backup } = require('../../soul');
|
|
126
|
+
const isSoul = path.basename(input.path) === 'SOUL.md';
|
|
127
|
+
const filePath = isSoul ? path.join(PERSONALITY_DIR, 'SOUL.md') : (userDir ? resolveUserPath(input.path, userDir) : input.path);
|
|
123
128
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
124
129
|
fs.writeFileSync(filePath, input.content);
|
|
125
130
|
if (filePath.includes('personality/')) {
|
|
126
131
|
context._reloadPersonality?.();
|
|
132
|
+
if (isSoul && context.config?.supabase) {
|
|
133
|
+
backup(context.config.supabase, 'soul', input.content).catch(() => {});
|
|
134
|
+
}
|
|
127
135
|
}
|
|
128
136
|
return `Written: ${filePath}`;
|
|
129
137
|
},
|
|
130
138
|
|
|
131
139
|
async edit_file(input, memory, context) {
|
|
132
140
|
const { userDir } = context;
|
|
133
|
-
const
|
|
141
|
+
const { PERSONALITY_DIR, backup } = require('../../soul');
|
|
142
|
+
const isSoul = path.basename(input.path) === 'SOUL.md';
|
|
143
|
+
const filePath = isSoul ? path.join(PERSONALITY_DIR, 'SOUL.md') : (userDir ? resolveUserPath(input.path, userDir) : input.path);
|
|
134
144
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
135
145
|
const count = content.split(input.old_string).length - 1;
|
|
136
146
|
if (count === 0) return `Error: old_string not found in ${input.path}`;
|
|
137
147
|
if (count > 1) return `Error: old_string matches ${count} times — add more context to make it unique`;
|
|
138
|
-
|
|
148
|
+
const updated = content.replace(input.old_string, input.new_string);
|
|
149
|
+
fs.writeFileSync(filePath, updated);
|
|
139
150
|
if (filePath.includes('personality/')) {
|
|
140
151
|
context._reloadPersonality?.();
|
|
152
|
+
if (isSoul && context.config?.supabase) {
|
|
153
|
+
backup(context.config.supabase, 'soul', updated).catch(() => {});
|
|
154
|
+
}
|
|
141
155
|
}
|
|
142
156
|
return `Edited: ${filePath}`;
|
|
143
157
|
},
|
package/src/cli/config.js
CHANGED
|
@@ -353,38 +353,40 @@ async function runOAuthFlow(cfg) {
|
|
|
353
353
|
}
|
|
354
354
|
|
|
355
355
|
function updatePersonalityNames(oldBotName, newBotName, oldOwnerName, newOwnerName) {
|
|
356
|
+
const { PERSONALITY_DIR } = require('../soul');
|
|
357
|
+
|
|
358
|
+
if (oldBotName !== newBotName) {
|
|
359
|
+
const soulPath = path.join(PERSONALITY_DIR, 'SOUL.md');
|
|
360
|
+
if (fs.existsSync(soulPath)) {
|
|
361
|
+
let content = fs.readFileSync(soulPath, 'utf-8');
|
|
362
|
+
content = content.replace(new RegExp(`# SOUL\\.md — Who is ${oldBotName}\\?`, 'g'), `# SOUL.md — Who is ${newBotName}?`);
|
|
363
|
+
content = content.replace(new RegExp(`\\*\\*Name:\\*\\* ${oldBotName}`, 'g'), `**Name:** ${newBotName}`);
|
|
364
|
+
fs.writeFileSync(soulPath, content, 'utf-8');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (oldOwnerName !== newOwnerName) {
|
|
369
|
+
const soulPath = path.join(PERSONALITY_DIR, 'SOUL.md');
|
|
370
|
+
if (fs.existsSync(soulPath)) {
|
|
371
|
+
let content = fs.readFileSync(soulPath, 'utf-8');
|
|
372
|
+
content = content.replace(new RegExp(`\\*\\*Created by:\\*\\* ${oldOwnerName}`, 'g'), `**Created by:** ${newOwnerName}`);
|
|
373
|
+
fs.writeFileSync(soulPath, content, 'utf-8');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
356
377
|
if (!fs.existsSync(USERS_DIR)) return;
|
|
357
378
|
const users = fs.readdirSync(USERS_DIR).filter(u => {
|
|
358
379
|
try { return fs.statSync(path.join(USERS_DIR, u)).isDirectory(); } catch { return false; }
|
|
359
380
|
});
|
|
360
381
|
for (const userId of users) {
|
|
361
|
-
const personalityDir = path.join(USERS_DIR, userId, 'personality');
|
|
362
|
-
if (!fs.existsSync(personalityDir)) continue;
|
|
363
|
-
|
|
364
382
|
if (oldBotName !== newBotName) {
|
|
365
|
-
const
|
|
366
|
-
if (fs.existsSync(soulPath)) {
|
|
367
|
-
let content = fs.readFileSync(soulPath, 'utf-8');
|
|
368
|
-
content = content.replace(new RegExp(`# SOUL\\.md — Who is ${oldBotName}\\?`, 'g'), `# SOUL.md — Who is ${newBotName}?`);
|
|
369
|
-
content = content.replace(new RegExp(`\\*\\*Name:\\*\\* ${oldBotName}`, 'g'), `**Name:** ${newBotName}`);
|
|
370
|
-
fs.writeFileSync(soulPath, content, 'utf-8');
|
|
371
|
-
}
|
|
372
|
-
const agentsPath = path.join(personalityDir, 'AGENTS.md');
|
|
383
|
+
const agentsPath = path.join(USERS_DIR, userId, 'personality', 'AGENTS.md');
|
|
373
384
|
if (fs.existsSync(agentsPath)) {
|
|
374
385
|
let content = fs.readFileSync(agentsPath, 'utf-8');
|
|
375
386
|
content = content.replace(new RegExp(`# AGENTS\\.md — How ${oldBotName} Works`, 'g'), `# AGENTS.md — How ${newBotName} Works`);
|
|
376
387
|
fs.writeFileSync(agentsPath, content, 'utf-8');
|
|
377
388
|
}
|
|
378
389
|
}
|
|
379
|
-
|
|
380
|
-
if (oldOwnerName !== newOwnerName) {
|
|
381
|
-
const soulPath = path.join(personalityDir, 'SOUL.md');
|
|
382
|
-
if (fs.existsSync(soulPath)) {
|
|
383
|
-
let content = fs.readFileSync(soulPath, 'utf-8');
|
|
384
|
-
content = content.replace(new RegExp(`\\*\\*Created by:\\*\\* ${oldOwnerName}`, 'g'), `**Created by:** ${newOwnerName}`);
|
|
385
|
-
fs.writeFileSync(soulPath, content, 'utf-8');
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
390
|
}
|
|
389
391
|
}
|
|
390
392
|
|
package/src/cli/init.js
CHANGED
|
@@ -752,13 +752,12 @@ function ensureDirs() {
|
|
|
752
752
|
}
|
|
753
753
|
|
|
754
754
|
function createPersonalityFiles(config) {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
fs.mkdirSync(personalityDir, { recursive: true });
|
|
755
|
+
const { PERSONALITY_DIR } = require('../soul');
|
|
756
|
+
fs.mkdirSync(PERSONALITY_DIR, { recursive: true });
|
|
757
|
+
const ownerName = config.users?.[String(config.telegram.allowedUsers[0])]?.name || config.owner.name;
|
|
759
758
|
|
|
760
|
-
|
|
761
|
-
|
|
759
|
+
if (!fs.existsSync(path.join(PERSONALITY_DIR, 'SOUL.md'))) {
|
|
760
|
+
fs.writeFileSync(path.join(PERSONALITY_DIR, 'SOUL.md'), `# SOUL.md — Who is ${config.bot.name}?
|
|
762
761
|
|
|
763
762
|
Write your bot's personality here. This shapes how it talks, thinks, and behaves.
|
|
764
763
|
|
|
@@ -781,7 +780,13 @@ Write your bot's personality here. This shapes how it talks, thinks, and behaves
|
|
|
781
780
|
---
|
|
782
781
|
*Edit this file anytime to reshape your bot's personality.*
|
|
783
782
|
`);
|
|
784
|
-
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
for (const userId of config.telegram.allowedUsers) {
|
|
786
|
+
const ownerName = config.users?.[String(userId)]?.name || config.owner.name;
|
|
787
|
+
const personalityDir = path.join(OBOL_DIR, 'users', String(userId), 'personality');
|
|
788
|
+
fs.mkdirSync(personalityDir, { recursive: true });
|
|
789
|
+
|
|
785
790
|
if (!fs.existsSync(path.join(personalityDir, 'USER.md'))) {
|
|
786
791
|
fs.writeFileSync(path.join(personalityDir, 'USER.md'), `# USER.md — About ${ownerName}
|
|
787
792
|
|
package/src/cli/prompt-debug.js
CHANGED
|
@@ -76,13 +76,22 @@ function truncate(str, max = 500) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
function printFullPrompt(params) {
|
|
79
|
-
label(
|
|
79
|
+
label(`FULL PROMPT → SYSTEM \x1b[2m[${params.model || '?'}]\x1b[0m`);
|
|
80
80
|
for (const block of (params.system || [])) {
|
|
81
81
|
const cached = block.cache_control ? ' \x1b[2m[ephemeral]\x1b[0m' : '';
|
|
82
82
|
console.log(`${truncate(block.text, 800)}${cached}`);
|
|
83
83
|
console.log(hr('·'));
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
if (params.tools?.length) {
|
|
87
|
+
label(`FULL PROMPT → TOOLS [${params.tools.length}]`);
|
|
88
|
+
for (const tool of params.tools) {
|
|
89
|
+
const cached = tool.cache_control ? ' \x1b[2m[ephemeral]\x1b[0m' : '';
|
|
90
|
+
const desc = tool.description ? ` \x1b[2m${truncate(tool.description, 100)}\x1b[0m` : '';
|
|
91
|
+
console.log(` \x1b[35m${tool.name}\x1b[0m${cached}${desc}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
86
95
|
label(`FULL PROMPT → MESSAGES [${params.messages.length}]`);
|
|
87
96
|
for (let i = 0; i < params.messages.length; i++) {
|
|
88
97
|
const msg = params.messages[i];
|
|
@@ -92,7 +101,15 @@ function printFullPrompt(params) {
|
|
|
92
101
|
: msg.content;
|
|
93
102
|
for (const block of blocks) {
|
|
94
103
|
if (block.type === 'text') {
|
|
95
|
-
|
|
104
|
+
if (block.text.startsWith('[Runtime context')) {
|
|
105
|
+
console.log(`\x1b[2m── runtime metadata ──\x1b[0m`);
|
|
106
|
+
} else if (block.text.startsWith('Current time:')) {
|
|
107
|
+
console.log(`\x1b[2m${block.text}\x1b[0m`);
|
|
108
|
+
} else if (block.text.includes('## Relevant memories') || block.text.includes('## Self-knowledge')) {
|
|
109
|
+
console.log(`\x1b[33m${block.text}\x1b[0m`);
|
|
110
|
+
} else {
|
|
111
|
+
console.log(truncate(block.text, 1000));
|
|
112
|
+
}
|
|
96
113
|
} else if (block.type === 'tool_use') {
|
|
97
114
|
console.log(`\x1b[35m[tool_use: ${block.name}]\x1b[0m ${truncate(JSON.stringify(block.input), 200)}`);
|
|
98
115
|
} else if (block.type === 'tool_result') {
|
|
@@ -131,10 +148,11 @@ async function runInspect(opts, config) {
|
|
|
131
148
|
}
|
|
132
149
|
}
|
|
133
150
|
|
|
134
|
-
|
|
151
|
+
const recentLimit = Math.ceil(25 / 3);
|
|
152
|
+
label(`RECENT MEMORY — ${recentLimit} entries \x1b[2m(budget/3, sonnet)\x1b[0m`);
|
|
135
153
|
const memHeaders = makeSupabaseHeaders(config.supabase);
|
|
136
154
|
const memRes = await fetch(
|
|
137
|
-
`${config.supabase.url}/rest/v1/obol_memory?user_id=eq.${opts.userId}&order=created_at.desc&limit
|
|
155
|
+
`${config.supabase.url}/rest/v1/obol_memory?user_id=eq.${opts.userId}&order=created_at.desc&limit=${recentLimit}&select=content,category,importance,tags,created_at`,
|
|
138
156
|
{ headers: memHeaders }
|
|
139
157
|
);
|
|
140
158
|
const memories = await memRes.json();
|
|
@@ -148,6 +166,23 @@ async function runInspect(opts, config) {
|
|
|
148
166
|
}
|
|
149
167
|
}
|
|
150
168
|
|
|
169
|
+
label('OBOL SELF MEMORY — 20 entries');
|
|
170
|
+
const selfRes = await fetch(
|
|
171
|
+
`${config.supabase.url}/rest/v1/obol_self_memory?user_id=eq.${opts.userId}&order=created_at.desc&limit=20&select=content,category,importance,tags,source,created_at`,
|
|
172
|
+
{ headers: memHeaders }
|
|
173
|
+
);
|
|
174
|
+
const selfMemories = await selfRes.json();
|
|
175
|
+
if (!selfMemories.length) {
|
|
176
|
+
console.log(' (none)');
|
|
177
|
+
} else {
|
|
178
|
+
for (const m of selfMemories) {
|
|
179
|
+
const tags = m.tags?.length ? ` \x1b[2m[${m.tags.join(', ')}]\x1b[0m` : '';
|
|
180
|
+
const src = m.source ? ` \x1b[2msrc=${m.source}\x1b[0m` : '';
|
|
181
|
+
console.log(`\x1b[2m${new Date(m.created_at).toLocaleString()}\x1b[0m \x1b[35m[${m.category}]\x1b[0m imp=${m.importance}${tags}${src}`);
|
|
182
|
+
console.log(` ${m.content}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
151
186
|
console.log('\n\x1b[2mTip: pass -m "message" to run the full production pipeline\x1b[0m\n');
|
|
152
187
|
}
|
|
153
188
|
|
package/src/messages.js
CHANGED
|
@@ -195,24 +195,32 @@ Importance: 0.3 minor, 0.5 useful, 0.7 important, 0.9 critical.`,
|
|
|
195
195
|
if (Array.isArray(facts) && facts.length > 0) {
|
|
196
196
|
const validCategories = new Set(['fact','preference','decision','lesson','person','project','event','conversation','resource','pattern','context','email']);
|
|
197
197
|
let stored = 0;
|
|
198
|
+
let updated = 0;
|
|
198
199
|
let duped = 0;
|
|
199
200
|
|
|
200
201
|
for (const fact of facts.slice(0, 5)) {
|
|
201
202
|
if (!fact.content || fact.content.length <= 10) continue;
|
|
202
|
-
try {
|
|
203
|
-
const existing = await this.memory.search(fact.content, { limit: 1, threshold: 0.82 });
|
|
204
|
-
if (existing.length > 0) { duped++; continue; }
|
|
205
|
-
} catch {}
|
|
206
203
|
const category = validCategories.has(fact.category) ? fact.category : 'fact';
|
|
207
204
|
const importance = typeof fact.importance === 'number' ? Math.min(1, Math.max(0, fact.importance)) : 0.5;
|
|
208
205
|
const tags = Array.isArray(fact.tags) ? fact.tags.slice(0, 5) : [];
|
|
206
|
+
try {
|
|
207
|
+
const related = await this.memory.search(fact.content, { limit: 1, threshold: 0.65 });
|
|
208
|
+
if (related.length > 0) {
|
|
209
|
+
const top = related[0];
|
|
210
|
+
if (top.similarity >= 0.82) { duped++; continue; }
|
|
211
|
+
await this.memory.update(top.id, { content: fact.content, category, importance, tags });
|
|
212
|
+
updated++;
|
|
213
|
+
vlog?.(`[extract] ~[${category}] ${fact.content}`);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
} catch {}
|
|
209
217
|
await this.memory.add(fact.content, { category, tags, importance, source: 'turn-extraction' });
|
|
210
218
|
stored++;
|
|
211
219
|
vlog?.(`[extract] +[${category}] ${fact.content}`);
|
|
212
220
|
}
|
|
213
221
|
|
|
214
|
-
if (stored > 0 || duped > 0) {
|
|
215
|
-
vlog?.(`[extract] ${stored} stored, ${duped} duped, ${facts.length} extracted`);
|
|
222
|
+
if (stored > 0 || updated > 0 || duped > 0) {
|
|
223
|
+
vlog?.(`[extract] ${stored} stored, ${updated} updated, ${duped} duped, ${facts.length} extracted`);
|
|
216
224
|
}
|
|
217
225
|
} else {
|
|
218
226
|
vlog?.('[extract] 0 facts (trivial exchange)');
|
|
@@ -84,7 +84,7 @@ Summarize what was cleaned and secrets migrated.`);
|
|
|
84
84
|
const taskPrompt = promptParts.join('\n\n');
|
|
85
85
|
|
|
86
86
|
const stopTyping = startTyping(ctx);
|
|
87
|
-
const status = createStatusTracker(ctx);
|
|
87
|
+
const status = createStatusTracker(ctx, config.bot?.name);
|
|
88
88
|
const chatContext = createChatContext(ctx, tenant, config, { allowedUsers: new Set(), bot, createAsk });
|
|
89
89
|
chatContext._model = 'claude-sonnet-4-6';
|
|
90
90
|
chatContext._onRouteDecision = (info) => { status.setRouteInfo(info); status.start(); };
|
|
@@ -113,7 +113,7 @@ Summarize what was cleaned and secrets migrated.`);
|
|
|
113
113
|
const testsAfter = fs.existsSync(testsDir) && fs.readdirSync(testsDir).filter(f => !f.startsWith('.')).length > 0;
|
|
114
114
|
if (!testsAfter && hasScripts) {
|
|
115
115
|
const testPrompt = `Read every script in ${plan.baseDir}/scripts/. For each script, write a corresponding test file in ${plan.baseDir}/tests/. Name each test file test-<script-name> (e.g. scripts/gmail-send.py → tests/test-gmail-send.py). After writing all tests, run them and fix any failures until they all pass. Summarize the test results.`;
|
|
116
|
-
const testStatus = createStatusTracker(ctx);
|
|
116
|
+
const testStatus = createStatusTracker(ctx, config.bot?.name);
|
|
117
117
|
const testCtx = createChatContext(ctx, tenant, config, { allowedUsers: new Set(), bot, createAsk });
|
|
118
118
|
testCtx._model = 'claude-sonnet-4-6';
|
|
119
119
|
testCtx._onRouteDecision = (info) => { testStatus.setRouteInfo(info); testStatus.start(); };
|
|
@@ -19,7 +19,7 @@ async function processMediaItems(ctx, items, { config, allowedUsers, bot, create
|
|
|
19
19
|
if (!ctx.from) return;
|
|
20
20
|
const userId = ctx.from.id;
|
|
21
21
|
const stopTyping = startTyping(ctx);
|
|
22
|
-
const status = createStatusTracker(ctx);
|
|
22
|
+
const status = createStatusTracker(ctx, config.bot?.name);
|
|
23
23
|
|
|
24
24
|
try {
|
|
25
25
|
const tenant = await getTenant(userId, config);
|
|
@@ -74,7 +74,7 @@ async function processSpecial(ctx, prompt, deps) {
|
|
|
74
74
|
if (!ctx.from) return;
|
|
75
75
|
const userId = ctx.from.id;
|
|
76
76
|
const stopTyping = startTyping(ctx);
|
|
77
|
-
const status = createStatusTracker(ctx);
|
|
77
|
+
const status = createStatusTracker(ctx, deps.config?.bot?.name);
|
|
78
78
|
|
|
79
79
|
try {
|
|
80
80
|
const tenant = await getTenant(userId, deps.config);
|
|
@@ -82,12 +82,13 @@ function createChatContext(ctx, tenant, config, { allowedUsers, bot, createAsk }
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
function createStatusTracker(ctx) {
|
|
85
|
+
function createStatusTracker(ctx, botName) {
|
|
86
86
|
let statusMsgId = null;
|
|
87
87
|
let statusText = 'Processing';
|
|
88
88
|
let statusTimer = null;
|
|
89
89
|
let statusStart = null;
|
|
90
90
|
let routeInfo = null;
|
|
91
|
+
const title = botName || 'OBOL';
|
|
91
92
|
const stopBtn = new InlineKeyboard()
|
|
92
93
|
.text('■ Stop', `stop:${ctx.chat.id}`)
|
|
93
94
|
.text('■ Force Stop', `force:${ctx.chat.id}`);
|
|
@@ -100,14 +101,14 @@ function createStatusTracker(ctx) {
|
|
|
100
101
|
const start = () => {
|
|
101
102
|
if (statusTimer) return;
|
|
102
103
|
statusStart = Date.now();
|
|
103
|
-
const html = buildStatusHtml({ route: routeInfo, elapsed: 0, toolStatus: statusText });
|
|
104
|
+
const html = buildStatusHtml({ route: routeInfo, elapsed: 0, toolStatus: statusText, title });
|
|
104
105
|
ctx.reply(html, { parse_mode: 'HTML', reply_markup: stopBtn }).then(sent => {
|
|
105
106
|
if (sent) statusMsgId = sent.message_id;
|
|
106
107
|
}).catch(() => {});
|
|
107
108
|
statusTimer = setInterval(() => {
|
|
108
109
|
if (!statusMsgId) return;
|
|
109
110
|
const elapsed = Math.round((Date.now() - statusStart) / 1000);
|
|
110
|
-
const html = buildStatusHtml({ route: routeInfo, elapsed, toolStatus: statusText });
|
|
111
|
+
const html = buildStatusHtml({ route: routeInfo, elapsed, toolStatus: statusText, title });
|
|
111
112
|
ctx.api.editMessageText(ctx.chat.id, statusMsgId, html, { parse_mode: 'HTML', reply_markup: stopBtn }).catch(() => {});
|
|
112
113
|
}, 5000);
|
|
113
114
|
};
|
|
@@ -126,7 +127,7 @@ function createStatusTracker(ctx) {
|
|
|
126
127
|
updateFormatting() {
|
|
127
128
|
if (!statusMsgId) return;
|
|
128
129
|
const elapsed = statusStart ? Math.round((Date.now() - statusStart) / 1000) : 0;
|
|
129
|
-
const html = buildStatusHtml({ route: routeInfo, elapsed, toolStatus: 'Formatting output' });
|
|
130
|
+
const html = buildStatusHtml({ route: routeInfo, elapsed, toolStatus: 'Formatting output', title });
|
|
130
131
|
ctx.api.editMessageText(ctx.chat.id, statusMsgId, html, { parse_mode: 'HTML' }).catch(() => {});
|
|
131
132
|
},
|
|
132
133
|
deleteMsg() {
|
|
@@ -151,7 +152,7 @@ async function processTextMessage(ctx, fullMessage, { config, allowedUsers, bot,
|
|
|
151
152
|
|
|
152
153
|
const chatMessage = replyContext + fullMessage;
|
|
153
154
|
const stopTyping = startTyping(ctx);
|
|
154
|
-
const status = createStatusTracker(ctx);
|
|
155
|
+
const status = createStatusTracker(ctx, config.bot?.name);
|
|
155
156
|
|
|
156
157
|
const batcher = tenant.verbose ? createVerboseBatcher(ctx) : null;
|
|
157
158
|
try {
|
package/src/tenant.js
CHANGED
|
@@ -60,7 +60,7 @@ async function createTenant(userId, config) {
|
|
|
60
60
|
|
|
61
61
|
let personalityMtime = 0;
|
|
62
62
|
try {
|
|
63
|
-
personalityMtime = fs.statSync(path.join(
|
|
63
|
+
personalityMtime = fs.statSync(path.join(PERSONALITY_DIR, 'SOUL.md')).mtimeMs;
|
|
64
64
|
} catch {}
|
|
65
65
|
|
|
66
66
|
return {
|