feat(inspect): port legacy parser rules the new resolver missed (upstream nodes)

R6 and R9 for nodes not installed beside ComfyUI here. Their input names
and output slots come from each pack's source at the commit the saved
workflows record in the node's cnr_id and ver properties (the task report
lists repository, commit, file and line):

- samplers ClownsharKSampler_Beta (RES4LYF), KSampler (Efficient) and
  ttN KSampler_v2 (latent input "latent");
- pass-throughs SeedVarianceEnhancer and ApplyInstantIDAdvanced (positive
  1, negative 2; output 0 is the model);
- selector Switch conditioning [Crystools];
- encoder smZ CLIPTextEncode: its text when with_SDXL is false; when true
  the connected CLIP decides at run time which inputs are read, so the
  prompt is known only when text, text_g and text_l agree;
- strings ttN text, CR Text, CR Prompt Text and TextBoxMira;
- facts model pass-through CR Apply LoRA Stack.

Inputs that replace what a sampler reads are not followed: RES4LYF options
(may carry a guider, and with sigmas replace the settings), an Efficiency
script and a ttN XY plot leave the prompt unsupported and the facts
unresolved. The overwritten PromptManager refusal (R1) still holds behind
SeedVarianceEnhancer. The default profile does not read the table.
This commit is contained in:
Vito Sansevero
2026-10-19 15:00:33 -07:00
parent 46aeff92ca
commit 815ece045b
10 changed files with 421 additions and 16 deletions
+16 -4
View File
@@ -41,7 +41,7 @@ STRATEGIES: Mapping[str, FrozenSet[str]] = MappingProxyType(
"conditioning": frozenset({"passthrough", "zeroed"}),
"selectors": frozenset({"boolean-select"}),
"composites": frozenset({"combine"}),
"encoders": frozenset({"single", "parts-equal", "pm-compose"}),
"encoders": frozenset({"single", "parts-equal", "pm-compose", "flag-parts"}),
"strings": frozenset({"literal", "strip-newlines", "join-multi", "concat"}),
"savers": frozenset({"comfy-counter", "vhs-counter"}),
}
@@ -54,7 +54,7 @@ SAVER_PATTERNS = MappingProxyType(
"vhs-counter": "{name}_{counter:05}.{ext}",
}
)
_SAMPLER_OPTIONS = frozenset({"prompt_overrides"})
_SAMPLER_OPTIONS = frozenset({"prompt_overrides", "fact_overrides"})
_CONFIGURATION_KINDS = frozenset({"noise", "sampler", "sigmas", "guider"})
_SCALAR_KINDS = frozenset({"number", "string"})
_TYPE_FIELDS = frozenset({"type_fields"})
@@ -183,8 +183,10 @@ def _check_sampler(entry: dict, strategy: str, where: str) -> None:
_name(entry[stage], where)
# Inputs that, when set, replace the prompt the sampler's conditioning
# inputs carry (a detailer's wildcard); the prompt is then not read.
if "prompt_overrides" in entry:
_names(entry["prompt_overrides"], where + ".prompt_overrides")
# ``fact_overrides`` (read by comfy-metadata.js): inputs that, when set,
# replace the settings the facts would read (a script or an XY plot).
for option in sorted(_SAMPLER_OPTIONS & entry.keys()):
_names(entry[option], f"{where}.{option}")
keys = {"guider"} if strategy == "custom-advanced" else {"positive", "negative"}
conditioning = entry["conditioning"]
_fields(conditioning, where + ".conditioning", frozenset(keys), frozenset())
@@ -246,6 +248,16 @@ def _check_conditioning(entry: dict, strategy: str, where: str) -> None:
def _check_encoder(entry: dict, strategy: str, where: str) -> None:
if strategy == "flag-parts":
# A literal boolean ``flag`` false reads the first input; true needs
# every input equal (smZ CLIPTextEncode's with_SDXL).
_fields(entry, where, frozenset({"strategy", "inputs", "params"}), frozenset())
params = entry["params"]
if not isinstance(params, dict) or set(params) != {"flag"}:
raise SemanticsError(f"{where}: params must be exactly ['flag']")
_name(params["flag"], where + ".params")
_names(entry["inputs"], where, minimum=2)
return
_fields(entry, where, frozenset({"strategy", "inputs"}), frozenset({"compose"}))
_names(entry["inputs"], where, minimum=2 if strategy == "parts-equal" else 1)
if "compose" in entry and not isinstance(entry["compose"], bool):
+20
View File
@@ -759,6 +759,8 @@ class _GraphResolver:
names = entry["inputs"]
if entry["strategy"] == "single":
return self.text(inputs.get(names[0]), "string", depth + 1)
if entry["strategy"] == "flag-parts":
return self.flagged(node_id, entry, inputs, depth)
if entry["strategy"] != "parts-equal":
raise self.unsupported("Unsupported encoder strategy", node_id)
parts = [self.text(inputs.get(name), "string", depth + 1) for name in names]
@@ -771,6 +773,24 @@ class _GraphResolver:
return self.joined_paths(filled, parts)
raise self.named("Encoder parts have different texts", "composite", node_id)
def flagged(
self, node_id: str, entry: Mapping, inputs: dict, depth: int
) -> Tuple[str, list, str]:
"""smZ CLIPTextEncode: ``text`` unless its SDXL flag may apply.
With the literal flag false the first input is encoded. With it true,
the connected CLIP's model class decides at run time which inputs are
read, so the prompt is known only when every input is the same text.
"""
names = entry["inputs"]
if not self.setting(node_id, inputs, entry["params"]["flag"], bool):
return self.text(inputs.get(names[0]), "string", depth + 1)
parts = [self.text(inputs.get(name), "string", depth + 1) for name in names]
self.scan(sum(len(part[0]) for part in parts))
if len({part[0] for part in parts}) == 1:
return self.joined_paths(parts, parts)
raise self.named(_RUNTIME_REASON, "runtime-value", node_id)
def stringed(
self, node_id: str, entry: Mapping, inputs: dict, depth: int
) -> Tuple[str, list, str]: