feat(security,device): security 成为独立内核模块(闸门+策略+限式),device 接管内置工具路由

security(新模块 internal/kernelmod/security)
- 凭据闸门:出向拦截成 @{别名}、入向清洗、执行前回填(internal/security/{gate,runtime}.go)
- 工具黑白名单 + 沙箱(读写根、网络、.. 逃逸):internal/security/policy.go
- 敏感词过滤 + 提示词注入预警(Finding 不带原文):internal/security/content.go
- 按主体授权(hard-private 对非内部主体永久拒绝):internal/security/principal.go
- 插件命名限式(可选档位,插件声明/内核执行,只能收窄):internal/security/profile.go
- 启动装配从 cmd/homed 迁入模块;启动顺序判据改盯真实装配点

device(新模块)
- 正式内核模块,Provides FacDevice,KernelState.Device
- 内置工具路由表下沉到 internal/toolroute(避免 core↔kernelboot 环),
  device 保留对外入口;agent 未挂载时退化本地 switch
- Agent 侧只留 api.ToolHost/DeviceSurface 适配层

SDK
- InjectOptions.SecurityProfile:插件按 inputch 声明本轮安全档位(可选)
- InjectOptions.TurnHint:插件注入**本轮事实**,内核以独立 system 消息注入,
  不混进用户正文(人设负责怎么说话,TurnHint 只放这一轮成立的事实)
- FacilitySecurity / FacilityDevice

提示词
- 内核自带 prompt/skill/工具返回全面英文化(中文提示会让模型行为漂移)
- 机制解释下沉到 4 个内置 skill(memory/output/tool-order/plugins),
  skill 描述压到 80 runes 内(原来 94–108 全被索引截断)
- 默认人格模板英文化;[Voice] 段删除,规则并入 persona 块(单一段落)
- 历史记忆工具改名 coldcontext_query/commit,图记忆语义改称 stable memory

修复
- tracker: exec mount → unix.Mount(降权运行 + AmbientCapabilities 下才能挂)、
  区分 EPERM 与不支持;启动日志说明后果与恢复方式
- healthcheck_kernel 输出瘦身:status.Tools 只给摘要(原 98,989 tokens/151% 预算)
- plugin_list 描述讲清 auto_start(按需)与 alive(此刻是否在跑),
  明确不能由 alive=false 推断能力不存在
- pluginmgr/webui 去掉写死的 /home/newqqagent 回退路径(env → pluginDir 反推 → 报错)
- lua: 盘上适配器与内置版本不一致且无基线时响亮告警(deepseek 流式丢失
  tool_calls 导致'模型不调工具'的真因)

测试:go build ./... 通过;go test ./... 60 包全绿
This commit is contained in:
JianFeeeee
2026-10-26 20:55:25 +08:00
parent ca956f69bf
commit 55fa2685ae
92 changed files with 3932 additions and 596 deletions

View File

@ -0,0 +1,159 @@
package core
// Agent 侧的 **device 数据面适配层**。
//
// ## 为什么适配层在 core 而不是让 device 直接操作 Agent
//
// 工具执行必须**始终**经过 Agent 的完整执行链:
//
// 参数 schema 校验 → 工具授权闸 / 设备授权闸 → 超时与取消
// → panic 恢复 → 明文脱敏(出向 + 入向)→ ToolResult 成败判定
//
// 这些是**执行安全**,与敏感信息安全(security 模块)是两件事,
// 都不能被对方顶替(见 plan.md §五)。所以 device 只拿到窄接口
// (api.ToolHost),拿不到 Agent 内部字段,也就绕过不了这条链。
import (
"sort"
"github.com/JianFeeeee/HologramAgent/internal/agent/api"
)
// ToolDefinitions 实现 api.ToolHost:返回当前可见的工具定义。
//
// ★ 复用 buildToolDefs 的同一批生成逻辑 —— 两处各写一份必然漂移
// (历史上就出现过"模型看得到但插件查不到"的两套语义)。
func (a *Agent) ToolDefinitions() []api.ToolDefinition {
if a == nil {
return nil
}
raw := a.buildToolDefs()
out := make([]api.ToolDefinition, 0, len(raw))
for _, item := range raw {
m, ok := item.(map[string]interface{})
if !ok {
continue
}
fn, ok := m["function"].(map[string]interface{})
if !ok {
continue
}
name, _ := fn["name"].(string)
if name == "" {
continue
}
desc, _ := fn["description"].(string)
out = append(out, api.ToolDefinition{
Name: name,
Description: desc,
// 并发资格与超时**不在这里重算**:它们由 argvalidate.go 的
// batchRunnable / resolveToolTimeout 统一判定(单一事实来源)。
ParallelSafe: a.toolParallelSafe(name),
Timeout: "",
})
}
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
return out
}
// ExecuteTool 实现 api.ToolHost:走完整执行链执行一次工具调用。
//
// 通道用内置标记:工具执行本身不产出"用户可见输出",
// 而记忆写入等场景需要一个稳定的来源标识(见 defaultChannelForBuiltin)。
func (a *Agent) ExecuteTool(name string, args map[string]interface{}) (string, error) {
if a == nil {
return "", errNoAgentForDevice
}
return a.executeToolCall(api.ToolCall{
ID: "device_" + name,
Name: name,
Arguments: args,
}, a.defaultChannelForBuiltin()), nil
}
// MountDevice 挂载 device 数据面。
//
// 单向依赖:core 只认识低层接口(api.DeviceSurface),不认识
// kernelmod/device 的具体类型 —— 否则 core ↔ kernelboot ↔ device 会成环。
//
// 幂等:重复挂载只覆盖(装配期可能因重建 Agent 而重挂)。
func (a *Agent) MountDevice(d api.DeviceSurface) {
if a == nil {
return
}
// 挂载同时接上路由适配器:device 由此拿到"选族后调谁"。
a.builtinHandlers = builtinHandlers{a: a}
a.deviceMu.Lock()
a.device = d
a.deviceMu.Unlock()
}
// Device 返回已挂载的数据面(未挂载时为 nil)。
func (a *Agent) Device() api.DeviceSurface {
if a == nil {
return nil
}
a.deviceMu.RLock()
defer a.deviceMu.RUnlock()
return a.device
}
// errNoAgentForDevice 表示 device 调用时 Agent 未装配(装配顺序错误)。
var errNoAgentForDevice = &deviceHostErr{"device: agent 未装配"}
type deviceHostErr struct{ msg string }
func (e *deviceHostErr) Error() string { return e.msg }
// builtinHandlers 把 Agent 的族处理器暴露成 api.BuiltinToolHandlers。
//
// 为什么用适配器而不是把 Agent 的方法全部导出:
// - 导出只会让"谁都能调内置工具",绕过 executeToolCall 的授权闸
// - 适配器只在 core 内部构造,然后交给 device 的路由层
type builtinHandlers struct{ a *Agent }
func (h builtinHandlers) ExecutePersonaTool(tc api.ToolCall) string {
return h.a.executePersonaTool(tc)
}
func (h builtinHandlers) ExecuteMemoryTool(tc api.ToolCall, scenes []string) string {
return h.a.executeMemoryTool(tc, scenes)
}
func (h builtinHandlers) ExecuteSocialTool(tc api.ToolCall) string {
return h.a.executeSocialTool(tc)
}
func (h builtinHandlers) ExecuteKnowledgeTool(tc api.ToolCall) string {
return h.a.executeKnowledgeTool(tc)
}
func (h builtinHandlers) ExecuteColdContextTool(tc api.ToolCall) string {
return h.a.executeDocTool(tc)
}
func (h builtinHandlers) ExecuteSecretTool(tc api.ToolCall) string {
return h.a.executeSecretTool(tc)
}
func (h builtinHandlers) ExecuteOutputSendHelp(tc api.ToolCall) string {
return h.a.executeOutputSendHelp(tc)
}
func (h builtinHandlers) ExecuteOutputSend(tc api.ToolCall) string {
return h.a.executeOutputSendTool(tc)
}
func (h builtinHandlers) ExecuteOutputListChannels() string {
return h.a.executeOutputListChannels()
}
func (h builtinHandlers) ExecuteInputChannels(tc api.ToolCall) string {
return h.a.executeInputChannels(tc)
}
func (h builtinHandlers) ExecuteResidentAgents(tc api.ToolCall) string {
return h.a.executeResidentAgents(tc)
}
func (h builtinHandlers) ExecuteNotifyParent(tc api.ToolCall) string {
return h.a.executeNotifyParent(tc)
}
func (h builtinHandlers) ExecuteInputchNote(tc api.ToolCall) string {
return h.a.executeInputchNote(tc)
}
func (h builtinHandlers) ExecutePluginReload() string {
return h.a.executePluginReload()
}
func (h builtinHandlers) ExecuteGetPluginTools(pluginName string) string {
return h.a.executeGetPluginTools(pluginName)
}