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

196 lines • 8.17 kB
/** * #1870 — `panel_update_node` labeled a previous generation traceback as the * Manager update traceback. * * The panel (#1320) fetches `/internal/logs/raw` when Manager stores only * "An error occurred while updating 'X'." and attaches the last traceback that * NAMES the pack. After a failed generation the log still holds that node's * execution stack (`FalApiError: Downstream service error (HTTP 500)` in the * report). For pack `fal-api` that stack names the pack, so it is attached as * `Manager traceback:` while the Manager log only says * `res.result=False, res.action=update-git`. * * The orchestrator keeps Manager task evidence (the generic update ERROR line, * `res.action`/`res.result`, `do_update` / `repo_update` frames) and drops * ComfyUI execution history. When that evidence shows an update-git (or * generic update) failure and the pack is a registry zip (no nested `.git`), * it names that and re-routes to reinstall rather than leaving the reader * debugging a stale generation error. The reinstall note is gated on that * observed Manager evidence, not on the caller's `version` argument (#1888). * * Never throws: this runs on an error path, and a guard that becomes the error * is strictly worse than the message it was improving. */ import { existsSync } from "node:fs"; import { join } from "node:path"; import { capturePackPresenceContext, findPackOnDisk, } from "./node-management.js"; const MANAGER_TRACEBACK_MARKER = /Manager traceback:\s*/i; /** Frames / log lines that belong to a Manager UPDATE, not a generation. */ const MANAGER_UPDATE_EVIDENCE = /\bdo_update\b|\bunified_update\b|\brepo_update\b|manager_server\.py|manager_core\.py|\bres\.action\s*=|\bres\.result\s*=/i; const GENERIC_UPDATE_LINE = /An error occurred while updating\s+'([^']+)'/i; const UPDATE_GIT = /res\.action\s*=\s*update-git/i; const ANSI = new RegExp(String.fromCharCode(27) + "\\[[0-9;]*m", "g"); function stripAnsi(line) { return line.replace(ANSI, ""); } function isTracebackStart(line) { return /Traceback \(most recent call last\):/.test(line); } function isTracebackContinuation(line) { const s = stripAnsi(line); if (!s.trim()) return true; if (/^\s/.test(s)) return true; if (isTracebackStart(s)) return true; if (/During handling of the above exception/.test(s)) return true; if (/The above exception was the direct cause/.test(s)) return true; return /^[A-Za-z_][\w.]*(Error|Exception|Exit|Warning|Interrupt):/.test(s.trim()); } /** * True when `text` is evidence of the Manager UPDATE itself — not a node's * generation failure that happens to name the same pack. */ export function isManagerUpdateEvidence(text) { return typeof text === "string" && MANAGER_UPDATE_EVIDENCE.test(text); } /** * Observe whether the installed pack has a nested `.git`. * * `true` — a git checkout (file or directory `.git`). * `false` — the pack is on disk here and has no `.git` (registry zip / copy). * `undefined` — we could not look (remote session, no root, pack not found, * unreadable). Absence of an observation is not "no git". */ export function observePackNestedGit(id) { try { const ctx = capturePackPresenceContext(); if (ctx.remote || !ctx.diskRoot) return undefined; const disk = findPackOnDisk(id, ctx.diskRoot); if (disk.state !== "found") return undefined; return existsSync(join(disk.dir, ".git")); } catch { return undefined; } } function nonGitRegistryNote(id) { return (`NOTE: "${id}" is installed as a Comfy Registry zip (no nested .git), so ` + `Manager's update-git cannot pull a nightly/git HEAD. The pack was NOT ` + `git-updated. Reinstall it from the registry instead: install_custom_node ` + `(action:"reinstall", id:"${id}") — or panel_install_node with the same id. ` + `Nightly needs a git checkout; a zip-installed pack cannot be git-updated.`); } function collectTracebackAt(lines, start) { if (start < 0 || start >= lines.length || !isTracebackStart(lines[start])) return null; const out = [lines[start]]; for (let i = start + 1; i < lines.length; i++) { if (!isTracebackContinuation(lines[i])) break; out.push(lines[i]); } while (out.length && !out[out.length - 1].trim()) out.pop(); return out.length ? out : null; } /** * Keep Manager-task lines from an attached traceback; drop execution-history * frames. Returns `{ kept, droppedExecution }` so the caller can disclose the * omission rather than silently shrinking the message. */ export function keepManagerUpdateEvidence(traceback) { const lines = traceback.split(/\r?\n/).map(stripAnsi); const kept = []; let droppedExecution = false; let i = 0; while (i < lines.length) { if (isTracebackStart(lines[i])) { const block = collectTracebackAt(lines, i); if (!block) { i += 1; continue; } i += block.length; if (isManagerUpdateEvidence(block.join("\n"))) { kept.push(block.join("\n").trimEnd()); } else { droppedExecution = true; } continue; } const line = lines[i].trimEnd(); if (GENERIC_UPDATE_LINE.test(line) || /\bres\.(action|result)\s*=/.test(line)) { kept.push(line.trim()); } i += 1; } return { kept: kept.join("\n").trim(), droppedExecution }; } /** * #1888 — the reinstall note is a claim about Manager's update, so it fires * only when the message itself carries that evidence. `version === "nightly"` * is the caller's argument, not an observation: on a zip-installed pack it * bolted the note onto every `isError`, including a disconnected tab and a * reply-timeout whose own text says the mutation's outcome is unknown. */ function wantsNonGitReroute(message) { return UPDATE_GIT.test(message) || GENERIC_UPDATE_LINE.test(message); } /** * Rewrite a `panel_update_node` failure so Manager task evidence is not mixed * with ComfyUI execution history. Identity when there is nothing to change. */ export function sanitizePanelUpdateFailure(message, opts) { if (typeof message !== "string" || !message) return message; let out = message; const marker = MANAGER_TRACEBACK_MARKER.exec(message); if (marker && marker.index !== undefined) { const head = message.slice(0, marker.index).trimEnd(); const tb = message.slice(marker.index + marker[0].length); const { kept, droppedExecution } = keepManagerUpdateEvidence(tb); if (droppedExecution) { const omitted = " (The previous ComfyUI generation traceback is execution history, not this Manager update, and was omitted.)"; out = kept ? `${head}${omitted} Manager traceback:\n${kept}` : `${head}${omitted}`; } } if (wantsNonGitReroute(out) && observePackNestedGit(opts.id) === false) { const note = nonGitRegistryNote(opts.id); if (!out.includes(note)) out = `${out.trimEnd()}\n\n${note}`; } return out; } /** * Post-process the ToolResult `panel_update_node` got from the panel. Success * and unrelated failures pass through. Never throws. */ export function sanitizePanelUpdateNodeResult(res, opts) { try { if (!res?.isError || !Array.isArray(res.content)) return res; const idx = res.content.findIndex((c) => c.type === "text" && typeof c.text === "string"); if (idx < 0) return res; const text = res.content[idx].text ?? ""; const cleaned = sanitizePanelUpdateFailure(text, opts); if (cleaned === text) return res; const content = res.content.slice(); content[idx] = { ...content[idx], text: cleaned }; return { ...res, content }; } catch { return res; } } //# sourceMappingURL=manager-update-error.js.map