mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
迁移前,「外部插件拿不到 Selftest/Supervisor/Tracker」是 C ABI 表达能力的 **意外产物**——C 结构体不好传函数指针,这些能力自然到不了插件侧。那是运气 不是策略:任何人给 dispatch 加个 case 就能捅穿。 现在变成显式声明并强制,分三道闸: 1. **类型层**(proc_core.go,Part 6.2 已落地):procCore 用命名字段持有 内核 SDK 而非嵌入,未在收窄面写出的方法编译期就不存在。 2. **能力集**(新增 capability.go):54 个 plugin→kernel method 划入 11 个 capability 组,manifest 未声明的组被拒。 3. **RPC 边界**(corehandler.Handle 入口):被拒时返回**明确错误**而非 静默忽略。 第 3 条针对一类真实故障:C ABI 时代 case 23/24(事件订阅)是空实现, 返回成功但永远收不到事件(§1.3 的「给不了」而非「不给」),插件作者无从得知。 错误消息含四要素:哪个插件、哪个调用、缺什么能力、在哪声明。 ## 能力划分的两个判断 **粒度按能力域而非单 method**。逐 method 授权看似更精细,但插件作者要在 manifest 里列 60 个名字,且内核每加 method 所有 manifest 都得改。 **空声明 = 不受限,而非「只有 core」**。17 个存量插件的 plugin.json 都没有 capabilities 字段。若空声明当作最小权限,它们会全部失去 IO 注入、记忆读写 而**静默降级**——违反「外部插件零改动」的硬约束。收紧的路径是让插件显式 声明,而不是默默拒绝老插件。 ## core 与受限能力的边界 core(无需声明,始终可用):注册自身工具/阶段/通道/API、读写**自己的**配置、 共享段锁仲裁、握手、autoRestart 自述、setToolBlocks。没有这些插件无法工作。 受限(需声明):io / memory / doc_memory / knowledge / text_memory / llm / social / events / plugin_mgr / settings_cross。 settings 刻意拆成两级:读写自己的配置属 core(正常工作所需),读写**其他插件** 配置或**内核核心**配置属 settings_cross(能改别人/内核的行为)。 ## withheldCapabilities:让「不给」可见 10 项刻意不提供的内核内部机制列在表里并附理由。它们没有对应 method 常量—— 不是忘了加,是决定不加。列表存在本身就是「这是策略而非疏漏」的证据, 读代码的人能看到边界在哪,而不是从「protocol.go 里没有」这个负面事实去推断。 ## 测试 proc 包 10 项: - AllMethodsClassified:**最重要的一项**。漏登记的 method 会按 CapCore 放行, 等于绕过整套检查。新增 method 忘登记时当场报出。 - EmptyDeclarationIsUnrestricted / DeclaredSetRestrictsOthers / CoreAlwaysAllowed - SettingsScopeSeparation:自身配置 vs 跨插件配置的归属 - DeniedErrorIsActionable:错误消息四要素 - HandleEnforcesAtRPCBoundary:被拒的调用不进 switch - WithheldListIsDocumented:每项都有理由,且不被任何 method 暴露 - UnknownMethodFallsThrough:未知 method 报「未知」而非「权限被拒」, 否则作者会以为是漏声明能力 写这个测试时踩到自己的坑:第一版用子串匹配查 withheld 泄漏,"Tool" 匹配到 tool.register 和 io.setToolBlocks 误报——那两个是合法开放的(注册自己的工具)。 改成前缀 + unregister 关键字匹配,withheld 项也改名带 API 后缀以示区分。 internal/plugins 2 项接线验证: - RestrictedPluginStillLoads:只声明 io 的 weather 仍能加载并注册工具 (它在 Start 里读 Settings,属 core) - LegacyManifestUnrestricted:无 capabilities 字段的存量插件正常加载 真实 homed 实测: [plugin] weather-capped 声明能力: [io] [plugin] weather-capped: 经 proc 通道加载(子进程) registering tool: weather-capped_current / _forecast / _set_location 验证:go build ./... 通过;go test ./... 全仓无失败; go test -race ./internal/plugin/... 全绿;go vet 干净。 Ref: docs/zh/架构迁移评估.md §3.8、docs/zh/plugin-migration-plan.md Part 6.4
60 lines
2.2 KiB
Go
60 lines
2.2 KiB
Go
package plugin
|
||
|
||
import (
|
||
"encoding/json"
|
||
"os"
|
||
"path/filepath"
|
||
)
|
||
|
||
const PackageExt = ".hmap"
|
||
|
||
// PluginManifest 每个插件目录中的 plugin.json 元数据。
|
||
type PluginManifest struct {
|
||
Name string `json:"name"`
|
||
NameZh string `json:"name_zh,omitempty"`
|
||
NameEn string `json:"name_en,omitempty"`
|
||
Version string `json:"version"`
|
||
Description string `json:"description,omitempty"`
|
||
Author string `json:"author,omitempty"`
|
||
License string `json:"license,omitempty"`
|
||
Homepage string `json:"homepage,omitempty"`
|
||
Repository string `json:"repository,omitempty"`
|
||
Entry string `json:"entry"` // "plugin.bin"(子进程) | "main.lua" | "SKILL.md"
|
||
Platforms []string `json:"platforms,omitempty"` // 声明的支持平台: ["linux","darwin","windows"]
|
||
MinVersion string `json:"min_version,omitempty"`
|
||
Tags []string `json:"tags,omitempty"`
|
||
Deprecated bool `json:"deprecated,omitempty"`
|
||
|
||
// Capabilities 声明本插件需要的内核能力组(§3.8 权限梯度)。
|
||
//
|
||
// 取值见 internal/plugin/proc.KnownCapabilities():
|
||
// io / memory / doc_memory / knowledge / text_memory / llm / social /
|
||
// events / plugin_mgr / settings_cross
|
||
//
|
||
// **省略或为空 = 不受限**,而不是「只有基础能力」。
|
||
// 理由:17 个存量插件的 plugin.json 都没有这个字段,若空声明当作最小权限,
|
||
// 它们会全部失去 IO 注入、记忆读写等能力而**静默降级**——
|
||
// 违反「外部插件零改动」的硬约束。收紧的路径是让插件显式声明。
|
||
//
|
||
// core(注册自身工具/阶段/通道 + 读写自己的配置)无需声明,始终可用。
|
||
Capabilities []string `json:"capabilities,omitempty"`
|
||
}
|
||
|
||
func ReadManifest(dir string) (*PluginManifest, error) {
|
||
data, err := os.ReadFile(filepath.Join(dir, "plugin.json"))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var m PluginManifest
|
||
if err := json.Unmarshal(data, &m); err != nil {
|
||
return nil, err
|
||
}
|
||
return &m, nil
|
||
}
|
||
|
||
// IsPluginDir 判断目录是否为有效的插件目录(包含 plugin.json)
|
||
func IsPluginDir(dir string) bool {
|
||
_, err := os.Stat(filepath.Join(dir, "plugin.json"))
|
||
return err == nil
|
||
}
|