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.
* docs(setups): add setup runner quickstart runbook
Standalone reference for opening the published-setup mockup frontend
(setup runner simulator): the `<comfy-address>/koolook/setup_runner_simulator.html`
URL pattern, the live-registry vs setup-file (inspect-only) load paths,
and demo mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(sidebar): close throwaway workflow tabs left by publish capture
Publishing a setup captures ComfyUI's API-format prompt by loading the
graph onto the canvas (graphToPrompt) and reloading the prior canvas.
In current Comfy each loadGraphData spawns an "Unsaved Workflow" tab, so
every publish left two stray tabs behind.
captureWorkflowApiPrompt now snapshots the open workflows before capture
and, afterward, re-activates the user's original tab and closes whatever
the capture opened, via app.extensionManager.workflow. All of it is
guarded and best-effort: the workflow-store API is Comfy-version
specific, so if it is absent capture still succeeds and behavior falls
back to today (no crash, no broken publish).
Covered by a source-pattern test. Needs live verification against the
1.45 frontend (dev-sync) to confirm the closeWorkflow API shape.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(nodes): Easy_LoadVideo degrades to empty on an unselected folder branch
When an upstream mode switch routes a path into Easy_LoadVideo, both the
EXR sequence loader and the video loader execute eagerly. On the EXR
branch the video loader was handed a folder (no filename) and raised
ValueError, crashing the whole prompt -- even though that branch is
discarded downstream. Lenient sequence loaders just ignore a wrong-type
path; the video loader was the only one that hard-failed.
_compose_input_video_path now returns EMPTY_BRANCH_SENTINEL instead of
raising when input_path resolves to an existing directory. load_video
turns that into a correctly-shaped empty result (IMAGE zeros,
frame_count 0, empty AUDIO, empty video_info) matching VHS_LoadVideoPath
returns, so the dead branch no longer crashes the prompt. VALIDATE_INPUTS
and IS_CHANGED handle the sentinel too. A path that does not exist is
still a genuine mistake and keeps raising.
torch is imported lazily in the empty-result builder so the path logic
stays importable/testable without torch.
Covered by unit tests for the sentinel (absolute + relative dirs), the
still-raises case, and a load_video integration test that asserts the
empty result and that the loader never runs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(setups): expose prompt as an always-on field in the publish frontend
The publish surface modelled `prompt` as a 4th source mode (mutually
exclusive with EXR/QT/Img) and hid it (visible:false), so a published
setup couldn't offer "pick a source AND describe the shot". This makes
prompt a standalone, always-on input.
- published_surface.js: prompt becomes visible + standalone + multiline,
rendered independently of the source switch; the author's prompt-widget
text is surfaced as a `placeholder` hint (submitted default is empty so
an untouched hint is never sent), with a `help` line. The switch
excludes standalone fields from its source options.
- setup_runner_simulator.js: renders always-on standalone inputs alongside
the selected source (textarea + placeholder + help), includes them in the
run payload, and updates the demo setup.
- setup_runner_simulator.html: .field-help styling.
- contract doc: documents the always-on prompt behaviour.
- launch.json: portability fix (hardcoded C:\Python313 -> python).
Contract test updated + a new test asserting prompt is always-on, carries
the placeholder, and is excluded from the switch source options (runs on
CI where Node is available). Behaviour verified in the runner simulator
(demo mode) in a browser: textarea + hint, EXR/QT/Img-only switch, prompt
stays visible across source changes, payload includes the typed prompt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(setups): mirror always-on prompt field in the Python publish inference
The publish surface is inferred server-side in Python (koolook_setups.py
_infer_app_surface, called by publishSetup), which overrides the client.
The earlier change only updated the JS (published_surface.js), so
re-publishing still produced the old surface with a hidden prompt.
Mirror the JS here: prompt is a standalone, always-on input (visible +
multiline) with the author's prompt-widget text as a placeholder hint and
an empty submitted default; it is excluded from the source switch options.
Keeps koolook_setups.py and published_surface.js in sync.
Registry test updated to the new shape. Server + publish-contract suites
green (115); full non-sidebar suite green (530).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(setups): keep the runner simulator polling through long renders
The poller used a fixed 30s total budget, so a multi-minute render (e.g.
428s) timed out mid-run with "Timed out waiting for run" even though
ComfyUI was still rendering and the run later landed as succeeded.
timeoutMs is now a "no-progress" window: while ComfyUI keeps reporting the
run as queued/running, the deadline is pushed forward, so a long-but-active
render never spuriously times out; only a stalled/vanished run gives up.
Default raised 30s -> 120s. The run status line shows elapsed seconds while
active so a long wait visibly ticks instead of looking frozen.
Happy path (queued -> running -> succeeded) unchanged; verified in the
runner simulator (demo mode) in a browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(setups): serve simulator assets with no-store so dev-syncs take effect
The runner-simulator HTML/JS route returned no cache headers, so browsers
cached the assets and kept running stale JS after a dev-sync -- e.g. the
old 30s poller persisted client-side even though the server was serving
the long-render fix, which looked like "the fix didn't land". Serve these
dev-synced assets with Cache-Control: no-store so a normal reload always
picks up the current code.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(setups): add live run/poll smoke check for the published-setup API
tools/check_setup_run.py queues a published setup through the same routes
the runner simulator uses and polls the run endpoint with the same
deadline-extension logic, printing the status timeline. Lets us self-verify
end-to-end run tracking against a live ComfyUI (e.g. "does a long render
reach succeeded?") without driving the browser UI.
Used it to confirm the run/poll server flow is correct (terminal status
detected in ~2s; run-000001 reported succeeded), isolating the simulator's
earlier "Timed out" to stale browser cache rather than a server bug.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(nodes): Koolook_NextVersion auto-detects the next output version
Adds filesystem-aware version auto-detection so a re-run lands on a fresh
version instead of failing with "Output file already exists and overwrite
is disabled" (the real EasyAIPipeline failure seen on the RMGB setup).
- koolook_versioning.next_version_token(directory, name, ...): scans for
existing <name>_vNNN files AND sequence-folders (handles name_v002.png,
name_v002.0001.exr, name_v003/), returns highest+1 zero-padded; missing/
empty dir -> start (v001); exact base-name match so other shots never
bump this one; empty name matches bare vNNN; configurable prefix/padding.
- Koolook_NextVersion node (Koolook/Publish): folder + name -> next version
token. Wire once into both the EXR and video version inputs so branches
stay in sync. IS_CHANGED returns NaN to re-scan every run.
Tested: helper edge cases + node registration/run. Preflight clean (20
node IDs). Full non-sidebar suite green (539).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(nodes): 'auto' version keyword for EasyAIPipeline + Easy_VideoCombine
No new node or wiring needed: type 'auto' (or 'next') in the version field
and the node scans its own output folder for existing <name>_vNNN entries
and writes the next free version -- fixing "Output file already exists and
overwrite is disabled" without manual version bumps.
- koolook_versioning.is_auto_version(): detects the auto/next keyword.
- EasyAIPipeline (build_pipeline_outputs): on auto, scans the version-folder
parent (base, or base/shot/method when subfolders are on) for vNNN dirs and
picks the next; version computed after path segments so the scan dir is known.
- Easy_VideoCombine: on auto, resolves the token after the output dir + name
root are composed, scanning that dir for <name>_vNNN outputs.
- Non-auto/typed/wired tokens and disable_versioning are unchanged.
Tests: is_auto_version keywords + EasyAIPipeline auto (v003 after v001/v002,
v001 first run). Full non-sidebar suite green (542), preflight clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(nodes): drop redundant Koolook_NextVersion node
The 'auto' version keyword on EasyAIPipeline + Easy_VideoCombine covers
auto-detection without any new node or wiring, so the standalone
Koolook_NextVersion node is redundant. Remove it (and its node test) to keep
the node list clean; the shared next_version_token helper stays (the 'auto'
keyword uses it).
Node-ID count back to 19; full non-sidebar suite green (541), preflight clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tools): satisfy security gate for setup smoke check
* fix(pr244): address verified review findings
* fix(video): fail if relative auto-version scan root is unavailable
---------
Co-authored-by: Cristian Predut <cristian.predut@x.baconcph.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: AI Machine <ai.machine@x.baconcph.com>
* feat(setups): publish success feedback + published-setup discoverability (#227)
Post-publish feedback (issue #227 Part 1):
- POST /koolook/api/setups now returns the on-disk storagePath
- new POST /koolook/api/setups/reveal opens the published-setups folder
(shared _open_dir_in_file_manager helper; the snapshot reveal route was
refactored onto it, behaviour-identical)
- the publish modal renders a success card reusing the snapshot Save dialog's
"Saved to ... Open folder" language (Open folder / Copy path / Close)
instead of closing silently; a draft publish still gets a confirmation
Published discoverability (reuses the tag system, no new schema):
- publishing auto-tags the source workflow `published` -> a "published" badge
in the Workflows tree + the Tags-section "published" pool
- a new "P" toggle in the sidebar Tools row prunes the Workflows tree to
published-only, folders force-expanded, so setups show in their origin
structure; toggling off restores the full tree
- workflowHasTag() shared helper (unit-tested)
Deferred, still open on #227: separate destination/library setting (Part 2),
relocating the storage folder beside the snapshot library, and clearing the
filter when entering Compare mode.
Tests: +server (storagePath, reveal route happy + 404), +client (reveal
client, buildPublishSuccessView view-model), +sidebar (workflowHasTag).
Full suite: 610 passed, 1 skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(setups): store published setups beside the snapshot library (#227)
- the published-setup registry path now derives from the configured snapshot
library (`_configured_dir().parent / koolook-published-setups/setups.json`)
instead of a fixed user-dir folder, so it follows libraryPath /
KFORGELABS_PRESETS. The default config resolves to the same path as before
(target == legacy), so existing default installs are unchanged.
- non-destructive one-time migration: copy a legacy user-dir registry to the
new location when the new one is absent, leaving the original in place.
- sidebar: clear the "P" published filter when entering Compare mode so it
doesn't render both compare columns filtered with no toggle to undo it.
Tests: +tests/server/test_published_setups_relocation.py (path beside library;
migration copy / no-op-when-present / no-op-when-no-legacy). Full suite: 614
passed, 1 skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(tests): remove unused pathlib.Path import (ruff F401)
The relocation test only uses the `tmp_path` fixture, not `Path` directly, so
`from pathlib import Path` was unused and failed the Lint & Smoke (ruff) gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(setups): address PR #239 review — migration masking + tag persist
- HIGH: a failed legacy->new migration no longer masks real setups with the
bundled sample. The default registry keeps the legacy file as its primary
storage when migration could not produce the new file, so stranded data
stays visible. (+2 tests: keeps-legacy-on-copy-failure / uses-new-on-success)
- MEDIUM/LOW: the publish flow now awaits the `published`-tag persist so the
success card reflects a committed tag (persist failures already surface via
persistMutation's critical toast), and warns when the source workflow could
not be tagged (vanished mid-publish) rather than on a harmless republish.
Full suite: 616 passed, 1 skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(setups): harden migration against partial/corrupt relocation target
Addresses the remaining HIGH from the PR #239 consolidated re-review: a
zero-byte/partial/corrupt setups.json at the relocated path could shadow the
intact legacy registry (existence was treated as proof of migration).
- migration now copies via temp file + atomic os.replace (the same pattern
FileSetupStorage.save_setups uses), so an interrupted copy can never leave
a partial setups.json at the target; failed-copy temp files are cleaned up.
- primary selection is corruption-aware: when the relocated file exists but
does not parse as a setups document while the legacy file is intact, the
registry keeps serving the legacy file and logs a loud diagnostic. The
unreadable file is left untouched (never overwritten) as evidence. A valid
relocated file stays authoritative; if both are corrupt, the relocated path
stays primary so storage diagnostics point at the current location.
Tests (+4): corrupt-target-prefers-intact-legacy (file left untouched),
valid-target-stays-authoritative, both-corrupt-keeps-target, and
interrupted-copy-leaves-no-partial-target-and-no-tmp-litter.
Full suite: 620 passed, 1 skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>