plugins

package
v0.0.0-...-cef46df Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2026 License: MPL-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package plugins implements the plugin loader, registry, settings store, and state store for the bud2 extensibility framework (WS2 + WS3).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CronMatches

func CronMatches(c *ParsedCron, t time.Time) bool

CronMatches returns true if all fields of the parsed cron expression match t. Exported for use in tests.

Types

type ActionProxy

type ActionProxy struct {
	// contains filtered or unexported fields
}

ActionProxy exposes plugin-declared shell actions to the MCP server and to workflow type:direct steps. It implements mcp.ToolCaller so it can be set as the reflex engine's action caller.

func NewActionProxy

func NewActionProxy(registry *Registry) *ActionProxy

NewActionProxy builds an ActionProxy from the loaded registry. All plugin capabilities with type=="action" and a non-empty run: field are indexed. Call RegisterMCPTools to wire callable_from:both|model actions onto an MCP server.

func (*ActionProxy) Call

func (p *ActionProxy) Call(toolName string, args map[string]any) (string, error)

Call invokes a shell action by its fully-qualified "<ext>:<cap>" name. This is the direct-invocation path used by workflow type:direct steps. Both callable_from:both|model and callable_from:direct actions are reachable here.

func (*ActionProxy) HasAction

func (p *ActionProxy) HasAction(toolName string) bool

HasAction reports whether the proxy knows about a tool with the given name.

func (*ActionProxy) RegisterMCPTools

func (p *ActionProxy) RegisterMCPTools(server *mcp.Server)

RegisterMCPTools registers shell actions with callable_from=both|model as MCP tools on the given server. Each tool is named "<ext>:<cap>" and its input schema is derived from the capability's params: block.

Actions with callable_from=direct are silently skipped — they are reachable only from workflow type:direct steps via the Call method.

type Behavior

type Behavior struct {
	Name     string         `yaml:"name"`
	Trigger  map[string]any `yaml:"trigger"`
	Workflow string         `yaml:"workflow,omitempty"`
}

Behavior is a trigger-to-workflow binding declared in plugin.yaml. The trigger field is kept as a generic map so it can hold any trigger type (schedule, slash_command, pattern_match, event, condition, manual) without requiring schema changes as new trigger types are added in later workstreams.

type Capability

type Capability struct {
	Name         string
	Description  string
	Type         string   // "skill", "agent", "workflow", "action"
	CallableFrom string   // "model", "direct", "both"
	Body         string   // raw markdown body (content after frontmatter); empty for YAML capabilities
	Model        string   // optional model override for agent capabilities ("sonnet", "opus", "haiku")
	Tools        []string // per-capability tool allow-list (agent capabilities only)

	// Action-specific fields (populated for type: action from .yaml capability files)
	Run    string              // path to shell script relative to extension dir
	Params map[string]ParamDef // parameter schema for input validation and MCP tool generation
}

Capability is a loaded capability from a capabilities/*.md or capabilities/*.yaml file.

type CapabilityMeta

type CapabilityMeta struct {
	CallableFrom string `yaml:"callable_from"`
}

CapabilityMeta is the per-capability entry in plugin.yaml capabilities map.

type Dispatcher

type Dispatcher struct {
	// contains filtered or unexported fields
}

Dispatcher registers and fires behaviors from all loaded plugins. It owns the EventBus and is the authority on trigger lifecycle.

func NewDispatcher

func NewDispatcher(registry *Registry, bus *EventBus, runner WorkflowRunner) *Dispatcher

NewDispatcher creates a Dispatcher. Call RegisterAll or RegisterPlugin to wire behaviors, then Start to begin condition-polling loops.

func (*Dispatcher) EmitCapabilityEvent

func (d *Dispatcher) EmitCapabilityEvent(phase, extName, capName string, payload map[string]any)

EmitCapabilityEvent fires before/after events for a capability invocation. The before event is fire-and-forget (goroutine); the after event is synchronous so on_result handlers complete before the caller proceeds.

func (*Dispatcher) FirePercept

func (d *Dispatcher) FirePercept(source, typ, content string, extra map[string]any)

FirePercept routes an incoming message to pattern_match triggers.

func (*Dispatcher) FireSlashCommand

func (d *Dispatcher) FireSlashCommand(name string, data map[string]any)

FireSlashCommand manually fires a slash_command event (called by the runtime when a Discord slash command arrives that should route through the plugin system).

func (*Dispatcher) HandleOnResult

func (d *Dispatcher) HandleOnResult(ctx context.Context, configs []OnResultConfig, result any, vars map[string]any)

HandleOnResult applies a list of on_result handlers to the output of a workflow run. result is the raw output (may be string, map, or nil). vars provides template resolution context.

func (*Dispatcher) ListSlashCommands

func (d *Dispatcher) ListSlashCommands() []DispatcherSlashCommandInfo

ListSlashCommands returns the slash command names registered across all enabled plugins.

func (*Dispatcher) RegisterAll

func (d *Dispatcher) RegisterAll(ctx context.Context)

RegisterAll registers behaviors for every plugin currently in the registry. Call this once after initial load.

func (*Dispatcher) RegisterPlugin

func (d *Dispatcher) RegisterPlugin(ctx context.Context, ext *Plugin) error

RegisterPlugin registers all behaviors declared in ext.Manifest.Behaviors. Idempotent — existing registrations for the plugin are removed first.

func (*Dispatcher) SetSaveThought

func (d *Dispatcher) SetSaveThought(s SaveThought)

SetSaveThought wires the on_result:action=log callback.

func (*Dispatcher) SetTalkToUser

func (d *Dispatcher) SetTalkToUser(t TalkToUser)

SetTalkToUser wires the on_result:action=notify callback.

func (*Dispatcher) Stop

func (d *Dispatcher) Stop()

Stop cancels all trigger registrations and waits for background goroutines to exit.

func (*Dispatcher) UnregisterPlugin

func (d *Dispatcher) UnregisterPlugin(name string)

UnregisterPlugin removes all trigger registrations for the named plugin.

type DispatcherSlashCommandInfo

type DispatcherSlashCommandInfo struct {
	Command     string
	Description string
}

DispatcherSlashCommandInfo describes a slash command behavior from a plugin.

type Event

type Event struct {
	// Topic is the canonical event name: "<ext>:<cap>:<phase>" (e.g. "bud-core:fetch:after").
	Topic   string
	Payload map[string]any
}

Event is an in-process pub/sub message emitted before or after a capability invocation.

type EventBus

type EventBus struct {
	// contains filtered or unexported fields
}

EventBus is an in-process publish/subscribe bus for capability lifecycle events.

Topic format: <ext>:<cap>:<phase> e.g. "bud-core:fetch:after" Wildcard rules: any segment may be replaced with "*", e.g. "bud-core:*:after" or "*:*:after".

Indexing strategy:

  • Subscriptions are indexed by a normalised key derived by replacing non-wildcard segments with "_" — this gives O(1) bucket lookup for each incoming topic.
  • On Publish, we compute all 8 possible bucket keys for a 3-segment topic and iterate only the matching buckets.

func NewEventBus

func NewEventBus() *EventBus

NewEventBus creates a ready-to-use EventBus.

func (*EventBus) Publish

func (b *EventBus) Publish(event Event)

Publish delivers event to all matching subscribers. Matching is determined by segment-wise equality or wildcard "*" in the subscription pattern. Publish is synchronous — all handlers run on the caller's goroutine. Callers that want fire-and-forget semantics should wrap the call in a goroutine.

func (*EventBus) Subscribe

func (b *EventBus) Subscribe(pattern string, handler EventHandler) func()

Subscribe registers handler for events matching pattern. Pattern must be a 3-segment colon-separated string; any segment may be "*". Returns an unsubscribe function that removes this handler.

type EventHandler

type EventHandler func(e Event)

EventHandler is a function invoked when a matching event is published.

type MCPServerDef

type MCPServerDef struct {
	Command string            `yaml:"command"`
	Args    []string          `yaml:"args,omitempty"`
	Env     map[string]string `yaml:"env,omitempty"`
}

MCPServerDef describes an MCP subprocess that the extension can start on demand.

type Manifest

type Manifest struct {
	Name         string                    `yaml:"name"`
	Version      string                    `yaml:"version,omitempty"`
	Description  string                    `yaml:"description"`
	Author       string                    `yaml:"author,omitempty"`
	Capabilities map[string]CapabilityMeta `yaml:"capabilities,omitempty"`
	Behaviors    []Behavior                `yaml:"behaviors,omitempty"`
	Lifecycle    map[string]string         `yaml:"lifecycle,omitempty"`
	Requires     Requirements              `yaml:"requires,omitempty"`
	MCPServers   map[string]MCPServerDef   `yaml:"mcp_servers,omitempty"`
	// Settings is a flat map from setting key to its JSON Schema subset node.
	// Treat this as the "properties" of an implicit root object schema.
	Settings map[string]SchemaNode `yaml:"settings,omitempty"`
	// SettingsRequired lists which top-level settings keys are required.
	// Missing required settings emit a warning on load, but never hard-fail.
	SettingsRequired []string `yaml:"settings_required,omitempty"`
}

Manifest is the parsed content of plugin.yaml.

type OnResultConfig

type OnResultConfig struct {
	// Parse pre-processes the workflow result before evaluation.
	// Supported: "json" (extract JSON from the result string).
	Parse string `yaml:"parse,omitempty"`

	// Condition is an expr-lang expression evaluated against the (possibly parsed) result.
	// The handler fires only when the condition is true or when Condition is empty.
	Condition string `yaml:"condition,omitempty"`

	// Action determines what to do when the handler fires.
	// "notify" → talk_to_user, "log" → save_thought, "invoke" → run a workflow.
	Action string `yaml:"action"`

	// Message is the notification message for action=notify. Supports {{var}} templates.
	Message string `yaml:"message,omitempty"`

	// Workflow is the workflow name for action=invoke. Supports {{var}} templates.
	Workflow string `yaml:"workflow,omitempty"`

	// Params are passed to the invoked workflow. Values support {{var}} templates.
	Params map[string]any `yaml:"params,omitempty"`
}

OnResultConfig is the parsed on_result block from a behavior or capability definition.

type ParamDef

type ParamDef struct {
	Type        string `yaml:"type,omitempty"`        // "string", "integer", "boolean", "number"
	Description string `yaml:"description,omitempty"` // human-readable description
	Required    bool   `yaml:"required,omitempty"`    // whether the parameter must be supplied
	Default     any    `yaml:"default,omitempty"`     // value applied when param is absent
	Enum        []any  `yaml:"enum,omitempty"`        // allowed values
}

ParamDef describes a single parameter for an action capability. Unlike SchemaNode (which uses Required as a []string for object schemas), ParamDef uses Required as a bool to indicate whether the parameter must be provided.

type ParsedCron

type ParsedCron struct {
	// contains filtered or unexported fields
}

ParsedCron holds the 5 fields: minute, hour, dom, month, dow. Exported for use in tests.

func ParseCron

func ParseCron(expr string) (*ParsedCron, error)

ParseCron parses a 5-field cron expression. Exported for use in tests.

type PatternMatcher

type PatternMatcher interface {
	// MatchAndRun attempts to match content against the named reflex (by name) and execute it.
	// Returns (matched, error).
	MatchAndRun(ctx context.Context, name string, source, typ, content string, data map[string]any) (bool, error)
}

PatternMatcher checks whether content from a given source/type matches a reflex trigger. The Dispatcher delegates pattern_match triggers to this.

type Plugin

type Plugin struct {
	Manifest     Manifest
	Dir          string
	Capabilities map[string]*Capability
	Settings     map[string]any // current settings (loaded + schema defaults applied)
	State        map[string]any // current state
	// contains filtered or unexported fields
}

Plugin is a fully-loaded plugin ready for use by the runtime.

func LoadPlugin

func LoadPlugin(dir string) (*Plugin, error)

LoadPlugin reads one plugin from dir, validates the manifest, loads all capabilities, applies settings schema defaults, and loads any existing state. Soft failures (unknown schema keywords, missing-but-defaultable settings, etc.) emit log warnings rather than returning errors.

func (*Plugin) Enabled

func (e *Plugin) Enabled() bool

Enabled reports whether the plugin is currently enabled. A plugin is considered enabled if _enabled is absent (default on) or explicitly set to true.

func (*Plugin) SettingsGet

func (e *Plugin) SettingsGet(key string) any

SettingsGet returns the current value for the given settings key. Returns nil (no error) if the key does not exist.

func (*Plugin) SettingsSet

func (e *Plugin) SettingsSet(key string, value any) error

SettingsSet writes a new value for the given settings key. If the plugin manifest declares a schema for this key, the value is validated against it; a type mismatch returns an error and nothing is written. On success the in-memory settings map is updated and settings.json is persisted.

func (*Plugin) StateGet

func (e *Plugin) StateGet(key string) any

StateGet returns the current value for the given state key. Returns nil (no error) if the key does not exist. The "_enabled" key is reserved by the runtime to track enable/disable status.

func (*Plugin) StateSet

func (e *Plugin) StateSet(key string, value any) error

StateSet writes a new value for the given state key and persists state.json. No schema validation is performed; state is arbitrary key-value storage.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds all successfully loaded plugins, ordered by dependency.

func LoadAll

func LoadAll(dirs ...string) (*Registry, error)

LoadAll loads plugins from each of the given dirs in order. Later dirs override earlier ones when the same plugin name appears in multiple dirs.

Plugins are ordered by their declared requires.plugins dependency graph. If a cycle is detected, the cycled plugins are excluded from the Registry.

Empty strings in dirs are silently skipped.

func (*Registry) All

func (r *Registry) All() []*Plugin

All returns all plugins in topological dependency order.

func (*Registry) Capabilities

func (r *Registry) Capabilities() []string

Capabilities returns the names of all capabilities across all loaded plugins. Names are in the form "<plugin-name>:<capability-name>".

func (*Registry) CapabilitiesOfType

func (r *Registry) CapabilitiesOfType(capType string) []struct {
	FullName string
	Cap      *Capability
	Ext      *Plugin
}

CapabilitiesOfType returns all capabilities across all plugins with the given type and a callable_from value that includes model invocation ("model" or "both"). Each entry is a (fullName, capability, plugin) triple.

func (*Registry) FindCapabilityByName

func (r *Registry) FindCapabilityByName(name string) (*Capability, *Plugin, bool)

FindCapabilityByName searches all plugins for a capability with the given short name. If multiple plugins define a capability with the same name, returns the first match. Returns (nil, nil, false) if not found.

func (*Registry) Get

func (r *Registry) Get(name string) *Plugin

Get returns the Plugin with the given name, or nil if not found.

func (*Registry) GetCapabilityByFullName

func (r *Registry) GetCapabilityByFullName(fullName string) (*Capability, *Plugin, bool)

GetCapabilityByFullName looks up a capability by its "pluginname:capname" full name. Returns (capability, plugin, true) on success, or (nil, nil, false) if not found.

func (*Registry) Len

func (r *Registry) Len() int

Len returns the number of plugins in the registry.

type Requirements

type Requirements struct {
	// Plugins lists required plugin names. LoadAll topologically sorts
	// based on this field and rejects cycles.
	Plugins    []string `yaml:"plugins,omitempty"`
	Tools      []string `yaml:"tools,omitempty"`
	MCPServers []string `yaml:"mcp_servers,omitempty"`
}

Requirements declares what a plugin depends on from the runtime.

type SaveThought

type SaveThought interface {
	Log(message string) error
}

SaveThought persists a log entry (used by on_result:action=log).

type SchemaNode

type SchemaNode struct {
	Type        string `yaml:"type,omitempty"`
	Description string `yaml:"description,omitempty"`
	Default     any    `yaml:"default,omitempty"`
	// Required lists required property names within an object-type SchemaNode.
	Required   []string              `yaml:"required,omitempty"`
	Enum       []any                 `yaml:"enum,omitempty"`
	Minimum    *float64              `yaml:"minimum,omitempty"`
	Maximum    *float64              `yaml:"maximum,omitempty"`
	MinLength  *int                  `yaml:"minLength,omitempty"`
	MaxLength  *int                  `yaml:"maxLength,omitempty"`
	Pattern    string                `yaml:"pattern,omitempty"`
	Items      *SchemaNode           `yaml:"items,omitempty"`
	Properties map[string]SchemaNode `yaml:"properties,omitempty"`
	// Unknown captures any YAML keys not listed above for warning purposes.
	Unknown map[string]any `yaml:",inline"`
}

SchemaNode is a subset of JSON Schema used to describe and validate settings values.

Supported keywords: type, description, default, required (array, for object types), enum, minimum, maximum, minLength, maxLength, pattern, items, properties.

Unknown YAML keys are captured in Unknown and produce a warning on load. They are never treated as errors — unsupported keywords are warn-and-ignored.

type TalkToUser

type TalkToUser interface {
	Notify(message string) error
}

TalkToUser sends a notification message to the user (used by on_result:action=notify).

type WorkflowRunner

type WorkflowRunner interface {
	RunWorkflow(ctx context.Context, name string, params map[string]any) (any, error)
}

WorkflowRunner executes a named workflow with given params and returns the result. The Dispatcher calls this to fire behaviors.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL