面向 OpenClaw / 小龙虾 等外部插件对接方。 本文档聚焦:代码结构 + 使用方法。
当前仓库的插件能力是“平台 API 层 + 业务服务层 + 移动端发 Token 辅助”的组合:
- 后端提供统一的插件 API 前缀:
/api/v1/platform/* - 插件通过 Bearer Token +
X-Bricks-Plugin-Id访问 - 数据复用现有聊天存储(
chat_messages),不引入新的插件专用消息表 - 移动端设置页可生成并复制插件 Token,便于远端服务接入
docs/
plugin_development_architecture.md # 本文档
apps/node_backend/src/
app.ts # 注入 platform 路由入口
middleware/
platformAuth.ts # 插件鉴权、scope 校验、token 签发
routes/
platform.ts # 平台 API 协议层(events/ack/messages/resolve)
config.ts # /api/config/platform-token(签发 token)
platform.test.ts # 平台路由鉴权与约束测试
services/
platformIntegrationService.ts # 平台协议与 chat_messages 的读写映射
apps/node_openclaw_plugin/
src/
index.ts # 插件运行时入口(JWT claims 校验 + loop 启动)
jwtClaims.ts # JWT-only claims 解析/校验
platformClient.ts # 平台 API 客户端
pluginRunner.ts # pull-only 主循环(pull/dedup/ack/writeback)
stateStore.ts # 本地状态持久化(cursor/processed/pendingAck)
README.md # 本地运行与 JWT-only 约束说明
apps/mobile_chat_app/lib/features/settings/
llm_config_service.dart # 拉取 platform token
model_settings_screen.dart # UI:生成/展示/复制 token
apps/mobile_chat_app/test/
model_settings_screen_test.dart # Token 交互与复制行为测试
职责:把平台接口挂载到统一 API 树。
- 新增:
app.use('/api/v1/platform', platformRoutes); - 效果:插件流量进入同一 Express 生命周期(限流、错误处理、迁移保护等)
职责:完成插件身份、插件标识和 scope 的边界校验。
支持两种访问模式:
-
静态 Key 模式
Authorization: Bearer <BRICKS_PLATFORM_API_KEY>- 适合内网/固定服务对接
⚠️ 安全限制:静态 Key 模式不携带用户身份(platformUserId为空),导致:/events和/conversations/resolve查询不做用户过滤/messages写入依赖 body 中的userId字段(可任意传入)
- 仅适合单租户/开发/内部部署场景,不得用于多用户生产环境
-
JWT 模式(推荐)
- 通过
issuePlatformAccessToken()签发typ=platform_plugin的 token - token 可携带:
userId、pluginId、scopes - 请求时必须带:
X-Bricks-Plugin-Id
- 通过
Scope 控制:
events:readevents:ackmessages:writeconversations:read
对
apps/node_openclaw_plugin参考实现:当前已收敛为 JWT-only 启动策略,不保留静态 key 运行路径。
职责:做协议兼容、参数校验、错误码约束,再调用 service。
核心接口:
GET /api/v1/platform/events- 拉取增量事件(cursor + limit)
POST /api/v1/platform/events/ack- ACK 已消费事件
- body 禁止传
pluginId
POST /api/v1/platform/messages- 创建消息(兼容
text/content与role/author字段)
- 创建消息(兼容
PATCH /api/v1/platform/messages/:messageId- 更新已有消息(文本或 metadata)
GET /api/v1/platform/conversations/resolve- 根据
conversationId或rawId解析会话归属
- 根据
职责:把“插件协议对象”映射到“聊天存储模型”。
- 事件读取:按
write_seq从chat_messages增量读取 - ACK:MVP 阶段只做参数合法性校验(幂等),不做持久化
- 创建/更新消息:通过
upsertMessages()写入chat_messages - 会话解析:支持
session_id和channel/thread双向定位
fetchPlatformToken()请求:GET /api/config/platform-token- 返回解析为
PlatformTokenBundle:tokenpluginIdbaseUrlscopesexpiresIn
- 提供按钮:
Get Xiaolongxia Token - 展示 token 基础信息(Plugin ID / Base URL / Scopes)
- 支持一键复制 token 到剪贴板
- 错误场景给出 Snackbar 提示
必须设置(至少其中一项):
| 变量 | 是否必需 | 说明 |
|---|---|---|
JWT_SECRET |
JWT 模式必需 | 用于签发和验证 platform JWT token |
BRICKS_PLATFORM_API_KEY |
静态 Key 模式必需 | 静态共享密钥;JWT 模式下可不设置 |
可选变量:
| 变量 | 默认/回退 | 说明 |
|---|---|---|
BRICKS_PLATFORM_API_SCOPES |
无默认(服务内写死) | 逗号分隔的默认 scope 列表 |
BRICKS_PLATFORM_DEFAULT_PLUGIN_ID |
无默认 | 不传 pluginId 参数时使用的默认值 |
BRICKS_PLATFORM_BASE_URL |
回退到 API_BASE_URL,再回退为空字符串 |
返回给客户端的推荐访问地址;如果两个变量都未设置,baseUrl 字段将返回空字符串,移动端同样会本地回退处理 |
- 打开 Model Settings
- 点击
Get Xiaolongxia Token - 复制生成的 token
GET /api/config/platform-token?pluginId=plugin_local_main
请求头:
Authorization: Bearer <user_jwt>返回示例:
{
"token": "<platform_jwt>",
"pluginId": "plugin_local_main",
"scopes": ["events:read", "events:ack", "messages:write", "conversations:read"],
"baseUrl": "https://your-api-base",
"expiresIn": "30d"
}所有请求必须携带:
Authorization: Bearer <platform_jwt_or_static_key>
X-Bricks-Plugin-Id: plugin_local_mainGET /api/v1/platform/events?cursor=cur_0&limit=50POST /api/v1/platform/events/ack
Content-Type: application/json
{
"cursor": "cur_12",
"ackedEventIds": ["evt_msg_xxx_12"]
}POST /api/v1/platform/messages
Content-Type: application/json
{
"conversationId": "conv_001",
"channelId": "ch_001",
"threadId": "main",
"text": "hello",
"role": "assistant"
}GET /api/v1/platform/conversations/resolve?conversationId=conv_001- 优先使用 JWT 模式,并把 token 作用域收敛到最小必要范围。
- 严格校验
X-Bricks-Plugin-Id,避免跨插件复用 token。 - 不要在日志里落完整 token,仅输出前后缀。
- JWT 场景下 body 的
userId不应覆盖 token 的userId(当前后端已做防护)。 - 生产环境定期轮换
JWT_SECRET与静态 Key。
- ACK 持久化(用于精确投递语义)
- 插件配额与速率策略(按 pluginId / userId)
- 细粒度 scope(例如
messages:patch与messages:create分离) - 插件审计日志(安全与合规)
- 插件版本协商(v1/v2 协议演进)
- 401 UNAUTHORIZED:检查 Bearer token 是否为空/过期/签名无效
- 400 MISSING_PLUGIN_ID:缺少
X-Bricks-Plugin-Id请求头 - 403 FORBIDDEN:scope 不足或 token 的 pluginId 与请求头不一致
- INVALID_CURSOR:游标格式不符合
cur_<number>