UNPKG

comfyui-mcp

Version:

Local-first, agent-native control plane for ComfyUI — MCP server + autonomous sidebar agent that drives your live graph in natural language on ANY LLM: Claude/ChatGPT/Gemini on your subscription (no API key), free local models via Ollama (fully offline),

157 lines • 7.39 kB
/** * #2437 — remaining host links after `graph_unexpose_subgraph_input/output`. * * Host SubgraphNode slots are positional and lockstep with `subgraph.inputs` / * `subgraph.outputs`. The panel executor counts host links at the OLD index, * calls `removeInput`/`removeOutput`, and reports. It does not re-point the * survivors. Later host slots then look connected in `panel_query_graph` / * `panel_graph_outline` (same positional lens) and missing at queue time * (`graphToPrompt` resolves the boundary differently). * * A post-removal snapshot of `node.inputs[i].link` cannot see this: it shares * the mutation's dependency and agrees with the reader. This note is a priori * from a landed `removed` object, not a detected divergence — UNLESS the panel * reports `removed.host_links_reindexed: true` (panel ≥0.15.120 / #1969), in * which case survivors were already re-pointed and the note would be a lie * (#2473), OR the removed index was last so no later slot exists to reindex * (#2491). The repair that re-resolves the index is disconnect + reconnect * remaining later host links BY NAME. The reindex itself is panel-side and * must not be improvised here: panel#668 saw a SubgraphNode disconnect * cascade into deleting unrelated nodes. */ function asRecord(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return null; return value; } function removedRecord(payload) { return asRecord(asRecord(payload)?.removed); } function landedSlot(removed) { if (removed.side !== "input" && removed.side !== "output") return null; const slot = removed.slot; return typeof slot === "number" && Number.isInteger(slot) && slot >= 0 ? slot : null; } function nonNegInt(value) { return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null; } function stringArray(value) { if (!Array.isArray(value)) return undefined; const out = []; for (const item of value) { if (typeof item !== "string") return undefined; out.push(item); } return out; } function railSide(removed) { return removed.side === "output" ? "output" : "input"; } /** * Remaining later slot names from a post-removal `rails` snapshot. * After splicing index `removedSlot`, survivors that shifted sit at * `index >= removedSlot`. `undefined` when that rail is not in the snapshot. */ export function laterSlotsFromRails(rails, side, removedSlot) { const rail = asRecord(asRecord(rails)?.[side]); if (!rail) return undefined; const slots = side === "output" ? rail.accepts_inputs : rail.provides_outputs; if (!Array.isArray(slots)) return undefined; const later = []; for (const [i, entry] of slots.entries()) { const rec = asRecord(entry); const index = rec ? (nonNegInt(rec.index) ?? i) : i; if (index < removedSlot) continue; const name = rec && typeof rec.name === "string" && rec.name ? rec.name : `(slot ${index})`; later.push(name); } return later; } /** * Later-slot evidence already on the unexpose payload. `[]` proves the * removed index was last (#2491). `undefined` means the payload did not look. */ export function laterSlotsFromUnexposePayload(payload) { const removed = removedRecord(payload); const slot = removed ? landedSlot(removed) : null; if (removed == null || slot == null) return undefined; const named = stringArray(removed.later_slots) ?? stringArray(removed.remaining_later_slots); if (named !== undefined) return named; const remainingCount = nonNegInt(removed.remaining_count) ?? nonNegInt(removed.remaining_slot_count); if (remainingCount != null && remainingCount <= slot) return []; return laterSlotsFromRails(asRecord(payload)?.rails, railSide(removed), slot); } /** * Combine the unexpose payload with an optional follow-up graph read that * carries `rails`. Payload evidence wins; the live rails fill an UNKNOWN list. */ export function laterSlotsAfterUnexpose(unexposePayload, remainingGraphPayload) { const fromPayload = laterSlotsFromUnexposePayload(unexposePayload); if (fromPayload !== undefined) return fromPayload; const removed = removedRecord(unexposePayload); const slot = removed ? landedSlot(removed) : null; if (removed == null || slot == null) return undefined; return laterSlotsFromRails(asRecord(remainingGraphPayload)?.rails, railSide(removed), slot); } /** * True when the panel reported a landed unexpose (`removed.side` + integer * `removed.slot`). A refusal has no `removed` and produces no note. */ export function isLandedUnexpose(payload) { const removed = removedRecord(payload); return removed != null && landedSlot(removed) != null; } /** True when the panel already re-pointed survivors (#2473 / panel ≥0.15.120). */ export function hostLinksAlreadyReindexed(payload) { return removedRecord(payload)?.host_links_reindexed === true; } /** * The agent-facing repair for a landed unexpose whose remaining later host * links were not reindexed. `null` when there is nothing to disclose: * - not a landed `removed` object (refusal / unparseable); * - panel reported `removed.host_links_reindexed: true` (#2473); * - the removed index was last — no later slot exists to reindex (#2491); * - caller proved there are no later slots (`laterSlotNames` is `[]`). * * An UNKNOWN later list still produces the note: the cheap positional * connectedness check cannot prove the survivors are valid, and skipping the * note on "we did not look" is how the reporter queued a broken graph. */ export function unexposeHostLinkShiftNote(payload, laterSlotNames) { const removed = removedRecord(payload); const slot = removed ? landedSlot(removed) : null; if (removed == null || slot == null) return null; if (removed.host_links_reindexed === true) return null; const later = laterSlotNames !== undefined ? laterSlotNames : laterSlotsFromUnexposePayload(payload); if (later && later.length === 0) return null; const side = railSide(removed); const name = typeof removed.name === "string" && removed.name ? removed.name : "(unnamed)"; const named = later && later.length > 0 ? ` Remaining later host ${side} slots that shifted: ${later.map((s) => JSON.stringify(s)).join(", ")}.` : ""; return (`Host links on remaining later boundary ${side} slots were not reindexed after removing ` + `"${name}" at index ${slot} (artokun/comfyui-mcp#2437). Host SubgraphNode slots are ` + `positional and lockstep with the rail; nothing in the panel executor re-points the ` + `survivors. panel_query_graph and panel_graph_outline may still show those host links ` + `as connected — they read the same positional array — but queue-time serialization ` + `does not, so panel_run can fail with Required input is missing.` + named + ` Do not trust that connectedness. Repair: panel_exit_subgraph, then disconnect and ` + `reconnect each remaining later host link by NAME (not index); reconnecting by name ` + `re-resolves the index. Then re-enter if you still need the interior.`); } //# sourceMappingURL=unexpose-host-link-shift.js.map