Files
HomeAgent/internal/agent/core/security_profile.go
JianFeeeee b850feaef2 feat: 段页式记忆流转、coldcontext 重命名与安全治理
记忆层:
- MemoryBlock 承载 FrameTS/PageSeq/PageKind/BlockRole/CallID
- coldcontext 归档与回填按完整页传递,工具调用参数和配对键不丢失
- 召回先估算总 token,超预算时按全局页相似度标记 inactive
- inactive 仅是 context 渲染期状态,不落盘、不进入 coldcontext,Prune 后重置
- Peek/确认/Remove 两阶段召回,失败保留冷上下文副本
- stablemem 记录 last_recalled_at,删块连带清理边

命名:
- internal/memory/document → internal/memory/coldcontext
- Doc/DocEvent/DocHit → ColdDoc/ColdEvent/ColdHit
- ContextToColdContext/RecentColdContexts/FindColdContexts
- 保留既有 JSON 标签、doc_ 文件前缀与 doc_count 状态键,兼容存量数据

安全治理:
- 工具策略门禁、安全配置、事件总线发布者身份与入站脱敏
- 声明式 SecurityAPI 与 QQ owner/guest profile
- 日志保留策略与相关 e2e/回归判据

验证:go build ./...、go vet ./...、go test ./...(62 包);
生产副本 e2e:7013 块迁移后 ID/向量/指纹 0 变化、0 缺失。
2026-10-29 23:53:48 +08:00

81 lines
3.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package core
// 插件声明限式的**生效点**:inputch 输入进来时激活,轮次内据此判定。
//
// ## 为什么在这里(而不是在工具调用时)
//
// 插件在注入时带上 `security_profile`,那一刻是**唯一**能确定
// "这条输入属于哪个身份档位"的时刻。等 LLM 决定调工具时,插件早已
// 不在调用栈上(异步通道 + 多轮工具循环),档位信息就断了。
//
// 所以:输入到达 → 激活档位(带 TTL)→ 该轮的工具/内容判定都读它。
//
// ## 缺省语义
//
// 没有携带 `security_profile` = **不使用档位**(沿用内核默认策略)。
// 这是可选参数:不声明档位的插件行为与之前完全一致。
//
// 携带了**未声明**的档位名 ⇒ 记日志并回退到默认策略,但**不改写输入**:
// 注入是 fire-and-forget,插件看不到内核错误;至少要在日志里留下
// 「你声明的档位没生效」的痕迹(与 cleaner_name 同一纪律)。
import (
"log"
"github.com/JianFeeeee/HologramAgent/internal/security"
)
// activateSecurityProfile 按输入 payload 激活插件声明的安全档位。
//
// 返回被激活的档位名(空 = 未使用档位),仅供日志/诊断。
func (a *Agent) activateSecurityProfile(payload map[string]interface{}) string {
if a == nil || a.security == nil || payload == nil {
return ""
}
name, _ := payload["security_profile"].(string)
if name == "" {
return ""
}
if err := a.security.ActivateProfile(name, 0); err != nil {
// 响亮但不断流:拼错档位名不该让整条输入丢失,
// 但也绝不能静默 —— 那等于悄悄降级到默认策略。
log.Printf("[agent] input declared security_profile=%q but activation failed: %v", name, err)
return ""
}
return name
}
// securityDecisionFor 用当前活跃档位 + 主体判定某工具是否可用。
//
// 工具执行前的统一检查点:调用方拿到 !Allowed 时应把 Reason 原样回给模型
// (统一拒绝语义 —— 模型据此能修正,而不是反复重试同一调用)。
//
// ★ 2026-10-29:本函数此前是**死代码**(`a.security` 从未被赋值 ⇒ 永远
// 走 nil 分支 ⇒ 永远放行),`Policy.CheckTool` 因此对工具执行链完全不生效。
// 修复分两步:① 补 `SetSecurity` 注入通道;② 在 `executeToolCallInner`
// 的**所有实际执行分支之前**调用本函数。
func (a *Agent) securityDecisionFor(tool string) security.Decision {
if a == nil || a.security == nil {
return security.Decision{Allowed: true}
}
return a.security.CheckTool(tool)
}
// SetSecurity 注入安全运行时(装配期调用,幂等)。
//
// ## 为什么需要显式注入点
//
// `Agent.security` 曾经是**只有测试赋过值**的字段:生产装配链里没有任何
// 地方写它,于是 `securityDecisionFor` 恒定放行。这不是"策略写错了",
// 而是"策略根本没接上",而两种故障在日志上看起来一样(都是没有拒绝记录)。
//
// ## 为什么允许 nil
//
// 测试与独立工具(memgc 等)不需要安全模块;传 nil 表示显式不启用。
func (a *Agent) SetSecurity(rt *security.Runtime) {
if a == nil {
return
}
a.security = rt
}