Files
overlord/Overlord-Client/cmd/agent/plugins/manager.go
T

148 lines
3.0 KiB
Go
Raw Normal View History

2026-03-05 00:04:26 -06:00
package plugins
import (
"context"
2026-03-19 16:29:26 -05:00
"encoding/json"
2026-03-05 00:04:26 -06:00
"errors"
"log"
"sync"
"overlord-client/cmd/agent/wire"
)
type Manager struct {
mu sync.Mutex
plugins map[string]*pluginInstance
writer wire.Writer
host HostInfo
}
type pluginInstance struct {
id string
manifest PluginManifest
2026-03-19 16:29:26 -05:00
native NativePlugin
2026-03-05 00:04:26 -06:00
}
func NewManager(writer wire.Writer, host HostInfo) *Manager {
return &Manager{
plugins: make(map[string]*pluginInstance),
writer: writer,
host: host,
}
}
2026-03-19 16:29:26 -05:00
func (m *Manager) Load(ctx context.Context, manifest PluginManifest, binary []byte) error {
2026-04-20 13:46:59 -05:00
//garble:controlflow block_splits=max junk_jumps=max flatten_passes=max
2026-03-19 16:29:26 -05:00
if len(binary) == 0 {
return errors.New("empty plugin binary")
2026-03-05 00:04:26 -06:00
}
pluginID := manifest.ID
if pluginID == "" {
return errors.New("missing plugin id")
}
m.mu.Lock()
if existing, ok := m.plugins[pluginID]; ok {
2026-03-19 16:29:26 -05:00
existing.native.Close()
2026-03-05 00:04:26 -06:00
delete(m.plugins, pluginID)
}
m.mu.Unlock()
2026-03-19 16:29:26 -05:00
np, err := loadNativePlugin(binary)
2026-03-05 00:04:26 -06:00
if err != nil {
return err
}
pi := &pluginInstance{
id: pluginID,
manifest: manifest,
2026-03-19 16:29:26 -05:00
native: np,
2026-03-05 00:04:26 -06:00
}
2026-03-19 16:29:26 -05:00
send := func(event string, payload []byte) {
var payloadVal interface{}
if len(payload) > 0 {
var parsed interface{}
if json.Unmarshal(payload, &parsed) == nil {
payloadVal = parsed
} else {
payloadVal = string(payload)
2026-03-05 00:04:26 -06:00
}
2026-03-19 16:29:26 -05:00
}
err := wire.WriteMsg(context.Background(), m.writer, wire.PluginEvent{
Type: "plugin_event",
PluginID: pluginID,
Event: event,
Payload: payloadVal,
2026-03-05 00:04:26 -06:00
})
2026-03-19 16:29:26 -05:00
if err != nil {
log.Printf("[plugin] %s send event error: %v", pluginID, err)
}
2026-03-05 00:04:26 -06:00
}
2026-03-19 16:29:26 -05:00
hostJSON, err := json.Marshal(m.host)
if err != nil {
np.Close()
return err
}
if err := np.Load(send, hostJSON); err != nil {
np.Close()
2026-03-05 00:04:26 -06:00
return err
}
m.mu.Lock()
m.plugins[pluginID] = pi
m.mu.Unlock()
rt := np.Runtime()
freeable := rt != "go"
log.Printf("[plugin] loaded %s (native, runtime=%s, freeable=%v)", pluginID, rt, freeable)
2026-03-05 00:04:26 -06:00
return nil
}
func (m *Manager) Dispatch(ctx context.Context, pluginId, event string, payload interface{}) error {
m.mu.Lock()
pi := m.plugins[pluginId]
m.mu.Unlock()
if pi == nil {
return nil
}
2026-03-19 16:29:26 -05:00
data, err := json.Marshal(payload)
if err != nil {
return err
}
return pi.native.Event(event, data)
2026-03-05 00:04:26 -06:00
}
func (m *Manager) Close() {
m.mu.Lock()
defer m.mu.Unlock()
for id, pi := range m.plugins {
rt := pi.native.Runtime()
2026-03-19 16:29:26 -05:00
pi.native.Close()
2026-03-05 00:04:26 -06:00
delete(m.plugins, id)
if rt != "go" {
log.Printf("[plugin] unloaded %s (runtime=%s, memory freed)", id, rt)
} else {
log.Printf("[plugin] unloaded %s (runtime=go, memory leaked — see golang/go#11100)", id)
}
2026-03-05 00:04:26 -06:00
}
}
func (m *Manager) Unload(pluginId string) {
m.mu.Lock()
defer m.mu.Unlock()
if pi, ok := m.plugins[pluginId]; ok {
rt := pi.native.Runtime()
2026-03-19 16:29:26 -05:00
pi.native.Close()
2026-03-05 00:04:26 -06:00
delete(m.plugins, pluginId)
if rt != "go" {
log.Printf("[plugin] unloaded %s (runtime=%s, memory freed)", pluginId, rt)
} else {
log.Printf("[plugin] unloaded %s (runtime=go, memory leaked — see golang/go#11100)", pluginId)
}
2026-03-05 00:04:26 -06:00
}
}