kernelbot 1.0.30 → 1.0.33
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/.env.example +0 -0
- package/README.md +0 -0
- package/bin/kernel.js +56 -2
- package/config.example.yaml +31 -0
- package/package.json +1 -1
- package/src/agent.js +200 -32
- package/src/automation/automation-manager.js +0 -0
- package/src/automation/automation.js +0 -0
- package/src/automation/index.js +0 -0
- package/src/automation/scheduler.js +0 -0
- package/src/bot.js +402 -6
- package/src/claude-auth.js +0 -0
- package/src/coder.js +0 -0
- package/src/conversation.js +51 -5
- package/src/intents/detector.js +0 -0
- package/src/intents/index.js +0 -0
- package/src/intents/planner.js +0 -0
- package/src/life/codebase.js +388 -0
- package/src/life/engine.js +1317 -0
- package/src/life/evolution.js +244 -0
- package/src/life/improvements.js +81 -0
- package/src/life/journal.js +109 -0
- package/src/life/memory.js +283 -0
- package/src/life/share-queue.js +136 -0
- package/src/persona.js +0 -0
- package/src/prompts/orchestrator.js +62 -2
- package/src/prompts/persona.md +7 -0
- package/src/prompts/system.js +0 -0
- package/src/prompts/workers.js +10 -9
- package/src/providers/anthropic.js +0 -0
- package/src/providers/base.js +0 -0
- package/src/providers/index.js +0 -0
- package/src/providers/models.js +8 -1
- package/src/providers/openai-compat.js +0 -0
- package/src/security/audit.js +0 -0
- package/src/security/auth.js +0 -0
- package/src/security/confirm.js +0 -0
- package/src/self.js +0 -0
- package/src/services/stt.js +0 -0
- package/src/services/tts.js +0 -0
- package/src/skills/catalog.js +0 -0
- package/src/skills/custom.js +0 -0
- package/src/swarm/job-manager.js +0 -0
- package/src/swarm/job.js +11 -0
- package/src/swarm/worker-registry.js +0 -0
- package/src/tools/browser.js +0 -0
- package/src/tools/categories.js +0 -0
- package/src/tools/coding.js +1 -1
- package/src/tools/docker.js +0 -0
- package/src/tools/git.js +0 -0
- package/src/tools/github.js +0 -0
- package/src/tools/index.js +0 -0
- package/src/tools/jira.js +0 -0
- package/src/tools/monitor.js +0 -0
- package/src/tools/network.js +0 -0
- package/src/tools/orchestrator-tools.js +60 -3
- package/src/tools/os.js +0 -0
- package/src/tools/persona.js +0 -0
- package/src/tools/process.js +0 -0
- package/src/utils/config.js +0 -0
- package/src/utils/display.js +0 -0
- package/src/utils/logger.js +0 -0
- package/src/worker.js +27 -8
package/src/bot.js
CHANGED
|
@@ -53,9 +53,16 @@ class ChatQueue {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
export function startBot(config, agent, conversationManager, jobManager, automationManager) {
|
|
56
|
+
export function startBot(config, agent, conversationManager, jobManager, automationManager, lifeDeps = {}) {
|
|
57
|
+
const { lifeEngine, memoryManager, journalManager, shareQueue, evolutionTracker, codebaseKnowledge } = lifeDeps;
|
|
57
58
|
const logger = getLogger();
|
|
58
|
-
const bot = new TelegramBot(config.telegram.bot_token, {
|
|
59
|
+
const bot = new TelegramBot(config.telegram.bot_token, {
|
|
60
|
+
polling: {
|
|
61
|
+
params: {
|
|
62
|
+
allowed_updates: ['message', 'callback_query', 'message_reaction'],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
59
66
|
const chatQueue = new ChatQueue();
|
|
60
67
|
const batchWindowMs = config.telegram.batch_window_ms || 3000;
|
|
61
68
|
|
|
@@ -87,6 +94,10 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
87
94
|
{ command: 'jobs', description: 'List running and recent jobs' },
|
|
88
95
|
{ command: 'cancel', description: 'Cancel running job(s)' },
|
|
89
96
|
{ command: 'auto', description: 'Manage recurring automations' },
|
|
97
|
+
{ command: 'life', description: 'Inner life engine status and control' },
|
|
98
|
+
{ command: 'journal', description: 'View today\'s journal or a past date' },
|
|
99
|
+
{ command: 'memories', description: 'View recent memories or search' },
|
|
100
|
+
{ command: 'evolution', description: 'Self-evolution status, history, and lessons' },
|
|
90
101
|
{ command: 'context', description: 'Show all models, auth, and context info' },
|
|
91
102
|
{ command: 'clean', description: 'Clear conversation and start fresh' },
|
|
92
103
|
{ command: 'history', description: 'Show message count in memory' },
|
|
@@ -125,7 +136,7 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
125
136
|
});
|
|
126
137
|
return edited.message_id;
|
|
127
138
|
} catch {
|
|
128
|
-
|
|
139
|
+
// Edit failed — fall through to send new message
|
|
129
140
|
}
|
|
130
141
|
}
|
|
131
142
|
}
|
|
@@ -1100,6 +1111,296 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1100
1111
|
return;
|
|
1101
1112
|
}
|
|
1102
1113
|
|
|
1114
|
+
// ── /life command ──────────────────────────────────────────────
|
|
1115
|
+
if (text === '/life' || text.startsWith('/life ')) {
|
|
1116
|
+
logger.info(`[Bot] /life command from ${username} (${userId}) in chat ${chatId}`);
|
|
1117
|
+
const args = text.slice('/life'.length).trim();
|
|
1118
|
+
|
|
1119
|
+
if (!lifeEngine) {
|
|
1120
|
+
await bot.sendMessage(chatId, 'Life engine is not available.');
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (args === 'pause') {
|
|
1125
|
+
lifeEngine.pause();
|
|
1126
|
+
await bot.sendMessage(chatId, '⏸️ Inner life paused. Use `/life resume` to restart.', { parse_mode: 'Markdown' });
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (args === 'resume') {
|
|
1130
|
+
lifeEngine.resume();
|
|
1131
|
+
await bot.sendMessage(chatId, '▶️ Inner life resumed!');
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
if (args.startsWith('trigger')) {
|
|
1135
|
+
const activityType = args.split(/\s+/)[1] || null;
|
|
1136
|
+
const validTypes = ['think', 'browse', 'journal', 'create', 'self_code', 'code_review', 'reflect'];
|
|
1137
|
+
if (activityType && !validTypes.includes(activityType)) {
|
|
1138
|
+
await bot.sendMessage(chatId, `Unknown activity type. Available: ${validTypes.join(', ')}`);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
await bot.sendMessage(chatId, `⚡ Triggering ${activityType || 'random'} activity...`);
|
|
1142
|
+
lifeEngine.triggerNow(activityType).catch(err => {
|
|
1143
|
+
logger.error(`[Bot] Life trigger failed: ${err.message}`);
|
|
1144
|
+
});
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
if (args === 'review') {
|
|
1148
|
+
if (evolutionTracker) {
|
|
1149
|
+
const active = evolutionTracker.getActiveProposal();
|
|
1150
|
+
const openPRs = evolutionTracker.getPRsToCheck();
|
|
1151
|
+
const lines = ['*Evolution Status*', ''];
|
|
1152
|
+
if (active) {
|
|
1153
|
+
lines.push(`Active: \`${active.id}\` — ${active.status}`);
|
|
1154
|
+
lines.push(` ${(active.triggerContext || '').slice(0, 150)}`);
|
|
1155
|
+
} else {
|
|
1156
|
+
lines.push('_No active proposals._');
|
|
1157
|
+
}
|
|
1158
|
+
if (openPRs.length > 0) {
|
|
1159
|
+
lines.push('', '*Open PRs:*');
|
|
1160
|
+
for (const p of openPRs) {
|
|
1161
|
+
lines.push(` • PR #${p.prNumber}: ${p.prUrl || 'no URL'}`);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
lines.push('', '_Use `/evolution` for full evolution status._');
|
|
1165
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1166
|
+
} else {
|
|
1167
|
+
await bot.sendMessage(chatId, 'Evolution system not available. Use `/evolution` for details.', { parse_mode: 'Markdown' });
|
|
1168
|
+
}
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// Default: show status
|
|
1173
|
+
const status = lifeEngine.getStatus();
|
|
1174
|
+
const lines = [
|
|
1175
|
+
'🌱 *Inner Life*',
|
|
1176
|
+
'',
|
|
1177
|
+
`*Status:* ${status.paused ? '⏸️ Paused' : status.status === 'active' ? '🟢 Active' : '⚪ Idle'}`,
|
|
1178
|
+
`*Total activities:* ${status.totalActivities}`,
|
|
1179
|
+
`*Last activity:* ${status.lastActivity || 'none'} (${status.lastActivityAgo})`,
|
|
1180
|
+
`*Last wake-up:* ${status.lastWakeUpAgo}`,
|
|
1181
|
+
'',
|
|
1182
|
+
'*Activity counts:*',
|
|
1183
|
+
` 💭 Think: ${status.activityCounts.think || 0}`,
|
|
1184
|
+
` 🌐 Browse: ${status.activityCounts.browse || 0}`,
|
|
1185
|
+
` 📓 Journal: ${status.activityCounts.journal || 0}`,
|
|
1186
|
+
` 🎨 Create: ${status.activityCounts.create || 0}`,
|
|
1187
|
+
` 🔧 Self-code: ${status.activityCounts.self_code || 0}`,
|
|
1188
|
+
` 🔍 Code review: ${status.activityCounts.code_review || 0}`,
|
|
1189
|
+
` 🪞 Reflect: ${status.activityCounts.reflect || 0}`,
|
|
1190
|
+
'',
|
|
1191
|
+
'_Commands:_',
|
|
1192
|
+
'`/life pause` — Pause activities',
|
|
1193
|
+
'`/life resume` — Resume activities',
|
|
1194
|
+
'`/life trigger [think|browse|journal|create|self_code|code_review|reflect]` — Trigger now',
|
|
1195
|
+
];
|
|
1196
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// ── /journal command ─────────────────────────────────────────
|
|
1201
|
+
if (text === '/journal' || text.startsWith('/journal ')) {
|
|
1202
|
+
logger.info(`[Bot] /journal command from ${username} (${userId}) in chat ${chatId}`);
|
|
1203
|
+
|
|
1204
|
+
if (!journalManager) {
|
|
1205
|
+
await bot.sendMessage(chatId, 'Journal system is not available.');
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const args = text.slice('/journal'.length).trim();
|
|
1210
|
+
|
|
1211
|
+
if (args && /^\d{4}-\d{2}-\d{2}$/.test(args)) {
|
|
1212
|
+
const entry = journalManager.getEntry(args);
|
|
1213
|
+
if (!entry) {
|
|
1214
|
+
await bot.sendMessage(chatId, `No journal entry for ${args}.`);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
const chunks = splitMessage(entry);
|
|
1218
|
+
for (const chunk of chunks) {
|
|
1219
|
+
try { await bot.sendMessage(chatId, chunk, { parse_mode: 'Markdown' }); }
|
|
1220
|
+
catch { await bot.sendMessage(chatId, chunk); }
|
|
1221
|
+
}
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
if (args === 'list') {
|
|
1226
|
+
const dates = journalManager.list(15);
|
|
1227
|
+
if (dates.length === 0) {
|
|
1228
|
+
await bot.sendMessage(chatId, 'No journal entries yet.');
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
const lines = ['📓 *Journal Entries*', '', ...dates.map(d => ` • \`${d}\``)];
|
|
1232
|
+
lines.push('', '_Use `/journal YYYY-MM-DD` to read an entry._');
|
|
1233
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// Default: show today's journal
|
|
1238
|
+
const today = journalManager.getToday();
|
|
1239
|
+
if (!today) {
|
|
1240
|
+
await bot.sendMessage(chatId, '📓 No journal entries today yet.\n\n_Use `/journal list` to see past entries._', { parse_mode: 'Markdown' });
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
const chunks = splitMessage(today);
|
|
1244
|
+
for (const chunk of chunks) {
|
|
1245
|
+
try { await bot.sendMessage(chatId, chunk, { parse_mode: 'Markdown' }); }
|
|
1246
|
+
catch { await bot.sendMessage(chatId, chunk); }
|
|
1247
|
+
}
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// ── /memories command ────────────────────────────────────────
|
|
1252
|
+
if (text === '/memories' || text.startsWith('/memories ')) {
|
|
1253
|
+
logger.info(`[Bot] /memories command from ${username} (${userId}) in chat ${chatId}`);
|
|
1254
|
+
|
|
1255
|
+
if (!memoryManager) {
|
|
1256
|
+
await bot.sendMessage(chatId, 'Memory system is not available.');
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
const args = text.slice('/memories'.length).trim();
|
|
1261
|
+
|
|
1262
|
+
if (args.startsWith('about ')) {
|
|
1263
|
+
const query = args.slice('about '.length).trim();
|
|
1264
|
+
const results = memoryManager.searchEpisodic(query, 10);
|
|
1265
|
+
if (results.length === 0) {
|
|
1266
|
+
await bot.sendMessage(chatId, `No memories matching "${query}".`);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
const lines = [`🧠 *Memories about "${query}"*`, ''];
|
|
1270
|
+
for (const m of results) {
|
|
1271
|
+
const date = new Date(m.timestamp).toLocaleDateString();
|
|
1272
|
+
lines.push(`• ${m.summary} _(${date}, importance: ${m.importance})_`);
|
|
1273
|
+
}
|
|
1274
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Default: show last 10 memories
|
|
1279
|
+
const recent = memoryManager.getRecentEpisodic(168, 10); // last 7 days
|
|
1280
|
+
if (recent.length === 0) {
|
|
1281
|
+
await bot.sendMessage(chatId, '🧠 No memories yet.');
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
const lines = ['🧠 *Recent Memories*', ''];
|
|
1285
|
+
for (const m of recent) {
|
|
1286
|
+
const ago = Math.round((Date.now() - m.timestamp) / 60000);
|
|
1287
|
+
const timeLabel = ago < 60 ? `${ago}m ago` : ago < 1440 ? `${Math.round(ago / 60)}h ago` : `${Math.round(ago / 1440)}d ago`;
|
|
1288
|
+
const icon = { interaction: '💬', discovery: '🔍', thought: '💭', creation: '🎨' }[m.type] || '•';
|
|
1289
|
+
lines.push(`${icon} ${m.summary} _(${timeLabel})_`);
|
|
1290
|
+
}
|
|
1291
|
+
lines.push('', '_Use `/memories about <topic>` to search._');
|
|
1292
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// ── /evolution command ──────────────────────────────────────────
|
|
1297
|
+
if (text === '/evolution' || text.startsWith('/evolution ')) {
|
|
1298
|
+
logger.info(`[Bot] /evolution command from ${username} (${userId}) in chat ${chatId}`);
|
|
1299
|
+
const args = text.slice('/evolution'.length).trim();
|
|
1300
|
+
|
|
1301
|
+
if (!evolutionTracker) {
|
|
1302
|
+
await bot.sendMessage(chatId, 'Evolution system is not available.');
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
if (args === 'history') {
|
|
1307
|
+
const proposals = evolutionTracker.getRecentProposals(10);
|
|
1308
|
+
if (proposals.length === 0) {
|
|
1309
|
+
await bot.sendMessage(chatId, 'No evolution proposals yet.');
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
const lines = ['*Evolution History*', ''];
|
|
1313
|
+
for (const p of proposals.reverse()) {
|
|
1314
|
+
const statusIcon = { research: '🔬', planned: '📋', coding: '💻', pr_open: '🔄', merged: '✅', rejected: '❌', failed: '💥' }[p.status] || '•';
|
|
1315
|
+
const age = Math.round((Date.now() - p.createdAt) / 3600_000);
|
|
1316
|
+
const ageLabel = age < 24 ? `${age}h ago` : `${Math.round(age / 24)}d ago`;
|
|
1317
|
+
lines.push(`${statusIcon} \`${p.id}\` — ${p.status} (${ageLabel})`);
|
|
1318
|
+
lines.push(` ${(p.triggerContext || '').slice(0, 100)}`);
|
|
1319
|
+
if (p.prUrl) lines.push(` PR: ${p.prUrl}`);
|
|
1320
|
+
lines.push('');
|
|
1321
|
+
}
|
|
1322
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
if (args === 'lessons') {
|
|
1327
|
+
const lessons = evolutionTracker.getRecentLessons(15);
|
|
1328
|
+
if (lessons.length === 0) {
|
|
1329
|
+
await bot.sendMessage(chatId, 'No evolution lessons learned yet.');
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
const lines = ['*Evolution Lessons*', ''];
|
|
1333
|
+
for (const l of lessons.reverse()) {
|
|
1334
|
+
lines.push(`• [${l.category}] ${l.lesson}`);
|
|
1335
|
+
if (l.fromProposal) lines.push(` _from ${l.fromProposal}_`);
|
|
1336
|
+
lines.push('');
|
|
1337
|
+
}
|
|
1338
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
if (args === 'trigger') {
|
|
1343
|
+
if (!lifeEngine) {
|
|
1344
|
+
await bot.sendMessage(chatId, 'Life engine is not available.');
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
await bot.sendMessage(chatId, '⚡ Triggering evolution cycle...');
|
|
1348
|
+
lifeEngine.triggerNow('self_code').catch(err => {
|
|
1349
|
+
logger.error(`[Bot] Evolution trigger failed: ${err.message}`);
|
|
1350
|
+
});
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
if (args === 'scan') {
|
|
1355
|
+
if (!codebaseKnowledge) {
|
|
1356
|
+
await bot.sendMessage(chatId, 'Codebase knowledge is not available.');
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
await bot.sendMessage(chatId, '🔍 Scanning codebase...');
|
|
1360
|
+
codebaseKnowledge.scanChanged().then(count => {
|
|
1361
|
+
bot.sendMessage(chatId, `✅ Scanned ${count} changed files.`).catch(() => {});
|
|
1362
|
+
}).catch(err => {
|
|
1363
|
+
bot.sendMessage(chatId, `Failed: ${err.message}`).catch(() => {});
|
|
1364
|
+
});
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// Default: show status
|
|
1369
|
+
const active = evolutionTracker.getActiveProposal();
|
|
1370
|
+
const stats = evolutionTracker.getStats();
|
|
1371
|
+
const openPRs = evolutionTracker.getPRsToCheck();
|
|
1372
|
+
|
|
1373
|
+
const lines = [
|
|
1374
|
+
'🧬 *Self-Evolution*',
|
|
1375
|
+
'',
|
|
1376
|
+
`*Stats:* ${stats.totalProposals} total | ${stats.merged} merged | ${stats.rejected} rejected | ${stats.failed} failed`,
|
|
1377
|
+
`*Success rate:* ${stats.successRate}%`,
|
|
1378
|
+
`*Open PRs:* ${openPRs.length}`,
|
|
1379
|
+
];
|
|
1380
|
+
|
|
1381
|
+
if (active) {
|
|
1382
|
+
const statusIcon = { research: '🔬', planned: '📋', coding: '💻', pr_open: '🔄' }[active.status] || '•';
|
|
1383
|
+
lines.push('');
|
|
1384
|
+
lines.push(`*Active proposal:* ${statusIcon} \`${active.id}\` — ${active.status}`);
|
|
1385
|
+
lines.push(` ${(active.triggerContext || '').slice(0, 120)}`);
|
|
1386
|
+
if (active.prUrl) lines.push(` PR: ${active.prUrl}`);
|
|
1387
|
+
} else {
|
|
1388
|
+
lines.push('', '_No active proposal_');
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
lines.push(
|
|
1392
|
+
'',
|
|
1393
|
+
'_Commands:_',
|
|
1394
|
+
'`/evolution history` — Recent proposals',
|
|
1395
|
+
'`/evolution lessons` — Learned lessons',
|
|
1396
|
+
'`/evolution trigger` — Trigger evolution now',
|
|
1397
|
+
'`/evolution scan` — Scan codebase',
|
|
1398
|
+
);
|
|
1399
|
+
|
|
1400
|
+
await bot.sendMessage(chatId, lines.join('\n'), { parse_mode: 'Markdown' });
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1103
1404
|
if (text === '/help') {
|
|
1104
1405
|
const activeSkill = agent.getActiveSkill(chatId);
|
|
1105
1406
|
const skillLine = activeSkill
|
|
@@ -1117,6 +1418,10 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1117
1418
|
'/jobs — List running and recent jobs',
|
|
1118
1419
|
'/cancel — Cancel running job(s)',
|
|
1119
1420
|
'/auto — Manage recurring automations',
|
|
1421
|
+
'/life — Inner life engine status & control',
|
|
1422
|
+
'/journal — View today\'s journal or a past date',
|
|
1423
|
+
'/memories — View recent memories or search',
|
|
1424
|
+
'/evolution — Self-evolution status, history, lessons',
|
|
1120
1425
|
'/context — Show all models, auth, and context info',
|
|
1121
1426
|
'/clean — Clear conversation and start fresh',
|
|
1122
1427
|
'/history — Show message count in memory',
|
|
@@ -1293,12 +1598,12 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1293
1598
|
});
|
|
1294
1599
|
return edited.message_id;
|
|
1295
1600
|
} catch {
|
|
1296
|
-
|
|
1601
|
+
// Edit failed — fall through to send new message
|
|
1297
1602
|
}
|
|
1298
1603
|
}
|
|
1299
1604
|
}
|
|
1300
1605
|
|
|
1301
|
-
// Send new message(s)
|
|
1606
|
+
// Send new message(s) — also reached when edit fails
|
|
1302
1607
|
const parts = splitMessage(update);
|
|
1303
1608
|
let lastMsgId = null;
|
|
1304
1609
|
for (const part of parts) {
|
|
@@ -1331,11 +1636,18 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1331
1636
|
}
|
|
1332
1637
|
};
|
|
1333
1638
|
|
|
1639
|
+
const sendReaction = async (targetChatId, targetMsgId, emoji, isBig = false) => {
|
|
1640
|
+
await bot.setMessageReaction(targetChatId, targetMsgId, {
|
|
1641
|
+
reaction: [{ type: 'emoji', emoji }],
|
|
1642
|
+
is_big: isBig,
|
|
1643
|
+
});
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1334
1646
|
logger.debug(`[Bot] Sending to orchestrator: chat ${chatId}, text="${mergedText.slice(0, 80)}"`);
|
|
1335
1647
|
const reply = await agent.processMessage(chatId, mergedText, {
|
|
1336
1648
|
id: userId,
|
|
1337
1649
|
username,
|
|
1338
|
-
}, onUpdate, sendPhoto);
|
|
1650
|
+
}, onUpdate, sendPhoto, { sendReaction, messageId: msg.message_id });
|
|
1339
1651
|
|
|
1340
1652
|
clearInterval(typingInterval);
|
|
1341
1653
|
|
|
@@ -1369,6 +1681,90 @@ export function startBot(config, agent, conversationManager, jobManager, automat
|
|
|
1369
1681
|
});
|
|
1370
1682
|
});
|
|
1371
1683
|
|
|
1684
|
+
// Handle message reactions (love, like, etc.)
|
|
1685
|
+
bot.on('message_reaction', async (reaction) => {
|
|
1686
|
+
const chatId = reaction.chat.id;
|
|
1687
|
+
const userId = reaction.user?.id;
|
|
1688
|
+
const username = reaction.user?.username || reaction.user?.first_name || 'unknown';
|
|
1689
|
+
|
|
1690
|
+
if (!userId || !isAllowedUser(userId, config)) return;
|
|
1691
|
+
|
|
1692
|
+
const newReactions = reaction.new_reaction || [];
|
|
1693
|
+
const emojis = newReactions
|
|
1694
|
+
.filter(r => r.type === 'emoji')
|
|
1695
|
+
.map(r => r.emoji);
|
|
1696
|
+
|
|
1697
|
+
if (emojis.length === 0) return;
|
|
1698
|
+
|
|
1699
|
+
logger.info(`[Bot] Reaction from ${username} (${userId}) in chat ${chatId}: ${emojis.join(' ')}`);
|
|
1700
|
+
|
|
1701
|
+
const reactionText = `[User reacted with ${emojis.join(' ')} to your message]`;
|
|
1702
|
+
|
|
1703
|
+
chatQueue.enqueue(chatId, async () => {
|
|
1704
|
+
try {
|
|
1705
|
+
const onUpdate = async (update, opts = {}) => {
|
|
1706
|
+
if (opts.editMessageId) {
|
|
1707
|
+
try {
|
|
1708
|
+
const edited = await bot.editMessageText(update, {
|
|
1709
|
+
chat_id: chatId,
|
|
1710
|
+
message_id: opts.editMessageId,
|
|
1711
|
+
parse_mode: 'Markdown',
|
|
1712
|
+
});
|
|
1713
|
+
return edited.message_id;
|
|
1714
|
+
} catch {
|
|
1715
|
+
try {
|
|
1716
|
+
const edited = await bot.editMessageText(update, {
|
|
1717
|
+
chat_id: chatId,
|
|
1718
|
+
message_id: opts.editMessageId,
|
|
1719
|
+
});
|
|
1720
|
+
return edited.message_id;
|
|
1721
|
+
} catch {
|
|
1722
|
+
// fall through
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
const parts = splitMessage(update);
|
|
1727
|
+
let lastMsgId = null;
|
|
1728
|
+
for (const part of parts) {
|
|
1729
|
+
try {
|
|
1730
|
+
const sent = await bot.sendMessage(chatId, part, { parse_mode: 'Markdown' });
|
|
1731
|
+
lastMsgId = sent.message_id;
|
|
1732
|
+
} catch {
|
|
1733
|
+
const sent = await bot.sendMessage(chatId, part);
|
|
1734
|
+
lastMsgId = sent.message_id;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
return lastMsgId;
|
|
1738
|
+
};
|
|
1739
|
+
|
|
1740
|
+
const sendReaction = async (targetChatId, targetMsgId, emoji, isBig = false) => {
|
|
1741
|
+
await bot.setMessageReaction(targetChatId, targetMsgId, {
|
|
1742
|
+
reaction: [{ type: 'emoji', emoji }],
|
|
1743
|
+
is_big: isBig,
|
|
1744
|
+
});
|
|
1745
|
+
};
|
|
1746
|
+
|
|
1747
|
+
const reply = await agent.processMessage(chatId, reactionText, {
|
|
1748
|
+
id: userId,
|
|
1749
|
+
username,
|
|
1750
|
+
}, onUpdate, null, { sendReaction, messageId: reaction.message_id });
|
|
1751
|
+
|
|
1752
|
+
if (reply && reply.trim()) {
|
|
1753
|
+
const chunks = splitMessage(reply);
|
|
1754
|
+
for (const chunk of chunks) {
|
|
1755
|
+
try {
|
|
1756
|
+
await bot.sendMessage(chatId, chunk, { parse_mode: 'Markdown' });
|
|
1757
|
+
} catch {
|
|
1758
|
+
await bot.sendMessage(chatId, chunk);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
} catch (err) {
|
|
1763
|
+
logger.error(`[Bot] Error processing reaction in chat ${chatId}: ${err.message}`);
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
});
|
|
1767
|
+
|
|
1372
1768
|
bot.on('polling_error', (err) => {
|
|
1373
1769
|
logger.error(`Telegram polling error: ${err.message}`);
|
|
1374
1770
|
});
|
package/src/claude-auth.js
CHANGED
|
File without changes
|
package/src/coder.js
CHANGED
|
File without changes
|
package/src/conversation.js
CHANGED
|
@@ -68,6 +68,48 @@ export class ConversationManager {
|
|
|
68
68
|
return this.conversations.get(key);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Get the timestamp of the most recent message in a chat.
|
|
73
|
+
* Used by agent.js for time-gap detection before the current message is added.
|
|
74
|
+
*/
|
|
75
|
+
getLastMessageTimestamp(chatId) {
|
|
76
|
+
const history = this.getHistory(chatId);
|
|
77
|
+
if (history.length === 0) return null;
|
|
78
|
+
return history[history.length - 1].timestamp || null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Format a timestamp as a relative time marker.
|
|
83
|
+
* Returns null for missing timestamps (backward compat with old messages).
|
|
84
|
+
*/
|
|
85
|
+
_formatRelativeTime(ts) {
|
|
86
|
+
if (!ts) return null;
|
|
87
|
+
const diff = Date.now() - ts;
|
|
88
|
+
const seconds = Math.floor(diff / 1000);
|
|
89
|
+
if (seconds < 60) return '[just now]';
|
|
90
|
+
const minutes = Math.floor(seconds / 60);
|
|
91
|
+
if (minutes < 60) return `[${minutes}m ago]`;
|
|
92
|
+
const hours = Math.floor(minutes / 60);
|
|
93
|
+
if (hours < 24) return `[${hours}h ago]`;
|
|
94
|
+
const days = Math.floor(hours / 24);
|
|
95
|
+
return `[${days}d ago]`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Return a shallow copy of a message with a time marker prepended to string content.
|
|
100
|
+
* Skips tool_result arrays and messages without timestamps.
|
|
101
|
+
*/
|
|
102
|
+
_annotateWithTime(msg) {
|
|
103
|
+
const marker = this._formatRelativeTime(msg.timestamp);
|
|
104
|
+
if (!marker || typeof msg.content !== 'string') return msg;
|
|
105
|
+
return { ...msg, content: `${marker} ${msg.content}` };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Strip internal metadata fields, returning only API-safe {role, content}. */
|
|
109
|
+
_sanitize(msg) {
|
|
110
|
+
return { role: msg.role, content: msg.content };
|
|
111
|
+
}
|
|
112
|
+
|
|
71
113
|
/**
|
|
72
114
|
* Get history with older messages compressed into a summary.
|
|
73
115
|
* Keeps the last `recentWindow` messages verbatim and summarizes older ones.
|
|
@@ -76,18 +118,19 @@ export class ConversationManager {
|
|
|
76
118
|
const history = this.getHistory(chatId);
|
|
77
119
|
|
|
78
120
|
if (history.length <= this.recentWindow) {
|
|
79
|
-
return
|
|
121
|
+
return history.map(m => this._sanitize(this._annotateWithTime(m)));
|
|
80
122
|
}
|
|
81
123
|
|
|
82
124
|
const olderMessages = history.slice(0, history.length - this.recentWindow);
|
|
83
125
|
const recentMessages = history.slice(history.length - this.recentWindow);
|
|
84
126
|
|
|
85
|
-
// Compress older messages into a single summary
|
|
127
|
+
// Compress older messages into a single summary (include time markers when available)
|
|
86
128
|
const summaryLines = olderMessages.map((msg) => {
|
|
129
|
+
const timeTag = this._formatRelativeTime(msg.timestamp);
|
|
87
130
|
const content = typeof msg.content === 'string'
|
|
88
131
|
? msg.content.slice(0, 200)
|
|
89
132
|
: JSON.stringify(msg.content).slice(0, 200);
|
|
90
|
-
return `[${msg.role}]: ${content}`;
|
|
133
|
+
return `[${msg.role}]${timeTag ? ` ${timeTag}` : ''}: ${content}`;
|
|
91
134
|
});
|
|
92
135
|
|
|
93
136
|
const summaryMessage = {
|
|
@@ -95,8 +138,11 @@ export class ConversationManager {
|
|
|
95
138
|
content: `[CONVERSATION SUMMARY - ${olderMessages.length} earlier messages]\n${summaryLines.join('\n')}`,
|
|
96
139
|
};
|
|
97
140
|
|
|
141
|
+
// Annotate recent messages with time markers and strip metadata
|
|
142
|
+
const annotatedRecent = recentMessages.map(m => this._sanitize(this._annotateWithTime(m)));
|
|
143
|
+
|
|
98
144
|
// Ensure result starts with user role
|
|
99
|
-
const result = [summaryMessage, ...
|
|
145
|
+
const result = [summaryMessage, ...annotatedRecent];
|
|
100
146
|
|
|
101
147
|
// If the first real message after summary is assistant, that's fine since
|
|
102
148
|
// our summary is role:user. But ensure recent starts correctly.
|
|
@@ -105,7 +151,7 @@ export class ConversationManager {
|
|
|
105
151
|
|
|
106
152
|
addMessage(chatId, role, content) {
|
|
107
153
|
const history = this.getHistory(chatId);
|
|
108
|
-
history.push({ role, content });
|
|
154
|
+
history.push({ role, content, timestamp: Date.now() });
|
|
109
155
|
|
|
110
156
|
// Trim to max history
|
|
111
157
|
while (history.length > this.maxHistory) {
|
package/src/intents/detector.js
CHANGED
|
File without changes
|
package/src/intents/index.js
CHANGED
|
File without changes
|
package/src/intents/planner.js
CHANGED
|
File without changes
|