comfyui-mcp 0.52.35 → 0.52.36
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.
|
@@ -1460,16 +1460,7 @@ export function classifyTabReconnect({ serverReady, baselineCaptured, tabBack, }
|
|
|
1460
1460
|
export function classifyRestartFallbackTarget({ headlessBase, panelBase, observedOrigin = null, }) {
|
|
1461
1461
|
if (panelBase != null && sameHttpBase(headlessBase, panelBase))
|
|
1462
1462
|
return { kind: "same" };
|
|
1463
|
-
const dnsAmbiguous =
|
|
1464
|
-
if (!u)
|
|
1465
|
-
return false;
|
|
1466
|
-
try {
|
|
1467
|
-
return new URL(u).hostname.toLowerCase().replace(/^\[|\]$/g, "") === "localhost";
|
|
1468
|
-
}
|
|
1469
|
-
catch {
|
|
1470
|
-
return false;
|
|
1471
|
-
}
|
|
1472
|
-
};
|
|
1463
|
+
const dnsAmbiguous = isDnsAmbiguousLoopback;
|
|
1473
1464
|
const originMismatch = observedOrigin != null &&
|
|
1474
1465
|
!dnsAmbiguous(observedOrigin) &&
|
|
1475
1466
|
!dnsAmbiguous(headlessBase) &&
|
|
@@ -1597,7 +1588,11 @@ function restartRefusedPreservingBinding(ctx, note) {
|
|
|
1597
1588
|
note,
|
|
1598
1589
|
});
|
|
1599
1590
|
}
|
|
1600
|
-
function unboundLocalRestartRefusalNote(ctx, panelBase
|
|
1591
|
+
function unboundLocalRestartRefusalNote(ctx, panelBase,
|
|
1592
|
+
// panel#1546 — the blocker from the SAME capture that produced `panelBase`.
|
|
1593
|
+
// Passed in rather than re-derived: a second call to the classifier could read
|
|
1594
|
+
// a binding that changed in between and explain a refusal that did not happen.
|
|
1595
|
+
blocker = null) {
|
|
1601
1596
|
return ("Refusing to restart ComfyUI: I could not confirm that this panel's ComfyUI is " +
|
|
1602
1597
|
"the local instance I can account for, so I cannot tell which server the restart " +
|
|
1603
1598
|
"would stop — and it STOPS it, relying on whatever supervises it to start it " +
|
|
@@ -1623,6 +1618,10 @@ function unboundLocalRestartRefusalNote(ctx, panelBase) {
|
|
|
1623
1618
|
panelBase,
|
|
1624
1619
|
observedOrigin: ctx.bridge?.tabServerOrigin?.(ctx.tabId) ?? null,
|
|
1625
1620
|
}) +
|
|
1621
|
+
// panel#1546 — and say WHICH proof was missing. Without this, a `localhost`
|
|
1622
|
+
// origin (curable in one step), an absent Origin (cured by updating the
|
|
1623
|
+
// panel) and a relayed tab (not curable here at all) were the same sentence.
|
|
1624
|
+
restartIdentityBlockerNote(blocker) +
|
|
1626
1625
|
// artokun/comfyui-mcp-panel#769 — a REMOTE ComfyUI reached over a
|
|
1627
1626
|
// tunnel or port-forward has a LOOPBACK host, and remoteUrlActive is
|
|
1628
1627
|
// derived from exactly that (`forceRemote || !isLoopbackHost(host)`).
|
|
@@ -1848,16 +1847,28 @@ function objectInfoHostMismatchMessage(err) {
|
|
|
1848
1847
|
|
|
1849
1848
|
Node definitions are read from COMFYUI_URL (${configured}) for a pack/path/inline source. That host did not answer /object_info.`;
|
|
1850
1849
|
}
|
|
1851
|
-
|
|
1850
|
+
/** The SINGLE decision. `captureRebootHealthBase` returns `.base`; the refusal
|
|
1851
|
+
* note reads `.blocker`. Two readers of one evidence set that cannot disagree —
|
|
1852
|
+
* the same reason `classifyRestartFallbackTarget` was extracted for #1593. */
|
|
1853
|
+
function resolveRebootHealthBinding(ctx) {
|
|
1854
|
+
const no = (blocker) => ({ base: null, blocker });
|
|
1852
1855
|
if (isCloudMode() || isRemoteMode())
|
|
1853
|
-
return
|
|
1856
|
+
return no({ kind: "not-local-mode" });
|
|
1854
1857
|
const bootBase = getBootLocalComfyUIBaseUrl(); // server-authorized, hello-immutable
|
|
1855
|
-
if (!bootBase
|
|
1856
|
-
return
|
|
1858
|
+
if (!bootBase)
|
|
1859
|
+
return no({ kind: "no-boot-base" });
|
|
1860
|
+
// `localhost` fails isLoopbackOrigin (loopbackFamily returns null for it by
|
|
1861
|
+
// design), so it lands here — NOT in the origin comparison below, which never
|
|
1862
|
+
// sees an ambiguous boot base. Naming it separately is the difference between
|
|
1863
|
+
// "your ComfyUI is not on this machine" (false) and a config line to change.
|
|
1864
|
+
if (isDnsAmbiguousLoopback(bootBase))
|
|
1865
|
+
return no({ kind: "boot-base-dns-ambiguous", bootBase });
|
|
1866
|
+
if (!isLoopbackOrigin(bootBase))
|
|
1867
|
+
return no({ kind: "boot-base-not-loopback", bootBase });
|
|
1857
1868
|
const base = bootBase.replace(/\/+$/, "");
|
|
1858
1869
|
// Server-trusted provenance: the tab arrived on the token-less loopback listener.
|
|
1859
1870
|
if (ctx.bridge?.tabIsLocal?.(ctx.tabId) !== true)
|
|
1860
|
-
return
|
|
1871
|
+
return no({ kind: "tab-not-local" });
|
|
1861
1872
|
// And the rebooted tab must provably front THAT SAME boot instance. Use the SERVER-
|
|
1862
1873
|
// OBSERVED handshake Origin (tabServerOrigin) — the browser sets it on the WS upgrade
|
|
1863
1874
|
// and blocks page JS from forging it — NOT the spoofable client hello.comfyui_url
|
|
@@ -1874,13 +1885,143 @@ function captureRebootHealthBase(ctx) {
|
|
|
1874
1885
|
// (loopbackFamily → null), so it never matches a concrete literal and this returns null
|
|
1875
1886
|
// (coordinator P0). A different instance / family / path / absent Origin → null too.
|
|
1876
1887
|
const origin = ctx.bridge?.tabServerOrigin?.(ctx.tabId);
|
|
1877
|
-
if (!sameHttpBase(origin, base))
|
|
1878
|
-
|
|
1888
|
+
if (!sameHttpBase(origin, base)) {
|
|
1889
|
+
// SAME verdict as before — only the reason is now carried out. Ordered so each
|
|
1890
|
+
// kind names the FIRST thing that made a match impossible, because that is the
|
|
1891
|
+
// one the reader can act on.
|
|
1892
|
+
if (!origin)
|
|
1893
|
+
return no({ kind: "origin-absent", bootBase: base });
|
|
1894
|
+
return no(classifyOriginBlocker(origin, base));
|
|
1895
|
+
}
|
|
1879
1896
|
// Return a CONNECTABLE probe URL bound to the SAME concrete family identity matched
|
|
1880
1897
|
// above: a wildcard-bound (0.0.0.0/::) local ComfyUI is reachable on loopback, so probe
|
|
1881
1898
|
// the family literal at that port (127.0.0.1 / [::1]). The probe (and the auth headers
|
|
1882
1899
|
// it carries) can therefore never cross to a different-family instance.
|
|
1883
|
-
return loopbackProbeUrl(base);
|
|
1900
|
+
return { base: loopbackProbeUrl(base), blocker: null };
|
|
1901
|
+
}
|
|
1902
|
+
function captureRebootHealthBase(ctx) {
|
|
1903
|
+
return resolveRebootHealthBinding(ctx).base;
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* panel#1546 — WHY a present Origin failed to match the boot base.
|
|
1907
|
+
*
|
|
1908
|
+
* Ordered so each verdict names the FIRST thing that made a match impossible, and
|
|
1909
|
+
* NEVER blames the DNS ambiguity for something the ambiguity did not cause. A
|
|
1910
|
+
* `localhost` origin on a DIFFERENT PORT is a different server and is reported as
|
|
1911
|
+
* one; a `localhost` origin against a basePath mount is unprovable even with the
|
|
1912
|
+
* host resolved, so it stays the path verdict. Only the case where resolving
|
|
1913
|
+
* `localhost` would ACTUALLY settle it — same port, no mount, exactly one
|
|
1914
|
+
* ambiguous side — gets the DNS answer and its one-step remedy.
|
|
1915
|
+
*
|
|
1916
|
+
* Exported for test: the boot base is fixed at process start, so the
|
|
1917
|
+
* boot-side-ambiguous direction has no handler-driven fixture.
|
|
1918
|
+
*/
|
|
1919
|
+
export function classifyOriginBlocker(origin, bootBase) {
|
|
1920
|
+
// Callers reach here only past `isLoopbackOrigin(bootBase)`, which rejects
|
|
1921
|
+
// `localhost` — so the boot base is always a concrete literal and the panel is
|
|
1922
|
+
// the only side that can be ambiguous. Asserted, not assumed: an ambiguous boot
|
|
1923
|
+
// base would make the comparison below meaningless rather than merely wrong.
|
|
1924
|
+
if (isDnsAmbiguousLoopback(origin) && !isDnsAmbiguousLoopback(bootBase)) {
|
|
1925
|
+
let sameSpot = false;
|
|
1926
|
+
try {
|
|
1927
|
+
const o = new URL(origin);
|
|
1928
|
+
const b = new URL(bootBase);
|
|
1929
|
+
// A handshake Origin is pathless BY CONSTRUCTION — normalizeHandshakeOrigin
|
|
1930
|
+
// rebuilds it from protocol/hostname/port and relayIdentityOrigin uses
|
|
1931
|
+
// URL.origin — so only the BOOT base can carry a mount, and the DNS answer
|
|
1932
|
+
// can never settle one. Ports must already agree; resolving a hostname never
|
|
1933
|
+
// reconciles :8188 with :8189. Compare EFFECTIVE ports: the same normalizer
|
|
1934
|
+
// fills in 80/443 explicitly, so a default-port boot base ("") would
|
|
1935
|
+
// otherwise read as a port mismatch against an origin that says "80".
|
|
1936
|
+
const port = (u) => u.port || (u.protocol === "https:" ? "443" : "80");
|
|
1937
|
+
sameSpot =
|
|
1938
|
+
o.protocol === b.protocol &&
|
|
1939
|
+
port(o) === port(b) &&
|
|
1940
|
+
b.pathname.replace(/\/+$/, "") === "";
|
|
1941
|
+
}
|
|
1942
|
+
catch {
|
|
1943
|
+
sameSpot = false;
|
|
1944
|
+
}
|
|
1945
|
+
if (sameSpot)
|
|
1946
|
+
return { kind: "origin-dns-ambiguous", bootBase, origin };
|
|
1947
|
+
}
|
|
1948
|
+
// The Origin is pathless by construction, so a boot base under a basePath mount
|
|
1949
|
+
// can be same-ORIGIN yet unprovable as the same INSTANCE. Distinguish that from a
|
|
1950
|
+
// genuinely different host:port, which is the #851 wrong-server case.
|
|
1951
|
+
if (sameHttpOrigin(origin, bootBase))
|
|
1952
|
+
return { kind: "origin-path-ambiguous", bootBase, origin };
|
|
1953
|
+
return { kind: "origin-different", bootBase, origin };
|
|
1954
|
+
}
|
|
1955
|
+
/** True when a URL's host is the DNS-ambiguous `localhost` — it names a loopback
|
|
1956
|
+
* service without revealing which family the browser actually reached, which is
|
|
1957
|
+
* exactly why `loopbackFamily` refuses it (coordinator P0, #1233). Shared by the
|
|
1958
|
+
* blocker classifier and `classifyRestartFallbackTarget` so "ambiguous" means one
|
|
1959
|
+
* thing in this file. */
|
|
1960
|
+
function isDnsAmbiguousLoopback(rawUrl) {
|
|
1961
|
+
if (!rawUrl)
|
|
1962
|
+
return false;
|
|
1963
|
+
try {
|
|
1964
|
+
return new URL(rawUrl).hostname.toLowerCase().replace(/^\[|\]$/g, "") === "localhost";
|
|
1965
|
+
}
|
|
1966
|
+
catch {
|
|
1967
|
+
return false;
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* panel#1546 — the one sentence that turns the refusal from a dead end into a step.
|
|
1972
|
+
*
|
|
1973
|
+
* The reporter's panel was attached, local and graph-tool ready; the refusal named
|
|
1974
|
+
* neither what was missing nor how to supply it, so the filed diagnosis was
|
|
1975
|
+
* "target identity cannot be confirmed" with no next action. Each blocker has a
|
|
1976
|
+
* DIFFERENT next action, and `origin-dns-ambiguous` — a browser at
|
|
1977
|
+
* `localhost:8188` against a `127.0.0.1:8188` boot base, the ordinary local setup
|
|
1978
|
+
* this file's own comments already flag as returning null — has a one-step one.
|
|
1979
|
+
*
|
|
1980
|
+
* Deliberately NOT a fix for the underlying ambiguity. Proving that a `localhost`
|
|
1981
|
+
* tab reached the boot family would mean trusting a resolution this process did
|
|
1982
|
+
* not observe, and #1233 recorded why that is a P0. Naming the cause costs no
|
|
1983
|
+
* soundness; guessing it would.
|
|
1984
|
+
*/
|
|
1985
|
+
export function restartIdentityBlockerNote(blocker) {
|
|
1986
|
+
if (!blocker)
|
|
1987
|
+
return "";
|
|
1988
|
+
const why = " WHY I could not confirm it: ";
|
|
1989
|
+
switch (blocker.kind) {
|
|
1990
|
+
case "not-local-mode":
|
|
1991
|
+
return `${why}this target is configured as remote/cloud, so there is no local process here to account for.`;
|
|
1992
|
+
case "no-boot-base":
|
|
1993
|
+
return `${why}no local ComfyUI address was resolvable when this server started, so there is no boot instance to compare against.`;
|
|
1994
|
+
case "boot-base-not-loopback":
|
|
1995
|
+
return (`${why}the ComfyUI this server booted against (${blocker.bootBase}) is not a loopback address, ` +
|
|
1996
|
+
"so it is not a process on this machine that I can account for.");
|
|
1997
|
+
case "boot-base-dns-ambiguous":
|
|
1998
|
+
return (`${why}this server is configured with COMFYUI_URL=${blocker.bootBase}. That IS loopback, but ` +
|
|
1999
|
+
`"localhost" does not say which address it resolves to — 127.0.0.1 and ::1 can be DIFFERENT ` +
|
|
2000
|
+
"ComfyUI instances on the same port — so I cannot tie it to a specific local process, and no " +
|
|
2001
|
+
"panel tab can be matched against it. This disables the panel restart for every tab, not just " +
|
|
2002
|
+
"this one. FIX: set COMFYUI_URL to the literal your ComfyUI actually listens on " +
|
|
2003
|
+
"(http://127.0.0.1:<port>, or http://[::1]:<port> for an IPv6-only bind) and restart this server.");
|
|
2004
|
+
case "tab-not-local":
|
|
2005
|
+
return (`${why}this panel tab did not arrive on the local loopback listener — it reached me over a relay, ` +
|
|
2006
|
+
"tunnel, LAN address or pairing link, so I cannot treat the ComfyUI behind it as one of mine. " +
|
|
2007
|
+
"Restart it from the machine it actually runs on.");
|
|
2008
|
+
case "origin-absent":
|
|
2009
|
+
return (`${why}this tab's WebSocket handshake carried no Origin, so there is nothing to check against ` +
|
|
2010
|
+
`the boot instance (${blocker.bootBase}). An older panel build does this; updating the panel restores the proof.`);
|
|
2011
|
+
case "origin-dns-ambiguous":
|
|
2012
|
+
return (`${why}this panel is open at ${blocker.origin}, and "localhost" does not say which loopback ` +
|
|
2013
|
+
`address the browser actually reached — it can be 127.0.0.1 or ::1, and those can be DIFFERENT ` +
|
|
2014
|
+
`ComfyUI instances on the same port. The boot instance I account for is ${blocker.bootBase}, so ` +
|
|
2015
|
+
`I cannot show the two are one server. FIX: open the panel at ${loopbackProbeUrl(blocker.bootBase)} ` +
|
|
2016
|
+
`— the same ComfyUI, addressed by its literal.`);
|
|
2017
|
+
case "origin-path-ambiguous":
|
|
2018
|
+
return (`${why}the boot instance is mounted under a path (${blocker.bootBase}) and a handshake Origin ` +
|
|
2019
|
+
`carries no path (${blocker.origin}), so this tab could be fronting a different ComfyUI at the ` +
|
|
2020
|
+
"same host and port. I will not stop a server on that.");
|
|
2021
|
+
case "origin-different":
|
|
2022
|
+
return (`${why}this panel is open at ${blocker.origin}, which is not the ComfyUI this server booted ` +
|
|
2023
|
+
`against (${blocker.bootBase}) — a different server, so stopping mine would not restart yours.`);
|
|
2024
|
+
}
|
|
1884
2025
|
}
|
|
1885
2026
|
/**
|
|
1886
2027
|
* #1671 — the configured LOCAL boot instance, when that is a known loopback
|
|
@@ -13238,7 +13379,8 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
13238
13379
|
// of the current binding: a silent rebind here would confirm a restart of
|
|
13239
13380
|
// a tab the user was not working in. Healing belongs on the refusal path.
|
|
13240
13381
|
if (!isRemoteMode() && !isCloudMode()) {
|
|
13241
|
-
const
|
|
13382
|
+
const identityBinding = resolveRebootHealthBinding(ctx);
|
|
13383
|
+
const identityHealthBase = identityBinding.base;
|
|
13242
13384
|
const identityBound = identityHealthBase != null &&
|
|
13243
13385
|
sameHttpBase(getComfyUIBaseUrl(), identityHealthBase);
|
|
13244
13386
|
if (!identityBound) {
|
|
@@ -13246,7 +13388,7 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
13246
13388
|
? ctx.bridge.canReach(ctx.tabId) === true
|
|
13247
13389
|
: true;
|
|
13248
13390
|
if (tabStillHere) {
|
|
13249
|
-
return restartRefusedPreservingBinding(ctx, unboundLocalRestartRefusalNote(ctx, identityHealthBase));
|
|
13391
|
+
return restartRefusedPreservingBinding(ctx, unboundLocalRestartRefusalNote(ctx, identityHealthBase, identityBinding.blocker));
|
|
13250
13392
|
}
|
|
13251
13393
|
// Tab is gone: confirm will be unreachable and #1671 crash-recovery
|
|
13252
13394
|
// decides whether a headless restart can still be accounted for.
|
|
@@ -13876,7 +14018,8 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
13876
14018
|
// can be proven. They keep restart_comfyui, and the note says so. Weighed
|
|
13877
14019
|
// against #814 — where exactly such a user's server was stopped and never came
|
|
13878
14020
|
// back — declining is the recoverable side.
|
|
13879
|
-
const
|
|
14021
|
+
const preflightBinding = resolveRebootHealthBinding(ctx);
|
|
14022
|
+
const preflightHealthBase = preflightBinding.base;
|
|
13880
14023
|
const preflightBound = preflightHealthBase != null &&
|
|
13881
14024
|
sameHttpBase(getComfyUIBaseUrl(), preflightHealthBase);
|
|
13882
14025
|
// #848: what the instance was OBSERVED running with, taken from the preflight
|
|
@@ -13903,7 +14046,7 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
13903
14046
|
// Identity was proven before the card; landing here means the binding
|
|
13904
14047
|
// was lost during the confirm wait. Same refusal, and the tab that is
|
|
13905
14048
|
// still here must stay usable (#1819).
|
|
13906
|
-
return restartRefusedPreservingBinding(ctx, unboundLocalRestartRefusalNote(ctx, preflightHealthBase));
|
|
14049
|
+
return restartRefusedPreservingBinding(ctx, unboundLocalRestartRefusalNote(ctx, preflightHealthBase, preflightBinding.blocker));
|
|
13907
14050
|
}
|
|
13908
14051
|
if (preflightBound) {
|
|
13909
14052
|
// Snapshot the target GENERATION at the decision (r11): a final-state
|