Paperclip 插件规范怎么读:哪些能力现在能用,哪些还只是设计稿

2026-08-17

想给 Paperclip 写个插件,翻开仓库会看到两份文档:doc/plugins/PLUGIN_SPEC.mddoc/plugins/PLUGIN_AUTHORING_GUIDE.md。照着第一份写,你会得到一个跑不起来的插件;照着第二份写,你会觉得功能少得可怜,回头又忍不住去翻第一份。

问题出在两份文档身份不一样,而文档自己写得很清楚,只是容易被略过。规范开头第一行就标着 Status: proposed complete spec for the post-V1 plugin system,并且明确说「这不是 doc/SPEC-implementation.md 里 V1 实现契约的一部分」。开发指南则在开头写明:它「有意比 PLUGIN_SPEC.md 窄」,规范里包含未来构想,而指南只覆盖当下存在的 alpha 面。

所以正确的读法是:开发指南定义你今天能写什么,规范定义这套东西将来长成什么样

规范自己列出的「当前实现注意事项」

规范并没有假装自己已经实现了。它在 Scope 之前专门有一节 Current implementation caveats,讲清了仓库里那套早期插件运行时和管理界面的实际边界。这一节比后面 30 个章节都更值得先看:

规范写明的当前限制对写插件的实际影响
插件 UI 包目前作为同源 JavaScript 跑在主应用里把插件 UI 当作可信代码,不要把它当成前端能力边界
manifest 里的 capabilities 只拦 worker 侧的宿主 RPC它不阻止插件 UI 代码直接调普通的 Paperclip HTTP API
运行期安装假设本地文件系统可写插件包目录和插件数据目录都要落盘
运行期 npm 安装假设环境里有 npm 且能连到配置的 registry离线或锁死出网的部署要提前想清楚
动态安装尚未适配云端没有共享制品库、安装协调、跨节点分发层
仓库 packages/plugins/examples/ 下的示例插件是开发便利它们从源码检出可用,不能假定通用发布版里存在
当前运行时不支持插件资源(asset)上传/读取插件 asset API 属于未来构想,不是当前承诺

开发指南里还有一句更直接的补刀:ctx.assets 在当前运行时不被支持。而规范第 14 节的「必需 SDK 客户端」列表里明明写着 ctx.assets。规范列的是目标清单,指南列的是可用清单,决定你代码能不能跑的是后者。

规范给的结论也很坦白:当前实现适合本地开发和自托管的持久化部署,还不适合多实例的云端插件分发。

先分清平台模块和插件,别选错扩展类

Paperclip 有两类扩展,选错方向会白写一大圈。

**平台模块(Platform Module)**是可信的、进程内的、宿主集成的低层扩展,走显式注册表而不是插件 worker 协议。它的注册面是四个函数:registerAgentAdapter()registerStorageProvider()registerSecretProvider()registerRunLogStore()。新的 Agent 适配器包、新的存储后端、新的密钥后端属于这一类。如果你的目标是接一个新的执行端,那要走的是适配器那条路,不是插件那条路(参见自己写一个 Paperclip 适配器)。

**插件(Plugin)**是按实例全局安装的、通过插件运行时加载的、加法式的、受能力门控的扩展,通过稳定 SDK 和宿主协议与核心隔离。分四个 category:connectorworkspaceautomationui,一个插件可以声明多个。

安装模型是全局的、由运维方驱动的:没有按公司维度的安装表,也没有按公司的启停开关。如果插件需要针对某个业务对象做映射,那属于插件配置或插件状态——规范举的例子是一个全局 Linear 插件安装,配置里存「公司 A 映射到 team X、公司 B 映射到 team Y」。

manifest:唯一的静态声明面

包契约要求每个插件包导出 manifest、worker 入口,UI 包可选。建议布局是 dist/manifest.jsdist/worker.jsdist/ui/,并在 package.json 里用 paperclipPlugin 键指路。

manifest 的规范形状(PaperclipPluginManifestV1)里,几个硬规则值得记住:

  • id 必须全局唯一,通常等于 npm 包名;
  • apiVersion 必须匹配宿主支持的插件 API 版本,装的时候校验,不兼容直接拒;
  • capabilities 必须是静态的、安装时可见的;
  • minimumHostVersion 是现在推荐的字段,minimumPaperclipVersion 标了 @deprecated,只为向后兼容保留;
  • ui.slots 声明插件要填哪些扩展位,每个 slot 引用 UI 包里的一个 exportName,宿主据此知道要挂什么,而不必提前加载整个包。

槽位类型的枚举相当长:pagesettingsPagedashboardWidgetsidebarrouteSidebarsidebarPaneldetailTabtaskDetailViewprojectSidebarItemglobalToolbarButtontoolbarButtoncontextMenuItemcommentAnnotationcommentContextMenuItemcompanySettingsPage。开发指南单列了一份「宿主里当前已经接线的挂载面」,和上面这份基本重合——这是少见的、规范和实现对得比较齐的一块。

UI slot ID 由宿主按插件 ID 自动加命名空间(形如 @paperclip/plugin-linear:sync-health-widget),跨插件冲突在结构上不可能发生;但单个插件自己 manifest 里出现重复 slot ID,宿主必须在安装时拒绝。

能力清单是白名单,而且有一份明确的禁区

能力是强制且静态的,宿主在 SDK 层执行,拒绝授予范围之外的调用。分类涵盖数据读、数据写、插件状态、运行时/集成、Agent 工具、UI 六块,条目相当细,光读的一侧就区分了 issues.readissue.comments.readissue.documents.readissue.relations.readissue.subtree.read

真正要记住的是禁区。规范第 15.2 节写明宿主不得为以下事项暴露能力:

  • 审批决策
  • 预算超额覆盖
  • 认证绕过
  • issue checkout 锁覆盖
  • 直接访问数据库

这跟核心假设是一条线:董事会治理、审批门、预算硬停止、核心任务不变量永远归 Paperclip 核心所有。Non-Goals 里也重申了一遍——任意插件不得覆盖核心路由或核心不变量,不得改动审批、认证、issue checkout、预算强制逻辑。

升级时若新版本增加了能力,宿主必须把插件标为 upgrade_pending,运维方显式批准新能力集合之前,新版本不会进入 ready

有一个细节体现了这套设计的谨慎程度:ctx.issues.createComment() 默认以插件自己的 agent 身份发评论;如果要以某个人类成员的身份发,除了 issue.comments.create 还要额外的 issue.comments.create_human_attributed,并且宿主会独立校验这个 actorUserId 确实是该公司在职的人类成员。插件伪造不了归属。

worker 是独立进程,协议就那几个方法

第三方插件默认跑在进程外。Paperclip 服务端为每个已安装插件起一个 worker 进程,是 Node 进程,宿主与 worker 之间走 stdio 上的 JSON-RPC。换来的是故障隔离、更清晰的日志边界和更容易做的资源限制。

必需的 RPC 方法只有三个:initialize(input)health()shutdown()。可选的是 validateConfigconfigChangedonEventrunJobhandleWebhookgetDataperformActionexecuteTool

事件投递语义要留意:至少一次,插件必须幂等;跨事件类型没有全局顺序保证;重试之后连按实体的顺序也只是尽力而为。任何依赖「事件只来一次」或「事件严格有序」的实现都会出问题。

关闭流程有时限阶梯:宿主先发 shutdown(),worker 有 10 秒收尾并干净退出;超时发 SIGTERM;SIGTERM 后 5 秒仍未退出发 SIGKILL。被强杀时进行中的 job 运行会标为 cancelled 并附注说明,进行中的 getData / performAction 调用向桥返回错误。需要更长排空时间的插件,可以在插件配置里调这个截止时间。

worker 挂了的处理是隔离的:把插件状态标 error、在健康页面暴露错误、实例其余部分继续跑、按有界退避重试启动,不牵连其他插件和核心服务。

「托管资源」是当前最该照做的一条建议

开发指南和规范都反复强调同一件事:别把长期存在的工作藏在插件私有状态里。插件提供的如果是持久的 Paperclip 业务对象,就在 manifest 里声明出来,让宿主按公司创建或重新关联真实记录。

四类托管资源,各自对应一个能力:

声明字段需要的能力用在什么时候
agents[]agents.managed插件提供一个具名的、可被调用的工作者,董事会要能看到它、给它预算、暂停它、检查它
projects[]projects.managed插件需要一个稳定的、公司范围的项目来装它的 issue、routine 或工作区导向的 UI
routines[]routines.managed定时、webhook 或手工触发、且应该产生可见 Paperclip issue 的工作
skills[]skills.managed可复用的、对运维方可见的能力,同步进托管 agent

指南明确给了取舍:recurring 的业务工作优先用托管 routine,而不是插件的 jobs[];插件 job 留给不需要看板可见任务痕迹的运行时维护活。规范第 17 节也是同一口径。

托管资源按稳定的插件 key 解析,不是硬编码的数据库 id。在 worker 的 action 或 data handler 里,对当前 companyIdctx.agents.managed.reconcile()ctx.projects.managed.reconcile() 之类的方法:reconcile() 会创建缺失的资源、重新关联可恢复的绑定,或返回已有资源;reset() 则把 manifest 默认值重新应用一遍。资源之间的依赖用 ref 声明,routine 可以通过 assigneeRef 指向托管 agent、通过 projectRef 指向托管项目;引用的 agent 和 project 要先 reconcile,ref 还缺的话 routine 解析会报 missing_refs 而不是猜。

有一条授权规则是硬的:key 一旦发布就别改。改 agentKeyprojectKeyroutineKeyskillKey,在宿主眼里就是新建了一个托管资源。

仓库里的 LLM Wiki 插件是这套模式的参考实现:manifest 里声明托管 agent、项目、routine 和 skill,按公司 reconcile,用托管 routine 跑周期性的 wiki 维护和摄取。内容型插件被建议照抄这个结构,而不是自己跑不受管的后台循环。把可复用提示与工具指引挂成技能这条线,见给 Paperclip 写一个 skill;把周期工作交给 routine,见流水线与例行任务

数据库、密钥、API 路由三条硬边界

数据库:插件不直接连库。可信的编排类插件可以在 manifest 里声明 database: { migrationsDir, coreReadTables },需要 database.namespace.migratedatabase.namespace.read,运行期改数据还要 database.namespace.write。宿主派生出 ctx.db.namespace,在 worker 启动前按文件名顺序跑 SQL 文件,把校验和记进 plugin_migrations,已应用过又被改动的迁移会被拒绝。迁移 SQL 只能在自己的命名空间里建表改表,可以为外键或只读视图引用白名单内的 public 核心表,但不得修改/删除/清空 public 表,不得建扩展、触发器、非可信语言,也不得跑运行期多语句 SQL。运行期 ctx.db.query() 只允许 SELECTctx.db.execute() 只允许命名空间内的 INSERTUPDATEDELETE

密钥:插件配置永远不落原始密钥值,只存 { type: "secret_ref", secretId, version? } 这种共享引用形状,旧的 UUID 字符串引用会被拒绝。保存时会校验引用的密钥属于所选公司。worker 只在执行时拿到解析后的密钥,且解析会写 secret_access_eventsconsumerType 标为 plugin_worker。密钥值绝不允许写进插件配置 JSON、活动日志、webhook 投递行、错误消息。这套引用机制的上游细节见 Paperclip 密钥管理

API 路由:插件自有的 JSON 路由必须在 manifest 的 apiRoutes 里声明,只挂在 /api/plugins/:pluginId/api/* 下面,抢不到核心路径。声明项包括 routeKeymethod、插件本地 pathauthcapability、可选的 checkout 策略和公司解析方式。分派给 worker 的 onApiRequest 之前,宿主会解析插件、确认它 ready、强制 api.routes.register、匹配方法与路径、解析公司访问权、应用 checkout 策略。只有安全的请求头会转发,认证头和 cookie 永不传给 worker。

上手实际要敲的命令

开发指南给的脚手架命令是:

paperclipai plugin init @yourscope/plugin-name --output /absolute/path/to/plugin-repos

生成的目录里有 src/manifest.tssrc/worker.tssrc/ui/index.tsxtests/plugin.spec.ts,以及 esbuild.config.mjsrollup.config.mjs。在 monorepo 内部,脚手架用 workspace:*@paperclipai/plugin-sdk;在外部,它会把本地检出里的 SDK 快照成一个 .paperclip-sdk/ tarball,让你不必先发 npm 就能构建和测试,多个检出的情况用 --sdk-path 指定。

交付前的最低验证:

pnpm --filter <your-plugin-package> typecheck
pnpm --filter <your-plugin-package> test
pnpm --filter <your-plugin-package> build

改了宿主集成的话,还要跑 pnpm -r typecheckpnpm test:runpnpm build

发布口径也明确:npm 包是部署制品,仓库内示例安装只当开发流程;插件 UI 尽量自包含在包里;不要依赖宿主的设计系统组件或没写进文档的应用内部结构。GitHub 仓库直装今天不是一等公民工作流——本地开发用检出的本地路径,生产环境发到 npm 或兼容 npm 的私有 registry。

什么时候不适用,以及还没解决的部分

这套插件系统现在不适合几类场景:

多实例云端部署。规范说得很直白:动态插件安装还没有 cloud-ready,没有共享制品存储、安装协调和跨节点分发层。一次成功安装只把包写到本地宿主,其他节点不会自动拿到这个插件。

把插件 UI 当安全边界。UI 包以标准 ES 模块加载,不是 iframe。规范给了隔离规则(不得从宿主内部导入、不得直接用 window.fetchXMLHttpRequest 调宿主 API、要能被静态分析、不得动态 import() 包外 URL),并说宿主可以用 CSP 限制网络访问;但当前口径仍是「把插件 UI 当作可信代码」。iframe 隔离在 Phase 2,规范说到那时插件源码不用改,桥 API 保持不变。

第三方任意 schema 迁移。第一版插件系统不允许,规范把「以后若真有必要,可能加一条仅限可信模块的迁移路径」留成了未来选项。

外部对象引用提供方。指南说这块在 MVP 阶段仅限可信安装,能力只门控检测/解析和宿主 API 调用,不是给不可信市场代码用的沙箱边界。还有条渲染红线:内联 markdown 渲染归 Paperclip 自己所有,插件不得为内联引用返回 React、HTML 或 dangerouslySetInnerHTML 内容。

还有一些是规范里写成规范性要求、但你不该假定已经实现的:热生命周期(安装、卸载、升级、改配置都不重启服务端)、@paperclipai/plugin-test-harness 测试工具包、create-paperclip-plugin starter、插件日志表与健康看板、卸载后默认 30 天的数据保留宽限期、SDK 多版本共存与至少 6 个月的弃用期。这些在规范第 25 到 29 节写得很细,但它们属于「post-V1 目标架构」这一栏。想确认某条到底能不能用,办法只有一个:回到开发指南那份 alpha 支持面清单去对照。

最后提醒一句 manifest 与运行时的错位:能力门控的是 worker 侧的宿主 RPC 调用,拦不住 UI 代码直接打普通 HTTP API。做安全评估时,别把 manifest 的 capabilities 当成插件的实际权限上限——它是 worker 的上限,不是整个插件包的上限。

延伸阅读


本文依据 Paperclip 官方仓库(github.com/paperclipai/paperclip,MIT 协议)的 docs/ 用户文档 与 doc/ 下的规范、运维与连接器手册整理,核对日 2026-08-17。 我们没有部署或运行过 Paperclip,因此不涉及界面外观与操作手感; 部分规范文档描述的是目标架构而非当前实现,文中已就地标注,不构成对实际行为的保证。 请以仓库最新内容为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。