跳转到内容

Capability API 总览

node 插件的后端经 CapabilityClient 访问宿主能力。类型单源在 @xinghan/plugin-sdk/server(capability-client.ts),本页是导航图;方法签名以类型定义与编辑器提示为准。

import { CapabilityClient } from '@xinghan/plugin-sdk/server'
const cap = new CapabilityClient({
baseUrl: process.env.CAPABILITY_BASE_URL, // 宿主注入
token: process.env.CAPABILITY_TOKEN,
})

所有方法 (args, ctx) 两段式,ctx = { actor, projectId }。失败抛 CapabilityError(结构化 code + message)。

命名空间需声明管什么
cap.dataEnginedataEngine表格数据与结构(下详)
cap.llmllm⚠️ 暂未接线(SDK 有客户端、宿主尚未挂载路由,调用 404)。插件用 AI 的现实路径是 Agent 微对话通道
cap.storagestorage插件私有 KV(get/set;settings: 前缀与宿主配置表单互通)
cap.eventsevents订阅宿主事件
cap.file—资产库:uploadAssetRaw / downloadAssetRaw(图片/附件字节)
cap.fileTree—文件树:list / create / move / rename、文档内容 getContent / saveContent、版本 saveDocVersion / listDocVersions / restoreDocVersion
cap.log—append-only 日志流:append(记结构化事件)/ range(按序读回)/ count / delete —— 插件自己的操作留痕/回放用
cap.search—项目内搜索(与页内搜索同引擎)
cap.automation—自动化节点执行侧(配合 defineAutomationNode 注册执行路由)
cap.dataPr—数据审批单:create / get(把写入变成待审提议而非直写)
cap.share—分享令牌作用域下的受限访问(分享页场景)
cap.render仅内置插件离屏渲染截图。第三方插件调用被拒(能截任意页面的能力不外开);无 Electron 环境返回 not_supported
cap.sandbox—沙箱执行:跑不可信/生成代码的受限环境(Agent 的 run_sandbox 底座,网络与文件面受护栏)
cap.contributions—运行时贡献点注册/投稿 —— 与 manifest 声明式形态等价(见与 Agent 集成),node 插件想动态增删投稿时用运行时 API,纯数据投稿优先写 manifest
cap.pluginDev仅内置插件开发辅助(dev 插件目录写入等)

capabilities 数组是申报面:市场审核与用户安装时看到的授权清单,请如实只报真用到的。运行时强制目前覆盖 satellite:<kind>(未声明访问卫星一律 403);其余命名空间的运行时强制在路线上 —— 别把”没声明”当运行时会拦的保证。

读:getFields · getViews · queryRecords(过滤/排序/分页在 opts: QueryOptions 里)· getRecord · getRecords · aggregate

写:updateCell · updateCells(批量=一次撤销)· addRecord · addRecords · deleteRecord

结构:addField · addFields · addTable · createView

底层:applyChangeset(直接提交 changeset —— 上面的写方法都是它的糖;一般用不到)

await cap.dataEngine.queryRecords(
{
tableNodeId,
opts: {
filters: [{ fieldId, op: 'contains', value: '北京' }],
sorts: [{ fieldId, desc: true }],
limit: 100, offset: 0,
},
},
{ actor, projectId },
)

用法与治理语义见读写表格数据。

插件私有 KV(按 actor 隔离);settings: 前缀的 key 与宿主偏好页的自动表单互通:

await cap.storage.set('note', '内容', { actor })
const note = await cap.storage.get<string>('note', { actor }) // 没有时 null
const apiUrl = await cap.storage.get<string>('settings:apiUrl', { actor }) // 用户在偏好页填的
await cap.storage.delete('note', { actor })

SDK 定义了 cap.llm.chat 客户端,但宿主尚未挂载对应路由 —— 当前调用会 404。插件借用 AI 的现实路径是 Agent 微对话通道(UI 侧 createMicroClient/mountMicroPanel,复用用户在 Agent 里配好的模型,见与 Agent 集成)。本节保留占位,接线后更新。

文件树与文档内容。做”批量整理/文档生成器”类插件的主面:

// 树操作
const nodes = await cap.fileTree.list({ projectId }, ctx) // 全树
await cap.fileTree.create({ projectId, name, fileType /* … */ }, ctx)
// ⚠️ 目标父节点参数叫 newParentId;写错名(如 parentId)不报错 ——
// 宿主把缺失当 ''(项目根),节点被**静默搬到根目录**
await cap.fileTree.move({ projectId, nodeId, newParentId }, ctx)
await cap.fileTree.rename({ projectId, nodeId, name }, ctx)
// 文档内容(text 存储形态的节点:markdown/画板/导图…)
// ⚠️ 参数名注意:读按 filePath,写与版本按 fileId —— 传错宿主直接拒
const { content } = await cap.fileTree.getContent({ projectId, filePath }, ctx)
await cap.fileTree.saveContent({ projectId, fileId, content }, ctx)
// 文档版本:保存命名快照、列出、回滚 —— 给"重要节点存个版本"类功能用
await cap.fileTree.saveDocVersion({ projectId, fileId /* label */ }, ctx)
const versions = await cap.fileTree.listDocVersions({ projectId, fileId }, ctx)
await cap.fileTree.restoreDocVersion({ projectId, fileId, versionId }, ctx)

UI 侧(iframe 里)操作当前文件用 ui 桥的 docLoad/docSave 更顺手(见文件类型指南);后端的 fileTree 面向跨文件批量场景。

把写入变成待审提议而不是直写 —— 大改动(“agent 建议改 500 行”)的正路:

// ① 开一张审批单(projectId 在 ctx 里,args 只有 title/description)
const { prId } = await cap.dataPr.create(
{ title: '批量清洗手机号格式' }, ctx,
)
// ② 写入统统带 prId → 变成提议,不落 live 数据
await cap.dataEngine.updateCells({ tableNodeId, prId, cells }, ctx)
await cap.dataEngine.addFields({ tableNodeId, prId, fields }, ctx) // 新列返回 tfld_ 临时 id
// ③ 用户在审批中心(GitHub PR 式页面)逐项看 diff → 合并或拒绝
// 读方法带 prId 可做"叠加提议值"的预览(queryRecords/getRecord 的读分叉)

合并/拒绝是用户的动作,插件到 ② 为止 —— 别试图替用户合并。

项目可以挂卫星库(id 形如 <父项目>#<kind>)—— Agent 的会话记录库就是 #agent 卫星。插件要读写某类卫星,manifest 必须声明 satellite:<kind>(如 satellite:agent),否则一律 403。这是审计数据的最小权限闸:普通插件默认碰不到 Agent 的对话史。

订阅项目里资源(表等)的变更 —— 做”数据变了就响应”的插件(同步器、通知器、事件驱动 agent)用它,别轮询:

const stop = cap.events.subscribe(
{ projectId, resourceIds: [tableNodeId], actor },
(env) => {
// env: { event, data } —— changeset 变更/热层帧等信封
if (isSelfActor(env, actor)) return // ⚠️ 防环:跳过自己写入触发的事件
handleChange(env)
},
{ onClosed: (reason) => console.warn('事件流断开:', reason) },
)
// 不用了记得 stop()

isSelfActor(SDK 同处导出)是必修课:事件驱动插件最容易犯的错是”自己写 → 收到自己的事件 → 再写”死循环。SSE 的断线重连、心跳判死 SDK 都处理好了,只管消费。

iframe 里的前端从 @xinghan/plugin-sdk/ui 拿宿主上下文与交互桥:

import { installContextBridge, getContext, onContextChange } from '@xinghan/plugin-sdk/ui'

HostContext 字段:

字段何时有值含义
projectId恒有(面板必在项目内打开)当前项目
activeNodeId / activeFileType / activeNodeName编辑器有激活节点时用户正在看的表/文件
tableNodeIdtableViews 挂载时本视图渲染的表
viewIdtableViews 挂载时视图实体 id(styleInfo 按它存取)
nodeIdfileTypes/fileRenderers 挂载时本 iframe 绑定的文件节点

其它 UI 桥模块:micro-panel(向 Agent 皮肤投稿微面板)、modal-chrome(字段编辑器/配置面板等模态的 resolve/reject 协议)。字段编辑器的 ui.resolveModal({ value }) 契约见 fieldTypes 参考。

UI ↔ 你自己的后端不经任何桥 —— 就是 HTTP。UI 里用相对路径(fetch('../api/xxx')),宿主把 /plugins/<id>/* 反代到你的进程;用户身份在请求头 x-actor。