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
JavaScript
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