refactor: migrate built-in plugins to SDK-only interface

- Six-phase plan complete: webui/cli/healthcheck/pluginmgr/clawhubadapter
  now interact with the kernel exclusively via internal/sdk interfaces;
  all Configure() calls and package-level global injection removed
- buildSDK in internal/plugin/registry.go is the single assembly point
- Add internal/sdk/events.go exporting event types/constants
- Fix ProviderManager cooldown sharing: LuaAdaptedProvider.Name() now
  returns the source name instead of lua_<adapter>, so multiple sources
  sharing an adapter (single script load via shared VM AdapterCache) no
  longer share failure-cooldown state
- Verified: build/vet/tests green, deployed to homeagent.service with
  full plugin capability testing via local OpenAI-compatible mock
This commit is contained in:
root
2026-08-01 12:17:17 +08:00
parent 512777e27d
commit 57712f220b
47 changed files with 1515 additions and 1137 deletions

View File

@ -31,18 +31,11 @@ var pySimulatorSrc string
//go:embed simulator/openclaw_cli.js
var openclawCliSrc string
var SkillsDir string
var SimulatorDir string
func init() {
plugin.RegisterPluginMeta("clawhubadapter", "ClawHub 适配器", "ClawHub Adapter")
plugin.RegisterFactory("clawhubadapter", func(name string, config map[string]interface{}) (sdk.Plugin, error) {
dir := SkillsDir
if dir == "" {
dataDir, ok := config["data_dir"].(string)
if !ok {
return nil, fmt.Errorf("clawhubadapter plugin: config missing 'data_dir' or not a string")
}
dir := ""
if dataDir, ok := config["data_dir"].(string); ok && dataDir != "" {
dir = filepath.Join(dataDir, "skills")
}
return New(name, dir), nil
@ -63,8 +56,8 @@ type Plugin struct {
}
func New(name, skillsDir string) *Plugin {
sd := SimulatorDir
if sd == "" {
sd := ""
if skillsDir != "" {
sd = filepath.Join(skillsDir, ".simulator")
}
return &Plugin{
@ -100,6 +93,20 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
p.simulatorDir = s
}
}
// 默认目录:内核 data_dir 下 skills 目录(与配置 core.daemon.data_dir 对齐)
if p.skillsDir == "" {
if v, _ := s.Settings().GetCore("daemon.data_dir"); v != nil {
if dir, ok := v.(string); ok && dir != "" {
p.skillsDir = filepath.Join(dir, "skills")
}
}
}
if p.skillsDir == "" {
p.skillsDir = filepath.Join("data", "skills")
}
if p.simulatorDir == "" {
p.simulatorDir = filepath.Join(p.skillsDir, ".simulator")
}
// Launch OC plugin manager first (handles OC-format plugin installation and lifecycle)
os.MkdirAll(p.skillsDir, 0755)
@ -283,7 +290,7 @@ func (p *Plugin) launchManager(s *sdk.PluginSDK) error {
// Ensure skills dir exists for the manager to scan
os.MkdirAll(p.skillsDir, 0755)
sp, err := launchProcess("node", managerPath, p.skillsDir, "manager")
sp, err := launchProcess("node", managerPath, p.skillsDir, "manager", p.simulatorDir)
if err != nil {
return fmt.Errorf("launch manager: %w", err)
}
@ -775,7 +782,7 @@ func (p *Plugin) loadPySidecar(s *sdk.PluginSDK, dir, name string) error {
}
}
sp, err := launchProcess(pythonBin, simPath, dir, name)
sp, err := launchProcess(pythonBin, simPath, dir, name, p.simulatorDir)
if err != nil {
return fmt.Errorf("launch pysimulator: %w", err)
}
@ -808,7 +815,7 @@ func (p *Plugin) loadPySidecar(s *sdk.PluginSDK, dir, name string) error {
}
func (p *Plugin) loadSidecar(s *sdk.PluginSDK, dir, name string) error {
sp, err := launchSidecar(dir, name)
sp, err := launchSidecar(dir, name, p.simulatorDir)
if err != nil {
return fmt.Errorf("launch: %w", err)
}

View File

@ -143,15 +143,15 @@ func (s *sidecarProcess) NotifyChan() <-chan OCNotification {
return s.notifyCh
}
func launchSidecar(dir, name string) (*sidecarProcess, error) {
func launchSidecar(dir, name, simDir string) (*sidecarProcess, error) {
mainJS := filepath.Join(dir, "main.js")
if _, err := os.Stat(mainJS); os.IsNotExist(err) {
return nil, nil
}
return launchProcess("node", mainJS, dir, name)
return launchProcess("node", mainJS, dir, name, simDir)
}
func launchProcess(bin, arg, dir, name string) (*sidecarProcess, error) {
func launchProcess(bin, arg, dir, name, simDir string) (*sidecarProcess, error) {
nodePath := bin
if bin == "node" {
if p := os.Getenv("NODE_PATH"); p != "" {
@ -164,8 +164,8 @@ func launchProcess(bin, arg, dir, name string) (*sidecarProcess, error) {
cmd.Stderr = os.Stderr
// Add openclaw CLI bin dir to PATH so subprocesses can exec 'openclaw' command
if SimulatorDir != "" {
binDir := filepath.Join(SimulatorDir, "bin")
if simDir != "" {
binDir := filepath.Join(simDir, "bin")
if info, err := os.Stat(binDir); err == nil && info.IsDir() {
env := os.Environ()
binDirPath := binDir + string(os.PathListSeparator)

View File

@ -26,10 +26,12 @@ func (m *mockSettings) RegisterDef(def pubsdk.ConfigDef) {}
func (m *mockSettings) Defs(prefix string) []*pubsdk.ConfigDef { return nil }
func (m *mockSettings) Dump() map[string]interface{} { return nil }
func (m *mockSettings) Plugins() []string { return nil }
func (m *mockSettings) DefsCore(prefix string) []*sdk.ConfigDef { return nil }
func (m *mockSettings) DefsPlugin(plugin, prefix string) []*sdk.ConfigDef { return nil }
func TestLaunchSidecarNoMainJS(t *testing.T) {
tmpDir := t.TempDir()
sp, err := launchSidecar(tmpDir, "nonexistent")
sp, err := launchSidecar(tmpDir, "nonexistent", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -50,7 +52,7 @@ func TestLaunchSidecarAndListTools(t *testing.T) {
t.Fatalf("write test plugin: %v", err)
}
sp, err := launchSidecar(tmpDir, "echoplugin")
sp, err := launchSidecar(tmpDir, "echoplugin", "")
if err != nil {
t.Fatalf("launch sidecar: %v", err)
}
@ -92,7 +94,7 @@ func TestCallEchoTool(t *testing.T) {
t.Fatalf("write test plugin: %v", err)
}
sp, err := launchSidecar(tmpDir, "echoplugin")
sp, err := launchSidecar(tmpDir, "echoplugin", "")
if err != nil {
t.Fatalf("launch sidecar: %v", err)
}
@ -124,7 +126,7 @@ func TestCallAddTool(t *testing.T) {
t.Fatalf("write test plugin: %v", err)
}
sp, err := launchSidecar(tmpDir, "echoplugin")
sp, err := launchSidecar(tmpDir, "echoplugin", "")
if err != nil {
t.Fatalf("launch sidecar: %v", err)
}
@ -157,7 +159,7 @@ func TestCallNonexistentTool(t *testing.T) {
t.Fatalf("write test plugin: %v", err)
}
sp, err := launchSidecar(tmpDir, "echoplugin")
sp, err := launchSidecar(tmpDir, "echoplugin", "")
if err != nil {
t.Fatalf("launch sidecar: %v", err)
}
@ -182,7 +184,7 @@ func TestConcurrentCalls(t *testing.T) {
t.Fatalf("write test plugin: %v", err)
}
sp, err := launchSidecar(tmpDir, "echoplugin")
sp, err := launchSidecar(tmpDir, "echoplugin", "")
if err != nil {
t.Fatalf("launch sidecar: %v", err)
}
@ -217,7 +219,7 @@ func launchSimulator(t *testing.T, pluginDir, name string) *sidecarProcess {
if err != nil {
t.Fatalf("abs simulator path: %v", err)
}
sp, err := launchProcess("node", simPath, pluginDir, name)
sp, err := launchProcess("node", simPath, pluginDir, name, "")
if err != nil {
t.Fatalf("launch simulator for %s: %v", name, err)
}
@ -338,8 +340,6 @@ func TestLoadOCPluginViaPluginStart(t *testing.T) {
}
}
SimulatorDir = filepath.Join(t.TempDir(), ".simulator")
p := New("openclaw", skillsDir)
var registeredTools []string