@cindy/plugin-protocol 是 plugin-server 与未来 Desktop Plugin 客户端共享的零运行时依赖 TypeScript contract。它不是服务,也不负责运行时协议协商。
主仓库通过 cindy-protocol submodule 和 pnpm workspace 引用本包:
{
"dependencies": {
"@cindy/plugin-protocol": "workspace:*"
}
}所有公开类型、常量和校验器都从包根入口导入:
import {
PluginProtocolError,
parseGetPluginResponse,
parseListPluginsResponse,
parsePluginMemberUploadStatusResponse,
parsePluginDownloadResponse,
validateGhostManifest,
type GhostManifest,
} from '@cindy/plugin-protocol';本包只包含:
- Ghost 包的
ghost.json类型、格式常量和validateGhostManifest; - Desktop 消费的 Plugin 列表、详情与下载响应 DTO、枚举和解析器。
- organization 成员上传
.cindy包时由 plugin-server、Cindy Host 和发布者插件共享的 DTO、限制常量和解析器。
本包不包含服务端数据模型、organization 管理 API DTO、Plugin 生命周期实现、受众策略实现、鉴权、对象存储实现、安装目录、启停状态、IPC、panel 布局或其他 Desktop 运行时逻辑。管理面尚无跨仓 TypeScript 消费方,相关类型由 plugin-server 本地维护;未来出现真实共享消费者时再抽取。
读取并解析 ghost.json 后,把未知值直接交给校验器。校验器不抛异常,而是返回可判别联合类型:
const rawManifest: unknown = JSON.parse(ghostJsonText);
const result = validateGhostManifest(rawManifest);
if (!result.ok) {
throw new Error(`ghost.json 不合法: ${result.reason}`);
}
const manifest: GhostManifest = result.manifest;成功结果是只包含协议已知字段的规范化对象;kind 等有缺省语义的字段会被补齐。不要在校验前把 unknown 强转为 GhostManifest,也不要在服务端或 Desktop 另写一套 manifest 校验规则。
network.secrets[].oauth.scopes 最多包含 256 条;第 257 条会被
validateGhostManifest 拒绝。每条 scope 仍必须是 1–200 字符、不含空白的
唯一字符串,本变更不改变单条校验或重复项规则。
这是 schema v2 的宽松校验变更,不需要提升 manifest 版本。新版 plugin-server 可以发布 49–256 条 scope 的包;仍使用旧协议校验器的 Desktop 会拒绝安装这些包 并保留现有安装,因此应先升级客户端,再分发超过 48 条 scope 的 Plugin。
插件可通过 cindy.search: ["web"] 请求 Host 提供 Cindy 托管的公网搜索:
{
"slots": ["tool", "cindy"],
"tools": [
{
"name": "search_web",
"description": "Search the public web"
}
],
"cindy": {
"search": ["web"]
}
}search当前只接受动作web,未知、空或重复动作均拒绝。cindy.search.web只能由真实 tool-call 触发,因此 manifest 必须同时声明tool槽和至少一个工具;只声明 Cindy 能力但没有工具入口会被拒绝。- Host 负责路由、凭证与计费,插件不声明或接触网关 Key、模型名和搜索工具定义。
这是 schema v2 的新增严格能力类目。旧版 plugin-server 和 Desktop 会拒绝包含
cindy.search 的包,因此必须先合并本协议、升级两个消费方并部署,再发布使用
该能力的插件。插件 Release 应配合 minCindyVersion,避免旧客户端收到不兼容版本。
插件可通过 ios-simulator slot 提供 Host 托管的内嵌 iOS 模拟器入口:
{
"schemaVersion": 2,
"id": "simulator-workflow",
"name": "Simulator Workflow",
"version": "1.0.0",
"minCindyVersion": "1.2.3",
"entry": "main.js",
"slots": ["ios-simulator"]
}- 插件只能读取当前任务的脱敏状态,并请求 Host 打开模拟器控制面板。
- 视频帧、输入事件、设备标识、Native Helper、WDA、生命周期、恢复与兼容回退 均由 Cindy Host 管理,不会通过本 slot 交给插件。
- 声明
ios-simulator时必须同时声明minCindyVersion。Plugin Server 依照X-Cindy-Version选择 Release;版本未知或低于门槛的客户端不会收到该 Release。 - 本包只登记 manifest 能力,不定义 Cindy Host 内部的 IPC、Sidecar 或媒体协议。
这是 schema v2 的新增严格 slot,不提升 manifest schema 版本。旧版 plugin-server 和 Desktop 会拒绝包含该 slot 的包,因此发布顺序必须是:先合并本协议并升级、部署 Plugin Server 与 Cindy Host,再发布使用该能力的插件。
Plugin 可通过可选的 locales 字段声明宿主支持语言对应的包内 JSON 资源:
{
"locales": {
"en": "locales/en.json",
"zh-CN": "locales/zh-CN.json",
"ja": "locales/ja.json",
"ko": "locales/ko.json"
}
}- 支持语言固定为
zh-CN、en、ja、ko;声明locales时必须包含en,供宿主语言不受支持或目标资源缺失时回退。 - 每条值必须是包内安全相对路径并以
.json结尾;不同语言不能复用大小写 折叠后相同的路径,也不能与ghost.json、入口、图标、设置页、面板、Node 入口或 Skill 目录冲突。 - 单个 locale JSON 的大小上限由
GHOST_LOCALE_MAX_BYTES固定为 64 KiB。 包文件存在性、UTF-8 JSON 和资源内容由打包、发布及安装侧在读取制品时校验。 - 这是 schema v2 的可选追加字段,不需要提升 manifest 版本。旧消费方会按未知 字段忽略并继续使用顶层文案;支持该字段的消费方按宿主语言读取资源。
Plugin 可通过独立顶层字段 manual 声明由 Host 按需读取的 Markdown 手册索引:
{
"manual": {
"items": [
{
"dir": "manual/getting-started",
"name": "getting-started",
"description": "安装、连接与首个任务的完整工作流"
}
]
}
}manual不进入slots,不是运行时能力或权限授权;无手册的既有 Plugin 不受影响。items必须是 1–8 条。dir是包内安全相对目录;name是模型调用ghost_manual时使用的逻辑路径首段,沿用小写字母、数字与单连字符分段规则, 最长 64 字符;description是一级索引说明,长度 1–300 字符。- 每个声明目录必须包含固定入口
MANUAL.md。目录树可包含任意深度的子目录;所有 非目录条目都必须是普通 Markdown 文件,且每个文件不超过 64 KiB。文件存在性、 普通文件/非符号链接、严格 UTF-8、大小与二进制拒收由打包、发布及安装侧在读取 制品时校验。不同逻辑name的手册单元允许声明祖先/后代目录。 manual.items[].dir与ghost.json、entry、icon、settingsHtml、panel.html、node.entry、node.entries中声明的文件路径不得相同,也不得存在任一方向的 祖先/后代嵌套关系。Manifest 层会提前拒绝,避免 Markdown-only 手册目录在打包或 装入阶段才因包含已声明的非 Markdown 文件而失败。manual.items[].dir与任一locales文件路径不得相同,也不得存在任一方向的 祖先/后代嵌套关系:Manual 目录不能位于 locale 路径之下,locale 路径也不能位于 Manual 目录之下。Manifest 层会直接拒绝这类声明,避免 Markdown-only 手册目录在 打包或装入阶段才因包含 locale JSON 而失败。- 这是 schema v2 的 append-only 可选顶层字段,不需要提升 manifest 版本。旧消费方会
忽略该字段,Plugin 的其它能力继续可用;首个依赖手册才能正确工作的 Release 应声明
minCindyVersion,并在支持ghost_manual的 Cindy 发布后再投递。
声明了 node 槽的插件可以通过 node.secretBindings 请求主机把用户凭证安全持久化,并仅在指定 Worker 入口和 JSON-RPC 方法同时命中时临时注入:
{
"settingsHtml": "settings.html",
"slots": ["tool", "node"],
"node": {
"entry": "node/worker.cjs",
"protocol": "json-rpc-stdio",
"secretBindings": [
{
"key": "mail_code",
"label": "Mail authorization code",
"methods": ["account/connect", "mail/action"],
"hint": "Use the provider-generated authorization code",
"url": "https://mail.example.com/settings"
}
]
}
}- 每个插件最多声明 4 条绑定,每条最多绑定 16 个方法;
key与network.secrets、network.connections共用命名空间。 settingsHtml必填,负责把凭证一次性写入宿主保险库;浏览器沙箱与 Agent 参数不得接触凭证明文。entry可省略,省略时仅绑定node.entry;显式值必须逐字命中node.entry或node.entries。mcp-stdio绑定不得占用宿主保留的initialize、notifications/initialized握手方法。url仅接受不含内嵌用户名或密码的 HTTPS 地址。
这是 schema v2 的可选、追加字段,不改变未声明该字段的现有插件。旧版发布服务器会因严格的 node 字段白名单而拒绝包含该字段的包;因此发布顺序必须是协议仓合并、plugin-server 升级并部署,然后再发布使用该字段的插件。旧版客户端同样会拒绝安装而不会降级为不安全的明文传参。
组织插件可以声明 network.secrets[].source: "oidc-token",请求 Cindy
Desktop 为当前企业 Membership 按需签发短时 Connection JWT。令牌只在 Host
的 Main 进程内存中使用,插件代码和 Node Worker 都不能读取、保存或转交令牌:
{
"slots": ["network"],
"network": {
"hosts": ["api.example.com"],
"secrets": [
{
"key": "cindy_identity",
"label": "Cindy organization identity",
"source": "oidc-token",
"inject": {
"header": "Authorization",
"format": "Bearer {value}",
"hosts": ["api.example.com"]
}
}
]
}
}该来源必须同时满足:
inject.hosts必须显式非空,并且每项是network.hosts中的精确域名;不接受通配符;inject.header固定为Authorization,inject.format固定为Bearer {value};- 不得声明
input、url、exchange或oauth,也不要求settingsHtml; - Plugin Server 只允许
scope=organization的 Release 发布该来源;Public 和 Personal Release 必须拒绝。
这是 schema v2 的新增 manifest 能力,不改变未声明该来源的既有插件。旧版 plugin-server 或 Desktop 不认识该来源时必须拒绝发布/安装,并保留已有安装;部署顺序 应为先合并协议,再升级 plugin-server,最后发布支持正式 Market provenance 的 Desktop。
受信的 GitHub 插件可以声明 network.secrets[].source: "gh-cli",请求
Cindy Desktop 优先使用本机 gh auth token 的登录令牌;本机未安装 gh、
未登录或读取失败时,回落到用户在该插件设置页保存的备用 Token。
两种令牌都由 Host 选择并注入,插件代码和 Node Worker 无法读取明文:
{
"settingsHtml": "settings.html",
"slots": ["network"],
"network": {
"hosts": ["api.github.com"],
"secrets": [
{
"key": "github_pat",
"label": "GitHub login",
"source": "gh-cli",
"url": "https://github.com/settings/tokens",
"inject": {
"header": "Authorization",
"format": "Bearer {value}",
"hosts": ["api.github.com"]
}
}
]
}
}该来源必须同时满足:
settingsHtml必填,作为备用 Token 的写入、替换和清除入口;inject.header固定为Authorization,inject.format固定为Bearer {value};inject.hosts必须且只能声明精确域名api.github.com;- 不得声明
input、exchange或oauth;url可用于展示备用 Token 的 HTTPS 申请入口; - Plugin Server 与其他 source 一样只消费本 Protocol 的结构校验, 不读取、解析或注入 GitHub 凭证;宿主凭证的信任、用户授权与运行期 注入边界由 Desktop 的统一插件权限模型执行。
这是 schema v2 的新增 manifest 能力,不改变未声明该来源的既有插件。
旧版 plugin-server 或 Desktop 不认识该来源时必须拒绝发布/安装并保留已有
安装。部署顺序为:先合并协议,再升级并部署 plugin-server,然后发布支持
gh-cli 且接入统一插件权限模型的 Desktop,最后上架声明该来源的插件。
HTTP 返回体必须先作为 unknown 解析,再交给对应解析器:
const list = parseListPluginsResponse(await listResponse.json());
const detail = parseGetPluginResponse(await detailResponse.json());
const download = parsePluginDownloadResponse(await downloadResponse.json());parseListPluginsResponse:解析分页列表摘要与清理通告,不包含完整 manifest;parseGetPluginResponse:解析单个 Plugin 详情及当前 Release 的完整 manifest;parsePluginDownloadResponse:解析短期 HTTPS 下载地址及完整性元数据。
当前 Release 摘要可带 icon 元数据。它描述发布时从 .cindy 包中安全提取并独立存储的图标,而不是包内相对路径:
interface PluginIconMetadata {
mimeType: string;
sha256: string;
sizeBytes: number;
url: string;
expiresAt: string;
}icon 为 null 表示 manifest 未声明图标,或服务端暂未提供图标对象。旧 v2 响应缺少该字段时解析器也规范化为 null,客户端应继续使用兜底图标;提供该字段时,URL 必须是短期 HTTPS 地址,MIME 必须为 image/*,并经过 SHA-256、大小和过期时间校验。
三个解析器校验失败都会抛出 PluginProtocolError,错误消息包含出错字段路径,调用方应把它视为服务端响应不兼容或损坏,不应继续安装或切换 Release:
try {
const result = parseGetPluginResponse(await response.json());
// 使用 result.plugin
} catch (error) {
if (error instanceof PluginProtocolError) {
// 停止本轮远程对账,保留现有本地安装。
}
throw error;
}解析器返回的对象只保留协议已知字段。列表、详情和下载响应中的 SHA-256 必须是 64 位小写十六进制,字节数必须是正整数,时间必须是带毫秒的 UTC ISO 8601 字符串;下载地址只接受 HTTPS。下载响应不含 schemaVersion,因为它只会在列表或详情 envelope 已成功解析后请求。
列表响应可携带可选的顶层 removals 数组,通告「曾上架、现已下架并要求处置本地副本」的 Plugin。它与 plugins 互补:plugins 只含在架条目,被清理的 Plugin 不会回到列表里;detail 与 download 对被清理的 Plugin 维持 404。
- 这是 v2 的可选追加字段,不提升
PLUGIN_API_SCHEMA_VERSION。老服务端不下发、老客户端按未知字段忽略;解析器在字段缺失或为null时规范化为空数组。 - 服务端只对已验签的组织身份下发其所属组织的通告,与请求的
scope查询参数无关;当前scope恒为organization、action恒为purge(删除本地已安装副本及插件本地数据)。 - 分页时每一页都携带完整且相同的
removals,不受搜索关键字与游标影响;客户端聚合分页时按pluginId去重。 - 服务端保证单个响应内
plugins与removals不含相同pluginId,但跨分页请求期间状态可能翻转:客户端应在整轮分页完成后再应用通告;同一轮内某pluginId既出现在任一页plugins又出现在任一页removals时,以在架为准、不执行清理。 removedAt是最近一次下架时间,重新上架再下架会刷新;消费方不得据此假设单调或首次下架时间,去重与匹配一律以pluginId为准。action是取值级前向兼容位:其结构形状固定为 1–64 字符的字符串,形状合法但取值未知的通告会被解析器跳过,不影响其余内容,服务端未来新增动作(动作名必须落在该形状内)不要求客户端同步升级;结构不合法(pluginId、ghostId、scope 一致性、action形状、时间格式)仍抛出PluginProtocolError。- 通告不是无条件删除指令:客户端执行前必须与本地安装记录双重校验(
pluginId一致、来源为服务端市场、本地记录的 scope 为organization),校验不过时最多把该 Plugin 标记为不可更新,不得删除本地内容。
| 字段 | 语义 |
|---|---|
Plugin.id |
plugin-server 生成的永久资源 ID;用于详情、下载和本地 managed marker,不等于包内名称。 |
ghostId |
ghost.json.id;在同一 owner 内唯一,不同 Public、Organization、Personal owner 间允许相同。 |
scope |
public 对任意已登录 Cindy 身份可用;organization 只对对应组织可用;personal 只对发布者本人可用。 |
organizationId |
Organization 必须是非空组织 ID;Public 和 Personal 恒为 null。 |
defaultInstall |
对当前请求身份计算后的有效默认安装值;表示未安装时自动安装,不表示强制安装或强制启用。 |
minCindyVersion |
Release 的最低 Cindy 版本;必须是合法 SemVer。通常可选且缺失表示兼容所有版本;ios-simulator 等 Host-only slot 可要求必须声明。 |
X-Cindy-Version |
客户端请求列表、详情和下载时携带的 Cindy SemVer;共享常量为 CINDY_CLIENT_VERSION_HEADER,HTTP 头名称大小写不敏感。 |
currentRelease |
服务端为当前客户端选择的 Release;优先服务端 current,不兼容时回退到最新且仍有效的历史兼容 Release。列表只含摘要,详情额外包含 manifest。 |
currentRelease.icon |
所选兼容 Release 的可直接展示图标元数据;为 null 时使用客户端兜底图标,URL 为短期授权地址。 |
nextCursor |
下一页不透明游标或 null;客户端不得解析其内部结构,只能原样回传。当前服务端使用 sortOrder + Plugin.id 的复合位置,并兼容接收滚动期旧 Plugin ID 游标。 |
parseGetPluginResponse 还会校验 ghostId === manifest.id、Release version === manifest.version、顶层 name/description/author 与当前 manifest 一致,以及声明 oidc-token 的 manifest 只能属于 organization scope。调用方不能用 ghostId 合并不同来源的记录,应以 Plugin.id 标识服务端管理的安装实例。
supportsCindyVersion是 Desktop、Plugin Server 和 Forge 的统一比较语义:minCindyVersion缺失时恒兼容;声明后按 SemVer 比较,当前 Cindy 版本必须大于或等于最低版本。0.0.0和0.0.0-*表示没有正式版本号的开发构建,按当前源码兼容处理。其他非法版本不能参与比较。- 老客户端缺少
X-Cindy-Version,或请求头不是合法 SemVer 时,服务端把客户端版本视为未知:仍可交付没有minCindyVersion的旧 Release,不得交付声明了最低版本的 Release。 - 服务端先选择兼容的 current Release;不兼容时,只能从已批准、曾发布并上架且未撤销的历史 Release 中按内部创建顺序回退到最新兼容项。没有兼容 Release 时,列表不下发该 Plugin,详情和下载返回 404。
- 列表、详情和下载必须使用同一客户端版本选择同一个 Release。Desktop 下载真实
.cindy包后还要用同一函数复核包内 manifest,不能安装不兼容的包。
- Ghost manifest 当前只接受
GHOST_MANIFEST_SCHEMA_VERSION=2; - Plugin HTTP list/detail envelope 当前只接受
PLUGIN_API_SCHEMA_VERSION=2;v2 将global替换为public并新增personal; - 两个版本号独立演进,不能相互替代。
v2 的 nextCursor 语义见上文字段表;解析器接受这种非空、有界字符串,是对既有 v2 线上响应的兼容修复,不是新的 envelope 形状,也不提升 PLUGIN_API_SCHEMA_VERSION。
校验器对未知字段保持宽容,对已知字段和值严格校验。新增可选字段不要求服务端和 Desktop 同时发布;破坏性格式变化必须提升对应 schema version。
未知字段只用于前向兼容,不会出现在校验后的返回对象中。消费方不得依赖当前版本未声明的字段。
plugin-server 上传 Release 时使用本包校验 ghost.json,不支持的 manifest 不得发布。客户端使用本包解析列表、详情和短期下载凭证;下载响应本身不重复 envelope 版本,客户端只会在成功解析本轮列表/详情后请求它。下载只进入 staging,Desktop 必须以真实包内 manifest 再次判断兼容性。
客户端不支持 manifest 版本或真实包的 minCindyVersion 时:
- 首次安装失败并提示当前 Cindy 版本不兼容;
- 更新失败时丢弃 staging,继续保留本地旧 Release;
- 不执行 final switch,也不更新本地 managed marker。
HTTP envelope 版本不支持时,客户端停止本轮远程对账并保留本地状态。本期不提供多 current Release、capability 上报或其他协商机制。
成员上传是独立于市场 list/detail envelope 的异步发布契约,通过
@cindy/plugin-protocol/member-upload 或包根入口消费。它不改变
PLUGIN_API_SCHEMA_VERSION,也不复用现有 CI raw POST 的请求体。
import {
PluginProtocolError,
parsePluginMemberUploadStatusResponse,
} from '@cindy/plugin-protocol/member-upload';成员上传子路径与 delivery 子路径导出同一个 PluginProtocolError 类,调用方可以稳定使用
instanceof PluginProtocolError 识别解析失败。
| 操作 | HTTP 契约 | DTO |
|---|---|---|
| prepare | POST /api/publisher/uploads |
PreparePluginMemberUploadRequest → PreparePluginMemberUploadResponse |
| 上传文件 | PUT <putUrl> |
原始 .cindy body;使用 prepare 响应的 headers |
| commit | POST /api/publisher/uploads/:uploadId/commit |
CommitPluginMemberUploadRequest → CommitPluginMemberUploadResponse |
| status | GET /api/publisher/uploads/:uploadId |
PluginMemberUploadStatusResponse |
| my-publishes | GET /api/publisher/releases/mine |
ListMyPluginMemberReleasesResponse |
成员通道统一使用以下权威限制:
| 常量 | 值 | 含义 |
|---|---|---|
PLUGIN_MEMBER_UPLOAD_MAX_ARCHIVE_BYTES |
128 MiB | 单个 .cindy 压缩包最大字节数 |
PLUGIN_MEMBER_UPLOAD_MAX_UNCOMPRESSED_BYTES |
256 MiB | ZIP 条目解压后的累计最大字节数 |
PLUGIN_MEMBER_UPLOAD_MAX_ZIP_ENTRIES |
2048 | ZIP 条目数上限 |
这些常量只约束新的成员上传通道。plugin-server 接入该通道前,既有发布路径继续使用自己的现行限制; 消费方不得因为协议常量已发布,就假定尚未接线的服务端已经接受 128 MiB 包。
interface PreparePluginMemberUploadRequest {
sizeBytes: number;
sha256: string;
}
interface PreparePluginMemberUploadResponse {
uploadId: string;
putUrl: string;
headers: Record<string, string>;
expiresAt: string;
status: 'awaiting_upload';
}sizeBytes必须是1..PLUGIN_MEMBER_UPLOAD_MAX_ARCHIVE_BYTES的安全整数;sha256是 64 位小写十六进制。putUrl必须是 HTTPS;调用方按响应提供的 headers 上传,不把 Connection JWT 注入对象存储地址。- organization、membership、passport、ghostId、version、对象 key 和幂等身份都不在 body 中。身份只来自服务端已验证的鉴权上下文;幂等键使用
Idempotency-Key请求头。 - 相同身份范围和
Idempotency-Key的 prepare 重放返回同一 upload session,不创建第二份发布。
type CommitPluginMemberUploadRequest = Record<string, never>;
interface CommitPluginMemberUploadResponse {
uploadId: string;
status: PluginMemberUploadStatus;
}commit body 必须为空,不能覆盖 actor、organization、ghostId、version、hash、大小或对象位置。
commit 只启动或复用持久化异步任务;相同身份范围和 Idempotency-Key 的重放必须观察同一任务/result,
不能重复创建 Release 或审计记录。
interface PluginMemberUploadStatusResponse {
uploadId: string;
status: 'awaiting_upload' | 'validating' | 'publishing' | 'succeeded' | 'failed' | 'expired';
pluginId: string | null;
releaseId: string | null;
ghostId: string | null;
version: string | null;
reviewStatus: 'pending' | 'approved' | 'rejected' | null;
failure: { code: PluginMemberUploadFailureCode; message: string } | null;
}
interface PluginMemberReleaseSummary extends PluginMemberUploadStatusResponse {
createdAt: string;
updatedAt: string;
}
interface ListMyPluginMemberReleasesResponse {
releases: PluginMemberReleaseSummary[];
nextCursor: string | null;
}- 上传任务状态与 Release 审核状态相互独立。
succeeded必须同时带非空pluginId/releaseId/ghostId/version/reviewStatus;其他任务状态的reviewStatus必须为null。 failed必须带failure,其他任务状态不得带;message是处理后的用户可见原因,不得包含内部对象 key、审计 detail、凭证或签名 URL。- 稳定异步失败码为:
UPLOAD_OBJECT_MISSING、UPLOAD_SIZE_MISMATCH、UPLOAD_SHA256_MISMATCH、PLUGIN_PACKAGE_INVALID、MEMBERSHIP_INACTIVE、PUBLISH_NOT_AUTHORIZED、PLUGIN_GHOST_ID_CONFLICT、PUBLISH_STORAGE_UNAVAILABLE、PUBLISH_INTERNAL_ERROR。 - 非空
ghostId必须满足与ghost.json.id相同的isValidGhostId规则。 - 非空
pluginId必须满足与市场 delivery 响应相同的isValidPluginResourceId规则。 createdAt/updatedAt/expiresAt均为带毫秒的 UTC ISO 8601 时间。nextCursor是 1–4096 字符的不透明字符串或null;调用方只能原样回传,不得从中推导成员、Release 或时间信息。- my-publishes 只包含当前已验证成员有权查看的发布记录;身份和筛选范围不接受 body/query 自报覆盖。
- 此协议包先提供唯一 DTO、限制和解析规则;plugin-server、Cindy Host 和发布者插件分别 bump 后才能开启成员上传。
- 在三方实现和部署完成前,成员发布入口必须保持关闭;不能让任一消费方靠手抄字段或数字提前接线。
- 新成员上传解析器对已知字段和值 fail-closed。状态响应不合法或 PUT 结果不确定时,调用方不得把任务当成功,也不得自动重放 prepare/commit;应先重新查询服务端 status。
- 该契约不改变既有 GitHub OIDC CI 发布路径,也不改变市场 list/detail/download 的 v2 兼容策略。
本期由 plugin-server 和 Desktop 共同消费该包。协议合并后,两个消费方仓库分别 bump submodule 指针并在各自 PR 中完成适配;不能让任一方长期停留在只认识旧 manifest 的版本。
{ "schemaVersion": 2, "plugins": [], "nextCursor": null, "removals": [ { "pluginId": "cxxxxxxxxxxxxxxxxxxxxxxxx", "ghostId": "acme-report", "scope": "organization", "organizationId": "org_123", "action": "purge", "removedAt": "2026-08-03T08:00:00.000Z", }, ], }