REPOSITORY / ScuroNeko/Laniakea
Wiki
Refresh scenes draft wiki
Clarify scene routing, entry steps, and local commands Document scene state storage and expose RU draft links in sidebar
+45
-3
@@ -13,17 +13,19 @@ DRAFT: эта страница описывает предлагаемую мо
|
||||
|
||||
## Цели дизайна
|
||||
|
||||
- Сцены должны быть опциональными и additve.
|
||||
- Сцены должны быть опциональными и additive.
|
||||
- Plugins должны оставаться основной единицей регистрации.
|
||||
- `MsgContext` должен оставаться базовым контекстом для обычных handlers.
|
||||
- Нужно покрыть и пошаговые flow, и модальные chat loops.
|
||||
- Нужно явно различать сессии в личке и в чате.
|
||||
- Локальные stop/escape-команды сцены должны быть first-class механизмом.
|
||||
- Нужен встроенный способ хранить состояние сцены между шагами.
|
||||
|
||||
## Ментальная модель
|
||||
|
||||
- Команды и payload маршрутизируются по trigger.
|
||||
- Сцены маршрутизируются по активному состоянию.
|
||||
- Сцены являются отдельным слоем маршрутизации поверх commands, payloads и обычных update handlers.
|
||||
- Команда вроде `/rpstart` входит в сцену.
|
||||
- Пока сцена активна, обычные сообщения сначала уходят в сцену.
|
||||
- Локальные команды сцены вроде `/rpstop` парсятся внутри активной сцены.
|
||||
@@ -59,6 +61,7 @@ const (
|
||||
type SceneSession struct {
|
||||
Scene string
|
||||
Step string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type SessionStore interface {
|
||||
@@ -80,6 +83,8 @@ plugin.NewScene("rp").
|
||||
AddCommand("rpstop", stopRP)
|
||||
```
|
||||
|
||||
`SetEntry(...)` задаёт начальный step или state сцены. Когда `ctx.EnterScene("rp")` создаёт новую сессию, фреймворк записывает это значение в `SceneSession.Step`.
|
||||
|
||||
Так plugin API остаётся визуально согласованным:
|
||||
|
||||
- `NewCommand(...)`
|
||||
@@ -93,6 +98,7 @@ plugin.NewScene("rp").
|
||||
|
||||
- Пошаговые сцены: именованный step обрабатывает каждый update и выбирает следующий step.
|
||||
- Модальные сцены: долгоживущий "режим" обрабатывает обычные сообщения, пока пользователь явно не выйдет.
|
||||
- Обе формы должны уметь регистрировать локальные команды сцены.
|
||||
|
||||
Пример пошагового flow:
|
||||
|
||||
@@ -172,6 +178,41 @@ func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||
}
|
||||
```
|
||||
|
||||
## Состояние между шагами
|
||||
|
||||
Для реальных сценариев недостаточно хранить только `Scene` и `Step`. Обычно между update нужно накапливать данные: черновик профиля, выбранные параметры, временные ID и другие промежуточные значения.
|
||||
|
||||
Это не стоит полностью перекладывать на разработчика приложения. Фреймворк должен давать слот для хранения в `SceneSession` и удобные helper-методы на `SceneContext`.
|
||||
|
||||
Предлагаемый helper API:
|
||||
|
||||
- `ctx.BindState(&dst)`
|
||||
- `ctx.SaveState(src)`
|
||||
- `ctx.ClearState()`
|
||||
|
||||
Пример:
|
||||
|
||||
```go
|
||||
type ProfileDraft struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||
var draft ProfileDraft
|
||||
_ = ctx.BindState(&draft)
|
||||
|
||||
draft.Name = ctx.Text
|
||||
if err := ctx.SaveState(draft); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ctx.Next("age"), nil
|
||||
}
|
||||
```
|
||||
|
||||
Использование `Data []byte` в `SceneSession` делает контракт `SessionStore` маленьким и не завязанным на конкретный формат хранения. В первой реализации scene helpers могут работать через JSON.
|
||||
|
||||
## Алгоритм маршрутизации
|
||||
|
||||
Роутер должен вести себя так:
|
||||
@@ -183,7 +224,7 @@ func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||
5. Если сцена активна, сначала попробовать scene-local command или payload routing.
|
||||
6. Если локальный route не совпал, вызвать message handler или step handler сцены.
|
||||
7. Если сцена вернула `Stay`, сохранить текущую сессию без изменений.
|
||||
8. Если сцена вернула `Next(step)`, сохранить новый step.
|
||||
8. Если сцена вернула `Next(step)`, сохранить новый step и оставить `Data`.
|
||||
9. Если сцена вернула `Exit`, удалить сессию.
|
||||
10. Если сцена вернула `Pass`, продолжить обычную маршрутизацию.
|
||||
|
||||
@@ -194,11 +235,11 @@ func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||
- Middleware должны продолжать работать на базовом `MsgContext`.
|
||||
- Scene handlers должны так же пользоваться `ctx.BindArgs(...)` и `ctx.Context()`.
|
||||
- Первая версия может поставляться с in-memory store плюс интерфейсом `SessionStore` для кастомного persistence.
|
||||
- Helpers для сериализации состояния должны входить в первый scene API, а не оставаться приложенческим boilerplate.
|
||||
|
||||
## Открытые вопросы
|
||||
|
||||
- Нужно ли добавлять scene-local payloads уже в первой версии или отложить на вторую?
|
||||
- Должен ли `SceneContext` напрямую предоставлять helpers для scene-session storage?
|
||||
- Стоит ли поставлять `MemorySessionStore` по умолчанию, или сцены должны требовать явный store?
|
||||
- Должны ли modal scenes иметь явный fallback mode вроде "pass to normal routing" против "consume silently"?
|
||||
|
||||
@@ -210,6 +251,7 @@ func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||
- Добавить `MsgContext.EnterScene(...)`, `ExitScene(...)` и `CurrentScene(...)`.
|
||||
- Маршрутизировать активные сцены раньше обычного command/payload flow.
|
||||
- Поддержать scene-local commands и `OnMessage(...)`.
|
||||
- Поддержать helpers для состояния сцены поверх `SceneSession.Data`.
|
||||
|
||||
Связанные страницы:
|
||||
|
||||
|
||||
+44
-2
@@ -19,11 +19,13 @@ Scenes are the planned stateful routing layer for long-lived interactions in Lan
|
||||
- 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.
|
||||
- Provide a built-in way to persist scene state between steps.
|
||||
|
||||
## Mental model
|
||||
|
||||
- Commands and payloads route by trigger.
|
||||
- Scenes route by active state.
|
||||
- Scenes are a separate routing layer above commands, payloads, and generic update handlers.
|
||||
- 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.
|
||||
@@ -59,6 +61,7 @@ const (
|
||||
type SceneSession struct {
|
||||
Scene string
|
||||
Step string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type SessionStore interface {
|
||||
@@ -80,6 +83,8 @@ plugin.NewScene("rp").
|
||||
AddCommand("rpstop", stopRP)
|
||||
```
|
||||
|
||||
`SetEntry(...)` defines the initial step or state of the scene. When `ctx.EnterScene("rp")` creates a new session, the framework writes that entry step into `SceneSession.Step`.
|
||||
|
||||
This keeps the plugin API visually consistent:
|
||||
|
||||
- `NewCommand(...)`
|
||||
@@ -93,6 +98,7 @@ 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.
|
||||
- Both shapes should still be able to register scene-local commands.
|
||||
|
||||
Step-based flow example:
|
||||
|
||||
@@ -172,6 +178,41 @@ func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||
}
|
||||
```
|
||||
|
||||
## State between steps
|
||||
|
||||
Scenes need more than just `Scene` and `Step`. Real flows also accumulate data between updates, such as a draft profile, selected options, or temporary IDs.
|
||||
|
||||
That state should not be pushed entirely onto the application author. The framework should provide a storage slot inside `SceneSession` plus ergonomic helpers on `SceneContext`.
|
||||
|
||||
Proposed helper API:
|
||||
|
||||
- `ctx.BindState(&dst)`
|
||||
- `ctx.SaveState(src)`
|
||||
- `ctx.ClearState()`
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
type ProfileDraft struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||
var draft ProfileDraft
|
||||
_ = ctx.BindState(&draft)
|
||||
|
||||
draft.Name = ctx.Text
|
||||
if err := ctx.SaveState(draft); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ctx.Next("age"), nil
|
||||
}
|
||||
```
|
||||
|
||||
Using `Data []byte` in `SceneSession` keeps the `SessionStore` contract small and storage-agnostic. The scene helpers can use JSON for the first implementation.
|
||||
|
||||
## Routing algorithm
|
||||
|
||||
The router should behave like this:
|
||||
@@ -183,7 +224,7 @@ The router should behave like this:
|
||||
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.
|
||||
8. If the scene returns `Next(step)`, persist the new step and keep `Data`.
|
||||
9. If the scene returns `Exit`, delete the session.
|
||||
10. If the scene returns `Pass`, continue normal routing.
|
||||
|
||||
@@ -194,11 +235,11 @@ The router should behave like this:
|
||||
- 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.
|
||||
- State serialization helpers should be part of the first scene API, not left as app-specific boilerplate.
|
||||
|
||||
## 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"?
|
||||
|
||||
@@ -210,6 +251,7 @@ The router should behave like this:
|
||||
- Add `MsgContext.EnterScene(...)`, `ExitScene(...)`, and `CurrentScene(...)`.
|
||||
- Route active scenes before normal command and payload flow.
|
||||
- Support scene-local commands and `OnMessage(...)`.
|
||||
- Support scene-state helpers backed by `SceneSession.Data`.
|
||||
|
||||
Related pages:
|
||||
|
||||
|
||||
+6
-3
@@ -12,6 +12,7 @@
|
||||
- [[Bot-Lifecycle]]
|
||||
- [[Middleware]]
|
||||
- [[Scenes]]
|
||||
- [[Scenes-RU|Scenes (RU)]]
|
||||
|
||||
## Changes and troubleshooting
|
||||
- [[Migration]]
|
||||
@@ -26,6 +27,8 @@
|
||||
- [[Semver-and-Releases]]
|
||||
- [[Page-Priority]]
|
||||
|
||||
## Drafts and RU companion pages
|
||||
- [[Scenes-RU]]
|
||||
- [[Framework-Backlog-RU]]
|
||||
## Drafts
|
||||
- [[Framework-Backlog]]
|
||||
- [[Scenes]]
|
||||
- [[Framework-Backlog-RU|Framework Backlog (RU)]]
|
||||
- [[Scenes-RU|Scenes (RU)]]
|
||||
|
||||
Reference in New Issue
Block a user