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

R6 and R9 for nodes whose sources are installed beside ComfyUI, each
entry's input names and output slots read from the node's code (the task
report lists file and line):

- detailer stages UltimateSDUpscale (image), UltimateSDUpscaleNoUpscale
  (upscaled_image) and DetailerForEach (image), judged with their upstream
  samplers like FaceDetailer;
- pass-throughs ControlNetInpaintingAliMamaApply and LTXVConditioning
  (positive 0, negative 1) and FluxKontextMultiReferenceLatentMethod;
- encoder Text to Conditioning, strings TextInput and DisplayText;
- facts model pass-throughs Lora Loader (LoraManager), Power Lora Loader
  (rgthree), ModelPatchTorchSettings, CFGNorm, PerturbedAttentionGuidance.

Impact Pack encodes a detailer's non-empty wildcard instead of its positive
input, so FaceDetailer and DetailerForEach now list it as a prompt
override: a literal wildcard is decided at run time and a linked one names
its source, instead of resolving to the positive input. The overwritten
PromptManager refusal (R1) still holds behind the new pass-throughs. The
default profile does not read the table.
This commit is contained in:
Vito Sansevero
2026-10-19 15:00:33 -07:00
parent baf788b863
commit 46aeff92ca
8 changed files with 364 additions and 9 deletions
+6 -1
View File
@@ -54,6 +54,7 @@ SAVER_PATTERNS = MappingProxyType(
"vhs-counter": "{name}_{counter:05}.{ext}",
}
)
_SAMPLER_OPTIONS = frozenset({"prompt_overrides"})
_CONFIGURATION_KINDS = frozenset({"noise", "sampler", "sigmas", "guider"})
_SCALAR_KINDS = frozenset({"number", "string"})
_TYPE_FIELDS = frozenset({"type_fields"})
@@ -177,9 +178,13 @@ def _check_sampler(entry: dict, strategy: str, where: str) -> None:
entry,
where,
frozenset({"strategy", "conditioning", stage, "facts"}),
frozenset(),
_SAMPLER_OPTIONS,
)
_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")
keys = {"guider"} if strategy == "custom-advanced" else {"positive", "negative"}
conditioning = entry["conditioning"]
_fields(conditioning, where + ".conditioning", frozenset(keys), frozenset())
+16
View File
@@ -1241,6 +1241,7 @@ class _GraphResolver:
try:
name, inputs = self.node(sampler_id)
entry = self.sampler_entry(name)
self.check_overrides(sampler_id, entry, inputs)
# A second sampler in the latent ancestry denotes a multistage run;
# associating the final image with one prompt pair would be a guess.
latent = entry.get("latent")
@@ -1284,6 +1285,21 @@ class _GraphResolver:
node=error.node,
)
def check_overrides(self, sampler_id: str, entry: Mapping, inputs: dict) -> None:
"""A set prompt override replaces the conditioning inputs' prompt.
Exact ``""`` (or a missing input) leaves the prompt alone. A literal is
processed at run time (a detailer wildcard); a link is a source the
inspector does not follow, so it is named.
"""
for name in entry.get("prompt_overrides", ()):
value = inputs.get(name)
if value is None or value == "":
continue
if _link(value):
raise self.unsupported("Unsupported prompt override", str(value[0]))
raise self.named(_RUNTIME_REASON, "runtime-value", sampler_id)
def latent_source(self, value: Any, seen: set, depth: int) -> None:
if not _link(value) or depth >= self.max_depth:
code = "malformed" if not _link(value) else "depth-limit"