Files
HomeAgent/internal/plugin/dynamic.go
root 2d314b3e9c 重构: 插件自注册 + .so 动态加载 + 中断打断机制
- 所有内置插件 init() 自注册 (plugin.RegisterFactory), 移除 main.go 硬编码
- 新增 .so 动态加载器 (internal/plugin/dynamic.go), 插件可编译为 plugin.so
- 新增 plugin.json 元数据 (internal/plugin/manifest.go)
- 新增 interceptLoop 独立 goroutine:
  (a) cancelLLM() 取消进行中的 HTTP 请求
  (b) interceptCh → drainInterrupt() 注入 [打断消息] 到 LLM 上下文
  (c) InjectInput 空闲时触发新处理循环
- 新增 internal/plugins/all.go 空白导入触发所有内置插件 init()
- internal/sdk/ 作为 PluginSDK 正式 Go API
- internal/api/ → internal/plugins/webui/ 迁移
- 删除旧 cmd/cli/, 使用 cmd/waiter/ 替代
- 更新 PLAN.md / ARCHITECTURE.md / README.md 文档
2026-07-03 16:53:34 +08:00

88 lines
2.4 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 plugin
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"plugin"
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
)
// .so 插件必须导出函数 NewPlugin签名与 NativeFactory 一致:
//
// func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) {
// return &myPlugin{name: name}, nil
// }
const (
soEntry = "plugin.so"
luaEntry = "main.lua"
metaEntry = "plugin.json"
)
type dynamicPlugin struct {
name string
impl sdk.Plugin
}
func (p *dynamicPlugin) Name() string { return p.name }
func (p *dynamicPlugin) Start(s *sdk.PluginSDK) error { return p.impl.Start(s) }
func (p *dynamicPlugin) Stop() error { return p.impl.Stop() }
// readManifest 读取插件目录下的 plugin.json。文件不存在时不报错。
func readManifest(dir string) *PluginManifest {
data, err := os.ReadFile(filepath.Join(dir, metaEntry))
if err != nil {
return nil
}
var m PluginManifest
if err := json.Unmarshal(data, &m); err != nil {
return nil
}
return &m
}
// tryLoadSO 尝试从插件目录加载 plugin.soGo plugin -buildmode=plugin
// 返回 nil,nil 表示目录中没有 plugin.so。
func tryLoadSO(dir, name string, config map[string]interface{}) (sdk.Plugin, error) {
soPath := filepath.Join(dir, soEntry)
if _, err := os.Stat(soPath); os.IsNotExist(err) {
return nil, nil
}
p, err := plugin.Open(soPath)
if err != nil {
return nil, fmt.Errorf("plugin.Open %s: %w", soPath, err)
}
sym, err := p.Lookup("NewPlugin")
if err != nil {
return nil, fmt.Errorf(".so %s must export NewPlugin: %w", soPath, err)
}
fn, ok := sym.(func(string, map[string]interface{}) (sdk.Plugin, error))
if !ok {
return nil, fmt.Errorf("NewPlugin in %s has wrong signature", soPath)
}
plg, err := fn(name, config)
if err != nil {
return nil, fmt.Errorf("NewPlugin %s: %w", name, err)
}
return &dynamicPlugin{name: name, impl: plg}, nil
}
// tryLoadLua 尝试从插件目录加载 main.luaLua 插件)。
// 返回 nil,nil 表示目录中没有 main.lua。
func tryLoadLua(dir, name string, config map[string]interface{}) (sdk.Plugin, error) {
luaPath := filepath.Join(dir, luaEntry)
if _, err := os.Stat(luaPath); os.IsNotExist(err) {
return nil, nil
}
// 预留Lua 插件需在 LuaVM 中注册一个 LuaPlugin 包装器
return nil, fmt.Errorf("lua plugin loading not yet implemented: %s", name)
}