Skip to content

fix(cql): expand dynamic-combo sub-inputs in slot extraction and set-slot/vary write path (BE-3371)#574

Open
mattmillerai wants to merge 1 commit into
mainfrom
matt/be-3371-cql-dynamic-combo-slots
Open

fix(cql): expand dynamic-combo sub-inputs in slot extraction and set-slot/vary write path (BE-3371)#574
mattmillerai wants to merge 1 commit into
mainfrom
matt/be-3371-cql-dynamic-combo-slots

Conversation

@mattmillerai

Copy link
Copy Markdown
Collaborator

ELI-5

ComfyUI has "shape-shifter" dropdowns: pick a model in the dropdown and the node grows extra knobs (size, width, height…) that belong to that model. The CLI didn't know these dropdowns existed, so when it counted a node's knobs it skipped the dropdown and its extra knobs — every knob after it got the wrong label, and set-slot turned the wrong knob (setting the seed actually overwrote the model choice, silently). Now the CLI reads which model is picked, counts that model's knobs in their real positions, and when you switch models it swaps the knob set for the new model's defaults and tells you it did.

Problem

On the pristine api_bytedance_seedream_5_0_pro_t2i template (reproduced on origin/main):

  • comfy workflow slots showed 1.seed = 'seedream 5.0 pro', 1.watermark/1.thinking = 2048, and no 1.model / 1.model.* slots at all
  • apply_slots({"1.seed": 42}) wrote index 1 — silently clobbering the model selector — with zero warnings

Root cause: _is_link treated COMFY_DYNAMICCOMBO_V3 as a connection (dropped from widget_order), and widget_order was value-independent while the frontend inlines the selected option's widget sub-values into the flat positional widgets_values right after the selector.

Fix

  • Parsing: a COMFY_*COMBO* type is a widget port. Its enum = the option key strings; the raw option dicts are retained on Port.dynamic_options for expansion. _is_scalar_choice still rejects dict options for ordinary combos.
  • Graph.widget_order_for_node(class_name, widgets_values): value-aware order — at a dynamic combo it reads the selector from its own positional slot and expands the matching option's widget-like sub-inputs as <name>.<sub> (same widget-vs-link test as top level; nested dynamic combos recurse with a depth cap; connection subs like COMFY_AUTOGROW_V3 contribute no slot; control_after_generate markers follow control-flagged inputs, sub-inputs included). widget_order(class_name) is kept for callers without node context.
  • Slots: _node_widget_slots emits N.model (type COMFY_DYNAMICCOMBO_V3, enum = option keys, current = selector) plus N.model.<sub> slots with correct types/enums/current values. Slots with enum choices now carry an additive enum key.
  • Write path (set-slot / vary):
    • N.model.<sub>=v writes the correct position, validated against the sub-spec.
    • N.model=<other key> rebuilds widgets_values for that node: values before the combo kept, new selector written, the new option's widget sub-inputs filled from schema defaults (sub-spec default, first enum option for combos), trailing values (seed/marker/watermark/thinking) re-aligned — and emits a dynamic_combo_roster_rebuilt warning describing the rebuild.
    • N.model.<sub> that doesn't exist under the current selector → unknown_dynamic_sub_input warning, no write, listing the valid addresses and how to switch rosters. This denial is schema-grounded, not assumed: under the pro selector the frontend serializes no max_images slot (the vendored pristine template's 8-value layout confirms it), so the write has no position to land in — the warning redirects to the valid addresses instead of dead-ending.
    • N.model=<unknown key> → hard error listing valid options (an unknown selector has no roster to build; writing it would corrupt the positional alignment of everything after — deliberate deviation from ordinary combos, which warn-and-write).
  • _resolve_proxy_value (curated subgraph proxy reads) migrated to the value-aware order as well.

Judgment calls

  • Shared vs duplicated widget-ness rules: the ticket suggested importing the converter's expansion helper (workflow_to_api). I kept them separate — the converter operates on raw INPUT_TYPES specs, the engine on parsed Ports, and a cross-module import would couple the CQL engine to the converter's internals. The rules are aligned (_is_dynamic_combo_type mirrors _is_widget_input's COMFY_*COMBO* test) and both sides are pinned by tests on the same fixtures.
  • Selector-roster rebuild fills from schema defaults (as the ticket specifies) rather than carrying over same-named sub-values — options can have divergent enums (lite's size_preset list ≠ pro's), so defaults are the safe deterministic choice; the warning tells the agent what was reset.
  • Updated one existing test (test_slot_address_with_dotted_input_name) to monkeypatch widget_order_for_node instead of widget_order — its intent (first-dot address split) is unchanged.

Tests

  • tests/comfy_cli/command/test_workflow_slots.py: pristine-template slots (addresses/types/enums/currents, 1.seed current is 0 not the model name), seed write doesn't clobber the selector, sub-input write hits index 2, selector change to lite rebuilds the roster (+2 slots, max_images/fail_on_partial appear, prompt/seed/watermark preserved, warning emitted, new roster addressable), unknown sub-address warns without mutating, unknown selector rejected, vary over a sub-input, autogrow subs not exposed.
  • tests/comfy_cli/cql/test_engine.py: dynamic combo parses as widget with option-key enum; value-independent vs value-aware order; nested-combo recursion; unknown selector expands nothing; nested + outer selector-change rebuilds.

Verification

  • uv run pytest tests/comfy_cli/command/test_workflow_slots.py tests/comfy_cli/cql/ tests/comfy_cli/test_workflow_to_api.py — 285 passed
  • Full uv run pytest tests/ — 2653 passed, 2 failed: test_validate_command.py::test_api_format_unchanged + ::test_empty_dict_payload_unchanged fail identically on clean origin/main (pre-existing: the BE-3357 prompt_no_outputs hard error now rejects those tests' output-node-less fixtures) — unrelated to this diff
  • ruff check clean on all touched files; ruff format applied (the 15 repo-wide UP038 findings also pre-exist on origin/main under the local ruff)

…slot/vary write path (BE-3371)

COMFY_DYNAMICCOMBO_V3 ports were treated as connections and dropped from
widget_order, so on a pristine Seedream template every slot after the
selector was mislabeled (1.seed showed the model name) and set-slot/vary
wrote into the wrong widgets_values positions (1.seed=42 silently
overwrote the model selector).

- parse dynamic combos as widget ports: enum = option keys, raw option
  dicts retained for expansion
- add value-aware Graph.widget_order_for_node that expands the selected
  option's widget sub-inputs in place (model.size_preset, model.width, …),
  recursing for nested combos and skipping connection subs (AUTOGROW)
- migrate _node_widget_slots, _resolve_proxy_value, and _write_widget to
  the value-aware order; slots now surface N.model + N.model.<sub> with
  types/enums/current values
- set-slot N.model=<other key> rebuilds the sub-widget roster from schema
  defaults, preserving values before/after the combo, with a warning
- an N.model.<sub> address not under the current selector warns (no
  write) and lists the valid addresses

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 61122f60-1a22-463d-a0e6-4ba3ef3aed48

📥 Commits

Reviewing files that changed from the base of the PR and between d4ff53e and a9ba57f.

📒 Files selected for processing (3)
  • comfy_cli/cql/engine.py
  • tests/comfy_cli/command/test_workflow_slots.py
  • tests/comfy_cli/cql/test_engine.py

📝 Walkthrough

Walkthrough

Walkthrough

The engine now parses V3 dynamic-combo metadata, expands widget slots based on selected values, resolves proxy indices accordingly, and supports validated sub-input writes and selector-driven roster rebuilding. Tests cover dynamic-combo workflows, nested options, warnings, and Seedream fixtures.

Changes

Dynamic combo workflow editing

Layer / File(s) Summary
Dynamic-combo schema metadata
comfy_cli/cql/engine.py
Port and input parsing now retain dynamic-combo option dictionaries and classify dynamic-combo ports as widget selectors.
Value-aware widget slots
comfy_cli/cql/engine.py, tests/comfy_cli/cql/test_engine.py
Widget ordering expands selected and nested dynamic-combo inputs, excludes connection-only fields, preserves control markers, and updates proxy resolution.
Dynamic-combo selector editing
comfy_cli/cql/engine.py, tests/comfy_cli/command/test_workflow_slots.py
Sub-input writes, invalid-address warnings, selector validation, roster rebuilding, value alignment, varying, and Seedream workflow fixtures are covered.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Graph
  participant Workflow
  CLI->>Graph: resolve dynamic-combo widget slots
  Graph->>Workflow: read selected widget values
  Workflow-->>Graph: return current selector values
  Graph-->>CLI: return expanded slot addresses
  CLI->>Workflow: write selector or sub-input
  Workflow-->>CLI: return updated values and warnings
Loading
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matt/be-3371-cql-dynamic-combo-slots
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/be-3371-cql-dynamic-combo-slots

Comment @coderabbitai help to get the list of available commands.

@mattmillerai mattmillerai added the agent-coded PR authored by the agent-work loop label Jul 22, 2026
@mattmillerai
mattmillerai marked this pull request as ready for review July 22, 2026 19:13
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jul 22, 2026
@mattmillerai mattmillerai added the cursor-review Request Cursor bot review label Jul 22, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Cursor Review — Consolidated panel

Triggered by @mattmillerai.

⚠️ Review failed

Judge call failed (status=parse_error): Could not parse JSON findings from output. First 500 chars:
I've verified the findings against the actual code. Key confirmations: `_dynamic_combo_sub_ports` returns `[]` on unknown selectors (line 1421); the repo's own `workflow_to_api._has_control_after_generate_companion` handles implicit `seed`/`noise_seed` markers that `engine.py`'s expansion ignores; the `extend` flag is not checked before padding on selector change; and the fixtures use explicit `control_after_generate`.

[
  {
    "file": "comfy_cli/cql/engine.py",
    "line": 1421,
    "side": "

Re-trigger by removing and re-adding the cursor-review label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-coded PR authored by the agent-work loop cursor-review Request Cursor bot review size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants