extension

package
v1.11.0 Latest Latest
Warning

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

Go to latest
Published: Jul 16, 2026 License: GPL-3.0 Imports: 24 Imported by: 0

Documentation

Overview

Package extension provides the core extension framework for OpsKat. It loads WASM extensions, manages their lifecycle, and bridges them into the main application's tool, policy, and frontend systems.

pkg/extension/host.go

pkg/extension/host_capability.go

pkg/extension/host_default.go

pkg/extension/hostfn.go

pkg/extension/io_handle.go

pkg/extension/io_http.go

pkg/extension/runtime.go

Index

Constants

View Source
const (
	CredentialAccessNone = ""
	CredentialAccessRead = "read"
)

CredentialAccessNone means the extension receives only opaque credential handles (default). CredentialAccessRead means the extension may request plaintext credentials (discouraged).

View Source
const HostABIVersion = "1.0"

HostABIVersion is the current host ABI contract version. Extensions must declare a compatible hostABI in their manifest. Bump the minor version when adding new host functions (backward compatible); bump the major version when removing or changing existing host function signatures.

Variables

View Source
var SupportedHostABIs = []string{"1.0"}

SupportedHostABIs lists all host ABI versions the runtime accepts.

Functions

func IsBuiltinSnippetCategoryID added in v1.4.0

func IsBuiltinSnippetCategoryID(id string) bool

IsBuiltinSnippetCategoryID reports whether id is reserved by the app core.

func IsPrivateIP

func IsPrivateIP(ip net.IP) bool

IsPrivateIP returns true for RFC1918, loopback, link-local, unspecified, IPv6 ULA, IPv6 link-local, and AWS/GCP metadata endpoints. Exported so that the dial-time guard in io_http.go can reuse it.

func LoadLocales

func LoadLocales(dir string) map[string]map[string]string

LoadLocales reads all JSON files from the extension's locales/ directory. Language codes are normalized to lowercase for consistent matching (e.g. "zh-CN" → "zh-cn").

func PasswordFieldsFromSchema

func PasswordFieldsFromSchema(schema map[string]any) []string

PasswordFieldsFromSchema extracts property names that have "format": "password" from a JSON Schema configSchema.

Types

type ActionCancellation added in v1.4.0

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

ActionCancellation is a flag polled by long-running actions via host_action_should_stop. Set by the host when the frontend requests cancel.

func NewActionCancellation added in v1.4.0

func NewActionCancellation() *ActionCancellation

func (*ActionCancellation) Cancel added in v1.4.0

func (c *ActionCancellation) Cancel()

Cancel marks the action as canceled. Idempotent.

func (*ActionCancellation) ShouldStop added in v1.4.0

func (c *ActionCancellation) ShouldStop() bool

ShouldStop returns true if Cancel has been called.

type ActionEventHandler

type ActionEventHandler interface {
	OnActionEvent(eventType string, data json.RawMessage) error
}

type AssetConfigGetter

type AssetConfigGetter interface {
	GetAssetConfig(assetID int64) (json.RawMessage, error)
}

Dependency interfaces for DefaultHostProvider

type AssetTypeDef

type AssetTypeDef struct {
	Type         string         `json:"type"`
	I18n         I18nName       `json:"i18n"`
	ConfigSchema map[string]any `json:"configSchema"`
	ProxyChain   bool           `json:"proxyChain,omitempty"` // opt in; false keeps the asset direct
}

type Bridge

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

Bridge connects loaded extensions to the main app's tool, policy, and frontend systems.

func NewBridge

func NewBridge() *Bridge

func (*Bridge) FindExtensionByTool

func (b *Bridge) FindExtensionByTool(extName, toolName string) *Extension

func (*Bridge) GetAssetTypes

func (b *Bridge) GetAssetTypes() []ExtAssetType

func (*Bridge) GetDefaultPolicyGroups

func (b *Bridge) GetDefaultPolicyGroups(assetType string) []string

func (*Bridge) GetExtensionByAssetType

func (b *Bridge) GetExtensionByAssetType(assetType string) *Extension

GetExtensionByAssetType returns the Extension that registered the given asset type, or nil if no extension owns that type.

func (*Bridge) GetExtensionPolicyGroups

func (b *Bridge) GetExtensionPolicyGroups(extName, assetType string, assetID int64) []string

func (*Bridge) GetPolicyGroups

func (b *Bridge) GetPolicyGroups() []ExtPolicyGroup

func (*Bridge) GetSkillMD

func (b *Bridge) GetSkillMD(assetType string) string

func (*Bridge) GetSkillMDWithExtension

func (b *Bridge) GetSkillMDWithExtension(assetType string) SkillMDWithExtension

GetSkillMDWithExtension returns the SKILL.md content and extension name for an asset type. Returns empty struct if no SKILL.md is registered for the type.

func (*Bridge) ListNames

func (b *Bridge) ListNames() []string

func (*Bridge) Register

func (b *Bridge) Register(ext *Extension)

func (*Bridge) Unregister

func (b *Bridge) Unregister(name string)

type Capabilities

type Capabilities struct {
	FS          FSCapability   `json:"fs"`
	HTTP        HTTPCapability `json:"http"`
	Credentials string         `json:"credentials"` // "" (none) | "read"
	Tunnel      bool           `json:"tunnel"`      // allow routing HTTP through the asset's SSH tunnel
}

Capabilities declares what host resources an extension is permitted to access. Default (zero value) is deny-all except an implicit read-only ${EXT_DIR}/** for the extension's own directory. Extensions must declare every capability they need; the host enforces these at each host_* call site.

type DefaultHostConfig

type DefaultHostConfig struct {
	Logger           *zap.Logger
	AssetConfigs     AssetConfigGetter
	FileDialogs      FileDialogOpener
	KV               KVStore
	ActionEvents     ActionEventHandler
	TunnelDialer     TunnelDialer // SSH tunnel dialer (nil = no tunnel support)
	AssetSSHTunnelID int64        // Current asset's SSH tunnel ID (0 = direct)
}

type DefaultHostProvider

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

func NewDefaultHostProvider

func NewDefaultHostProvider(cfg DefaultHostConfig) *DefaultHostProvider

func (*DefaultHostProvider) ActionEvent

func (h *DefaultHostProvider) ActionEvent(eventType string, data json.RawMessage) error

func (*DefaultHostProvider) ActionShouldStop added in v1.4.0

func (h *DefaultHostProvider) ActionShouldStop() bool

func (*DefaultHostProvider) CloseAll

func (h *DefaultHostProvider) CloseAll()

func (*DefaultHostProvider) FileDialog

func (h *DefaultHostProvider) FileDialog(dialogType string, opts DialogOptions) (string, error)

func (*DefaultHostProvider) GetAssetConfig

func (h *DefaultHostProvider) GetAssetConfig(assetID int64) (json.RawMessage, error)

func (*DefaultHostProvider) IOClose

func (h *DefaultHostProvider) IOClose(handleID uint32) error

func (*DefaultHostProvider) IOFlush

func (h *DefaultHostProvider) IOFlush(handleID uint32) (*IOMeta, error)

func (*DefaultHostProvider) IOOpen

func (h *DefaultHostProvider) IOOpen(params IOOpenParams) (uint32, IOMeta, error)

func (*DefaultHostProvider) IORead

func (h *DefaultHostProvider) IORead(handleID uint32, size int) ([]byte, error)

func (*DefaultHostProvider) IOSetDeadline added in v1.4.0

func (h *DefaultHostProvider) IOSetDeadline(handleID uint32, kind string, unixNanos int64) error

func (*DefaultHostProvider) IOWrite

func (h *DefaultHostProvider) IOWrite(handleID uint32, data []byte) (int, error)

func (*DefaultHostProvider) KVGet

func (h *DefaultHostProvider) KVGet(key string) ([]byte, error)

func (*DefaultHostProvider) KVSet

func (h *DefaultHostProvider) KVSet(key string, value []byte) error

func (*DefaultHostProvider) Log

func (h *DefaultHostProvider) Log(level, msg string)

func (*DefaultHostProvider) SetActiveCancellation added in v1.4.0

func (h *DefaultHostProvider) SetActiveCancellation(c *ActionCancellation)

type DialFunc

type DialFunc func(network, addr string) (net.Conn, error)

DialFunc is a custom dialer for HTTP transports (e.g. SSH tunnel).

type DialogOptions

type DialogOptions struct {
	Title       string   `json:"title"`
	DefaultName string   `json:"defaultName"`
	Filters     []string `json:"filters"`
}

type ExtAssetType

type ExtAssetType struct {
	Type          string
	ExtensionName string
	ConfigSchema  map[string]any
	I18n          I18nName
	ProxyChain    bool
}

ExtAssetType represents an extension-provided asset type.

type ExtPolicyGroup

type ExtPolicyGroup struct {
	ID            string
	ExtensionName string
	PolicyType    string
	I18n          I18nNameDesc
	Policy        map[string]any
}

ExtPolicyGroup represents an extension-provided policy group.

type Extension

type Extension struct {
	Name     string
	Dir      string
	Manifest *Manifest
	Plugin   *Plugin
	SkillMD  string                       // Contents of SKILL.md
	Locales  map[string]map[string]string // lang → key → translated text
}

Extension represents a loaded extension.

func (*Extension) Translate

func (e *Extension) Translate(lang, key string) string

Translate resolves an i18n key for the given language. Falls back to "en", then returns the key itself.

type FSCapability

type FSCapability struct {
	Read  []string `json:"read"`
	Write []string `json:"write"`
}

FSCapability lists filesystem path patterns an extension may access. Patterns support one ${EXT_DIR} placeholder (resolved at load time to the extension's directory) and a trailing /** for recursive match. All other entries are treated as absolute path prefixes.

type FileDialogOpener

type FileDialogOpener interface {
	FileDialog(dialogType string, opts DialogOptions) (string, error)
}

type FrontendDef

type FrontendDef struct {
	Entry  string    `json:"entry"`
	Styles string    `json:"styles"`
	Pages  []PageDef `json:"pages"`
}

type HTTPCapability

type HTTPCapability struct {
	Allowlist []string `json:"allowlist"`
}

HTTPCapability lists URL prefix patterns an extension may open via host_io_open(type=http). Entries are URL prefixes; the request URL must start with at least one allowlist entry. Private-network destinations (RFC1918, loopback, link-local) are always rejected unless the target also matches an allowlist entry AND Tunnel=true.

type HostProvider

type HostProvider interface {
	IOOpen(params IOOpenParams) (uint32, IOMeta, error)
	IORead(handleID uint32, size int) ([]byte, error)
	IOWrite(handleID uint32, data []byte) (int, error)
	IOFlush(handleID uint32) (*IOMeta, error)
	IOClose(handleID uint32) error
	// IOSetDeadline sets read/write/both deadline on a handle.
	// unixNanos is an absolute deadline in Unix nanoseconds; 0 clears any existing deadline.
	// kind ∈ {"read","write","both"}. Returns an error if the underlying handle type does not support deadlines.
	IOSetDeadline(handleID uint32, kind string, unixNanos int64) error
	GetAssetConfig(assetID int64) (json.RawMessage, error)
	FileDialog(dialogType string, opts DialogOptions) (string, error)
	Log(level, msg string)
	KVGet(key string) ([]byte, error)
	KVSet(key string, value []byte) error
	ActionEvent(eventType string, data json.RawMessage) error
	ActionShouldStop() bool
	SetActiveCancellation(c *ActionCancellation)
	CloseAll()
}

HostProvider defines the capabilities that the host provides to extensions. Main App and DevServer each provide their own implementation.

func NewCapabilityHost

func NewCapabilityHost(inner HostProvider, manifest *Manifest, extDir string) HostProvider

NewCapabilityHost wraps inner with capability enforcement.

type I18nDesc

type I18nDesc struct {
	Description string `json:"description"`
}

type I18nName

type I18nName struct {
	Name string `json:"name"`
}

type I18nNameDesc

type I18nNameDesc struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

type IOHandleManager

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

IOHandleManager manages IO handles for a single WASM invocation.

func NewIOHandleManager

func NewIOHandleManager() *IOHandleManager

func (*IOHandleManager) Close

func (m *IOHandleManager) Close(id uint32) error

func (*IOHandleManager) CloseAll

func (m *IOHandleManager) CloseAll()

func (*IOHandleManager) Flush

func (m *IOHandleManager) Flush(id uint32) (*IOMeta, error)

Flush flushes the HTTP handle (sends the request and waits for response).

func (*IOHandleManager) GetMeta

func (m *IOHandleManager) GetMeta(id uint32) (IOMeta, error)

func (*IOHandleManager) OpenFile

func (m *IOHandleManager) OpenFile(path string, mode string) (uint32, IOMeta, error)

func (*IOHandleManager) OpenHTTP

func (m *IOHandleManager) OpenHTTP(params IOOpenParams, dial DialFunc) (uint32, IOMeta, error)

OpenHTTP creates an HTTP handle, wraps it with adapters, and stores it.

func (*IOHandleManager) Read

func (m *IOHandleManager) Read(id uint32, buf []byte) (int, error)

func (*IOHandleManager) Register

func (m *IOHandleManager) Register(r io.Reader, w io.Writer, c io.Closer, meta IOMeta) (uint32, error)

Register adds an externally-created handle entry and returns its ID. Returns 0 and does not register if handle IDs are exhausted.

func (*IOHandleManager) SetDeadline added in v1.4.0

func (m *IOHandleManager) SetDeadline(id uint32, kind string, t time.Time) error

SetDeadline sets read/write deadline on a handle whose underlying resource supports it. kind ∈ {"read", "write", "both"}.

func (*IOHandleManager) Write

func (m *IOHandleManager) Write(id uint32, data []byte) (int, error)

type IOMeta

type IOMeta struct {
	Size        int64             `json:"size,omitempty"`
	ContentType string            `json:"contentType,omitempty"`
	Status      int               `json:"status,omitempty"`
	Headers     map[string]string `json:"headers,omitempty"`
}

IOMeta contains metadata about an IO handle.

type IOOpenParams

type IOOpenParams struct {
	Type         string            `json:"type"`
	Path         string            `json:"path"`
	Mode         string            `json:"mode"`
	Method       string            `json:"method"`
	URL          string            `json:"url"`
	Headers      map[string]string `json:"headers"`
	AllowPrivate bool              `json:"allowPrivate"` // dial-time guard: allow connections to private/loopback IPs
	// tcp (new)
	Addr    string `json:"addr,omitempty"`
	Timeout int    `json:"timeout,omitempty"` // ms; 0 = default 10s
}

type KVStore

type KVStore interface {
	Get(key string) ([]byte, error)
	Set(key string, value []byte) error
}

type Manager

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

Manager handles extension discovery, loading, and lifecycle.

func NewManager

func NewManager(dir string, newHost func(extName string) HostProvider, logger *zap.Logger) *Manager

func (*Manager) Close

func (m *Manager) Close(ctx context.Context)

func (*Manager) ExtDir

func (m *Manager) ExtDir(name string) string

ExtDir returns the path to a named extension's directory.

func (*Manager) GetExtension

func (m *Manager) GetExtension(name string) *Extension

func (*Manager) Install

func (m *Manager) Install(ctx context.Context, sourcePath string) (*Manifest, error)

Install installs an extension from a zip file or directory.

func (*Manager) ListExtensions

func (m *Manager) ListExtensions() []*Extension

func (*Manager) LoadExtension

func (m *Manager) LoadExtension(ctx context.Context, dir string) (*Manifest, error)

func (*Manager) Scan

func (m *Manager) Scan(ctx context.Context) ([]*Manifest, error)

Scan discovers and loads extensions from the extensions directory.

func (*Manager) ScanManifests

func (m *Manager) ScanManifests() ([]*ManifestInfo, error)

ScanManifests reads manifests from disk without loading WASM plugins.

func (*Manager) Shutdown

func (m *Manager) Shutdown(ctx context.Context)

Shutdown closes all extensions and releases the compilation cache.

func (*Manager) Uninstall

func (m *Manager) Uninstall(ctx context.Context, name string) error

Uninstall stops and removes an extension from disk.

func (*Manager) Unload

func (m *Manager) Unload(ctx context.Context, name string) error

func (*Manager) Watch

func (m *Manager) Watch(ctx context.Context, onChange func()) error

Watch monitors the extensions directory for changes and calls onChange. The caller is responsible for handling the reload logic.

type Manifest

type Manifest struct {
	Name          string          `json:"name"`
	Version       string          `json:"version"`
	Icon          string          `json:"icon"`
	MinAppVersion string          `json:"minAppVersion"`
	HostABI       string          `json:"hostABI"`
	Capabilities  Capabilities    `json:"capabilities"`
	I18n          ManifestI18n    `json:"i18n"`
	Backend       ManifestBackend `json:"backend"`
	AssetTypes    []AssetTypeDef  `json:"assetTypes"`
	Tools         []ToolDef       `json:"tools"`
	Policies      PoliciesDef     `json:"policies"`
	Frontend      FrontendDef     `json:"frontend"`
	Snippets      SnippetsDef     `json:"snippets"`
}

func ParseManifest

func ParseManifest(data []byte) (*Manifest, error)

func (*Manifest) CheckCredentialRead

func (m *Manifest) CheckCredentialRead() error

CheckCredentialRead returns nil if the extension is allowed to request plaintext credential values. Default is deny.

func (*Manifest) CheckFSRead

func (m *Manifest) CheckFSRead(path, extDir string) error

CheckFSRead returns nil if reading the given absolute path is permitted by the extension's fs.read capabilities. extDir is the extension's own directory (used to resolve ${EXT_DIR} placeholders).

func (*Manifest) CheckFSWrite

func (m *Manifest) CheckFSWrite(path, extDir string) error

CheckFSWrite returns nil if writing to the given absolute path is permitted by the extension's fs.write capabilities.

func (*Manifest) CheckHTTPURL

func (m *Manifest) CheckHTTPURL(urlStr string, tunnelAllowed bool) error

CheckHTTPURL returns nil if the given URL is permitted by the extension's http capabilities. Enforces:

  1. The URL prefix must match an allowlist entry.
  2. Private/loopback/link-local destinations are rejected unless tunnelAllowed is true.
  3. The scheme must be http or https.

tunnelAllowed should be true ONLY when the host has an active SSH tunnel configured AND the extension declared capabilities.tunnel=true.

func (*Manifest) CheckTunnel

func (m *Manifest) CheckTunnel() error

CheckTunnel returns nil if the extension is allowed to use SSH tunnel routing.

func (*Manifest) Localized

func (m *Manifest) Localized(tr func(key string) string) *Manifest

Localized returns a shallow copy of the manifest with all i18n string fields resolved using the provided translate function.

type ManifestBackend

type ManifestBackend struct {
	Runtime string `json:"runtime"`
	Binary  string `json:"binary"`
}

type ManifestI18n

type ManifestI18n struct {
	DisplayName string `json:"displayName"`
	Description string `json:"description"`
}

type ManifestInfo

type ManifestInfo struct {
	Name     string
	Dir      string
	Manifest *Manifest
	Locales  map[string]map[string]string
}

ManifestInfo holds manifest data for an extension that may not be loaded.

func LoadManifestInfo

func LoadManifestInfo(dir string) (*ManifestInfo, error)

LoadManifestInfo reads a manifest from disk without loading the WASM plugin.

func (*ManifestInfo) Translate

func (mi *ManifestInfo) Translate(lang, key string) string

Translate resolves an i18n key for the given language.

type PageDef

type PageDef struct {
	ID        string   `json:"id"`
	Slot      string   `json:"slot,omitempty"`
	I18n      I18nName `json:"i18n"`
	Component string   `json:"component"`
}

type Plugin

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

Plugin represents a loaded WASM extension.

func LoadPlugin

func LoadPlugin(ctx context.Context, manifest *Manifest, wasmBytes []byte, host HostProvider, cache wazero.CompilationCache) (*Plugin, error)

LoadPlugin compiles a WASM binary and prepares it for execution. If cache is non-nil, compiled modules are cached to disk for faster subsequent loads.

func (*Plugin) CallAction

func (p *Plugin) CallAction(ctx context.Context, actionName string, args json.RawMessage) (json.RawMessage, error)

CallAction calls execute_action on the extension.

The cancellation is installed AFTER acquiring p.mu so that concurrent CallAction invocations don't race on host.activeCancel: the setup and the WASM execution live in the same critical section, and the defer clears the cancel before releasing the lock, so the next caller installs fresh state.

func (*Plugin) CallTool

func (p *Plugin) CallTool(ctx context.Context, toolName string, args json.RawMessage) (json.RawMessage, error)

CallTool calls execute_tool on the extension.

func (*Plugin) CancelActiveAction added in v1.4.0

func (p *Plugin) CancelActiveAction()

CancelActiveAction triggers cancellation of the currently running action. Due to Plugin.mu serializing CallAction invocations, at most one action runs per plugin at a time — this cancels that one. No-op if idle.

func (*Plugin) CheckPolicy

func (p *Plugin) CheckPolicy(ctx context.Context, toolName string, args json.RawMessage) (action, resource string, err error)

CheckPolicy calls check_policy on the extension.

func (*Plugin) Close

func (p *Plugin) Close(ctx context.Context) error

Close releases the WASM runtime resources.

func (*Plugin) Manifest

func (p *Plugin) Manifest() *Manifest

Manifest returns the plugin's manifest.

func (*Plugin) ValidateConfig

func (p *Plugin) ValidateConfig(ctx context.Context, config json.RawMessage) ([]ValidationError, error)

ValidateConfig calls validate_config on the extension.

type PoliciesDef

type PoliciesDef struct {
	Type    string           `json:"type"`
	Actions []string         `json:"actions"`
	Groups  []PolicyGroupDef `json:"groups"`
	Default []string         `json:"default"`
}

type PolicyGroupDef

type PolicyGroupDef struct {
	ID     string         `json:"id"`
	I18n   I18nNameDesc   `json:"i18n"`
	Policy map[string]any `json:"policy"`
}

type SeedSnippetDef added in v1.4.0

type SeedSnippetDef struct {
	Key         string `json:"key"`
	Name        string `json:"name"`
	Category    string `json:"category"`
	Content     string `json:"content"`
	Description string `json:"description"`
}

SeedSnippetDef is a read-only snippet shipped with the extension.

Validation rules:

  • Key must match ^[a-z0-9][a-z0-9-]{0,63}$ and be unique within this manifest.
  • Name and Content must be non-empty after trimming.
  • Category must be either a builtin category id or a category declared in THIS manifest.

type SkillMDWithExtension

type SkillMDWithExtension struct {
	ExtensionName string
	Content       string
}

SkillMDWithExtension pairs SKILL.md content with its originating extension name.

type SnippetCategoryDef added in v1.4.0

type SnippetCategoryDef struct {
	ID        string   `json:"id"`
	AssetType string   `json:"assetType"`
	I18n      I18nName `json:"i18n"`
}

SnippetCategoryDef declares a new snippet category id that this extension owns.

Validation rules:

  • ID must match ^[a-z][a-z0-9-]{0,31}$ (lowercase kebab, <=32 chars).
  • ID must NOT collide with any builtin category (see IsBuiltinSnippetCategoryID).
  • ID must be unique within this manifest.
  • AssetType must be non-empty and must match a type declared in assetTypes[].

type SnippetsDef added in v1.4.0

type SnippetsDef struct {
	Categories []SnippetCategoryDef `json:"categories"`
	Seed       []SeedSnippetDef     `json:"seed"`
}

SnippetsDef declares snippet categories and seed snippets contributed by this extension. All fields are optional; an empty block is a no-op.

type ToolDef

type ToolDef struct {
	Name       string         `json:"name"`
	I18n       I18nDesc       `json:"i18n"`
	Parameters map[string]any `json:"parameters"`
}

type TunnelDialer

type TunnelDialer interface {
	Dial(tunnelAssetID int64, addr string) (net.Conn, error)
}

TunnelDialer dials a TCP address through an SSH tunnel.

type ValidationError

type ValidationError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

ValidationError represents a config validation error.

Jump to

Keyboard shortcuts

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