fix(loop,gateway,persona): awakening 逐 turn 按角色生效 + 修「面板保存角色抹掉 5 个块」的数据丢失(裁决 14.4.3)
- per-role 注入补齐:Options.PersonaAwakening(personaID) → Loop.personaAwakening → l.awakeningFor(ctx) → capacity.Input.Awakening(toolSchemas 是全仓唯一构造该 Input 处) · 按 turn 解析(不绑 Loop 构造期,避免并发会话数据竞争),与既有 personaFor/knobsFor 同构 · 未知角色/空角色/档案无该块 ⇒ 零值 ⇒ 回落 Config.Awakening(不声明者行为与今天构造性一致) - **数据丢失修复**:persona PUT 是整块替换,此前只保留 thinking ⇒ 保存一次会同时抹掉 awakening / visual(头像·背景) / triggerWords(群聊提醒词) / emotion(情绪机参数) / affection(好感度曲线)。 新增 applyAwakeningEdit + applyPersonaReadOnlyBlocks 逐一保留(cyrene.yaml 五块全写,属必现故障) - 文件守 500 行:主人档案段原样搬移到 admin_owner_profile.go(零逻辑变更) - 复核:build/vet 绿 + 全树 `go test ./... -count=1` 0 FAIL;端到端=真 YAML→loader→buildCapacity→真 loop 流式请求→断言工具面(tasky 声明 force_state 唤醒 → 含 web-search;cyrene 不声明 → 与无解析器对照逐字相同); **8 条变异反证**先红后还原(含逐块删除各自的保留逻辑) - 残留缺口(登记):wire_runtime.go:254 那一行本身无用例覆盖;loader 非 strict(未知 YAML 键静默忽略, 需主人裁决是否开 KnownFields);POST /admin/personas 仍写不出这五个块
This commit is contained in:
1 parent
58cba0b8d2
commit
7afef8d037
11 files changed
+778
-142
No files matched your search
+45
-13
@@ -27,9 +27,14 @@ import (
|
||||
// (CAPACITY_L3_CACHE_TTL, default 5m) is parsed here because the config loader
|
||||
// only carries scalars.
|
||||
//
|
||||
// The persona-level awakening override (裁决 14.4.3) is filled here from the
|
||||
// current main persona's YAML block (see defaultAwakening); a persona that
|
||||
// declares no block contributes the zero value, i.e. no override.
|
||||
// The persona-level awakening override (裁决 14.4.3) enters the decision layer in
|
||||
// two places. This function fills the *global* fallback (Config.Awakening) from the
|
||||
// current main persona's YAML block (see defaultAwakening): it is what a persona
|
||||
// declaring no block inherits. The per-turn value is resolved by awakeningFor,
|
||||
// which the loop calls for every turn's own role through
|
||||
// loop.Options.PersonaAwakening — without it, a non-default character's
|
||||
// `awakening` block would be parsed, validated and then silently ignored
|
||||
// (forbidden by design/01 §2.1).
|
||||
func (a *app) buildCapacity() *capacity.Decider {
|
||||
if !a.cfg.Capacity.Enabled {
|
||||
return nil
|
||||
@@ -96,21 +101,48 @@ func parseCapacityToolSets(raw string) (map[capacity.State][]string, error) {
|
||||
}
|
||||
|
||||
// defaultAwakening 取当前主角色(`configs/users/owner.yaml` 的 `defaultPersona`,
|
||||
// 未配置时回落 `cyrene`)档案里的 `awakening` 块,映射为 capacity 的人格档位覆盖
|
||||
// (design/14 §3,裁决 14.4.3)。下列情形一律返回零值 = **无覆盖**,由决策层走
|
||||
// 全局默认,绝不在此处凭空补档位:
|
||||
// - 尚未装配人格注册表(降级启动 / 单测构造的 app);
|
||||
// - 该角色档案里没写 `awakening` 块(缺席就是没有);
|
||||
// - 档位名不是规范态名(loader 已在载入期拒绝非法值;这里按无覆盖处理并
|
||||
// 告警——未知态若被塞进决策层,会落进「不受限工具集」分支)。
|
||||
// 未配置时回落 `cyrene`)档案里的 `awakening` 块。它填的是决策层的**全局兜底**
|
||||
// (capacity.Config.Awakening);每个 turn 真正生效的档位由 awakeningFor 按该
|
||||
// turn 的角色解析(裁决 14.4.3 的后半)。两者共用同一处映射,因此「全局兜底」
|
||||
// 与「按角色取值」的口径绝不会分叉。
|
||||
//
|
||||
// 档位是倾向/呈现,不是能力边界——它只影响本轮暴露的工具面与唤醒阈值。
|
||||
// 未装配人格注册表(降级启动 / 单测构造的 app)时返回零值 —— 注册表缺席时连
|
||||
// 默认角色都问不出来,此时不补任何档位。
|
||||
func (a *app) defaultAwakening() capacity.Awakening {
|
||||
if a.personas == nil {
|
||||
return capacity.Awakening{}
|
||||
}
|
||||
p, ok := a.defaultPersona()
|
||||
if !ok || p == nil || p.Awakening == nil {
|
||||
return a.awakeningFor(a.defaultPersonaID())
|
||||
}
|
||||
|
||||
// awakeningFor 取**该角色**档案里的 `awakening` 块,映射为决策层的人格档位覆盖
|
||||
// (design/14 §3,裁决 14.4.3)。它是 loop.Options.PersonaAwakening 的实现:循环
|
||||
// 每个 turn 按该 turn 的角色 id 调它,因此绑定非默认角色的会话用**自己**的档位
|
||||
// 判定能力面,而不是只吃默认角色的全局兜底。
|
||||
//
|
||||
// 下列情形一律返回零值 = **无覆盖**,由决策层走全局默认(Config.Awakening +
|
||||
// 全局阈值),绝不在此处凭空补档位:
|
||||
// - 尚未装配人格注册表(降级启动 / 单测构造的 app);
|
||||
// - 角色 id 为空或该角色不在注册表里(未知/已删除人格);
|
||||
// - 该角色档案里没写 `awakening` 块(缺席就是没有)。
|
||||
//
|
||||
// 档位是倾向/呈现,不是能力边界——它只影响本轮暴露的工具面与唤醒阈值。
|
||||
func (a *app) awakeningFor(personaID string) capacity.Awakening {
|
||||
if a.personas == nil || personaID == "" {
|
||||
return capacity.Awakening{}
|
||||
}
|
||||
p, ok := a.personas.Persona(personaID)
|
||||
if !ok || p == nil {
|
||||
return capacity.Awakening{}
|
||||
}
|
||||
return a.awakeningOf(p)
|
||||
}
|
||||
|
||||
// awakeningOf 把一份人格档案的 `awakening` 块映射为决策层的档位覆盖。映射不了
|
||||
// 的档位名按「无该档位」处理(awakeningStateOf 会告警),因此非法值绝不会被塞进
|
||||
// 决策层(未知态会落进「不受限工具集」分支)。
|
||||
func (a *app) awakeningOf(p *loader.Persona) capacity.Awakening {
|
||||
if p == nil || p.Awakening == nil {
|
||||
return capacity.Awakening{}
|
||||
}
|
||||
aw := capacity.Awakening{
|
||||
|
||||
Reference in new issue
Block a user