AI Art Engine EN
菜单

Developers · Marketplace

插件市场开发者文档

面向往插件市场发布内容的开发者:市场里有什么、内容长什么样、怎么被校验与分发、 客户端拿到之后会做什么。作者侧的逐字段写作规范在内容仓库里(见下方「内容仓库」), 这里讲的是客户端契约与交付链路——那部分只有在应用侧才能看到。

用户视角怎么用市场,见使用手册 §5.7 插件市场。

相关的客户端实现:src/shared/workflowMarket.ts(契约与解析)、 src/main/services/marketPipeline.ts(下载 / 缓存 / 原子安装)、 src/main/services/workflowMarketService.ts(目录、兼容性、安装记账)、 src/renderer/src/views/MarketplaceView.vue(界面)。

1. 市场里有什么

市场是一个独立窗口(顶栏「插件市场」按钮打开,紧挨「设置」左边),分四个页签:

页签 内容 托管位置
MCP 内置 aiartengine 工具服务、Blender 工具面,以及用户自己添加的第三方 MCP 服务(http / stdio) 应用内 + 用户本地配置
技能 应用内置技能快照、用户放进技能目录的自定义技能、随工作流安装的技能包 $DSH_HOME/skills
工作流 远端工作流仓库里的条目(可随包附带技能包) 内容仓库(见下)
全部 以上三类混排,带搜索框 —

只有工作流这一类是从远端仓库分发的;MCP 与技能是本地 / 内置来源。 工作流条目本身可以附带一个技能包,这是本市场区别于「一个下载站」的地方: 装工作流时把「给 Agent 看的操作手册」一起装上。

内容仓库

  • 主源:https://github.com/Justin-sky/ai-art-engine-workflow(raw 路径 /main)
  • 镜像:https://gitee.com/beijing_blue_whale_era_zhangjian/ai-art-engine-workflow(raw 路径 /main)

两个仓库的内容必须逐字节一致——不一致会造成「同一版本号、不同内容」, 这是最难排查的一类状态。同步方式见仓库 README。

作者侧的字段规范、审核标准、逐条拒绝原因在内容仓库的 CONTRIBUTING.md; 目录结构、workflow.json 各字段含义在同仓库 README.md。 这里不重复那份规范,只讲它没覆盖的客户端行为。

2. 交付链路:一步都不能省

贡献者提交 workflows/<id>/{workflow.json, cover.png, skill/**}
   ↓  CI 校验(validate.mjs + 单测)
   ↓  build-index.mjs 从磁盘派生 index.json(含技能文件清单)
客户端拉 index.json(先主源,失败降级镜像)
   ↓  用户点「安装」
下载 workflow.json + cover.png(+ 技能文件)
   ↓  原子安装(staging → rename,失败不留半成品)
<userData>/workflows/<id>/            工作流本体
   ↓  同一次安装内
$DSH_HOME/skills/<skill.name>/        技能包(随工作流一起)
   ↓
dsh 下次对话扫到该技能 → 注入 <available_skills> → Agent 按需加载

2.0 官方那 15 条的 plan 是导出的,不是手写的

应用内置的一键工作流预设(src/shared/graph/aiWorkflowPresets.ts)与市场上官方那 15 条 (game-ua-video … anim2d-gif)是同一张图的两份拷贝。市场要能独立分发、 内置预设又要离线可用,两份就都得在 —— 但不能靠人手同步:

npm run export:market        # 由内置预设生成各 workflow.json 的 plan 与 requires.nodeTypes
npm run check:market-plans   # 只校验,不一致退出码 1(CI 就在跑:拉市场仓库后核对)
  • 只写 plan 与 requires.nodeTypes 两个字段(前者是内容,后者是它的派生值 —— 市场校验器要求两者一致)。元数据、封面、skill/ 全部保持手写,一个字节都不碰。
  • 市场仓库默认在 ../ai-art-engine-workflow,可用 --dir <path> 或 AAE_WORKFLOW_MARKET_DIR 指定。
  • 因此改官方工作流的图要改应用预设,再导出;直接改市场那侧的 plan 会被判为漂移。 第三方工作流没有内置预设:在画布上右键「导出为市场工作流」,填好元数据与封面,应用会把当前图写成 workflows/<id>/{workflow.json, cover.png}(requires.nodeTypes 由 plan 派生, 运行时写回字段与节点类型没声明过的参数一律不进包);导出完成后按提示到仓库里跑 node scripts/build-index.mjs && node scripts/validate.mjs。内置预设占用的市场 id(那 15 条)会被拒绝导出 —— 它们的 plan 只能来自预设。
没有这道检查时的失败形态值得写下来:内置「一键工作流」和市场装出来的工作流会同名同版本、却是两张不同的图, 而两边都不报错 —— 用户只会以为模型不稳定。

2.1 索引里的派生字段由脚本生成,贡献者不要手写

index.json 里这些字段全部由 scripts/build-index.mjs 从磁盘派生:

  • requires.nodeTypes(该工作流用到哪些节点类型)、requires.appMinVersion
  • nodeCount / edgeCount / sizeBytes
  • skill:技能包的名字、描述、是否含脚本、文件清单、整包体积

最后一项尤其关键,原因值得写下来:

raw 源没有目录列表 API。客户端必须先知道每个文件的路径才能逐个下载, 所以技能包的文件清单只能由服务端(仓库脚本)派生。把清单交给脚本从磁盘生成, 就不可能出现「清单与磁盘漂移」——而这正是手写清单必然发生的故障。

workflow.json 里不写技能文件清单,只靠「skill/SKILL.md 是否存在」这一事实作为声明。CI 会比对索引与磁盘派生结果,手改索引会被拒。

2.2 技能包为什么要带 wf- 前缀

技能包落地目录是 $DSH_HOME/skills/<name>,而应用内置技能也在同一个目录 (扁平 <kebab-id>.md),两者 dsh 扫描的 rank 也相同。所以市场技能包的 name 强制为 wf-<工作流 id>,从源头避免与内置技能重名。 仓库校验器逐条拦这件事。

2.3 技能包文件布局

workflows/<id>/skill/
  SKILL.md          必需。frontmatter: name(wf-<id>) / description / workflow: <id>
  references/       可选。端口与参数速查、长契约、示例
  scripts/          可选。见 §4
  assets/           可选
  • 只允许上面三个子目录;不允许符号链接。
  • 单文件 ≤ 128KB,整包 ≤ 512KB,文件数 ≤ 40(客户端逐个下载,不能没有天花板)。
  • description ≤ 500 字符:dsh 的技能目录会截断它,而它是 Agent 决定是否加载这份技能之前唯一能看到的东西——请写成「什么时候该加载它」,而不是功能罗列。

2.4 正文里提到的节点类型必须真实存在

仓库校验器会检查技能正文里反引号包裹的 x.y 记号:若 x 是已知节点命名空间(asset / image / play / ui / model / spatialWorld / frame / note / beat / episode / anim / comic / svg / video / voice / media / output / graph / prompt / decisions …), 则该记号必须是真实节点类型。

为什么值得单独一条:技能是写给 Agent 的操作手册,它会照着里面的 typeId 去 graph_edit 建图。编一个不存在的类型(asset.pic 之类),Agent 就会写出一张坏图, 而运行时没有任何兜底会报出来——只会表现为「工作流莫名其妙不跑」。 按命名空间过滤是为了不误伤 cover.png / references/ports.md / SKILL.md 这类写法。 参数名请单独用反引号写(如 generateAspectRatio),不要写成 type.param 的点号形式。

3. 客户端安装契约

3.1 卡片与动作

工作流卡片上直接有 安装 / 更新 / 重新安装 / 卸载 与 详情,不必先进详情页。 卡片徽标会显示 含技能 或 含技能 · 含脚本——装之前就让用户知道 Agent 那边会多拿到一份手册(含脚本时更要说清)。

装好的技能包出现在市场的技能页签里(来源标为「技能包」)。技能的管理动作 (打开目录 / 生成示例模板 / 导入自定义技能)也在市场窗口里——市场改版时它们从设置页 搬了过来,设置页不再有独立的技能区块。

3.2 原子性与回滚

安装走 marketPipeline:先下到 staging,全部就位后再 rename 换入,失败清理。 技能包与工作流本体同生共死:技能装失败(或校验不通过)会把工作流本体一并卸掉并返回 失败原因。理由很直接——卡片写着「含技能」而技能没落地,等于界面在骗人; 安装是幂等的,重试即可。

3.3 安装前校验(否则「装上了却看不见」)

dsh 对不合法的技能是静默忽略(只写一行日志然后跳过)。所以客户端在 staging 阶段就校验 SKILL.md:frontmatter 是否存在、name 是否等于清单里的名字、 description 是否非空、是否用了 dsh 会直接抛错的 camelCase 旧键(disableModelInvocation 等)。不通过就安装失败并给出具体原因,绝不会留下一个装了却不生效的技能包。

3.4 卸装

卸载工作流会连带删除它装上的那个技能包——只按记账里记着的那一个目录名删, 绝不按前缀批量删(用户可能自建了同名技能)。技能包不是独立可卸载的产物。

3.5 目录刷新

打开市场窗口即强制刷新目录(这样往仓库推了新内容,重开窗口就能看到,不必等缓存 TTL)。 刷新失败但手上已有内容时保留旧列表并给软提示,不用错误顶掉已有内容。 缓存按源分别落盘:主源与镜像各存一份,避免把镜像的缓存当成主源的数据。

5. 可执行代码节点(script.run)与同意流

§4 讲的是技能包里的脚本(skill/scripts/,给 AI agent 在本机跑的文件)。 这一节讲的是另一条、互相独立的路径:工作流本体 plan 里的可执行代码。

script.run 是一种节点类型,代码(JavaScript)作为它的参数写在 plan 里, 由应用的图执行引擎在渲染进程里跑——而不是 harness shell。它运行在 受限沙箱(QuickJS WASM,src/shared/graph/execute/scriptSandboxQuickjs.ts)里: 无文件、无网络、无 require、无宿主全局。沙箱与应用之间只有三样东西过得去—— 上游输入进、端口输出出,以及一组 await om.<能力>(...) 的受控能力调用: <能力> 必须在该节点的 capabilities 白名单里声明过,每个 om.* 都转发到应用治理过的能力(提供商 / 密钥 / 配额 / 日志),不是后门。再叠加超时(deadline 中断)与内存上限。

5.1 识别与派生(仓库侧 + 客户端同口径)

  • 仓库 build-index.mjs 从 plan 派生 hasExecutableCode: true 与逐条脚本摘要(节点 key / 标题 / 声明用到的能力 / 代码字符数 / 短 hash)写进 index.json;贡献者不手写这些字段(与 requires.nodeTypes、技能清单同理)。
  • 客户端 parseWorkflowMarketEntry / parseWorkflowBundle 认这些字段并再校验一遍。
  • 校验器对每个 script.run 节点核对边界:code 非空且 < 100000 字符、 capabilities 只能取白名单内的能力、端口 id 合法不重名、limits 不超硬顶 (timeout ≤ 120s、mem ≤ 512MB)。代码为空 / 超长这类无法规整的会拒绝整条工作流 (代码是工作流的一部分,不像坏技能能降级丢弃);越白名单的能力、超硬顶的资源会被就地规整 (丢弃 / 夹到上限),不致整体拒绝。

5.2 安装时:含代码必须逐次同意,拒绝即不导入

  • 含 script.run 的工作流卡片标 含可执行代码(与技能的「含脚本」区分开)。
  • 安装前弹确认框,逐条列出每个脚本节点声明用到的能力与代码规模。
  • 确定 = 安装并允许运行;取消 = 不安装这条工作流—— 这与技能脚本的「取消只跳过脚本、照装其余」不同,因为代码就是工作流的一部分,去掉它工作流不成立。
  • 每次安装都重新问、不记忆、无「总是允许」。主进程只认字面量 scriptConsent === true。
  • 本地自建豁免:用户自己在应用里创建 / 编辑的脚本节点不走市场同意流(本来就是本人写的), 只有从市场导入才拦。
与技能脚本的关系:两条路径各自独立同意。一条工作流可能既「含技能 · 含脚本」又「含可执行代码」, 届时两个确认框各问各的。

6. 兼容性与版本

  • 索引 schemaVersion 目前是 1;比客户端更新的索引会让客户端明确报「请先更新应用」, 而不是半渲染出一个误导性的空市场。
  • 条目可声明 requires.appMinVersion:应用版本低于它时卡片会被禁用并给出原因。
  • requires.nodeTypes 由脚本从 plan 派生:旧版应用缺某个节点类型时会提前判定并在安装前提示, 而不是等物化时才落出一张残图。
  • 同内容必须升版本:同一 version 的内容不得再改。

7. 本地调试

7.1 在内容仓库里

node scripts/validate.mjs          # 格式 / 依赖 / 索引一致性 / 技能包全部规则
node scripts/build-index.mjs       # 重建 index.json(派生字段)
node scripts/build-index.mjs --check   # CI 用:索引是否与磁盘一致
node --test scripts/validate.test.mjs  # 校验器自身的单测

validate.mjs 与 CI 跑的是同一份代码——本地过、CI 不过这种事不该发生。

7.2 在应用侧

  • 自建源:数据层支持把 workflowMarket.source 设成一个或多个地址 (空白 / 逗号分隔;填了就只用填的,不混入官方源,避免回退把排查带偏)。 ⚠️ 当前设置界面还没有暴露这个输入项,属于数据层能力。
  • 想验证「Agent 到底有没有真的加载技能」,最可靠的证据是 dsh 的会话日志 ($DSH_HOME/sessions/<workspace>/<session>/session.v3.jsonl.zstd): 里面能查到 tool/call,name: "skill"、arguments: {"name": "wf-<id>"}, 以及紧随其后的 <skill_content> 结果。别只看界面——技能是否被加载, 界面是看不出来的。

8. 常见退回原因(客户端视角)

除内容仓库 CONTRIBUTING.md 列的那些之外,这几条会直接导致装上了却不生效:

现象 原因
装完 Agent 那边没有这份技能 SKILL.md 的 frontmatter 不合法(dsh 静默忽略)。客户端的安装前校验 就是为了把这种失败提前成一句可读的报错
技能名与内置技能冲突 name 没带 wf- 前缀
描述在 Agent 那里被截断 description 超过 500 字符
更新后文件对不上 手改了 index.json;它必须由 build-index.mjs 派生
技能里写的节点类型建不出图 正文里出现了不存在的 typeId(仓库校验器会拦,但改写正文后请重跑)
用户拒绝脚本后功能不可用 技能把关键步骤只写在 scripts/ 里;脚本必须是可选的