fix(loop): keep cross-batch voice success and gate speak without dispatcher (M57)
两处 review 必修(review-m57 r1): P2-1 跨批覆盖:applyBatchVoice 原先无条件用后一批的语音结论覆盖 前一批,于是「先成功合成 abc.wav、下一批换个参数再试却失败」会把 Result.Voice 退化成 tts_failed —— 音频已落盘,退回文本既无收益也 违背那次表达。改为守 captureVoice 的同一条优先级:已落盘的音频不被 后续批次的失败抹掉;先降级后成功仍然覆盖(后到的成功即最终语音)。 P2-2 nil-dispatcher 安全门:speak 由循环自己承载、无 dispatcher 也 能执行(invokeOne 短路),但 applyToolAdmissions 在 dispatcher 缺席 时整段跳过安全门,使「没配 dispatcher」成为绕过 design/11 §3 的 旁路。新增 applyDispatcherlessVoiceGate:与策略引擎对未登记工具的 口径一致(默认关闭、只有 owner 放行),拒绝走同一条 not_allowed 软结果与 tool.reject.not_allowed 审计通道;并改正该处过时注释。 两条回归测试都做过改前红/改后绿: - TestApplyBatchVoice_KeepsCrossBatchSuccess:注释掉守卫即报 「voice = ... FallbackReason:tts_failed, want abc.wav」 - TestApplyToolAdmissions_DispatcherlessSpeakGate:去掉 else 分支即报 「env group_member run = true, want false」 验收:go build ./... / go vet ./... / go test ./... 全绿。
This commit is contained in:
1 parent
c9da488749
commit
a0b4c941e4
5 files changed
+159
-4
No files matched your search
@@ -3,6 +3,7 @@ package loop
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/restrict"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/agent"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/tool"
|
||||
@@ -128,6 +129,37 @@ func notAllowedReason(name string) string {
|
||||
return "not_allowed: tool " + name + " is not allowed in a group/stranger conversation"
|
||||
}
|
||||
|
||||
// applyDispatcherlessVoiceGate 是 dispatcher 缺席时对 speak 的兜底安全门
|
||||
// (design/11 §3)。正常接线恒有 dispatcher,安全门由 applyToolSecurityGate
|
||||
// 走策略引擎;但 speak 由循环自己承载,无 dispatcher 也能执行(invokeOne
|
||||
// 的短路),若此处不拦,退化配置就会让群聊/陌生人(甚至模型幻觉出的)
|
||||
// speak 调用真的去合成音频并落盘 —— 绕过身份判定,也白烧一次上游调用。
|
||||
//
|
||||
// 口径与策略引擎对未登记工具的处理一致:**默认关闭**,只有主人环境放行。
|
||||
// 拒绝走与安全门相同的软结果与审计通道(not_allowed + auditToolRejectNotAllowed),
|
||||
// 使模型看到的原因、前端小字与审计轨迹在不同配置下逐字同源。
|
||||
func (l *Loop) applyDispatcherlessVoiceGate(ctx context.Context, calls []agent.ToolCall, actions []toolCallAction) {
|
||||
if !l.voiceEnabled() {
|
||||
return
|
||||
}
|
||||
env := sessionEnvFrom(ctx)
|
||||
if env.ActorEnv == restrict.ActorOwner {
|
||||
return
|
||||
}
|
||||
for i := range calls {
|
||||
if !actions[i].run || calls[i].Name != speakToolName {
|
||||
continue
|
||||
}
|
||||
reason := notAllowedReason(calls[i].Name)
|
||||
actions[i] = toolCallAction{run: false, reason: reason}
|
||||
l.emitAudit(AuditEvent{
|
||||
Who: string(env.ActorEnv),
|
||||
What: auditToolRejectNotAllowed,
|
||||
Result: reason,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// emitAudit 把 AuditEvent 转发给配置的 OnAudit sink。未接线
|
||||
// sink 时为空操作,因此在未挂审计 sink 的叶子测试中
|
||||
// 安全门仍能工作(软 not_allowed 结果)。
|
||||
|
||||
Reference in new issue
Block a user