feat(publish): independent output type for published setups; fix(pipeline): auto-versioning re-runs each queue (#267)

Publish: Koolook_PublishOutput output_mode/input_switch/switch; setupSurface.app.outputSwitch; routers auto-bound as the output selector; unwired output types hidden; 'Same as input' offered only when every input type has a wired writer; result path follows the output switch. Pipeline: EasyAIPipeline IS_CHANGED keeps version=auto/next perpetually dirty so the vNNN disk scan re-runs every queue; literal versions stay cacheable.
This commit is contained in:
malkuthro
2026-07-19 23:34:30 +02:00
committed by GitHub
parent 950f15e569
commit 7b00a551d9
15 changed files with 1389 additions and 126 deletions
@@ -149,6 +149,9 @@ naming/location controls for the external app and downstream writer/path nodes:
| `folder` | Destination folder for generated outputs. |
| `name` | Base output name. |
| `version` | Version token/number used by the graph's naming logic. |
| `output_mode` | Output type control: `Same as input`, `EXR`, `QT`, `Img`. Default `Same as input` follows the input switch; a concrete choice overrides it. |
| `input_switch` (input slot) | Wired from `Koolook_PublishInput.switch`; only consulted when `output_mode` is `Same as input`. |
| `switch` (output slot) | Resolved output-type index (0=EXR, 1=QT, 2=Img). Wire into `Koolook_PublishRouter.selector` to route the writer branch by output type. |
The external frontend can expose these as editable output controls when the
setup author wants the user to choose where generated files should land.
@@ -156,6 +159,49 @@ setup author wants the user to choose where generated files should land.
the final result and should not own a mode-specific `result` field. Its outputs
feed the writer/path-building nodes for each supported branch.
The `switch` output makes output type **independent of input type**: a setup can
read an EXR sequence and write a QT movie. When `output_mode` is `Same as input`
the node passes the wired `input_switch` through unchanged, so setups that don't
need divergence behave exactly as before. The inferred app surface exposes this
as `setupSurface.app.outputSwitch` (key `output_switch`), a switch whose options
are `EXR`/`QT`/`Img` (no `Prompt`), carrying `sameAsInput: true` so the frontend
can offer a "Same as input" choice. The frontend must resolve "Same as input" to
the concrete input-switch value **before submitting** `output_switch` — the
runner validates the submitted value against the concrete option list, so an
explicit `-1` sentinel is rejected. Omitting `output_switch` entirely is valid:
when the published default is the `-1` "Same as input" sentinel, the runner
resolves it server-side to the input switch's selected value, so direct API
clients that skip the key get the same follow-input behavior the simulator
pre-resolves in the browser.
A `Koolook_PublishRouter` is inherently the **output selector** — its slots are
named EXR/QT/Img/Prompt. So when a setup exposes an output switch, the publisher
auto-binds every router to it (keying the execution-map router with `switchKey:
"output_switch"`) regardless of what the router's `selector` is physically wired
from. The author does **not** need to wire `Koolook_PublishOutput.switch` into
the router — just wire the writers to the router's branches. Whatever branches
have writers become the offered output types; the app user picks among them.
Each `outputSwitch` option carries a `visible` flag: the publisher marks an
output type visible only when some router has a wired writer branch for it (from
the execution map's `writerNodes`). The frontend renders only visible options,
so a user can't pick an output type that would write nothing. The runner
enforces this server-side too: a submitted switch value whose option is
`visible: false` is rejected with a 400 before queueing (the error lists only
the visible choices), so a direct API caller can't bypass the frontend and
queue a run with no writer.
`sameAsInput` is now conditional: "Same as input" is offered **only when every
selectable input type has a matching wired output writer** (a format-preserving
setup — EXR→EXR, QT→QT, Img→Img all wired). Otherwise it could resolve to a type
with no writer (an EXR source in a QT-only setup), so it is dropped and the
control offers only the concrete wired types. The `default` is the author's
`output_mode` widget when it names a wired type; else "Same as input" when safe;
else the first wired type — so an EXR-in / QT-only setup defaults to QT.
`output_mode`/`input_switch` on `Koolook_PublishOutput` remain optional:
`output_mode` sets the preferred default, and nothing needs wiring for the
output control to appear.
### `Koolook_PublishRouter`
Placed in the `Koolook Output` area when one setup switch selects between
@@ -164,7 +210,7 @@ each `Koolook_PublishInput.switch` value:
| Field/output | Purpose |
|---|---|
| `selector` | INT input wired from `Koolook_PublishInput.switch`. |
| `selector` | INT input wired from `Koolook_PublishInput.switch` (output type follows input) **or** `Koolook_PublishOutput.switch` (independent output type). The publisher tags the resulting execution-map router with `switchKey` `switch` or `output_switch` accordingly. |
| `payload` | Main setup payload, such as the mask/image result from the body graph. |
| `EXR` | Output slot 0, connected to the EXR writer branch. |
| `QT` | Output slot 1, connected to the QuickTime/movie writer branch. |
@@ -197,6 +243,12 @@ the path based on extension/type. When the result node is omitted, the runner
can still execute the selected writer branch through `Koolook_PublishRouter`;
the frontend should display returned writer/history items.
When the result is fed by a mode-switch node, the runner reports the branch for
whichever switch drives that node — the input switch **or** the independent
output switch. So a divergent EXR-in/QT-out setup whose result index-switch is
wired from `Koolook_PublishOutput.switch` reports the QT movie path, matching
the file actually written.
Decision: the first public result contract is the resolved path string. The
important requirement is that the string matches what the real setup resolved
inside ComfyUI. A frontend may optionally preview images or other known file
@@ -220,6 +272,7 @@ If QT:
If Img:
Single file: <path>
Output type: [Same as input] [EXR] [QT] [Img]
Output folder: <path>
Output name: <name>
Version: <version>
@@ -234,6 +287,7 @@ The frontend should read this from `setupSurface.app`, not from arbitrary
ComfyUI node classes:
- `setupSurface.app.switch`
- `setupSurface.app.outputSwitch` (optional; independent output type)
- `setupSurface.app.inputs`
- `setupSurface.app.outputs`
- `setupSurface.app.results`
@@ -331,6 +385,10 @@ Implemented:
- `setupSurface.app` support in the server schema and runner summaries.
- Switch-selected writer execution maps let the runner prune unselected writer
branches and report the selected result/writer output.
- Independent output type: `Koolook_PublishOutput.output_mode`/`switch`, the
`setupSurface.app.outputSwitch` surface, `output_switch`-keyed execution-map
routers, result-path reporting that follows the output switch, and output-type
options auto-hidden when their writer branch is unwired.
- Publish contract nodes:
- `Koolook_PublishInput`
- `Koolook_PublishOutput`
@@ -19,8 +19,9 @@ The nodes are not image processors. They are contract markers:
- `Koolook_PublishInput` declares source inputs such as an EXR sequence folder,
QuickTime file, single image/file, or prompt.
- `Koolook_PublishOutput` declares output folder/name/version controls.
- `Koolook_PublishRouter` connects the selected source mode to matching writer
- `Koolook_PublishOutput` declares output folder/name/version controls, plus an
optional independent **output type** (so an EXR source can write a QT movie).
- `Koolook_PublishRouter` connects the selected mode to matching writer
branches so only the chosen branch runs.
- `Koolook_PublishResult` optionally reports the final resolved path or status
string back to the external app.
@@ -57,16 +58,20 @@ The publisher records nearby nodes for review, but only recognized
| `folder` | Destination folder for generated outputs. |
| `name` | Base output name. |
| `version` | Version token or number. |
| `output_mode` | Output type: `Same as input`, `EXR`, `QT`, or `Img`. `Same as input` (default) follows the input type; a concrete choice writes a different type than the source. `Prompt` is not an output format. |
| `input_switch` (input) | Wire `Koolook_PublishInput.switch` here. Used only when `output_mode` is `Same as input`, so the output type mirrors the chosen input type. |
| `switch` (output) | Resolved output-type index. Wire into `Koolook_PublishRouter.selector` to drive the writer branch. |
### `Koolook_PublishRouter`
Wire `Koolook_PublishInput.switch` into `selector`, and wire the workflow's
main payload into `payload`. Connect each router output to the writer branch
for that source mode:
The router **is** the output selector. Wire the workflow's main payload into
`payload`, wire something into `selector` (typically the input switch — its
value is the "Same as input" default), and connect each output slot to the
writer branch for that type:
```text
Koolook_PublishInput.switch -> Koolook_PublishRouter.selector
workflow payload -> Koolook_PublishRouter.payload
Koolook_PublishInput.switch -> Koolook_PublishRouter.selector # default follows input
Koolook_PublishRouter.EXR -> EXR writer branch
Koolook_PublishRouter.QT -> video writer branch
@@ -74,8 +79,13 @@ Koolook_PublishRouter.Img -> image writer branch
Koolook_PublishRouter.Prompt -> prompt/no-op branch if needed
```
When the setup runs from the external app, Koolook keeps the selected writer
branch and prunes the unselected branches from the queued prompt.
Whichever branches you wire to writers become the **Output type** options the
external app offers — a user can override to any wired type, defaulting to the
input type (or the `Koolook_PublishOutput.output_mode` widget if you set one).
You do **not** need to wire `Koolook_PublishOutput.switch` anywhere; the router
is auto-detected as the output control. Put the writer nodes inside the
`Koolook Output` group so they're recognized. When the setup runs, Koolook keeps
the chosen writer branch and prunes the rest.
### `Koolook_PublishResult`