Skip to main content
xbot
切换暗/亮/自动模式 切换暗/亮/自动模式 切换暗/亮/自动模式 返回首页
编辑页面

本文档的英文版本包含完整的代码示例和 API 参考。请参阅 English version 获取完整内容。

本文档提供 PluginContext API 的中文概览。详细的 API 参考和代码示例请参阅英文版本。

PluginContext is the only interface plugins use to interact with xbot. It provides controlled, permission-filtered access to xbot’s capabilities.

Overview

PluginContext is a composite interface combining multiple sub-interfaces:

type PluginContext interface {
    ToolRegistrar      // Register tools and middleware
    HookSubscriber     // Subscribe to lifecycle hooks
    StorageProvider    // Per-plugin KV storage
    SessionMetadata    // Read-only session info
    EventBusPublisher  // Plugin-to-plugin events
    UIContributor      // Widgets, themes, overlays
    CronScheduler      // Schedule cron jobs
    // ... plus configuration and channel provider methods
}

Access is filtered by declared permissions — plugins can only use what they declare in plugin.json.

Sub-Interfaces

ToolRegistrar

Register tools for the LLM to use:

type ToolRegistrar interface {
    RegisterTool(tool PluginTool) error
    RegisterTools(tools ...PluginTool) error
    UseMiddleware(middleware PluginMiddleware) error
}

Permission required: tools.register

func (p *MyPlugin) Activate(ctx plugin.PluginContext) error {
    tool := plugin.ToolFromFunc("greet", "Greet someone", func(ctx context.Context, input string) (string, error) {
        return "Hello!", nil
    })
    return ctx.RegisterTool(tool)
}

HookSubscriber

Subscribe to lifecycle hooks:

type HookSubscriber interface {
    OnPreToolUse(matcher string, handler HookHandler) error
    OnPostToolUse(matcher string, handler HookHandler) error
    OnUserPrompt(handler HookHandler) error
    OnAgentStop(handler HookHandler) error
    OnSessionStart(handler HookHandler) error
    OnSessionEnd(handler HookHandler) error
    OnEvent(event HookEvent, matcher string, handler HookHandler) error
    OnAllToolUse(handler HookHandler) error
    OnError(handler HookHandler) error
}

Permission required: hooks.register

func (p *MyPlugin) Activate(ctx plugin.PluginContext) error {
    return ctx.OnPreToolUse("Shell", func(ctx context.Context, payload *plugin.HookPayload) (*plugin.HookResult, error) {
        // Intercept Shell tool calls before execution
        return &plugin.HookResult{Decision: plugin.DecisionAllow}, nil
    })
}

StorageProvider

Per-plugin persistent key-value storage:

type StorageProvider interface {
    Storage() StorageAccessor
    StorageInt(key string) (int64, bool)
    StorageBool(key string) (bool, bool)
    StorageJSON(key string, value any) error
    StorageGetJSON(key string, target any) error
}

Permission required: storage

Storage location: ~/.xbot/plugins/<id>/data/storage.json

func (p *MyPlugin) Activate(ctx plugin.PluginContext) error {
    // Store a value
    ctx.Storage().Set("counter", "42")
    
    // Retrieve typed values
    count, ok := ctx.StorageInt("counter")  // int64(42), true
    
    // Store JSON
    ctx.StorageJSON("config", map[string]any{"theme": "dark"})
    
    // Retrieve JSON
    var cfg map[string]any
    ctx.StorageGetJSON("config", &cfg)
    
    return nil
}

SessionMetadata

Read-only session information:

type SessionMetadata interface {
    PluginID() string
    WorkingDir() string
    Channel() string    // "cli", "web", "feishu", etc.
    ChatID() string
    TenantID() int64
    Logger() Logger
}

Available to all plugins (no permission required).

func (p *MyPlugin) Activate(ctx plugin.PluginContext) error {
    logger := ctx.Logger()
    logger.Info("Plugin activated", 
        plugin.Field{Key: "workDir", Value: ctx.WorkingDir()},
        plugin.Field{Key: "channel", Value: ctx.Channel()},
    )
    return nil
}

EventBusPublisher

Plugin-to-plugin pub/sub communication:

type EventBusPublisher interface {
    Subscribe(topic string, handler PluginEventHandler) error
    Publish(topic string, data any) error
}

Permissions required: bus.read (subscribe), bus.write (publish), bus.plugin (both)

func (p *MyPlugin) Activate(ctx plugin.PluginContext) error {
    // Subscribe to events from other plugins
    ctx.Subscribe("xbot.git-fancy:commit", func(ctx context.Context, topic string, data any) error {
        logger := ctx.Logger()
        logger.Info("Received commit event")
        return nil
    })
    
    // Publish events to other plugins
    ctx.Publish("xbot.my-plugin:ready", map[string]any{"version": "1.0.0"})
    
    return nil
}

UIContributor

Register UI widgets, themes, and overlays:

type UIContributor interface {
    ContributeUI(widgetID, zone string, widget UIWidget, priority int) error
    UpdateWidget(widgetID string) error
    SetWidgetRegistry(wr *WidgetRegistry)
    ContributeTheme(id string, themeData []byte) error
    RegisterOverlay(id string, provider OverlayProvider) error
    ShowOverlay(id string) error
    HideOverlay() error
    RegisterWebActionHandler(widgetID string, handler WebActionHandler) error
}

Permission required: ui.contribute (widgets), ui.themes (themes)

func (p *MyPlugin) Activate(ctx plugin.PluginContext) error {
    widget := &MyWidget{}
    return ctx.ContributeUI("my-widget", "statusBarRight", widget, 100)
}

CronScheduler

Schedule cron jobs:

type CronScheduler interface {
    ScheduleCron(spec CronContribution) (string, error)
}

Permission required: cron

func (p *MyPlugin) Activate(ctx plugin.PluginContext) error {
    _, err := ctx.ScheduleCron(plugin.CronContribution{
        Message: "Check for updates",
        EverySeconds: 3600,
    })
    return err
}

Configuration

Read and update the plugin’s own user configuration, and subscribe to live changes:

// PluginContext exposes three configuration methods:
Config() (map[string]any, error)               // merged config: manifest defaults + user overrides
SetConfig(key string, value any) error         // persist a single key (~/.xbot/plugins/<id>/config.json)
OnConfigChanged(cb func(map[string]any)) error // subscribe to changes (hot reload)

Permission required: config

Declare configurable settings in plugin.json under contributes.configuration:

{
  "contributes": {
    "configuration": {
      "title": "My Plugin Settings",
      "properties": {
        "mode": {
          "type": "select",
          "label": "运行模式",
          "description": "Choose how the plugin behaves",
          "default": "auto",
          "options": [
            { "label": "Auto", "value": "auto" },
            { "label": "Manual", "value": "manual" }
          ]
        },
        "level": { "type": "number", "label": "Level", "default": 5, "minimum": 1, "maximum": 100 }
      }
    }
  }
}

Supported property types: boolean, string, number, select, multiselect. Each property may also declare label, description, default, options (for select/multiselect), section (grouping), secret (masked input), placeholder, required, minimum/maximum.

The Web UI renders these into a settings form (Settings, Plugins category) automatically from the schema. Users can edit them there, and changes are hot-reloaded: OnConfigChanged fires with the new merged config within the running plugin (no reload needed).

func (p *MyPlugin) Activate(ctx plugin.PluginContext) error {
    cfg, err := ctx.Config()
    if err != nil {
        return err
    }
    mode, _ := cfg["mode"].(string)

    // Apply live changes without restarting:
    if err := ctx.OnConfigChanged(func(merged map[string]any) {
        // e.g. update an internal field, re-run a widget, etc.
    }); err != nil {
        return err
    }
    return nil
}

Configuration is stored at ~/.xbot/plugins/<id>/config.json (global, shared by all users).

Permission Enforcement

Every PluginContext method call is checked against the plugin’s declared permissions. If a plugin tries to use a capability it didn’t declare, the call returns an error:

// plugin.json declares: ["tools.register"]
// This works:
ctx.RegisterTool(tool)

// This returns an error:
ctx.Subscribe("topic", handler)  // bus.read not declared

SDK Helpers

The plugin/sdk.go file provides convenience functions:

// Create a simple tool from a function
tool := plugin.ToolFromFunc("name", "desc", func(ctx context.Context, input string) (string, error) {
    return "result", nil
})

// Create a tool with JSON input
tool := plugin.ToolFromJSONFunc("name", "desc", params, func(ctx context.Context, input json.RawMessage) (any, error) {
    return result, nil
})

// Pre-built hook handlers
plugin.DenyHook("blocked")   // Always deny
plugin.AllowHook()            // Always allow
plugin.LogHook(logger, "msg") // Log and allow

See Also