Documentation
¶
Overview ¶
Package plugin 实现插件系统.
插件系统允许通过外部目录扩展 Flyto Agent Engine 的功能. 每个插件是一个包含 plugin.json 清单的目录,可以提供:
- 技能(markdown 文件,注入到系统提示词)
- Hook 定义(扩展 hook 系统)
- MCP 服务器配置
插件搜索路径:
- ~/.flyto/plugins/ - 用户级插件
- <project>/.flyto/plugins/ - 项目级插件
插件加载顺序:用户级先加载,项目级后加载,同名时后者覆盖前者.
升华改进(ELEVATED): 相较早期方案只支持 CLI 安装的插件,我们增加了:
- RegisterBuiltin - SDK 模式下直接在代码中注册插件,无需文件系统
- LoadAll 返回 LoadResult - 区分成功/降级/错误,而非遇到一个错误就中断
- Source 字段 - 精确追踪插件来源,支持依赖跨源校验
原方案 <Host.LoadAll 返回 error> - 否决原因:第一个插件加载失败会屏蔽其他所有插件的状态.
Index ¶
- Constants
- Variables
- func ComputeChecksum(pluginDir string) (string, error)
- func DefaultSearchPaths(cwd string) []string
- func FindReverseDependents(targetName string, all []*Plugin) []string
- func FormatManifestInfo(m *Manifest) string
- func LoadHooks(hooksPath string) (map[string][]HookDef, error)
- func NewPluginShellTool(def PluginToolDef, pluginDir, pluginName string, executor execenv.Executor) tools.Tool
- func ResolveClosure(names []string, registry map[string]*Plugin) ([]*Plugin, []PluginError)
- func ValidatePluginConfig(pluginName string, schema []ConfigFieldDef, config map[string]string) error
- func VerifyAndDemote(enabled []*Plugin, disabled []*Plugin, loadErrs []PluginError) (newEnabled []*Plugin, newDisabled []*Plugin, warnings []PluginError)
- func WriteChecksumFile(pluginDir string) (string, error)
- type BuiltinDef
- type ConfigFieldDef
- type Definition
- type HookDef
- type HooksDef
- type Host
- func (h *Host) Close()
- func (h *Host) Count() int
- func (h *Host) Disable(name string) error
- func (h *Host) Enable(name string) error
- func (h *Host) Get(name string) (*Plugin, bool)
- func (h *Host) GetAllHooks() map[string][]HookDef
- func (h *Host) GetAllMCPServers() []MCPServerDef
- func (h *Host) GetAllSkills() []*Skill
- func (h *Host) GetAllTools() []tools.Tool
- func (h *Host) GetPluginConfig(name string) map[string]string
- func (h *Host) GetPluginConfigField(name, key string) (string, bool)
- func (h *Host) List() []*Plugin
- func (h *Host) ListEnabled() []*Plugin
- func (h *Host) Load(def Definition) error
- func (h *Host) LoadAll(searchPaths []string) LoadResult
- func (h *Host) LoadFromDir(dir string) error
- func (h *Host) RegisterBuiltin(def BuiltinDef) error
- func (h *Host) SetPluginConfig(name string, config map[string]string) error
- type LoadResult
- type MCPDef
- type MCPServerDef
- type Manifest
- type NoopVerifier
- type Plugin
- type PluginError
- type PluginErrorCode
- type PluginToolDef
- type RejectUnsignedVerifier
- type SHA256IntegrityVerifier
- type SignatureVerifier
- type Skill
- type Source
- type ValidationResult
Constants ¶
const ChecksumFileName = "plugin.checksum"
ChecksumFileName 是 plugin 目录下完整性校验 sidecar 文件的约定名称. 文件存在 → plugin 启用完整性校验; 不存在 → SHA256IntegrityVerifier 宽松放行.
const ChecksumPrefix = "sha256:"
ChecksumPrefix 是当前唯一支持的 checksum 算法前缀. 格式: "sha256:" + 64 hex chars. 未来引入 sha512/blake3 时可扩展 readChecksumFile 同时接受多种前缀.
const DefaultPluginToolTimeout = 30 * time.Second
DefaultPluginToolTimeout 是 PluginToolDef.TimeoutSeconds 未指定时的默认超时. 30 秒对"快执行的 shell 命令"场景足够, 长 running 的 tool 应显式声明更长值 (或使用 MCP server 路径, 它支持更丰富的生命周期管理).
Variables ¶
var ErrChecksumMissing = errors.New("plugin.checksum file not found")
ErrChecksumMissing 表示 plugin 目录缺少 plugin.checksum 文件. 通常不会直接暴露给调用方 (SHA256IntegrityVerifier 宽松模式下吞掉这个错误 返回 nil), 只有 RejectUnsignedVerifier 会把它升级成 ErrInvalidSignature.
var ErrInvalidSignature = errors.New("plugin integrity verification failed")
ErrInvalidSignature 表示 plugin 完整性 / 签名验证失败. 所有 SignatureVerifier 实现失败时都应 wrap 此 sentinel, 便于调用方 errors.Is 判断.
Functions ¶
func ComputeChecksum ¶
ComputeChecksum 计算 plugin 目录的完整性哈希 (公开 SDK, 供工具生成器使用).
返回格式: "sha256:<64 hex chars>"
使用场景:
- 打包工具在发布 plugin 前计算并写入 plugin.checksum
- CI 管线在 build 后自动生成 checksum
- `flyto plugin checksum <dir>` CLI 命令 (未来)
本函数和 SHA256IntegrityVerifier.Verify 共用同一份内部算法 computeDirChecksum, 保证生成器和校验器的结果永远一致.
func DefaultSearchPaths ¶
DefaultSearchPaths 返回默认的插件搜索路径列表. 搜索路径按优先级从低到高排列(用户级在前,项目级在后).
func FindReverseDependents ¶
FindReverseDependents 返回所有直接依赖 targetName 的插件名称列表. 用于"禁用 X 会影响哪些插件"的诊断查询.
func FormatManifestInfo ¶
FormatManifestInfo 将清单格式化为人类可读的信息字符串.
func NewPluginShellTool ¶
func NewPluginShellTool(def PluginToolDef, pluginDir, pluginName string, executor execenv.Executor) tools.Tool
NewPluginShellTool 从 PluginToolDef 声明构造一个实现 tools.Tool interface 的实例.
公开 factory, 允许消费层在 SDK 嵌入场景里手动构造 tool (而不是走 plugin loader 流程). 参数 pluginName 用于 tool 名命名空间化, 空字符串则不加前缀.
参数:
- def: PluginToolDef 声明 (来自 plugin.json 或手动构造)
- pluginDir: plugin 目录, 用于默认 WorkDir 和解析相对路径
- pluginName: plugin 名, 用于命名空间化 (空则裸 tool 名)
- executor: 子进程启动抽象 (本地 = execenv.DefaultExecutor{}, 云端 = platform sandbox.Backend). **必填, nil 直接 panic** — 方案 β 严格 DI, 不做静默降级.
默认值填充:
- InputSchema 空 → emptyObjectSchema ({"type":"object",...})
- WorkDir 空 → pluginDir
- WorkDir 相对 → filepath.Join(pluginDir, WorkDir)
- TimeoutSeconds <=0 → DefaultPluginToolTimeout (30s)
- PermissionClass 空 → "bash" (shell 命令是天然的 bash class)
func ResolveClosure ¶
func ResolveClosure(names []string, registry map[string]*Plugin) ([]*Plugin, []PluginError)
ResolveClosure 从给定的起点插件集合出发,通过 DFS 计算传递闭包. 返回所有需要加载的插件(包括所有传递依赖),按拓扑顺序排列(依赖先于被依赖者).
参数:
- names: 用户请求加载的插件名称集合
- registry: 所有已发现的插件,name → *Plugin
返回:
- ordered: 拓扑排序后的插件列表(可直接按序加载)
- errors: 依赖缺失或循环错误(每个受影响插件一条)
算法:三色 DFS
- 白(未访问):初始状态
- 灰(访问中):当前 DFS 路径上,发现灰节点 = 有环
- 黑(已完成):已排入结果
func ValidatePluginConfig ¶
func ValidatePluginConfig(pluginName string, schema []ConfigFieldDef, config map[string]string) error
ValidatePluginConfig 按 schema 验证用户提供的配置 map.
验证规则:
- Required=true 的字段必须在 config 中存在(且非空字符串)
- 字段的值类型须与 Type 声明匹配(number: 可解析为数字;boolean: "true"/"false")
- Secret 字段的值在错误消息中脱敏为 "***"
config 为 nil 等同于空 map(没有任何配置). schema 为空时直接返回 nil(无约束,向后兼容).
精妙之处(CLEVER): 收集所有错误后一次性返回(而非遇第一个错误就停)-- 开发者能一次看到所有缺失/错误的字段,不需要改了一个再发现下一个. 替代方案:第一个错误即返回(快速失败,代码更简单但用户体验差).
func VerifyAndDemote ¶
func VerifyAndDemote(enabled []*Plugin, disabled []*Plugin, loadErrs []PluginError) ( newEnabled []*Plugin, newDisabled []*Plugin, warnings []PluginError, )
VerifyAndDemote 固定点迭代:如果某个插件的依赖被禁用或有错误, 将该插件从 enabled 移入 disabled,并记录原因.
算法:固定点(fixed-point)迭代 - 反复扫描直到没有新的降级发生.
精妙之处(CLEVER): 为什么用固定点迭代而不是拓扑排序倒序遍历? 因为输入 enabled 列表不保证已经拓扑排序.固定点迭代不依赖顺序, 正确处理任意顺序的输入列表.最坏情况 O(n²),实际插件数量小(<100),可接受. 如果改用拓扑排序倒序,需要先建图,实现更复杂,收益微小.
func WriteChecksumFile ¶
WriteChecksumFile 计算 plugin 目录哈希并写入 pluginDir/plugin.checksum.
幂等: 已存在的 checksum 文件被覆盖, 且 computeDirChecksum 会自动排除 plugin.checksum 自身, 所以多次调用都会产生相同内容 (只要目录内容没变).
返回写入的完整 checksum 字符串 (含 "sha256:" 前缀), 供调用方打 log 或回显.
Types ¶
type BuiltinDef ¶
type BuiltinDef struct {
// Name 内置插件名称(必须唯一,不能与已注册插件冲突)
Name string
// Description 描述
Description string
// Version 版本,可留空(默认 "0.0.0")
Version string
// Skills 内置技能列表
Skills []*Skill
// Hooks 内置 hook 定义
Hooks map[string][]HookDef
// MCPServers 内置 MCP 服务器配置
MCPServers []MCPServerDef
}
BuiltinDef 是内置插件的注册信息.
升华改进(ELEVATED): SDK 嵌入场景下,消费层可以将内置能力(如仓储查询工具) 打包为内置插件注册,享受与文件系统插件相同的 enable/disable/skill 聚合接口, 无需在代码和插件系统之间维护两套资源管理逻辑. 原方案 <直接向 Host 注入 Skills 列表> - 否决原因:绕过了插件生命周期管理(enable/disable), 且与文件系统插件的统一查询接口不兼容.
type ConfigFieldDef ¶
type ConfigFieldDef struct {
// Key 配置项的键名(在 user config map 中对应的 key)
Key string `json:"key"`
// Type 值类型:"string" / "number" / "boolean"
// 空字符串时默认视为 "string"
Type string `json:"type"`
// Required 是否为必填项.
// true 时若用户配置 map 中不存在该 key,ValidatePluginConfig 返回错误.
Required bool `json:"required"`
// Default 默认值(字符串表示,非必填项可以有默认).
// 校验时若 key 缺失且 Required==false,可用此值填充.
// 历史包袱(LEGACY): 当前校验器不自动填充默认值(只报告缺失),
// 调用方(Host.SetPluginConfig)负责应用默认值.
// 未来如有需要,可在 ValidatePluginConfig 中补充自动填充逻辑.
Default string `json:"default,omitempty"`
// Description 人类可读的字段描述(用于 help 输出和 UI 展示)
Description string `json:"description,omitempty"`
// Secret 标记此字段是否为敏感值(如 API Key,密码).
// true 时日志/错误消息中对该字段值进行脱敏(显示为 "***").
//
// 升华改进(ELEVATED): Secret 标记让引擎主动保护敏感值--
// 插件作者只需声明"这是秘密",引擎层统一处理脱敏,
// 无需每个插件自己实现 mask 逻辑.
// 替代方案:<命名约定(key 包含 "secret"/"password" 的自动脱敏)>
// - 否决:名称约定脆弱,容易遗漏("api_key" vs "apiKey" vs "key").
Secret bool `json:"secret"`
}
ConfigFieldDef 是插件配置字段的定义(12.6 Plugin Config Schema).
字段 Type 使用 JSON Schema 风格的基础类型:
"string" - 字符串(最常见:API Key,endpoint URL) "number" - 数字(整数或浮点,如超时秒数,并发限制) "boolean" - 布尔(true/false,如是否开启调试模式)
精妙之处(CLEVER): 只支持 3 种基础类型而非完整 JSON Schema-- 插件配置通常只需要 string/number/boolean,完整的 $ref/$defs/oneOf 反而 增加了实现复杂度和消费层解析成本.若未来需要复杂类型(array/object), 可在 Type 中增加,向后兼容(旧校验器遇到未知类型时 fallback 为 string).
type Definition ¶
type Definition struct {
Name string `json:"name"`
Description string `json:"description"`
Source string `json:"source"` // 插件标识符
}
Definition 是插件定义(旧接口兼容).
type HookDef ¶
type HookDef struct {
// Command 要执行的 shell 命令
Command string `json:"command"`
// Timeout 超时时间(秒),默认 30
Timeout int `json:"timeout"`
}
HookDef 是单个 hook 的定义.
type Host ¶
type Host struct {
// contains filtered or unexported fields
}
Host 是插件宿主,管理所有插件的加载,启用/禁用和资源访问. 是消费层与插件系统交互的唯一入口.
线程安全:所有操作通过 sync.RWMutex 保护.
func NewHost ¶
NewHost 创建一个新的插件宿主.
executor 必填, nil panic. 方案 β 严格 DI 要求所有子进程启动点都走 统一 execenv.Executor 抽象, 禁止静默降级. 本地 CLI 传 execenv.DefaultExecutor{}, 云端 platform 层传 sandbox backend.
func (*Host) GetAllHooks ¶
GetAllHooks 收集所有启用插件提供的 hook 定义.
func (*Host) GetAllMCPServers ¶
func (h *Host) GetAllMCPServers() []MCPServerDef
GetAllMCPServers 收集所有启用插件提供的 MCP 服务器配置.
func (*Host) GetAllTools ¶
GetAllTools 收集所有启用插件通过 plugin.json 的 tools 字段声明式注册的 tool 实例.
与 GetAllMCPServers 的区别: GetAllMCPServers 返回的是 **配置** (MCPServerDef), 调用方还要自己启动 subprocess 并走 MCP 协议发现 tool. GetAllTools 返回的是 **已实例化**的 tools.Tool, 调用方可以直接 `tools.Registry.Register(t)`.
使用场景: 消费层 (Host/engine 启动时) 调用此方法收集所有 declarative tool, 注册到全局 Registry, 让 agent 可以直接调用. 典型代码:
for _, t := range host.GetAllTools() {
if err := registry.Register(t); err != nil {
log.Printf("register tool %q: %v", t.Name(), err)
}
}
精妙之处 (CLEVER): 本方法返回类型是 []tools.Tool 而非 []*pluginShellTool, 因为未来 plugin 可能通过别的机制贡献 tool (WASM / Go static binary / 内置 built-in tool 提供器), 接口类型能无缝包含所有实现.
func (*Host) GetPluginConfig ¶
GetPluginConfig 获取指定插件的完整配置 map.
返回值是配置的副本(修改不影响内部存储). 若未设置配置,返回空 map(非 nil).
升华改进(ELEVATED): 插件通过此方法读取自己的配置,隔离命名空间-- 不同插件的 "api_key" 字段各自独立,无需前缀拼接. 替代方案:<直接暴露 pluginConfigStore> - 否决:绕过了 Host 的封装,外部可能破坏内部一致性.
func (*Host) GetPluginConfigField ¶
GetPluginConfigField 获取指定插件的单个配置字段值. 若字段不存在,返回空字符串和 false.
func (*Host) Load ¶
func (h *Host) Load(def Definition) error
Load registers a single plugin definition as a metadata-only placeholder in the host registry. It does NOT parse skills / hooks / tools / MCPServers -- those come from LoadFromDir (which reads plugin.json on disk) or RegisterBuiltin (in-binary). Use this when the caller wants the host to know a plugin exists but has no plugin.json at hand, e.g. SDK tests or engine.Config.Plugins wiring.
The created Plugin is marked Enabled=true and pinned to SourceUser so RemoveByScope(SourceUser) sweeps it alongside real disk-loaded user plugins. The Source enum value is a coarse tag; finer "came from Config.Plugins" attribution is the caller's responsibility if needed.
Load 把一个 plugin 定义作为 metadata-only 占位登记到 host registry. 不解析 skills / hooks / tools / MCPServers -- 那些走 LoadFromDir (读磁盘上的 plugin.json) 或 RegisterBuiltin (内嵌二进制). 调用方想 让 host 知道某 plugin 存在但手头没 plugin.json 时用这条, e.g. SDK 测试或 engine.Config.Plugins wiring.
创建的 Plugin 标记 Enabled=true, Source 钉为 SourceUser, 这样 RemoveByScope(SourceUser) 扫的时候能跟真正从磁盘加载的 user plugin 一起清. Source 枚举是粗分类 tag; 如果需要更细的 "来自 Config.Plugins" 归因, 由调用方自己记.
func (*Host) LoadAll ¶
func (h *Host) LoadAll(searchPaths []string) LoadResult
LoadAll 发现并加载所有搜索路径下的插件.
加载流程:
- 按路径顺序发现所有插件目录(后发现的同名插件覆盖先发现的)
- 完整加载每个插件(清单→技能→hooks→MCP)
- 依赖解析:ResolveClosure 计算传递闭包
- VerifyAndDemote:依赖缺失/错误的插件降级禁用
- 更新 Host 内部状态
返回 LoadResult,调用方可以按需处理 Errors/Warnings, 不会因为单个插件问题中断其他插件的加载.
func (*Host) RegisterBuiltin ¶
func (h *Host) RegisterBuiltin(def BuiltinDef) error
RegisterBuiltin 在内存中注册一个内置插件. 内置插件不来自文件系统,由 SDK 消费者在代码中直接注册.
若同名内置插件已存在则覆盖(允许重复注册,方便测试). 若与已加载的文件系统插件同名,返回 ErrBuiltinConflict 错误.
func (*Host) SetPluginConfig ¶
SetPluginConfig 设置指定插件的配置 map,并按 schema 验证配置有效性.
调用时机:消费层在 LoadAll/LoadFromDir 之后,Run 之前调用-- 提供插件运行所需的配置(API Key,endpoint 等).
若插件未加载(name 不在注册表中),仍接受配置设置(允许预配置后再加载). 若插件有 ConfigSchema,ValidatePluginConfig 验证 Required 字段和类型约束; 验证失败返回 PluginError{Code: ErrConfigValidation}.
精妙之处(CLEVER): 先验证再存储--无效配置不会写入存储, 确保 GetPluginConfig 返回的配置一定通过了 schema 校验. 替代方案:先存储后验证(消费层可能用到未验证的配置,带来不一致风险).
type LoadResult ¶
type LoadResult struct {
// Enabled 成功加载并启用的插件列表
Enabled []*Plugin
// Disabled 因依赖降级而被禁用的插件列表
// 精妙之处(CLEVER): 保留禁用插件而非直接丢弃,使消费层可以展示"因 X 禁用了 Y"的诊断信息.
Disabled []*Plugin
// Errors 致命错误列表(插件完全无法加载)
Errors []PluginError
// Warnings 非致命警告列表(插件加载但有降级)
Warnings []PluginError
}
LoadResult 是 LoadAll 操作的完整结果.
升华改进(ELEVATED): 早期方案直接返回 error,第一个失败就停止. 我们区分 Enabled/Disabled/Errors/Warnings,使单个插件的问题不阻断其他插件加载. CLI 消费者可以在启动时打印 Warnings 而不中断;SDK 消费者可以编程式处理 Errors. 原方案 <返回 []error> - 否决原因:丢失了"哪些加载成功了"的信息,消费层无法区分部分成功.
func (*LoadResult) Summary ¶
func (r *LoadResult) Summary() string
Summary 返回加载结果的人类可读摘要. 适合在启动日志或 CLI 输出中展示.
type MCPDef ¶
type MCPDef struct {
// Transport 传输方式:stdio / sse / http / ws
Transport string `json:"transport"`
// Command stdio 模式下的可执行命令
Command string `json:"command,omitempty"`
// Args 命令参数
Args []string `json:"args,omitempty"`
// URL sse/http/ws 模式下的服务器 URL
URL string `json:"url,omitempty"`
// Env 环境变量
Env map[string]string `json:"env,omitempty"`
}
MCPDef 是 MCP 服务器定义.
type MCPServerDef ¶
type MCPServerDef struct {
// Name 服务器名称
Name string
// PluginName 来源插件名称
PluginName string
// MCPDef 服务器配置
MCPDef
}
MCPServerDef 是带有来源信息的 MCP 服务器定义.
type Manifest ¶
type Manifest struct {
// Name 插件名称(唯一标识符)
Name string `json:"name"`
// Description 插件描述
Description string `json:"description"`
// Version 插件版本(语义化版本号)
Version string `json:"version"`
// SkillsPath 技能文件目录的相对路径(相对于插件目录)
SkillsPath string `json:"skills_path"`
// HooksPath hooks 配置文件的相对路径
HooksPath string `json:"hooks_path"`
// MCPServers MCP 服务器配置.
//
// 升华改进(ELEVATED): JSON 字段名从 "mcp_servers" 改为 "mcpServers",
// 与 Claude Code 插件格式对齐.这样同一个 plugin.json 可直接被 Claude Code 读取,
// 无需手动转换,降低跨平台插件的维护成本.
// 早期方案字段名 <"mcp_servers"> - 保留为向后兼容在下方 UnmarshalJSON 中处理.
MCPServers map[string]MCPDef `json:"mcpServers"`
// Dependencies 依赖的其他插件名称列表
Dependencies []string `json:"dependencies"`
// ConfigSchema 插件配置字段定义(12.6 Plugin Config Schema).
//
// 升华改进(ELEVATED): 插件声明自己需要哪些配置项,引擎在加载时验证用户提供的
// 配置是否满足 Required 字段,并提供类型约束和描述.
// 等价于 npm package.json 里的 peerDependencies + engines 约束--
// 消费层不必逐一阅读插件源码就能知道"这个插件需要我提供什么".
// 替代方案:<文档约定(README 里写需要什么环境变量)>
// - 否决:文档和代码分离,容易过期;机器无法校验,只能靠人工排查.
ConfigSchema []ConfigFieldDef `json:"config_schema,omitempty"`
// Tools 是 plugin 声明式注册的 shell tool 列表 (2026-04-15 新增).
//
// 每个 PluginToolDef 在加载时被 loadPluginTools 翻译成一个 pluginShellTool
// 实例 (实现 tools.Tool interface), 存放在 Plugin.Tools 里, 供消费层
// (Host.GetAllTools) 注册到全局 tools.Registry.
//
// 这是 plugin 扩展 tool 的**第二条路径** (第一条是 MCPServers 配置).
// 适用场景: 单 tool / 无状态 / shell 脚本就能搞定的简单扩展.
// 详见 plugin_tool.go 的 "为什么同时需要 MCP server 和 declarative tool" 段落.
Tools []PluginToolDef `json:"tools,omitempty"`
}
Manifest 是插件清单的结构体表示. 对应插件目录下的 plugin.json 文件.
func LoadManifest ¶
LoadManifest 从插件目录加载并解析清单文件.
参数:
- pluginDir: 插件目录的绝对路径
返回解析后的清单和错误. 如果 plugin.json 不存在或格式错误,返回相应的错误.
func (*Manifest) UnmarshalJSON ¶
UnmarshalJSON 自定义反序列化,同时支持新字段名(mcpServers)和旧字段名(mcp_servers).
精妙之处(CLEVER): 用 type alias 的嵌套 struct 技巧,避免 UnmarshalJSON 无限递归: manifestAlias 与 Manifest 有相同的字段(通过嵌入),但没有 UnmarshalJSON 方法, 所以 json.Unmarshal 会走默认路径而不会再次调用此函数. legacyFields 只声明旧字段名,两者都解码后再合并--新字段优先,旧字段兜底.
type NoopVerifier ¶
type NoopVerifier struct{}
NoopVerifier 是"零校验"实现, 对任何输入都返回 nil.
用途:
- DefaultVerifier() 默认返回此实现 (保持向后兼容)
- 单元测试中不关心完整性的场景
- 本地开发时临时禁用校验
注意: 生产部署中如果启用了完整性校验, 应该用 SHA256IntegrityVerifier (宽松) 或 RejectUnsignedVerifier (严格), 不要直接用 NoopVerifier.
精妙之处 (CLEVER): NoopVerifier 是空 struct, 零内存占用, 天然线程安全. 即便 LoadAll 并发加载 100 个 plugin, 共用一个实例也毫无代价.
type Plugin ¶
type Plugin struct {
// Name 插件名称(唯一标识符)
Name string
// Description 插件描述
Description string
// Version 插件版本
Version string
// Path 插件目录的绝对路径(内置插件为空字符串)
Path string
// Source 插件的来源级别(内置/用户级/项目级)
// 影响依赖解析中的跨来源规则.
Source Source
// Manifest 插件清单(plugin.json 的解析结果);内置插件为 nil
Manifest *Manifest
// Enabled 插件是否启用
Enabled bool
// Skills 插件提供的技能列表
Skills []*Skill
// Hooks 插件提供的 hook 定义,key 是 hook 类型
Hooks map[string][]HookDef
// MCPServers 插件提供的 MCP 服务器配置
MCPServers []MCPServerDef
// Tools 插件通过 plugin.json 的 tools 字段声明式注册的 shell tool 运行时实例.
//
// 区别于 MCPServers 字段: MCPServers 是配置 (启动外部 MCP server 进程),
// Tools 是已经实例化的 tools.Tool, 可以直接注册到 tools.Registry.
// 详见 plugin_tool.go 的 "为什么同时需要 MCP server 和 declarative tool
// 两条路径" 段落.
//
// 消费层 (Host 或 engine) 通过 Host.GetAllTools() 收集所有启用插件的 tools,
// 并调用 tools.Registry.Register() 逐个注册.
Tools []tools.Tool
}
Plugin 是一个已加载的插件实例.
func DiscoverPlugins ¶
DiscoverPlugins 发现所有可用的插件(旧接口兼容). 返回发现的插件列表(尚未完全加载,仅解析了清单). executor 透传给每个 LoadPlugin 调用, 本地模式传 execenv.DefaultExecutor{}.
func LoadPlugin ¶
LoadPlugin 完整加载一个插件 (公开接口, 向后兼容). 参数 dir 是插件目录路径 (必须包含 plugin.json). 错误时返回第一个致命错误 (如有).
本函数使用 DefaultVerifier (当前是 NoopVerifier, 不做完整性校验). 消费层如需启用完整性校验, 使用 LoadPluginWithVerifier 并传入:
- NewSHA256IntegrityVerifier() 宽松模式 (有 plugin.checksum 才校验)
- RejectUnsignedVerifier{Inner: ...} 严格模式 (必须有 plugin.checksum)
executor 必填 (方案 β 严格 DI), 透传给每个 pluginShellTool. 详见 integrity.go 和 signature.go.
func LoadPluginWithVerifier ¶
func LoadPluginWithVerifier(dir string, verifier SignatureVerifier, executor execenv.Executor) (*Plugin, error)
LoadPluginWithVerifier 加载插件并执行完整性 / 签名验证.
参数:
- dir: 插件目录路径 (必须包含 plugin.json)
- verifier: 完整性验证器 (nil 则用 DefaultVerifier, 当前是 NoopVerifier)
执行顺序:
- 加载 manifest
- 调用 verifier.Verify(dir, manifest) - 策略由 verifier 自主决定 (宽松 / 严格 / 零校验)
- 验证通过才继续走 loadPluginWithErrors 加载 skills/hooks/MCP
- 验证失败返回 wrap 后的 error (通常 errors.Is(err, ErrInvalidSignature))
宽松 vs 严格策略**不在 loader 层做判断**, 完全交给 verifier:
- NoopVerifier: 零校验
- SHA256IntegrityVerifier: plugin.checksum 不存在放行, 存在则校验
- RejectUnsignedVerifier: plugin.checksum 不存在直接拒绝
这种 "loader 调度 + verifier 策略" 的分离让未来新增 verifier 实现 (Ed25519 / Sshsig / Sigstore / PKCS#7) 不需要改 loader 一行代码.
type PluginError ¶
type PluginError struct {
// Code 错误分类码
Code PluginErrorCode
// PluginName 触发错误的插件名称
PluginName string
// Message 人类可读的错误描述
Message string
// Cause 底层原因(可选)
Cause error
}
PluginError 是一个结构化的插件错误,包含错误码,上下文和原始原因. 实现 error 接口,可直接用于 errors.Is/As 链.
func (PluginError) Unwrap ¶
func (e PluginError) Unwrap() error
Unwrap 实现 errors.Unwrap,支持 errors.Is/As 链式检查.
type PluginErrorCode ¶
type PluginErrorCode int
PluginErrorCode 是插件错误码枚举. 10 种类型覆盖从清单到依赖的全路径错误场景.
精妙之处(CLEVER): 用 iota+1 而非 iota,使零值(0)明确表示"无错误", 避免 PluginErrorCode 的零值被误判为有效错误码.
const ( // ErrManifestNotFound plugin.json 文件不存在 ErrManifestNotFound PluginErrorCode = iota + 1 // ErrManifestInvalid plugin.json 格式非法(JSON 语法错误) ErrManifestInvalid // ErrManifestValidation 清单内容验证失败(必填字段缺失,非法字符等) ErrManifestValidation // ErrSkillsLoadFailed 技能目录加载失败(读权限,文件损坏等) ErrSkillsLoadFailed // ErrHooksLoadFailed hooks 配置文件加载失败 ErrHooksLoadFailed // ErrMCPConfigInvalid MCP 服务器配置无效(缺少必填字段) ErrMCPConfigInvalid // ErrDependencyMissing 依赖的插件未找到(未安装或未在搜索路径) ErrDependencyMissing // ErrDependencyCycle 依赖关系中存在循环(A→B→A) ErrDependencyCycle // ErrDependencyCrossSource 跨来源依赖不允许(项目级插件依赖用户级插件) // 历史包袱(LEGACY): 当前以插件名前缀推断来源,未来应改为插件注册时携带 // 显式的 Source 字段,以实现精确的来源隔离. ErrDependencyCrossSource // ErrBuiltinConflict 与内置插件名称冲突 ErrBuiltinConflict // ErrConfigValidation 插件配置验证失败(Required 字段缺失 / 类型不匹配). // 12.6 Plugin Config Schema - 用户提供的配置不满足 plugin.json 中 config_schema 的约束. ErrConfigValidation )
type PluginToolDef ¶
type PluginToolDef struct {
// Name 是 tool 的本地名称 (不含 plugin 前缀).
// 最终注册到 Registry 的名字是 "pluginName:Name".
// 必填, 空值会被 loadPluginTools 跳过.
Name string `json:"name"`
// Description 是给 LLM 的 tool 描述. 必填 (否则 LLM 不知道该不该调).
Description string `json:"description"`
// InputSchema 是 tool 参数的 JSON Schema. 可选, 缺省为空对象 schema
// ({"type":"object","properties":{},"additionalProperties":true}),
// 即"接受任意 JSON 对象"- 适合无参数或参数非常灵活的 tool.
InputSchema json.RawMessage `json:"input_schema,omitempty"`
// Command 是要执行的命令 (可以是 $PATH 里的名字如 "python", 或绝对/
// 相对路径如 "/usr/bin/env" / "./script.sh"). 必填.
// 相对路径会相对于 WorkDir (默认为 pluginDir) 解析, 由 exec.Command 处理.
Command string `json:"command"`
// Args 是传给 Command 的命令行参数 (不含 command 本身).
Args []string `json:"args,omitempty"`
// Env 是要注入子进程环境变量的 key/value. 会叠加到白名单 OS env
// (PATH/HOME/LANG/LC_ALL) 之上, 同名变量以 Env 里的为准. Value 支持
// ${HOST_VAR} / $HOST_VAR 展开宿主 env, 未设置的引用会导致 tool error.
// 常用于传 API Key / endpoint / plugin config. 详见 pkg/execenv 包.
Env map[string]string `json:"env,omitempty"`
// WorkDir 是子进程的工作目录. 可选:
// - 空字符串 (默认): 使用 pluginDir
// - 相对路径: 相对于 pluginDir 解析
// - 绝对路径: 直接使用
WorkDir string `json:"work_dir,omitempty"`
// TimeoutSeconds 是子进程执行的超时时间 (秒). <=0 或未指定 → 30s 默认.
// 超时后进程被 SIGKILL, Execute 返回 IsError=true 的 Result.
TimeoutSeconds int `json:"timeout_seconds,omitempty"`
// PermissionClass 是权限检查类型 (参见 tools.Metadata.PermissionClass).
// 空字符串默认 "bash" (命令执行类, 子命令分割 + 危险命令检测).
PermissionClass string `json:"permission_class,omitempty"`
// ConcurrencySafe 声明本 tool 是否可以与其他 tool 并发执行.
// 保守默认 false (和 tools.GetMetadata 的 fallback 一致).
ConcurrencySafe bool `json:"concurrency_safe,omitempty"`
// ReadOnly 声明本 tool 是否只读 (无副作用).
// 保守默认 false.
ReadOnly bool `json:"read_only,omitempty"`
// Destructive 声明本 tool 是否会造成不可逆的破坏
// (rm / DROP TABLE / kubectl delete 等).
// 保守默认 false.
Destructive bool `json:"destructive,omitempty"`
}
PluginToolDef 是 plugin.json 的 tools 字段里的一个 tool 声明.
每个声明在 plugin 加载时被 loadPluginTools 翻译成一个 pluginShellTool 并加入 Plugin.Tools 列表, 供消费层 (Host 或 engine 层) 注册到全局 tools.Registry.
JSON 示例:
{
"name": "git_branch",
"description": "Return the current git branch name",
"command": "git",
"args": ["branch", "--show-current"],
"timeout_seconds": 5,
"read_only": true,
"permission_class": "readonly"
}
type RejectUnsignedVerifier ¶
type RejectUnsignedVerifier struct {
// Inner 是实际执行验证的 verifier. 通常是 NewSHA256IntegrityVerifier().
// 为 nil 时默认使用 SHA256IntegrityVerifier{}.
Inner SignatureVerifier
}
RejectUnsignedVerifier 是严格模式装饰器: 拒绝加载没有 plugin.checksum 的 目录, 有 checksum 的则 delegate 给 Inner 做实际验证.
使用示例 (企业合规场景):
strict := plugin.RejectUnsignedVerifier{
Inner: plugin.NewSHA256IntegrityVerifier(),
}
p, err := plugin.LoadPluginWithVerifier(dir, strict)
Inner 为 nil 时默认使用 SHA256IntegrityVerifier{} 作为底层验证器.
精妙之处 (CLEVER): 用 decorator 模式而不是在 SHA256IntegrityVerifier 里 开一个 StrictMode bool 字段, 因为策略组合能力更强 - 未来可以用 RejectUnsignedVerifier 包装 Ed25519Verifier / Pkcs7Verifier 等任意实现, 而不用每个 verifier 都重复实现严格/宽松切换.
type SHA256IntegrityVerifier ¶
type SHA256IntegrityVerifier struct{}
SHA256IntegrityVerifier 是基于 plugin.checksum sidecar 文件的完整性验证器.
行为 (宽松模式):
- plugin.checksum 不存在: 返回 nil (不校验, 向后兼容已有 plugin)
- plugin.checksum 存在: 计算目录哈希并比对, 不一致返回 ErrInvalidSignature
严格模式通过 RejectUnsignedVerifier 包装获得 (拒绝无 checksum 的 plugin).
精妙之处 (CLEVER): 这是一个空 struct, 无状态, 天然线程安全. LoadAll 并发加载多个 plugin 时共用一个 verifier 实例无竞态.
type SignatureVerifier ¶
type SignatureVerifier interface {
// Verify 验证 plugin 目录的完整性 / 签名.
//
// 参数:
// - pluginDir: plugin 目录的绝对路径
// - manifest: 已加载的 Manifest (可能为 nil, 虽然 LoadPluginWithVerifier
// 总是传非 nil, 但 verifier 应该容错处理)
//
// 返回 nil 表示通过, 非 nil 表示拒绝加载.
Verify(pluginDir string, manifest *Manifest) error
}
SignatureVerifier 是 plugin 完整性 / 签名验证器接口.
调用时机: LoadPluginWithVerifier 在加载 manifest 后, 加载 skills/hooks/MCP 前 调用 Verify. 返回非 nil 即拒绝加载整个 plugin.
实现必须**无状态 + 线程安全**, 因为同一个 Verifier 会被 LoadAll 并发调用多次.
**宽松 vs 严格策略不在 interface 层预设**, 由实现自己决定:
- NoopVerifier: 永远放行 (零校验)
- SHA256IntegrityVerifier: plugin.checksum 不存在放行, 存在则校验
- RejectUnsignedVerifier: 包装 Inner, 无 checksum 时直接拒绝 (decorator 模式)
调用方通过**组合 verifier** 来表达策略, 而不是在 interface 里开 bool 参数. 这种 "verifier 自治策略" 的设计让 loader 保持简单 (只管调 Verify), 同时支持未来任意策略组合 (e.g. "宽松 + 白名单" / "严格 + Sigstore fallback").
Shape: synchronous callback. Loader calls Verify synchronously before activating a plugin; verifier consults checksum / signature / trust store as needed and returns accept or rejection reason.
形态: 同步回调. Loader 在激活 plugin 前同步调 Verify; verifier 按需查 checksum / 签名 / 信任存储, 返回接受或拒绝原因.
func DefaultVerifier ¶
func DefaultVerifier() SignatureVerifier
DefaultVerifier 返回默认的 SignatureVerifier.
当前默认是 NoopVerifier (向后兼容, 不强制完整性校验). 消费层若想启用完整性校验, 应显式传入 NewSHA256IntegrityVerifier() 到 LoadPluginWithVerifier.
未来可能切换默认为 SHA256IntegrityVerifier (宽松模式): 当生态成熟到大部分 plugin 都会 ship plugin.checksum 文件时, 默认启用宽松模式完整性校验是 无摩擦的 (没 checksum 的 plugin 继续放行, 有的被校验). 那个时点的切换由 产品决策, 不是代码决策, 改这里一行即可.
替代方案: <硬编码 NoopVerifier 到 LoadPlugin 内部> - 否决, 独立函数让 未来切换默认值只需改一行, 不需要在 loader.go 里做 search-replace.
func NewSHA256IntegrityVerifier ¶
func NewSHA256IntegrityVerifier() SignatureVerifier
NewSHA256IntegrityVerifier 创建一个 SHA-256 完整性验证器.
使用示例:
v := plugin.NewSHA256IntegrityVerifier() p, err := plugin.LoadPluginWithVerifier(dir, v)
type Skill ¶
type Skill struct {
// Name 技能名称(命名空间化后的名称,格式:pluginName:skillName)
Name string
// RawName 技能原始名称(不含插件前缀)
RawName string
// PluginName 来源插件名称
PluginName string
// Description 技能描述
Description string
// WhenToUse 何时使用此技能的说明
WhenToUse string
// AllowedTools 此技能允许使用的工具列表
AllowedTools []string
// Content 技能正文内容(markdown)
Content string
// FilePath 技能文件的原始路径
FilePath string
}
Skill 是一个插件技能定义. 结构与 engine.Skill 兼容,但增加了插件来源信息.
func LoadPluginSkills ¶
LoadPluginSkills 从插件的技能目录加载所有技能文件.
参数:
- skillsDir: 技能目录的绝对路径
- pluginName: 插件名称(用于命名空间化)
返回加载的技能列表.跳过无法解析的文件.
func (*Skill) FormatAsPrompt ¶
FormatAsPrompt 将技能格式化为系统提示词片段.
type Source ¶
type Source int
Source 表示插件的来源级别. 来源决定了依赖规则:只允许同级或向上(项目级→用户级不允许).
精妙之处(CLEVER): 用整数而非字符串,使来源比较可以用 >= 运算符. SourceBuiltin=0 < SourceUser=1 < SourceProject=2,越大优先级越高. 如果改用字符串比较,跨来源逻辑会更复杂.
type ValidationResult ¶
type ValidationResult struct {
// Valid 清单是否通过所有验证
Valid bool
// Errors 阻断加载的验证错误
Errors []PluginError
// Warnings 不阻断加载的警告(如建议字段缺失)
Warnings []PluginError
}
ValidationResult 是 ValidateManifest 操作的结果. 与 LoadResult 分离,支持在实际加载前做"预检".
func ValidateManifest ¶
func ValidateManifest(dir string) ValidationResult
ValidateManifest 主动验证插件目录的清单,不实际加载插件. 用于安装/更新时的预检,或 lint 工具.
与 LoadManifest 的区别:
- ValidateManifest 返回结构化 ValidationResult(含 Warnings)
- LoadManifest 只返回 error(pass/fail)