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

144 lines • 7.6 kB
import { QueueMonitor } from "../services/queue-monitor.js"; // #948 — a correct instruction delivered to the wrong place is a wrong instruction. // // A user saw `Not logged in, please run /login` in the PANEL CHAT and reported // that `/login` "doesn't exist as a command". They were right: `/login` is typed // at the `pi` CLI's own prompt. It is not a panel command, not a ComfyUI command, // and not a shell command — and the message named none of that. // // In a chat box a leading `/` reads as "type this here". That is the whole defect: // the remedy was accurate and unusable at the same time, and it points the reader // at the one place it cannot work. // // It also misleads ACROSS providers. A user on the Claude or Codex chip has no // `/login` at all (`claude setup-token`, `codex login`), so a passed-through // string naming it is not merely unhelpful there — it is false. // // Two rules follow, and both are about the reader's location rather than the // text's accuracy: // 1. Never let a bare `/command` reach chat. Say where its prompt is. // 2. When we wrap a provider's auth failure, name the tool, the place, and the // follow-up — not just the keystrokes. /** The CLI whose prompt a backend's slash commands belong to, when it has one. */ const SLASH_CLI_BY_BACKEND = { pi: "pi", grok: "grok", }; /** * Rewrite bare `/command` mentions so they cannot read as "type this here". * * Only touches a slash-command that stands as its own word — `/login`, * `/status` — and leaves paths (`/usr/bin/pi`), URLs and dates alone, since * those are not instructions and rewriting them would corrupt real values. * * The first occurrence gets the full "at <cli>'s prompt" qualification; later * ones in the same message get a shorter form, because repeating the whole * clause reads as noise and noise is what gets skimmed past. */ export function qualifySlashCommands(text, cli) { if (!text) return text; let seen = 0; return text.replace(/(^|[\s("'`])\/([a-z][a-z0-9-]{1,24})\b(?!\/)/gi, (match, lead, cmd) => { // A path segment (`/foo/bar`) or a URL is not an instruction. seen += 1; const qualified = seen === 1 ? `\`${cli}\`, then type \`/${cmd}\` at its prompt` : `\`/${cmd}\` (at the \`${cli}\` prompt)`; return `${lead}${qualified}`; }); } /** True when a CLI's failure text is about missing/expired credentials. */ export function looksLikeAuthFailure(text) { return /sign.?in|log.?in|logged.?out|auth|credential|unauthoriz|api.?key|no provider/i.test(text); } /** * OUR wrapper for a provider's not-authenticated failure. * * Names the tool, WHERE to run it, and the follow-up — a raw CLI string assumes * the reader is already standing in that CLI, which from a panel chat is exactly * the assumption that fails. * * `detail` is the CLI's own tail, kept because it often carries the real reason * (an expired token vs a missing file) — but passed through `qualifySlashCommands` * first, so the child's phrasing can never smuggle a bare `/login` into chat. */ export function providerAuthRemedy(backend, detail) { const cli = SLASH_CLI_BY_BACKEND[backend]; const tail = detail?.trim() ? ` (${cli ? qualifySlashCommands(detail.trim(), cli) : detail.trim()})` : ""; switch (backend) { case "pi": return (`pi has no usable provider credentials. Fix it in a TERMINAL, not here: either set a provider ` + `API key (ANTHROPIC_API_KEY / OPENAI_API_KEY / …), or run \`pi\` once and type \`/login\` at its ` + `prompt — that writes ~/.pi/agent/auth.json. Then Disconnect → Connect and send your message ` + `again.${tail}`); case "codex": return (`Codex is not logged in. In a TERMINAL run \`codex login\` (ChatGPT sign-in) or set CODEX_API_KEY, ` + `then Disconnect → Connect.${tail}`); case "claude": return (`Claude has no usable credentials. In a TERMINAL run \`claude setup-token\`, or set ANTHROPIC_API_KEY, ` + `then Disconnect → Connect.${tail}`); case "gemini": return (`Gemini is not authenticated. In a TERMINAL run \`gemini\` once and complete its sign-in, or set ` + `GEMINI_API_KEY, then Disconnect → Connect.${tail}`); default: return (`${backend} has no usable credentials. Set the provider's API key in the API Keys card, or complete ` + `its CLI sign-in in a TERMINAL, then Disconnect → Connect.${tail}`); } } // --------------------------------------------------------------------------- // #889 — a run-error notification must not assert who queued the run. // --------------------------------------------------------------------------- /** * The ComfyUI prompt id named in a run-error string, when there is one. * * Best-effort by design: the id travels inside prose ("Render status for prompt * 9d70fd9d-… could not be confirmed"), so a miss is normal and simply yields the * `unknown` attribution rather than a wrong one. Matches a bare uuid, which is * what ComfyUI mints. */ export function promptIdOf(text) { const m = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i.exec(text ?? ""); return m?.[0]; } /** * The run-error notification, phrased by what we can actually establish. * * The three wordings differ in the ONE thing that was wrong before — the claim * about authorship — and in how hard they push. An agent told to STOP and to * relate a failure to its own work will relate it, so the imperative only * belongs on a run we can show is the agent's. */ export function describeRunError(error, attribution) { const details = `(panel_get_errors has the full node-level details)`; switch (attribution) { case "mine": return (`[panel event] ⚠️ The workflow run you queued ERRORED on the user's canvas: ${error}. ` + `STOP — do not carry on as if it succeeded. Diagnose it ${details} and fix it.`); case "not-mine": return (`[panel event] A workflow run ERRORED on the user's canvas: ${error}. ` + `You did NOT queue this run — this session has queued none — so do not treat it as your own ` + `failure and do not look for a connection to what you were doing. Tell the user briefly. ` + `Only investigate ${details} if they ask, or if it is obviously about a graph you just edited.`); default: return (`[panel event] ⚠️ A workflow run ERRORED on the user's canvas: ${error}. ` + `Whether it is one you queued could NOT be determined, so do not assume either way. ` + `If you were waiting on a render, treat this as it and diagnose it ${details}; if you were not, ` + `it is the user's own run — tell them briefly and carry on.`); } } /** * The run-error notice for a raw error string: extract the prompt id, ask the * QueueMonitor whose run it was, and phrase it accordingly. * * Kept as one named step so the composition is testable. Left inline in * PanelAgent it was reachable only through a full agent harness, and a mutation * that hardcoded the attribution back to "mine" — the exact #889 regression — * broke no test at all. */ export function runErrorNotice(error) { return describeRunError(error, QueueMonitor.attributeRun(promptIdOf(error))); } //# sourceMappingURL=cli-remedy.js.map