comfyui-mcp 0.24.4 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/orchestrator/backend-readiness.js +7 -0
- package/dist/orchestrator/backend-readiness.js.map +1 -1
- package/dist/orchestrator/index.js +235 -17
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/ollama-backend.js +45 -4
- package/dist/orchestrator/ollama-backend.js.map +1 -1
- package/dist/orchestrator/panel-tools.js +10 -2
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/services/model-resolver.js +26 -6
- package/dist/services/model-resolver.js.map +1 -1
- package/dist/services/panel-secrets.js +66 -0
- package/dist/services/panel-secrets.js.map +1 -1
- package/dist/services/panel-settings.js +25 -0
- package/dist/services/panel-settings.js.map +1 -1
- package/dist/services/ui-bridge.js +27 -9
- package/dist/services/ui-bridge.js.map +1 -1
- package/dist/services/workspace-env.js +27 -1
- package/dist/services/workspace-env.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/ideogram-ultra/SKILL.md +17 -0
package/README.md
CHANGED
|
@@ -574,6 +574,8 @@ npx -y comfyui-mcp@latest --comfyui-url http://localhost:8188 --force-remote
|
|
|
574
574
|
| `COMFYUI_PORT` | *(auto-detect)* | ComfyUI server port (tries 8188, then 8000) |
|
|
575
575
|
| `COMFYUI_PATH` | *(auto-detect)* | Path to ComfyUI data directory. Auto-detection suppressed in remote/cloud modes. |
|
|
576
576
|
| `COMFYUI_PYTHON` | `python` | Python interpreter for cm-cli subprocess operations (`useCmCli`, `sync_node_dependencies`) — point it at your ComfyUI venv's python. cm-cli needs the local filesystem, so these are **local-mode only**; on remote targets use the Manager HTTP path (the default). |
|
|
577
|
+
| `COMFYUI_MCP_BRIDGE_HOST` | `127.0.0.1` | Panel-bridge bind host. Set `0.0.0.0` (or a LAN IP) to run the orchestrator on a 24/7 server and connect panels from other machines — **requires a token** (below); the orchestrator prints a ready-to-paste `ws://…/?token=…` Bridge URL. |
|
|
578
|
+
| `COMFYUI_MCP_BRIDGE_TOKEN` | *(generated when needed)* | Shared secret gating every bridge connection (checked constant-time on the WS upgrade). Mandatory for a non-loopback `COMFYUI_MCP_BRIDGE_HOST`; pin it so the Bridge URL survives restarts. Never logged beyond the startup banner. |
|
|
577
579
|
| `COMFYUI_MCP_DATA_DIR` | `~/.comfyui-mcp` | Base dir for per-instance data (the `generations.db` used by `suggest_settings`) when there's no local `COMFYUI_PATH` (remote/cloud/undetected). Scoped per target under `instances/<host_port>/`. |
|
|
578
580
|
| `COMFYUI_API_KEY` | | Comfy Cloud API key. When set, **cloud mode** is active and the server talks to `cloud.comfy.org`. Never logged. |
|
|
579
581
|
| `COMFYUI_CLOUD_URL` | `https://cloud.comfy.org` | Override the Comfy Cloud endpoint (testing/staging). |
|
|
@@ -90,6 +90,13 @@ export function backendReadiness(backend, opts) {
|
|
|
90
90
|
const cli = ollamaInstalled(home);
|
|
91
91
|
return { backend: "ollama", cli, auth: cli ? true : null, ready: cli };
|
|
92
92
|
}
|
|
93
|
+
if (b === "openrouter") {
|
|
94
|
+
// Hosted — no CLI. Readiness = an OpenRouter API key in the orchestrator's
|
|
95
|
+
// env (OPENROUTER_API_KEY, or the shared COMFYUI_MCP_OLLAMA_API_KEY). A bad
|
|
96
|
+
// key still surfaces via the connect ack's model probe (degraded).
|
|
97
|
+
const key = !!(process.env.OPENROUTER_API_KEY || process.env.COMFYUI_MCP_OLLAMA_API_KEY);
|
|
98
|
+
return { backend: "openrouter", cli: key, auth: key ? true : false, ready: key };
|
|
99
|
+
}
|
|
93
100
|
return { backend: b, cli: false, auth: false, ready: false };
|
|
94
101
|
}
|
|
95
102
|
/** Readiness for every known backend, plus a rolled-up any_ready. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend-readiness.js","sourceRoot":"","sources":["../../src/orchestrator/backend-readiness.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"backend-readiness.js","sourceRoot":"","sources":["../../src/orchestrator/backend-readiness.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
// key. See docs/design/panel-orchestrator.md.
|
|
11
11
|
import { existsSync, writeFileSync, unlinkSync, readFileSync, readdirSync } from "node:fs";
|
|
12
12
|
import { execFileSync } from "node:child_process";
|
|
13
|
-
import { tmpdir } from "node:os";
|
|
13
|
+
import { tmpdir, networkInterfaces } from "node:os";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { randomBytes } from "node:crypto";
|
|
17
17
|
import readline from "node:readline";
|
|
18
|
-
import { startUiBridge } from "../services/ui-bridge.js";
|
|
18
|
+
import { startUiBridge, isLoopbackBindHost } from "../services/ui-bridge.js";
|
|
19
19
|
import { setupSecureBridge } from "../services/secure-bridge.js";
|
|
20
20
|
import { detectInstallMode } from "../services/self-update.js";
|
|
21
21
|
import { SessionStore } from "./session-store.js";
|
|
@@ -24,7 +24,7 @@ import { PanelAgentManager, fetchSupportedModels, fetchSupportedCommands, isEffo
|
|
|
24
24
|
import { createPanelMcpServer } from "./panel-tools.js";
|
|
25
25
|
import { readUserMcpServers } from "../services/user-mcp-config.js";
|
|
26
26
|
import { isForceRemoteFlagSet, isLoopbackHost } from "../config.js";
|
|
27
|
-
import { buildComfyuiMcpEnv, comfyuiSecretKeys, onComfyuiSecretsChanged, } from "../services/panel-secrets.js";
|
|
27
|
+
import { buildComfyuiMcpEnv, comfyuiSecretKeys, onComfyuiSecretsChanged, hydrateAgentSecretsIntoEnv, onAgentSecretsChanged, setAgentSecret, } from "../services/panel-secrets.js";
|
|
28
28
|
import { CodexBackend } from "./codex-backend.js";
|
|
29
29
|
import { GeminiBackend, GEMINI_DEFAULT_MODEL } from "./gemini-backend.js";
|
|
30
30
|
import { OllamaBackend } from "./ollama-backend.js";
|
|
@@ -32,6 +32,7 @@ import { allBackendReadiness } from "./backend-readiness.js";
|
|
|
32
32
|
import { startPanelMcpHttpServer } from "./panel-mcp-http.js";
|
|
33
33
|
import { readComfyuiCrashLog, formatCrashNote } from "../services/crash-log.js";
|
|
34
34
|
import { QueueMonitor } from "../services/queue-monitor.js";
|
|
35
|
+
import { getAgentSettings, setAgentSettings } from "../services/panel-settings.js";
|
|
35
36
|
import { gatherEnvCapabilities, buildPanelSystemAppend, } from "../services/env-capabilities.js";
|
|
36
37
|
const PANEL_SYSTEM_APPEND = `You are the autonomous assistant embedded directly in a ComfyUI sidebar panel. The person is working in ComfyUI and talks to you through that panel: their messages arrive as your prompts, and everything you write is shown to them in the panel chat. Write for that reader — lead with the result, keep replies short and concrete, and don't narrate routine internal steps.
|
|
37
38
|
|
|
@@ -402,6 +403,17 @@ export async function runPanelOrchestrator() {
|
|
|
402
403
|
// Subscription lane: the background agent must authenticate against the user's
|
|
403
404
|
// claude.ai login, never an API key. Unset the key for the SDK subprocess.
|
|
404
405
|
delete process.env.ANTHROPIC_API_KEY;
|
|
406
|
+
// First non-internal IPv4 — display-only, for the LAN-bridge banner when the
|
|
407
|
+
// bind host is 0.0.0.0/:: (the real reachable address depends on the network).
|
|
408
|
+
const firstLanIPv4 = () => {
|
|
409
|
+
for (const addrs of Object.values(networkInterfaces())) {
|
|
410
|
+
for (const a of addrs ?? []) {
|
|
411
|
+
if (!a.internal && a.family === "IPv4")
|
|
412
|
+
return a.address;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return undefined;
|
|
416
|
+
};
|
|
405
417
|
// Secure bridge: when driving a REMOTE https ComfyUI (a pod), the pod's HTTPS
|
|
406
418
|
// panel page can't reach a plain ws:// loopback bridge (mixed-content / Private
|
|
407
419
|
// Network Access), so auto-upgrade to a token-gated wss:// exposed via a
|
|
@@ -410,11 +422,42 @@ export async function runPanelOrchestrator() {
|
|
|
410
422
|
const insecureBridge = process.env.COMFYUI_MCP_INSECURE_BRIDGE === "1" ||
|
|
411
423
|
process.env.COMFYUI_MCP_INSECURE_BRIDGE === "true";
|
|
412
424
|
const wantSecureBridge = !insecureBridge && isRemoteHttpsUrl(process.env.COMFYUI_URL ?? "");
|
|
413
|
-
|
|
414
|
-
//
|
|
425
|
+
// LAN bridge (panel #54 — the 24/7 server / standalone OpenClaw topology):
|
|
426
|
+
// COMFYUI_MCP_BRIDGE_HOST binds the bridge on a non-loopback interface so
|
|
427
|
+
// browsers on OTHER machines can connect. Non-loopback ALWAYS token-gates the
|
|
428
|
+
// WS upgrade — the token comes from COMFYUI_MCP_BRIDGE_TOKEN (pin it for
|
|
429
|
+
// stable reconnects across restarts) or is generated fresh and printed below.
|
|
430
|
+
const bridgeHost = (process.env.COMFYUI_MCP_BRIDGE_HOST ?? "127.0.0.1").trim() || "127.0.0.1";
|
|
431
|
+
const lanBridge = !isLoopbackBindHost(bridgeHost);
|
|
432
|
+
const envBridgeToken = process.env.COMFYUI_MCP_BRIDGE_TOKEN?.trim() || null;
|
|
433
|
+
const bridgeToken = envBridgeToken ?? (wantSecureBridge || lanBridge ? randomBytes(24).toString("hex") : null);
|
|
434
|
+
// Dedicated PANEL bridge port (default 9180). Token-gated in secure/LAN mode.
|
|
415
435
|
const lockPort = Number(process.env.COMFYUI_MCP_BRIDGE_PORT) || 9180;
|
|
416
436
|
const lockPath = orchLockPath(lockPort);
|
|
417
|
-
const bridge = startUiBridge(lockPort, bridgeToken);
|
|
437
|
+
const bridge = startUiBridge(lockPort, bridgeToken, bridgeHost);
|
|
438
|
+
if (lanBridge) {
|
|
439
|
+
// Ready-to-paste connection info: the panel's Settings → Advanced →
|
|
440
|
+
// Bridge URL takes the full URL incl. ?token= verbatim.
|
|
441
|
+
const displayHost = bridgeHost === "0.0.0.0" || bridgeHost === "::"
|
|
442
|
+
? (firstLanIPv4() ?? "<this-machine-ip>")
|
|
443
|
+
: bridgeHost;
|
|
444
|
+
process.stderr.write([
|
|
445
|
+
"",
|
|
446
|
+
"════════════════════════════════════════════════════════════════════",
|
|
447
|
+
" ComfyUI MCP — panel bridge exposed on the LAN (token-gated)",
|
|
448
|
+
"════════════════════════════════════════════════════════════════════",
|
|
449
|
+
` Bridge URL : ws://${displayHost}:${lockPort}/?token=${bridgeToken}`,
|
|
450
|
+
"",
|
|
451
|
+
" In the panel: Settings → Advanced → Bridge URL → paste the URL above,",
|
|
452
|
+
" then click Connect. Anyone with this URL can drive the agent — treat",
|
|
453
|
+
" it like a password.",
|
|
454
|
+
envBridgeToken
|
|
455
|
+
? " Token source: COMFYUI_MCP_BRIDGE_TOKEN (stable across restarts)."
|
|
456
|
+
: " Token was GENERATED for this run — set COMFYUI_MCP_BRIDGE_TOKEN to keep the same URL across restarts.",
|
|
457
|
+
"════════════════════════════════════════════════════════════════════",
|
|
458
|
+
"",
|
|
459
|
+
].join("\n") + "\n");
|
|
460
|
+
}
|
|
418
461
|
// Owning the bridge port is the orchestrator's whole job — if another process
|
|
419
462
|
// holds it, fail loudly instead of running uselessly. (This also avoids the
|
|
420
463
|
// case where a failed bind leaves the process with no live handles and it
|
|
@@ -570,19 +613,54 @@ export async function runPanelOrchestrator() {
|
|
|
570
613
|
// applied PER REQUEST — switching live is free. Default = the LLM Arena's best
|
|
571
614
|
// performer (scripts/llm-arena.mjs): gemma4:e4b, 9/10 with the cleanest runs
|
|
572
615
|
// (first-try tool dispatch, no nudges) and multimodal headroom for vision.
|
|
573
|
-
|
|
616
|
+
//
|
|
617
|
+
// Config precedence: env (escape hatch, always wins) → persisted user settings
|
|
618
|
+
// (~/.comfyui-mcp/panel-settings.json, edited from the panel Settings dialog
|
|
619
|
+
// via set_config) → built-in default. Mutable (`let`) because set_config can
|
|
620
|
+
// retarget them live; API keys stay env-only and never touch the settings file.
|
|
621
|
+
// Copy any panel-stored provider keys (OPENROUTER_API_KEY) into env BEFORE we
|
|
622
|
+
// read them below, so a key set on a prior run enables its provider on boot.
|
|
623
|
+
const hydratedSecrets = hydrateAgentSecretsIntoEnv();
|
|
624
|
+
if (hydratedSecrets.length) {
|
|
625
|
+
logger.info(`[panel-orchestrator] hydrated agent secrets from store: ${hydratedSecrets.join(", ")}`);
|
|
626
|
+
}
|
|
627
|
+
const persistedAgent = getAgentSettings();
|
|
628
|
+
let ollamaModel = process.env.COMFYUI_MCP_OLLAMA_MODEL ?? persistedAgent.ollama?.model ?? "gemma4:e4b";
|
|
574
629
|
// The same backend also speaks any OpenAI-compatible endpoint (OpenRouter,
|
|
575
630
|
// DeepSeek, vLLM, LM Studio): COMFYUI_MCP_OLLAMA_API=openai +
|
|
576
631
|
// COMFYUI_MCP_OLLAMA_BASE_URL (incl. /v1) + COMFYUI_MCP_OLLAMA_API_KEY
|
|
577
632
|
// (falls back to OPENROUTER_API_KEY). The chip stays "Ollama (local)".
|
|
578
|
-
|
|
579
|
-
|
|
633
|
+
let ollamaApi = (process.env.COMFYUI_MCP_OLLAMA_API
|
|
634
|
+
? process.env.COMFYUI_MCP_OLLAMA_API === "openai"
|
|
635
|
+
: persistedAgent.ollama?.api === "openai")
|
|
636
|
+
? "openai"
|
|
637
|
+
: "ollama";
|
|
638
|
+
let ollamaBaseUrl = process.env.COMFYUI_MCP_OLLAMA_BASE_URL ?? persistedAgent.ollama?.baseUrl;
|
|
580
639
|
const ollamaApiKey = process.env.COMFYUI_MCP_OLLAMA_API_KEY || process.env.OPENROUTER_API_KEY;
|
|
581
640
|
const ollamaDeps = () => ({
|
|
582
641
|
api: ollamaApi,
|
|
583
642
|
...(ollamaBaseUrl ? { host: ollamaBaseUrl } : {}),
|
|
584
643
|
...(ollamaApi === "openai" && ollamaApiKey ? { apiKey: ollamaApiKey } : {}),
|
|
585
644
|
});
|
|
645
|
+
// OpenRouter is a first-class provider = the Ollama backend hard-wired to
|
|
646
|
+
// OpenRouter's OpenAI-compatible endpoint, so its picker leads with the
|
|
647
|
+
// curated arena-winning models (RECOMMENDED_OPENROUTER_MODELS, MiMo/MiniMax
|
|
648
|
+
// tagged 1M · SOTA). Key comes from OPENROUTER_API_KEY (or the shared ollama
|
|
649
|
+
// key). Default model = the arena's top open-weight, MiMo v2.5.
|
|
650
|
+
const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
651
|
+
let openrouterModel = process.env.COMFYUI_MCP_OPENROUTER_MODEL ?? "xiaomi/mimo-v2.5";
|
|
652
|
+
// Read the key FRESH each call (not a startup const) so a key the user sets
|
|
653
|
+
// later via the panel — setAgentSecret hydrates it into env — takes effect on
|
|
654
|
+
// the next backend build without an orchestrator restart.
|
|
655
|
+
const openrouterApiKey = () => process.env.OPENROUTER_API_KEY || process.env.COMFYUI_MCP_OLLAMA_API_KEY;
|
|
656
|
+
const openrouterDeps = () => {
|
|
657
|
+
const key = openrouterApiKey();
|
|
658
|
+
return {
|
|
659
|
+
api: "openai",
|
|
660
|
+
host: OPENROUTER_BASE_URL,
|
|
661
|
+
...(key ? { apiKey: key } : {}),
|
|
662
|
+
};
|
|
663
|
+
};
|
|
586
664
|
// ── Per-tab backend (single-port multi-provider) ──────────────────────────
|
|
587
665
|
// ONE orchestrator on ONE bridge port serves ALL providers; the panel picks a
|
|
588
666
|
// provider per tab via the `hello`/`set_backend` handshake, instead of the node
|
|
@@ -592,7 +670,7 @@ export async function runPanelOrchestrator() {
|
|
|
592
670
|
// provider (the panel replays the transcript to seed it) while a same-provider
|
|
593
671
|
// reconnect RESUMES. `backendId`/`codexModel`/`geminiModel` above are the
|
|
594
672
|
// DEFAULT + per-provider model config; the process is no longer pinned to one.
|
|
595
|
-
const KNOWN_BACKENDS = new Set(["claude", "codex", "gemini", "ollama"]);
|
|
673
|
+
const KNOWN_BACKENDS = new Set(["claude", "codex", "gemini", "ollama", "openrouter"]);
|
|
596
674
|
const defaultBackend = KNOWN_BACKENDS.has(backendId) ? backendId : "claude";
|
|
597
675
|
const AGENT_KEY_SEP = "::";
|
|
598
676
|
const tabBackends = new Map(); // panel tabId -> selected backend
|
|
@@ -739,6 +817,16 @@ export async function runPanelOrchestrator() {
|
|
|
739
817
|
...ollamaDeps(),
|
|
740
818
|
});
|
|
741
819
|
}
|
|
820
|
+
if (backend === "openrouter") {
|
|
821
|
+
return new OllamaBackend({
|
|
822
|
+
cwd: comfyuiPath ?? process.cwd(),
|
|
823
|
+
model: openrouterModel,
|
|
824
|
+
systemAppend: panelSystemAppend,
|
|
825
|
+
comfyuiUrl,
|
|
826
|
+
mcpServers: makeHttpBackendMcpServers(panelTabId),
|
|
827
|
+
...openrouterDeps(),
|
|
828
|
+
});
|
|
829
|
+
}
|
|
742
830
|
return undefined; // claude → built-in ClaudeBackend
|
|
743
831
|
};
|
|
744
832
|
logger.info(`[panel-orchestrator] single-port multi-provider: default backend=${defaultBackend}; ` +
|
|
@@ -759,7 +847,9 @@ export async function runPanelOrchestrator() {
|
|
|
759
847
|
? new CodexBackend({ cwd: comfyuiPath ?? process.cwd(), model: codexModel })
|
|
760
848
|
: backend === "ollama"
|
|
761
849
|
? new OllamaBackend({ cwd: comfyuiPath ?? process.cwd(), model: ollamaModel, ...ollamaDeps() })
|
|
762
|
-
:
|
|
850
|
+
: backend === "openrouter"
|
|
851
|
+
? new OllamaBackend({ cwd: comfyuiPath ?? process.cwd(), model: openrouterModel, ...openrouterDeps() })
|
|
852
|
+
: new GeminiBackend({ cwd: comfyuiPath ?? process.cwd(), model: geminiModel });
|
|
763
853
|
probeBackends.set(backend, pb);
|
|
764
854
|
}
|
|
765
855
|
return pb;
|
|
@@ -914,6 +1004,24 @@ export async function runPanelOrchestrator() {
|
|
|
914
1004
|
manager.restartAllForMcpEnv("🔑 The API token you just provided is now active for the comfyui tools — retry the action that needed it (e.g. the download that returned 401).");
|
|
915
1005
|
logger.info(`[panel-orchestrator] tool secret saved → comfyui MCP env updated + agents respawn on idle (keys: ${comfyuiSecretKeys().join(", ") || "none"})`);
|
|
916
1006
|
});
|
|
1007
|
+
// An agent-provider secret changed (e.g. the OpenRouter API key set from the
|
|
1008
|
+
// panel). Hydrate it into env, drop the cached openrouter probe/model list so
|
|
1009
|
+
// the next probe uses the new key, and re-push readiness + models to every
|
|
1010
|
+
// live tab so the OpenRouter provider flips to "ready" and lists its models
|
|
1011
|
+
// without a reconnect.
|
|
1012
|
+
const unsubscribeAgentSecrets = onAgentSecretsChanged(() => {
|
|
1013
|
+
hydrateAgentSecretsIntoEnv();
|
|
1014
|
+
modelsByBackend.delete("openrouter");
|
|
1015
|
+
const pb = probeBackends.get("openrouter");
|
|
1016
|
+
if (pb?.close)
|
|
1017
|
+
void pb.close().catch(() => { });
|
|
1018
|
+
probeBackends.delete("openrouter");
|
|
1019
|
+
for (const tabId of tabBackends.keys()) {
|
|
1020
|
+
pushReadiness(tabId);
|
|
1021
|
+
pushModels(tabId);
|
|
1022
|
+
}
|
|
1023
|
+
logger.info("[panel-orchestrator] OpenRouter key saved → provider readiness + models refreshed");
|
|
1024
|
+
});
|
|
917
1025
|
// Debounce the connect ack: the panel re-sends `hello` on reconnect and on
|
|
918
1026
|
// workflow-title changes, which would otherwise stack duplicate greetings.
|
|
919
1027
|
const lastAckAt = new Map();
|
|
@@ -950,7 +1058,23 @@ export async function runPanelOrchestrator() {
|
|
|
950
1058
|
: fetchSupportedModels(model);
|
|
951
1059
|
p = probe.then((list) => {
|
|
952
1060
|
if (!list.length)
|
|
953
|
-
modelsByBackend.delete(backend); // don't cache a
|
|
1061
|
+
modelsByBackend.delete(backend); // don't cache a failed probe
|
|
1062
|
+
// User-curated preferred models (panel Settings → set_config) pin to the
|
|
1063
|
+
// top of the ollama picker, ahead of the discovered catalog. Read fresh
|
|
1064
|
+
// on every probe; set_config evicts the cache so edits apply live.
|
|
1065
|
+
if (backend === "ollama") {
|
|
1066
|
+
const preferred = getAgentSettings().preferredModels ?? [];
|
|
1067
|
+
if (preferred.length) {
|
|
1068
|
+
const discovered = new Map(list.map((m) => [m.value, m]));
|
|
1069
|
+
list = [
|
|
1070
|
+
...preferred.map((id) => (discovered.get(id) ?? {
|
|
1071
|
+
value: id,
|
|
1072
|
+
displayName: `${id} ★`,
|
|
1073
|
+
})),
|
|
1074
|
+
...list.filter((m) => !preferred.includes(m.value)),
|
|
1075
|
+
];
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
954
1078
|
return list;
|
|
955
1079
|
});
|
|
956
1080
|
modelsByBackend.set(backend, p);
|
|
@@ -967,6 +1091,8 @@ export async function runPanelOrchestrator() {
|
|
|
967
1091
|
return geminiModel;
|
|
968
1092
|
if (backend === "ollama")
|
|
969
1093
|
return ollamaModel;
|
|
1094
|
+
if (backend === "openrouter")
|
|
1095
|
+
return openrouterModel;
|
|
970
1096
|
return model;
|
|
971
1097
|
}
|
|
972
1098
|
function pushModels(panelTabId) {
|
|
@@ -1088,9 +1214,24 @@ export async function runPanelOrchestrator() {
|
|
|
1088
1214
|
const isCx = backend === "codex";
|
|
1089
1215
|
const isGm = backend === "gemini";
|
|
1090
1216
|
const isOl = backend === "ollama";
|
|
1217
|
+
const isOr = backend === "openrouter";
|
|
1091
1218
|
// TRUTHFUL "connected": only claim ready after PROVING the SELECTED backend
|
|
1092
1219
|
// can run, by probing its model list. If the probe fails — the "connected
|
|
1093
1220
|
// but dead" wedge — send a degraded ack so the panel shows the real state.
|
|
1221
|
+
// OpenRouter needs an explicit key check FIRST: its /models endpoint is
|
|
1222
|
+
// PUBLIC, so the probe "succeeds" keyless and the tab would greet ready —
|
|
1223
|
+
// then 401 on the first real message. Degrade up front instead.
|
|
1224
|
+
if (isOr && !openrouterApiKey()) {
|
|
1225
|
+
bridge.push({
|
|
1226
|
+
type: "say",
|
|
1227
|
+
text: "⚠️ OpenRouter has no API key — the connection would fail on your first message. " +
|
|
1228
|
+
"Set it in Settings → OpenRouter → “Set API key…” (masked, stored by the orchestrator — takes effect immediately, no reconnect needed), " +
|
|
1229
|
+
"or set the OPENROUTER_API_KEY environment variable and restart the orchestrator. Keys: https://openrouter.ai/keys",
|
|
1230
|
+
}, panelTab);
|
|
1231
|
+
bridge.push({ type: "ack", ok: false, kind: "degraded" }, panelTab);
|
|
1232
|
+
logger.warn(`[panel-orchestrator] tab ${panelTab.slice(0, 8)} connected (openrouter) but no API key — degraded ack`);
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1094
1235
|
void ensureModels(backend)
|
|
1095
1236
|
.then((models) => {
|
|
1096
1237
|
if (models.length) {
|
|
@@ -1100,7 +1241,9 @@ export async function runPanelOrchestrator() {
|
|
|
1100
1241
|
? (geminiModel ?? models[0].value ?? "Gemini")
|
|
1101
1242
|
: isOl
|
|
1102
1243
|
? (ollamaModel ?? models[0].value ?? "Ollama")
|
|
1103
|
-
:
|
|
1244
|
+
: isOr
|
|
1245
|
+
? (openrouterModel ?? models[0].value ?? "OpenRouter")
|
|
1246
|
+
: model;
|
|
1104
1247
|
// Greet only on a FRESH session (a resume/reconnect already has the thread).
|
|
1105
1248
|
if (!resume) {
|
|
1106
1249
|
const readyText = isCx
|
|
@@ -1109,7 +1252,9 @@ export async function runPanelOrchestrator() {
|
|
|
1109
1252
|
? `🟢 comfyui-mcp agent ready — ${agentLabel} on your Google account (Gemini Code Assist). Ask away.`
|
|
1110
1253
|
: isOl
|
|
1111
1254
|
? `🟢 comfyui-mcp agent ready — ${agentLabel} running locally via Ollama (no account, no API key). Small local models are slower and simpler than frontier ones — expect fewer frills. Ask away.`
|
|
1112
|
-
:
|
|
1255
|
+
: isOr
|
|
1256
|
+
? `🟢 comfyui-mcp agent ready — ${agentLabel} via OpenRouter (hosted API, your OPENROUTER_API_KEY). Ask away.`
|
|
1257
|
+
: `🟢 comfyui-mcp agent ready — ${agentLabel} on your Claude subscription. Ask away.`;
|
|
1113
1258
|
bridge.push({ type: "say", text: readyText }, panelTab);
|
|
1114
1259
|
}
|
|
1115
1260
|
bridge.push({ type: "ack", ok: true, kind: "ready", agent: agentLabel, backend }, panelTab);
|
|
@@ -1157,17 +1302,89 @@ export async function runPanelOrchestrator() {
|
|
|
1157
1302
|
logger.info(`[panel-orchestrator] tab ${panelTab.slice(0, 8)} switched backend ${prev} → ${reqBackend}`);
|
|
1158
1303
|
return;
|
|
1159
1304
|
}
|
|
1160
|
-
// Live panel config
|
|
1161
|
-
//
|
|
1162
|
-
//
|
|
1305
|
+
// Live panel config: render-stall threshold, plus the user's agent-model
|
|
1306
|
+
// preferences (preferred_models list + ollama endpoint config), persisted to
|
|
1307
|
+
// ~/.comfyui-mcp/panel-settings.json. Sent by the panel on connect and
|
|
1308
|
+
// whenever a setting changes. Model-list changes apply live (cache evicted,
|
|
1309
|
+
// fresh `models` frame pushed); an endpoint change retargets NEW sessions —
|
|
1310
|
+
// live ollama sessions keep their connection until restarted.
|
|
1163
1311
|
if (event.type === "set_config" && event.tab_id) {
|
|
1164
1312
|
if ("stall_seconds" in event) {
|
|
1165
1313
|
setLiveStallSeconds(event.stall_seconds);
|
|
1166
1314
|
logger.info(`[panel-orchestrator] live stall threshold → ${liveStallSeconds ?? "default"}s`);
|
|
1167
1315
|
}
|
|
1316
|
+
const cfg = event;
|
|
1317
|
+
let ollamaChanged = false;
|
|
1318
|
+
if (Array.isArray(cfg.preferred_models)) {
|
|
1319
|
+
const ids = cfg.preferred_models.filter((m) => typeof m === "string");
|
|
1320
|
+
setAgentSettings({ preferredModels: ids });
|
|
1321
|
+
ollamaChanged = true;
|
|
1322
|
+
logger.info(`[panel-orchestrator] preferred models → [${ids.join(", ")}]`);
|
|
1323
|
+
}
|
|
1324
|
+
if (cfg.ollama && typeof cfg.ollama === "object") {
|
|
1325
|
+
const o = cfg.ollama;
|
|
1326
|
+
const patch = {};
|
|
1327
|
+
if (typeof o.model === "string" && o.model.trim()) {
|
|
1328
|
+
patch.model = o.model.trim();
|
|
1329
|
+
if (!process.env.COMFYUI_MCP_OLLAMA_MODEL)
|
|
1330
|
+
ollamaModel = patch.model;
|
|
1331
|
+
}
|
|
1332
|
+
if (o.api === "openai" || o.api === "ollama") {
|
|
1333
|
+
patch.api = o.api;
|
|
1334
|
+
if (!process.env.COMFYUI_MCP_OLLAMA_API)
|
|
1335
|
+
ollamaApi = o.api;
|
|
1336
|
+
}
|
|
1337
|
+
if (typeof o.base_url === "string") {
|
|
1338
|
+
patch.baseUrl = o.base_url.trim();
|
|
1339
|
+
if (!process.env.COMFYUI_MCP_OLLAMA_BASE_URL)
|
|
1340
|
+
ollamaBaseUrl = patch.baseUrl || undefined;
|
|
1341
|
+
}
|
|
1342
|
+
if (Object.keys(patch).length) {
|
|
1343
|
+
setAgentSettings({ ollama: patch });
|
|
1344
|
+
ollamaChanged = true;
|
|
1345
|
+
// Endpoint may have moved — drop the cached probe backend so the next
|
|
1346
|
+
// readiness/model probe hits the NEW host/api with fresh deps.
|
|
1347
|
+
const pb = probeBackends.get("ollama");
|
|
1348
|
+
if (pb?.close)
|
|
1349
|
+
void pb.close().catch(() => { });
|
|
1350
|
+
probeBackends.delete("ollama");
|
|
1351
|
+
logger.info(`[panel-orchestrator] ollama config → model=${ollamaModel} api=${ollamaApi} host=${ollamaBaseUrl ?? "(default)"}`);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
if (ollamaChanged) {
|
|
1355
|
+
modelsByBackend.delete("ollama");
|
|
1356
|
+
pushModels(event.tab_id);
|
|
1357
|
+
}
|
|
1168
1358
|
bridge.push({ type: "ack", ok: true, kind: "config" }, event.tab_id);
|
|
1169
1359
|
return;
|
|
1170
1360
|
}
|
|
1361
|
+
// Panel-initiated provider secret (Settings › "Set API key…") — NO agent, no
|
|
1362
|
+
// chat: the panel paints its own masked input and ships the value here
|
|
1363
|
+
// directly, over the same loopback/token-gated bridge the agent-initiated
|
|
1364
|
+
// request_secret reply already rides. setAgentSecret enforces the allowlist
|
|
1365
|
+
// (OPENROUTER_API_KEY), persists 0600, and hydrates process.env immediately,
|
|
1366
|
+
// so the refreshed readiness frame flips the provider picker live — a user
|
|
1367
|
+
// can enable OpenRouter before ANY backend is ready (no chicken-and-egg).
|
|
1368
|
+
if (event.type === "set_secret" && event.tab_id) {
|
|
1369
|
+
const rawKey = event.key;
|
|
1370
|
+
const rawValue = event.value;
|
|
1371
|
+
const key = typeof rawKey === "string" ? rawKey : "";
|
|
1372
|
+
const value = typeof rawValue === "string" ? rawValue : "";
|
|
1373
|
+
let error;
|
|
1374
|
+
try {
|
|
1375
|
+
if (!value.trim())
|
|
1376
|
+
throw new Error("No token entered — nothing was saved.");
|
|
1377
|
+
setAgentSecret(key, value.trim());
|
|
1378
|
+
logger.info(`[panel-orchestrator] provider secret set from panel Settings: ${key} (redacted)`);
|
|
1379
|
+
}
|
|
1380
|
+
catch (err) {
|
|
1381
|
+
error = err instanceof Error ? err.message : String(err);
|
|
1382
|
+
}
|
|
1383
|
+
bridge.push({ type: "secret_saved", key, ok: !error, ...(error ? { error } : {}) }, event.tab_id);
|
|
1384
|
+
if (!error)
|
|
1385
|
+
pushReadiness(event.tab_id);
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1171
1388
|
// Model / effort picker: apply and confirm. Model switches live; an effort
|
|
1172
1389
|
// change restarts the session (resumed) so the conversation carries over.
|
|
1173
1390
|
if (event.type === "set_options" && event.tab_id) {
|
|
@@ -1447,6 +1664,7 @@ export async function runPanelOrchestrator() {
|
|
|
1447
1664
|
clearInterval(downloadTimer);
|
|
1448
1665
|
QueueMonitor.stop();
|
|
1449
1666
|
unsubscribeSecrets();
|
|
1667
|
+
unsubscribeAgentSecrets();
|
|
1450
1668
|
await manager.stopAll();
|
|
1451
1669
|
// Dispose the readiness-probe backends (kills each Codex/Gemini CLI child).
|
|
1452
1670
|
for (const pb of probeBackends.values()) {
|