Files
DumiFlex-ComfyUI-Wildcard-P…/engine/context.py
T
Claude add6506225 feat(derivation): run several actions per branch and read negatives with $var.neg
A derivation branch (and its ELSE) now runs its action and then each
entry of an optional extra_actions list, in order; each action sees the
previous one's write. The library editor gets "+ And" rows, the canvas
modal shows every action and gives each its own value override (keyed
"bi.K" / "else.K"), and WP Debug lists every action a branch ran.

$name.neg / $name.K.neg reads the negatives a variable carries. It rides
the accessor grammar's axis slot, so "neg" is reserved as an accepts
group name. Reading .neg is not a read of the variable, so it carries no
negatives along. Works in values, combines, the Assembler's negative
template and conditions; autocomplete offers it after the dot.

Both stamp schema v8. Cascade fixers and scans now walk the real
derivation action shape (action, extra_actions, else) instead of a stale
"actions" list, and read-only previews keep a var chip's accessor.
2026-09-30 15:33:01 +00:00

93 lines
3.7 KiB
Python

"""Context type definitions and internals helpers.
The engine uses a flat ``dict[str, Any]`` for context. Keys starting with ``__``
are reserved for engine internals (seed, trace, internal flags). User-defined
variables use plain identifier names.
"""
from __future__ import annotations
from typing import Any, TypedDict
class ContextInternals(TypedDict, total=False):
"""Documents the reserved internal keys. Not enforced at runtime."""
__wp_node_seed__: int
__wp_internal_flags__: dict[str, bool]
__wp_trace__: list[dict[str, Any]]
Context = dict[str, Any]
#: Engine tables the text resolver reads by KEY rather than as variables.
#: `strip_internals` still drops them — its result is the variable surface, and
#: anything enumerating it (the assembler's variables panel, debug views) would
#: list `$__wp_axes__` as a user variable. Callers that need accessor syntax
#: re-attach them to their own local dict via `with_resolver_tables`.
RESOLVER_TABLES = ("__wp_axes__", "__wp_picks__", "__wp_negatives__")
def with_resolver_tables(
stripped: dict[str, Any], source: dict[str, Any]
) -> dict[str, Any]:
"""Re-attach the accessor tables to an already-stripped ctx.
`$var.0` and `$var.AXIS` resolve against `__wp_picks__` / `__wp_axes__`,
which `strip_internals` removes along with every other `__` key. Without
them the assembler rendered both accessors as literal text while the same
template resolved one node upstream. The returned dict is for a resolver
call only — never for anything that enumerates variables.
"""
out = dict(stripped)
for key in RESOLVER_TABLES:
if key in source:
out[key] = source[key]
return out
def strip_internals(ctx: dict[str, Any]) -> dict[str, Any]:
"""Return a copy of ``ctx`` with engine-only + user-flagged-internal keys removed.
Use at the *prompt render* boundary (PromptAssembler) — both engine
bookkeeping AND user-marked-internal vars are filtered:
1. ``__``-prefixed keys: engine bookkeeping (``__wp_node_seed__``,
``__wp_trace__``, ``__wp_internal_flags__``, …).
2. Keys whose name appears with ``True`` in the
``__wp_internal_flags__`` map: user-marked-internal bindings
from modules with ``instance.internal == True``. These vars
drive composition (other modules can read them) but must not
appear as standalone ``$var`` substitutions in the rendered
prompt — that's the "internal" UX promise.
For the *socket* boundary (PIPELINE_CONTEXT carrying state between
Context nodes), use ``strip_engine_internals`` instead — it drops
only the ``__``-prefixed engine keys so user-flagged internal vars
continue to propagate through downstream Context / Combine / etc.
until they reach a PromptAssembler that filters them at render
time.
"""
flags = ctx.get("__wp_internal_flags__")
internal_names: set[str] = set()
if isinstance(flags, dict):
internal_names = {k for k, v in flags.items() if v}
return {
k: v for k, v in ctx.items()
if not k.startswith("__") and k not in internal_names
}
def strip_engine_internals(ctx: dict[str, Any]) -> dict[str, Any]:
"""Return a copy of ``ctx`` with only the ``__``-prefixed engine keys removed.
User-flagged internal vars stay in the result — they need to
propagate across Context-node boundaries so downstream Combine /
Derivation / Constraint modules can read them. Only the final
PromptAssembler filters them out at render time (via
``strip_internals``) so they don't appear as standalone ``$var``
substitutions in the prompt text.
"""
return {k: v for k, v in ctx.items() if not k.startswith("__")}