Developers · Marketplace
Plugin Marketplace Developer Docs
For developers publishing content to the plugin marketplace: what the marketplace contains, what the content looks like, how it is validated and distributed, and what the client does with it after install. The author-side, field-by-field writing rules live in the content repository (see “Content repository” below); what follows is the client contract and the delivery pipeline — the part you can only see on the app side.
For the user-facing side of the marketplace, see the manual §5.7 Plugin marketplace.
src/shared/workflowMarket.ts (contract and parsing),
src/main/services/marketPipeline.ts (download / cache / atomic install),
src/main/services/workflowMarketService.ts (catalog, compatibility, install
bookkeeping), src/renderer/src/views/MarketplaceView.vue (the UI).
1. What the marketplace contains
The marketplace is a separate window (opened by the “Marketplace” button in the top bar, immediately to the left of “Settings”) with four tabs:
| Tab | Content | Hosted at |
|---|---|---|
| MCP |
The built-in aiartengine tool service, the Blender tool surface, and
third-party MCP servers the user adds themselves (http / stdio)
|
In-app + the user’s local config |
| Skills | Built-in skill snapshots, custom skills the user drops into the skills directory, and skill bundles installed together with a workflow | $DSH_HOME/skills |
| Workflows | Entries from the remote workflow repository (may ship a skill bundle) | Content repository (below) |
| All | The three kinds above mixed together, with a search box | — |
Only Workflows are distributed from a remote repository; MCP and Skills come from local / built-in sources. A workflow entry itself may ship one skill bundle — that is what sets this marketplace apart from “a download site”: installing a workflow also installs the “operator’s manual for the Agent”.
Content repository
-
Primary source:
https://github.com/Justin-sky/ai-art-engine-workflow(raw path/main) -
Mirror:
https://gitee.com/beijing_blue_whale_era_zhangjian/ai-art-engine-workflow(raw path/main)
The two repositories must be byte-for-byte identical — any difference produces “same version number, different content”, the hardest class of state to debug. Syncing is described in the repository README.
The author-side field rules, review criteria and per-item rejection reasons live in the content
repository’s CONTRIBUTING.md; the directory layout and the meaning of each
workflow.json field are in that repository’s README.md.
Those rules are not repeated here — this page only covers the client behaviour
they don’t.
2. The delivery pipeline: no step can be skipped
Contributor submits workflows/<id>/{workflow.json, cover.png, skill/**}
↓ CI validation (validate.mjs + unit tests)
↓ build-index.mjs derives index.json from disk (including the skill file list)
Client fetches index.json (primary source first, falls back to the mirror on failure)
↓ user clicks “Install”
Downloads workflow.json + cover.png (+ skill files)
↓ atomic install (staging → rename; a failure leaves nothing half-installed)
<userData>/workflows/<id>/ the workflow itself
↓ within that same install
$DSH_HOME/skills/<skill.name>/ skill bundle (installed together with the workflow)
↓
the next dsh conversation picks up the skill → injects <available_skills> → the Agent loads it on demand
2.0 The 15 official plans are exported, not hand-written
The app's built-in One-Click Workflow presets (src/shared/graph/aiWorkflowPresets.ts) and the 15
official marketplace entries (game-ua-video … anim2d-gif) are two copies of
the same graph. The marketplace has to be independently distributable and the presets have to work
offline, so both must exist — but they must not be kept in sync by hand:
npm run export:market # regenerate each workflow.json's plan + requires.nodeTypes from the presets
npm run check:market-plans # verify only; exit code 1 on any mismatch (this is what CI runs)
-
It writes exactly two fields:
planandrequires.nodeTypes(the former is the content, the latter its derived value — the marketplace validator requires them to agree). Metadata, cover images andskill/stay hand-written; not a byte of them is touched. -
The marketplace repo defaults to
../ai-art-engine-workflow; override with--dir <path>orAAE_WORKFLOW_MARKET_DIR. -
So to change an official workflow's graph, change the app preset and re-export; editing the
planon the marketplace side is treated as drift. Third-party workflows have no built-in preset: right-click the canvas and pick “Export as marketplace workflow”, fill in the metadata and a cover, and the app writes the current graph asworkflows/<id>/{workflow.json, cover.png}(requires.nodeTypesis derived from the plan; runtime write-back fields and any parameter the node type does not declare are kept out of the pack). When it finishes, runnode scripts/build-index.mjs && node scripts/validate.mjsin the repo as prompted. The market ids used by the built-in presets (those 15) are refused — theirplancan only come from a preset.
2.1 Derived fields in the index are generated by scripts — never hand-write them
Every one of these fields in index.json is derived from disk by
scripts/build-index.mjs:
-
requires.nodeTypes(which node types the workflow uses),requires.appMinVersion nodeCount/edgeCount/sizeBytes-
skill: the skill bundle’s name, description, whether it contains scripts, its file list, and its total size
The last one matters most, and the reason is worth writing down:
workflow.json does not carry the skill file list: the only
declaration is the fact that skill/SKILL.md exists. CI compares the index against the
disk-derived result, and a hand-edited index is rejected.
2.2 Why skill bundles carry the wf- prefix
A skill bundle lands in $DSH_HOME/skills/<name>, and the
app’s built-in skills live in that same directory (flat
<kebab-id>.md), scanned at the same rank by dsh. So a marketplace skill
bundle’s name is forced to wf-<workflow id>,
ruling out a name clash with built-in skills at the source. The repository validator blocks this
item by item.
2.3 Skill bundle file layout
workflows/<id>/skill/
SKILL.md required. frontmatter: name(wf-<id>) / description / workflow: <id>
references/ optional. port and parameter cheat sheets, long contracts, examples
scripts/ optional. see §4
assets/ optional
- Only the three subdirectories above are allowed; symbolic links are not.
- Single file ≤ 128KB, whole bundle ≤ 512KB, file count ≤ 40 (the client downloads file by file, so there has to be a ceiling).
-
description≤ 500 characters: dsh’s skill catalog truncates it, and it is the only thing the Agent can see before deciding whether to load this skill — write “when should this be loaded”, not a feature list.
2.4 Node types mentioned in the body must actually exist
The repository validator checks the backtick-wrapped x.y tokens in a
skill’s body: if x is a known node namespace (asset / image /
play / ui / model / spatialWorld /
frame / note / beat / episode /
anim / comic / svg / video /
voice / media / output / graph /
prompt / decisions …), that token must be a
real node type.
Why it deserves its own rule: a skill is an operator’s manual written for the
Agent, and the Agent will follow the typeId values in it when it builds a graph with
graph_edit. Invent a type that doesn’t exist (asset.pic and the like)
and the Agent writes a broken graph, with nothing at runtime to catch it — it
only shows up as “the workflow mysteriously won’t run”. Filtering by namespace is there so that
spellings like cover.png / references/ports.md / SKILL.md
are not caught by mistake. Write parameter names in their own backticks (for
example generateAspectRatio) rather than as the dotted form type.param.
3. Client install contract
3.1 Cards and actions
Workflow cards carry Install / Update / Reinstall / Uninstall and Details directly, with no need to open the details page first. Card badges show Includes skill or Includes skill · Includes scripts — so the user knows before installing that the Agent side will receive an extra manual (and the same applies, more so, when scripts are involved).
An installed skill bundle appears in the marketplace’s Skills tab (its source is labelled “Skill bundle”). Skill management actions (open directory / generate an example template / import a custom skill) are also in the marketplace window — they moved there from the settings page when the marketplace was redesigned, and the settings page no longer has a separate skills section.
3.2 Atomicity and rollback
Installation goes through marketPipeline: download into staging first, rename into
place only once everything is ready, clean up on failure.
A skill bundle and the workflow it belongs to live and die together: if the skill
fails to install (or fails validation), the workflow itself is uninstalled as well and the failure
reason is returned. The reasoning is direct — a card that says “Includes skill” while the skill
never landed means the UI is lying. Install is idempotent, so retrying is all it takes.
3.3 Pre-install validation (otherwise “installed but invisible”)
dsh silently ignores an invalid skill (it writes one log line and
skips it). So the client validates SKILL.md during the staging phase: does the
frontmatter exist, does name equal the name in the list, is
description non-empty, did it use one of the legacy camelCase keys that make dsh
throw outright (disableModelInvocation and friends). If it fails, the
install fails with a concrete reason and never leaves behind a skill bundle that
is installed but does nothing.
3.4 Uninstall
Uninstalling a workflow also deletes the skill bundle that workflow installed — only the one directory name recorded in the bookkeeping, never a bulk delete by prefix (the user may have created a skill with the same name). A skill bundle is not a separately uninstallable artifact.
3.5 Catalog refresh
Opening the marketplace window force-refreshes the catalog (so pushing new content to the repository shows up just by reopening the window, with no need to wait for a cache TTL). When a refresh fails but content is already on hand, the client keeps the previous list and shows a soft hint instead of replacing existing content with an error. The cache is written to disk per source: primary and mirror each get their own copy, so mirror cache is never mistaken for primary data.
4. Scripts (scripts/) and consent
A skill bundle may carry scripts, but they are not installed by default: they are “code that runs on the user’s machine” and require the user’s explicit consent.
4.1 At install time: asked every single time
- When scripts are present, a confirmation dialog appears before install, listing every script path one by one and explaining that this is code the Agent can run on this machine.
-
Confirm = write the scripts; Cancel = install only
SKILL.mdandreferences/(the workflow still works as usual). - Every install / reinstall asks again; consent is never remembered — it is a per-install decision, not a switch that stays on once it has been flipped.
-
The main process only accepts the literal
skillScriptsConsent === true, with no truthiness coercion. - Script files are subject to the same path allowlist and size limits.
4.2 At run time: one approval covers exactly one call
When the Agent actually runs a script, it goes through the harness’s general shell
(pwsh on Windows) and is bounded by the sandbox. As soon as a step needs to go beyond
the sandbox, the approval responder hands the decision to the user:
| User choice | Result |
|---|---|
| Allow once | That call is allowed through (the only path that allows it) |
| Deny | The tool call is rejected |
| No answer / timeout / unreadable payload / file read error / any exception | Fail closed (the tool call fails; it is never allowed through) |
| Request aborted (the turn ended) | Cancelled, not allowed through |
In the UI it is an in-conversation approval card: tool name + reason, offering only Allow once / Deny, with no “always allow” — one-shot approval is deliberate, because persistent grants silently widen what the Agent can run.
5. Executable-code node (script.run) and consent
§4 is about scripts inside a skill bundle (skill/scripts/, files the
AI agent runs on the local machine). This section is a separate, independent path:
executable code inside the workflow’s own plan.
script.run is a node type whose code (JavaScript) is a parameter written into the
plan, run by the app’s graph execution engine in the renderer process
— not the harness shell. It runs in a restricted sandbox (QuickJS WASM,
src/shared/graph/execute/scriptSandboxQuickjs.ts): no file system, no network,
no require, no host globals. Only three things cross the sandbox boundary —
upstream inputs in, port outputs out, and a set of governed capability calls
await om.<capability>(...): the <capability> must be declared in
the node’s capabilities whitelist, and every om.* forwards to an
app-governed capability (provider / key / quota / log), not a back door. Plus a timeout (deadline
interrupt) and a memory cap.
5.1 Detection and derivation (repo + client, same rule)
-
The repo
build-index.mjsderiveshasExecutableCode: trueand a per-node script summary (node key / title / declared capabilities / code char count / short hash) intoindex.json; contributors do not hand-write these fields (same asrequires.nodeTypesand the skill manifest). - The client
parseWorkflowMarketEntry/parseWorkflowBundleread those fields and re-validate. -
The validator checks each
script.runnode’s bounds:codenon-empty and < 100000 chars,capabilitiesonly from the whitelist, port ids valid and unique,limitswithin hard caps (timeout ≤ 120s, mem ≤ 512MB). Empty / oversized code — things that cannot be normalised — reject the whole workflow (the code is part of the workflow and, unlike a bad skill, cannot be dropped to degrade). Out-of- whitelist capabilities and over-cap resources are normalised in place (dropped / clamped) rather than rejecting the whole thing.
5.2 Install time: executable code needs per-install consent; declining means not imported
- Workflows with a
script.runnode show a Contains executable code badge (distinct from a skill’s “includes scripts”). - Before install a confirm dialog lists each script node’s declared capabilities and code size.
- OK = install and allow it to run; Cancel = do not install this workflow — this differs from skill scripts’ “Cancel skips only the scripts and installs the rest”, because the code is part of the workflow and without it the workflow doesn’t stand.
- Asked on every install, never remembered, no “always allow”. The main process only accepts the literal
scriptConsent === true. - Locally authored is exempt: script nodes you create / edit in the app yourself don’t go through the marketplace consent flow (you wrote them); only importing from the marketplace gates.
6. Compatibility and versions
-
The index
schemaVersionis currently 1; an index newer than the client makes the client report plainly “please update the app” rather than half-render a misleading empty marketplace. -
An entry may declare
requires.appMinVersion: when the app version is below it, the card is disabled and the reason is shown. -
requires.nodeTypesis derived by the script from theplan: when an older app is missing a node type, that is determined up front and surfaced before install, instead of materialising into a broken graph later. -
The same content must bump its version: content at an existing
versionmust not be changed again.
7. Local debugging
7.1 In the content repository
node scripts/validate.mjs # format / dependencies / index consistency / all skill bundle rules
node scripts/build-index.mjs # rebuild index.json (the derived fields)
node scripts/build-index.mjs --check # what CI runs: is the index consistent with disk?
node --test scripts/validate.test.mjs # unit tests for the validator itself
validate.mjs and CI run the very same code — passing locally while
failing in CI is not something that should happen.
7.2 On the app side
-
Custom sources: the data layer supports setting
workflowMarket.sourceto one or more addresses (blank / comma-separated; once set, only what you set is used, with no official source mixed in, so fallback can’t lead your debugging astray). ⚠️ The settings UI does not expose this input yet — it is a data-layer capability. -
To verify “did the Agent really load the skill”, the most reliable evidence is the dsh session log
(
$DSH_HOME/sessions/<workspace>/<session>/session.v3.jsonl.zstd): it shows thetool/callwithname: "skill"andarguments: {"name": "wf-<id>"}, followed immediately by the<skill_content>result. Don’t just look at the UI — the UI cannot tell you whether a skill was loaded.
8. Common rejection reasons (client-side view)
Beyond the ones listed in the content repository’s CONTRIBUTING.md, these directly
produce “installed but not in effect”:
| Symptom | Cause |
|---|---|
| The Agent side has no such skill after install |
The SKILL.md frontmatter is invalid (dsh silently ignores it).
The client’s pre-install validation exists to turn this failure into one readable error
ahead of time
|
| The skill name clashes with a built-in skill | name is missing the wf- prefix |
| The description gets truncated on the Agent side | description exceeds 500 characters |
| Files don’t match after an update |
index.json was hand-edited; it must be derived by
build-index.mjs
|
| A node type written in the skill can’t build a graph |
The body contains a typeId that doesn’t exist (the repository validator
catches it, but re-run it after editing the body)
|
| Features unavailable after the user declines scripts |
The skill put its critical steps only in scripts/; scripts must be optional
|