plugin

package
v0.11.0-beta.2 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: Apache-2.0, MIT Imports: 17 Imported by: 0

Documentation

Overview

Package plugin implements a subprocess-based plugin runtime for Overkill. Plugins are external executables that speak JSON-RPC 2.0 over stdio.

Wire format mirrors internal/mcp: newline-delimited JSON, requests carry numeric ids, notifications carry no id, and responses echo the request id. Unlike MCP, the host both calls into the plugin (tool.call, event.fire, etc.) and accepts inbound calls from the plugin (host.register_tool, host.toast, host.config_get, etc.).

Index

Constants

View Source
const (
	EventToolCall      = "tool_call"
	EventChatMessage   = "chat_message"
	EventCompact       = "compact"
	EventSessionSwitch = "session_switch"
	EventError         = "error"
)

Event names the plugin runtime understands. Keep this list in lockstep with the wire-protocol documentation — these are the only strings plugins can put in manifest.permissions.events.

View Source
const (
	ErrCodeParse          = -32700
	ErrCodeInvalidRequest = -32600
	ErrCodeMethodNotFound = -32601
	ErrCodeInvalidParams  = -32602
	ErrCodeInternal       = -32603

	// ErrCodePermissionDenied is returned when a plugin tries to read a
	// config key, call a tool, or subscribe to an event it didn't declare
	// in its manifest.
	ErrCodePermissionDenied = -32001
	// ErrCodeUnknownPlugin is returned for routing failures.
	ErrCodeUnknownPlugin = -32002
)

Standard JSON-RPC 2.0 error codes plus a few we use for permission and internal failures. Codes -32000 to -32099 are reserved for server-defined implementation-specific errors per the JSON-RPC spec.

View Source
const HostVersion = "0.1.0"

HostVersion is what the host advertises during plugin.initialize. Plugins can use it for back-compat decisions.

View Source
const MaxRestarts = 3

MaxRestarts is how many times a crashed plugin will be respawned before the manager gives up until the next overkill launch.

Variables

KnownEvents is exported so the discovery / doctor surfaces can validate permissions at install time.

Functions

func AssembleSnippets

func AssembleSnippets(snippets []ContextSnippet, maxTotalChars int) string

AssembleSnippets turns a slice of context snippets into a system-prompt fragment. Returns "" when there's nothing to add. Used by the agent before each model call.

maxTotalChars is a global budget for the entire assembled fragment (including headers). Snippets are added until the budget is exhausted; leftover snippets are dropped and a truncation warning is appended.

func CommandID

func CommandID(plugin, id string) string

CommandID is the wire format for plugin slash-commands in the TUI: it looks like `plugin:<plugin>:<id>` so the central dispatch can route by prefix without ambiguity.

func DefaultPluginsDir

func DefaultPluginsDir() string

DefaultPluginsDir returns ~/.overkill/plugins.

func ParseCommandID

func ParseCommandID(s string) (plugin, id string, ok bool)

ParseCommandID splits a plugin command id into (plugin, id, ok).

func ProvideAndAssemble

func ProvideAndAssemble(ctx context.Context, m *Manager, prompt, sessionID string, maxTotalChars int) string

ProvideAndAssemble is a convenience wrapper around Manager.Provide + AssembleSnippets. Safe to call when m is nil.

maxTotalChars is forwarded to AssembleSnippets as the global budget.

func ValidateManifest

func ValidateManifest(m Manifest) error

ValidateManifest performs basic structural checks. Returns a permission denial only for clearly-invalid manifests; everything else is up to the caller (e.g. matching against a static plugin.toml).

Types

type Client

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

Client is one running plugin: a subprocess plus the JSON-RPC connection over its stdio. The client owns the manifest and the sets of tools, commands, and event subscriptions the plugin registered.

func NewClient

func NewClient(name string, command string, args []string, env map[string]string, bridge HostBridge) *Client

NewClient prepares a client; call Start to spawn it.

func (*Client) CallTool

func (c *Client) CallTool(ctx context.Context, name string, args json.RawMessage) (json.RawMessage, error)

CallTool invokes a tool registered by this plugin.

func (*Client) Commands

func (c *Client) Commands() []CommandDecl

Commands returns a snapshot of registered commands.

func (*Client) Connected

func (c *Client) Connected() bool

Connected reports whether the plugin completed initialize and hasn't died.

func (*Client) FireEvent

func (c *Client) FireEvent(event string, payload any) error

FireEvent delivers a notification for a subscribed event.

func (*Client) HasContextProvider

func (c *Client) HasContextProvider() bool

HasContextProvider reports whether the plugin opted into context provision.

func (*Client) InvokeCommand

func (c *Client) InvokeCommand(ctx context.Context, id, args string) error

InvokeCommand fires a slash-command invocation at the plugin.

func (*Client) IsShuttingDown

func (c *Client) IsShuttingDown() bool

IsShuttingDown reports whether Shutdown has been called. Supervise uses this to decide whether a process exit was deliberate.

func (*Client) LastError

func (c *Client) LastError() error

LastError surfaces the last failure for the /plugins dialog.

func (*Client) Manifest

func (c *Client) Manifest() Manifest

Manifest returns the post-initialize manifest.

func (*Client) Name

func (c *Client) Name() string

Name returns the plugin name from discovery (may differ from the manifest name; if so, the manifest name wins after initialize).

func (*Client) Provide

func (c *Client) Provide(ctx context.Context, prompt, sessionID string) ([]ContextSnippet, error)

Provide asks the plugin's context provider for snippets. Returns nil when the plugin didn't register a context provider or if the call times out.

func (*Client) SetStaticManifest

func (c *Client) SetStaticManifest(m Manifest)

SetStaticManifest is called by discovery when a plugin.toml was loaded.

func (*Client) Shutdown

func (c *Client) Shutdown(ctx context.Context) error

Shutdown sends plugin.shutdown then SIGTERMs the process with a grace period before SIGKILL.

func (*Client) Start

func (c *Client) Start(ctx context.Context) error

Start spawns the process, runs the initialize handshake, and registers the inbound host.* handlers.

func (*Client) SubscribedEvents

func (c *Client) SubscribedEvents() []string

SubscribedEvents returns the current subscription set.

func (*Client) Tools

func (c *Client) Tools() []ToolDecl

Tools returns a snapshot of registered tools.

func (*Client) Wait

func (c *Client) Wait() error

Wait blocks until the underlying process exits, returning its error (or nil for a clean exit).

type CommandDecl

type CommandDecl struct {
	ID          string `json:"id"`
	Title       string `json:"title"`
	Description string `json:"description,omitempty"`
}

CommandDecl is what host.register_command receives.

type CommandWithPlugin

type CommandWithPlugin struct {
	Plugin  string
	Command CommandDecl
}

CommandWithPlugin is the same idea for slash commands.

type Conn

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

Conn is a bidirectional JSON-RPC 2.0 framer over a stdio pair.

func NewConn

func NewConn(w io.Writer, r io.Reader) *Conn

NewConn wraps an existing stdio pair. Call Serve in a goroutine to start processing inbound messages.

func (*Conn) Call

func (c *Conn) Call(ctx context.Context, method string, params any) (json.RawMessage, error)

Call sends a request and waits for the response (or ctx cancellation).

func (*Conn) Close

func (c *Conn) Close()

Close marks the connection as closed; the next read or write fails.

func (*Conn) Handle

func (c *Conn) Handle(method string, h Handler)

Handle registers an inbound RPC handler. Handlers run in their own goroutine so they can issue further calls without deadlocking the read loop.

func (*Conn) Notify

func (c *Conn) Notify(method string, params any) error

Notify sends a fire-and-forget notification.

func (*Conn) Serve

func (c *Conn) Serve(ctx context.Context) error

Serve runs the read loop. Returns when the underlying reader yields an error (typically io.EOF when the peer exits).

type ContextResponse

type ContextResponse struct {
	Snippets []ContextSnippet `json:"snippets"`
}

ContextResponse is what context.provide returns.

type ContextSnippet

type ContextSnippet struct {
	Title   string `json:"title"`
	Content string `json:"content"`
}

ContextSnippet is one item returned by a plugin's context.provide. Title is rendered as a short header; Content is the body that gets prepended to the system prompt.

type Discovered

type Discovered struct {
	Name           string
	EntryPath      string
	EntryArgs      []string
	Env            map[string]string
	StaticManifest *Manifest
}

Discovered is one plugin found by walking the plugins directory.

func Discover

func Discover(root string) ([]Discovered, error)

Discover walks the plugins directory and returns one Discovered entry per plugin. Two layouts are supported:

  • <dir>/<name>/plugin.toml + entry script
  • <dir>/<name>/<name> executable file
  • <dir>/<name> single-file executable

type Handler

type Handler func(ctx context.Context, params json.RawMessage) (any, error)

Handler executes an inbound RPC call. The returned value will be marshaled as the `result` field. To return a JSON-RPC error, return a *RPCError.

type HostBridge

type HostBridge interface {
	// SessionInfo returns the current session for host.session_get.
	SessionInfo() SessionInfo
	// ConfigValue returns a config value the plugin asked for. Returning
	// (nil, nil) is treated as "key exists but no value" — the host should
	// only return ErrCodePermissionDenied through the bridge by surfacing
	// the lookup as a permission failure on the manifest layer.
	ConfigValue(key string) (any, bool)
	// Toast displays a toast in the host UI. Best-effort.
	Toast(kind, text string)
}

HostBridge is the abstract surface a plugin Client uses to satisfy host.* RPCs. The Manager implements it. Pulling this out of the Client keeps the wire layer testable in isolation.

type Manager

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

Manager owns N Clients keyed by plugin name. It performs discovery, supervises restarts with exponential backoff, and routes RPCs.

func NewManager

func NewManager(root string, bridge HostBridge, disabled []string) *Manager

NewManager constructs a Manager rooted at the given directory. `disabled` is the set of plugin names to skip (typically cfg.Plugins.Disabled).

func (*Manager) CallTool

func (m *Manager) CallTool(ctx context.Context, plugin, name string, args json.RawMessage) (json.RawMessage, error)

CallTool routes a tool invocation to the owning plugin.

func (*Manager) Commands

func (m *Manager) Commands() []CommandWithPlugin

Commands returns every connected plugin's slash commands.

func (*Manager) ContextProviders

func (m *Manager) ContextProviders() []*Client

ContextProviders returns connected plugins that registered as providers.

func (*Manager) Counts

func (m *Manager) Counts() (int, int)

Counts returns (running, failed) for the status bar.

func (*Manager) Dispatch

func (m *Manager) Dispatch(ctx context.Context, fullID, args string) error

Dispatch is the helper the TUI's dispatchCommandWithArgs uses. Given a prefixed command id and the user-supplied args string, it routes to the right plugin via the manager.

func (*Manager) FireEvent

func (m *Manager) FireEvent(event string, payload any)

FireEvent broadcasts an event to every subscribed plugin.

func (*Manager) InvokeCommand

func (m *Manager) InvokeCommand(ctx context.Context, plugin, id, args string) error

InvokeCommand routes a slash-command selection to the owning plugin.

func (*Manager) Provide

func (m *Manager) Provide(ctx context.Context, prompt, sessionID string) []ContextSnippet

Provide queries every context provider in parallel with a 2s timeout per plugin. Plugins that error or time out are silently skipped.

func (*Manager) Start

func (m *Manager) Start(ctx context.Context) error

Start runs discovery and spawns a supervisor for each plugin. Returns immediately; readiness is observable via Status.

func (*Manager) Status

func (m *Manager) Status() []Status

Status returns a snapshot for the /plugins dialog.

func (*Manager) Stop

func (m *Manager) Stop()

Stop sends shutdown to every client and waits briefly.

func (*Manager) Subscribers

func (m *Manager) Subscribers(event string) []*Client

Subscribers returns the plugins that subscribed to the given event.

func (*Manager) Tools

func (m *Manager) Tools() []ToolWithPlugin

Tools returns every connected plugin's tools, flattened.

type Manifest

type Manifest struct {
	Name        string      `json:"name"`
	Version     string      `json:"version"`
	Description string      `json:"description,omitempty"`
	Permissions Permissions `json:"permissions"`
}

Manifest is the plugin self-description returned from plugin.initialize. Permissions are enforced — any host RPC that touches a config key, an outbound tool call, or an event subscription is gated against this list.

type Permissions

type Permissions struct {
	ConfigKeys []string `json:"config_keys,omitempty"`
	ToolsCall  []string `json:"tools_call,omitempty"`
	Events     []string `json:"events,omitempty"`
}

Permissions is the declared scope of host capabilities the plugin needs. Anything outside these lists is rejected with ErrCodePermissionDenied.

func (Permissions) AllowsConfigKey

func (p Permissions) AllowsConfigKey(key string) bool

AllowsConfigKey reports whether the manifest declared the given config key.

func (Permissions) AllowsEvent

func (p Permissions) AllowsEvent(ev string) bool

AllowsEvent reports whether the plugin declared an interest in an event. Subscribing to an undeclared event is rejected.

func (Permissions) AllowsTool

func (p Permissions) AllowsTool(name string) bool

AllowsTool reports whether the plugin may invoke the given host tool by name through its tool-call permission. (Plugins always own the tools they register themselves; this gate is for invoking *other* tools the host exposes.)

type RPCError

type RPCError struct {
	Code    int             `json:"code"`
	Message string          `json:"message"`
	Data    json.RawMessage `json:"data,omitempty"`
}

RPCError mirrors the JSON-RPC 2.0 error object. Exported so call sites can inspect the code (e.g. permission denial vs transport failure).

func (*RPCError) Error

func (e *RPCError) Error() string

type SessionInfo

type SessionInfo struct {
	ID           string `json:"id"`
	Title        string `json:"title"`
	MessageCount int    `json:"message_count"`
}

SessionInfo is the response shape for host.session_get. Kept small on purpose — plugins shouldn't snoop the full conversation.

type Status

type Status struct {
	Name      string
	Version   string
	Running   bool
	LastError string
	Tools     int
	Commands  int
	Restarts  int
	Disabled  bool
}

Status is the snapshot the /plugins dialog reads.

type ToolAdapter

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

ToolAdapter exposes a plugin-registered tool through the agent's internal/tools.Tool interface. Tool name format: plugin:<plugin>:<tool>.

func NewToolAdapter

func NewToolAdapter(m *Manager, plugin, tool string) *ToolAdapter

NewToolAdapter wraps a single plugin tool for registration in the agent.

func (*ToolAdapter) Execute

func (a *ToolAdapter) Execute(ctx context.Context, input json.RawMessage) (json.RawMessage, error)

func (*ToolAdapter) Name

func (a *ToolAdapter) Name() string

type ToolDecl

type ToolDecl struct {
	Name        string          `json:"name"`
	Description string          `json:"description,omitempty"`
	ArgsSchema  json.RawMessage `json:"args_schema,omitempty"`
	RiskLevel   string          `json:"risk_level,omitempty"`
}

ToolDecl is the schema a plugin sends via host.register_tool.

type ToolWithPlugin

type ToolWithPlugin struct {
	Plugin string
	Tool   ToolDecl
}

ToolWithPlugin pairs a registered tool with its owning plugin name. The agent's tool registry uses this to namespace tools as plugin:<name>:<tool>.

Jump to

Keyboard shortcuts

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