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

- Бот запоминает участников (имя, фамилия, ник, активность) и 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

@@ -4,6 +4,7 @@ import type { ChatMessage } from "../types.js";
interface MessageRow {
chat_id: number;
message_id: number;
user_id: number | null;
date: number;
author: string;
text: string;
@@ -13,6 +14,7 @@ interface MessageRow {
const toMessage = (row: MessageRow): ChatMessage => ({
chatId: row.chat_id,
messageId: row.message_id,
userId: row.user_id,
date: row.date,
author: row.author,
text: row.text,
@@ -22,8 +24,8 @@ const toMessage = (row: MessageRow): ChatMessage => ({
export const createMessageRepository = (db: Db) => {
const statements = {
upsert: db.prepare(`
INSERT INTO messages (chat_id, message_id, date, author, text, reply_to_message_id)
VALUES (@chatId, @messageId, @date, @author, @text, @replyToMessageId)
INSERT INTO messages (chat_id, message_id, user_id, date, author, text, reply_to_message_id)
VALUES (@chatId, @messageId, @userId, @date, @author, @text, @replyToMessageId)
ON CONFLICT(chat_id, message_id) DO UPDATE SET text = excluded.text
`),
pruneChat: db.prepare(`
@@ -33,9 +35,14 @@ export const createMessageRepository = (db: Db) => {
)
`),
latest: db.prepare("SELECT * FROM messages WHERE chat_id = ? ORDER BY message_id DESC LIMIT ?"),
latestByUser: db.prepare(
"SELECT * FROM messages WHERE chat_id = ? AND user_id = ? ORDER BY message_id DESC LIMIT ?",
),
findById: db.prepare("SELECT * FROM messages WHERE chat_id = ? AND message_id = ?"),
};
const toChronological = (rows: unknown[]): ChatMessage[] => (rows as MessageRow[]).reverse().map(toMessage);
/** Сохраняет сообщение (при повторе — обновляет текст) и оставляет в чате не больше `keep` последних. */
const upsertAndPrune = db.transaction((message: ChatMessage, keep: number) => {
statements.upsert.run(message);
@@ -44,7 +51,11 @@ export const createMessageRepository = (db: Db) => {
/** Последние `limit` сообщений чата в хронологическом порядке. */
const latest = (chatId: number, limit: number): ChatMessage[] =>
(statements.latest.all(chatId, limit) as MessageRow[]).reverse().map(toMessage);
toChronological(statements.latest.all(chatId, limit));
/** Последние `limit` сообщений конкретного участника в хронологическом порядке. */
const latestByUser = (chatId: number, userId: number, limit: number): ChatMessage[] =>
toChronological(statements.latestByUser.all(chatId, userId, limit));
const findById = (chatId: number, messageId: number): ChatMessage | undefined => {
const row = statements.findById.get(chatId, messageId) as MessageRow | undefined;
@@ -52,7 +63,7 @@ export const createMessageRepository = (db: Db) => {
return row && toMessage(row);
};
return { upsertAndPrune, latest, findById };
return { upsertAndPrune, latest, latestByUser, findById };
};
export type MessageRepository = ReturnType<typeof createMessageRepository>;