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

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

@@ -1,8 +1,15 @@
import type { Message, User } from "grammy/types";
import type { ChatMessage } from "../types.js";
import type { Message, MessageEntity, User } from "grammy/types";
import type { ChatMessage, MemberProfile } from "../types.js";
const TELEGRAM_MESSAGE_LIMIT = 4096;
export const toMemberProfile = (user: User): MemberProfile => ({
userId: user.id,
username: user.username ?? null,
firstName: user.first_name,
lastName: user.last_name ?? null,
});
export const displayName = (user: User): string => {
const name = [user.first_name, user.last_name].filter(Boolean).join(" ");
@@ -43,6 +50,7 @@ export const toChatMessage = (msg: Message): ChatMessage | undefined => {
return {
chatId: msg.chat.id,
messageId: msg.message_id,
userId: msg.from?.id ?? null,
date: msg.date,
author: msg.from ? displayName(msg.from) : (msg.sender_chat?.title ?? "Аноним"),
text,
@@ -50,6 +58,26 @@ export const toChatMessage = (msg: Message): ChatMessage | undefined => {
};
};
const messageEntities = (msg: Message): { text: string; entities: MessageEntity[] } => ({
text: msg.text ?? msg.caption ?? "",
entities: msg.entities ?? msg.caption_entities ?? [],
});
/** Ники из упоминаний `@username` (без собаки), без повторов. */
export const mentionedUsernames = (msg: Message): string[] => {
const { text, entities } = messageEntities(msg);
const usernames = entities
.filter((entity) => entity.type === "mention")
.map((entity) => text.slice(entity.offset + 1, entity.offset + entity.length));
return [...new Set(usernames)];
};
/** Пользователи, упомянутые по имени-ссылке — так Telegram упоминает людей без ника. */
export const mentionedUsers = (msg: Message): User[] =>
messageEntities(msg).entities.flatMap((entity) => (entity.type === "text_mention" ? [entity.user] : []));
/** Режет длинный ответ на части под лимит Telegram, по возможности по переносам строк. */
export const splitMessage = (text: string): string[] => {
if (text.length <= TELEGRAM_MESSAGE_LIMIT) return text ? [text] : [];