REPOSITORY / ScuroNeko/Laniakea
Wiki
Refresh scene wiki status
Align Scenes pages with current API Mark scene backlog as work in progress
+19
-14
@@ -10,7 +10,7 @@ Current state:
|
|||||||
|
|
||||||
- Фреймворк хорошо обрабатывает один update через commands, payloads, middleware и update handlers.
|
- Фреймворк хорошо обрабатывает один update через commands, payloads, middleware и update handlers.
|
||||||
- В нём уже есть полезные низкоуровневые строительные блоки: `MsgContext`, drafts, payload routing, plugins и update handlers.
|
- В нём уже есть полезные низкоуровневые строительные блоки: `MsgContext`, drafts, payload routing, plugins и update handlers.
|
||||||
- Но пока нет first-class концепции для долгоживущих пользовательских interaction flows.
|
- Теперь в нём уже есть work-in-progress skeleton для долгоживущих interaction flows: сцены можно регистрировать в plugins, запускать через `MsgContext`, сохранять через `SessionStore` и маршрутизировать раньше обычной обработки команд.
|
||||||
|
|
||||||
Why this matters:
|
Why this matters:
|
||||||
|
|
||||||
@@ -18,22 +18,27 @@ Why this matters:
|
|||||||
- Реальным ботам часто нужны концепции вроде "подождать следующее сообщение пользователя", "пользователь сейчас на шаге 3 из 5" или "нажатие кнопки переводит пользователя в следующее состояние сцены".
|
- Реальным ботам часто нужны концепции вроде "подождать следующее сообщение пользователя", "пользователь сейчас на шаге 3 из 5" или "нажатие кнопки переводит пользователя в следующее состояние сцены".
|
||||||
- Без scene model пользователи библиотеки начинают строить свой mini-framework поверх Laniakea.
|
- Без scene model пользователи библиотеки начинают строить свой mini-framework поверх Laniakea.
|
||||||
|
|
||||||
What is missing:
|
What is already present:
|
||||||
|
|
||||||
- Способ маршрутизировать updates в активную сцену до обычного command routing.
|
- Маршрутизация активной сцены раньше обычного command flow.
|
||||||
- Способ хранить conversation state на пользователя или чат.
|
- Session scopes на пользователя, чат и пару пользователь-чат.
|
||||||
- Способ описывать шаги и переходы без ручного state machine вокруг middleware и storage.
|
- Явный вход и выход через `MsgContext`.
|
||||||
- Способ явно входить в flow, продолжать его, отменять и завершать.
|
- Step handlers, scene-local commands и `OnMessage(...)`.
|
||||||
- Способ поддерживать modal chat flows, когда пользователь "внутри" сцены, а обычный текст считается scene input, пока явная escape-команда не завершит режим.
|
- In-memory session storage по умолчанию плюс интерфейс `SessionStore` для кастомного persistence.
|
||||||
|
|
||||||
Possible API direction:
|
What is still missing or not yet settled:
|
||||||
|
|
||||||
- Концепции `Scene`, `Step` и `SessionStore`.
|
- Scene-local payload routing.
|
||||||
- `bot.AddScene(...)` или отдельный scene registry.
|
- Ясное решение о том, нужен ли вообще дополнительный публичный API для inspection сцен.
|
||||||
- `ctx.Scene()`, `ctx.NextStep(...)`, `ctx.ExitScene()` или похожие helpers для state transitions.
|
- Более широкая стабилизация и документация вокруг helper-методов для scene state.
|
||||||
- Routing rule: active scene first, then normal command/payload flow if no scene claims the update.
|
|
||||||
|
Current API direction:
|
||||||
|
|
||||||
|
- `Scene`, `SceneContext`, `SceneSession` и `SessionStore`.
|
||||||
|
- `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
|
||||||
|
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`.
|
||||||
|
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`.
|
||||||
- Storage-backed состояние на пользователя или чат с чистым интерфейсом для кастомного persistence.
|
- Storage-backed состояние на пользователя или чат с чистым интерфейсом для кастомного persistence.
|
||||||
- Локальные escape и passthrough-команды сцены, чтобы flow вроде `/startrp` могли переводить пользователя в отдельный chat mode, где большинство сообщений уходят прямо в сцену, а команды вроде `/exit` или небольшой whitelist сохраняют специальное поведение.
|
|
||||||
|
|
||||||
Important design constraints:
|
Important design constraints:
|
||||||
|
|
||||||
@@ -43,7 +48,7 @@ Important design constraints:
|
|||||||
|
|
||||||
Practical target:
|
Practical target:
|
||||||
|
|
||||||
- Сделать stateful bot flows first-class framework-supported паттерном вместо userland convention.
|
- Достабилизировать текущий scene skeleton до состояния first-class framework-supported паттерна.
|
||||||
- Покрыть и step-based формы, и mode-based chat flows без необходимости строить пользовательские routing layers вокруг активных sessions.
|
- Покрыть и step-based формы, и mode-based chat flows без необходимости строить пользовательские routing layers вокруг активных sessions.
|
||||||
|
|
||||||
## Secondary Backlog
|
## Secondary Backlog
|
||||||
|
|||||||
+19
-14
@@ -10,7 +10,7 @@ Current state:
|
|||||||
|
|
||||||
- The framework is strong at handling a single update through commands, payloads, middleware, and update handlers.
|
- 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 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.
|
- It now provides a work-in-progress scene skeleton for long-lived user interaction flows: scenes can be registered in plugins, entered through `MsgContext`, persisted through `SessionStore`, and routed before normal command handling.
|
||||||
|
|
||||||
Why this matters:
|
Why this matters:
|
||||||
|
|
||||||
@@ -18,22 +18,27 @@ Why this matters:
|
|||||||
- 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".
|
- 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.
|
- Without a scene model, library users end up building their own mini-framework on top of Laniakea.
|
||||||
|
|
||||||
What is missing:
|
What is already present:
|
||||||
|
|
||||||
- A way to route updates to an active scene before normal command routing.
|
- Active-scene routing before normal command flow.
|
||||||
- A way to persist conversation state per user or per chat.
|
- Per-user, per-chat, and per-user-chat session scopes.
|
||||||
- A way to describe steps and transitions without hand-rolling state machines around middleware and storage.
|
- Explicit scene entry and exit through `MsgContext`.
|
||||||
- A way to enter, continue, cancel, and complete a conversation flow explicitly.
|
- Step handlers, scene-local commands, and `OnMessage(...)`.
|
||||||
- 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.
|
- In-memory session storage by default, plus the `SessionStore` interface for custom persistence.
|
||||||
|
|
||||||
Possible API direction:
|
What is still missing or not yet settled:
|
||||||
|
|
||||||
- `Scene`, `Step`, and `SessionStore` concepts.
|
- Scene-local payload routing.
|
||||||
- `bot.AddScene(...)` or a dedicated scene registry.
|
- A clear decision on whether any additional public scene-inspection API is needed.
|
||||||
- `ctx.Scene()`, `ctx.NextStep(...)`, `ctx.ExitScene()`, or similar state-transition helpers.
|
- Broader stabilization and documentation around the scene-state helper surface.
|
||||||
- Routing rule: active scene first, then normal command/payload flow if no scene claims the update.
|
|
||||||
|
Current API direction:
|
||||||
|
|
||||||
|
- `Scene`, `SceneContext`, `SceneSession`, and `SessionStore`.
|
||||||
|
- `Plugin.NewScene(...)` and `Plugin.AddScene(...)`.
|
||||||
|
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene(...)`.
|
||||||
|
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)`, and `SaveData(...)`.
|
||||||
- Storage-backed per-user or per-chat state with a clean interface for custom persistence.
|
- 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:
|
Important design constraints:
|
||||||
|
|
||||||
@@ -43,7 +48,7 @@ Important design constraints:
|
|||||||
|
|
||||||
Practical target:
|
Practical target:
|
||||||
|
|
||||||
- Make stateful bot flows a first-class, framework-supported pattern instead of a userland convention.
|
- Stabilize the current scene skeleton into a first-class framework-supported pattern.
|
||||||
- Cover both step-based forms and mode-based chat flows without forcing users to build custom routing layers around active sessions.
|
- Cover both step-based forms and mode-based chat flows without forcing users to build custom routing layers around active sessions.
|
||||||
|
|
||||||
## Secondary Backlog
|
## Secondary Backlog
|
||||||
|
|||||||
+53
-57
@@ -1,8 +1,8 @@
|
|||||||
# Scenes
|
# Scenes
|
||||||
|
|
||||||
DRAFT: эта страница описывает предлагаемую модель сцен и пока не соответствует реализованному API.
|
Scenes — это work-in-progress слой stateful-маршрутизации для долгоживущих диалогов в Laniakea. Базовый скелет уже есть в библиотеке: сцены можно регистрировать в plugins, запускать через `MsgContext`, сохранять через `SessionStore` и маршрутизировать раньше обычных команд. Эта страница описывает текущий API, уже работающие части и зоны, которые пока намеренно остаются незавершёнными.
|
||||||
|
|
||||||
Сцены — это планируемый stateful-слой маршрутизации для долгоживущих диалогов в Laniakea. Он должен располагаться поверх обычной маршрутизации команд и payload: если пользователь или чат находится внутри активной сцены, сцена получает update первой и решает, обработать его, продолжить состояние, выйти или передать управление обратно обычному plugin flow.
|
Сцены располагаются поверх обычной маршрутизации команд и payload: если пользователь или чат находится внутри активной сцены, сцена получает update первой и решает, обработать его, продолжить состояние, выйти или передать управление обратно обычному plugin flow.
|
||||||
|
|
||||||
## Зачем нужны сцены
|
## Зачем нужны сцены
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ DRAFT: эта страница описывает предлагаемую мо
|
|||||||
|
|
||||||
Сессионная модель не должна предполагать, что "один пользователь" всегда является правильной единицей. Для Telegram у личных чатов и групп разные потребности.
|
Сессионная модель не должна предполагать, что "один пользователь" всегда является правильной единицей. Для Telegram у личных чатов и групп разные потребности.
|
||||||
|
|
||||||
Предлагаемые scope:
|
Текущие scope:
|
||||||
|
|
||||||
- `SceneScopeUser`: одна сессия на пользователя во всех чатах.
|
- `SceneScopeUser`: одна сессия на пользователя во всех чатах.
|
||||||
- `SceneScopeChat`: одна сессия на чат.
|
- `SceneScopeChat`: одна сессия на чат.
|
||||||
@@ -47,7 +47,7 @@ DRAFT: эта страница описывает предлагаемую мо
|
|||||||
- `SceneScopeUser` оставлять для редких account-level flow, которые специально должны жить между чатами.
|
- `SceneScopeUser` оставлять для редких account-level flow, которые специально должны жить между чатами.
|
||||||
- `SceneScopeChat` использовать только для общих room-level сценариев.
|
- `SceneScopeChat` использовать только для общих room-level сценариев.
|
||||||
|
|
||||||
## Предлагаемые типы
|
## Текущие типы
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type SceneScope int
|
type SceneScope int
|
||||||
@@ -65,25 +65,27 @@ type SceneSession struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SessionStore interface {
|
type SessionStore interface {
|
||||||
Get(key string) (*SceneSession, error)
|
Get(key string) (SceneSession, error)
|
||||||
Set(key string, session SceneSession) error
|
Set(key string, session SceneSession) error
|
||||||
Delete(key string) error
|
Delete(key string) error
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Предлагаемая регистрация сцен
|
Laniakea уже поставляется с `MemorySessionStore` по умолчанию, а `Bot.SetSessionStore(...)` позволяет заменить его на кастомный store.
|
||||||
|
|
||||||
Сцены стоит регистрировать внутри plugin в том же стиле, что и команды с payload.
|
## Текущая регистрация сцен
|
||||||
|
|
||||||
|
Сцены регистрируются внутри plugin в том же стиле, что и команды с payload.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
plugin.NewScene("rp").
|
plugin.NewScene("rp").
|
||||||
SetScope(laniakea.SceneScopeUserChat).
|
SetScope(laniakea.SceneScopeUserChat).
|
||||||
SetEntry("chat").
|
SetEntry("chat").
|
||||||
OnMessage(handleRPMessage).
|
OnMessage(handleRPMessage).
|
||||||
AddCommand("rpstop", stopRP)
|
OnCommand("rpstop", stopRP)
|
||||||
```
|
```
|
||||||
|
|
||||||
`SetEntry(...)` задаёт начальный step или state сцены. Когда `ctx.EnterScene("rp")` создаёт новую сессию, фреймворк записывает это значение в `SceneSession.Step`.
|
`SetEntry(...)` задаёт начальный step или state сцены. `ctx.EnterScene("rp")` теперь валидирует, что entry-step задан и зарегистрирован, до того как создать сессию.
|
||||||
|
|
||||||
Так plugin API остаётся визуально согласованным:
|
Так plugin API остаётся визуально согласованным:
|
||||||
|
|
||||||
@@ -92,9 +94,9 @@ plugin.NewScene("rp").
|
|||||||
- `AddUpdateHandler(...)`
|
- `AddUpdateHandler(...)`
|
||||||
- `NewScene(...)`
|
- `NewScene(...)`
|
||||||
|
|
||||||
## Режимы сцен
|
## Формы сцен
|
||||||
|
|
||||||
Для первой версии достаточно покрыть две самые частые формы.
|
Текущий скелет уже покрывает две самые частые формы.
|
||||||
|
|
||||||
- Пошаговые сцены: именованный step обрабатывает каждый update и выбирает следующий step.
|
- Пошаговые сцены: именованный step обрабатывает каждый update и выбирает следующий step.
|
||||||
- Модальные сцены: долгоживущий "режим" обрабатывает обычные сообщения, пока пользователь явно не выйдет.
|
- Модальные сцены: долгоживущий "режим" обрабатывает обычные сообщения, пока пользователь явно не выйдет.
|
||||||
@@ -106,8 +108,8 @@ plugin.NewScene("rp").
|
|||||||
plugin.NewScene("profile").
|
plugin.NewScene("profile").
|
||||||
SetScope(laniakea.SceneScopeUserChat).
|
SetScope(laniakea.SceneScopeUserChat).
|
||||||
SetEntry("name").
|
SetEntry("name").
|
||||||
AddStep("name", askName).
|
OnStep("name", askName).
|
||||||
AddStep("confirm", confirmProfile)
|
OnStep("confirm", confirmProfile)
|
||||||
```
|
```
|
||||||
|
|
||||||
Пример модального flow:
|
Пример модального flow:
|
||||||
@@ -117,36 +119,35 @@ plugin.NewScene("rp").
|
|||||||
SetScope(laniakea.SceneScopeUserChat).
|
SetScope(laniakea.SceneScopeUserChat).
|
||||||
SetEntry("chat").
|
SetEntry("chat").
|
||||||
OnMessage(handleRPMessage).
|
OnMessage(handleRPMessage).
|
||||||
AddCommand("rpstop", stopRP)
|
OnCommand("rpstop", stopRP)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Предлагаемые контексты и результаты
|
## Текущие контексты и результаты
|
||||||
|
|
||||||
Обычные команды должны и дальше использовать `*MsgContext`. Scene handlers лучше давать отдельный wrapper, который встраивает обычный контекст и добавляет state information.
|
Обычные команды по-прежнему используют `*MsgContext`. Scene handlers используют `*SceneContext`, который встраивает `*MsgContext` и добавляет helper-методы для состояния сцены.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type SceneContext struct {
|
type SceneContext struct {
|
||||||
*MsgContext
|
*MsgContext
|
||||||
Scene string
|
// internal session state
|
||||||
Step string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SceneResult interface{ isSceneResult() }
|
type SceneResult struct {
|
||||||
|
Action SceneAction
|
||||||
type SceneStay struct{}
|
Next string
|
||||||
type SceneNext struct{ Step string }
|
}
|
||||||
type SceneExit struct{}
|
|
||||||
type ScenePass struct{}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Возможные helper-методы на `SceneContext`:
|
Текущие helper-методы на `SceneContext`:
|
||||||
|
|
||||||
- `ctx.Stay()`
|
- `ctx.Stay()`
|
||||||
- `ctx.Next(step)`
|
- `ctx.Next(step)`
|
||||||
- `ctx.Exit()`
|
- `ctx.Exit()`
|
||||||
- `ctx.Pass()`
|
- `ctx.Pass()`
|
||||||
|
- `ctx.BindData(&dst)`
|
||||||
|
- `ctx.SaveData(src)`
|
||||||
|
|
||||||
## Предлагаемые сигнатуры handlers
|
## Сигнатуры handlers
|
||||||
|
|
||||||
Обычная команда входа:
|
Обычная команда входа:
|
||||||
|
|
||||||
@@ -182,13 +183,12 @@ func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
|||||||
|
|
||||||
Для реальных сценариев недостаточно хранить только `Scene` и `Step`. Обычно между update нужно накапливать данные: черновик профиля, выбранные параметры, временные ID и другие промежуточные значения.
|
Для реальных сценариев недостаточно хранить только `Scene` и `Step`. Обычно между update нужно накапливать данные: черновик профиля, выбранные параметры, временные ID и другие промежуточные значения.
|
||||||
|
|
||||||
Это не стоит полностью перекладывать на разработчика приложения. Фреймворк должен давать слот для хранения в `SceneSession` и удобные helper-методы на `SceneContext`.
|
Это уже не стоит полностью перекладывать на разработчика приложения. Состояние живёт в `SceneSession.Data`, а `SceneContext` уже даёт JSON-backed helper-методы поверх него.
|
||||||
|
|
||||||
Предлагаемый helper API:
|
Текущий helper API:
|
||||||
|
|
||||||
- `ctx.BindState(&dst)`
|
- `ctx.BindData(&dst)`
|
||||||
- `ctx.SaveState(src)`
|
- `ctx.SaveData(src)`
|
||||||
- `ctx.ClearState()`
|
|
||||||
|
|
||||||
Пример:
|
Пример:
|
||||||
|
|
||||||
@@ -200,58 +200,54 @@ type ProfileDraft struct {
|
|||||||
|
|
||||||
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||||
var draft ProfileDraft
|
var draft ProfileDraft
|
||||||
_ = ctx.BindState(&draft)
|
_ = ctx.BindData(&draft)
|
||||||
|
|
||||||
draft.Name = ctx.Text
|
draft.Name = ctx.Text
|
||||||
if err := ctx.SaveState(draft); err != nil {
|
if err := ctx.SaveData(draft); err != nil {
|
||||||
return nil, err
|
return laniakea.SceneResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.Next("age"), nil
|
return ctx.Next("age"), nil
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Использование `Data []byte` в `SceneSession` делает контракт `SessionStore` маленьким и не завязанным на конкретный формат хранения. В первой реализации scene helpers могут работать через JSON.
|
Использование `Data []byte` в `SceneSession` делает контракт `SessionStore` маленьким и не завязанным на конкретный формат хранения. Текущие helper-методы используют JSON.
|
||||||
|
|
||||||
## Алгоритм маршрутизации
|
## Алгоритм маршрутизации
|
||||||
|
|
||||||
Роутер должен вести себя так:
|
Текущий роутер ведёт себя так:
|
||||||
|
|
||||||
1. Построить `MsgContext` для update.
|
1. Построить `MsgContext` для update.
|
||||||
2. Вычислить session key на основе scope сцены и текущего update.
|
2. Вычислить session key на основе scope сцены и текущего update.
|
||||||
3. Спросить `SessionStore`, есть ли активная сцена.
|
3. Спросить `SessionStore`, есть ли активная сцена.
|
||||||
4. Если активной сцены нет, продолжить обычную маршрутизацию command, payload и update handlers.
|
4. Если активной сцены нет, продолжить обычную маршрутизацию command, payload и update handlers.
|
||||||
5. Если сцена активна, сначала попробовать scene-local command или payload routing.
|
5. Если сцена активна, сначала попробовать scene-local command routing.
|
||||||
6. Если локальный route не совпал, вызвать message handler или step handler сцены.
|
6. Если локальный route не совпал, вызвать текущий step handler, затем scene message handler.
|
||||||
7. Если сцена вернула `Stay`, сохранить текущую сессию без изменений.
|
7. Если сцена вернула `Stay`, сохранить текущую сессию без изменений.
|
||||||
8. Если сцена вернула `Next(step)`, сохранить новый step и оставить `Data`.
|
8. Если сцена вернула `Next(step)`, сохранить новый step и оставить `Data`.
|
||||||
9. Если сцена вернула `Exit`, удалить сессию.
|
9. Если сцена вернула `Exit`, удалить сессию.
|
||||||
10. Если сцена вернула `Pass`, продолжить обычную маршрутизацию.
|
10. Если сцена вернула `Pass`, продолжить обычную маршрутизацию.
|
||||||
|
|
||||||
## Важные решения по совместимости
|
## Что уже реализовано
|
||||||
|
|
||||||
- Вход в сцену должен происходить через явный API вроде `ctx.EnterScene(...)`.
|
- `Plugin.NewScene(...)` и `Plugin.AddScene(...)`.
|
||||||
- Выход из сцены тоже должен быть явным.
|
- `Scene.SetScope(...)`, `SetEntry(...)`, `OnStep(...)`, `OnCommand(...)` и `OnMessage(...)`.
|
||||||
- Middleware должны продолжать работать на базовом `MsgContext`.
|
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)` и `ExitScene(...)`.
|
||||||
- Scene handlers должны так же пользоваться `ctx.BindArgs(...)` и `ctx.Context()`.
|
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)` и `SaveData(...)`.
|
||||||
- Первая версия может поставляться с in-memory store плюс интерфейсом `SessionStore` для кастомного persistence.
|
- `SessionStore` плюс дефолтный `MemorySessionStore`.
|
||||||
- Helpers для сериализации состояния должны входить в первый scene API, а не оставаться приложенческим boilerplate.
|
- Маршрутизация активной сцены до обычного command flow.
|
||||||
|
|
||||||
## Открытые вопросы
|
## Что ещё не реализовано или намеренно отложено
|
||||||
|
|
||||||
- Нужно ли добавлять scene-local payloads уже в первой версии или отложить на вторую?
|
- Scene-local payload routing пока не реализован.
|
||||||
- Стоит ли поставлять `MemorySessionStore` по умолчанию, или сцены должны требовать явный store?
|
- Scene runtime намеренно остаётся внутренним; публичный API не отдаёт низкоуровневые helper-методы для lookup sessions.
|
||||||
- Должны ли modal scenes иметь явный fallback mode вроде "pass to normal routing" против "consume silently"?
|
- Поверхность helper-методов для scene state пока намеренно небольшая.
|
||||||
|
|
||||||
## Рекомендуемый первый срез реализации
|
## Оставшаяся работа
|
||||||
|
|
||||||
- Добавить `Scene`, `SceneContext`, `SceneSession` и `SessionStore`.
|
- Расширить тесты вокруг scene-state helpers и fallback-поведения.
|
||||||
- Добавить in-memory session store по умолчанию.
|
- Решить, нужны ли scene-local payloads уже в первом стабильном scene release.
|
||||||
- Добавить `Plugin.NewScene(...)`.
|
- Решить, нужен ли вообще дополнительный публичный inspection API.
|
||||||
- Добавить `MsgContext.EnterScene(...)`, `ExitScene(...)` и `CurrentScene(...)`.
|
|
||||||
- Маршрутизировать активные сцены раньше обычного command/payload flow.
|
|
||||||
- Поддержать scene-local commands и `OnMessage(...)`.
|
|
||||||
- Поддержать helpers для состояния сцены поверх `SceneSession.Data`.
|
|
||||||
|
|
||||||
Связанные страницы:
|
Связанные страницы:
|
||||||
|
|
||||||
|
|||||||
+53
-57
@@ -1,8 +1,8 @@
|
|||||||
# Scenes
|
# Scenes
|
||||||
|
|
||||||
DRAFT: this page describes the proposed scene model and is not implemented yet.
|
Scenes are Laniakea's work-in-progress stateful routing layer for long-lived interactions. The core skeleton already exists in the library: scenes can be registered in plugins, entered through `MsgContext`, persisted through `SessionStore`, and routed before normal command handling. This page describes the current API, the behaviors that already work, and the parts that are still intentionally unfinished.
|
||||||
|
|
||||||
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.
|
Scenes 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
|
## Why scenes exist
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ Scenes are the planned stateful routing layer for long-lived interactions in Lan
|
|||||||
|
|
||||||
Scene sessions should not assume that "one user" is always the right unit. Telegram private chats and group chats need different defaults.
|
Scene sessions should not assume that "one user" is always the right unit. Telegram private chats and group chats need different defaults.
|
||||||
|
|
||||||
Proposed scopes:
|
Current scopes:
|
||||||
|
|
||||||
- `SceneScopeUser`: one session per user across all chats.
|
- `SceneScopeUser`: one session per user across all chats.
|
||||||
- `SceneScopeChat`: one session per chat.
|
- `SceneScopeChat`: one session per chat.
|
||||||
@@ -47,7 +47,7 @@ Recommended default:
|
|||||||
- Reserve `SceneScopeUser` for rare account-level flows that intentionally cross chats.
|
- Reserve `SceneScopeUser` for rare account-level flows that intentionally cross chats.
|
||||||
- Use `SceneScopeChat` only for room-level shared workflows.
|
- Use `SceneScopeChat` only for room-level shared workflows.
|
||||||
|
|
||||||
## Proposed types
|
## Current types
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type SceneScope int
|
type SceneScope int
|
||||||
@@ -65,25 +65,27 @@ type SceneSession struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SessionStore interface {
|
type SessionStore interface {
|
||||||
Get(key string) (*SceneSession, error)
|
Get(key string) (SceneSession, error)
|
||||||
Set(key string, session SceneSession) error
|
Set(key string, session SceneSession) error
|
||||||
Delete(key string) error
|
Delete(key string) error
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Proposed scene registration
|
Laniakea currently ships with `MemorySessionStore` as the default implementation, and `Bot.SetSessionStore(...)` can replace it with a custom store.
|
||||||
|
|
||||||
Scenes should be registered inside plugins in the same style as commands and payloads.
|
## Current scene registration
|
||||||
|
|
||||||
|
Scenes are registered inside plugins in the same style as commands and payloads.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
plugin.NewScene("rp").
|
plugin.NewScene("rp").
|
||||||
SetScope(laniakea.SceneScopeUserChat).
|
SetScope(laniakea.SceneScopeUserChat).
|
||||||
SetEntry("chat").
|
SetEntry("chat").
|
||||||
OnMessage(handleRPMessage).
|
OnMessage(handleRPMessage).
|
||||||
AddCommand("rpstop", stopRP)
|
OnCommand("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`.
|
`SetEntry(...)` defines the initial step or state of the scene. `ctx.EnterScene("rp")` validates that the entry step is set and registered before creating a session.
|
||||||
|
|
||||||
This keeps the plugin API visually consistent:
|
This keeps the plugin API visually consistent:
|
||||||
|
|
||||||
@@ -92,9 +94,9 @@ This keeps the plugin API visually consistent:
|
|||||||
- `AddUpdateHandler(...)`
|
- `AddUpdateHandler(...)`
|
||||||
- `NewScene(...)`
|
- `NewScene(...)`
|
||||||
|
|
||||||
## Scene modes
|
## Scene shapes
|
||||||
|
|
||||||
The first version should support two common shapes.
|
The current skeleton already supports 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.
|
||||||
@@ -106,8 +108,8 @@ Step-based flow example:
|
|||||||
plugin.NewScene("profile").
|
plugin.NewScene("profile").
|
||||||
SetScope(laniakea.SceneScopeUserChat).
|
SetScope(laniakea.SceneScopeUserChat).
|
||||||
SetEntry("name").
|
SetEntry("name").
|
||||||
AddStep("name", askName).
|
OnStep("name", askName).
|
||||||
AddStep("confirm", confirmProfile)
|
OnStep("confirm", confirmProfile)
|
||||||
```
|
```
|
||||||
|
|
||||||
Modal flow example:
|
Modal flow example:
|
||||||
@@ -117,36 +119,35 @@ plugin.NewScene("rp").
|
|||||||
SetScope(laniakea.SceneScopeUserChat).
|
SetScope(laniakea.SceneScopeUserChat).
|
||||||
SetEntry("chat").
|
SetEntry("chat").
|
||||||
OnMessage(handleRPMessage).
|
OnMessage(handleRPMessage).
|
||||||
AddCommand("rpstop", stopRP)
|
OnCommand("rpstop", stopRP)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Proposed contexts and results
|
## Current 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.
|
Normal commands still use `*MsgContext`. Scene handlers use `*SceneContext`, which embeds `*MsgContext` and adds scene-state helpers.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type SceneContext struct {
|
type SceneContext struct {
|
||||||
*MsgContext
|
*MsgContext
|
||||||
Scene string
|
// internal session state
|
||||||
Step string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SceneResult interface{ isSceneResult() }
|
type SceneResult struct {
|
||||||
|
Action SceneAction
|
||||||
type SceneStay struct{}
|
Next string
|
||||||
type SceneNext struct{ Step string }
|
}
|
||||||
type SceneExit struct{}
|
|
||||||
type ScenePass struct{}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Possible helper methods on `SceneContext`:
|
Current helper methods on `SceneContext`:
|
||||||
|
|
||||||
- `ctx.Stay()`
|
- `ctx.Stay()`
|
||||||
- `ctx.Next(step)`
|
- `ctx.Next(step)`
|
||||||
- `ctx.Exit()`
|
- `ctx.Exit()`
|
||||||
- `ctx.Pass()`
|
- `ctx.Pass()`
|
||||||
|
- `ctx.BindData(&dst)`
|
||||||
|
- `ctx.SaveData(src)`
|
||||||
|
|
||||||
## Proposed handler shapes
|
## Handler shapes
|
||||||
|
|
||||||
Normal command entry:
|
Normal command entry:
|
||||||
|
|
||||||
@@ -182,13 +183,12 @@ func stopRP(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
|||||||
|
|
||||||
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.
|
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`.
|
That state now lives in `SceneSession.Data`, and `SceneContext` already exposes JSON-backed helpers for it.
|
||||||
|
|
||||||
Proposed helper API:
|
Current helper API:
|
||||||
|
|
||||||
- `ctx.BindState(&dst)`
|
- `ctx.BindData(&dst)`
|
||||||
- `ctx.SaveState(src)`
|
- `ctx.SaveData(src)`
|
||||||
- `ctx.ClearState()`
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@@ -200,58 +200,54 @@ type ProfileDraft struct {
|
|||||||
|
|
||||||
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
func askName(ctx *laniakea.SceneContext, db *App) (laniakea.SceneResult, error) {
|
||||||
var draft ProfileDraft
|
var draft ProfileDraft
|
||||||
_ = ctx.BindState(&draft)
|
_ = ctx.BindData(&draft)
|
||||||
|
|
||||||
draft.Name = ctx.Text
|
draft.Name = ctx.Text
|
||||||
if err := ctx.SaveState(draft); err != nil {
|
if err := ctx.SaveData(draft); err != nil {
|
||||||
return nil, err
|
return laniakea.SceneResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.Next("age"), nil
|
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.
|
Using `Data []byte` in `SceneSession` keeps the `SessionStore` contract small and storage-agnostic. The current helpers use JSON.
|
||||||
|
|
||||||
## Routing algorithm
|
## Routing algorithm
|
||||||
|
|
||||||
The router should behave like this:
|
The current router behaves like this:
|
||||||
|
|
||||||
1. Build `MsgContext` for the update.
|
1. Build `MsgContext` for the update.
|
||||||
2. Compute the session key from scene scope and the current update.
|
2. Compute the session key from scene scope and the current update.
|
||||||
3. Ask `SessionStore` whether an active scene exists.
|
3. Ask `SessionStore` whether an active scene exists.
|
||||||
4. If no scene is active, continue normal command, payload, and update routing.
|
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.
|
5. If a scene is active, try scene-local command 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 current step handler, then the scene message 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 and keep `Data`.
|
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.
|
||||||
|
|
||||||
## Important compatibility decisions
|
## Implemented today
|
||||||
|
|
||||||
- Scene entry should happen through explicit APIs such as `ctx.EnterScene(...)`.
|
- `Plugin.NewScene(...)` and `Plugin.AddScene(...)`.
|
||||||
- Scene exit should also be explicit.
|
- `Scene.SetScope(...)`, `SetEntry(...)`, `OnStep(...)`, `OnCommand(...)`, and `OnMessage(...)`.
|
||||||
- Middleware should still run on the base `MsgContext`.
|
- `MsgContext.EnterScene(...)`, `EnterSceneStep(...)`, and `ExitScene(...)`.
|
||||||
- Scene handlers should continue to benefit from `ctx.BindArgs(...)` and `ctx.Context()`.
|
- `SceneContext.Stay()`, `Next(...)`, `Exit()`, `Pass()`, `BindData(...)`, and `SaveData(...)`.
|
||||||
- The first version can ship with an in-memory store plus a `SessionStore` interface for custom persistence.
|
- `SessionStore` plus the default `MemorySessionStore`.
|
||||||
- State serialization helpers should be part of the first scene API, not left as app-specific boilerplate.
|
- Active-scene routing before normal command flow.
|
||||||
|
|
||||||
## Open questions
|
## Still missing or intentionally deferred
|
||||||
|
|
||||||
- Should scene-local payloads be added in the first version or the second?
|
- Scene-local payload routing is not implemented yet.
|
||||||
- Should the framework ship with a default `MemorySessionStore`, or should scenes require an explicit store?
|
- The scene runtime remains intentionally internal; the public API does not expose low-level session lookup helpers.
|
||||||
- Should modal scenes have an explicit fallback mode such as "pass to normal routing" versus "consume silently"?
|
- The scene-state helper surface is still intentionally small.
|
||||||
|
|
||||||
## Recommended first implementation slice
|
## Remaining work
|
||||||
|
|
||||||
- Add `Scene`, `SceneContext`, `SceneSession`, and `SessionStore`.
|
- Expand tests around scene-state helpers and fallback behavior.
|
||||||
- Add a default in-memory session store.
|
- Decide whether scene-local payloads belong in the first stable scene release.
|
||||||
- Add `Plugin.NewScene(...)`.
|
- Decide whether any additional public inspection API is actually needed.
|
||||||
- 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:
|
Related pages:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user