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
- Variables
- func AssembleSnippets(snippets []ContextSnippet, maxTotalChars int) string
- func CommandID(plugin, id string) string
- func DefaultPluginsDir() string
- func ParseCommandID(s string) (plugin, id string, ok bool)
- func ProvideAndAssemble(ctx context.Context, m *Manager, prompt, sessionID string, maxTotalChars int) string
- func ValidateManifest(m Manifest) error
- type Client
- func (c *Client) CallTool(ctx context.Context, name string, args json.RawMessage) (json.RawMessage, error)
- func (c *Client) Commands() []CommandDecl
- func (c *Client) Connected() bool
- func (c *Client) FireEvent(event string, payload any) error
- func (c *Client) HasContextProvider() bool
- func (c *Client) InvokeCommand(ctx context.Context, id, args string) error
- func (c *Client) IsShuttingDown() bool
- func (c *Client) LastError() error
- func (c *Client) Manifest() Manifest
- func (c *Client) Name() string
- func (c *Client) Provide(ctx context.Context, prompt, sessionID string) ([]ContextSnippet, error)
- func (c *Client) SetStaticManifest(m Manifest)
- func (c *Client) Shutdown(ctx context.Context) error
- func (c *Client) Start(ctx context.Context) error
- func (c *Client) SubscribedEvents() []string
- func (c *Client) Tools() []ToolDecl
- func (c *Client) Wait() error
- type CommandDecl
- type CommandWithPlugin
- type Conn
- type ContextResponse
- type ContextSnippet
- type Discovered
- type Handler
- type HostBridge
- type Manager
- func (m *Manager) CallTool(ctx context.Context, plugin, name string, args json.RawMessage) (json.RawMessage, error)
- func (m *Manager) Commands() []CommandWithPlugin
- func (m *Manager) ContextProviders() []*Client
- func (m *Manager) Counts() (int, int)
- func (m *Manager) Dispatch(ctx context.Context, fullID, args string) error
- func (m *Manager) FireEvent(event string, payload any)
- func (m *Manager) InvokeCommand(ctx context.Context, plugin, id, args string) error
- func (m *Manager) Provide(ctx context.Context, prompt, sessionID string) []ContextSnippet
- func (m *Manager) Start(ctx context.Context) error
- func (m *Manager) Status() []Status
- func (m *Manager) Stop()
- func (m *Manager) Subscribers(event string) []*Client
- func (m *Manager) Tools() []ToolWithPlugin
- type Manifest
- type Permissions
- type RPCError
- type SessionInfo
- type Status
- type ToolAdapter
- type ToolDecl
- type ToolWithPlugin
Constants ¶
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.
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.
const HostVersion = "0.1.0"
HostVersion is what the host advertises during plugin.initialize. Plugins can use it for back-compat decisions.
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 ¶
var KnownEvents = []string{ EventToolCall, EventChatMessage, EventCompact, EventSessionSwitch, EventError, }
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 ¶
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 ¶
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 ¶
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 ¶
Connected reports whether the plugin completed initialize and hasn't died.
func (*Client) HasContextProvider ¶
HasContextProvider reports whether the plugin opted into context provision.
func (*Client) InvokeCommand ¶
InvokeCommand fires a slash-command invocation at the plugin.
func (*Client) IsShuttingDown ¶
IsShuttingDown reports whether Shutdown has been called. Supervise uses this to decide whether a process exit was deliberate.
func (*Client) Name ¶
Name returns the plugin name from discovery (may differ from the manifest name; if so, the manifest name wins after initialize).
func (*Client) Provide ¶
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 ¶
SetStaticManifest is called by discovery when a plugin.toml was loaded.
func (*Client) Shutdown ¶
Shutdown sends plugin.shutdown then SIGTERMs the process with a grace period before SIGKILL.
func (*Client) Start ¶
Start spawns the process, runs the initialize handshake, and registers the inbound host.* handlers.
func (*Client) SubscribedEvents ¶
SubscribedEvents returns the current subscription set.
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 ¶
NewConn wraps an existing stdio pair. Call Serve in a goroutine to start processing inbound messages.
func (*Conn) Close ¶
func (c *Conn) Close()
Close marks the connection as closed; the next read or write fails.
func (*Conn) Handle ¶
Handle registers an inbound RPC handler. Handlers run in their own goroutine so they can issue further calls without deadlocking the read loop.
type ContextResponse ¶
type ContextResponse struct {
Snippets []ContextSnippet `json:"snippets"`
}
ContextResponse is what context.provide returns.
type ContextSnippet ¶
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 ¶
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 ¶
ContextProviders returns connected plugins that registered as providers.
func (*Manager) Dispatch ¶
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) InvokeCommand ¶
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 ¶
Start runs discovery and spawns a supervisor for each plugin. Returns immediately; readiness is observable via Status.
func (*Manager) Stop ¶
func (m *Manager) Stop()
Stop sends shutdown to every client and waits briefly.
func (*Manager) Subscribers ¶
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).
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 ¶
ToolWithPlugin pairs a registered tool with its owning plugin name. The agent's tool registry uses this to namespace tools as plugin:<name>:<tool>.