Stdio 插件通过 stdin/stdout 上的 JSON 与 xbot 通信。该协议支持任何编程语言。
stdio 运行时使用双向 JSON 行协议:
- xbot → 插件:请求(带
method字段的 JSON 对象) - 插件 → xbot:响应(带
result或error的 JSON 对象)或入站消息(带method字段的 JSON 对象)
xbot Plugin Process
│ │
│ ── activate request ────→ │
│ ←── activate response ── │
│ │
│ ── execute_tool req ────→ │
│ ←── tool result ───────── │
│ │
│ ←── channel_inbound ───── │ (async push)
│ │
│ ── hook event ──────────→ │
│ ←── hook result ───────── │
│ │
│ ── deactivate ──────────→ │
│ ←── response ──────────── │
{
"method": "activate",
"params": {
"pluginId": "my-plugin",
"config": { "key": "value" }
}
}
{
"result": "success",
"tools": [...],
"hooks": [...],
"enrichers": [...],
"channel_provider": {...}
}
或错误:
{
"error": "something went wrong"
}
{
"method": "channel_inbound",
"params": {
"message": "user input"
}
}
插件被激活时发送。插件应在响应中注册其能力。
请求:
{
"method": "activate",
"params": {
"pluginId": "my-plugin",
"config": { "setting1": "value1" }
}
}
响应:
{
"tools": [
{
"name": "my-tool",
"description": "Does something useful",
"parameters": [
{"name": "input", "type": "string", "description": "Input text", "required": true}
]
}
],
"hooks": [
{"event": "PreToolUse", "matcher": "Shell"}
],
"enrichers": [
{"name": "context-enricher"}
],
"channel_provider": {
"name": "my-channel",
"config_schema": [...]
}
}
插件被停用时发送。插件应清理资源。
请求:
{
"method": "deactivate"
}
响应:
{
"result": "ok"
}
LLM 调用插件注册的工具时发送。
请求:
{
"method": "execute_tool",
"params": {
"toolName": "my-tool",
"input": "user input"
}
}
响应:
{
"result": "Tool output text"
}
或错误:
{
"error": "Tool execution failed: ..."
}
生命周期 hook 触发时发送。
请求:
{
"method": "hook",
"params": {
"event": "PreToolUse",
"toolName": "Shell",
"toolInput": "ls -la",
"sessionId": "session-123",
"channel": "cli",
"chatId": "chat-456"
}
}
响应:
{
"hook_result": {
"decision": "allow",
"message": ""
}
}
Hook 决策:allow、deny、defer、ask。
上下文增强器被调用时发送。
请求:
{
"method": "enrich",
"params": {
"enricherName": "context-enricher"
}
}
响应:
{
"result": "Additional context to inject"
}
插件配置发生变化时发送(热重载)。
请求:
{
"method": "config_changed",
"params": {
"config": { "setting1": "new-value" }
}
}
响应:
{
"result": "ok"
}
频道插件使用额外的入站消息:
为特定频道声明工具:
{
"method": "channel_tools",
"params": {
"tools": [...]
}
}
声明频道特定的系统提示词片段:
{
"method": "channel_prompt",
"params": {
"system_parts": {
"05_channel_xxx": "Channel-specific context"
}
}
}
声明 Web UI 组件:
{
"method": "web_ui",
"params": {
"widgets": [...]
}
}
将频道的用户消息推送到 xbot:
{
"method": "channel_inbound",
"params": {
"message": "user input from channel"
}
}
- 进程崩溃:xbot 检测到 stdout 关闭并将插件标记为错误状态
- 超时:插件调用有 30 秒超时(可通过清单
timeout配置) - 畸形 JSON:解析失败的行会被记录日志并跳过
- 自动重试:若启用,xbot 会以指数退避重试激活
#!/usr/bin/env python3
import json
import sys
def handle_request(req):
method = req.get("method")
params = req.get("params", {})
if method == "activate":
return {
"tools": [{
"name": "greet",
"description": "Greet someone",
"parameters": [
{"name": "name", "type": "string", "description": "Name to greet", "required": True}
]
}],
"hooks": [
{"event": "PostToolUse", "matcher": "Shell"}
]
}
elif method == "execute_tool":
tool = params.get("toolName")
if tool == "greet":
return {"result": f"Hello, {params.get('input', 'World')}!"}
return {"error": f"unknown tool: {tool}"}
elif method == "hook":
event = params.get("event")
if event == "PostToolUse":
# 记录工具使用
return {"hook_result": {"decision": "allow"}}
return {"hook_result": {"decision": "allow"}}
elif method == "deactivate":
return {"result": "ok"}
return {"error": f"unknown method: {method}"}
for line in sys.stdin:
try:
req = json.loads(line)
resp = handle_request(req)
sys.stdout.write(json.dumps(resp) + "\n")
sys.stdout.flush()
except Exception as e:
sys.stdout.write(json.dumps({"error": str(e)}) + "\n")
sys.stdout.flush()
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
rl.on('line', (line) => {
const req = JSON.parse(line);
const method = req.method;
const params = req.params || {};
let resp;
if (method === 'activate') {
resp = {
tools: [{
name: 'timestamp',
description: 'Get current timestamp',
parameters: []
}]
};
} else if (method === 'execute_tool') {
if (params.toolName === 'timestamp') {
resp = { result: new Date().toISOString() };
} else {
resp = { error: `unknown tool: ${params.toolName}` };
}
} else if (method === 'deactivate') {
resp = { result: 'ok' };
} else {
resp = { error: `unknown method: ${method}` };
}
process.stdout.write(JSON.stringify(resp) + '\n');
});
type PluginRequest struct {
Method string `json:"method"`
Params map[string]any `json:"params,omitempty"`
}
type PluginResponse struct {
Result string `json:"result,omitempty"`
Error string `json:"error,omitempty"`
Tools []ToolDef `json:"tools,omitempty"`
Hooks []hookReg `json:"hooks,omitempty"`
HookResult *HookResult `json:"hook_result,omitempty"`
Enrichers []enricherReg `json:"enrichers,omitempty"`
ChannelProvider *ChannelProviderDecl `json:"channel_provider,omitempty"`
}
type PluginInbound struct {
Method string `json:"method"`
Params map[string]any `json:"params,omitempty"`
}
- 频道插件 — 完整频道适配器开发
- 脚本运行时 — 更简单的脚本插件
- 架构概览 — stdio 运行时在系统中的位置
- 开发指南:Stdio 插件 — 分步指南