- P0-5 护栏不再是空壳:挂 WithSensitiveInfo(阻断) + 长度上限(输入 262144 / 输出 65536 字符,依据=
组装完成的请求体量级与「不误伤正常对话」) + 关键词**仅在配置时**挂;正常对话有用例证明放行
- P0-11 上下文压缩默认启用:CONTEXT_MAX_TOKENS 默认 32768(=loop.DefaultMaxContextTokens,两常量由测试锁死),
0/负 = 关闭(行为退回接线前);长对话真压缩且摘要消息进请求首位、最近一轮保留
- P0-10 身份说明书(owner.yaml identityFile)注入人格上下文,并且**收窄为仅主人会话**:
· 新增最小钩子 loop.Options.PersonaKnowledgeFor(env):**按该轮会话环境**解析(不看提示词、不看会话 id),
块只进每轮视图、**不进 messages**(不进 checkpoint、不进记忆提取面)
· 开关 OWNER_IDENTITY_INJECTION:owner_only(默认) / always(仅自测) / off;非法值与读不动 → WARN + 回退默认(不静默)
· **顺手关掉第二条同型泄漏**:channelbridge 群聊参与度判定器的 PersonaPrompt 此前带身份正文 ⇒ 现改为纯人格指令
· 回归:缺文件/目录/未配置逐字节等于旧输出;静态指令与角色池 Instructions() 全不含正文;
文件里点名的陌生账号仍 IsOwner==false(身份知识绝不参与权限判定)
- 复核:build/vet 绿 + cmd/config/loop 包测试绿;四类断言(主人含 / **群聊与陌生人不含** / always·off 两档 /
回归不变)+ 4 条变异反证(含把判定改成恒真 → 提示词里真的出现身份说明书)均已还原
- 未做(登记):knowledge 块启动时读一次(改文件需重启);`.env.example` 的 OWNER_IDENTITY_INJECTION 行随后补;
`internal/user/owner.go` 注释与 design/01 §2.2、design/11 §0 待同步
67 lines
2.9 KiB
Go
67 lines
2.9 KiB
Go
package config
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
// OwnerIdentityInjectionEnv 是「主人身份说明书注入范围」开关的环境变量名
|
||
// (design/11 §0 红线;见 cmd/cyrene/persona_identity.go 的消费点)。
|
||
const OwnerIdentityInjectionEnv = "OWNER_IDENTITY_INJECTION"
|
||
|
||
// 注入范围的三档取值。默认(也是唯一安全的一档)是 OwnerIdentityOwnerOnly:
|
||
// 只有主人会话能拿到那段知识,群聊与陌生人私聊的上下文里根本不出现。
|
||
const (
|
||
// OwnerIdentityOwnerOnly 只在主人会话注入(默认)。
|
||
OwnerIdentityOwnerOnly = "owner_only"
|
||
// OwnerIdentityAlways 所有会话都注入,含群聊与陌生人私聊。
|
||
// 它显式开启红线例外,只应在本地自测/排障时短暂使用。
|
||
OwnerIdentityAlways = "always"
|
||
// OwnerIdentityOff 完全关闭注入:连主人会话也没有这段知识。
|
||
OwnerIdentityOff = "off"
|
||
)
|
||
|
||
// LoadOwnerIdentityInjection 读取注入范围开关,优先级与其余配置完全一致:
|
||
// 进程环境 > .env > 默认 owner_only。返回值已去空白(空串按默认处理),
|
||
// 由调用方用 ParseOwnerIdentityInjection 判定合法性 —— 非法值必须被显式
|
||
// 报告(记日志 + 回退默认),绝不静默生效或静默失效。
|
||
//
|
||
// 为什么需要这个读取函数,而不是给 Config 加一个字段:Config 的反射解码器
|
||
// 只做标量赋值,承载不了「三档 + 非法值判定」的语义;而直接 os.Getenv 会
|
||
// 让只写在 .env 里的声明被静默忽略(.env 由 parseDotEnv 读进独立 map,
|
||
// 并不会写回进程环境)。这里复用同一套优先级与同一份 .env 解析,因此两种
|
||
// 写法都真实生效。envPath 为空表示不看 .env(与 Load 的约定一致)。
|
||
func LoadOwnerIdentityInjection(envPath string) (string, error) {
|
||
fromEnv := map[string]string{}
|
||
if envPath != "" {
|
||
m, err := parseDotEnv(envPath)
|
||
if err != nil {
|
||
return "", fmt.Errorf("config: read %s: %w", envPath, err)
|
||
}
|
||
fromEnv = m
|
||
}
|
||
raw := strings.TrimSpace(lookupRaw(OwnerIdentityInjectionEnv, OwnerIdentityOwnerOnly, fromEnv))
|
||
if raw == "" {
|
||
raw = OwnerIdentityOwnerOnly
|
||
}
|
||
return raw, nil
|
||
}
|
||
|
||
// ParseOwnerIdentityInjection 把开关取值判定成三档之一(大小写不敏感,
|
||
// 允许首尾空白)。空串按默认 owner_only 处理;其余无法识别的取值返回错误,
|
||
// 由调用方明确回退到 owner_only 并把非法值记进日志。
|
||
func ParseOwnerIdentityInjection(raw string) (string, error) {
|
||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||
case "", OwnerIdentityOwnerOnly:
|
||
return OwnerIdentityOwnerOnly, nil
|
||
case OwnerIdentityAlways:
|
||
return OwnerIdentityAlways, nil
|
||
case OwnerIdentityOff:
|
||
return OwnerIdentityOff, nil
|
||
default:
|
||
return OwnerIdentityOwnerOnly, fmt.Errorf(
|
||
"config: %s=%q is not one of %s|%s|%s",
|
||
OwnerIdentityInjectionEnv, raw, OwnerIdentityOwnerOnly, OwnerIdentityAlways, OwnerIdentityOff)
|
||
}
|
||
}
|