obol-ai 0.3.2 → 0.3.3
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 +5 -0
- package/package.json +1 -1
- package/src/claude/chat.js +5 -1
- package/src/claude/prompt.js +0 -19
- package/src/claude/router.js +54 -19
- package/src/claude/tools/files.js +2 -2
- package/src/clean.js +1 -1
- package/src/cli/prompt-debug.js +275 -0
- package/src/config.js +0 -5
- package/src/evolve/evolve.js +1 -21
- package/src/evolve/prompts.js +1 -18
- package/src/messages.js +10 -4
- package/src/personality.js +1 -21
- package/src/telegram/bot.js +0 -3
- package/src/telegram/commands/admin.js +2 -2
- package/src/telegram/commands/conversation.js +0 -1
- package/src/telegram/commands/status.js +1 -7
- package/src/telegram/utils.js +1 -9
- package/src/defaults/traits.json +0 -8
- package/src/telegram/commands/traits.js +0 -50
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
## 0.3.3
|
|
2
|
+
- update changelog
|
|
3
|
+
- improve memory retrieval quality: tighter dedup, wider window, recency boost, self-memory, jaccard dedup
|
|
4
|
+
- remove trait system entirely
|
|
5
|
+
|
|
1
6
|
## 0.3.2
|
|
2
7
|
- update changelog
|
|
3
8
|
- add list_pending_events tool to dispatch and humor passes to prevent duplicates
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
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
|
@@ -83,6 +83,7 @@ function createClaude(anthropicConfig, { personality, memory, selfMemory, userDi
|
|
|
83
83
|
onRouteDecision: context._onRouteDecision,
|
|
84
84
|
onRouteUpdate: context._onRouteUpdate,
|
|
85
85
|
recentHistory: history,
|
|
86
|
+
selfMemory,
|
|
86
87
|
});
|
|
87
88
|
memoryBlock = result.memoryBlock;
|
|
88
89
|
if (result.model) context._model = result.model;
|
|
@@ -174,11 +175,14 @@ function createClaude(anthropicConfig, { personality, memory, selfMemory, userDi
|
|
|
174
175
|
cachedTools[lastIdx] = { ...lastDef, cache_control: { type: 'ephemeral' }, run };
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
const assembledMessages = withCacheBreakpoints(withRuntimeContext([...history]));
|
|
179
|
+
context._onPromptReady?.({ system: systemPrompt, messages: assembledMessages, model: activeModel, tools: cachedTools });
|
|
180
|
+
|
|
177
181
|
const runner = client.beta.messages.toolRunner({
|
|
178
182
|
model: activeModel,
|
|
179
183
|
max_tokens: 128000,
|
|
180
184
|
system: systemPrompt,
|
|
181
|
-
messages:
|
|
185
|
+
messages: assembledMessages,
|
|
182
186
|
tools: cachedTools ?? undefined,
|
|
183
187
|
max_iterations: getMaxToolIterations(),
|
|
184
188
|
stream: true,
|
package/src/claude/prompt.js
CHANGED
|
@@ -15,25 +15,6 @@ You serve multiple people — partners, friends, people who know each other. You
|
|
|
15
15
|
parts.push(`\n## Personality\nYou are a fresh instance. Be helpful, direct, and naturally curious. Pay attention to how your owner communicates and adapt. Your personality will develop through conversation and periodic evolution.`);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
if (personality.traits) {
|
|
19
|
-
const t = personality.traits;
|
|
20
|
-
const descriptions = {
|
|
21
|
-
humor: [0, 'suppress all wit', 50, 'balanced wit', 100, 'lean heavily into jokes and playfulness'],
|
|
22
|
-
honesty: [0, 'maximize diplomatic softening', 50, 'balanced honesty', 100, 'lean toward blunt truth'],
|
|
23
|
-
directness: [0, 'elaborate context and preamble', 50, 'balanced', 100, 'get straight to the point'],
|
|
24
|
-
curiosity: [0, 'only answer what is asked', 50, 'balanced', 100, 'proactively explore and ask follow-ups'],
|
|
25
|
-
empathy: [0, 'purely task-focused', 50, 'balanced', 100, 'deeply emotionally attuned'],
|
|
26
|
-
creativity: [0, 'stick to proven patterns', 50, 'balanced', 100, 'favor novel approaches'],
|
|
27
|
-
};
|
|
28
|
-
const lines = Object.entries(t).map(([trait, val]) => {
|
|
29
|
-
const desc = descriptions[trait];
|
|
30
|
-
if (!desc) return null;
|
|
31
|
-
const label = val <= 30 ? desc[1] : val <= 70 ? desc[3] : desc[5];
|
|
32
|
-
return `- ${trait.charAt(0).toUpperCase() + trait.slice(1)}: ${val} — ${label}`;
|
|
33
|
-
}).filter(Boolean);
|
|
34
|
-
parts.push(`\n## Personality Calibration\n\nThese values (0-100) define your behavioral tendencies:\n${lines.join('\n')}\n\nInterpret these as a spectrum: 0 = suppress entirely, 50 = balanced, 100 = lean heavily into it.`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
18
|
if (personality.user) {
|
|
38
19
|
parts.push(`\n## About This User\n${personality.user}`);
|
|
39
20
|
} else {
|
package/src/claude/router.js
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
function buildRouterMessages(recentHistory, userMessage) {
|
|
2
|
+
const context = recentHistory.slice(-20).map(m => ({
|
|
3
|
+
role: m.role,
|
|
4
|
+
content: typeof m.content === 'string'
|
|
5
|
+
? m.content.substring(0, 500)
|
|
6
|
+
: m.content.filter(b => b.type === 'text').map(b => b.text).join('').substring(0, 500),
|
|
7
|
+
})).filter(m => m.content);
|
|
8
|
+
|
|
9
|
+
const firstUserIdx = context.findIndex(m => m.role === 'user');
|
|
10
|
+
const trimmed = firstUserIdx > 0 ? context.slice(firstUserIdx) : context;
|
|
11
|
+
|
|
12
|
+
return [...trimmed, { role: 'user', content: userMessage }];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function jaccardSim(a, b) {
|
|
16
|
+
const words = s => new Set(s.toLowerCase().split(/\W+/).filter(Boolean));
|
|
17
|
+
const setA = words(a), setB = words(b);
|
|
18
|
+
let inter = 0;
|
|
19
|
+
for (const w of setA) if (setB.has(w)) inter++;
|
|
20
|
+
return inter / (setA.size + setB.size - inter);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function routeMessage(client, memory, userMessage, { vlog, onRouteDecision, onRouteUpdate, recentHistory = [], selfMemory = null }) {
|
|
2
24
|
let memoryBlock = null;
|
|
3
25
|
let model = null;
|
|
4
26
|
|
|
5
27
|
try {
|
|
6
|
-
const lastAssistantMsgs = recentHistory
|
|
7
|
-
.filter(m => m.role === 'assistant')
|
|
8
|
-
.slice(-3)
|
|
9
|
-
.map(m => typeof m.content === 'string' ? m.content : m.content.filter(b => b.type === 'text').map(b => b.text).join(''))
|
|
10
|
-
.filter(Boolean);
|
|
11
|
-
|
|
12
|
-
const contextNote = lastAssistantMsgs.length > 0
|
|
13
|
-
? `\n\nRecent assistant context (last ${lastAssistantMsgs.length} turns):\n${lastAssistantMsgs.map((t, i) => `[${i + 1}] ${t.substring(0, 300)}`).join('\n')}`
|
|
14
|
-
: '';
|
|
15
|
-
|
|
16
28
|
const routerDecision = await client.messages.create({
|
|
17
29
|
model: 'claude-haiku-4-5',
|
|
18
30
|
max_tokens: 200,
|
|
19
|
-
system: `You are a router. Analyze
|
|
31
|
+
system: `You are a router. Analyze the conversation and decide:
|
|
20
32
|
|
|
21
33
|
1. Does it need memory context? (past conversations, facts, preferences, people, events)
|
|
22
34
|
2. What model complexity does it need?
|
|
@@ -24,14 +36,14 @@ async function routeMessage(client, memory, userMessage, { vlog, onRouteDecision
|
|
|
24
36
|
Reply with ONLY a JSON object:
|
|
25
37
|
{"need_memory": true/false, "search_queries": ["query1", "query2"], "model": "haiku|sonnet|opus"}
|
|
26
38
|
|
|
27
|
-
search_queries: 1-3 optimized search queries
|
|
39
|
+
search_queries: 1-3 optimized search queries based on the full conversation context. Cover distinct topics, people, or entities referenced. Single-topic messages need just one query.
|
|
28
40
|
|
|
29
41
|
Memory: casual messages (greetings, jokes, simple questions) → false. References to past, people, projects, preferences → true.
|
|
30
42
|
|
|
31
43
|
Model: Default to "sonnet". Use "haiku" for: greetings, brief acknowledgments (thanks/ok/bye), casual chitchat, quick yes/no questions, and short single-turn exchanges that don't need any tool calling. Use "sonnet" for: code generation, data analysis, content creation, explanations, creative writing, agentic tool use, general questions, opinions, advice, and most conversational exchanges with substance. Use "opus" for: professional software engineering tasks, advanced multi-step agent work, complex reasoning, scientific or mathematical problems, tasks requiring nuanced understanding, advanced coding challenges, in-depth research, and architecture or design decisions.
|
|
32
44
|
|
|
33
|
-
If recent context shows an ongoing task (sonnet/opus was just used, multi-step work in progress), bias toward that model even for short follow-up messages
|
|
34
|
-
messages:
|
|
45
|
+
If recent context shows an ongoing task (sonnet/opus was just used, multi-step work in progress), bias toward that model even for short follow-up messages.`,
|
|
46
|
+
messages: buildRouterMessages(recentHistory, userMessage),
|
|
35
47
|
});
|
|
36
48
|
|
|
37
49
|
const decisionText = routerDecision.content[0]?.text || '';
|
|
@@ -63,7 +75,7 @@ If recent context shows an ongoing task (sonnet/opus was just used, multi-step w
|
|
|
63
75
|
const budget = decision.model === 'opus' ? 40 : decision.model === 'haiku' ? 15 : 25;
|
|
64
76
|
const searchQueries = queries.length > 0 ? queries : [userMessage];
|
|
65
77
|
|
|
66
|
-
const recentMemories = await memory.byDate('
|
|
78
|
+
const recentMemories = await memory.byDate('7d', { limit: Math.ceil(budget / 3) });
|
|
67
79
|
|
|
68
80
|
const semanticResults = await Promise.all(
|
|
69
81
|
searchQueries.map(q => memory.search(q, { limit: Math.ceil(budget / searchQueries.length), threshold: 0.4 }))
|
|
@@ -75,14 +87,21 @@ If recent context shows an ongoing task (sonnet/opus was just used, multi-step w
|
|
|
75
87
|
for (const m of [...recentMemories, ...semanticMemories]) {
|
|
76
88
|
if (!seen.has(m.id)) {
|
|
77
89
|
seen.add(m.id);
|
|
78
|
-
const
|
|
79
|
-
|
|
90
|
+
const ageDays = m.created_at ? (Date.now() - new Date(m.created_at).getTime()) / 86400000 : 7;
|
|
91
|
+
const recencyBonus = Math.max(0, 1 - ageDays / 7) * 0.3;
|
|
92
|
+
m._score = (m.similarity || 0.5) * 0.5 + (m.importance || 0.5) * 0.2 + recencyBonus;
|
|
80
93
|
combined.push(m);
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
|
|
84
97
|
combined.sort((a, b) => b._score - a._score);
|
|
85
|
-
|
|
98
|
+
|
|
99
|
+
const topFacts = [];
|
|
100
|
+
for (const m of combined) {
|
|
101
|
+
if (topFacts.length >= budget) break;
|
|
102
|
+
const isDup = topFacts.some(kept => jaccardSim(kept.content, m.content) > 0.7);
|
|
103
|
+
if (!isDup) topFacts.push(m);
|
|
104
|
+
}
|
|
86
105
|
|
|
87
106
|
vlog(`[memory] ${topFacts.length} facts (${recentMemories.length} recent, ${semanticMemories.length} semantic, budget=${budget})`);
|
|
88
107
|
onRouteUpdate?.({ memoryCount: topFacts.length });
|
|
@@ -94,6 +113,22 @@ If recent context shows an ongoing task (sonnet/opus was just used, multi-step w
|
|
|
94
113
|
});
|
|
95
114
|
memoryBlock = `## Relevant memories\n${lines.join('\n')}`;
|
|
96
115
|
}
|
|
116
|
+
|
|
117
|
+
if (selfMemory) {
|
|
118
|
+
const selfResults = await Promise.all(
|
|
119
|
+
searchQueries.map(q => selfMemory.search(q, { limit: 5, threshold: 0.4 }))
|
|
120
|
+
);
|
|
121
|
+
const seen2 = new Set();
|
|
122
|
+
const topSelf = [];
|
|
123
|
+
for (const m of selfResults.flat()) {
|
|
124
|
+
if (!seen2.has(m.id)) { seen2.add(m.id); topSelf.push(m); }
|
|
125
|
+
}
|
|
126
|
+
if (topSelf.length > 0) {
|
|
127
|
+
const selfLines = topSelf.slice(0, 8).map(m => `- [${m.category}] ${m.content}`);
|
|
128
|
+
memoryBlock = (memoryBlock || '') + `\n\n## Self-knowledge\n${selfLines.join('\n')}`;
|
|
129
|
+
vlog(`[memory] +${topSelf.length} self-memory facts`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
97
132
|
}
|
|
98
133
|
} catch (e) {
|
|
99
134
|
console.error('[router] Memory/routing decision failed:', e.message);
|
|
@@ -122,7 +122,7 @@ const handlers = {
|
|
|
122
122
|
const filePath = userDir ? resolveUserPath(input.path, userDir) : input.path;
|
|
123
123
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
124
124
|
fs.writeFileSync(filePath, input.content);
|
|
125
|
-
if (
|
|
125
|
+
if (filePath.includes('personality/')) {
|
|
126
126
|
context._reloadPersonality?.();
|
|
127
127
|
}
|
|
128
128
|
return `Written: ${filePath}`;
|
|
@@ -136,7 +136,7 @@ const handlers = {
|
|
|
136
136
|
if (count === 0) return `Error: old_string not found in ${input.path}`;
|
|
137
137
|
if (count > 1) return `Error: old_string matches ${count} times — add more context to make it unique`;
|
|
138
138
|
fs.writeFileSync(filePath, content.replace(input.old_string, input.new_string));
|
|
139
|
-
if (
|
|
139
|
+
if (filePath.includes('personality/')) {
|
|
140
140
|
context._reloadPersonality?.();
|
|
141
141
|
}
|
|
142
142
|
return `Edited: ${filePath}`;
|
package/src/clean.js
CHANGED
|
@@ -19,7 +19,7 @@ const ASSET_EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.
|
|
|
19
19
|
|
|
20
20
|
// Dirs where only .md files are allowed (with per-dir exceptions)
|
|
21
21
|
const MD_ONLY_DIRS = new Set(['personality', 'commands']);
|
|
22
|
-
const MD_DIR_EXCEPTIONS = {
|
|
22
|
+
const MD_DIR_EXCEPTIONS = {};
|
|
23
23
|
|
|
24
24
|
function safeReaddir(dir) {
|
|
25
25
|
try {
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Full production chat flow in the CLI — router, memory injection, tools, response.
|
|
5
|
+
* Usage: node src/cli/prompt-debug.js [options]
|
|
6
|
+
* -u, --user <id> Telegram user ID (default: 206639616)
|
|
7
|
+
* -m, --message <text> Message to send through the full production pipeline
|
|
8
|
+
* -l, --limit <n> History messages to load (default: 20)
|
|
9
|
+
* --log Write exchange to obol_messages (default: off)
|
|
10
|
+
* --no-system Skip printing the system prompt in inspect mode
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { loadConfig, getUserDir } = require('../config');
|
|
15
|
+
const { PERSONALITY_DIR } = require('../soul');
|
|
16
|
+
const { loadPersonality } = require('../personality');
|
|
17
|
+
const { buildSystemPrompt } = require('../claude/prompt');
|
|
18
|
+
const { createAnthropicClient } = require('../claude/client');
|
|
19
|
+
const { createMemory } = require('../memory');
|
|
20
|
+
const { createSelfMemory } = require('../memory-self');
|
|
21
|
+
const { createClaude } = require('../claude');
|
|
22
|
+
const { createMessageLog } = require('../messages');
|
|
23
|
+
const { BackgroundRunner } = require('../background');
|
|
24
|
+
|
|
25
|
+
const DEFAULT_USER_ID = 206639616;
|
|
26
|
+
|
|
27
|
+
function parseArgs() {
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const opts = { userId: DEFAULT_USER_ID, message: null, limit: 20, log: false, showSystem: true };
|
|
30
|
+
for (let i = 0; i < args.length; i++) {
|
|
31
|
+
const a = args[i];
|
|
32
|
+
if ((a === '-u' || a === '--user') && args[i + 1]) opts.userId = parseInt(args[++i]);
|
|
33
|
+
else if ((a === '-m' || a === '--message') && args[i + 1]) opts.message = args[++i];
|
|
34
|
+
else if ((a === '-l' || a === '--limit') && args[i + 1]) opts.limit = parseInt(args[++i]);
|
|
35
|
+
else if (a === '--log') opts.log = true;
|
|
36
|
+
else if (a === '--no-system') opts.showSystem = false;
|
|
37
|
+
}
|
|
38
|
+
return opts;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function makeSupabaseHeaders(supabase) {
|
|
42
|
+
return {
|
|
43
|
+
'apikey': supabase.serviceKey,
|
|
44
|
+
'Authorization': `Bearer ${supabase.serviceKey}`,
|
|
45
|
+
'Content-Type': 'application/json',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function fetchMessages(supabase, chatId, limit) {
|
|
50
|
+
const headers = makeSupabaseHeaders(supabase);
|
|
51
|
+
const res = await fetch(
|
|
52
|
+
`${supabase.url}/rest/v1/obol_messages?chat_id=eq.${chatId}&order=created_at.desc&limit=${limit}&select=role,content,created_at,model`,
|
|
53
|
+
{ headers }
|
|
54
|
+
);
|
|
55
|
+
if (!res.ok) throw new Error(`messages fetch failed: HTTP ${res.status}`);
|
|
56
|
+
return (await res.json()).reverse();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function hr(char = '─', width = 80) { return char.repeat(width); }
|
|
60
|
+
|
|
61
|
+
function label(text) {
|
|
62
|
+
console.log(`\n\x1b[33m\x1b[1m${text}\x1b[0m`);
|
|
63
|
+
console.log(hr());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function formatRole(role) {
|
|
67
|
+
if (role === 'user') return '\x1b[36mUSER\x1b[0m';
|
|
68
|
+
if (role === 'assistant') return '\x1b[32mASSISTANT\x1b[0m';
|
|
69
|
+
return role.toUpperCase();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function truncate(str, max = 500) {
|
|
73
|
+
if (!str) return '';
|
|
74
|
+
if (str.length <= max) return str;
|
|
75
|
+
return str.slice(0, max) + `\x1b[2m... [+${str.length - max} chars]\x1b[0m`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function printFullPrompt(params) {
|
|
79
|
+
label('FULL PROMPT → SYSTEM');
|
|
80
|
+
for (const block of (params.system || [])) {
|
|
81
|
+
const cached = block.cache_control ? ' \x1b[2m[ephemeral]\x1b[0m' : '';
|
|
82
|
+
console.log(`${truncate(block.text, 800)}${cached}`);
|
|
83
|
+
console.log(hr('·'));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
label(`FULL PROMPT → MESSAGES [${params.messages.length}]`);
|
|
87
|
+
for (let i = 0; i < params.messages.length; i++) {
|
|
88
|
+
const msg = params.messages[i];
|
|
89
|
+
console.log(`\n\x1b[1m[${i + 1}/${params.messages.length}] ${formatRole(msg.role)}\x1b[0m`);
|
|
90
|
+
const blocks = typeof msg.content === 'string'
|
|
91
|
+
? [{ type: 'text', text: msg.content }]
|
|
92
|
+
: msg.content;
|
|
93
|
+
for (const block of blocks) {
|
|
94
|
+
if (block.type === 'text') {
|
|
95
|
+
console.log(truncate(block.text, 1000));
|
|
96
|
+
} else if (block.type === 'tool_use') {
|
|
97
|
+
console.log(`\x1b[35m[tool_use: ${block.name}]\x1b[0m ${truncate(JSON.stringify(block.input), 200)}`);
|
|
98
|
+
} else if (block.type === 'tool_result') {
|
|
99
|
+
const content = Array.isArray(block.content)
|
|
100
|
+
? block.content.map(b => b.text || '').join('')
|
|
101
|
+
: String(block.content || '');
|
|
102
|
+
console.log(`\x1b[35m[tool_result: ${block.tool_use_id}]\x1b[0m ${truncate(content, 200)}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
console.log('');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function runInspect(opts, config) {
|
|
110
|
+
const userDir = getUserDir(opts.userId);
|
|
111
|
+
const personality = loadPersonality(PERSONALITY_DIR, path.join(userDir, 'personality'));
|
|
112
|
+
|
|
113
|
+
console.log('\x1b[1m' + hr('═') + '\x1b[0m');
|
|
114
|
+
console.log(`\x1b[1m INSPECT — user ${opts.userId}\x1b[0m`);
|
|
115
|
+
console.log('\x1b[1m' + hr('═') + '\x1b[0m');
|
|
116
|
+
|
|
117
|
+
if (opts.showSystem) {
|
|
118
|
+
label('SYSTEM PROMPT');
|
|
119
|
+
console.log(buildSystemPrompt(personality, userDir, { botName: config.bot?.name }));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const messages = await fetchMessages(config.supabase, opts.userId, opts.limit);
|
|
123
|
+
label(`CONVERSATION HISTORY — ${messages.length} messages`);
|
|
124
|
+
if (messages.length === 0) {
|
|
125
|
+
console.log(' (none)');
|
|
126
|
+
} else {
|
|
127
|
+
for (const msg of messages) {
|
|
128
|
+
const model = msg.model ? ` \x1b[2m[${msg.model}]\x1b[0m` : '';
|
|
129
|
+
console.log(`\n${formatRole(msg.role)}${model} \x1b[2m${new Date(msg.created_at).toLocaleString()}\x1b[0m`);
|
|
130
|
+
console.log(truncate(msg.content));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
label('RECENT MEMORY — 20 entries');
|
|
135
|
+
const memHeaders = makeSupabaseHeaders(config.supabase);
|
|
136
|
+
const memRes = await fetch(
|
|
137
|
+
`${config.supabase.url}/rest/v1/obol_memory?user_id=eq.${opts.userId}&order=created_at.desc&limit=20&select=content,category,importance,tags,created_at`,
|
|
138
|
+
{ headers: memHeaders }
|
|
139
|
+
);
|
|
140
|
+
const memories = await memRes.json();
|
|
141
|
+
if (!memories.length) {
|
|
142
|
+
console.log(' (none)');
|
|
143
|
+
} else {
|
|
144
|
+
for (const m of memories) {
|
|
145
|
+
const tags = m.tags?.length ? ` \x1b[2m[${m.tags.join(', ')}]\x1b[0m` : '';
|
|
146
|
+
console.log(`\x1b[2m${new Date(m.created_at).toLocaleString()}\x1b[0m \x1b[35m[${m.category}]\x1b[0m imp=${m.importance}${tags}`);
|
|
147
|
+
console.log(` ${m.content}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log('\n\x1b[2mTip: pass -m "message" to run the full production pipeline\x1b[0m\n');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function runChat(opts, config) {
|
|
155
|
+
const userId = opts.userId;
|
|
156
|
+
const userDir = getUserDir(userId);
|
|
157
|
+
const personality = loadPersonality(PERSONALITY_DIR, path.join(userDir, 'personality'));
|
|
158
|
+
|
|
159
|
+
process.stdout.write('\x1b[2mInitializing...\x1b[0m');
|
|
160
|
+
const memory = await createMemory(config.supabase, userId);
|
|
161
|
+
const selfMemory = await createSelfMemory(config.supabase, userId);
|
|
162
|
+
process.stdout.write(' done\n');
|
|
163
|
+
|
|
164
|
+
const claude = createClaude(config.anthropic, {
|
|
165
|
+
personality,
|
|
166
|
+
memory,
|
|
167
|
+
selfMemory,
|
|
168
|
+
userDir,
|
|
169
|
+
bridgeEnabled: false,
|
|
170
|
+
botName: config.bot?.name,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const messageLog = opts.log
|
|
174
|
+
? createMessageLog(config.supabase, memory, config.anthropic, userId, userDir)
|
|
175
|
+
: null;
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
// Load history and inject
|
|
179
|
+
const rawMessages = await fetchMessages(config.supabase, userId, opts.limit);
|
|
180
|
+
const firstUserIdx = rawMessages.findIndex(m => m.role === 'user');
|
|
181
|
+
const historyMessages = firstUserIdx > 0 ? rawMessages.slice(firstUserIdx) : rawMessages;
|
|
182
|
+
for (const msg of historyMessages) {
|
|
183
|
+
claude.injectHistory(userId, msg.role, msg.content);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log('\n\x1b[1m' + hr('═') + '\x1b[0m');
|
|
187
|
+
console.log(`\x1b[1m CHAT DEBUG — user ${userId}\x1b[0m`);
|
|
188
|
+
console.log('\x1b[1m' + hr('═') + '\x1b[0m');
|
|
189
|
+
|
|
190
|
+
label(`USER MESSAGE`);
|
|
191
|
+
console.log(opts.message);
|
|
192
|
+
|
|
193
|
+
label('PIPELINE');
|
|
194
|
+
|
|
195
|
+
const start = Date.now();
|
|
196
|
+
const bg = new BackgroundRunner();
|
|
197
|
+
|
|
198
|
+
const context = {
|
|
199
|
+
chatId: userId,
|
|
200
|
+
userId,
|
|
201
|
+
verbose: true,
|
|
202
|
+
bg,
|
|
203
|
+
scheduler: null,
|
|
204
|
+
toolPrefs: new Map(),
|
|
205
|
+
messageLog,
|
|
206
|
+
config,
|
|
207
|
+
_verboseNotify: (msg) => console.log(` \x1b[2m${msg}\x1b[0m`),
|
|
208
|
+
_onRouteDecision: ({ model, needMemory }) => {
|
|
209
|
+
console.log(` \x1b[2m[route] model=${model} memory=${needMemory}\x1b[0m`);
|
|
210
|
+
},
|
|
211
|
+
_onRouteUpdate: ({ memoryCount, model }) => {
|
|
212
|
+
if (memoryCount !== undefined) console.log(` \x1b[2m[route] memory=${memoryCount} facts injected\x1b[0m`);
|
|
213
|
+
if (model) console.log(` \x1b[2m[route] escalated → ${model}\x1b[0m`);
|
|
214
|
+
},
|
|
215
|
+
_onToolStart: (toolName, inputSummary) => {
|
|
216
|
+
console.log(` \x1b[2m[tool] ${toolName}${inputSummary ? ': ' + inputSummary : ''}\x1b[0m`);
|
|
217
|
+
},
|
|
218
|
+
_onPromptReady: (params) => {
|
|
219
|
+
printFullPrompt(params);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
if (messageLog) {
|
|
224
|
+
messageLog.log(userId, 'user', opts.message);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const { text: response, usage, model } = await claude.chat(opts.message, context);
|
|
228
|
+
|
|
229
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
230
|
+
|
|
231
|
+
if (messageLog) {
|
|
232
|
+
messageLog.log(userId, 'assistant', response, {
|
|
233
|
+
model,
|
|
234
|
+
tokensIn: usage?.input_tokens,
|
|
235
|
+
tokensOut: usage?.output_tokens,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const tokIn = usage?.input_tokens >= 1000
|
|
240
|
+
? `${(usage.input_tokens / 1000).toFixed(1)}k`
|
|
241
|
+
: usage?.input_tokens ?? '?';
|
|
242
|
+
const tokOut = usage?.output_tokens >= 1000
|
|
243
|
+
? `${(usage.output_tokens / 1000).toFixed(1)}k`
|
|
244
|
+
: usage?.output_tokens ?? '?';
|
|
245
|
+
|
|
246
|
+
label(`RESPONSE \x1b[2m${model} | ${tokIn} in / ${tokOut} out | ${elapsed}s\x1b[0m`);
|
|
247
|
+
console.log(response ?? '(no response)');
|
|
248
|
+
console.log('\n' + hr('═') + '\n');
|
|
249
|
+
|
|
250
|
+
if (opts.log) {
|
|
251
|
+
console.log('\x1b[2mLogged to obol_messages\x1b[0m\n');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function promptDebug(opts) {
|
|
256
|
+
const config = loadConfig();
|
|
257
|
+
if (!config) { console.error('No config found — run: obol init'); process.exit(1); }
|
|
258
|
+
if (!config.supabase?.url || !config.supabase?.serviceKey) { console.error('Supabase not configured'); process.exit(1); }
|
|
259
|
+
|
|
260
|
+
if (opts.message) {
|
|
261
|
+
await runChat(opts, config);
|
|
262
|
+
} else {
|
|
263
|
+
await runInspect(opts, config);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function main() {
|
|
268
|
+
await promptDebug(parseArgs());
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (require.main === module) {
|
|
272
|
+
main().catch(e => { console.error(e.message); process.exit(1); });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = { promptDebug };
|
package/src/config.js
CHANGED
|
@@ -161,11 +161,6 @@ function ensureUserDir(userId) {
|
|
|
161
161
|
if (fs.existsSync(defaultAgents) && !fs.existsSync(targetAgents)) {
|
|
162
162
|
fs.copyFileSync(defaultAgents, targetAgents);
|
|
163
163
|
}
|
|
164
|
-
const defaultTraits = path.join(__dirname, 'defaults', 'traits.json');
|
|
165
|
-
const targetTraits = path.join(dir, 'personality', 'traits.json');
|
|
166
|
-
if (fs.existsSync(defaultTraits) && !fs.existsSync(targetTraits)) {
|
|
167
|
-
fs.copyFileSync(defaultTraits, targetTraits);
|
|
168
|
-
}
|
|
169
164
|
return dir;
|
|
170
165
|
}
|
|
171
166
|
|
package/src/evolve/evolve.js
CHANGED
|
@@ -2,7 +2,6 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { execFileSync } = require('child_process');
|
|
4
4
|
const { OBOL_DIR } = require('../config');
|
|
5
|
-
const { loadTraits, saveTraits } = require('../personality');
|
|
6
5
|
const { isValidNpmPackage } = require('../sanitize');
|
|
7
6
|
const { loadEvolutionState, saveEvolutionState } = require('./state');
|
|
8
7
|
const { readDir, syncDir } = require('./filesystem');
|
|
@@ -33,7 +32,6 @@ async function evolve(claudeClient, messageLog, memory, userDir, supabaseConfig
|
|
|
33
32
|
const currentSoul = fs.existsSync(soulPath) ? fs.readFileSync(soulPath, 'utf-8') : '';
|
|
34
33
|
const currentUser = fs.existsSync(userPath) ? fs.readFileSync(userPath, 'utf-8') : '';
|
|
35
34
|
const currentAgents = fs.existsSync(agentsPath) ? fs.readFileSync(agentsPath, 'utf-8') : '';
|
|
36
|
-
const currentTraits = loadTraits(userPersonalityDir);
|
|
37
35
|
const currentScripts = readDir(scriptsDir);
|
|
38
36
|
const currentTests = readDir(testsDir);
|
|
39
37
|
const currentCommands = readDir(commandsDir);
|
|
@@ -192,8 +190,7 @@ Produce a structured growth report covering:
|
|
|
192
190
|
3. RELATIONSHIP SHIFTS — How the dynamic with the owner changed (closer, more trust, new friction, etc.)
|
|
193
191
|
4. BEHAVIORAL PATTERNS — Recurring interaction styles or habits observed
|
|
194
192
|
5. GROWTH EDGES — Areas where the personality is being pushed or pulled in new directions
|
|
195
|
-
6.
|
|
196
|
-
7. IDENTITY CONTINUITY — What core aspects stayed the same and should be preserved
|
|
193
|
+
6. IDENTITY CONTINUITY — What core aspects stayed the same and should be preserved
|
|
197
194
|
|
|
198
195
|
Be specific. Cite evidence from the conversations, memories, and self-memories. This report guides the evolution rewrite.`,
|
|
199
196
|
messages: [{
|
|
@@ -204,9 +201,6 @@ ${previousSoul || '(not available)'}
|
|
|
204
201
|
## Current SOUL
|
|
205
202
|
${currentSoul || '(empty)'}
|
|
206
203
|
|
|
207
|
-
## Current Traits
|
|
208
|
-
${JSON.stringify(currentTraits)}
|
|
209
|
-
|
|
210
204
|
## New Memories Since Last Evolution (${recentMemories.length})
|
|
211
205
|
${recentMemorySummary || '(none)'}
|
|
212
206
|
|
|
@@ -242,7 +236,6 @@ A pre-evolution analysis has been conducted comparing your previous state agains
|
|
|
242
236
|
lastEvolution: state.lastEvolution,
|
|
243
237
|
firstEvolutionPreamble,
|
|
244
238
|
growthPreamble,
|
|
245
|
-
currentTraits,
|
|
246
239
|
baselineResults,
|
|
247
240
|
});
|
|
248
241
|
|
|
@@ -431,19 +424,6 @@ Fix the scripts. Tests define correct behavior.`
|
|
|
431
424
|
fs.writeFileSync(agentsPath, result.agents);
|
|
432
425
|
}
|
|
433
426
|
|
|
434
|
-
if (result.traits && typeof result.traits === 'object') {
|
|
435
|
-
const validTraits = {};
|
|
436
|
-
for (const [key, val] of Object.entries(result.traits)) {
|
|
437
|
-
if (typeof val === 'number' && val >= 0 && val <= 100) {
|
|
438
|
-
validTraits[key] = Math.round(val);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
if (Object.keys(validTraits).length > 0) {
|
|
442
|
-
const merged = { ...currentTraits, ...validTraits };
|
|
443
|
-
saveTraits(personalityDir, merged);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
427
|
if (result.commands && typeof result.commands === 'object') {
|
|
448
428
|
if (Object.keys(result.commands).length > 0 || Object.keys(currentCommands).length > 0) {
|
|
449
429
|
syncDir(commandsDir, result.commands);
|
package/src/evolve/prompts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function buildEvolutionPrompt({ evolutionNumber, lastEvolution, firstEvolutionPreamble, growthPreamble,
|
|
1
|
+
function buildEvolutionPrompt({ evolutionNumber, lastEvolution, firstEvolutionPreamble, growthPreamble, baselineResults }) {
|
|
2
2
|
return `You are an AI undergoing evolution #${evolutionNumber}. ${lastEvolution ? `Last evolution: ${lastEvolution}.` : 'This is your first evolution.'}
|
|
3
3
|
${firstEvolutionPreamble}${growthPreamble}
|
|
4
4
|
|
|
@@ -24,22 +24,6 @@ Operational manual written as instructions to yourself. Focus on owner-specific
|
|
|
24
24
|
|
|
25
25
|
**What belongs in AGENTS.md:** Memory Strategy, Self-Extending patterns, Scripts & Service Integrations, Background Task Guidelines, Communication Style, Evolution notes, and any owner-specific workflows or lessons discovered from conversations. Keep what works, remove what doesn't.
|
|
26
26
|
|
|
27
|
-
## Part 3b: Personality Traits
|
|
28
|
-
|
|
29
|
-
Current trait values: ${JSON.stringify(currentTraits)}
|
|
30
|
-
|
|
31
|
-
Based on conversation patterns, adjust each trait (0-100). Consider:
|
|
32
|
-
- Does the owner respond well to humor? Increase/decrease humor.
|
|
33
|
-
- Does the owner prefer direct answers? Adjust directness.
|
|
34
|
-
- Does the owner appreciate creative solutions? Adjust creativity.
|
|
35
|
-
- Does the owner share emotions or stay task-focused? Adjust empathy.
|
|
36
|
-
- Does the owner want blunt truth or diplomatic framing? Adjust honesty.
|
|
37
|
-
- Does the owner welcome proactive questions? Adjust curiosity.
|
|
38
|
-
|
|
39
|
-
Small adjustments (±5-15) per evolution. Don't swing wildly.
|
|
40
|
-
|
|
41
|
-
Include in output JSON as: "traits": { "humor": 65, "honesty": 80, ... }
|
|
42
|
-
|
|
43
27
|
## Part 4: Scripts
|
|
44
28
|
|
|
45
29
|
Review and refactor every script. Standards:
|
|
@@ -156,7 +140,6 @@ The OBOL directory has a FIXED structure: personality/, scripts/, tests/, comman
|
|
|
156
140
|
"soul": "full SOUL.md content",
|
|
157
141
|
"user": "full USER.md content",
|
|
158
142
|
"agents": "full AGENTS.md content",
|
|
159
|
-
"traits": { "humor": 65, "honesty": 80, "directness": 70, "curiosity": 75, "empathy": 65, "creativity": 70 },
|
|
160
143
|
"scripts": { "name.js": "content" },
|
|
161
144
|
"tests": { "test-name.js": "content" },
|
|
162
145
|
"commands": { "name.md": "content" },
|
package/src/messages.js
CHANGED
|
@@ -41,7 +41,8 @@ class MessageLog {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
async log(chatId, role, content, opts = {}) {
|
|
44
|
-
const
|
|
44
|
+
const serialized = typeof content === 'string' ? content : JSON.stringify(content);
|
|
45
|
+
const truncated = serialized.substring(0, 50000);
|
|
45
46
|
|
|
46
47
|
try {
|
|
47
48
|
await fetch(`${this.url}/rest/v1/obol_messages`, {
|
|
@@ -60,7 +61,7 @@ class MessageLog {
|
|
|
60
61
|
console.error('[messages] Log failed:', e.message);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
if (role === 'user') {
|
|
64
|
+
if (role === 'user' && typeof content === 'string') {
|
|
64
65
|
this._lastUserMessage.set(chatId, truncated);
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -83,7 +84,12 @@ class MessageLog {
|
|
|
83
84
|
{ headers: this.headers }
|
|
84
85
|
);
|
|
85
86
|
const data = await res.json();
|
|
86
|
-
const rows = data.reverse()
|
|
87
|
+
const rows = data.reverse().map(row => {
|
|
88
|
+
if (typeof row.content === 'string' && row.content.startsWith('[')) {
|
|
89
|
+
try { row.content = JSON.parse(row.content); } catch {}
|
|
90
|
+
}
|
|
91
|
+
return row;
|
|
92
|
+
});
|
|
87
93
|
const firstUserIdx = rows.findIndex(r => r.role === 'user');
|
|
88
94
|
return firstUserIdx > 0 ? rows.slice(firstUserIdx) : rows;
|
|
89
95
|
} catch {
|
|
@@ -194,7 +200,7 @@ Importance: 0.3 minor, 0.5 useful, 0.7 important, 0.9 critical.`,
|
|
|
194
200
|
for (const fact of facts.slice(0, 5)) {
|
|
195
201
|
if (!fact.content || fact.content.length <= 10) continue;
|
|
196
202
|
try {
|
|
197
|
-
const existing = await this.memory.search(fact.content, { limit: 1, threshold: 0.
|
|
203
|
+
const existing = await this.memory.search(fact.content, { limit: 1, threshold: 0.82 });
|
|
198
204
|
if (existing.length > 0) { duped++; continue; }
|
|
199
205
|
} catch {}
|
|
200
206
|
const category = validCategories.has(fact.category) ? fact.category : 'fact';
|
package/src/personality.js
CHANGED
|
@@ -2,8 +2,6 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { OBOL_DIR } = require('./config');
|
|
4
4
|
|
|
5
|
-
const DEFAULT_TRAITS = require('./defaults/traits.json');
|
|
6
|
-
|
|
7
5
|
function loadPersonality(sharedDir, userDir) {
|
|
8
6
|
sharedDir = sharedDir || path.join(OBOL_DIR, 'personality');
|
|
9
7
|
userDir = userDir || sharedDir;
|
|
@@ -22,25 +20,7 @@ function loadPersonality(sharedDir, userDir) {
|
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
personality.traits = loadTraits(userDir);
|
|
26
|
-
|
|
27
23
|
return personality;
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
dir = dir || path.join(OBOL_DIR, 'personality');
|
|
32
|
-
const traitsPath = path.join(dir, 'traits.json');
|
|
33
|
-
try {
|
|
34
|
-
return JSON.parse(fs.readFileSync(traitsPath, 'utf-8'));
|
|
35
|
-
} catch {
|
|
36
|
-
return { ...DEFAULT_TRAITS };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function saveTraits(dir, traits) {
|
|
41
|
-
dir = dir || path.join(OBOL_DIR, 'personality');
|
|
42
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
43
|
-
fs.writeFileSync(path.join(dir, 'traits.json'), JSON.stringify(traits, null, 2));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
module.exports = { loadPersonality, loadTraits, saveTraits, DEFAULT_TRAITS };
|
|
26
|
+
module.exports = { loadPersonality };
|
package/src/telegram/bot.js
CHANGED
|
@@ -11,7 +11,6 @@ const memoryCommands = require('./commands/memory');
|
|
|
11
11
|
const statusCommands = require('./commands/status');
|
|
12
12
|
const conversationCommands = require('./commands/conversation');
|
|
13
13
|
const adminCommands = require('./commands/admin');
|
|
14
|
-
const traitsCommands = require('./commands/traits');
|
|
15
14
|
const secretsCommands = require('./commands/secrets');
|
|
16
15
|
const toolsCommands = require('./commands/tools');
|
|
17
16
|
const { registerTextHandler } = require('./handlers/text');
|
|
@@ -97,7 +96,6 @@ function createBot(telegramConfig, config) {
|
|
|
97
96
|
{ command: 'status', description: 'Bot status and uptime' },
|
|
98
97
|
{ command: 'backup', description: 'Trigger GitHub backup now' },
|
|
99
98
|
{ command: 'clean', description: 'Audit and fix workspace' },
|
|
100
|
-
{ command: 'traits', description: 'View or adjust personality traits' },
|
|
101
99
|
{ command: 'secret', description: 'Manage per-user secrets' },
|
|
102
100
|
{ command: 'evolution', description: 'Evolution progress' },
|
|
103
101
|
{ command: 'verbose', description: 'Toggle verbose mode on/off' },
|
|
@@ -112,7 +110,6 @@ function createBot(telegramConfig, config) {
|
|
|
112
110
|
memoryCommands.register(bot, config);
|
|
113
111
|
statusCommands.register(bot, config);
|
|
114
112
|
adminCommands.register(bot, config, createAsk);
|
|
115
|
-
traitsCommands.register(bot, config);
|
|
116
113
|
secretsCommands.register(bot, config);
|
|
117
114
|
toolsCommands.register(bot, config);
|
|
118
115
|
|
|
@@ -55,12 +55,12 @@ function register(bot, config, createAsk) {
|
|
|
55
55
|
## Workspace Structure
|
|
56
56
|
Allowed root directories: personality/, scripts/, tests/, commands/, apps/, logs/, assets/
|
|
57
57
|
Allowed root files: config.json, secrets.json, .evolution-state.json, .first-run-done, .post-setup-done
|
|
58
|
-
- personality/ and commands/ only contain .md files
|
|
58
|
+
- personality/ and commands/ only contain .md files
|
|
59
59
|
- Unknown directories at the root should be moved into apps/
|
|
60
60
|
- Script files (.js, .ts, .sh, etc.) go into scripts/
|
|
61
61
|
- Asset files (images, audio, pdf, etc.) go into assets/
|
|
62
62
|
- .DS_Store and other dotfiles should be deleted
|
|
63
|
-
- secrets.json
|
|
63
|
+
- secrets.json must NOT be moved
|
|
64
64
|
|
|
65
65
|
## Issues Found
|
|
66
66
|
${issueLines}
|
|
@@ -46,7 +46,6 @@ function register(bot, config) {
|
|
|
46
46
|
/events — Upcoming scheduled events
|
|
47
47
|
/tasks — Running background tasks
|
|
48
48
|
/tools — Toggle optional tools on/off
|
|
49
|
-
/traits — View/adjust personality traits
|
|
50
49
|
/secret — Manage per-user secrets
|
|
51
50
|
/evolution — Evolution progress
|
|
52
51
|
/status — Bot status and uptime
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
1
|
const { getTenant } = require('../../tenant');
|
|
3
|
-
const { loadTraits } = require('../../personality');
|
|
4
2
|
const { loadEvolutionState } = require('../../evolve');
|
|
5
3
|
const { getMaxToolIterations } = require('../../claude');
|
|
6
|
-
const { termBar
|
|
4
|
+
const { termBar } = require('../utils');
|
|
7
5
|
const { TERM_SEP } = require('../constants');
|
|
8
6
|
|
|
9
7
|
function register(bot, config) {
|
|
@@ -52,10 +50,6 @@ function register(bot, config) {
|
|
|
52
50
|
lines.push(` last ${new Date(evoState.lastEvolution).toLocaleDateString()}`);
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
const personalityDir = path.join(tenant.userDir, 'personality');
|
|
56
|
-
const traits = loadTraits(personalityDir);
|
|
57
|
-
lines.push(``, `TRAITS`);
|
|
58
|
-
lines.push(formatTraits(traits));
|
|
59
53
|
lines.push(TERM_SEP);
|
|
60
54
|
|
|
61
55
|
await ctx.reply(`<pre>${lines.join('\n')}</pre>`, { parse_mode: 'HTML' });
|
package/src/telegram/utils.js
CHANGED
|
@@ -54,14 +54,6 @@ function startTyping(ctx) {
|
|
|
54
54
|
return () => clearInterval(interval);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
function formatTraits(traits) {
|
|
58
|
-
const maxLen = Math.max(...Object.keys(traits).map(k => k.length));
|
|
59
|
-
return Object.entries(traits).map(([name, val]) => {
|
|
60
|
-
const label = (name.charAt(0).toUpperCase() + name.slice(1)).padEnd(maxLen + 1);
|
|
61
|
-
return ` ${label}${termBar(val)} ${val}`;
|
|
62
|
-
}).join('\n');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
57
|
function splitMessage(text, maxLength) {
|
|
66
58
|
const chunks = [];
|
|
67
59
|
let remaining = text;
|
|
@@ -78,4 +70,4 @@ function splitMessage(text, maxLength) {
|
|
|
78
70
|
return chunks;
|
|
79
71
|
}
|
|
80
72
|
|
|
81
|
-
module.exports = { termBar, markdownToTelegramHtml, sendHtml, editHtml, startTyping,
|
|
73
|
+
module.exports = { termBar, markdownToTelegramHtml, sendHtml, editHtml, startTyping, splitMessage };
|
package/src/defaults/traits.json
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const { getTenant } = require('../../tenant');
|
|
3
|
-
const { loadTraits, saveTraits, DEFAULT_TRAITS } = require('../../personality');
|
|
4
|
-
const { formatTraits } = require('../utils');
|
|
5
|
-
const { TERM_SEP } = require('../constants');
|
|
6
|
-
|
|
7
|
-
function register(bot, config) {
|
|
8
|
-
const botName = config.bot?.name || 'OBOL';
|
|
9
|
-
bot.command('traits', async (ctx) => {
|
|
10
|
-
if (!ctx.from) return;
|
|
11
|
-
const tenant = await getTenant(ctx.from.id, config);
|
|
12
|
-
const personalityDir = path.join(tenant.userDir, 'personality');
|
|
13
|
-
const args = ctx.message.text.split(' ').slice(1);
|
|
14
|
-
|
|
15
|
-
if (args[0] === 'reset') {
|
|
16
|
-
saveTraits(personalityDir, { ...DEFAULT_TRAITS });
|
|
17
|
-
tenant.claude.reloadPersonality();
|
|
18
|
-
const traits = { ...DEFAULT_TRAITS };
|
|
19
|
-
const lines = [`◈ ${botName} PERSONALITY MATRIX`, TERM_SEP, `RESET TO DEFAULTS`, ``, formatTraits(traits), TERM_SEP];
|
|
20
|
-
await ctx.reply(`<pre>${lines.join('\n')}</pre>`, { parse_mode: 'HTML' });
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (args[0] && args[1]) {
|
|
25
|
-
const traitName = args[0].toLowerCase();
|
|
26
|
-
const value = parseInt(args[1], 10);
|
|
27
|
-
if (!(traitName in DEFAULT_TRAITS)) {
|
|
28
|
-
await ctx.reply(`Unknown trait: ${traitName}\nValid: ${Object.keys(DEFAULT_TRAITS).join(', ')}`);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
if (isNaN(value) || value < 0 || value > 100) {
|
|
32
|
-
await ctx.reply('Value must be 0-100');
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const traits = loadTraits(personalityDir);
|
|
36
|
-
traits[traitName] = value;
|
|
37
|
-
saveTraits(personalityDir, traits);
|
|
38
|
-
tenant.claude.reloadPersonality();
|
|
39
|
-
const lines = [`◈ ${botName} PERSONALITY MATRIX`, TERM_SEP, `UPDATED ${traitName} → ${value}`, ``, formatTraits(traits), TERM_SEP];
|
|
40
|
-
await ctx.reply(`<pre>${lines.join('\n')}</pre>`, { parse_mode: 'HTML' });
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const traits = loadTraits(personalityDir);
|
|
45
|
-
const lines = [`◈ ${botName} PERSONALITY MATRIX`, TERM_SEP, ``, formatTraits(traits), ``, `/traits <name> <0-100>`, `/traits reset`, TERM_SEP];
|
|
46
|
-
await ctx.reply(`<pre>${lines.join('\n')}</pre>`, { parse_mode: 'HTML' });
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
module.exports = { register };
|