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),

131 lines • 5.35 kB
import { dispatchOutcomeOf } from "./ui-bridge.js"; import { isManagerTransportFetchFailure } from "./manager-node-search.js"; import { listInstalledNodes } from "./node-management.js"; export const HOST_HTTP_LIST_NOTE = "Listed from ComfyUI-Manager HTTP because this session's panel tab was not connected. " + "This is the host pack inventory, not a Manager outage."; export const HOST_HTTP_TRANSPORT_NOTE = "Listed from ComfyUI-Manager HTTP because the panel's browser request to Manager " + "installed-packs did not complete. Live canvas reads still work; this is a " + "panel-origin transport failure, not a Manager outage."; let listInstalledOverride; /** Test seam — restore with `undefined` in afterEach. */ export function setListInstalledNodesForTests(fn) { listInstalledOverride = fn; } function textFromUnknown(value) { if (typeof value === "string") return value; if (value instanceof Error) return value.message; if (!value || typeof value !== "object") return ""; if ("message" in value && typeof value.message === "string") return value.message; if ("content" in value && Array.isArray(value.content)) { return value.content .map((c) => c && typeof c === "object" && "text" in c && typeof c.text === "string" ? c.text : "") .join("\n"); } return ""; } function isErrorShaped(value) { return Boolean(value && typeof value === "object" && "isError" in value && value.isError === true); } /** * Pre-dispatch tab loss: typed `dispatchOutcomeOf === false`, or the * callOnce wrapper / bridge text the reporter saw when Connected: none. */ export function isPanelTabDispatchFailure(value) { const outcome = dispatchOutcomeOf(value); if (outcome === true) return false; if (outcome === false) return true; const text = textFromUnknown(value); const looksLikeDispatch = /could not be dispatched[\s\S]{0,240}panel tab/i.test(text) || /no connected tab/i.test(text) || /Connected:\s*none/i.test(text); if (!looksLikeDispatch) return false; if (value instanceof Error) return true; if (isErrorShaped(value)) return true; return typeof value === "string"; } /** Invert `InstalledNode[]` back to the panel map keyed by module. */ export function installedNodesToPanelMap(nodes) { const installed = {}; for (const n of nodes) { const pack = {}; if (n.version !== undefined) pack.ver = n.version; if (n.cnrId) pack.cnr_id = n.cnrId; if (n.auxId) pack.aux_id = n.auxId; if (n.enabled !== undefined) pack.enabled = n.enabled; installed[n.module] = pack; } return installed; } export function dualCauseListFailure(tabErr, hostErr) { const host = textFromUnknown(hostErr) || "host Manager HTTP failed"; if (isManagerTransportFetchFailure(tabErr)) { const panel = textFromUnknown(tabErr) || "panel Manager request did not complete"; return (`nodes_list reached the panel tab, but the panel's ComfyUI-Manager request ` + `did not complete, and the host Manager HTTP pack listing also failed. ` + `Panel: ${panel} Host: ${host} ` + `This is not a Manager outage inferred from a transport-only fetch failure.`); } const tab = textFromUnknown(tabErr) || "panel tab was not connected"; return (`nodes_list could not be dispatched to this session's panel tab, and the host ` + `Manager HTTP pack listing also failed. Tab: ${tab} Host: ${host} ` + `This is not a Manager outage inferred from tab loss alone.`); } function shouldHostList(value) { return isPanelTabDispatchFailure(value) || isManagerTransportFetchFailure(value); } function hostListNote(panelErr) { return isManagerTransportFetchFailure(panelErr) ? HOST_HTTP_TRANSPORT_NOTE : HOST_HTTP_LIST_NOTE; } function resolveListInstalled(fn) { return fn ?? listInstalledOverride ?? listInstalledNodes; } /** * Panel `nodes_list` first. On pre-dispatch tab loss, or when the live tab's * Manager fetch never completed (`Failed to fetch` wrap), serve the pack * listing from host Manager HTTP (`listInstalledNodes`) instead of dying as a * dispatch-only or transport-only error. A live tab's Manager/object_info * result is passed through unchanged. */ export async function listPanelNodes(opts) { const runHost = async (panelErr) => { try { const nodes = await resolveListInstalled(opts.listInstalled)(); return { via: "fallback", value: { installed: installedNodesToPanelMap(nodes), source: "host_http", note: hostListNote(panelErr), }, }; } catch (hostErr) { return { via: "failed", message: dualCauseListFailure(panelErr, hostErr) }; } }; try { const value = await opts.panelList(); if (!shouldHostList(value)) return { via: "panel", value }; return await runHost(value); } catch (err) { if (!shouldHostList(err)) throw err; return await runHost(err); } } //# sourceMappingURL=manager-node-list.js.map