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 должны оставаться основной единицей регистрации.
|
- Plugins должны оставаться основной единицей регистрации.
|
||||||
- `MsgContext` должен оставаться базовым контекстом для обычных handlers.
|
- `MsgContext` должен оставаться базовым контекстом для обычных handlers.
|
||||||
- Нужно покрыть и пошаговые flow, и модальные chat loops.
|
- Нужно покрыть и пошаговые flow, и модальные chat loops.
|
||||||
- Нужно явно различать сессии в личке и в чате.
|
- Нужно явно различать сессии в личке и в чате.
|
||||||
- Локальные stop/escape-команды сцены должны быть first-class механизмом.
|
- Локальные stop/escape-команды сцены должны быть first-class механизмом.
|
||||||
|
- Нужен встроенный способ хранить состояние сцены между шагами.
|
||||||
|
|
||||||
## Ментальная модель
|
## Ментальная модель
|
||||||
|
|
||||||
- Команды и payload маршрутизируются по trigger.
|
- Команды и payload маршрутизируются по trigger.
|
||||||
- Сцены маршрутизируются по активному состоянию.
|
- Сцены маршрутизируются по активному состоянию.
|
||||||
|
- Сцены являются отдельным слоем маршрутизации поверх commands, payloads и обычных update handlers.
|
||||||
- Команда вроде `/rpstart` входит в сцену.
|
- Команда вроде `/rpstart` входит в сцену.
|
||||||
- Пока сцена активна, обычные сообщения сначала уходят в сцену.
|
- Пока сцена активна, обычные сообщения сначала уходят в сцену.
|
||||||
- Локальные команды сцены вроде `/rpstop` парсятся внутри активной сцены.
|
- Локальные команды сцены вроде `/rpstop` парсятся внутри активной сцены.
|
||||||
@@ -59,6 +61,7 @@ const (
|
|||||||
type SceneSession struct {
|
type SceneSession struct {
|
||||||
Scene string
|
Scene string
|
||||||
Step string
|
Step string
|
||||||
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionStore interface {
|
type SessionStore interface {
|
||||||
@@ -80,6 +83,8 @@ plugin.NewScene("rp").
|
|||||||
AddCommand("rpstop", stopRP)
|
AddCommand("rpstop", stopRP)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`SetEntry(...)` задаёт начальный step или state сцены. Когда `ctx.EnterScene("rp")` создаёт новую сессию, фреймворк записывает это значение в `SceneSession.Step`.
|
||||||
|
|
||||||
Так plugin API остаётся визуально согласованным:
|
Так plugin API остаётся визуально согласованным:
|
||||||
|
|
||||||
- `NewCommand(...)`
|
- `NewCommand(...)`
|
||||||
@@ -93,6 +98,7 @@ plugin.NewScene("rp").
|
|||||||
|
|
||||||
- Пошаговые сцены: именованный step обрабатывает каждый update и выбирает следующий step.
|
- Пошаговые сцены: именованный step обрабатывает каждый update и выбирает следующий step.
|
||||||
- Модальные сцены: долгоживущий "режим" обрабатывает обычные сообщения, пока пользователь явно не выйдет.
|
- Модальные сцены: долгоживущий "режим" обрабатывает обычные сообщения, пока пользователь явно не выйдет.
|
||||||
|
- Обе формы должны уметь регистрировать локальные команды сцены.
|
||||||
|
|
||||||
Пример пошагового flow:
|
Пример пошагового 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.
|
5. Если сцена активна, сначала попробовать scene-local command или payload routing.
|
||||||
6. Если локальный route не совпал, вызвать message handler или step handler сцены.
|
6. Если локальный route не совпал, вызвать message handler или step handler сцены.
|
||||||
7. Если сцена вернула `Stay`, сохранить текущую сессию без изменений.
|
7. Если сцена вернула `Stay`, сохранить текущую сессию без изменений.
|
||||||
8. Если сцена вернула `Next(step)`, сохранить новый step.
|
8. Если сцена вернула `Next(step)`, сохранить новый step и оставить `Data`.
|
||||||
9. Если сцена вернула `Exit`, удалить сессию.
|
9. Если сцена вернула `Exit`, удалить сессию.
|
||||||
10. Если сцена вернула `Pass`, продолжить обычную маршрутизацию.
|
10. Если сцена вернула `Pass`, продолжить обычную маршрутизацию.
|
||||||
|
|
||||||
@@ -194,11 +235,11 @@ func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
|||||||
- Middleware должны продолжать работать на базовом `MsgContext`.
|
- Middleware должны продолжать работать на базовом `MsgContext`.
|
||||||
- Scene handlers должны так же пользоваться `ctx.BindArgs(...)` и `ctx.Context()`.
|
- Scene handlers должны так же пользоваться `ctx.BindArgs(...)` и `ctx.Context()`.
|
||||||
- Первая версия может поставляться с in-memory store плюс интерфейсом `SessionStore` для кастомного persistence.
|
- Первая версия может поставляться с in-memory store плюс интерфейсом `SessionStore` для кастомного persistence.
|
||||||
|
- Helpers для сериализации состояния должны входить в первый scene API, а не оставаться приложенческим boilerplate.
|
||||||
|
|
||||||
## Открытые вопросы
|
## Открытые вопросы
|
||||||
|
|
||||||
- Нужно ли добавлять scene-local payloads уже в первой версии или отложить на вторую?
|
- Нужно ли добавлять scene-local payloads уже в первой версии или отложить на вторую?
|
||||||
- Должен ли `SceneContext` напрямую предоставлять helpers для scene-session storage?
|
|
||||||
- Стоит ли поставлять `MemorySessionStore` по умолчанию, или сцены должны требовать явный store?
|
- Стоит ли поставлять `MemorySessionStore` по умолчанию, или сцены должны требовать явный store?
|
||||||
- Должны ли modal scenes иметь явный fallback mode вроде "pass to normal routing" против "consume silently"?
|
- Должны ли 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(...)`.
|
- Добавить `MsgContext.EnterScene(...)`, `ExitScene(...)` и `CurrentScene(...)`.
|
||||||
- Маршрутизировать активные сцены раньше обычного command/payload flow.
|
- Маршрутизировать активные сцены раньше обычного command/payload flow.
|
||||||
- Поддержать scene-local commands и `OnMessage(...)`.
|
- Поддержать 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.
|
- Support both step-based flows and modal chat loops.
|
||||||
- Distinguish private-chat and group-chat sessions explicitly.
|
- Distinguish private-chat and group-chat sessions explicitly.
|
||||||
- Make scene-local stop/escape commands first-class.
|
- Make scene-local stop/escape commands first-class.
|
||||||
|
- Provide a built-in way to persist scene state between steps.
|
||||||
|
|
||||||
## Mental model
|
## Mental model
|
||||||
|
|
||||||
- Commands and payloads route by trigger.
|
- Commands and payloads route by trigger.
|
||||||
- Scenes route by active state.
|
- 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.
|
- A command such as `/rpstart` enters a scene.
|
||||||
- Once the scene is active, regular messages are routed to the scene first.
|
- 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.
|
- Scene-local commands such as `/rpstop` are parsed inside the active scene.
|
||||||
@@ -59,6 +61,7 @@ const (
|
|||||||
type SceneSession struct {
|
type SceneSession struct {
|
||||||
Scene string
|
Scene string
|
||||||
Step string
|
Step string
|
||||||
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionStore interface {
|
type SessionStore interface {
|
||||||
@@ -80,6 +83,8 @@ plugin.NewScene("rp").
|
|||||||
AddCommand("rpstop", stopRP)
|
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:
|
This keeps the plugin API visually consistent:
|
||||||
|
|
||||||
- `NewCommand(...)`
|
- `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.
|
- 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.
|
- 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:
|
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
|
## Routing algorithm
|
||||||
|
|
||||||
The router should behave like this:
|
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.
|
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.
|
6. If no scene-local route matches, try the scene message or step handler.
|
||||||
7. If the scene returns `Stay`, keep the current session.
|
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.
|
9. If the scene returns `Exit`, delete the session.
|
||||||
10. If the scene returns `Pass`, continue normal routing.
|
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`.
|
- Middleware should still run on the base `MsgContext`.
|
||||||
- Scene handlers should continue to benefit from `ctx.BindArgs(...)` and `ctx.Context()`.
|
- 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.
|
- 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
|
## Open questions
|
||||||
|
|
||||||
- Should scene-local payloads be added in the first version or the second?
|
- 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 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"?
|
- 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(...)`.
|
- Add `MsgContext.EnterScene(...)`, `ExitScene(...)`, and `CurrentScene(...)`.
|
||||||
- Route active scenes before normal command and payload flow.
|
- Route active scenes before normal command and payload flow.
|
||||||
- Support scene-local commands and `OnMessage(...)`.
|
- Support scene-local commands and `OnMessage(...)`.
|
||||||
|
- Support scene-state helpers backed by `SceneSession.Data`.
|
||||||
|
|
||||||
Related pages:
|
Related pages:
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -12,6 +12,7 @@
|
|||||||
- [[Bot-Lifecycle]]
|
- [[Bot-Lifecycle]]
|
||||||
- [[Middleware]]
|
- [[Middleware]]
|
||||||
- [[Scenes]]
|
- [[Scenes]]
|
||||||
|
- [[Scenes-RU|Scenes (RU)]]
|
||||||
|
|
||||||
## Changes and troubleshooting
|
## Changes and troubleshooting
|
||||||
- [[Migration]]
|
- [[Migration]]
|
||||||
@@ -26,6 +27,8 @@
|
|||||||
- [[Semver-and-Releases]]
|
- [[Semver-and-Releases]]
|
||||||
- [[Page-Priority]]
|
- [[Page-Priority]]
|
||||||
|
|
||||||
## Drafts and RU companion pages
|
## Drafts
|
||||||
- [[Scenes-RU]]
|
- [[Framework-Backlog]]
|
||||||
- [[Framework-Backlog-RU]]
|
- [[Scenes]]
|
||||||
|
- [[Framework-Backlog-RU|Framework Backlog (RU)]]
|
||||||
|
- [[Scenes-RU|Scenes (RU)]]
|
||||||
|
|||||||
Reference in New Issue
Block a user