REPOSITORY / ScuroNeko/Laniakea
Wiki
wip
@@ -0,0 +1,157 @@
|
||||
# Framework Backlog
|
||||
|
||||
Эта страница отслеживает backlog framework-level задач, связанных с отсутствующими концепциями в библиотеке, а не просто с нехваткой документации.
|
||||
|
||||
## High-Priority Core Concepts
|
||||
|
||||
### 1. Conversation / Scene Model
|
||||
|
||||
Current state:
|
||||
|
||||
- Фреймворк хорошо обрабатывает один update через commands, payloads, middleware и update handlers.
|
||||
- В нём уже есть полезные низкоуровневые строительные блоки: `MsgContext`, drafts, payload routing, plugins и update handlers.
|
||||
- Но пока нет first-class концепции для долгоживущих пользовательских interaction flows.
|
||||
|
||||
Why this matters:
|
||||
|
||||
- Многие Telegram-боты быстро перерастают из изолированных команд в stateful multi-step flows.
|
||||
- Реальным ботам часто нужны концепции вроде "подождать следующее сообщение пользователя", "пользователь сейчас на шаге 3 из 5" или "нажатие кнопки переводит пользователя в следующее состояние сцены".
|
||||
- Без scene model пользователи библиотеки начинают строить свой mini-framework поверх Laniakea.
|
||||
|
||||
What is missing:
|
||||
|
||||
- Способ маршрутизировать updates в активную сцену до обычного command routing.
|
||||
- Способ хранить conversation state на пользователя или чат.
|
||||
- Способ описывать шаги и переходы без ручного state machine вокруг middleware и storage.
|
||||
- Способ явно входить в flow, продолжать его, отменять и завершать.
|
||||
- Способ поддерживать modal chat flows, когда пользователь "внутри" сцены, а обычный текст считается scene input, пока явная escape-команда не завершит режим.
|
||||
|
||||
Possible API direction:
|
||||
|
||||
- Концепции `Scene`, `Step` и `SessionStore`.
|
||||
- `bot.AddScene(...)` или отдельный scene registry.
|
||||
- `ctx.Scene()`, `ctx.NextStep(...)`, `ctx.ExitScene()` или похожие helpers для state transitions.
|
||||
- Routing rule: active scene first, then normal command/payload flow if no scene claims the update.
|
||||
- Storage-backed состояние на пользователя или чат с чистым интерфейсом для кастомного persistence.
|
||||
- Локальные escape и passthrough-команды сцены, чтобы flow вроде `/startrp` могли переводить пользователя в отдельный chat mode, где большинство сообщений уходят прямо в сцену, а команды вроде `/exit` или небольшой whitelist сохраняют специальное поведение.
|
||||
|
||||
Important design constraints:
|
||||
|
||||
- Это должно быть опциональным и additive.
|
||||
- Это не должно заменять plugins, commands или handlers как обычные точки входа во фреймворк.
|
||||
- Это должно работать поверх существующих middleware и `MsgContext`, а не вводить вторую несовместимую модель выполнения.
|
||||
|
||||
Practical target:
|
||||
|
||||
- Сделать stateful bot flows first-class framework-supported паттерном вместо userland convention.
|
||||
- Покрыть и step-based формы, и mode-based chat flows без необходимости строить пользовательские routing layers вокруг активных sessions.
|
||||
|
||||
## Secondary Backlog
|
||||
|
||||
- Webhook runtime model: у библиотеки есть хороший polling model, но нет first-class webhook execution model на уровне framework.
|
||||
- Service layer and dependency graph model: `DatabaseContext(T)` намеренно минималистичен, но нет более сильной framework-level концепции для application services или scoped dependencies.
|
||||
- User-facing vs internal error model: у framework есть unified error flow, но он ещё плохо различает user-visible, internal-only, retryable и silent errors.
|
||||
- Authorization and policy model: middleware могут реализовать auth и permissions, но нет явной framework-level модели для access policies, roles или capability checks.
|
||||
- Observability model: logging уже сильный, но metrics, tracing и structured framework hooks пока не first-class.
|
||||
- Plugin composition contract: plugins — хороший grouping unit, но нет явной модели plugin dependencies, shared capabilities или composition contracts.
|
||||
- Update schema contract: update handling уже есть, но нет formal framework-level понятия, описывающего, какие поля `MsgContext` гарантированы для каких update kinds.
|
||||
- Configuration freeze model: у framework уже есть реальные commit points вроде `AddPlugins(...)`, но пока это скорее implementation truth, чем явная top-level концепция.
|
||||
|
||||
## Done
|
||||
|
||||
### [1.0.0-rc.12] Typed Handler Input Model
|
||||
|
||||
Current state:
|
||||
|
||||
- Commands и payloads сейчас отдают parsed text через `ctx.Text` и `ctx.Args`.
|
||||
- `CommandArg` даёт базовую проверку аргументов и их формы.
|
||||
- Handlers всё ещё делают большую часть нетривиального парсинга вручную.
|
||||
|
||||
Why this matters:
|
||||
|
||||
- По мере роста бота handlers часто начинают с повторяющегося boilerplate для разбора `ctx.Args`.
|
||||
- Validation logic расползается по handlers вместо того, чтобы жить в одном предсказуемом binding layer.
|
||||
- Текущая модель проста и честна, но помогает недостаточно, когда команды становятся более структурированными.
|
||||
|
||||
What is missing:
|
||||
|
||||
- First-class способ bind'ить command или payload arguments в typed Go value.
|
||||
- Framework-level паттерн для conversion errors и validation errors вместо чистой работы со строками.
|
||||
- Low-friction путь перехода от positional arguments к structured input object.
|
||||
|
||||
Possible API direction:
|
||||
|
||||
- Lightweight binding API вроде `ctx.BindArgs(&input)`.
|
||||
- Или explicit typed command registration вроде `NewCommandTyped(...)`.
|
||||
- Positional mapping в structs, optional fields, basic conversion support и интеграция с текущим validation flow.
|
||||
- Unified binding и validation failures, которые идут через существующий centralized error path.
|
||||
|
||||
Example of the kind of user code this should enable:
|
||||
|
||||
```go
|
||||
type BanInput struct {
|
||||
UserID int
|
||||
Reason string
|
||||
}
|
||||
|
||||
func ban(ctx *laniakea.MsgContext, db *App) error {
|
||||
var input BanInput
|
||||
if err := ctx.BindArgs(&input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.Ban(input.UserID, input.Reason)
|
||||
}
|
||||
```
|
||||
|
||||
Important design constraints:
|
||||
|
||||
- Избегать reflection-heavy и слишком "магической" подсистемы.
|
||||
- Сохранять текущую модель `ctx.Args` как минимальный baseline.
|
||||
- Относиться к typed binding как к ergonomic layer поверх текущей command model, а не как к её замене.
|
||||
|
||||
Practical target:
|
||||
|
||||
- Убрать повторяющийся parsing boilerplate, сохранив явный и Go-like характер framework.
|
||||
|
||||
### [1.0.0-rc.12] Request Context / Cancellation Model
|
||||
|
||||
Current state:
|
||||
|
||||
- `RunWithContext(...)` управляет runtime lifecycle бота и graceful shutdown.
|
||||
- `tgapi` уже поддерживает context-aware методы.
|
||||
- Обычные handlers не получают first-class request-scoped `context.Context`.
|
||||
|
||||
Why this matters:
|
||||
|
||||
- Handler business logic часто требует cancellation-aware database calls, HTTP calls или обращения к downstream services.
|
||||
- У framework уже есть хорошая runtime cancellation story, но она пока не доходит естественным образом до пользовательского кода внутри handlers.
|
||||
- В современном Go API `context.Context` — стандартная часть operational correctness.
|
||||
|
||||
What is missing:
|
||||
|
||||
- Чистый request-scoped context, который сопровождает каждый update через всё выполнение handler.
|
||||
- Стандартный способ для application code остановить работу, когда бот shutting down или update processing context отменён.
|
||||
- Прямой мост между bot lifecycle control и service-layer cancellation.
|
||||
|
||||
Possible API direction:
|
||||
|
||||
- Предпочесть non-breaking подход и выдавать context через `MsgContext`, например `ctx.Context()`.
|
||||
- Строить context из update-processing lifecycle, чтобы он был meaningful во время graceful shutdown.
|
||||
- Сделать естественным передачу этого context в database methods, HTTP clients и `tgapi.WithContext(...)`.
|
||||
|
||||
Why this should probably not be a signature change:
|
||||
|
||||
- Изменение handler signatures на прямой `context.Context` было бы public breaking change.
|
||||
- Accessor на `MsgContext` сохраняет совместимость и при этом даёт handlers idiomatic Go path для cancellation.
|
||||
|
||||
Practical target:
|
||||
|
||||
- Позволить handler code естественно участвовать в cancellation и graceful shutdown, не заставляя пользователей строить собственный context plumbing.
|
||||
|
||||
Связанные страницы:
|
||||
|
||||
- [[Scenes]]
|
||||
- [[MsgContext]]
|
||||
- [[Bot-Lifecycle]]
|
||||
- [[Migration]]
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
# Framework Backlog
|
||||
|
||||
This page tracks framework-level backlog items that are about missing concepts in the library itself, not just missing documentation.
|
||||
|
||||
## High-Priority Core Concepts
|
||||
|
||||
### 1. Conversation / Scene Model
|
||||
|
||||
Current state:
|
||||
|
||||
- The framework is strong at handling a single update through commands, payloads, middleware, and update handlers.
|
||||
- It already has useful lower-level building blocks such as `MsgContext`, drafts, payload routing, plugins, and update handlers.
|
||||
- It does not yet provide a first-class concept for long-lived user interaction flows.
|
||||
|
||||
Why this matters:
|
||||
|
||||
- Many Telegram bots quickly move beyond isolated commands and need stateful multi-step flows.
|
||||
- Real bots often need concepts like "wait for the user's next message", "user is currently on step 3 of 5", or "button press moves the user to the next scene state".
|
||||
- Without a scene model, library users end up building their own mini-framework on top of Laniakea.
|
||||
|
||||
What is missing:
|
||||
|
||||
- A way to route updates to an active scene before normal command routing.
|
||||
- A way to persist conversation state per user or per chat.
|
||||
- A way to describe steps and transitions without hand-rolling state machines around middleware and storage.
|
||||
- A way to enter, continue, cancel, and complete a conversation flow explicitly.
|
||||
- A way to support modal chat flows where the user is "inside" a scene and ordinary text is treated as scene input until an explicit escape command exits the mode.
|
||||
|
||||
Possible API direction:
|
||||
|
||||
- `Scene`, `Step`, and `SessionStore` concepts.
|
||||
- `bot.AddScene(...)` or a dedicated scene registry.
|
||||
- `ctx.Scene()`, `ctx.NextStep(...)`, `ctx.ExitScene()`, or similar state-transition helpers.
|
||||
- Routing rule: active scene first, then normal command/payload flow if no scene claims the update.
|
||||
- Storage-backed per-user or per-chat state with a clean interface for custom persistence.
|
||||
- Scene-local escape and passthrough commands, so flows like `/startrp` can put a user into a dedicated chat mode where most messages go straight to the scene, while commands like `/exit` or a small whitelist still retain special meaning.
|
||||
|
||||
Important design constraints:
|
||||
|
||||
- This should be additive and optional.
|
||||
- It should not replace plugins, commands, or handlers as the normal framework entry points.
|
||||
- It should work with existing middleware and `MsgContext` instead of introducing a second incompatible execution model.
|
||||
|
||||
Practical target:
|
||||
|
||||
- Make stateful bot flows a first-class, framework-supported pattern instead of a userland convention.
|
||||
- Cover both step-based forms and mode-based chat flows without forcing users to build custom routing layers around active sessions.
|
||||
|
||||
## Secondary Backlog
|
||||
|
||||
- Webhook runtime model: the library has a solid polling model, but no first-class webhook execution model at the framework level.
|
||||
- Service layer and dependency graph model: `DatabaseContext(T)` is intentionally minimal, but there is no stronger framework concept for application services or scoped dependencies.
|
||||
- User-facing vs internal error model: the framework has a unified error flow, but it does not yet distinguish well between user-visible, internal-only, retryable, or silent errors.
|
||||
- Authorization and policy model: middleware can implement auth and permissions, but there is no explicit framework concept for access policies, roles, or capability checks.
|
||||
- Observability model: logging is strong, but metrics, tracing, and structured framework hooks are still missing as first-class concepts.
|
||||
- Plugin composition contract: plugins are a good grouping unit, but there is no explicit model for plugin dependencies, shared capabilities, or composition contracts.
|
||||
- Update schema contract: update handling exists, but there is no formal framework-level concept describing which `MsgContext` fields are guaranteed in which update kinds.
|
||||
- Configuration freeze model: the framework already has real commit points like `AddPlugins(...)`, but this is still more of an implementation truth than an explicit top-level concept.
|
||||
|
||||
## Done
|
||||
|
||||
### [1.0.0-rc.12] Typed Handler Input Model
|
||||
|
||||
Current state:
|
||||
|
||||
- Commands and payloads currently expose parsed text through `ctx.Text` and `ctx.Args`.
|
||||
- `CommandArg` provides basic argument validation and shape checks.
|
||||
- Handlers still do most non-trivial parsing manually.
|
||||
|
||||
Why this matters:
|
||||
|
||||
- As bots grow, handlers often start with repetitive `ctx.Args` parsing boilerplate.
|
||||
- Validation logic tends to spread across handlers instead of living in one predictable binding layer.
|
||||
- The current model is simple and honest, but it does not help enough once commands become more structured.
|
||||
|
||||
What is missing:
|
||||
|
||||
- A first-class way to bind command or payload arguments into a typed Go value.
|
||||
- A framework-level pattern for conversion errors and validation errors beyond raw string handling.
|
||||
- A low-friction way to move from positional arguments to a structured input object.
|
||||
|
||||
Possible API direction:
|
||||
|
||||
- A lightweight binding API such as `ctx.BindArgs(&input)`.
|
||||
- Or explicit typed command registration such as `NewCommandTyped(...)`.
|
||||
- Positional mapping into structs, optional fields, basic conversion support, and integration with current validation flow.
|
||||
- Unified binding and validation failures routed through the current centralized error path.
|
||||
|
||||
Example of the kind of user code this should enable:
|
||||
|
||||
```go
|
||||
type BanInput struct {
|
||||
UserID int
|
||||
Reason string
|
||||
}
|
||||
|
||||
func ban(ctx *laniakea.MsgContext, db *App) error {
|
||||
var input BanInput
|
||||
if err := ctx.BindArgs(&input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.Ban(input.UserID, input.Reason)
|
||||
}
|
||||
```
|
||||
|
||||
Important design constraints:
|
||||
|
||||
- Avoid a reflection-heavy, magical subsystem.
|
||||
- Keep the current `ctx.Args` model as the minimal baseline.
|
||||
- Treat typed binding as an ergonomic layer on top of the current command model, not a replacement for it.
|
||||
|
||||
Practical target:
|
||||
|
||||
- Remove repetitive parsing boilerplate while preserving the framework's explicit, Go-like feel.
|
||||
|
||||
### [1.0.0-rc.12] Request Context / Cancellation Model
|
||||
|
||||
Current state:
|
||||
|
||||
- `RunWithContext(...)` controls bot runtime lifecycle and graceful shutdown.
|
||||
- `tgapi` already supports context-aware methods.
|
||||
- Regular handlers do not receive a first-class request-scoped `context.Context`.
|
||||
|
||||
Why this matters:
|
||||
|
||||
- Handler business logic often needs cancellation-aware database calls, HTTP calls, or downstream service calls.
|
||||
- The framework already has a good runtime cancellation story, but it does not flow naturally into user code inside handlers.
|
||||
- In modern Go APIs, `context.Context` is a standard part of operational correctness.
|
||||
|
||||
What is missing:
|
||||
|
||||
- A clean request-scoped context that follows each update through handler execution.
|
||||
- A standard way for application code to stop work when the bot is shutting down or the update processing context is canceled.
|
||||
- A direct bridge between bot lifecycle control and service-layer cancellation.
|
||||
|
||||
Possible API direction:
|
||||
|
||||
- Prefer a non-breaking approach by exposing context through `MsgContext`, for example `ctx.Context()`.
|
||||
- Build the context from the update-processing lifecycle so it is meaningful during graceful shutdown.
|
||||
- Make it natural to pass that context into database methods, HTTP clients, and `tgapi.WithContext(...)` calls.
|
||||
|
||||
Why this should probably not be a signature change:
|
||||
|
||||
- Changing handler signatures to accept `context.Context` directly would be a public breaking change.
|
||||
- A `MsgContext` accessor would preserve compatibility while still giving handlers an idiomatic Go cancellation path.
|
||||
|
||||
Practical target:
|
||||
|
||||
- Let handler code participate naturally in cancellation and graceful shutdown without forcing users to invent their own context plumbing.
|
||||
|
||||
Related pages:
|
||||
|
||||
- [[Scenes]]
|
||||
- [[MsgContext]]
|
||||
- [[Bot-Lifecycle]]
|
||||
- [[Migration]]
|
||||
+8
@@ -23,6 +23,7 @@ Use this wiki as the structured companion to the README: start with setup, then
|
||||
- [[Runners]]
|
||||
- [[Error-Handling]]
|
||||
- [[Logging]]
|
||||
- [[Scenes]]
|
||||
|
||||
## Telegram API and Interaction
|
||||
- [[Inline-Keyboards-and-Payloads]]
|
||||
@@ -94,3 +95,10 @@ If these are prioritized, the most useful order is:
|
||||
- [[FAQ]]
|
||||
- [[Semver-and-Releases]]
|
||||
- [[Page-Priority]]
|
||||
|
||||
## Additional topics
|
||||
- [[Drafts]]
|
||||
- [[Framework-Backlog]]
|
||||
- [[Localization]]
|
||||
- [[Rate-Limiting]]
|
||||
- [[Recipes]]
|
||||
|
||||
@@ -24,10 +24,18 @@ This page tracks maintenance priority for the wiki now that the core page set is
|
||||
- [[Error-Handling]]
|
||||
- [[Logging]]
|
||||
- [[Testing-Bots-with-Laniakea]]
|
||||
- [[Scenes]]
|
||||
- [[Migration]]
|
||||
- [[FAQ]]
|
||||
|
||||
## Priority 3
|
||||
- [[Drafts]]
|
||||
- [[Framework-Backlog]]
|
||||
- [[Localization]]
|
||||
- [[Rate-Limiting]]
|
||||
- [[Recipes]]
|
||||
|
||||
## Priority 4
|
||||
- [[Semver-and-Releases]]
|
||||
|
||||
## Notes
|
||||
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
# Scenes
|
||||
|
||||
DRAFT: эта страница описывает предлагаемую модель сцен и пока не соответствует реализованному API.
|
||||
|
||||
Сцены — это планируемый stateful-слой маршрутизации для долгоживущих диалогов в Laniakea. Он должен располагаться поверх обычной маршрутизации команд и payload: если пользователь или чат находится внутри активной сцены, сцена получает update первой и решает, обработать его, продолжить состояние, выйти или передать управление обратно обычному plugin flow.
|
||||
|
||||
## Зачем нужны сцены
|
||||
|
||||
- Команды и payload хорошо подходят как точки входа, но их недостаточно для многошаговых и модальных сценариев.
|
||||
- Реальным ботам часто нужен режим "оставайся в этом состоянии, пока пользователь явно не выйдет".
|
||||
- У фреймворка уже есть подходящие строительные блоки: `MsgContext`, middleware, plugins, typed input binding и request-scoped context.
|
||||
- Модель сцен должна переиспользовать эти части, а не создавать вторую независимую модель выполнения.
|
||||
|
||||
## Цели дизайна
|
||||
|
||||
- Сцены должны быть опциональными и additve.
|
||||
- Plugins должны оставаться основной единицей регистрации.
|
||||
- `MsgContext` должен оставаться базовым контекстом для обычных handlers.
|
||||
- Нужно покрыть и пошаговые flow, и модальные chat loops.
|
||||
- Нужно явно различать сессии в личке и в чате.
|
||||
- Локальные stop/escape-команды сцены должны быть first-class механизмом.
|
||||
|
||||
## Ментальная модель
|
||||
|
||||
- Команды и payload маршрутизируются по trigger.
|
||||
- Сцены маршрутизируются по активному состоянию.
|
||||
- Команда вроде `/rpstart` входит в сцену.
|
||||
- Пока сцена активна, обычные сообщения сначала уходят в сцену.
|
||||
- Локальные команды сцены вроде `/rpstop` парсятся внутри активной сцены.
|
||||
- Если сцена не хочет обрабатывать update, она может передать управление обратно стандартной маршрутизации.
|
||||
|
||||
## Область действия сессии
|
||||
|
||||
Сессионная модель не должна предполагать, что "один пользователь" всегда является правильной единицей. Для Telegram у личных чатов и групп разные потребности.
|
||||
|
||||
Предлагаемые scope:
|
||||
|
||||
- `SceneScopeUser`: одна сессия на пользователя во всех чатах.
|
||||
- `SceneScopeChat`: одна сессия на чат.
|
||||
- `SceneScopeUserChat`: одна сессия на пару `(user, chat)`.
|
||||
|
||||
Рекомендуемый default:
|
||||
|
||||
- Для большинства интерактивных сценариев использовать `SceneScopeUserChat`.
|
||||
- `SceneScopeUser` оставлять для редких account-level flow, которые специально должны жить между чатами.
|
||||
- `SceneScopeChat` использовать только для общих room-level сценариев.
|
||||
|
||||
## Предлагаемые типы
|
||||
|
||||
```go
|
||||
type SceneScope int
|
||||
|
||||
const (
|
||||
SceneScopeUser SceneScope = iota
|
||||
SceneScopeChat
|
||||
SceneScopeUserChat
|
||||
)
|
||||
|
||||
type SceneSession struct {
|
||||
Scene string
|
||||
Step string
|
||||
}
|
||||
|
||||
type SessionStore interface {
|
||||
Get(key string) (*SceneSession, error)
|
||||
Set(key string, session SceneSession) error
|
||||
Delete(key string) error
|
||||
}
|
||||
```
|
||||
|
||||
## Предлагаемая регистрация сцен
|
||||
|
||||
Сцены стоит регистрировать внутри plugin в том же стиле, что и команды с payload.
|
||||
|
||||
```go
|
||||
plugin.NewScene("rp").
|
||||
SetScope(laniakea.SceneScopeUserChat).
|
||||
SetEntry("chat").
|
||||
OnMessage(handleRPMessage).
|
||||
AddCommand("rpstop", stopRP)
|
||||
```
|
||||
|
||||
Так plugin API остаётся визуально согласованным:
|
||||
|
||||
- `NewCommand(...)`
|
||||
- `NewPayload(...)`
|
||||
- `AddUpdateHandler(...)`
|
||||
- `NewScene(...)`
|
||||
|
||||
## Режимы сцен
|
||||
|
||||
Для первой версии достаточно покрыть две самые частые формы.
|
||||
|
||||
- Пошаговые сцены: именованный step обрабатывает каждый update и выбирает следующий step.
|
||||
- Модальные сцены: долгоживущий "режим" обрабатывает обычные сообщения, пока пользователь явно не выйдет.
|
||||
|
||||
Пример пошагового flow:
|
||||
|
||||
```go
|
||||
plugin.NewScene("profile").
|
||||
SetScope(laniakea.SceneScopeUserChat).
|
||||
SetEntry("name").
|
||||
AddStep("name", askName).
|
||||
AddStep("confirm", confirmProfile)
|
||||
```
|
||||
|
||||
Пример модального flow:
|
||||
|
||||
```go
|
||||
plugin.NewScene("rp").
|
||||
SetScope(laniakea.SceneScopeUserChat).
|
||||
SetEntry("chat").
|
||||
OnMessage(handleRPMessage).
|
||||
AddCommand("rpstop", stopRP)
|
||||
```
|
||||
|
||||
## Предлагаемые контексты и результаты
|
||||
|
||||
Обычные команды должны и дальше использовать `*MsgContext`. Scene handlers лучше давать отдельный wrapper, который встраивает обычный контекст и добавляет state information.
|
||||
|
||||
```go
|
||||
type SceneContext struct {
|
||||
*MsgContext
|
||||
Scene string
|
||||
Step string
|
||||
}
|
||||
|
||||
type SceneResult interface{ isSceneResult() }
|
||||
|
||||
type SceneStay struct{}
|
||||
type SceneNext struct{ Step string }
|
||||
type SceneExit struct{}
|
||||
type ScenePass struct{}
|
||||
```
|
||||
|
||||
Возможные helper-методы на `SceneContext`:
|
||||
|
||||
- `ctx.Stay()`
|
||||
- `ctx.Next(step)`
|
||||
- `ctx.Exit()`
|
||||
- `ctx.Pass()`
|
||||
|
||||
## Предлагаемые сигнатуры handlers
|
||||
|
||||
Обычная команда входа:
|
||||
|
||||
```go
|
||||
func startRP(ctx *laniakea.MsgContext, db *App) error {
|
||||
return ctx.EnterScene("rp")
|
||||
}
|
||||
```
|
||||
|
||||
Scene message handler:
|
||||
|
||||
```go
|
||||
func handleRPMessage(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||
if ctx.Text == "" {
|
||||
return ctx.Pass(), nil
|
||||
}
|
||||
|
||||
// Передать сообщение ИИ-агенту и остаться внутри сцены.
|
||||
return ctx.Stay(), db.ReplyFromAgent(ctx.Context(), ctx.Text)
|
||||
}
|
||||
```
|
||||
|
||||
Локальная команда сцены:
|
||||
|
||||
```go
|
||||
func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||
ctx.Answer("RP mode disabled")
|
||||
return ctx.Exit(), nil
|
||||
}
|
||||
```
|
||||
|
||||
## Алгоритм маршрутизации
|
||||
|
||||
Роутер должен вести себя так:
|
||||
|
||||
1. Построить `MsgContext` для update.
|
||||
2. Вычислить session key на основе scope сцены и текущего update.
|
||||
3. Спросить `SessionStore`, есть ли активная сцена.
|
||||
4. Если активной сцены нет, продолжить обычную маршрутизацию command, payload и update handlers.
|
||||
5. Если сцена активна, сначала попробовать scene-local command или payload routing.
|
||||
6. Если локальный route не совпал, вызвать message handler или step handler сцены.
|
||||
7. Если сцена вернула `Stay`, сохранить текущую сессию без изменений.
|
||||
8. Если сцена вернула `Next(step)`, сохранить новый step.
|
||||
9. Если сцена вернула `Exit`, удалить сессию.
|
||||
10. Если сцена вернула `Pass`, продолжить обычную маршрутизацию.
|
||||
|
||||
## Важные решения по совместимости
|
||||
|
||||
- Вход в сцену должен происходить через явный API вроде `ctx.EnterScene(...)`.
|
||||
- Выход из сцены тоже должен быть явным.
|
||||
- Middleware должны продолжать работать на базовом `MsgContext`.
|
||||
- Scene handlers должны так же пользоваться `ctx.BindArgs(...)` и `ctx.Context()`.
|
||||
- Первая версия может поставляться с in-memory store плюс интерфейсом `SessionStore` для кастомного persistence.
|
||||
|
||||
## Открытые вопросы
|
||||
|
||||
- Нужно ли добавлять scene-local payloads уже в первой версии или отложить на вторую?
|
||||
- Должен ли `SceneContext` напрямую предоставлять helpers для scene-session storage?
|
||||
- Стоит ли поставлять `MemorySessionStore` по умолчанию, или сцены должны требовать явный store?
|
||||
- Должны ли modal scenes иметь явный fallback mode вроде "pass to normal routing" против "consume silently"?
|
||||
|
||||
## Рекомендуемый первый срез реализации
|
||||
|
||||
- Добавить `Scene`, `SceneContext`, `SceneSession` и `SessionStore`.
|
||||
- Добавить in-memory session store по умолчанию.
|
||||
- Добавить `Plugin.NewScene(...)`.
|
||||
- Добавить `MsgContext.EnterScene(...)`, `ExitScene(...)` и `CurrentScene(...)`.
|
||||
- Маршрутизировать активные сцены раньше обычного command/payload flow.
|
||||
- Поддержать scene-local commands и `OnMessage(...)`.
|
||||
|
||||
Связанные страницы:
|
||||
|
||||
- [[Commands-and-Plugins]]
|
||||
- [[MsgContext]]
|
||||
- [[Middleware]]
|
||||
- [[Bot-Lifecycle]]
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
# Scenes
|
||||
|
||||
DRAFT: this page describes the proposed scene model and is not implemented yet.
|
||||
|
||||
Scenes are the planned stateful routing layer for long-lived interactions in Laniakea. They are meant to sit above command and payload routing: if a user or chat is inside an active scene, the scene gets the update first and decides whether to handle it, continue, exit, or pass control back to the normal plugin flow.
|
||||
|
||||
## Why scenes exist
|
||||
|
||||
- Commands and payloads are good entry points, but they are not enough for multi-step or modal flows.
|
||||
- Real bots often need "stay in this mode until the user exits" behavior.
|
||||
- The current framework already has the right building blocks: `MsgContext`, middleware, plugins, typed input binding, and request-scoped context.
|
||||
- A scene model should reuse those pieces instead of creating a second execution model.
|
||||
|
||||
## Design goals
|
||||
|
||||
- Keep scenes optional and additive.
|
||||
- Preserve plugins as the primary registration unit.
|
||||
- Keep `MsgContext` as the base context for normal handlers.
|
||||
- Support both step-based flows and modal chat loops.
|
||||
- Distinguish private-chat and group-chat sessions explicitly.
|
||||
- Make scene-local stop/escape commands first-class.
|
||||
|
||||
## Mental model
|
||||
|
||||
- Commands and payloads route by trigger.
|
||||
- Scenes route by active state.
|
||||
- A command such as `/rpstart` enters a scene.
|
||||
- Once the scene is active, regular messages are routed to the scene first.
|
||||
- Scene-local commands such as `/rpstop` are parsed inside the active scene.
|
||||
- If the scene does not want the update, it can pass control back to normal routing.
|
||||
|
||||
## Session scope
|
||||
|
||||
Scene sessions should not assume that "one user" is always the right unit. Telegram private chats and group chats need different defaults.
|
||||
|
||||
Proposed scopes:
|
||||
|
||||
- `SceneScopeUser`: one session per user across all chats.
|
||||
- `SceneScopeChat`: one session per chat.
|
||||
- `SceneScopeUserChat`: one session per `(user, chat)` pair.
|
||||
|
||||
Recommended default:
|
||||
|
||||
- Use `SceneScopeUserChat` for most interactive flows.
|
||||
- Reserve `SceneScopeUser` for rare account-level flows that intentionally cross chats.
|
||||
- Use `SceneScopeChat` only for room-level shared workflows.
|
||||
|
||||
## Proposed types
|
||||
|
||||
```go
|
||||
type SceneScope int
|
||||
|
||||
const (
|
||||
SceneScopeUser SceneScope = iota
|
||||
SceneScopeChat
|
||||
SceneScopeUserChat
|
||||
)
|
||||
|
||||
type SceneSession struct {
|
||||
Scene string
|
||||
Step string
|
||||
}
|
||||
|
||||
type SessionStore interface {
|
||||
Get(key string) (*SceneSession, error)
|
||||
Set(key string, session SceneSession) error
|
||||
Delete(key string) error
|
||||
}
|
||||
```
|
||||
|
||||
## Proposed scene registration
|
||||
|
||||
Scenes should be registered inside plugins in the same style as commands and payloads.
|
||||
|
||||
```go
|
||||
plugin.NewScene("rp").
|
||||
SetScope(laniakea.SceneScopeUserChat).
|
||||
SetEntry("chat").
|
||||
OnMessage(handleRPMessage).
|
||||
AddCommand("rpstop", stopRP)
|
||||
```
|
||||
|
||||
This keeps the plugin API visually consistent:
|
||||
|
||||
- `NewCommand(...)`
|
||||
- `NewPayload(...)`
|
||||
- `AddUpdateHandler(...)`
|
||||
- `NewScene(...)`
|
||||
|
||||
## Scene modes
|
||||
|
||||
The first version should support two common shapes.
|
||||
|
||||
- Step-based scenes: a named step handles each update and decides the next step.
|
||||
- Modal scenes: a long-lived "mode" handles ordinary messages until an explicit exit command ends it.
|
||||
|
||||
Step-based flow example:
|
||||
|
||||
```go
|
||||
plugin.NewScene("profile").
|
||||
SetScope(laniakea.SceneScopeUserChat).
|
||||
SetEntry("name").
|
||||
AddStep("name", askName).
|
||||
AddStep("confirm", confirmProfile)
|
||||
```
|
||||
|
||||
Modal flow example:
|
||||
|
||||
```go
|
||||
plugin.NewScene("rp").
|
||||
SetScope(laniakea.SceneScopeUserChat).
|
||||
SetEntry("chat").
|
||||
OnMessage(handleRPMessage).
|
||||
AddCommand("rpstop", stopRP)
|
||||
```
|
||||
|
||||
## Proposed contexts and results
|
||||
|
||||
Normal commands should still use `*MsgContext`. Scene handlers should use a scene-specific wrapper that embeds the normal context and adds state information.
|
||||
|
||||
```go
|
||||
type SceneContext struct {
|
||||
*MsgContext
|
||||
Scene string
|
||||
Step string
|
||||
}
|
||||
|
||||
type SceneResult interface{ isSceneResult() }
|
||||
|
||||
type SceneStay struct{}
|
||||
type SceneNext struct{ Step string }
|
||||
type SceneExit struct{}
|
||||
type ScenePass struct{}
|
||||
```
|
||||
|
||||
Possible helper methods on `SceneContext`:
|
||||
|
||||
- `ctx.Stay()`
|
||||
- `ctx.Next(step)`
|
||||
- `ctx.Exit()`
|
||||
- `ctx.Pass()`
|
||||
|
||||
## Proposed handler shapes
|
||||
|
||||
Normal command entry:
|
||||
|
||||
```go
|
||||
func startRP(ctx *laniakea.MsgContext, db *App) error {
|
||||
return ctx.EnterScene("rp")
|
||||
}
|
||||
```
|
||||
|
||||
Scene message handler:
|
||||
|
||||
```go
|
||||
func handleRPMessage(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||
if ctx.Text == "" {
|
||||
return ctx.Pass(), nil
|
||||
}
|
||||
|
||||
// Send the message to the AI agent and stay inside the scene.
|
||||
return ctx.Stay(), db.ReplyFromAgent(ctx.Context(), ctx.Text)
|
||||
}
|
||||
```
|
||||
|
||||
Scene-local command:
|
||||
|
||||
```go
|
||||
func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||
ctx.Answer("RP mode disabled")
|
||||
return ctx.Exit(), nil
|
||||
}
|
||||
```
|
||||
|
||||
## Routing algorithm
|
||||
|
||||
The router should behave like this:
|
||||
|
||||
1. Build `MsgContext` for the update.
|
||||
2. Compute the session key from scene scope and the current update.
|
||||
3. Ask `SessionStore` whether an active scene exists.
|
||||
4. If no scene is active, continue normal command, payload, and update routing.
|
||||
5. If a scene is active, try scene-local command or payload routing first.
|
||||
6. If no scene-local route matches, try the scene message or step handler.
|
||||
7. If the scene returns `Stay`, keep the current session.
|
||||
8. If the scene returns `Next(step)`, persist the new step.
|
||||
9. If the scene returns `Exit`, delete the session.
|
||||
10. If the scene returns `Pass`, continue normal routing.
|
||||
|
||||
## Important compatibility decisions
|
||||
|
||||
- Scene entry should happen through explicit APIs such as `ctx.EnterScene(...)`.
|
||||
- Scene exit should also be explicit.
|
||||
- Middleware should still run on the base `MsgContext`.
|
||||
- Scene handlers should continue to benefit from `ctx.BindArgs(...)` and `ctx.Context()`.
|
||||
- The first version can ship with an in-memory store plus a `SessionStore` interface for custom persistence.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Should scene-local payloads be added in the first version or the second?
|
||||
- Should `SceneContext` expose scene-session storage helpers directly?
|
||||
- Should the framework ship with a default `MemorySessionStore`, or should scenes require an explicit store?
|
||||
- Should modal scenes have an explicit fallback mode such as "pass to normal routing" versus "consume silently"?
|
||||
|
||||
## Recommended first implementation slice
|
||||
|
||||
- Add `Scene`, `SceneContext`, `SceneSession`, and `SessionStore`.
|
||||
- Add a default in-memory session store.
|
||||
- Add `Plugin.NewScene(...)`.
|
||||
- Add `MsgContext.EnterScene(...)`, `ExitScene(...)`, and `CurrentScene(...)`.
|
||||
- Route active scenes before normal command and payload flow.
|
||||
- Support scene-local commands and `OnMessage(...)`.
|
||||
|
||||
Related pages:
|
||||
|
||||
- [[Commands-and-Plugins]]
|
||||
- [[MsgContext]]
|
||||
- [[Middleware]]
|
||||
- [[Bot-Lifecycle]]
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Navigation
|
||||
|
||||
## Start here
|
||||
- [[Home]]
|
||||
- [[Getting-Started]]
|
||||
- [[Commands-and-Plugins]]
|
||||
- [[MsgContext]]
|
||||
|
||||
## Core API
|
||||
- [[Inline-Keyboards-and-Payloads]]
|
||||
- [[tgapi-Overview]]
|
||||
- [[Bot-Lifecycle]]
|
||||
- [[Middleware]]
|
||||
- [[Scenes]]
|
||||
|
||||
## Changes and troubleshooting
|
||||
- [[Migration]]
|
||||
- [[FAQ]]
|
||||
|
||||
## Additional topics
|
||||
- [[Drafts]]
|
||||
- [[Framework-Backlog]]
|
||||
- [[Localization]]
|
||||
- [[Rate-Limiting]]
|
||||
- [[Recipes]]
|
||||
- [[Semver-and-Releases]]
|
||||
- [[Page-Priority]]
|
||||
|
||||
## Drafts and RU companion pages
|
||||
- [[Scenes-RU]]
|
||||
- [[Framework-Backlog-RU]]
|
||||
Reference in New Issue
Block a user