插件 Manifest 与打包

为 schema 版本 2、3 和 4 定义插件描述符,并分发构建产物 JAR。

每个 Turboism 插件 JAR 都恰好包含一个位于以下位置的描述符:

META-INF/turboism/plugin.json

描述符声明 "format": "turboism.plugin.meta" 和 schemaVersion。Runtime 接受 schema 版本 2、3 和 4,并严格按对应契约进行校验,不会自动升级描述符。

版本契约
2基础生命周期、入口点、API 范围、依赖、权限、资源和本地化。
3新增必需的 category 字段和可选的 tags(第一方插件使用 v3)。
4新增声明的跨插件事件契约:eventExports 和 eventImports。

不会加载 schema 版本 1。未知字段会被拒绝,而不是被忽略。

Manifest 示例

{
  "format": "turboism.plugin.meta",
  "schemaVersion": 3,
  "id": "dev.example.plugin.hello",
  "name": "Hello Plugin",
  "version": "0.1.0",
  "description": "Minimal Turboism plugin lifecycle example.",
  "category": "workflow",
  "tags": ["example", "lifecycle"],
  "entrypoints": [
    "dev.example.plugin.HelloPlugin"
  ],
  "turboismApi": "[0.1.0,0.2.0)",
  "authors": [
    { "name": "Example Author" }
  ],
  "license": "MIT",
  "website": "https://example.org/hello-plugin",
  "resources": [],
  "i18n": {
    "baseName": "META-INF/turboism/i18n/messages",
    "locales": []
  },
  "dependencies": [],
  "permissions": [],
  "capabilities": [],
  "environment": {
    "requiresCubism": false,
    "ui": "none"
  }
}

必需字段为 id、name、version、entrypoints、turboismApi、authors、website、resources 和 i18n;schema 3 还额外要求 category。

入口点规则

每个入口点必须:

  • 存在于同一个插件 JAR 中;
  • 为 public;
  • 实现 TurboismPlugin 或其子接口;
  • 提供一个 public 无参数构造函数。

多个入口点共享一个插件身份、描述符、ClassLoader、PluginContext、DisposableScope、权限集、配置命名空间、存储命名空间、资源和本地化声明。

construct/init/enable: manifest order
disable/shutdown:       reverse manifest order

构造、初始化或启用期间发生的失败会回滚整个 JAR。

category 与 tags

category 必须是小写的 kebab-case 标记,长度为 2–32 个字符。已审查的第一方 category 为 modeling、workflow、appearance、analysis、performance、integration、system 和 development。格式正确但未注册的 category 仍会加载,以 other 呈现,并产生结构化的 PLUGIN_CATEGORY_UNKNOWN 诊断。

tags 是可选的,最多包含 12 个长度为 2–32 个字符的小写 kebab-case 标记,且不携带任何权限或能力。

依赖

{
  "id": "dev.example.plugin.base",
  "version": "[1.0.0,2.0.0)",
  "type": "required",
  "ordering": "after",
  "reason": "Uses the base plugin contract."
}
  • type 为 required 或 optional。
  • ordering 为 none、before 或 after。
  • 依赖 ID 使用反向域名语法。
  • 版本范围使用 Turboism 版本范围语法。

依赖记录控制解析与排序。它不会将另一个插件的私有实现包变为公共 Java 依赖。

权限

每条权限记录都包含一个已知 ID、application 或 user scope,以及非空的原因。

{
  "id": "turboism.action.register",
  "scope": "application",
  "reason": "Registers the Hello action."
}

仅声明插件调用的 SDK 服务所需的权限。权限不保证 Provider 可用,也不会绕过操作验证。

资源与本地化

资源根目录是以 / 结尾的规范化相对前缀:

"resources": ["icons/"],
"i18n": {
  "baseName": "META-INF/turboism/i18n/messages",
  "locales": ["base", "en", "zh-Hans"]
}

随后 JAR 必须包含:

icons/hello.png
META-INF/turboism/i18n/messages.properties
META-INF/turboism/i18n/messages_en.properties
META-INF/turboism/i18n/messages_zh_Hans.properties

声明的根目录和目录必须存在。普通的非 class 资源必须属于已声明的根目录。路径遍历、绝对路径、反斜杠、空路径段和未声明资源都会被拒绝。

支持的运行时 locale 为 en、ja、ko、zh-Hans 和 zh-Hant,外加 base 捆绑包。Manifest 中的 locale ID 使用带连字符的形式(zh-Hans);资源包文件名使用 Java 下划线形式(messages_zh_Hans.properties)。

跨插件事件契约(schema 4)

Schema 4 允许插件声明其拥有和消费的公共事件类型,使 Runtime 无需 ClassLoader 委托即可解析这些契约:

"eventExports": [
  {
    "id": "dev.example.event.hello",
    "contractVersion": "1.0.0",
    "eventType": "dev.example.api.HelloEvent",
    "abiSha256": "<sha256 of the event type bytes>"
  }
],
"eventImports": [
  {
    "provider": "dev.example.plugin.base",
    "eventId": "dev.example.event.base-status",
    "contractVersion": "1.0.0",
    "eventType": "dev.example.api.BaseStatusEvent",
    "abiSha256": "<sha256 of the event type bytes>"
  }
]

eventImports 条目还可以携带 "required": true,以要求其提供程序契约被接纳。Runtime 会校验每个条目声明的 ABI 摘要,拒绝同一插件内重复的导出 ID 或事件类型,并拒绝无法解析到已接纳提供程序契约的导入。

插件 ID 使用至少包含一个点的反向域名语法(dev.example.plugin.hello);事件 ID 使用小写的点/短横线标记形式。

构建与验证

对于仓库内的插件模块:

./gradlew :plugins:hello:test :plugins:hello:jar validatePluginMeta \
  --no-daemon --console=plain

该仓库使用 Java 17,并在以下位置写入模块构建产物:

build/worktree/<worktreeId>/<module>/libs/

libs/ 中的 JAR 就是发布产物。不存在包装归档,也没有单独的打包步骤。

严格的 JAR 验证

JAR 本身即是安装产物。Runtime 将所选 JAR 视为不受信任的本地输入,并验证:

  • 常规文件、源链中没有符号链接,并受原始大小、单条目大小、总展开量、条目数量和压缩比上限约束;
  • 恰好一个 META-INF/turboism/plugin.json;
  • 描述符 ID、版本和 API 范围一致;
  • 没有复制的 SDK、Runtime、test、test-framework 或 Live2D 类;
  • 没有原生二进制或 installer payload;
  • 没有嵌套 JAR;
  • 声明的入口点、资源根目录和 i18n catalog 都存在;
  • 没有未声明的普通资源。

托管安装会暂存验证过的 JAR,并在 Cubism 重启后应用。不要通过重命名任意 ZIP 或 JAR 来创建插件。始终分发 Gradle 构建产出的 JAR。