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

97 lines • 5.71 kB
/** * PER-TAB start-failure notice (issue #250, extracted for testability — issue * #255 review finding 5): when a tab's agent backend rejects at * prepare()/first-connect (an invalid API key 401ing on an OpenAI-dialect * provider, an unreachable endpoint), the orchestrator degrades THAT tab only. * This module builds the exact bridge frames the orchestrator pushes: * * 1. an honest `say` naming the provider, with check-your-key guidance * (hint selected via the OpenAI-key provider registry when the backend is * a registered key provider, with openrouter/custom/generic fallbacks); * 2. a degraded `ack` so the panel shows the real state; * 3. `turn: done` — the user_message path already pushed turn:"working", and * the panel clears its thinking spinner ONLY on turn:"done"; without this * the degraded tab sits on a live spinner for the 120s safety timeout * (adversarial review of #253, finding 1). * * The manager reports failures under the COMPOSITE agent key * (`panelTabId::backend`); frames must go to the PANEL tab, so the composite * key is split here on its LAST separator (a panel tab id never contains "::"; * backend names never do). * * The `say` is HUMAN-facing and names panel controls ("Settings → Custom * endpoint", "the API Keys card"), so it renders in the DESTINATION TAB's panel * language rather than the process's. There is usually more than one destination * — #884's conversation spans every tab on the failed backend, and those panels * can be in different languages — so the sentence also lives on its own in * `startFailureSay()`, which the orchestrator calls once per recipient. `locale` * defaults to English throughout, so every existing caller and test keeps its * exact current output. The `ack` and `turn` frames beside it are machine state * and stay untranslated. */ import { trFor } from "../i18n/index.js"; import { openAiKeyProvider } from "../services/openai-provider-registry.js"; export const AGENT_KEY_SEP = "::"; /** The panel tab id half of a composite agent key (the whole key when bare). */ export function panelTabOfKey(key) { const i = key.lastIndexOf(AGENT_KEY_SEP); return i >= 0 ? key.slice(0, i) : key; } /** The backend half of a composite agent key (`fallback` when bare). */ export function backendOfKey(key, fallback) { const i = key.lastIndexOf(AGENT_KEY_SEP); return i >= 0 ? key.slice(i + AGENT_KEY_SEP.length) : fallback; } /** Check-your-credentials guidance for a backend that failed to start, in `locale`. * The provider label and env-var name are interpolated, never translated: they are the * literal strings the user has to find in the API Keys card and in their shell. */ export function startFailureHint(backend, locale = "en") { const reg = openAiKeyProvider(backend); if (reg) { return trFor(locale, "say.start_failure.hint.key_provider", "Check your {provider} API key in the API Keys card ({env}), then Disconnect → Connect to retry.", { provider: reg.slotLabel, env: reg.envKeys[0] }); } if (backend === "openrouter") { return trFor(locale, "say.start_failure.hint.openrouter", "Check your OpenRouter API key in the API Keys card (OPENROUTER_API_KEY), then Disconnect → Connect to retry."); } if (backend === "custom") { return trFor(locale, "say.start_failure.hint.custom", "Check the base URL and API key in Settings → Custom endpoint, then Disconnect → Connect to retry."); } return trFor(locale, "say.start_failure.hint.generic", "Check the provider's credentials/login, then Disconnect → Connect to retry."); } /** * The HUMAN-facing bubble for a start failure, in `locale`. * * Exported separately from the notice below because the conversation this is announced to * can span several tabs whose panels are in DIFFERENT languages: the orchestrator renders * this once per recipient, while the `ack`/`turn` frames beside it are machine state and fan * out unchanged. This IS the sentence — `buildStartFailureNotice` calls it too rather than * holding a second copy, so the delivered text and the tested text cannot drift apart. */ export function startFailureSay(backend, message, locale = "en") { // ⚠️ outside the translated span — a status marker, not prose, and the same in every // language. `message` is the provider's own error text and is passed through verbatim; // trFor interpolates in ONE pass, so a `{hint}`-looking sequence inside it stays literal. return `⚠️ ${trFor(locale, "say.start_failure.notice", "The {backend} agent could not start: {message} — {hint}", { backend, hint: startFailureHint(backend, locale), message })}`; } /** * Build the per-tab degradation frames for a start failure on `key`. * * The `say` here is ENGLISH, deliberately and always. The orchestrator does not deliver this * copy of it — it re-renders the sentence per recipient through `startFailureSay`, because one * conversation can span panels in different languages. A `locale` parameter on this function * would therefore be exercised only by its own tests, pinning a path production never takes; * the two could then drift apart with everything still green. */ export function buildStartFailureNotice(key, message, defaultBackend) { const backend = backendOfKey(key, defaultBackend); return { panelTab: panelTabOfKey(key), backend, frames: [ { type: "say", text: startFailureSay(backend, message) }, { type: "ack", ok: false, kind: "degraded" }, { type: "turn", state: "done" }, ], }; } //# sourceMappingURL=start-failure-notice.js.map