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:技能包的名字、描述、是否含脚本、文件清单、整包体积
最后一项尤其关键,原因值得写下来:
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)。 刷新失败但手上已有内容时保留旧列表并给软提示,不用错误顶掉已有内容。 缓存按源分别落盘:主源与镜像各存一份,避免把镜像的缓存当成主源的数据。
4. 脚本(scripts/)与同意流
技能包可以带脚本,但默认不装:它属于「会在用户机器上运行的代码」, 必须由用户明示同意。
4.1 安装时:每次都要问
- 含脚本时,安装前弹出确认框,逐条列出脚本路径,并说明这是 Agent 可以在本机运行的代码。
-
确定 = 写入脚本;取消 = 只装
SKILL.md与references/(工作流照常可用)。 - 每次安装 / 重新安装都会再问一次,不记住同意——同意是每次安装的独立决定, 不是一个装过一次就永久生效的开关。
- 主进程只认字面量
skillScriptsConsent === true,不做真值 coercion。 - 脚本文件同样受路径白名单与体积上限约束。
4.2 运行时:一次授权只对一次调用有效
Agent 真正去跑脚本时,走的是 harness 的通用 shell(Windows 上是 pwsh),
受沙箱约束。一旦某一步需要越出沙箱,就由审批应答器把决定权交给用户:
| 用户选择 | 结果 |
|---|---|
| 允许一次 | 该次调用放行(唯一放行路径) |
| 拒绝 | 工具调用被拒 |
| 无人应答 / 超时 / 载荷读不懂 / 读文件出错 / 任何异常 | 失败即关闭(工具调用失败,绝不放行) |
| 请求被中止(本轮结束) | 取消,不放行 |
界面上是一张对话内授权卡:工具名 + 原因,只有 允许一次 / 拒绝, 没有「总是允许」——一次性授权是刻意的,持久授予等于悄悄放宽 Agent 能跑的东西。
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/ 里;脚本必须是可选的 |