Skip to content

ci(deps): bump actions/upload-artifact from 4 to 7#3

Closed
dependabot[bot] wants to merge 558 commits intomainfrom
dependabot/github_actions/actions/upload-artifact-7
Closed

ci(deps): bump actions/upload-artifact from 4 to 7#3
dependabot[bot] wants to merge 558 commits intomainfrom
dependabot/github_actions/actions/upload-artifact-7

Conversation

@dependabot
Copy link
Copy Markdown

@dependabot dependabot Bot commented on behalf of github Apr 19, 2026

Bumps actions/upload-artifact from 4 to 7.

Release notes

Sourced from actions/upload-artifact's releases.

v7.0.0

v7 What's new

Direct Uploads

Adds support for uploading single files directly (unzipped). Callers can set the new archive parameter to false to skip zipping the file during upload. Right now, we only support single files. The action will fail if the glob passed resolves to multiple files. The name parameter is also ignored with this setting. Instead, the name of the artifact will be the name of the uploaded file.

ESM

To support new versions of the @actions/* packages, we've upgraded the package to ESM.

What's Changed

New Contributors

Full Changelog: actions/upload-artifact@v6...v7.0.0

v6.0.0

v6 - What's new

[!IMPORTANT] actions/upload-artifact@v6 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

Node.js 24

This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

What's Changed

Full Changelog: actions/upload-artifact@v5.0.0...v6.0.0

v5.0.0

What's Changed

BREAKING CHANGE: this update supports Node v24.x. This is not a breaking change per-se but we're treating it as such.

... (truncated)

Commits
  • 043fb46 Merge pull request #797 from actions/yacaovsnc/update-dependency
  • 634250c Include changes in typespec/ts-http-runtime 0.3.5
  • e454baa Readme: bump all the example versions to v7 (#796)
  • 74fad66 Update the readme with direct upload details (#795)
  • bbbca2d Support direct file uploads (#764)
  • 589182c Upgrade the module to ESM and bump dependencies (#762)
  • 47309c9 Merge pull request #754 from actions/Link-/add-proxy-integration-tests
  • 02a8460 Add proxy integration test
  • b7c566a Merge pull request #745 from actions/upload-artifact-v6-release
  • e516bc8 docs: correct description of Node.js 24 support in README
  • Additional commits viewable in compare view

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

64v and others added 30 commits April 10, 2026 20:26
One-click Windows script that:
- Builds installer, manual-update, and Wizard Guild executables
- Commits to SFW repo and pushes
- Mirrors source + binaries to NSFW sibling repo and pushes
- Supports --no-push, --installer-only, --guild-only, --push-only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previous version tried to git-commit 300MB .exe files which exceeds
GitHub's 100MB limit. Now:
- Adds .gitignore (dist/, build/, __pycache__, etc.)
- Builds all executables into dist/
- Pushes SOURCE only to both SFW and NSFW repos
- Uploads binaries to GitHub Release via gh CLI or Python fallback
- Uses selective file copy for NSFW mirror (preserves NSFW-only content)
- Supports --tag vX.Y to set release version

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ntax

Inline Python in batch files breaks cmd.exe parser. Moved release
upload to standalone release_upload.py. Also fixed NSFW mirror to
use selective copy preserving NSFW-only content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root now shows only:
  Install.bat      — run the installer
  Settings.bat     — configure guild settings
  Wizard Guild.bat — launch the wizard guild

Developer scripts moved to dev/ (rebuild.bat, release_upload.py,
debug_guild.bat, update.bat). Rebuild.bat paths updated for dev/ location.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Canonical spellcaster_core/ package with architectures, composites,
node_factory, model_detect, and prompt_enhance modules. GIMP plugin
and Wizard Guild now import from this single source via thin shims.

4 ComfyUI nodes: SpellcasterLoader (auto-arch), SpellcasterPromptEnhance
(LLM), SpellcasterSampler (auto-config), SpellcasterOutput (privacy).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds _install_spellcaster_nodes() to copy comfyui-spellcaster/ into
custom_nodes/ComfyUI-Spellcaster during installation. Skips local
nodes in the git clone loop. Manifest updated with node pack entry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ComfyUI-Spellcaster now lives at github.com/laboratoiresonore/ComfyUI-Spellcaster
- Installer manifest updated: git clone (auto-update) instead of local copy
- Removed _install_spellcaster_nodes() local copy logic from installer
- Synced all rewritten node files with correct ComfyUI APIs
- Added web extension, workflow templates, README, LICENSE

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Added comfyui-spellcaster/ with all 4 base nodes + 2 NSFW additions:
  SpellcasterNSFWLoRA (preset/manual/stack) and SpellcasterNSFWLoRAModelOnly
- NSFW LoRA presets for Flux Dev, Klein, SDXL, Illustrious, WAN I2V
- Updated manifest.json to git clone from ComfyUI-Spellcaster-NSFW repo
- Updated rebuild.bat to sync only base files (preserve NSFW additions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…aming

- Fix missing architectures (ltx, wan, seedvr, chroma, pony) in GIMP,
  Darktable, and guild_common ARCH_LORA_PREFIXES — video LoRAs no longer
  leak into SDXL/SD1.5 dropdowns
- Change server.py LoRA fallback from ["sd15"] to ["unknown"] to prevent
  unclassified LoRAs from polluting SD1.5 menus
- Add POST /api/scaffold_edit endpoint with persistent scaffold_overrides.json
  — scaffold editor changes now survive restarts
- Wire frontend ScaffoldEditor to debounce-save edits to server
- Load server-side scaffolds into editor on mount (auto-detected wizards
  now appear in the scaffold list)
- Add background LLM-powered scaffold enhancement: auto-names "Unnamed
  Wizard" entries using local LLM when available
- Installer: add LLM URL prompt, ComfyUI server probing, shared settings
- Add "Wan-2.2-I2V\\" to wan LoRA prefixes in canonical model_detect.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When installed via ComfyUI Manager (git clone), users only get the
nodes. On first load, Spellcaster now:
- Drops Install_Spellcaster_Suite.bat into custom_nodes/ — one click
  to clone the full repo and run the installer
- Prints a console banner with instructions
- Shows a dismissable toast in the ComfyUI web UI pointing to the bat

All signals are suppressed once the full suite is detected
(spellcaster_settings.json or .suite_installed marker).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nager flow

- Main README: add "Already Using ComfyUI Manager?" section explaining
  the auto-dropped Install_Spellcaster_Suite.bat
- ComfyUI node README: add Full Spellcaster Suite section, note about
  install toast, add Pony to supported architectures table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
During installation, features whose custom nodes and required models are
already present on the ComfyUI server are now shown as locked (✓) and
cannot be toggled off — reinstalling them would be a no-op.

- Add feature_already_installed() that cross-references manifest against
  server_info from probing (nodes via provides, models via filename match)
- step_select_features now accepts server_info, shows locked features as
  cyan [✓] with "Already on server" reason
- step_install_nodes skips node packs whose provides are already in
  available_nodes
- Hardware profile summary shows installed count separately

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract KLEIN_MODELS, FLUX2_VAE to module-level constants in
  _workflows_v2.py; delete 8 duplicated inline dicts (~70 lines)
- Import KLEIN_MODELS in comfyui-connector.py instead of redefining
- Add STUDIO_FACE/BODY/SCENE dimension constants for canvas-aware
  compositing across the Magic Studio 5-act pipeline
- build_photobooth: fixed square output (STUDIO_FACE_W×STUDIO_FACE_H)
  instead of deriving from reference aspect; add transparent= flag
  for rembg background removal with alpha matting
- build_rembg: expose alpha_matting and model params
- build_klein_blend: default scale from STUDIO_BODY_IN_SCENE_SCALE
  so body PNGs fill ~85% of scene height automatically

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
For users who run ComfyUI on a separate machine in their local network.
Probes the remote server, auto-detects local apps (GIMP, Darktable),
installs plugins + Wizard Guild + shortcuts — zero interaction needed.

- install_remote.py: standalone autonomous installer
  - Network scan (--scan) to auto-discover ComfyUI on /24 subnet
  - Server probing via /system_stats + /object_info endpoints
  - Feature detection by cross-referencing server nodes vs manifest
  - Auto-detect GIMP, Darktable paths (cross-platform)
  - Plugin deployment with remote server URL pre-configured
  - Wizard Guild + scaffold installation with launcher scripts
  - Desktop/Start Menu shortcuts pointing at remote ComfyUI
  - --dry-run, --interactive, --skip-* flags for flexibility

- build_installer.py: --remote / --remote-only flags for PyInstaller build

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The remote installer now probes the ComfyUI server's LoRA and model lists
for known NSFW patterns (nicegirls, aidmansfw, phr00t, etc.) to detect
whether the SFW or NSFW edition is running. Based on detection:

- Writes nsfw_mode flag to spellcaster_settings.json
- Sets up nsfw/.github_token for Wizard Guild NSFW auto-updates
- Shows edition in summary banner

New flags: --nsfw-token <PAT>, --force-sfw

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Documents the new install_remote.py with usage examples, feature list,
full flag reference, and download badges. Updates the existing installer
walkthrough to link to the dedicated remote installer section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e instructions

No release binary exists yet — point users at the Python script instead,
with a note on how to build the standalone .exe themselves.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a clearly labeled button on page 1 of the GUI installer for users
whose ComfyUI backend runs on a separate LAN machine. The button
launches install_remote.py --interactive in a new terminal window.

Descriptive text makes it clear this only installs local plugins and
shortcuts (GIMP, Darktable, Wizard Guild) — it does NOT install
ComfyUI or models on the local machine.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The main GUI installer now has the Antenna Installer button built into
the Welcome page, so the verbose clone-and-run-from-source instructions,
build-your-own-exe section, collapsible walkthrough, and CLI flag table
are no longer needed. Replaced with a single sentence pointing users to
the button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When user enters a remote ComfyUI server URL in the Welcome page prereqs
panel and the connection test succeeds, the Antenna Installer button now
appears right there — offering to install only local plugins and
shortcuts without going through the full 8-step wizard.

Also scrubs remaining example IPs from install.py and
patch_sillytavern.py doc strings, replacing with <SERVER-IP> placeholders.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rebuild.bat now passes --remote to build_installer.py so the
spellcaster-remote-installer.exe is built alongside the main installer
and uploaded to GitHub Releases.

_launch_antenna_installer now correctly handles frozen .exe mode by
looking for spellcaster-remote-installer.exe next to the main
installer, instead of trying to run a .py with no Python interpreter.
Also passes the server URL if the user already entered one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace supplementary-plane emoji (U+1F4E1) with BMP arrow (U+2192)
in antenna button labels — Tcl/Tk on Windows can choke on codepoints
above U+FFFF in certain widget contexts.

Replace <SERVER-IP> placeholder in entry field with plain text
"your-server-ip" to avoid potential Tcl angle-bracket interpretation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of warning and skipping, rebuild.bat now clones
spellcaster_NSFW automatically next to the SFW repo when it
can't find it. Falls back gracefully if the clone fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Windows 'start' command interprets the first quoted argument as
a window title. When the exe path has spaces, start treats it as
the title and launches nothing (or crashes). Fix: pass empty ""
as the title so the exe path is correctly interpreted as the
program to run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The NSFW repo is private. The previous clone URL had no auth and
would hang prompting for credentials or fail with 403. Now extracts
the full remote URL from the SFW repo (which includes the PAT) and
swaps the repo name, so the clone authenticates automatically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Step 7 crash: The Python one-liner for version auto-detection had
nested single quotes that broke cmd.exe parsing (`.read( was
unexpected`). Fixed by writing to a temp .py file first.

Steps 5/6 push failures: Added git pull --rebase before push for
both SFW and NSFW repos so remote changes don't cause rejections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parentheses in .read() and .group(1) were parsed by cmd.exe as
batch block delimiters when inside an if() block. Moved the
detection outside the if block using goto, and pipe output to a
temp file with set /p instead of for /f. Uses \x22 and \x27
escapes for quote chars to avoid all quoting conflicts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
64v and others added 21 commits April 18, 2026 16:10
The installer now asks, per registered service (comfyui, kobold, ollama,
sillytavern, gimp, darktable, resolve, ...), where it runs:

  [1] Local — on THIS machine
  [2] Remote — on another machine on my network
  [3] Skip — I'm not using this

Remote selections are grouped by IP so one antenna.bat is generated per
machine, not per service. A single box hosting both ComfyUI + Ollama
gets a single .bat that advertises both.

Generated files per remote machine (written to <install>/antennas/):
  - antenna_for_<ip>.bat    Windows launcher
  - antenna_for_<ip>.sh     Linux/macOS launcher
  - README.txt              One-line instructions per file

Each launcher:
  - Embeds a UNIQUE per-machine token (secrets.token_urlsafe(32))
    so leaking one bat doesn't compromise other remotes
  - Embeds the hub URL (auto-detected via LAN socket trick)
  - Embeds the services array the remote antenna should advertise
  - On first launch: downloads the antenna bundle from
    <hub>/api/antenna/bundle.zip (endpoint coming next commit),
    writes config + token to ~/.spellcaster/, launches the agent
  - On subsequent launches: just starts the agent
  - Friendly error if hub is unreachable

step_ask_remote_services(args) returns a dict suitable for passing
straight into generate_antenna_files(remotes, output_dir, hub_url).
install.py will wire these into main() in the next commit.

Live-tested:
  - 2 remote machines declared (ComfyUI+Ollama, SillyTavern)
  - Correct per-machine service isolation (sillytavern box doesn't
    advertise comfyui in its 'services' array)
  - Tokens are unique per machine (verified via regex extraction)
  - README lists each remote with its services

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…esence

Introduces the "no dead functions" architecture. Every frontend (GIMP,
Darktable, Resolve, Guild, SillyTavern, Signal) can now publish events,
upload assets, and heartbeat its presence to the Wizard Guild. The UI
renders interface-specific chips (Send to Resolve / Open in GIMP / Edit
in Darktable) only when the target is actually installed + enabled +
online — never dead buttons pointing at nonexistent plugins.

Core modules (spellcaster_core/, synced across 4 repos):
- event_bus.py       — thread-safe pub/sub with ring-buffer replay
- interface_registry.py — installed+enabled+online gate for every UI
- asset_gallery.py   — hash-indexed shared asset store with SHA-256
                       dedup and sharded blob layout
- cross_interface.py — thin HTTP client every frontend imports
- model_registry.py  — unified /object_info cache shared across tools
- signal_notifier.py — outbound notifications on render completion

Guild server (tavern/server.py):
- Added 8 endpoints: /api/interfaces, /api/interfaces/heartbeat,
  /api/events, /api/events/stream, /api/events/emit, /api/assets (GET/POST),
  /api/assets/<hash>, /api/models
- HTTPServer → ThreadingHTTPServer so SSE subscriptions don't block
  the rest of the API

Guild UI (tavern/static/):
- Sidebar "Connected apps" strip polls /api/interfaces every 10s,
  renders only active interfaces. Empty = hidden entirely.
- Image action chips filter by interface availability. New cross-
  interface chips: 🎬 Send to Resolve, 🖼️ Open in GIMP, 📷 Edit in
  Darktable. Each first persists the image to the shared gallery,
  then emits the bus event with a stable hash URL.

DaVinci Resolve plugin suite (plugins/resolve/):
- Spellcaster Bridge workflow integration (always-on): SSE client,
  media pool sync, auto-heartbeat, bus subscription to resolve.* events
- Generate from Playhead: keyboard-shortcut script that grabs the
  current frame and creates a new shot on the Guild
- Smart Fill Gap: detects gaps between clips and queues FLF renders
- Shared HTTP client + resolve_helpers, README with install paths

GIMP plugin:
- Lazy-start cross-interface client, auto-heartbeat on every menu
  invocation, _publish_event helper ready for _run_* methods

Darktable plugin:
- guild_heartbeat() + guild_emit_event() helpers (curl-based to match
  existing HTTP pattern), fires a heartbeat on plugin load

Dynamic presence guarantee: all "Send to X" chips, overflow entries,
sidebar items, and event handlers gate on the registry. No UI element
exists for a plugin that isn't there — verified end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User hit RuntimeError on first launch: 'openssl is required' even
though Git for Windows was installed (bundles openssl, just not on
PATH). Now:

1. _find_openssl() searches PATH first, then Git-for-Windows common
   locations (Program Files\Git\usr\bin, mingw64\bin), Chocolatey bin,
   and C:\OpenSSL-Win64. Anyone who cloned via Git has openssl.

2. _generate_cert_powershell() added as Windows-without-openssl fallback
   using New-SelfSignedCertificate. The PFX-to-PEM extraction is a
   dangling TODO — noted with a clear RuntimeError that directs the
   user to install Git or OpenSSL instead. Easiest win given the
   PKCS12-decode complexity without the cryptography package.

3. ANTENNA_NO_TLS=1 escape hatch in config.tls_enabled() + agent.serve():
   - Skips cert generation entirely on bootstrap
   - Serves plain HTTP on the configured port instead of wrapping TLS
   - Startup banner shows scheme + reminds user it's plain HTTP
   - Bearer token auth still required — LAN + token alone is 95% safe
   - Clear "install openssl to re-enable TLS" hint in banner

4. Flushed the 'Ready' banner so buffered stdout in subprocess launches
   displays the full startup header immediately instead of waiting for
   the first request (saw this in earlier testing on Windows).

The user can now run ANTENNA_NO_TLS=1 python -m antenna.agent on the
ComfyUI box and the agent starts immediately. Upgrade to TLS later by
installing openssl + re-running without the env var.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enables zero-touch remote iteration: dev pushes to main, curls
/self-update with the bearer token, the remote agent pulls the new
code and restarts within ~2 seconds. The operator never has to touch
the remote terminal.

Endpoint flow:
  1. git fetch + reset --hard origin/main   (if source is a git repo)
  2. Compile-check every antenna/**/*.py    (py_compile each file)
  3. On syntax error:
       - git reset back to the pre-update SHA
       - return 500 with the error — agent stays alive on old code
  4. On success:
       - record pre-update SHA to ~/.spellcaster/antenna_last_sha
       - return 202 with {updated_from, updated_to}
       - schedule a detached successor process (python -m antenna.agent)
         after a 500ms delay so the HTTP response flushes first
       - os._exit(0) after the successor has had 800ms to rebind :7334

Rollback mode ({"rollback": true} in body):
  - Reads antenna_last_sha
  - git reset --hard to it
  - Re-validates + restarts
  - Returns 202 {rolled_back_to: <sha>}
  - 409 if no last_sha exists (nothing to roll back to)

Safety layers:
  - Bearer-token gated (audit-logged)
  - Syntax validation BEFORE restart — bad push never bricks remote
  - Never touches ~/.spellcaster/ (token, cert, audit log, last_sha
    all survive the git reset)
  - Timeouts on every subprocess call (no hung git fetches)
  - Detached successor on Windows (CREATE_NEW_PROCESS_GROUP +
    DETACHED_PROCESS) so parent exit doesn't kill child

From here on, every installer/antenna fix can reach the remote box
via one curl to /self-update.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_build_routes() was swallowing all ImportErrors silently, making it
impossible to debug why /self-update wasn't appearing. Now:

  - ImportError → WARN line to stderr with the error
  - Any other exception → also logged (catches SyntaxError in
    dynamically-imported modules which isn't an ImportError)
  - Success → 'registered: POST /self-update'
  - At end of _build_routes: prints full route table

Safe for production — no behaviour change, just better observability
on startup. Operators can now tell from the first 20 lines of stdout
whether an endpoint module loaded or failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First real auto-deploy round-trip confirmed:
  push a923fa4 → curl /self-update → successor process started → old
  agent exited → new agent (uptime 3s) serving requests on the same port.

No more manual restarts needed on the remote box.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dge)

Remote antennas now advertise each declared service as its own
interface chip in the Guild sidebar. A beefy GPU box running both
ComfyUI and Ollama emits two heartbeats per cycle:
  interface=antenna.comfyui, meta={machine, ip, agent_url, vram_*}
  interface=antenna.ollama,  meta={machine, ip, agent_url, reachable}

The antenna.* namespace keeps remote services distinct from local
ones of the same name (local GIMP and remote GIMP coexist without
collision). Meta includes machine hostname + LAN IP + agent_url so
cross-app events (davinci.asset.send, gimp.asset.open) can route to
the right box.

antenna/heartbeat.py
  - _HeartbeatThread: daemon thread, 10s interval, wake-on-stop event
  - _build_payloads: one payload per service, adds service-specific
    vitals (ComfyUI VRAM probe)
  - _send_one: POST with 5s timeout, silent on transport errors
  - Consecutive-failure tracking: log first failure + every 30th
    (every 5 min at 10s interval) instead of spamming stderr
  - start()/stop() idempotent singleton

antenna/agent.py
  - Import heartbeat module
  - serve() calls heartbeat.start(cfg) after server banner
  - No-op when cfg["hub_url"] is empty (local-only agent)

The protocol contract (POST {hub}/api/interfaces/heartbeat, body
{interface, meta}) matches the Mega Bridge Round 1 spec. If that
evolves, only _build_payloads() needs updating.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…to main()

Closes the installer loop for multi-machine setups. After the regular
install finishes, the installer asks per service (ComfyUI, Kobold,
Ollama, SillyTavern, GIMP, Darktable, Resolve) whether it's local,
remote, or skipped. Each remote IP collected gets one tailored
.bat + .sh with:

  - Embedded unique bearer token (secrets.token_urlsafe 256-bit)
  - Embedded hub_url pointing at THIS machine (installer LAN IP:7777)
  - Embedded services array the remote antenna should advertise
  - git clone bootstrap on first launch (replaces the old
    /api/antenna/bundle.zip approach — simpler, no Guild endpoint
    dependency, works today since all boxes cloning via git already
    have git)
  - git pull on subsequent launches so the antenna picks up updates
    even when /self-update isn't reachable
  - Prominent bootstrap banner on first launch printing token +
    fingerprint so the operator can paste back if needed

Install.py integration:
  - Only runs in interactive mode (skipped on --yes / --dry-run)
  - Any exception in the remote step logs a warning but never crashes
    the install (user's local setup is done before we ask about remotes)
  - Output dir: <install>/../antennas/ — sits next to ComfyUI so the
    user can see it in the same directory they set during install
  - Prints generated filenames + README.txt pointer

The generated scripts no longer depend on /api/antenna/bundle.zip —
one less hub endpoint to build. Each remote box pulls directly from
GitHub via git. The hub URL is still used for heartbeats.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…estarts

When the operator edits ~/.spellcaster/antenna_config.json manually
(e.g. setting hub_url after bootstrap), they need a way to restart the
agent to pick up the new config without pushing a code change. Now
POST /self-update with {"force": true} does exactly that — no code
pull needed, just schedules a restart.

No-force behavior unchanged: already-up-to-date returns 200 with a
helpful hint about the force flag.
Operators couldn't tell from outside whether the antenna was actually
trying to heartbeat or silently skipping. Now /status includes:

  'heartbeat': {
    'enabled': true,
    'hub_url': 'http://...',
    'consecutive_failures': 3,
    'last_cycle_ok': false,
    'interval_seconds': 10
  }

So a client curling /status can distinguish:
  - enabled=false → antenna wasn't given a hub_url
  - enabled=true, last_cycle_ok=true → heartbeats are flowing
  - enabled=true, last_cycle_ok=false → antenna is trying but hub
    is unreachable (firewall, down, wrong URL, etc.)

Useful right now: the user's Guild hit a startup error and isn't
accepting connections. With this, we can confirm the antenna side
is healthy while debugging the hub side independently.
load_config() drops any key not in DEFAULT_CONFIG as a typo guard.
hub_url wasn't listed, so every time the agent restarted, it silently
stripped the user's hub_url out of their config file.

Symptom: heartbeat thread always showed {enabled: false, hub_url: null}
on /status despite the user having manually added hub_url to their
antenna_config.json.

Now hub_url is a first-class config key with empty-string default
(= heartbeats disabled, agent still serves /status locally).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HANDOVER_*.md files at repo root contain real LAN IPs + local paths
(intended for Claude-to-Claude orchestration between sessions, never
for the public repo). Per CLAUDE.md rule 11.

Also deleted 7 throwaway Playwright screenshots that were cluttering
the repo root from prior automated testing runs.
Rounds 37-43 (all fully shipped, 384 tests pass):
- R37: prompt character count + limit warning
- R38: auto-scroll to rendering shot, data-shot-id attr
- R39: queue ETA from historical render-time avg
- R40: shot diff indicator — prompt/preset/overrides vs last render
- R41: revert shot to last-rendered settings (per-shot + audit)
- R42: side-by-side comparison view for changed fields
- R43: negative prompt in diff/revert, batch revert for selected shots

Round 44 (PARTIAL — backend + server only, no frontend, no tests yet):
- Shotboard.batch_prompt_edit(shot_ids, prefix, suffix, mode)
  Idempotent add/remove of common prefix/suffix across selected
  prompts. Skips locked shots. Returns {modified, skipped}.
- POST /api/video/batch-prompt-edit with 400-validation on missing
  prefix+suffix, invalid mode, empty shot_ids.

R44 frontend (batch UI + keyboard nav) and tests land in the next
commit per the 2-features-per-round-commit contract — this partial
is safe because the endpoint is auth-gated and the method is
idempotent with no cross-shot side effects.

Verified backward-compat (audit 2026-04-18):
- GIMP plugin standalone works without Guild
- Guild works without antennas or bridge wiring
- test_video_layer.py exercises only shotboard + /api/video/*

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Every successful ComfyUI download now also uploads to the Wizard
Guild's shared asset gallery via CrossInterfaceClient.upload_asset(),
firing a gimp.asset.uploaded bus event. That lights the image up in
the Guild's Recent-across-apps sidebar strip, so generations made in
GIMP are visible to users in the Guild chat UI without re-upload.

Implementation:
- _maybe_publish_to_guild_gallery(data, filename) — best-effort
  helper with silent-fail semantics. Hooked into _download_image at
  both the cache-hit and cache-miss branches.
- Size gate: skip <100 bytes (sentinel error images) and >32 MB
  (probably video frames — gallery is image-only).
- Extension gate: only .png/.jpg/.jpeg/.webp uploaded; skip video
  and anim formats to avoid flooding.
- Zero breakage when Guild is offline — lazy CrossInterfaceClient
  returns None when unreachable; try/except wraps the whole path.

Audit-confirmed backward-compat: GIMP plugin standalone operation
(no Guild running) unaffected. All errors swallowed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Thread-safe bounded deque (100 msgs, 5-min TTL) exposing:
  - Mailbox class with deliver() / peek(consume=..., since_id=...) / ack_ids()
  - get_mailbox(iface_key) singleton registry
  - fanout_from_event(event) — origin-aware router that drops
    <iface>.<rest> events into the matching mailbox, skipping echo

Synced to canonical (comfyui-spellcaster/) and dev copy
(plugins/gimp/comfyui-connector/). Sibling repos
(ComfyUI-Spellcaster[-NSFW]) get this in their own commits.

Not yet wired into tavern/server.py — the route table / handler
methods the Mega Bridge handover describes were never saved to disk
by the originating session. This primitive is ready for whoever
picks up that work. Zero runtime impact until wired: audit confirmed
no file imports mailbox outside its own docstring example.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- spellcaster_api.py: queue_shot() added with render_shot back-compat
  alias; list_presets() normalises 4 different server response shapes
- media_pool_sync.py: accept both new 'shot-update' SSE shape AND
  legacy 'shot.ready' / 'shot.updated' events
- scripts/generate_from_playhead.py + smart_fill_gap.py: updated
  against the current /api/video/* contract

Pure drift fixes. Independent of the mailbox / cross-iface work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5f — ship antenna/endpoints/comfyui.py as Phase-2 stubs
  Previously every antenna startup logged
    [antenna] comfyui service declared but endpoints not yet built
  because agent.py unconditionally imports this module. Stubs expose
  install_node() and install_model() returning 501 not_yet_implemented
  with a clear hint pointing operators at the CLI installer for now.
  Future request/response shapes documented in docstrings so client
  code written today pins the right contract.

5g — README endpoint table split Phase 1 (live) vs Phase 2 (stub)
  Was misleading operators into curl-ing /install-node and seeing it
  "work" (it was previously a 404 when the agent had the ImportError
  silently swallowed). Now the table explicitly flags Phase 1 vs Phase
  2 status, cross-links to the endpoint stubs.

Both fixes from the backward-compat audit 2026-04-18. Zero behavior
change for existing Phase 1 endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GitHub's dependency graph only tracks PyPI packages, but Spellcaster's real
AI-side dependencies are ComfyUI custom-node packs (24 of them, tracked in
installer/manifest.json). This adds:

- DEPENDENCIES.md  — human-readable table grouped into required (20) vs
  optional (4) packs, with repo links, the features each unlocks, and notes.
- scripts/generate_dependencies_md.py — regenerator that keeps DEPENDENCIES.md
  in sync with installer/manifest.json as the single source of truth.
- README: extra status badges (stars, issues, last-commit, downloads, dep
  count) and a Dependencies link in the nav row.

Part of the GitHub discoverability pass across the Spellcaster ecosystem.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Auto-surfaces which Spellcaster-compatible apps are present on the
machine where the antenna runs, without needing to manually edit
antenna_config.json's services array. Callers get an inventory keyed
by service_key with installed/evidence/signals details.

Three detection signals, combined (ANY hit = installed=True):

1. Filesystem — walks drive letters (Windows) / $HOME, /Applications,
   /opt, /usr (POSIX) looking for declared detect_paths. Hits return
   the full resolved path as evidence.
2. Process   — tasklist/ps scan for detect_process substring match.
3. Network   — HTTP GET on http://127.0.0.1:<default_port><probe_path>
   with 1.5s timeout (only for services with default_port > 0).

Evidence priority: network > process > filesystem (stronger signals
win). Parallelized via threads with a 3-second batch cap so /status
stays snappy. 30-second result cache so consecutive /status calls
don't re-probe.

Surface:
- `antenna/detect.py:detect_installed_services(services)` — pure
  function, returns {key: evidence}. No side effects.
- `antenna/detect.py:invalidate_cache()` — force re-probe (called by
  self-update after code refresh).
- `antenna/endpoints/status.py` — /status response now includes
  `services_detected` alongside `services_declared`. Operators can
  see "hey, Darktable's installed here but I didn't enable it on
  the antenna — maybe add it".

Stdlib-only. Silent on every error path — detection must never 500
the /status endpoint. All exceptions caught and surfaced as
"evidence: detect failed: <msg>".

Live-tested on this machine: 7 services inventoried correctly —
GIMP/Darktable via filesystem, Ollama via network+process, others
correctly reported absent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot @github
Copy link
Copy Markdown
Author

dependabot Bot commented on behalf of github Apr 19, 2026

Labels

The following labels could not be found: ci, dependencies. Please create them before Dependabot can add them to a pull request.

Please fix the above issues or remove invalid values from dependabot.yml.

laboratoiresonore pushed a commit that referenced this pull request May 1, 2026
Closes the loop on the eval doc's "highest-leverage product
improvement pending" (`_dev_docs/EVAL_LANGGRAPH_COMFYSCRIPT.md` §6
Implement #2). Two distinct wins shipped together:

  1. **Kill the /history poll race.** Tight workflows (<2s on warm
     cache) routinely complete between the poll loop's 500 ms
     ticks; the client either misses the result or sees a stale
     empty entry. With ws, ComfyUI pushes an `executing` message
     with `node==None` the instant the prompt graph finishes.
  2. **Eliminate filesystem round-trip on output.** Pre-Phase-9
     output went `SaveImage -> output/foo.png -> /view?filename=foo.png`.
     With `SaveImageWebsocket` (ComfyUI core) /
     `ETN_SendImageWebSocket` (Acly's pack), image bytes arrive as
     binary ws frames on the same socket as the status messages. No
     file ever lands in `output/`. Privacy improvement + ~50-200 ms
     saved per image.

Pair with `ETN_LoadImageBase64` (Acly's pack) for the input side —
embeds input images as base64 inside the prompt JSON, eliminating
`POST /upload/image` + `GET /view` for input-side round-trip too.

Wire format (binary frames) -- per ComfyUI server.py:
    header = struct.pack(">II", event_type, image_format)
    frame  = header + image_bytes
event_type=1 is preview/output image; image_format is 1=jpg, 2=png,
3=jpeg legacy, 4=webp.

What landed
-----------

`comfyui-spellcaster/spellcaster_core/comfy_ws.py` (NEW, 478 LOC)
  * `WSImageFrame` dataclass for binary frames (event/format/bytes)
  * `WSDispatchResult` for the full collection from one prompt
  * `WSError` hierarchy: `WSUnreachable`, `WSTimeout`,
    `WSExecutionError`, `WSDependencyMissing`
  * `_build_ws_url`, `_decode_binary_frame`,
    `_collect_outputs_from_executed`, `_format_execution_error`
    helpers
  * `submit_and_listen()` — single entry point. Connects
    `/ws?clientId=<uuid>` BEFORE posting `/prompt` (race-free),
    then listens until the canonical done signal. Filters
    messages by `prompt_id` so other clients' broadcasts don't
    leak into the result.
  * Lazy import of `websockets.sync.client` so the module loads
    even if the package is missing (only fails at submit time
    with a clear message).

`comfyui-spellcaster/spellcaster_core/dispatch.py` (modified)
  * `DispatchResult` gains `binary_outputs` (list of
    (format_name, bytes) tuples) and `transport` ("poll" |
    "websocket"). Backward-compat: defaults are empty list and
    "poll" so existing callers work unchanged.
  * `dispatch_workflow()` gains `use_websocket: bool = False`
    (opt-in for Phase 9) and `ws_fallback_to_poll: bool = True`
    (graceful degradation on ws failure: imports missing,
    connection refused, mid-listen drop).
  * Branch path replaces the submit + poll block with
    `submit_and_listen()` when ws is enabled. Same DispatchResult
    shape on the way out.
  * Privacy cleanup pass still runs against any FILE outputs
    produced in the same workflow (mixed-mode supported).
  * Same `extract_execution_error` / `has_usable_outputs` spirit:
    if execution_error fires AND outputs exist, warn + return
    partial; if execution_error fires AND no outputs, raise.

`comfyui-spellcaster/spellcaster_core/node_factory.py` (modified)
  Three new methods on NodeFactory, mirroring the existing
  load_image / save_image pattern:
  * `etn_load_image_base64(image_b64)` — input via base64
    (Acly's ETN_LoadImageBase64 class, GPL-3 sibling pack)
  * `etn_send_image_websocket(images_ref, format="PNG")` —
    output via ws binary frame (Acly's ETN_SendImageWebSocket)
  * `save_image_websocket(images_ref)` — output via ws binary
    frame (ComfyUI core SaveImageWebsocket; no sibling pack
    needed, always PNG). Documented as the lic-clean alternative
    when the GPL-3 sibling dep is undesirable.

`tests/test_phase9_ws.py` (NEW, 28 tests)
  Mocks `websockets.sync.client.connect` so no real ComfyUI
  server is needed. Coverage:
  * URL building (http->ws, https->wss, trailing slash, no scheme)
  * Binary frame decoding (png, jpg, too-short guard,
    unknown-event guard)
  * `_collect_outputs_from_executed` (images + gifs, empty)
  * `submit_and_listen` happy path (text + binary mixed)
  * `submit_and_listen` execution_error
  * `submit_and_listen` execution_interrupted
  * `submit_and_listen` filters other clients' prompt_ids
  * `submit_and_listen` passes client_id in /prompt body AND
    matching ws URL
  * `submit_and_listen` post unreachable / http error paths
  * `submit_and_listen` progress callback fires for each stage
  * `dispatch_workflow(use_websocket=True)` happy + execution_error
    + partial-success + interrupted
  * `dispatch_workflow(use_websocket=True)` ws-failure fallback to
    poll path
  * `dispatch_workflow(use_websocket=True, ws_fallback_to_poll=False)`
    ws-failure raises hard
  * `dispatch_workflow()` poll path UNCHANGED — same result shape
    minus the new fields' defaults
  * NodeFactory ETN methods emit correct class_types

`.gitignore` (modified)
  Add `tests/test_phase9_ws.py` to the carve-out whitelist
  (matches the convention for canonical shared harnesses; tests/*
  is gitignored by default).

Verification
------------

`python tests/test_phase9_ws.py` -> 28/28 passed.

Sibling test sweep -> all unchanged from baseline. The only
failures (test_quality_boost: 3/54, test_video_layer: ImportError)
reproduce on the pre-Phase-9 tree, verified via `git stash` +
retest. Not caused by this change.

Adoption
--------

Opt-in per call:
    dispatch_workflow(server, workflow,
                      use_websocket=True,    # turn on ws path
                      ws_fallback_to_poll=True)  # graceful degrade

For full inline-transport (no filesystem):
    nf = NodeFactory()
    img_id = nf.etn_load_image_base64(b64_input)  # input-side
    # ... pipeline nodes ...
    nf.save_image_websocket([decode_id, 0])       # output-side
    workflow = nf.build()
    result = dispatch_workflow(server, workflow,
                                use_websocket=True)
    # result.binary_outputs == [("png", <image_bytes>)]
    # result.outputs == []  (no file landed)

Default behavior is unchanged (use_websocket=False); existing
build_* / dispatch callers keep the historical poll path until
they opt in. Per the eval doc's "ship the lower-risk transport
upgrade first" guidance, this lands without any caller changes.

Refs:
  _dev_docs/EVAL_LANGGRAPH_COMFYSCRIPT.md §6 Implement #2 + §7
  _dev_docs/ARCHITECTURAL_STUDY_2026-04-30.md sprint-1 #3 +
  research-doc PARTIAL items

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
laboratoiresonore added a commit that referenced this pull request May 2, 2026
Bug: The Photobooth did a ReActor self-swap (same image as input AND
source), so all 3 "variants" were identical except for face restore
intensity. No actual visual variation.

Fix: Now uses txt2img to generate 3 genuinely different headshot bases
with different lighting and angles, THEN swaps the user's face onto
each. Same approach as Body Factory.

3 headshot prompts for variety:
  #1: Soft studio lighting from left, neutral grey background
  #2: Bright even lighting, white background, slight smile
  #3: Dramatic Rembrandt lighting, dark background, confident

Each gets quality-boosted prompts (skin realism tokens) and a unique
random seed. After txt2img, ReActor High preset (ReSwapper 256 +
GPEN-2048) swaps the reference face onto each base.

Added model selector dropdown to the dialog (SDXL/Flux/etc.).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dependabot @github
Copy link
Copy Markdown
Author

dependabot Bot commented on behalf of github May 2, 2026

OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting @dependabot ignore this major version or @dependabot ignore this minor version. You can also ignore all major, minor, or patch releases for a dependency by adding an ignore condition with the desired update_types to your config file.

If you change your mind, just re-open this PR and I'll resolve any conflicts on it.

laboratoiresonore added a commit that referenced this pull request May 2, 2026
Replaced the txt2img + ReActor face swap approach with Klein Flux 2
iterative img2img. This preserves the ACTUAL face throughout — no
face swap means no identity loss or artifacts.

Klein pipeline (denoise 0.35):
  1. Load user's photo as Flux2ReferenceLatent
  2. Klein img2img with portrait prompt → replaces background,
     enhances lighting, cleans up while preserving face identity
  3. 3 variants with different backgrounds:
     #1: Neutral grey studio, soft even lighting
     #2: White studio, bright soft lighting
     #3: Dark studio, dramatic portrait lighting

Why Klein beats txt2img + face swap:
  - Face is REAL throughout — no swap = no uncanny valley
  - Klein's low denoise preserves identity perfectly at 0.35
  - Background replacement is Klein's strongest use case
  - One ComfyUI call per variant instead of two
  - No dependency on ReActor for the core pipeline

Removed the generation model selector (no longer needed — Klein
uses its own Flux 2 model directly).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dependabot dependabot Bot deleted the dependabot/github_actions/actions/upload-artifact-7 branch May 2, 2026 02:54
laboratoiresonore added a commit that referenced this pull request May 2, 2026
Closes the loop on the eval doc's "highest-leverage product
improvement pending" (`_dev_docs/EVAL_LANGGRAPH_COMFYSCRIPT.md` §6
Implement #2). Two distinct wins shipped together:

  1. **Kill the /history poll race.** Tight workflows (<2s on warm
     cache) routinely complete between the poll loop's 500 ms
     ticks; the client either misses the result or sees a stale
     empty entry. With ws, ComfyUI pushes an `executing` message
     with `node==None` the instant the prompt graph finishes.
  2. **Eliminate filesystem round-trip on output.** Pre-Phase-9
     output went `SaveImage -> output/foo.png -> /view?filename=foo.png`.
     With `SaveImageWebsocket` (ComfyUI core) /
     `ETN_SendImageWebSocket` (Acly's pack), image bytes arrive as
     binary ws frames on the same socket as the status messages. No
     file ever lands in `output/`. Privacy improvement + ~50-200 ms
     saved per image.

Pair with `ETN_LoadImageBase64` (Acly's pack) for the input side —
embeds input images as base64 inside the prompt JSON, eliminating
`POST /upload/image` + `GET /view` for input-side round-trip too.

Wire format (binary frames) -- per ComfyUI server.py:
    header = struct.pack(">II", event_type, image_format)
    frame  = header + image_bytes
event_type=1 is preview/output image; image_format is 1=jpg, 2=png,
3=jpeg legacy, 4=webp.

What landed
-----------

`comfyui-spellcaster/spellcaster_core/comfy_ws.py` (NEW, 478 LOC)
  * `WSImageFrame` dataclass for binary frames (event/format/bytes)
  * `WSDispatchResult` for the full collection from one prompt
  * `WSError` hierarchy: `WSUnreachable`, `WSTimeout`,
    `WSExecutionError`, `WSDependencyMissing`
  * `_build_ws_url`, `_decode_binary_frame`,
    `_collect_outputs_from_executed`, `_format_execution_error`
    helpers
  * `submit_and_listen()` — single entry point. Connects
    `/ws?clientId=<uuid>` BEFORE posting `/prompt` (race-free),
    then listens until the canonical done signal. Filters
    messages by `prompt_id` so other clients' broadcasts don't
    leak into the result.
  * Lazy import of `websockets.sync.client` so the module loads
    even if the package is missing (only fails at submit time
    with a clear message).

`comfyui-spellcaster/spellcaster_core/dispatch.py` (modified)
  * `DispatchResult` gains `binary_outputs` (list of
    (format_name, bytes) tuples) and `transport` ("poll" |
    "websocket"). Backward-compat: defaults are empty list and
    "poll" so existing callers work unchanged.
  * `dispatch_workflow()` gains `use_websocket: bool = False`
    (opt-in for Phase 9) and `ws_fallback_to_poll: bool = True`
    (graceful degradation on ws failure: imports missing,
    connection refused, mid-listen drop).
  * Branch path replaces the submit + poll block with
    `submit_and_listen()` when ws is enabled. Same DispatchResult
    shape on the way out.
  * Privacy cleanup pass still runs against any FILE outputs
    produced in the same workflow (mixed-mode supported).
  * Same `extract_execution_error` / `has_usable_outputs` spirit:
    if execution_error fires AND outputs exist, warn + return
    partial; if execution_error fires AND no outputs, raise.

`comfyui-spellcaster/spellcaster_core/node_factory.py` (modified)
  Three new methods on NodeFactory, mirroring the existing
  load_image / save_image pattern:
  * `etn_load_image_base64(image_b64)` — input via base64
    (Acly's ETN_LoadImageBase64 class, GPL-3 sibling pack)
  * `etn_send_image_websocket(images_ref, format="PNG")` —
    output via ws binary frame (Acly's ETN_SendImageWebSocket)
  * `save_image_websocket(images_ref)` — output via ws binary
    frame (ComfyUI core SaveImageWebsocket; no sibling pack
    needed, always PNG). Documented as the lic-clean alternative
    when the GPL-3 sibling dep is undesirable.

`tests/test_phase9_ws.py` (NEW, 28 tests)
  Mocks `websockets.sync.client.connect` so no real ComfyUI
  server is needed. Coverage:
  * URL building (http->ws, https->wss, trailing slash, no scheme)
  * Binary frame decoding (png, jpg, too-short guard,
    unknown-event guard)
  * `_collect_outputs_from_executed` (images + gifs, empty)
  * `submit_and_listen` happy path (text + binary mixed)
  * `submit_and_listen` execution_error
  * `submit_and_listen` execution_interrupted
  * `submit_and_listen` filters other clients' prompt_ids
  * `submit_and_listen` passes client_id in /prompt body AND
    matching ws URL
  * `submit_and_listen` post unreachable / http error paths
  * `submit_and_listen` progress callback fires for each stage
  * `dispatch_workflow(use_websocket=True)` happy + execution_error
    + partial-success + interrupted
  * `dispatch_workflow(use_websocket=True)` ws-failure fallback to
    poll path
  * `dispatch_workflow(use_websocket=True, ws_fallback_to_poll=False)`
    ws-failure raises hard
  * `dispatch_workflow()` poll path UNCHANGED — same result shape
    minus the new fields' defaults
  * NodeFactory ETN methods emit correct class_types

`.gitignore` (modified)
  Add `tests/test_phase9_ws.py` to the carve-out whitelist
  (matches the convention for canonical shared harnesses; tests/*
  is gitignored by default).

Verification
------------

`python tests/test_phase9_ws.py` -> 28/28 passed.

Sibling test sweep -> all unchanged from baseline. The only
failures (test_quality_boost: 3/54, test_video_layer: ImportError)
reproduce on the pre-Phase-9 tree, verified via `git stash` +
retest. Not caused by this change.

Adoption
--------

Opt-in per call:
    dispatch_workflow(server, workflow,
                      use_websocket=True,    # turn on ws path
                      ws_fallback_to_poll=True)  # graceful degrade

For full inline-transport (no filesystem):
    nf = NodeFactory()
    img_id = nf.etn_load_image_base64(b64_input)  # input-side
    # ... pipeline nodes ...
    nf.save_image_websocket([decode_id, 0])       # output-side
    workflow = nf.build()
    result = dispatch_workflow(server, workflow,
                                use_websocket=True)
    # result.binary_outputs == [("png", <image_bytes>)]
    # result.outputs == []  (no file landed)

Default behavior is unchanged (use_websocket=False); existing
build_* / dispatch callers keep the historical poll path until
they opt in. Per the eval doc's "ship the lower-risk transport
upgrade first" guidance, this lands without any caller changes.

Refs:
  _dev_docs/EVAL_LANGGRAPH_COMFYSCRIPT.md §6 Implement #2 + §7
  _dev_docs/ARCHITECTURAL_STUDY_2026-04-30.md sprint-1 #3 +
  research-doc PARTIAL items

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant