Files
HomeAgent/internal/agent/core/builtin_toolapi.go
JianFeeeee 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

138 lines
5.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
import (
"strings"
agentAPI "github.com/JianFeeeee/HomeAgent/internal/agent/api"
sdk "github.com/JianFeeeee/HomeAgent/internal/sdk"
)
// 本文件实现内核侧对"内置工具注册面"(方案 B)的注入。
//
// 背景与方案 B 的完整理由见 internal/sdk/tool.go 末尾的注释。
// 一句话:`memory_*` / `knowledge_*` / `doc_*` / `person_*` 这 20+ 个是
// **内核内置**的,在 executeToolCallInner 里按前缀分派,从不进 ToolAPI,
// 于是插件(seq)既查不到也调不了 —— 真机实跑实证:seq_run 报
// 「工具 knowledge_list 不存在或未注册」,而同一轮模型直接调它是成功的。
//
// 注入必须**晚绑定**且**per-agent**:内置工具的可见性由运行期状态门控
// (`if a.memory != nil` / `if a.knowledge != nil` …),而驻留子是轻量内核、
// memory 为 nil。<6C><E38082> ToolAPI 是全局单例,拿不到 agent,只能由 agent 自己
// 提供一份 provider。
// builtinProvider 是 *Agent 上的适配器:把 agent 的内置工具面
// 转成 sdk.BuiltinProvider。
type builtinProvider struct{ a *Agent }
// Defs 返回当前 agent 可见的内置工具声明。
//
// ⚠️ **必须复用 buildToolDefs 的同一批生成逻辑**,否则会出现"两套语义":
// 一套决定模型看得到什么(buildToolDefs),另一套决定插件看得到什么。
// 这里直接从 buildToolDefs 里筛出**不在** StageHost/IOManager 中的那些,
// 从而保证门控条件(memory/knowledge 是否就绪)完全一致。
func (p builtinProvider) Defs() []sdk.BuiltinToolDef {
if p.a == nil {
return nil
}
// 先算出"插件/设备侧已有的名字",剩下的才是内核内置的。
external := map[string]bool{}
if p.a.io != nil {
for _, d := range p.a.io.GetAllTools() {
external[d.Name] = true
}
}
if p.a.stageHost != nil {
for _, d := range p.a.stageHost.GetToolDefs() {
external[d.Name] = true
}
}
var out []sdk.BuiltinToolDef
for _, raw := range p.a.buildToolDefs() {
m, ok := raw.(map[string]interface{})
if !ok {
continue
}
fn, ok := m["function"].(map[string]interface{})
if !ok {
continue
}
name, _ := fn["name"].(string)
if name == "" || external[name] {
continue
}
desc, _ := fn["description"].(string)
params, _ := fn["parameters"].(map[string]interface{})
out = append(out, sdk.BuiltinToolDef{
Name: name, Description: desc, Parameters: params,
})
}
return out
}
// Exec 执行一个内置工具。
//
// 直接复用 executeToolCall 的完整路径(内置分支 + 授权闸 + 异常处理),
// **不复用** executeToolCallInner:后者要求经前缀 switch,而 ToolAPI 的
// 存在性判定已在 ToolDefByName/ExecuteTool 做过一次。
//
// ⚠️ 传入的 toolCall 不带 RawArguments(插件侧没有原始 JSON),
// 因此 __arg_error 的信息面在插件路径上天然缺失 —— 插件调用的是
// **已解析**的参数,不存在被截断的中间态。
func (p builtinProvider) Exec(name string, args map[string]interface{}) (string, error) {
if p.a == nil {
return "", errBuiltinNoAgent
}
tc := agentAPI.ToolCall{
ID: "builtin_" + name,
Name: name,
Arguments: args,
}
return p.a.executeToolCall(tc, p.a.defaultChannelForBuiltin()), nil
}
// defaultChannelForBuiltin 给出内置工具执行时的输出通道。
//
// 内置工具本身不产出"用户可见输出"(结果回给调用方),但 executeToolCall
// 的签名需要 channel(如记忆写入会记场景)。用 agent 的调度当前通道不可靠
// (它逐任务变化),故用一个稳定的内部标记。
func (a *Agent) defaultChannelForBuiltin() string { return "builtin" }
// errBuiltinNoAgent 表示 provider 未绑定 agent(装配顺序错误)。
var errBuiltinNoAgent = &builtinErr{"内置工具执行器未绑定 agent"}
type builtinErr struct{ msg string }
func (e *builtinErr) Error() string { return e.msg }
// InstallBuiltinToolProvider 把本 agent 的内置工具面注入 ToolAPI。
//
// 供内核在**创建 agent 之后**调用(bootstrap / resident 创建处)。
// 幂等:重复调用只是覆盖为同一个 agent。
func (a *Agent) InstallBuiltinToolProvider() { a.installBuiltinProvider() }
// installBuiltinProvider 把本 agent 的内置工具面注入 ToolAPI。
//
// 由内核在**创建 agent 之后**调用(bootstrat / resident 创建处)。
// 注入是**全局**的:最后一次注入生效。⚠️ 因此多 agent 场景下,
// ToolAPI 看到的是"最近一个注入者"的内置工具面 —— 这是当前架构的
// 已知局限(ToolAPI 是单例却需要 per-agent 数据)。
// 记入设计文档 §10 待定项,不在本次解决。
func (a *Agent) installBuiltinProvider() {
sdk.SetBuiltinProvider(builtinProvider{a: a})
}
// isBuiltinToolName 粗判某名字是否可能是内置工具(供提示词/文档用)。
// 真正的判定以 ToolAPI 查询为准(带门控)。
func isBuiltinToolName(name string) bool {
for _, p := range []string{
"memory_", "knowledge_", "doc_", "person_",
"output_", "input_", "resident_", "notify_parent", "persona_set",
} {
if strings.HasPrefix(name, p) {
return true
}
}
return false
}