按 design/22 §2/§3/§4.4/§7(3/4/5)。 - ①「消息级角色归属」进入模型上下文(批次 1 只做存储+DTO):历史消息的发送角色现在对模型可见,且**不把系统元数据塞进用户正文**(design/11 §10 红线) - ② IM owner 私聊镜像解析出的角色 = **主会话绑定的角色**(§7-3) - ③ 渠道级绑定:ChannelAccount 加 personaId(channels.json 读写/校验/脱敏沿用既有约定)+ 角色解析优先级 **会话 > 渠道 > 默认** + **群聊/陌生人强制默认角色**(§7-4);admin 渠道配置端点支持 personaId - Docs/api-client 版本 +1 - 验收:go build/vet/test 全绿 + linux/darwin 交叉编译
84 lines
3.9 KiB
Go
84 lines
3.9 KiB
Go
package loop
|
|
|
|
import (
|
|
"context"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/restrict"
|
|
)
|
|
|
|
// SessionEnv is the per-conversation context that gates memory injection and
|
|
// extraction for one Run call. It travels on the call's context rather than on
|
|
// the shared Loop because Loop is reused across concurrent sessions (every
|
|
// channel/group/stranger message resolves its own isolation boundary); storing it
|
|
// on the Loop struct would race.
|
|
type SessionEnv struct {
|
|
// ActorEnv is the resolved actor environment: owner, group_member, stranger.
|
|
// It is the strongly-typed identity lane decided structurally by the channel
|
|
// layer (design/11 §0), never inferred from prompt text.
|
|
ActorEnv restrict.ActorEnv
|
|
// MemWritable reports whether this conversation may write shared memory. It is
|
|
// true only for the owner's private conversation; group chats and strangers
|
|
// are read-only (design/02 §3, design/11 §2).
|
|
MemWritable bool
|
|
// PersonaID is the role this conversation speaks as (design/22 §2/§4.2), resolved
|
|
// per turn by the caller (normally the gateway's session-binding resolver). Empty
|
|
// means "inherit the loop's configured role" (Options.PersonaID, the global default
|
|
// role), which keeps every pre-multi-character caller — channelbridge, backthink,
|
|
// internal autonomous passes — behaving exactly as before. It scopes the system
|
|
// prompt, the memory namespace, the emotion machine and the thinking budget; it is
|
|
// NOT an identity or permission input: ActorEnv/MemWritable alone decide access
|
|
// (project red line).
|
|
PersonaID string
|
|
}
|
|
|
|
// Restricted reports whether this is a group-chat or stranger conversation, i.e.
|
|
// the read-only memory isolation context. Because MemWritable is true only for
|
|
// the owner's private conversation, !MemWritable is exactly the restricted set.
|
|
func (e SessionEnv) Restricted() bool { return !e.MemWritable }
|
|
|
|
// OwnerEnv returns the full-access owner session environment (private
|
|
// conversation, memory read+write). It is the Web main-session and internal
|
|
// automated-task default, matching the pre-existing owner-full behavior. It binds
|
|
// no explicit role (PersonaID is empty), so the loop falls back to its configured
|
|
// default role — the caller that knows the session's role sets PersonaID on the
|
|
// returned value (design/22 §4.2).
|
|
func OwnerEnv() SessionEnv {
|
|
return SessionEnv{ActorEnv: restrict.ActorOwner, MemWritable: true}
|
|
}
|
|
|
|
// defaultSessionEnv is the owner-full environment used when a caller attaches no
|
|
// explicit env: full memory read and write, the pre-existing behavior.
|
|
func defaultSessionEnv() SessionEnv {
|
|
return OwnerEnv()
|
|
}
|
|
|
|
type sessionEnvKey struct{}
|
|
|
|
// WithSessionEnv attaches env to ctx so Run's memory gates honor the
|
|
// group/stranger read-only isolation per call. It is concurrency-safe: the env
|
|
// travels with the call's context and is never stored on the shared Loop.
|
|
func WithSessionEnv(ctx context.Context, env SessionEnv) context.Context {
|
|
return context.WithValue(ctx, sessionEnvKey{}, env)
|
|
}
|
|
|
|
// SessionEnvFrom returns the environment attached to ctx by WithSessionEnv, or the
|
|
// owner-full default when none was attached (backward compatible). It is the read
|
|
// side of WithSessionEnv, for a caller that receives only a context — a Chatter
|
|
// implementation that must know the turn's role/isolation boundary, or a test
|
|
// asserting what an inbound path resolved. The value is display/scope metadata
|
|
// only: identity and permission were already decided by the actor metadata before
|
|
// the env existed (project red line).
|
|
func SessionEnvFrom(ctx context.Context) SessionEnv { return sessionEnvFrom(ctx) }
|
|
|
|
// sessionEnvFrom returns the env attached to ctx, or the owner-full default when
|
|
// none was attached (backward compatible).
|
|
func sessionEnvFrom(ctx context.Context) SessionEnv {
|
|
if ctx == nil {
|
|
return defaultSessionEnv()
|
|
}
|
|
if env, ok := ctx.Value(sessionEnvKey{}).(SessionEnv); ok {
|
|
return env
|
|
}
|
|
return defaultSessionEnv()
|
|
}
|