Участники чата, промпты в файлах, личности только из кода

- Бот запоминает участников (имя, фамилия, ник, активность) и ID автора каждого сообщения
- Упоминания в вопросе (@ник и по имени-ссылке) передаются модели с данными участника
- Новые инструменты агента: list_chat_members, read_member_messages (поиск по нику/имени)
- Бот обращается к людям по имени
- Промпты вынесены в prompts/system.md и prompts/personas/<id>.md
- Личности больше не редактируются из чата: скрипт seed синхронизирует их с БД при старте
- Оставлена одна личность boltun (Dirty D)
- Миграция старых баз: колонка messages.user_id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
yunogasai
2026-09-24 18:42:23 +02:00
parent 99c4660da9
commit 26da4bb5ee
35 changed files with 567 additions and 241 deletions

View File

@@ -0,0 +1,60 @@
import type { Db } from "../libs/db.js";
import type { ChatMember, MemberProfile } from "../types.js";
interface ChatMemberRow {
chat_id: number;
user_id: number;
username: string | null;
first_name: string;
last_name: string | null;
message_count: number;
last_seen: number;
}
const toMember = (row: ChatMemberRow): ChatMember => ({
chatId: row.chat_id,
userId: row.user_id,
username: row.username,
firstName: row.first_name,
lastName: row.last_name,
messageCount: row.message_count,
lastSeen: row.last_seen,
});
export const createChatMemberRepository = (db: Db) => {
const statements = {
upsert: db.prepare(`
INSERT INTO chat_members (chat_id, user_id, username, first_name, last_name, message_count, last_seen)
VALUES (@chatId, @userId, @username, @firstName, @lastName, @messages, @seenAt)
ON CONFLICT(chat_id, user_id) DO UPDATE SET
username = excluded.username,
first_name = excluded.first_name,
last_name = excluded.last_name,
message_count = message_count + excluded.message_count,
last_seen = MAX(last_seen, excluded.last_seen)
`),
listByChat: db.prepare("SELECT * FROM chat_members WHERE chat_id = ? ORDER BY last_seen DESC"),
findById: db.prepare("SELECT * FROM chat_members WHERE chat_id = ? AND user_id = ?"),
};
/**
* Сохраняет актуальные имя и ник участника.
* `messages` добавляется к счётчику сообщений, `seenAt` сдвигает время последней активности вперёд.
*/
const upsert = (chatId: number, profile: MemberProfile, activity: { messages: number; seenAt: number }): void => {
statements.upsert.run({ chatId, ...profile, ...activity });
};
const listByChat = (chatId: number): ChatMember[] =>
(statements.listByChat.all(chatId) as ChatMemberRow[]).map(toMember);
const findById = (chatId: number, userId: number): ChatMember | undefined => {
const row = statements.findById.get(chatId, userId) as ChatMemberRow | undefined;
return row && toMember(row);
};
return { upsert, listByChat, findById };
};
export type ChatMemberRepository = ReturnType<typeof createChatMemberRepository>;