- ui.Session (UserID/Address/Title/BoundTask) + фабрики NewFreeSession/NewTaskSession и префикс ui://sess-* - свободная вкладка без задачи: свой UserID, диалог с LLM в памяти (agent=chat, resume по sessionID) - /start в свободной вкладке создаёт задачу с ChatID == UserID вкладки -> вкладка становится task-сессией - desktop окно: container.AppTabs с вкладками, кнопка «Новый чат», маршрутизация Send/Ask/истории по Address вкладки - app.FreeChat: in-memory связка uid -> opencode sessionID; роутинг свободного чата в handleIncoming - агент chat.md + регистрация 'chat' в agents.Names - тесты: session_test.go (фабрики/хелперы)
44 lines
2.3 KiB
Go
44 lines
2.3 KiB
Go
package ui
|
||
|
||
import (
|
||
"github.com/kamelion/ratatoskr-go/internal/chat"
|
||
"github.com/kamelion/ratatoskr-go/internal/storage"
|
||
)
|
||
|
||
// Session — одна вкладка-сессия UI: независимый диалог с LLM.
|
||
//
|
||
// Каждая вкладка имеет собственный UserID и Address, поэтому core видит её как
|
||
// отдельный чат со своим активным task (политика «одна задача на чат» работает
|
||
// из коробки). Свободная сессия (BoundTask==0) — in-memory диалог с моделью
|
||
// вне машины задач; task-сессия (BoundTask>0) — диалог конкретной задачи, у
|
||
// которой UserID == task.ChatID (чат задачи уникален).
|
||
type Session struct {
|
||
UserID chat.UserID
|
||
Address chat.Address
|
||
Title string
|
||
BoundTask int64 // 0 = свободный чат; >0 — задача, к которой привязана вкладка
|
||
}
|
||
|
||
// NewFreeSession создаёт свободную сессию (без задачи). userID/address должны
|
||
// быть уникальными (например "ui://sess-<n>"). Транскрипт живёт только в памяти.
|
||
func NewFreeSession(userID chat.UserID, address chat.Address) *Session {
|
||
return &Session{UserID: userID, Address: address, Title: "Новый чат", BoundTask: 0}
|
||
}
|
||
|
||
// NewTaskSession создаёт сессию, привязанную к задаче. UserID == task.ChatID,
|
||
// чтобы Router/core находили активную задачу этой вкладки. address — уникальный
|
||
// адрес доставки вкладки (не должен совпадать с UserID других вкладок).
|
||
func NewTaskSession(address chat.Address, t storage.Task) *Session {
|
||
title := t.Title
|
||
if title == "" {
|
||
title = "Задача #" + Itoa(t.ID)
|
||
}
|
||
return &Session{UserID: chat.UserID(t.ChatID), Address: address, Title: title, BoundTask: t.ID}
|
||
}
|
||
|
||
// SessionID — префикс идентификаторов UI-сессий (и UserID, и Address).
|
||
const SessionID = "ui://sess-"
|
||
|
||
// IsFreeSession — true, если вкладка не привязана к задаче.
|
||
func IsFreeSession(s *Session) bool { return s != nil && s.BoundTask == 0 }
|