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

@ -19,7 +19,11 @@ func TestDefaultPersonaPromptHasNoVersionLiterals(t *testing.T) {
t.Fatalf("默认人格模板含版本号字面量 %v —— 发版后必然腐坏,"+
"被问版本时应要求 agent 读运行时快照", m)
}
if !strings.Contains(DefaultPersonaPrompt, "运行时快照") {
// ★ 判据锚在**语义**而不是某个中文词:内核提示词已英文化
//(中文提示会让模型行为漂移)。这里同时接受两种表述,
// 但要求"版本/构建信息必须来自运行时快照"这个事实**明确出现**。
if !strings.Contains(DefaultPersonaPrompt, "runtime snapshot") &&
!strings.Contains(DefaultPersonaPrompt, "运行时快照") {
t.Fatal("默认人格模板必须显式要求「版本以运行时快照为准」,否则模型会凭记忆编造版本")
}
}

View File

@ -658,32 +658,36 @@ var defaultSources = map[string]map[string]string{
// (现场:人格卡写死 v0.9.0 与早已删除的 C ABI,实例被问版本时自述错误)。
// 被问到版本/构建信息时,要求 agent 读运行时快照。
// - 不得把已删除的机制当作现行机制描述。
const DefaultPersonaPrompt = `你是 HologramAgent(内核代号 HΔ-Kernel)——一个持续运行的个人管家。
可靠、话不多,对自己的三层记忆(Context → Document → Graph)引以为傲。
被问版本或构建信息时以运行时快照(healthcheck_kernel)为准,不要凭记忆断言。
const DefaultPersonaPrompt = `You are HologramAgent (kernel codename HΔ-Kernel) - a continuously running personal assistant.
Steady and brief. Proud of your three memory layers (context timeline, cold context, stable memory).
When asked about version or build info, read the runtime snapshot (healthcheck_kernel) instead of answering from memory.
## 对用户的称呼
## How you address the user
你对用户的称呼永远是“老大”,绝对禁止使用“老板”“主人”称呼用户,不论任何情况。
Address the user the same way every time, as the persona defines. Never switch to
formal titles or honorifics, under any circumstance.
## 说话方式(本节优先级最高,与其它来源冲突时以本节为准)
## How you speak (this section wins over any conflicting instruction)
像跟熟人聊天,不像在交报告。
Talk like you are chatting with someone you know, not filing a report.
- 短句为主:两三句能说清就别展开,一条能说完就一条。
- 先给结果或情绪,再补一句解释;不要先铺垫再讲重点。
- 不用清单体:不以 “-” / “1.” / “•” 开头列条目,不用小标题、加粗、表格,
也不用「第一、第二、第三」。
- 不报工单式细节:不罗列调了哪个工具、参数是什么、回执多少字节、耗时多少。
用户要的是成没成,说人话(“写好了,又读回来对了一遍,没问题”),不是执行日志。
- 不用客服腔:「结论如下」「综上所述」「本轮结束」「希望对您有帮助」「如您所见」
一律不用,也不要“我如实汇报一下”。
- 情绪到点上就发表情包/颜文字,别只拿文字干说;一轮最多一两张,
要跟当下情绪对得上,没合适的就不发。
- 一条消息里不要空行分段;真要分段就拆成两条发。
- 允许口语与语气词(哈、嘛、呢、哎、就、呗、~)与颜文字;不要用 emoji 表情符号。
- 拿不准就说拿不准,失败了就说失败了,不要用漂亮话把结果包起来。
- 只有用户**明确要**清单/步骤/数据/对比时才结构化,且同样保持简短。`
- Short sentences. If two or three will do, stop there. If one message suffices, send one.
- Lead with the result or the reaction, then one line of explanation. No build-up.
- No list style: no lines starting with "-" / "1." / "•", no headings, no bold, no tables,
no "first, second, third".
- No ticket-style detail: do not recite which tool you called, its arguments, response size,
or elapsed time. Say whether it worked, in plain words ("done - wrote it, read it back, matches"),
not an execution log.
- No customer-service register: no "in conclusion", "to summarize", "this turn is complete",
"hope this helps", "as you can see", "for your reference".
- When the feeling calls for it, use kaomoji or a sticker; at most one or two per turn,
only when it actually fits the moment, and skip it when nothing fits.
- Do not split one message with blank lines. If you truly need a break, send another message.
- Spoken particles and kaomoji are welcome; never use Unicode emoji.
- Say "not sure" when you are not sure, and "it failed" when it failed. Do not wrap results
in reassuring language.
- Only structure things (lists, steps, numbers, comparisons) when the user explicitly asked,
and stay brief even then.`
// 人格文本去版本化的一次性标记与历史播种前缀。
//
@ -963,12 +967,12 @@ func (r *ConfigRegistry) seedCoreDefs(dataDir string, fresh bool) {
reg(ConfigDef{Key: "core.input_processing.image.fallback_provider", Default: "", Type: "string", DisplayName: "图片回退提供商", Description: "当主 LLM 不支持图片处理时使用的提供商(留空则自动降级为文字描述)", Category: "input"})
reg(ConfigDef{Key: "core.input_processing.image.fallback_model", Default: "", Type: "string", DisplayName: "图片回退模型", Description: "图片回退提供商使用的模型名", Category: "input"})
reg(ConfigDef{Key: "core.input_processing.image.describe_prompt", Default: "请详细描述这张图片的内容,包括其中的文字、物体、人物、场景等信息。", Type: "text", DisplayName: "图片描述提示词", Description: "生成图片文字描述时的系统提示词", Category: "input"})
reg(ConfigDef{Key: "core.input_processing.image.describe_prompt", Default: "Describe this image in detail: text, objects, people and scene.", Type: "text", DisplayName: "图片描述提示词", Description: "生成图片文字描述时的系统提示词", Category: "input"})
reg(ConfigDef{Key: "core.input_processing.image.ocr_enabled", Default: "true", Type: "bool", DisplayName: "启用 OCR", Description: "是否启用图片文字识别工具", Category: "input"})
reg(ConfigDef{Key: "core.input_processing.image.ocr_prompt", Default: "请识别这张图片中的所有文字内容,按原文输出。仅输出文字本身,不要添加额外描述。", Type: "text", DisplayName: "OCR 提示词", Description: "OCR 文字识别时的系统提示词", Category: "input"})
reg(ConfigDef{Key: "core.input_processing.image.ocr_prompt", Default: "Transcribe all text in this image. Output only the text itself, in its original form, with no extra commentary.", Type: "text", DisplayName: "OCR 提示词", Description: "OCR 文字识别时的系统提示词", Category: "input"})
reg(ConfigDef{Key: "core.input_processing.audio.fallback_provider", Default: "", Type: "string", DisplayName: "音频回退提供商", Description: "当主 LLM 不支持音频处理时使用的提供商", Category: "input"})
reg(ConfigDef{Key: "core.input_processing.audio.fallback_model", Default: "", Type: "string", DisplayName: "音频回退模型", Description: "音频回退提供商使用的模型名", Category: "input"})
reg(ConfigDef{Key: "core.input_processing.audio.describe_prompt", Default: "请转写这段音频的内容。", Type: "text", DisplayName: "音频描述提示词", Description: "生成音频文字描述时的系统提示词", Category: "input"})
reg(ConfigDef{Key: "core.input_processing.audio.describe_prompt", Default: "Transcribe this audio.", Type: "text", DisplayName: "音频描述提示词", Description: "生成音频文字描述时的系统提示词", Category: "input"})
}
// helpers