init: surfaces-bot — Telegram & Matrix prototype

- Surface Protocol: unified IncomingMessage/OutgoingUI/ChatContext
- Telegram: Forum Topics (group + topics per chat)
- Matrix: Space + rooms per chat
- MockPlatformClient with PlatformClient Protocol
- docs: surface-protocol, telegram/matrix specs, api-contract, claude-code-guide
- project scaffold: src/, tests/, pyproject.toml

Co-Authored-By: Claude Sonnet 4-6 <noreply@anthropic.com>
This commit is contained in:
Mikhail Putilovskij 2026-03-27 00:35:42 +03:00
commit b6df29bd9b
29 changed files with 2504 additions and 0 deletions

150
docs/api-contract.md Normal file
View file

@ -0,0 +1,150 @@
# API Contract — Lambda Platform
> **Статус:** ЧЕРНОВИК — проектируем сами, не ждём SDK
> **Автор:** @architect
> **Последнее обновление:** заполнить дату
Это описание того, что нам нужно от платформы.
`MockPlatformClient` реализует этот контракт. При подключении реального SDK — только он меняется.
---
## Base URL
```
https://api.lambda-platform.io/v1
```
## Аутентификация
```
Authorization: Bearer {SERVICE_TOKEN}
```
Сервисный токен выдаётся команде поверхностей. Не путать с токеном пользователя.
---
## Users
### GET /users/{external_id}?platform={platform}
Получает или создаёт пользователя.
**Query params:**
- `platform``telegram` | `matrix`
**Response 200:**
```json
{
"user_id": "usr_abc123",
"external_id": "12345678",
"platform": "telegram",
"display_name": "Иван Иванов",
"created_at": "2025-01-15T10:30:00Z",
"is_new": false
}
```
---
## Sessions
### POST /sessions
Создаёт новую сессию с AI-агентом.
**Request:**
```json
{
"user_id": "usr_abc123",
"platform": "telegram",
"context": {}
}
```
**Response 201:**
```json
{
"session_id": "ses_xyz789",
"agent_id": "agt_def456",
"created_at": "2025-01-15T10:30:00Z",
"expires_at": "2025-01-16T10:30:00Z"
}
```
### GET /sessions/{session_id}
Получает информацию о сессии.
**Response 200:** — см. структуру выше + поле `status: "active" | "closed"`
**Response 404:** `{"error": "SESSION_NOT_FOUND", "message": "..."}`
### DELETE /sessions/{session_id}
Завершает сессию.
**Response 200:**
```json
{"closed": true}
```
---
## Messages
### POST /sessions/{session_id}/messages
Отправляет сообщение и получает ответ агента.
**Request:**
```json
{
"text": "Привет, что ты умеешь?",
"attachments": []
}
```
**Response 200:**
```json
{
"message_id": "msg_qwe012",
"response": "Я AI-агент Lambda...",
"tokens_used": 142,
"finished": true
}
```
### GET /sessions/{session_id}/messages?limit=20&offset=0
История сообщений сессии.
**Response 200:**
```json
[
{
"message_id": "msg_qwe012",
"user_text": "Привет",
"response": "Привет!",
"tokens_used": 42,
"created_at": "2025-01-15T10:31:00Z"
}
]
```
---
## Error format
Все ошибки возвращаются в едином формате:
```json
{
"error": "ERROR_CODE",
"message": "Human readable description",
"details": {}
}
```
Коды ошибок: `SESSION_NOT_FOUND`, `USER_NOT_FOUND`, `RATE_LIMITED`, `PLATFORM_ERROR`
---
## TODO (открытые вопросы к команде платформы)
- [ ] Нужна ли стриминговая передача ответа (SSE / WebSocket)?
- [ ] Как обрабатываются вложения (изображения, файлы)?

View file

292
docs/claude-code-guide.md Normal file
View file

@ -0,0 +1,292 @@
# Гайд по Claude Code для разработки surfaces-bot
## Что такое Claude Code
Claude Code — это CLI-инструмент который запускается в терминале внутри твоего
проекта. Он читает файлы, пишет код, запускает команды. Главное отличие от
чата — он видит весь проект и действует автономно.
Запускается так:
```bash
cd surfaces-bot
claude "твоя задача"
```
---
## Агенты и когда их использовать
В проекте настроены специализированные агенты в `.claude/agents/`.
Claude Code сам выбирает нужного если ты пишешь `@имя` в запросе.
| Агент | Когда | Пример |
|---|---|---|
| `@researcher` | Нужно разобраться как что-то работает | "как работает Forum Topics в aiogram?" |
| `@architect` | Нужно принять архитектурное решение | "как хранить маппинг chat_id → room_id?" |
| `@core-developer` | Писать core/ и platform/ | "реализуй core/protocol.py" |
| `@tg-developer` | Писать Telegram-адаптер | "реализуй обработчик /start" |
| `@matrix-developer` | Писать Matrix-адаптер | "реализуй создание Space" |
| `@reviewer` | Проверить код перед PR | "проверь adapter/telegram/" |
---
## Базовые команды
### Простая задача (без агента)
```bash
# Небольшое изменение — быстрее без агента
claude "добавь поле platform_user_id в ChatContext в core/protocol.py"
```
### Задача через агента
```bash
# Агент читает нужную документацию сам
claude "Use @tg-developer to implement adapter/telegram/handlers/auth.py
based on docs/telegram-prototype.md auth flow"
```
### Вопрос без изменений
```bash
claude "объясни как работает m.space.child в Matrix"
# или просто интерактивно:
claude # запускает интерактивный режим
```
---
## Параллельная разработка двух ботов
Главная идея: два терминала, два worktree, два агента одновременно.
### Шаг 1 — инициализация (один раз)
```bash
cd surfaces-bot
git init
git add . && git commit -m "init"
# Создать worktrees для адаптеров
git worktree add .worktrees/telegram -b feat/telegram-adapter
git worktree add .worktrees/matrix -b feat/matrix-adapter
```
### Шаг 2 — сначала ядро (в main)
```bash
# В основной папке — core/ должен быть готов до адаптеров
claude "Use @core-developer to implement core/protocol.py and platform/interface.py
and platform/mock.py based on docs/surface-protocol.md and docs/api-contract.md"
```
Дождись — это фундамент. Остальное на нём строится.
### Шаг 3 — параллельно адаптеры (два терминала)
**Терминал 1:**
```bash
cd .worktrees/telegram
claude "Use @tg-developer to implement the auth flow from docs/telegram-prototype.md.
Start with adapter/telegram/handlers/auth.py and adapter/telegram/states.py"
```
**Терминал 2 (одновременно):**
```bash
cd .worktrees/matrix
claude "Use @matrix-developer to implement the auth flow from docs/matrix-prototype.md.
Start with adapter/matrix/handlers/auth.py and adapter/matrix/space.py"
```
Они работают в разных папках — не мешают друг другу.
### Шаг 4 — смёрджить когда готово
```bash
cd surfaces-bot # вернуться в main
git merge feat/telegram-adapter
git merge feat/matrix-adapter
```
---
## Экономия токенов (важно для Pro)
### Правило 1: Haiku для исследований
```bash
# @researcher использует Haiku — дёшево
claude "Use @researcher to find examples of aiogram Forum Topics creation with code samples.
Save findings to docs/research/telegram-forum-topics.md"
# НЕ делай так (Sonnet на простой вопрос — дорого):
claude "Use @tg-developer to research how Forum Topics work"
```
### Правило 2: точный контекст
```bash
# Хорошо — агент читает один конкретный файл
claude "Use @tg-developer to implement adapter/telegram/keyboards/settings.py.
Read docs/telegram-prototype.md section 'Настройки' for the button structure"
# Плохо — агент читает весь проект зря
claude "Use @tg-developer to implement keyboards"
# (агент начнёт читать всё подряд)
```
### Правило 3: не запускай ревью на каждый коммит
```bash
# Ревью — только перед merge в main
claude "Use @reviewer to review all changes in adapter/telegram/ before merging"
# НЕ после каждого файла
```
### Правило 4: маленькие задачи без агента
```bash
# Просто правка — без агента быстрее и дешевле
claude "в adapter/telegram/keyboards/confirm.py замени текст кнопки с 'Да' на '✅ Подтвердить'"
```
### Правило 5: не больше двух Sonnet одновременно
```bash
# Можно (Haiku + Sonnet):
# Терминал 1: claude "Use @researcher ..." ← Haiku
# Терминал 2: claude "Use @tg-developer ..." ← Sonnet
# Можно (два Sonnet в разных worktrees):
# Терминал 1: cd .worktrees/telegram && claude "Use @tg-developer ..."
# Терминал 2: cd .worktrees/matrix && claude "Use @matrix-developer ..."
# Слишком много (три Sonnet):
# Терминал 1: claude "Use @core-developer ..."
# Терминал 2: claude "Use @tg-developer ..."
# Терминал 3: claude "Use @matrix-developer ..." ← стоп, подожди
```
---
## Типичные сценарии с примерами
### Сценарий А: начало с нуля
```bash
# 1. Исследование (Haiku, дёшево)
claude "Use @researcher to find matrix-nio examples of Space creation and room management.
Save to docs/research/matrix-spaces.md"
# 2. Уточнение архитектуры (если что-то неясно)
claude "Use @architect to clarify: should ConfirmationRequest timeout be handled in
core/handler.py or in each adapter separately? Update docs/surface-protocol.md"
# 3. Ядро
claude "Use @core-developer to implement core/protocol.py based on docs/surface-protocol.md"
claude "Use @core-developer to implement core/session.py and core/auth.py"
claude "Use @core-developer to implement platform/interface.py and platform/mock.py
based on docs/api-contract.md"
# 4. Адаптеры параллельно (два терминала)
# T1: claude "Use @tg-developer to implement auth flow in adapter/telegram/"
# T2: claude "Use @matrix-developer to implement auth flow in adapter/matrix/"
```
### Сценарий Б: добавить новую фичу в оба бота
Например, команда `!status` / `/status`:
```bash
# 1. Убедиться что в core есть нужные структуры
claude "Use @architect to check if StatusRequest is needed in core/protocol.py
or can reuse existing structures. Read docs/surface-protocol.md"
# 2. Добавить в core если нужно
claude "Use @core-developer to add status handling in core/handler.py"
# 3. Оба адаптера параллельно
# T1: claude "Use @tg-developer to implement /status command in
# adapter/telegram/handlers/settings.py"
# T2: claude "Use @matrix-developer to implement !status command in
# adapter/matrix/handlers/settings.py"
```
### Сценарий В: что-то сломалось
```bash
# Диагностика — без агента, просто спроси
claude "pytest tests/adapter/telegram/test_auth.py провалился с ошибкой:
AttributeError: 'NoneType' has no attribute 'session_id'
Посмотри core/session.py и adapter/telegram/handlers/auth.py и скажи в чём причина"
# Починить точечно
claude "в core/session.py метод get_session возвращает None если сессия не найдена,
но в adapter/telegram/handlers/auth.py это не обрабатывается — исправь"
```
### Сценарий Г: ревью перед merge
```bash
# Запускать только когда фича готова
cd .worktrees/telegram
claude "Use @reviewer to review all files changed in this worktree (adapter/telegram/).
Check against docs/telegram-prototype.md and docs/surface-protocol.md"
```
---
## Интерактивный режим
Когда нужно работать итеративно — запусти без задачи:
```bash
cd surfaces-bot
claude
```
Откроется REPL. Можно:
```
> покажи структуру core/protocol.py
> добавь поле metadata в IncomingMessage
> запусти pytest и покажи результат
> что изменилось в последних 3 коммитах?
```
Полезно когда задача неясная или нужно исследовать.
---
## Флаги которые пригодятся
```bash
# Работать в конкретном worktree
cd .worktrees/telegram && claude "..."
# Не трогать файлы, только смотреть (безопасно)
claude --no-write "объясни как работает converter.py"
# Продолжить предыдущую сессию
claude --continue
# Запустить с конкретной моделью (если хочешь Haiku без @researcher)
claude --model claude-haiku-4-5-20251001 "быстрый вопрос про aiogram"
```
---
## Частые ошибки
**Агент начал читать весь проект** → прерви (Ctrl+C), переформулируй точнее:
```bash
# Плохо
claude "Use @tg-developer to implement settings"
# Хорошо
claude "Use @tg-developer to implement adapter/telegram/keyboards/settings.py —
кнопки главного меню настроек из docs/telegram-prototype.md раздел 'Главное меню настроек'"
```
**Агент пишет не в ту папку** → явно укажи путь:
```bash
claude "Use @matrix-developer to create adapter/matrix/space.py with SpaceManager class"
```
**Кончились токены в середине задачи** → задача сохраняется частично, продолжи:
```bash
claude --continue "продолжи реализацию, остановился на методе create_chat"
```
**Агент предлагает архитектурное решение вместо кода** → перенаправь:
```bash
claude "Use @architect to decide: [вопрос]. После решения Use @tg-developer to implement it"
```

250
docs/matrix-prototype.md Normal file
View file

@ -0,0 +1,250 @@
# Matrix — описание прототипа
## Концепция
Один бот, каждый чат — отдельная комната, все комнаты собраны в Space.
При первом входе бот создаёт для пользователя личное пространство (Space) —
это как папка в Element. Внутри Space бот создаёт комнату для каждого нового
чата с агентом. Пользователь видит аккуратную структуру: одно пространство,
внутри — список чатов. История хранится нативно в Matrix — это часть протокола,
ничего дополнительно делать не нужно.
Matrix выбран как внутренняя поверхность: команды лаборатории, тестировщики,
разработчики скиллов. Поэтому UX здесь — про удобство работы, а не онбординг.
---
## Аутентификация
### Флоу
1. Пользователь приглашает бота в личные сообщения или пишет в общей комнате
2. Бот проверяет `@user:matrix.org` — есть ли аккаунт на платформе
3. Если нет — бот отправляет одноразовый код или ссылку
4. Пользователь подтверждает, платформа возвращает токен
5. Бот сохраняет привязку `matrix_user_id → platform_user_id`
### В моке
- Любой пользователь проходит аутентификацию автоматически
- Бот отвечает: «Добро пожаловать, {display_name}. Создаю ваше пространство...»
- Демонстрирует флоу без реальной платформы
---
## Чаты через Space + комнаты (вариант Б)
### Структура
```
Space: «Lambda — {display_name}»
├── 📌 Настройки ← специальная комната для команд управления
├── 💬 Чат 1 ← первый чат, создаётся автоматически
├── 💬 Чат 2
└── 💬 Исследование рынка ← пользователь сам называет
```
### Создание Space
При первом входе бот:
1. Создаёт Space `Lambda — {display_name}`
2. Создаёт комнату `Настройки` (закреплена вверху)
3. Создаёт первую комнату-чат `Чат 1`
4. Приглашает пользователя во все комнаты
5. Пишет в `Чат 1` приветствие
### Управление чатами
Команды работают в любой комнате Space:
| Команда | Действие |
|---|---|
| `!new` | Создать новый чат (новую комнату в Space) |
| `!new Название` | Создать чат с именем |
| `!rename Название` | Переименовать текущую комнату |
| `!archive` | Вывести комнату из Space (не удалять) |
| `!chats` | Показать список чатов |
### Создание нового чата
1. Пользователь пишет `!new` или `!new Анализ конкурентов`
2. Бот создаёт новую комнату в Space
3. Приглашает пользователя
4. Пишет приветствие и создаёт сессию на платформе
5. Пользователь переходит в новую комнату — начинает диалог
### В моке
- Space и комнаты создаются реально через matrix-nio
- Сессии — через MockPlatformClient
- История хранится в Matrix нативно
---
## Основной диалог
### Флоу сообщения
1. Пользователь пишет текст в комнату-чат
2. Бот показывает typing (m.typing event)
3. Запрос уходит в платформу (MockPlatformClient)
4. Бот отвечает в той же комнате
### Вложения
- Файлы, изображения отправляются как Matrix media events
- Бот принимает `m.file`, `m.image`, `m.audio`
- Передаёт в платформу как `attachments` через `IncomingMessage`
- В моке: подтверждение получения + заглушка-ответ
### Реакции как действия
Matrix поддерживает реакции на сообщения (`m.reaction`).
Используем это для подтверждения действий агента:
```
Агент: Хочу отправить письмо на vasya@mail.ru
Тема: «Отчёт за неделю»
👍 — подтвердить ❌ — отменить
```
Пользователь ставит реакцию — бот обрабатывает. Нативно и удобно.
### Треды для длинных задач
Если агент выполняет долгую задачу (deep research, генерация документа),
бот создаёт тред от своего первого ответа и пишет промежуточные статусы туда.
Основной чат не засоряется.
```
Бот: Начинаю исследование по теме «AI агенты 2025» [→ в треде]
└── Ищу источники... (1/4)
└── Анализирую статьи... (2/4)
└── Формирую отчёт... (3/4)
└── Готово. Отчёт: [...]
```
---
## Комната «Настройки»
Специальная комната для управления агентом. Закреплена вверху Space.
Команды работают только здесь — не мешают диалогу в чатах.
### Коннекторы
```
!connectors — показать список
!connect gmail — подключить Gmail (OAuth ссылка)
!connect github — подключить GitHub
!connect calendar — подключить Google Calendar
!connect notion — подключить Notion
!disconnect gmail — отключить
```
Статус:
```
Коннекторы:
✅ Gmail — подключён (user@gmail.com)
❌ GitHub — не подключён → !connect github
❌ Google Calendar — не подключён
❌ Notion — не подключён
```
В моке: OAuth ссылка-заглушка → «Подключено ✓»
### Скиллы
```
!skills — показать список
!skill on browser — включить Browser Use
!skill off browser — выключить
```
Статус:
```
Скиллы:
✅ web-search — поиск в интернете
✅ fetch-url — чтение веб-страниц
✅ email — чтение почты (требует Gmail)
❌ browser — управление браузером
❌ image-gen — генерация изображений
❌ video-gen — генерация видео
✅ files — работа с файлами
❌ calendar — календарь (требует Google Calendar)
```
В моке: состояние хранится локально.
### Личность агента
```
!soul — показать текущий SOUL.md
!soul name Лямбда — задать имя агента
!soul style brief — стиль: brief | friendly | formal
!soul priority «разбирать почту утром» — приоритетная задача
!soul reset — сбросить к дефолту
```
В моке: SOUL.md генерируется и хранится локально, агент обращается по имени.
### Безопасность
```
!safety — показать настройки
!safety on email-send — требовать подтверждение перед отправкой письма
!safety off calendar-create — не спрашивать для создания событий
```
Статус:
```
Подтверждение требуется для:
✅ отправка письма
✅ удаление файлов
✅ публикация в соцсетях
❌ создание события в календаре
❌ поиск в интернете
```
### Подписка
```
!plan — показать текущий план
```
```
Подписка: Beta (бесплатно)
Токены этот месяц: 800 / 1000
━━━━━━━━░░ 80%
```
Заглушка, реализует другая команда.
### Статус и диагностика
```
!status — состояние агента и платформы
!sessions — список активных сессий
!whoami — текущий аккаунт платформы
```
```
Статус:
Платформа: ✅ доступна
Агент: ✅ активен (сессия #abc123)
Аккаунт: user@lambda.lab
Активных чатов: 3
```
---
## FSM состояния
```
[Invite] → AuthPending → AuthConfirmed
SpaceSetup → Idle (в комнате Настройки)
[новая комната] → SessionCreated → Idle (в чате)
ReceivingMessage → WaitingResponse → Idle
WaitingReaction (confirm) → [✅/❌] → Idle
LongTask → [тред со статусами] → Done → Idle
```
---
## Стек
- Python 3.11+
- matrix-nio (async) — Matrix клиент
- MockPlatformClient → `platform/interface.py`
- structlog для логирования
- SQLite для хранения `matrix_user_id → platform_user_id`, состояния скиллов, маппинга `chat_id → room_id`

0
docs/research/.gitkeep Normal file
View file

311
docs/surface-protocol.md Normal file
View file

@ -0,0 +1,311 @@
# Surface Protocol — унификация поверхностей
## Идея
Любая поверхность (Telegram, Matrix, будущий Discord, голос, web) делает одно
и то же: принимает события от пользователя и отправляет ответы обратно.
Разница только в том как событие выглядит снаружи: aiogram `Message`,
matrix-nio `RoomMessageText`, Discord `Interaction`. Внутри — одно и то же.
Surface Protocol — это общий язык между адаптерами поверхностей и ядром.
Адаптер конвертирует нативные события в протокольные структуры и обратно.
Ядро работает только с протокольными структурами и ничего не знает о транспорте.
**Добавить новую поверхность = написать один адаптер-конвертер.**
Ядро не трогается.
---
## Структура проекта
```
surfaces-bot/
core/
protocol.py — все унифицированные структуры данных
handler.py — логика: IncomingEvent → OutgoingEvent
session.py — управление сессиями и чатами
auth.py — AuthFlow
settings.py — SettingsAction, управление настройками
adapter/
telegram/ — aiogram адаптер
converter.py — aiogram Event → IncomingEvent, OutgoingEvent → aiogram API
bot.py — точка входа, роутер
matrix/ — matrix-nio адаптер
converter.py — matrix-nio Event → IncomingEvent, OutgoingEvent → Matrix API
bot.py — точка входа, клиент
_template.py — шаблон для новой поверхности
platform/
interface.py — Protocol: PlatformClient
mock.py — MockPlatformClient
```
---
## Входящие события (Incoming)
Всё что пользователь делает конвертируется в одно из трёх:
### IncomingMessage
Обычное сообщение — текст, файл, голос.
```python
@dataclass
class IncomingMessage:
user_id: str # "tg_123456" | "@user:matrix.org"
platform: str # "telegram" | "matrix"
chat_id: str # "C1" | "C2" — ID чата в воркспейсе платформы
text: str
attachments: list[Attachment]
reply_to: str | None # message_id если это ответ на сообщение
```
### IncomingCommand
Команда управления — `/start`, `!new`, `/settings`.
```python
@dataclass
class IncomingCommand:
user_id: str
platform: str
chat_id: str
command: str # "start" | "new" | "settings" | "archive" ...
args: list[str] # аргументы после команды
```
### IncomingCallback
Нажатие кнопки или реакции — подтверждение, переключение скилла.
```python
@dataclass
class IncomingCallback:
user_id: str
platform: str
chat_id: str
action: str # "confirm" | "cancel" | "toggle_skill" | "connect" ...
payload: dict # {"skill": "browser"} | {"action_id": "abc123"} ...
```
### Attachment
Вложение, нормализованное из любого источника.
```python
@dataclass
class Attachment:
type: str # "image" | "document" | "audio" | "video"
url: str | None # ссылка если доступна
content: bytes | None # содержимое если скачано
filename: str | None
mime_type: str | None
```
---
## Исходящие события (Outgoing)
Всё что ядро хочет показать пользователю:
### OutgoingMessage
Ответ агента или системное сообщение.
```python
@dataclass
class OutgoingMessage:
chat_id: str
text: str
parse_mode: str # "markdown" | "plain"
attachments: list[Attachment]
reply_to: str | None
```
### OutgoingUI
Интерактивные элементы — кнопки, меню, переключатели.
```python
@dataclass
class OutgoingUI:
chat_id: str
text: str
buttons: list[UIButton]
@dataclass
class UIButton:
label: str
action: str # action для IncomingCallback
payload: dict
style: str # "primary" | "danger" | "secondary"
```
Telegram рендерит это как InlineKeyboard.
Matrix рендерит как текст с описанием реакций или HTML-кнопки.
### OutgoingNotification
Асинхронное уведомление — агент закончил долгую задачу.
```python
@dataclass
class OutgoingNotification:
chat_id: str
text: str
level: str # "info" | "warning" | "success" | "error"
```
### OutgoingTyping
Индикатор что агент думает.
```python
@dataclass
class OutgoingTyping:
chat_id: str
is_typing: bool
```
---
## Жизненный цикл (Lifecycle)
Унифицированные события для управления чатами и подключением.
### ChatContext
Состояние чата — общее для всех поверхностей.
```python
@dataclass
class ChatContext:
chat_id: str # "C1" | "C2" — ID в воркспейсе платформы
display_name: str # «Чат 1» | «Анализ рынка»
platform: str
surface_ref: str # room_id в Matrix | topic_id в Telegram
session_id: str | None # активная сессия платформы
created_at: datetime
is_archived: bool
```
### AuthFlow
Флоу аутентификации — одинаков для всех поверхностей.
```python
@dataclass
class AuthFlow:
user_id: str
platform: str
state: str # "pending" | "code_sent" | "confirmed" | "failed"
platform_user_id: str | None
```
### ConfirmationRequest
Запрос подтверждения опасного действия — от агента к пользователю.
```python
@dataclass
class ConfirmationRequest:
action_id: str
chat_id: str
description: str # «Отправить письмо на vasya@mail.ru»
risk_level: str # "low" | "medium" | "high"
expires_at: datetime
```
Telegram показывает как Inline-кнопки.
Matrix показывает как реакции 👍 / ❌.
Ядро не знает как именно — только получает `IncomingCallback` с `action: "confirm"`.
---
## SettingsAction
Унифицированные действия в настройках — одинаковы для Telegram и Matrix.
```python
@dataclass
class SettingsAction:
action: str # "connect" | "disconnect" | "toggle_skill" | ...
payload: dict
# Примеры:
SettingsAction(action="connect", payload={"service": "gmail"})
SettingsAction(action="toggle_skill", payload={"skill": "browser", "enabled": True})
SettingsAction(action="set_soul", payload={"field": "name", "value": "Лямбда"})
SettingsAction(action="set_safety", payload={"trigger": "email-send", "enabled": True})
```
Ядро обрабатывает `SettingsAction` одинаково. Откуда пришло — не важно.
---
## PaymentEvent
Заглушка для биллинга — реализует другая команда.
```python
@dataclass
class PaymentRequired:
user_id: str
reason: str # "limit_reached" | "feature_locked"
current_plan: str
```
Поверхность получила `PaymentRequired` → показала заглушку.
Содержимое заглушки потом наполняет команда биллинга.
---
## Как написать новую поверхность
Скопируй `adapter/_template.py` и реализуй три метода:
```python
class MySurfaceAdapter:
def parse_incoming(self, raw_event) -> IncomingMessage | IncomingCommand | IncomingCallback:
"""Конвертировать нативное событие в протокольную структуру."""
...
def render_outgoing(self, event: OutgoingMessage | OutgoingUI | OutgoingNotification) -> any:
"""Конвертировать протокольную структуру в нативный формат."""
...
async def send(self, rendered) -> None:
"""Отправить нативным способом."""
...
```
Дальше подключи адаптер в точку входа и передай `core.handler.handle` как callback.
Всё остальное уже работает.
---
## Что ядро даёт бесплатно
Любая новая поверхность получает без дополнительного кода:
- управление сессиями (создание, переключение, закрытие)
- управление чатами (`ChatContext`)
- аутентификацию (`AuthFlow`)
- подтверждение действий (`ConfirmationRequest`)
- все настройки (коннекторы, скиллы, SOUL, безопасность, подписка)
- интеграцию с платформой через `PlatformClient`
- обработку ошибок платформы
---
## Замена MockPlatformClient на реальный SDK
Вся работа с платформой идёт через `PlatformClient` протокол:
```python
class PlatformClient(Protocol):
async def get_or_create_user(self, user_id: str, platform: str) -> User: ...
async def create_session(self, user_id: str, chat_id: str) -> Session: ...
async def send_message(self, session_id: str, text: str, attachments: list) -> AgentResponse: ...
async def close_session(self, session_id: str) -> None: ...
async def get_chat_history(self, user_id: str, chat_id: str) -> list[Message]: ...
async def get_settings(self, user_id: str) -> UserSettings: ...
async def update_settings(self, user_id: str, action: SettingsAction) -> None: ...
```
`MockPlatformClient` реализует этот протокол сейчас.
Реальный SDK — тоже реализует этот протокол, заменяя один файл.
Адаптеры поверхностей и ядро не меняются вообще.

221
docs/telegram-prototype.md Normal file
View file

@ -0,0 +1,221 @@
# Telegram — описание прототипа
## Концепция
Один бот, несколько чатов через Topics в Forum-группе.
При первом запуске бот создаёт для пользователя персональную Forum-группу
(супергруппу с включёнными темами). Каждый новый чат с агентом — отдельная тема
внутри группы. Пользователь видит это как список чатов в одном месте.
Бот управляет группой от имени пользователя через Telegram Bot API:
создаёт темы, переименовывает, архивирует.
---
## Аутентификация
### Флоу
1. Пользователь пишет боту `/start`
2. Бот проверяет — есть ли аккаунт на платформе привязанный к этому `tg_user_id`
3. Если нет — бот отправляет одноразовую ссылку или код для входа
4. Пользователь подтверждает, платформа возвращает токен сессии
5. Бот сохраняет привязку `tg_user_id → platform_user_id`
### В моке
- Любой пользователь проходит аутентификацию автоматически
- Кнопка «Войти» → пауза 1 сек → «Вы вошли как {имя}»
- Демонстрирует флоу без реальной платформы
---
## Чаты через Forum Topics (вариант В)
### Как это работает
- Бот создаёт супергруппу с Topics для каждого нового пользователя
- Каждый чат = отдельная тема (Topic) в этой группе
- История хранится нативно в Telegram (в самой теме)
- Переключение между чатами = переключение между темами
### Управление чатами
Внутри каждой темы доступны команды:
| Команда | Действие |
|---|---|
| `/new` | Создать новый чат (новую тему) |
| `/rename Название` | Переименовать текущий чат |
| `/archive` | Архивировать текущий чат |
| `/chats` | Показать список всех чатов |
### Создание нового чата
1. Пользователь пишет `/new` или нажимает кнопку
2. Бот спрашивает название (опционально, можно пропустить)
3. Бот создаёт новую тему в группе: «Чат 1», «Чат 2» и т.д.
4. Бот отправляет в новую тему приветствие и создаёт сессию на платформе
### В моке
- Группа и темы создаются реально через Bot API
- Сессии на платформе — через MockPlatformClient
- История в темах хранится нативно в Telegram, ничего не нужно делать
---
## Основной диалог
### Флоу сообщения
1. Пользователь пишет текст в тему
2. Бот показывает `typing...`
3. Запрос уходит в платформу (сейчас — MockPlatformClient)
4. Бот отвечает текстом агента
### Вложения
- Фото, документы, голосовые — передаются в платформу как `attachments`
- В моке: бот подтверждает получение файла и возвращает заглушку-ответ
- Форматы: PDF, изображения, текстовые файлы
### Подтверждение действий
Если агент собирается выполнить потенциально опасное действие
(отправить письмо, удалить файл, сделать запись в календарь):
```
Агент хочет выполнить действие:
📧 Отправить письмо на vasya@mail.ru
Тема: «Отчёт за неделю»
[✅ Подтвердить] [❌ Отменить]
```
Пользователь нажимает кнопку — действие выполняется или отменяется.
В моке: кнопки работают, действие логируется, ответ-заглушка.
---
## Настройки
Доступны через `/settings` в любой теме или в главном меню бота.
Реализованы как цепочка инлайн-кнопок.
### Главное меню настроек
```
⚙️ Настройки
[🔗 Коннекторы] [🧩 Скиллы]
[🧠 Личность] [🔔 Уведомления]
[🔒 Безопасность] [💳 Подписка]
```
### Коннекторы
Подключение внешних сервисов. Агент получает доступ через API Proxy,
пароли пользователю вводить не нужно — только OAuth.
| Коннектор | Способ подключения |
|---|---|
| Gmail / Outlook | OAuth ссылка |
| Google Calendar | OAuth ссылка |
| GitHub | OAuth ссылка |
| Notion | OAuth ссылка |
| Telegram (читать каналы) | Уже подключён |
В моке: кнопка «Подключить» → ссылка-заглушка → «Подключено ✓»
### Скиллы
Включение/выключение навыков агента.
```
🧩 Скиллы
✅ Поиск в интернете
✅ Чтение почты
❌ Управление браузером
❌ Генерация изображений
❌ Работа с календарём
✅ Работа с файлами
❌ Генерация видео
```
Каждый скилл — кнопка-переключатель. Нажал — включил/выключил.
В моке: состояние хранится локально, платформа не вызывается.
### Личность агента (SOUL.md)
Онбординг-анкета при первом входе, потом редактирование.
```
🧠 Личность агента
Как зовут твоего агента?
[_Лямбда_____________]
Что он должен делать в первую очередь?
[_Разбирать почту_____]
Стиль общения:
[Деловой] [Дружелюбный] [Краткий]
```
В моке: данные сохраняются, агент обращается к пользователю по имени из настроек.
### Уведомления
Когда агент выполнил долгую задачу — уведомление в Telegram.
```
🔔 Уведомления
✅ Задача выполнена
✅ Требуется подтверждение действия
❌ Ежедневный дайджест
❌ Напоминания из календаря
```
В моке: уведомления отправляются через бот с задержкой (симуляция).
### Безопасность
Какие действия требуют явного подтверждения.
```
🔒 Безопасность
Всегда спрашивать перед:
✅ Отправкой письма
✅ Удалением файлов
✅ Публикацией в соцсетях
❌ Созданием события в календаре
❌ Поиском в интернете
```
### Подписка
Заглушка — реализует другая команда.
```
💳 Подписка
Текущий план: Beta (бесплатно)
Токены: ████████░░ 800/1000
[Подробнее о планах]
```
---
## FSM состояния
```
[Start] → AuthPending → AuthConfirmed
GroupSetup → Idle
ReceivingMessage → WaitingResponse → Idle
ConfirmAction → [Confirmed/Cancelled] → Idle
Settings → [подменю] → Idle
```
---
## Стек
- Python 3.11+
- aiogram 3.x (Router, FSM, InlineKeyboard, Forum Topics API)
- MockPlatformClient → `platform/interface.py`
- structlog для логирования
- SQLite для хранения `tg_user_id → platform_user_id` и состояния скиллов

68
docs/user-flow.md Normal file
View file

@ -0,0 +1,68 @@
# User Flow — Lambda Bot
> **Статус:** ШАБЛОН — заполняет @architect после исследований
> **Зависит от:** docs/research/telegram-flows.md, docs/research/competitor-ux.md
---
## Основной сценарий (happy path)
```mermaid
sequenceDiagram
actor User
participant Bot as Telegram/Matrix Bot
participant Platform as Lambda Platform
User->>Bot: /start
Bot->>Platform: GET /users/{tg_id}?platform=telegram
Platform-->>Bot: {user_id, is_new}
alt Новый пользователь
Bot->>User: Приветствие + инструкция
else Существующий пользователь
Bot->>User: Добро пожаловать обратно
end
User->>Bot: Любое сообщение
Bot->>Platform: POST /sessions (создаём сессию)
Platform-->>Bot: {session_id, agent_id}
loop Диалог
User->>Bot: Сообщение
Bot->>Platform: POST /sessions/{id}/messages
Platform-->>Bot: {response}
Bot->>User: Ответ агента
end
User->>Bot: /end или таймаут
Bot->>Platform: DELETE /sessions/{id}
Bot->>User: Сессия завершена
```
---
## Состояния FSM (Telegram)
```mermaid
stateDiagram-v2
[*] --> Idle: /start
Idle --> InSession: любое сообщение
InSession --> InSession: сообщение пользователя
InSession --> Idle: /end
InSession --> Error: ошибка платформы
Error --> Idle: /start
Error --> InSession: retry
```
---
## Открытые вопросы
> Заполняет @researcher и @architect после исследований
- [ ] Как выглядит онбординг новых пользователей у конкурентов?
- [ ] Нужна ли кнопка "Новая сессия" или сессия стартует автоматически?
- [ ] Что показываем пока агент думает (typing indicator)?
- [ ] Как обрабатываем timeout ответа от платформы?