comfyui-mcp 0.52.2 → 0.52.4
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 +13 -0
- package/dist/comfyui/client.js +35 -8
- package/dist/comfyui/client.js.map +1 -1
- package/dist/comfyui/fetch.js +44 -0
- package/dist/comfyui/fetch.js.map +1 -1
- package/dist/comfyui/json-guard.js +47 -3
- package/dist/comfyui/json-guard.js.map +1 -1
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/orchestrator/panel-tools.js +1511 -332
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/orchestrator/promoted-widget.js +101 -0
- package/dist/orchestrator/promoted-widget.js.map +1 -0
- package/dist/services/download-cache.js +10 -0
- package/dist/services/download-cache.js.map +1 -1
- package/dist/services/manager-node-search.js +190 -0
- package/dist/services/manager-node-search.js.map +1 -0
- package/dist/services/manifest.js +27 -60
- package/dist/services/manifest.js.map +1 -1
- package/dist/services/mid-command-remedy.js +31 -4
- package/dist/services/mid-command-remedy.js.map +1 -1
- package/dist/services/model-resolver.js +217 -49
- package/dist/services/model-resolver.js.map +1 -1
- package/dist/services/node-authoring.js +25 -12
- package/dist/services/node-authoring.js.map +1 -1
- package/dist/services/process-control.js +230 -3
- package/dist/services/process-control.js.map +1 -1
- package/dist/services/queue-monitor.js +51 -2
- package/dist/services/queue-monitor.js.map +1 -1
- package/dist/services/trainer-bootstrap.js +51 -5
- package/dist/services/trainer-bootstrap.js.map +1 -1
- package/dist/services/ui-bridge.js +89 -17
- package/dist/services/ui-bridge.js.map +1 -1
- package/dist/services/workspace-env.js +40 -0
- package/dist/services/workspace-env.js.map +1 -1
- package/dist/tools/node-pack.js +20 -4
- package/dist/tools/node-pack.js.map +1 -1
- package/dist/tools/process-control.js +1 -1
- package/dist/tools/process-control.js.map +1 -1
- package/dist/tools/vocabulary.js +350 -0
- package/dist/tools/vocabulary.js.map +1 -1
- package/dist/utils/origin.js +12 -2
- package/dist/utils/origin.js.map +1 -1
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ import { logger } from "../utils/logger.js";
|
|
|
13
13
|
import { downloadWithCache, findResumablePartial, probeRemoteModelPayload } from "./download-cache.js";
|
|
14
14
|
import { reportDownloadProgress } from "./download-progress.js";
|
|
15
15
|
import { modelNotFoundMessage } from "./model-root-scope.js";
|
|
16
|
-
import {
|
|
16
|
+
import { resolveModelsDirWithBases, parseModelsDirFromArgv, parseExtraModelPathsConfigsFromArgvRaw, hasUnresolvableRelativeModelDirFlag, isLiveAuthoritativeModelsDir, modelsDirNamedByServer, } from "./output-dir.js";
|
|
17
17
|
import { applyDownloadAuth, redactUrlForLogs, } from "./download-auth.js";
|
|
18
18
|
export const MODEL_SUBDIRS = [
|
|
19
19
|
"checkpoints",
|
|
@@ -1143,32 +1143,54 @@ category) {
|
|
|
1143
1143
|
// "visible" and gives it the qualified note instead. That is the right trade:
|
|
1144
1144
|
// the note names the file, the root, and the remedy, whereas the false positive
|
|
1145
1145
|
// is silent and the user discovers it at queue time.
|
|
1146
|
-
|
|
1146
|
+
//
|
|
1147
|
+
// One exception (#369, the 0.52.1 reports): an extra model root is vouched for by
|
|
1148
|
+
// the server's OWN command line when argv carries an ABSOLUTE
|
|
1149
|
+
// --extra-model-paths-config — that statement is independent of how the PRIMARY
|
|
1150
|
+
// root was derived, and a download redirected onto such a root
|
|
1151
|
+
// (resolveModelSubfolderWithLiveRoot) must be able to confirm here, or the file
|
|
1152
|
+
// lands exactly where the server reads and is STILL reported unconfirmed. With no
|
|
1153
|
+
// argv-named config the extra roots may come from the auto-loaded config beside
|
|
1154
|
+
// an INFERRED main.py — that inherits the inference and vouches for nothing, so
|
|
1155
|
+
// the early UNKNOWN stands. The unvouched primary still cannot support a
|
|
1156
|
+
// NEGATIVE answer below: unknown stays unknown, never "outside the live roots".
|
|
1157
|
+
const primaryNamed = modelsDirNamedByServer(dest.source);
|
|
1158
|
+
if (!primaryNamed &&
|
|
1159
|
+
!parseExtraModelPathsConfigsFromArgvRaw(dest.snapshot.argv).some((p) => isAbsolute(p))) {
|
|
1147
1160
|
return { inRoots: undefined };
|
|
1161
|
+
}
|
|
1148
1162
|
const liveRoot = resolve(dest.modelsDir);
|
|
1149
|
-
// A negative answer is only honest when everything it rests on could actually be
|
|
1150
|
-
// canonicalized; otherwise say UNKNOWN rather than accuse a correct placement.
|
|
1151
|
-
let fullyCanonical = target.ok;
|
|
1152
|
-
const primary = await canon(dest.modelsDir);
|
|
1153
|
-
fullyCanonical = fullyCanonical && primary.ok;
|
|
1154
|
-
if (under(primary))
|
|
1155
|
-
return { inRoots: true, liveRoot };
|
|
1156
1163
|
let extra;
|
|
1157
1164
|
try {
|
|
1158
1165
|
extra = await getLiveExtraModelRoots(dest.snapshot);
|
|
1159
1166
|
}
|
|
1160
1167
|
catch {
|
|
1161
|
-
|
|
1168
|
+
extra = undefined;
|
|
1162
1169
|
}
|
|
1163
1170
|
const wantCat = (category ?? "").trim().toLowerCase();
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1171
|
+
// A negative answer is only honest when everything it rests on could actually be
|
|
1172
|
+
// canonicalized; otherwise say UNKNOWN rather than accuse a correct placement.
|
|
1173
|
+
let fullyCanonical = target.ok;
|
|
1174
|
+
if (primaryNamed) {
|
|
1175
|
+
const primary = await canon(dest.modelsDir);
|
|
1176
|
+
fullyCanonical = fullyCanonical && primary.ok;
|
|
1177
|
+
if (under(primary))
|
|
1170
1178
|
return { inRoots: true, liveRoot };
|
|
1171
1179
|
}
|
|
1180
|
+
if (extra?.authoritative) {
|
|
1181
|
+
for (const r of extra.roots) {
|
|
1182
|
+
if (wantCat && String(r.category ?? "").trim().toLowerCase() !== wantCat)
|
|
1183
|
+
continue;
|
|
1184
|
+
const rc = await canon(r.dir);
|
|
1185
|
+
fullyCanonical = fullyCanonical && rc.ok;
|
|
1186
|
+
if (under(rc))
|
|
1187
|
+
return { inRoots: true, liveRoot };
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (!primaryNamed)
|
|
1191
|
+
return { inRoots: undefined, liveRoot };
|
|
1192
|
+
if (!extra)
|
|
1193
|
+
return { inRoots: undefined, liveRoot };
|
|
1172
1194
|
// The live roots are known and this path is in none of them.
|
|
1173
1195
|
if (!extra.authoritative)
|
|
1174
1196
|
return { inRoots: undefined, liveRoot };
|
|
@@ -1568,18 +1590,40 @@ export async function verifyLandedModel(targetPath, targetSubfolder, opts) {
|
|
|
1568
1590
|
`registers a category serving this file type, this server cannot load it at all.`,
|
|
1569
1591
|
};
|
|
1570
1592
|
}
|
|
1593
|
+
// #369 (0.52.1 recurrence) — RESOLVE THE ROOT **WITH** ITS PROVENANCE.
|
|
1594
|
+
//
|
|
1595
|
+
// This used to call `resolveModelsDir()`, which reports the models dir whatever
|
|
1596
|
+
// its `source` — including `configured-base`, the one value a reachable server
|
|
1597
|
+
// never vouched for. The verdict below then compared the landed path against it
|
|
1598
|
+
// and, finding the file inside, told the user the connected ComfyUI READS that
|
|
1599
|
+
// directory. But that root is the very one this download chose, so the file is
|
|
1600
|
+
// inside it BY CONSTRUCTION: the comparison is true for a correct destination and
|
|
1601
|
+
// for a stale second install alike, and cannot tell them apart. On the reporter's
|
|
1602
|
+
// macOS Comfy Desktop pair (two installs under ~/ComfyUI-Installs, the live one
|
|
1603
|
+
// reached through extra_model_paths) that produced "it is in the right place …
|
|
1604
|
+
// Do NOT move the file" for ~25 GB written where the running server never looks.
|
|
1605
|
+
//
|
|
1606
|
+
// So the SOURCE travels with the path, and only a server-NAMED root licenses the
|
|
1607
|
+
// claim. `modelsDirNamedByServer` — not `isLiveAuthoritativeModelsDir` — because
|
|
1608
|
+
// an `observed-root` is INFERRED from where the interpreter lives and is exactly
|
|
1609
|
+
// the inference that lands on the wrong install (#1562).
|
|
1571
1610
|
let liveModelsDir;
|
|
1611
|
+
let liveModelsDirNamedByServer = false;
|
|
1572
1612
|
try {
|
|
1573
|
-
|
|
1613
|
+
const dest = await resolveModelsDirWithBases();
|
|
1614
|
+
liveModelsDir = dest.modelsDir;
|
|
1615
|
+
liveModelsDirNamedByServer = modelsDirNamedByServer(dest.source);
|
|
1574
1616
|
}
|
|
1575
1617
|
catch {
|
|
1576
1618
|
liveModelsDir = undefined;
|
|
1619
|
+
liveModelsDirNamedByServer = false;
|
|
1577
1620
|
}
|
|
1578
1621
|
return {
|
|
1579
1622
|
verifiedPath,
|
|
1580
1623
|
...notVisibleVerdict({
|
|
1581
1624
|
verifiedPath,
|
|
1582
1625
|
liveModelsDir,
|
|
1626
|
+
liveModelsDirNamedByServer,
|
|
1583
1627
|
wanted,
|
|
1584
1628
|
category,
|
|
1585
1629
|
baseUrl: getComfyUIBaseUrl(),
|
|
@@ -1595,14 +1639,33 @@ export async function verifyLandedModel(targetPath, targetSubfolder, opts) {
|
|
|
1595
1639
|
/**
|
|
1596
1640
|
* The verdict for a file that is on disk but absent from the live listing (#1131).
|
|
1597
1641
|
*
|
|
1598
|
-
* "Not listed" has
|
|
1599
|
-
*
|
|
1600
|
-
*
|
|
1601
|
-
*
|
|
1602
|
-
*
|
|
1603
|
-
*
|
|
1604
|
-
*
|
|
1605
|
-
* that
|
|
1642
|
+
* "Not listed" has THREE causes, and the first two take OPPOSITE remedies:
|
|
1643
|
+
*
|
|
1644
|
+
* INSIDE a root the SERVER NAMED — not misplaced. The server simply has not
|
|
1645
|
+
* re-read that folder yet: ComfyUI caches its loader option lists and
|
|
1646
|
+
* invalidates them on the directory's mtime, so a check this soon after the
|
|
1647
|
+
* write routinely races it. Telling that user to "move the file into the
|
|
1648
|
+
* running server's models tree" names a directory the file is ALREADY in — a
|
|
1649
|
+
* remedy that cannot be followed, which is what #1131's reporter received.
|
|
1650
|
+
*
|
|
1651
|
+
* OUTSIDE that root — genuinely in the wrong place. Move it.
|
|
1652
|
+
*
|
|
1653
|
+
* INSIDE a root NOBODY VOUCHED FOR — unknown, and it must be SAID (#369, the
|
|
1654
|
+
* 0.52.1 recurrence). This third state used to be folded into the first,
|
|
1655
|
+
* because the containment test ran against whatever `resolveModelsDir()`
|
|
1656
|
+
* returned — including a `configured-base`/`observed-root` value the running
|
|
1657
|
+
* server never named. That root is the one the download itself resolved to, so
|
|
1658
|
+
* the landed file is inside it BY CONSTRUCTION and the test answers "inside"
|
|
1659
|
+
* for a correct destination and a stale second install alike. It is the same
|
|
1660
|
+
* shape as #1603: two states, one indistinguishable answer, and no way to say
|
|
1661
|
+
* "unknown". The reporter's macOS Comfy Desktop pair got "so it is in the right
|
|
1662
|
+
* place … Do NOT move the file" for ~25 GB written into an install the running
|
|
1663
|
+
* server (reached through extra_model_paths) never reads.
|
|
1664
|
+
*
|
|
1665
|
+
* The unverified branch is deliberately NOT the move remedy either: refusing to
|
|
1666
|
+
* assert readership is not evidence of misplacement, and #1131's harm was exactly
|
|
1667
|
+
* an unfollowable move instruction. It states both candidates, orders them by
|
|
1668
|
+
* likelihood, and names the ONE check that separates them.
|
|
1606
1669
|
*
|
|
1607
1670
|
* The DECISION lives here rather than at the call site so it is covered by the
|
|
1608
1671
|
* same tests as the wording; a branch chosen upstream and passed in as a boolean
|
|
@@ -1611,29 +1674,78 @@ export async function verifyLandedModel(targetPath, targetSubfolder, opts) {
|
|
|
1611
1674
|
export function notVisibleVerdict(args) {
|
|
1612
1675
|
const { verifiedPath, liveModelsDir, wanted, category, baseUrl } = args;
|
|
1613
1676
|
const insideLexically = liveModelsDir !== undefined && isUnderRoot(verifiedPath, liveModelsDir);
|
|
1614
|
-
const
|
|
1677
|
+
const rootIsServerNamed = args.liveModelsDirNamedByServer === true;
|
|
1678
|
+
// Containment ALONE is not readership. It counts only when the root it is
|
|
1679
|
+
// measured against came from the server (`rootIsServerNamed`), or when the
|
|
1680
|
+
// caller's canonical membership answer — which applies that same narrowing
|
|
1681
|
+
// itself, and additionally covers live extra roots and in-tree junctions —
|
|
1682
|
+
// already said yes.
|
|
1683
|
+
const insideLiveRoot = (insideLexically && rootIsServerNamed) || args.knownInsideLiveRoots === true;
|
|
1684
|
+
// On disk under the root we resolved, with nothing establishing that the server
|
|
1685
|
+
// reads it. Neither remedy is earned; say so, and name the check that decides.
|
|
1686
|
+
const insideUnverifiedRoot = insideLexically && !insideLiveRoot;
|
|
1687
|
+
if (insideUnverifiedRoot) {
|
|
1688
|
+
return {
|
|
1689
|
+
liveVisible: "not-visible",
|
|
1690
|
+
note: `The file IS on disk at ${verifiedPath}, inside ${liveModelsDir} — the models root this ` +
|
|
1691
|
+
`download resolved to. Whether the connected ComfyUI (${baseUrl}) READS that root is ` +
|
|
1692
|
+
`UNCONFIRMED: the running server never named its own models directory, so this root came ` +
|
|
1693
|
+
`from local configuration (COMFYUI_PATH / the saved default workspace) or was inferred ` +
|
|
1694
|
+
`from where the server's interpreter lives — and the file is inside it either way, ` +
|
|
1695
|
+
`because that is the root the download picked. That server does not list "${wanted}" ` +
|
|
1696
|
+
`under "${category}".\n\n` +
|
|
1697
|
+
`TWO very different things produce exactly this, and only one is a problem:\n` +
|
|
1698
|
+
` 1. a STALE LISTING — ComfyUI caches its loader options and invalidates them on the ` +
|
|
1699
|
+
`directory's mtime, so a check this soon after a write routinely races it; or\n` +
|
|
1700
|
+
` 2. a DIFFERENT INSTALL — the bytes are in a models tree the running server never ` +
|
|
1701
|
+
`scans, which is #369 and is how multi-GB downloads get reported as landed and are ` +
|
|
1702
|
+
`never seen again.\n\n` +
|
|
1703
|
+
`Do not move it on the strength of this message alone, and do not treat it as placed ` +
|
|
1704
|
+
`either. SEPARATE THE TWO: refresh the node/model definitions — install_comfyui ` +
|
|
1705
|
+
`(action:"refresh_nodes"), or the panel's refresh — then re-check with list_local_models. ` +
|
|
1706
|
+
`If it STILL does not appear, it is case 2: list_local_models (action:"list_paths") ` +
|
|
1707
|
+
`reports the roots the running server actually reads — move the file into the one for ` +
|
|
1708
|
+
`"${category}". To make future downloads verifiable, point COMFYUI_PATH at the install ` +
|
|
1709
|
+
`that is actually running, or launch it with an absolute --base-directory.`,
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1615
1712
|
return {
|
|
1616
1713
|
// Still not VISIBLE — we did not observe it in the listing, and #369 exists
|
|
1617
1714
|
// because an unobserved placement must not render as confirmed. The verdict
|
|
1618
1715
|
// is unchanged; what changes is the explanation and the remedy.
|
|
1619
1716
|
liveVisible: "not-visible",
|
|
1620
1717
|
note: insideLiveRoot
|
|
1621
|
-
?
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1718
|
+
? // The lexical phrasing NAMES the directory as one the server reads, so it may
|
|
1719
|
+
// only be used when the SERVER named that directory. Otherwise the membership
|
|
1720
|
+
// answer is what vouched for the placement (an extra root / a junction), and
|
|
1721
|
+
// the wording that does not attribute the root is the honest one.
|
|
1722
|
+
(insideLexically && rootIsServerNamed
|
|
1723
|
+
? `The file IS on disk at ${verifiedPath}, which is INSIDE the models directory the ` +
|
|
1724
|
+
`connected ComfyUI reads (${liveModelsDir}) — so it is in the right place. `
|
|
1725
|
+
: `The file IS on disk at ${verifiedPath}, which is inside a models tree the ` +
|
|
1726
|
+
`connected ComfyUI reads — reached through a link/junction` +
|
|
1727
|
+
`${liveModelsDir ? ` under ${liveModelsDir}` : ""}, so its real path lying ` +
|
|
1728
|
+
`outside that directory is the layout working as intended, not a misplacement. `) +
|
|
1729
|
+
`That server does not list "${wanted}" under "${category}" YET, which almost always ` +
|
|
1730
|
+
`means its cached loader options have not been re-read since the write (ComfyUI ` +
|
|
1731
|
+
`invalidates them on the directory's mtime). Do NOT move the file. Refresh the ` +
|
|
1732
|
+
`node/model definitions — install_comfyui (action:"refresh_nodes"), or the panel's ` +
|
|
1733
|
+
`refresh — or restart ComfyUI, then check list_local_models again.`
|
|
1633
1734
|
: `The file IS on disk at ${verifiedPath}, but the connected ComfyUI ` +
|
|
1634
1735
|
`(${baseUrl}) does NOT list "${wanted}" under "${category}" — it will not be ` +
|
|
1635
1736
|
`usable in a workflow from there.` +
|
|
1636
|
-
|
|
1737
|
+
// Same rule as above: name it as WHAT THE SERVER READS only when the server
|
|
1738
|
+
// named it. An unvouched root is still worth printing — it is where the
|
|
1739
|
+
// bytes are — but as the destination this download resolved to, not as a
|
|
1740
|
+
// fact about the server (#369).
|
|
1741
|
+
(liveModelsDir
|
|
1742
|
+
? rootIsServerNamed
|
|
1743
|
+
? ` The models directory that server reads is ${liveModelsDir}.`
|
|
1744
|
+
: ` This download resolved its destination root to ${liveModelsDir} from local ` +
|
|
1745
|
+
`configuration — the running server never named its own models directory, so that ` +
|
|
1746
|
+
`is not a confirmed answer for where the file belongs; list_local_models ` +
|
|
1747
|
+
`(action:"list_paths") reports the roots it actually reads.`
|
|
1748
|
+
: "") +
|
|
1637
1749
|
" Move the file into the running server's models tree (or point COMFYUI_PATH at that install and re-download).",
|
|
1638
1750
|
};
|
|
1639
1751
|
}
|
|
@@ -1707,28 +1819,84 @@ export async function resolveModelSubfolderWithLiveRoot(targetSubfolder) {
|
|
|
1707
1819
|
const { modelsDir, baseDirs, snapshot, source } = await resolveModelsDirWithBases({
|
|
1708
1820
|
targetCategory: escapes ? "" : categoryOf(normalizedSub),
|
|
1709
1821
|
});
|
|
1710
|
-
|
|
1711
|
-
|
|
1822
|
+
let modelsRoot = resolve(modelsDir);
|
|
1823
|
+
let targetDir = resolve(modelsRoot, raw);
|
|
1712
1824
|
if (targetDir !== modelsRoot && !targetDir.startsWith(modelsRoot + sep)) {
|
|
1713
1825
|
throw new ModelError(`Refusing to write outside the models directory: ${raw}`);
|
|
1714
1826
|
}
|
|
1715
|
-
//
|
|
1716
|
-
//
|
|
1717
|
-
//
|
|
1718
|
-
//
|
|
1719
|
-
//
|
|
1720
|
-
|
|
1827
|
+
// #369 (the 0.52.1 reports) — a root the SERVER NAMED beats our best inference.
|
|
1828
|
+
//
|
|
1829
|
+
// When the primary root came from local configuration or from an INFERRED live
|
|
1830
|
+
// root, every guard below can do no better than fail OPEN on an empty tree
|
|
1831
|
+
// (#1147's deliberate trade: an empty listing contradicts nothing), so a stale
|
|
1832
|
+
// second install received the write whenever its evidence was silent — the
|
|
1833
|
+
// 0.52.1 reports had gigabytes land in the non-running install while the
|
|
1834
|
+
// connected Desktop server's models lived in the shared root its
|
|
1835
|
+
// --extra-model-paths-config declares (the very root list_paths shows).
|
|
1836
|
+
//
|
|
1837
|
+
// That config flag is the server's OWN command line naming a root it reads this
|
|
1838
|
+
// category from — a statement, not an inference — so it outranks the unvouched
|
|
1839
|
+
// primary and the download goes THERE instead. The gate is deliberately the
|
|
1840
|
+
// exact evidence: an ABSOLUTE --extra-model-paths-config in argv (a relative
|
|
1841
|
+
// one cannot be located from this process and fails closed, per extra-paths),
|
|
1842
|
+
// an AUTHORITATIVE read of it, and a MODEL category (never custom_nodes — a
|
|
1843
|
+
// download must not become a Python import). Anything less keeps the existing
|
|
1844
|
+
// resolution and its corroboration guards untouched.
|
|
1845
|
+
const serverNamedPrimary = modelsDirNamedByServer(source);
|
|
1846
|
+
let redirectedToExtraRoot = false;
|
|
1847
|
+
if (!serverNamedPrimary && snapshot.reachable && !isRemoteMode()) {
|
|
1848
|
+
const category = categoryOf(normalizedSub).toLowerCase();
|
|
1849
|
+
const argvNamesConfig = parseExtraModelPathsConfigsFromArgvRaw(snapshot.argv).some((p) => isAbsolute(p));
|
|
1850
|
+
if (argvNamesConfig && category && !NON_MODEL_EXTRA_CATEGORIES.has(category)) {
|
|
1851
|
+
const extra = await getLiveExtraModelRoots(snapshot);
|
|
1852
|
+
const namedRoot = extra.authoritative
|
|
1853
|
+
? extra.roots.find((r) => r.category.trim().toLowerCase() === category)
|
|
1854
|
+
: undefined;
|
|
1855
|
+
if (namedRoot) {
|
|
1856
|
+
const extraRoot = resolve(namedRoot.dir);
|
|
1857
|
+
const redirected = resolve(extraRoot, subfolderRemainder(normalizedSub));
|
|
1858
|
+
if (redirected !== extraRoot && !redirected.startsWith(extraRoot + sep)) {
|
|
1859
|
+
throw new ModelError(`Refusing to write outside the models directory: ${raw}`);
|
|
1860
|
+
}
|
|
1861
|
+
logger.info(`Downloading into the extra model root the connected server named for "${category}" ` +
|
|
1862
|
+
`(${extraRoot}) instead of the unvouched primary root (${modelsRoot}) — the root the ` +
|
|
1863
|
+
`server's --extra-model-paths-config declares is where it actually reads (#369).`);
|
|
1864
|
+
modelsRoot = extraRoot;
|
|
1865
|
+
targetDir = redirected;
|
|
1866
|
+
redirectedToExtraRoot = true;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
if (!redirectedToExtraRoot) {
|
|
1871
|
+
// The root did NOT come from the running server (it could not tell us where it
|
|
1872
|
+
// lives, so we fell back to local config). Before writing, check the one thing
|
|
1873
|
+
// that can still expose a stale-install destination: the server's own listing for
|
|
1874
|
+
// this category. #369 shipped 4.88 GB into a directory the live ComfyUI had never
|
|
1875
|
+
// heard of; refusing here costs nothing and saves the transfer. (A server-named
|
|
1876
|
+
// primary is not skipped here either — the guard itself decides, because a named
|
|
1877
|
+
// root that does not EXIST locally, e.g. a container-side --models-directory,
|
|
1878
|
+
// still gets the cheap check. Only the REDIRECTED destination is exempt: the
|
|
1879
|
+
// server's own config named it, so disagreeing with our inference is moot.)
|
|
1880
|
+
await assertDestinationVisibleToLiveServer(modelsRoot, raw, source, snapshot);
|
|
1881
|
+
}
|
|
1721
1882
|
// Path-string containment (above) is not enough: an EXISTING symlink somewhere
|
|
1722
1883
|
// between modelsRoot and targetDir could redirect the real write OUTSIDE the
|
|
1723
1884
|
// models dir. Because THIS resolver is the single canonical write-target for
|
|
1724
1885
|
// every local download (startDownloadJob keying AND downloadModel's write), the
|
|
1725
1886
|
// guard belongs here — co-located with the resolution the write actually uses —
|
|
1726
1887
|
// so it can never diverge from a caller's separate pre-validation (e.g.
|
|
1727
|
-
// apply_manifest resolving the root a second time; #490 codex review).
|
|
1888
|
+
// apply_manifest resolving the root a second time; #490 codex review). Runs on
|
|
1889
|
+
// the REDIRECTED root too: its code-root veto is what keeps a malicious or
|
|
1890
|
+
// miswritten extra-paths entry from turning this into a write into custom_nodes.
|
|
1728
1891
|
await assertNoEscapingSymlinkAncestor(modelsRoot, targetDir, raw, baseDirs, snapshot);
|
|
1729
1892
|
return {
|
|
1730
1893
|
targetDir,
|
|
1731
|
-
|
|
1894
|
+
// A redirected destination is bound to the EXTRA root, but the writer's
|
|
1895
|
+
// mid-flight swap check compares PRIMARY roots (currentLiveModelsRoot), so
|
|
1896
|
+
// there is no honest value to bind here — leave it unknown, exactly like any
|
|
1897
|
+
// other non-server-named primary. The post-write check still verifies the
|
|
1898
|
+
// landed file against the connected server.
|
|
1899
|
+
liveRootAtResolve: !redirectedToExtraRoot && isLiveAuthoritativeModelsDir(source) ? modelsRoot : undefined,
|
|
1732
1900
|
};
|
|
1733
1901
|
}
|
|
1734
1902
|
/**
|