mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-05 16:57:32 +00:00
## 一、插件声明层(用户裁定)
> 「敏感词策略也应该是插件声明啊,每个插件声明自己轮次以及自己工具的
> 敏感词策略」
SecurityProfile 新增两个字段:
ToolSensitiveWords map[string][]string // key=工具名模式(尾 *),value=词表
DetectInjection bool // 开启内核内置注入迹象识别
语义是**追加不覆盖**:整轮 SensitiveWords 仍然生效。
取宽不取窄的理由:当前处置是 B 档(标记+警示,不阻断),
多标一条只是多一句警示,漏标则是安全缺口。
## 二、修掉我自己造的「莲藕式分层」
前一版把 *security.Round 从 runInputTask 一路搬到工具链
(prepareInputTask(evt, round) → TaskFrame.SecRound →
executeToolCallOutcomeWithRound),并在 rtchild 里另补一条。
那是把**分层策略实现成跨层传参**:每多一条工具执行路径就要求调用方
多传一次句柄。profileActivator 的设计注释早就写明了正路:
> 工具调用发生在 LLM 决定之后,此时插件已不在调用栈上。
> 唯一可靠的传递方式是**内核在输入进来时记住**,在该轮的工具判定里使用。
改为 security 自取:
Runtime.ScanContentForAgent(agentID, isChild, ins, text)
→ ForAgent(agentID, isChild) // per-agent 视图,天然不串档
→ EffectiveProfile() // session > longtime
→ 合并 全局词表 + 档位词表 + 工具词表 + DetectInjection
core 只递交**事实**(我是谁 / 哪个工具 / 什么文本),不认识 Round。
与 CheckToolForAgent 同一套身份推断,不引入第二个入口语义。
TaskFrame.SecRound / executeToolCallOutcomeWithRound 全部撤除。
## 三、目录名统一
third_party/homeagent-sdk → third_party/hologramagentsdk
module 与远端仓早已是 hologramagentsdk,只剩物理目录是遗留名。
同步改了 go.mod replace、构建/打包/部署脚本、5 处测试里的分段路径。
注意:这里改的是**主仓 vendored 的 SDK 源码路径**,example/ 由 SDK
独立仓自己跟踪,主仓不涉及。
## 判据
TestToolSensitiveWordsAppendOnlyForThatTool 词表只对目标工具生效 + 追加语义
TestToolSensitiveWordsUseAgentScope security 按 agent scope 自取生效
(变异验证:删掉 ForAgent 查询 → 红)
## 验证
go build ./... → 0
go vet ./... → 0
go test ./... -count=1 -race → 62 ok / 0 FAIL / 0 DATA RACE
gofmt -l internal/ → 干净
127 lines
4.9 KiB
Go
127 lines
4.9 KiB
Go
package meta_test
|
||
|
||
import (
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// ===== 主仓不再 vendoring SDK 的示例代码 =====
|
||
//
|
||
// 决策(对应 .gitignore 里的 sdk_repo_only):外部插件与工具链只在 SDK 仓
|
||
// 维护,主仓经 go.mod 的 replace 引用,**只保留编译所需的那几个包**。
|
||
//
|
||
// 曾经的状况:主仓跟踪了 29 个 SDK 示例文件(example/*/plg.json、plugin.go、
|
||
// qq/plugin_test.go,加 remotedevice/ 的 8 个 C 文件)。它们不参与主仓构建
|
||
// (唯一 import 的 SDK 包是 sdk/ 与 meta/),却要在两个仓里同步同一份代码。
|
||
// 本轮修 example/bili 时的重复劳动就是这么来的。
|
||
//
|
||
// 本测试钉住这个决策:有人再把示例代码提交进主仓时立刻变红。
|
||
// ★ 必须用 git ls-files 判断「**版本控制里有没有**」而不是看目录是否存在 ——
|
||
// 工作树里可能因为 vendoring 或本地试验而临时存在,那不算回归。
|
||
|
||
// vendoredSDKRoot 是 vendored SDK 在主仓里的位置。
|
||
const vendoredSDKRoot = "third_party/hologramagentsdk"
|
||
|
||
// repoRoot 返回主仓根目录(本测试包在 internal/meta/,往上是两层)。
|
||
func repoRoot(t *testing.T) string {
|
||
t.Helper()
|
||
abs, err := filepath.Abs(filepath.Join("..", ".."))
|
||
if err != nil {
|
||
t.Fatalf("解析仓库根: %v", err)
|
||
}
|
||
return abs
|
||
}
|
||
|
||
// gitTracked 列出 git 版本控制里该路径下的文件(相对仓库根)。
|
||
//
|
||
// ★ 必须用 `git -C <仓库根>` 而不是就地执行:git ls-files 的路径参数
|
||
//
|
||
// 是**相对当前目录**解析的,而这个测试运行在 internal/meta/ 下,
|
||
// 就地执行会返回空 —— 表现为"必需包全都不在"的假红(我第一版就这样)。
|
||
func gitTracked(t *testing.T, pathPrefix string) []string {
|
||
t.Helper()
|
||
out, err := exec.Command("git", "-C", repoRoot(t), "ls-files", pathPrefix).Output()
|
||
if err != nil {
|
||
// 不是 git 仓库 / git 不可用:跳过而不是误报。
|
||
if _, statErr := os.Stat(filepath.Join(repoRoot(t), ".git")); statErr != nil {
|
||
t.Skip("非 git 仓库,跳过结构守卫")
|
||
}
|
||
t.Fatalf("git ls-files %s: %v", pathPrefix, err)
|
||
}
|
||
var files []string
|
||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||
if line = strings.TrimSpace(line); line != "" {
|
||
files = append(files, line)
|
||
}
|
||
}
|
||
return files
|
||
}
|
||
|
||
// example/ 与 remotedevice/ 不该再被主仓跟踪。
|
||
func TestVendoredSDKHasNoExampleOrRemotedevice(t *testing.T) {
|
||
for _, sub := range []string{"example", "remotedevice"} {
|
||
files := gitTracked(t, vendoredSDKRoot+"/"+sub)
|
||
if len(files) > 0 {
|
||
t.Errorf("主仓仍在跟踪 %s/ 下的 %d 个文件(决策:示例代码只在 SDK 仓维护):\n %s\n"+
|
||
" 这些文件不参与主仓构建(唯一 import 的 SDK 包是 sdk/ 与 meta/),\n"+
|
||
" 却要在两个仓同步同一份代码 —— 本轮修 example/bili 时已因此重复劳动。\n"+
|
||
" 修复:git rm -r --cached %s/%s",
|
||
sub, len(files), strings.Join(files, "\n "), vendoredSDKRoot, sub)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 反向保护:编译真正需要的包必须在,否则删过头会编译失败。
|
||
//
|
||
// 这条不能省:上一条只防"多了",若有人为了省事把 sdk/ 或 meta/ 也删了,
|
||
// 上一条照样绿,而 go build ./... 会在 CI 里才炸。
|
||
func TestVendoredSDKKeepsRequiredPackages(t *testing.T) {
|
||
required := []string{
|
||
"sdk", // 公开契约:所有插件的入口
|
||
"sdk/plugin.go", // 具体文件:确保不是空目录
|
||
"meta", // 版本号:主仓与 SDK 版本一致性检查
|
||
"meta/meta.go",
|
||
// ★ 不列 tools/hmapdev/yaegi:.gitignore 第 33 行忽略了
|
||
// third_party/hologramagentsdk/tools/,它从来没被跟踪过。
|
||
// 而 grep 到的 yaegi 引用是 **SDK 仓自己的文件**在互相 import
|
||
// (tools/hmapdev/yaegi/interp.go → .../mocksdk),不是主仓依赖。
|
||
// 我第一版把它当主仓依赖写进来,判据直接假红。
|
||
}
|
||
tracked := map[string]bool{}
|
||
for _, f := range gitTracked(t, vendoredSDKRoot) {
|
||
tracked[f] = true
|
||
}
|
||
for _, req := range required {
|
||
// 支持两种形态:目录(下面有文件)或直接是文件
|
||
found := tracked[vendoredSDKRoot+"/"+req]
|
||
if !found {
|
||
// 目录形态:检查它下面是否有被跟踪的文件
|
||
for f := range tracked {
|
||
if strings.HasPrefix(f, vendoredSDKRoot+"/"+req+"/") {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
}
|
||
if !found {
|
||
t.Errorf("必需包 %s 不在主仓 vendored 范围内(删过头了):%s/%s",
|
||
req, vendoredSDKRoot, req)
|
||
}
|
||
}
|
||
}
|
||
|
||
// go.mod 的 replace 必须仍指向 vendored 路径 —— 决策 A 依赖它。
|
||
func TestGoModStillReplacesSDKToVendoredPath(t *testing.T) {
|
||
data, err := os.ReadFile(filepath.Join(repoRoot(t), "go.mod"))
|
||
if err != nil {
|
||
t.Fatalf("读 go.mod: %v", err)
|
||
}
|
||
content := string(data)
|
||
if !strings.Contains(content, vendoredSDKRoot) {
|
||
t.Errorf("go.mod 里没有 replace 到 %s:\n%s", vendoredSDKRoot, content)
|
||
}
|
||
}
|