- 只翻注释/文档字符串:剥离注释后代码行逐字节不变(机械校验只剩 4 条行尾注释的翻译,代码前缀全等) - 交叉引用(design/16 §4.6、D4/B3、0010 迁移文件名)逐字保留 - 保留未译技术词:DDL/rowid/LIKE/LEFT JOIN/DTO/rune/WS/actor 及全部标识符与表名 - 顺带副作用:store.go、session_persona_test.go 由注释文字改变而变得 gofmt 干净(未运行 gofmt -w)
77 lines
3.5 KiB
Go
77 lines
3.5 KiB
Go
package history
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// 本文件负责每会话的角色绑定(DevDocs/design/22 多角色并存 +
|
||
// 按会话选角 §2/§4.1)。一个会话的角色是「这个对话里由哪个角色应答」的唯一
|
||
// 事实来源:循环据此解析系统提示词、记忆作用域、情绪机与思考预算。该值只是
|
||
// 展示/作用域元数据 —— 身份与权限仍由渠道 actor 元数据决定(项目红线)。
|
||
//
|
||
// 空意味着「继承当前全局默认角色」,因此它既是列默认值,也是一个合法的显式
|
||
// 取值:把它设为空即清除显式绑定,让会话重新跟随默认角色变更
|
||
// (design/22 §7-6)。
|
||
|
||
// NewSubSessionFor 创建绑定到 personaID 的子会话并返回其 id
|
||
// (MainSessionID:uuid)。personaID 为空则不绑定任何显式角色,会话因此继承
|
||
// 全局默认 —— 与 NewSubSession 行为相同。ownerID 为与 NewSubSession 对称而
|
||
// 接受(Web 主人由 JWT 识别,不按会话存储);它只以 debug 级别记日志,不被
|
||
// 持久化。
|
||
func (s *Store) NewSubSessionFor(ctx context.Context, ownerID, personaID string) (string, error) {
|
||
id, err := newID()
|
||
if err != nil {
|
||
return "", fmt.Errorf("history: new sub id: %w", err)
|
||
}
|
||
sessionID := MainSessionID + ":" + id
|
||
now := time.Now().UnixMilli()
|
||
if _, err := s.db.ExecContext(ctx, `INSERT INTO chat_sessions (id, type, title, persona_id, created_at)
|
||
VALUES (?, ?, ?, ?, ?)`, sessionID, SessionSub, "", strings.TrimSpace(personaID), now); err != nil {
|
||
return "", fmt.Errorf("history: insert sub session: %w", err)
|
||
}
|
||
if s.log != nil {
|
||
s.log.Debug("sub session created", "session_id", sessionID, "owner_id", ownerID, "persona_id", strings.TrimSpace(personaID))
|
||
}
|
||
return sessionID, nil
|
||
}
|
||
|
||
// SetSessionPersona 绑定(或在 personaID 为空时清除)一个会话的角色。固定主
|
||
// 会话与子会话都可被改绑:该绑定是作用域元数据,不是身份。未知 id 返回
|
||
// ErrSessionNotFound。它绝不触碰对话:已存消息保留说出它们的那个角色
|
||
// (design/22 §7-5),下一个回合只是解析新角色。
|
||
func (s *Store) SetSessionPersona(ctx context.Context, sessionID, personaID string) error {
|
||
res, err := s.db.ExecContext(ctx, `UPDATE chat_sessions SET persona_id = ? WHERE id = ?`,
|
||
strings.TrimSpace(personaID), sessionID)
|
||
if err != nil {
|
||
return fmt.Errorf("history: set session persona %s: %w", sessionID, err)
|
||
}
|
||
n, err := res.RowsAffected()
|
||
if err != nil {
|
||
return fmt.Errorf("history: set session persona %s: %w", sessionID, err)
|
||
}
|
||
if n == 0 {
|
||
return fmt.Errorf("history: set session persona %s: %w", sessionID, ErrSessionNotFound)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// SessionPersona 返回绑定到 sessionID 的角色,会话继承全局默认时返回空。未知
|
||
// id 返回 ErrSessionNotFound,使调用方能区分「没有显式角色」(真实会话、绑定
|
||
// 为空)与「没有这个会话」;只需要有效角色的调用方把两者都当作「用默认」。
|
||
func (s *Store) SessionPersona(ctx context.Context, sessionID string) (string, error) {
|
||
var personaID string
|
||
err := s.db.QueryRowContext(ctx, `SELECT persona_id FROM chat_sessions WHERE id = ?`, sessionID).Scan(&personaID)
|
||
switch {
|
||
case errors.Is(err, sql.ErrNoRows):
|
||
return "", fmt.Errorf("history: session persona %s: %w", sessionID, ErrSessionNotFound)
|
||
case err != nil:
|
||
return "", fmt.Errorf("history: session persona %s: %w", sessionID, err)
|
||
}
|
||
return personaID, nil
|
||
}
|