Commit Graph

3 Commits

Author SHA1 Message Date
6d38a164eb chore(module): Go module 路径随托管地迁移 gitcode → github
## 背景

仓库早已迁移到 GitHub(`github.com/JianFeeeee/HomeAgent`,CI/CD 都在那边跑),
gitcode 保留为**国内只读镜像**(`ci.yml` 的 sync-gitcode-main 快进推送)。
但 go.mod 的 module 路径一直是 `gitcode.com/JianFeeeee/...` —— 既与权威源
不符,`meta.SourceURL`(AGPL §13 的源码提供入口)也指向镜像而非实际运行的代码。

## 关键区分(这是本次改动安全的前提)

两个 module 是**不同**的,影响面差一个数量级:

| module | 谁依赖 | 改它的后果 |
|---|---|---|
| `.../HomeAgent`(主仓) | **只有主仓自己** | 内部重构,零外部影响 |
| `.../homeagent-sdk`(SDK 仓) | 主仓 49 文件 + 所有外部插件 | 需重编插件 |

★ 实测确认:**插件不 import 主仓 module**(`strings qq/plugin.bin | grep HomeAgent` = 0),
只依赖 SDK 仓。而插件与内核的边界是 **RPC 方法名字符串**
(`plugin.init` / `tool.invoke`…,见 proc/protocol.go),**与 Go module 名无关**。
⇒ module 改名**不改变任何 wire 协议**,现网已装的插件照常运行。

SDK 的 GitHub 仓名是 `homeagentsdk`(无连字符),module 路径必须与之精确匹配,
否则 `go get` 会因路径不符失败,故取 `github.com/JianFeeeee/homeagentsdk`。

## 改动

- `go.mod` / `go.sum`:两个 module 路径 + require/replace
- 277 个 Go 文件 + Makefile + 打包脚本的 import 与 ldflags 路径
- SDK 仓 `meta.CoreModule`、`tools/hmapdev` 的 module 声明与工程模板
- 对外文档/链接的仓库地址(README、PLUGIN_DEV、OVERVIEW 等)

## 明确**保留** gitcode 的地方(镜像机制,不能动)

- `GITCODE_TOKEN`、`GC_URL="…@gitcode.com"`(CI 同步 job)
- `gitcode.com/api/v5/...`(release 附件上传)
- git remote `gitcode`(镜像拉取/推送)
- `docs/zh/ci-cd-runbook.md`(整篇讲镜像运维)

## 验证

- `go build ./...` / `go vet ./...` 绿
- `go test ./... -count=1` → 48 包 ok / 0 FAIL
- hmapdev 工具链编译通过(`third_party/homeagent-sdk/tools/hmapdev`)
- 全仓残留检查:module 形态的 `gitcode.com/JianFeeeee` = 0
- 镜像机制抽验:GC_URL / @gitcode / api/v5 / remote 均完好

## 顺带修正

`hmapdev/templates.go` 的注释此前写「gitcode 的模块不在 proxy.golang.org 上」——
module 已迁 GitHub,该论断不再成立。改为如实说明**为何仍默认 replace 到本机 SDK**:
离线可构建、避免版本漂移、内网可用。理由与托管地无关,故 replace 保留。
2026-10-08 10:40:17 +08:00
d003db7304 feat(kbtree): 知识库暴露范围配置 —— 按树状只暴露指定分类
## 问题

kbtree 是**唯一**把知识库开放给外部进程的通道(HomeAgent 自己的 agent
走进程内直调 knowledge_* 内核工具,不经此),但它只有 listen_addr 与
token 两个配置,**没有任何范围限制**:拿到 token 的任何 agent 都能
/tree 列出全部条目、/search 取回任意条目全文。

本机库里混着个人内容(航空发动机教材摘录、课表、身份合并规则),
不该 broadly 可读。

## 改动

1. `internal/plugins/kbtree/scope.go`(新):暴露范围语义
   - 留空 = 全部可见(范围是"限制"不是"必填",留空保持既有行为)
   - 前缀按**路径分段**匹配:public 命中 public 与 public/tech,
     但**不**命中 publication(否则 publication 意外暴露)
   - 根下无分类的条目在范围非空时不可见 —— 它没有分类可匹配,
     放行等于范围形同虚设
   - 分隔符容忍逗号/分号/空白/换行/竖线:这是给人手填的字段

2. `plugin.go`:注册 `expose_categories` 配置项,接入**全部四个端点**
   - /tree      服务端裁剪子树(就地改,不重建:TreeView 字段多)
   - /categories 过滤路径列表
   - /counts    过滤计数并**重算 total**(数量本身也是信息泄露)
   - /search    ★ 过滤结果条目;这处最关键:
     只过滤 /tree 而放过 /search 等于范围形同虚设(换个 ?q= 就能拿到全文)。
     同时修正 limit 语义 —— 范围外条目不占名额,范围内条目不会被挤掉。

3. SDK 契约补 `Knowledge.Category`(纯增量)
   - 此前 `sdk.Knowledge` 只有 Name/Content,内核明明返回了 Category
     却在 knowledge_impl 的拷贝里丢掉 ⇒ 外部服务无法按分类判定,
     范围过滤在 SDK 层根本做不了。
   - Name/Content 均保留,无删除。

## 判据(8 条 + 4 组变异)

范围过滤最容易"只做一半",所以每个端点都单独钉。

★ 判据补强一处:初版只查条目名(priv1),结果「/categories 不过滤」
  这个变异**完全逃过** —— 分类端点返回的是路径不是条目名。
  补上分类路径断言(private)后判红。

变异验证:
- /search 不过滤      → 泄露 priv1 全文        ✓ 判红
- /categories 不过滤  → 泄露 private 分类路径   ✓ 判红(补强后)
- /counts 不过滤      → TestCategoriesAndCountsEndpoint 判红
- 前缀退化为字符串前缀 → publication 被误暴露      ✓ 判红

★ 过程中我的 fake 有两处与真实内核不符,先修 fake 再修实现:
  1. 漏了内核 treeLocked 的"子分类提升一层" ⇒ 得到 children=0 的假空树
  2. filterTree 无差别清空 t.Items ⇒ 整棵树只剩空壳节点
  (第一版的实现是"看着测试红就改",实际是 fake 在骗我)

全量 41 包绿。SDK 接口纯增量,未发布故无需冻结检查。
2026-09-26 15:31:15 +08:00
55d5f2a1d0 feat(kbtree): 知识库分类树的独立只读服务 + agent 技能 + WebUI 树浏览
让**外部 agent** 也能按分类树用这套知识库。HomeAgent 自己的 agent 仍
直接调内部方法(knowledge_search/create 等),走进程内直调,不经此服务。

一、内核树视图(internal/knowledge/tree.go)
  为什么不复用 TreeIndex:那个是**内部导出物**,面向 .index.json 落盘,
  每个条目带 top-20 的 TF-IDF 特征向量。直接序列化给外部有三个问题:
  体积(200 条时 .index.json 已 246KB 且冗余存了 preview,而正本在
  content.md)、泄漏(稀疏特征表 = 分词/IDF 内部表示)、语义错位
  (外部要的是"有哪些分类、每类下有什么")。
  新增 TreeView/Subtree/Categories/CategoryCounts:不含向量,带条目数
  与可读摘要,支持 MaxDepth 懒加载、IncludeItems 只看结构。
  节点 Name 是**本级段名**("go")、Path 是完整路径("tech/go")——
  最初把全路径写进 Name,前端拼层级会得到 "tech/tech/go",已修。

二、kbtree 插件:独立 HTTP 服务(默认 127.0.0.1:9892)
  为何不挂在 WebUI 的 /api/v1/knowledge* 下:
  1. 不共享鉴权与端口。WebUI 的 api_key 是给人操作界面用的,把它分发给
     外部 agent 等于把管理面凭据扩散出去。本服务用**独立 token** +
     独立端口,可单独关闭(token 未配置则启动时随机生成)。
  2. 只读。写入要决定分类归属与媒体处理,外部自行拼装容易造出越界/重名
     条目 —— 写入留给内核工具。
  3. 形状按树组织,而不是平铺搜索接口。
  端点:/tree(可指定 category/depth/items)、/categories、/counts、
  /search、/ (自述)。全部需 token(X-API-Key / Bearer / ?token=),
  非 GET 一律 405。无知识库时 Start 直接失败,不占端口。
  鉴权与 Slowloris/超时设置照 remotedevice 范式。

三、agent 技能(assets/skills/knowledge-base/SKILL.md)
  指令文档型 skill:教模型"先看树 → 定位分类 → 分类内检索",并列出
  易错点(name 已含分类别再拼、只看第一条、404 附现有分类)。
  加载与校验由 internal/plugin/skill_bundled_test.go 守住 —— 这条断言
  的由来:非白名单的二级标题会被 extractToolDefs 当成工具定义,报错
  "invalid tool name",而提示与真正原因(标题层级)毫无关联。
  kbtree 的测试还会校验文档提到的端点与代码一致,防漂移。

四、WebUI 树浏览(前端真正用起来,而非留一个没人调的端点)
  面板加可折叠的分类树:逐级点选即把搜索范围切到该子树(原先是让人
  手打分类名)。当前范围有可见标签与「全库」复位。
2026-09-26 14:20:19 +08:00