comfyui-mcp 0.52.113 → 0.52.114
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.
|
@@ -3987,31 +3987,6 @@ function isObjectInfoUnavailableRefusal(res) {
|
|
|
3987
3987
|
return false;
|
|
3988
3988
|
return /object_info is unavailable|cannot verify (?:the )?node type/i.test(textOfToolResult(res));
|
|
3989
3989
|
}
|
|
3990
|
-
/**
|
|
3991
|
-
* #2004 — panel_set_widget refused an installed backend node because both
|
|
3992
|
-
* whole-schema routes exhausted their budget. Matched on the panel's own
|
|
3993
|
-
* wording (assertTypeAgainstFreshBackend / objectInfoOracleFailureNote) so a
|
|
3994
|
-
* miss costs a missing hint and a false positive costs an extra paragraph.
|
|
3995
|
-
* Used ONLY to APPEND advice to a refusal the panel already made — never to
|
|
3996
|
-
* authorize a write, and never to consult COMFYUI_URL (#1359).
|
|
3997
|
-
*/
|
|
3998
|
-
function isObjectInfoStarvationRefusal(res) {
|
|
3999
|
-
if (!res.isError)
|
|
4000
|
-
return false;
|
|
4001
|
-
const text = textOfToolResult(res);
|
|
4002
|
-
return (/no usable \/object_info schema was obtained/i.test(text) &&
|
|
4003
|
-
(/Tried \d+ routes:/i.test(text) ||
|
|
4004
|
-
(/api\.getNodeDefs\(\) did not answer/i.test(text) &&
|
|
4005
|
-
/GET \/object_info did not answer/i.test(text))));
|
|
4006
|
-
}
|
|
4007
|
-
function objectInfoStarvationNote() {
|
|
4008
|
-
return (`\n\nThis is a live /object_info refresh TIMEOUT, not a missing node. ` +
|
|
4009
|
-
`The write was refused before it mutated anything because the schema routes ` +
|
|
4010
|
-
`exhausted their budget. The node is already on the canvas, so this type is in use. ` +
|
|
4011
|
-
`Do NOT reinstall a pack. Retry panel_set_widget in a moment; if it keeps happening, ` +
|
|
4012
|
-
`the tab's whole-schema snapshot was never populated — wait for a successful ` +
|
|
4013
|
-
`panel_refresh_nodes, or reload the ComfyUI tab.`);
|
|
4014
|
-
}
|
|
4015
3990
|
/** #2004 — the panel's 13s save budget fired while the userdata write was still in flight. */
|
|
4016
3991
|
function isWorkflowSaveBudgetTimeout(res) {
|
|
4017
3992
|
if (!res.isError)
|
|
@@ -4137,6 +4112,70 @@ function isObjectInfoBudgetRefusal(res) {
|
|
|
4137
4112
|
return (isObjectInfoUnavailableRefusal(res) ||
|
|
4138
4113
|
/no usable \/?object_info|no whole (?:\/object_info|schema)|object_info_fetch_failed|did not answer within its \d+ms share|exceeded \d+ms share/i.test(textOfToolResult(res)));
|
|
4139
4114
|
}
|
|
4115
|
+
/**
|
|
4116
|
+
* #2274 — share one authoritative node-definition refresh per live panel tab.
|
|
4117
|
+
*
|
|
4118
|
+
* A schema-guarded widget write can time out its own /object_info routes while a
|
|
4119
|
+
* refresh started by another panel command is still registering definitions. Starting
|
|
4120
|
+
* one refresh per waiting write recreates the same stampede and makes every caller
|
|
4121
|
+
* refuse before the already-useful refresh can land. The promise is keyed to the live
|
|
4122
|
+
* tab incarnation, so a reconnect cannot let an old refresh authorize a new tab.
|
|
4123
|
+
*
|
|
4124
|
+
* The refresh is only a coordination primitive. Callers must still require the panel's
|
|
4125
|
+
* explicit `refreshed:true` result before retrying a write; a timeout, malformed reply,
|
|
4126
|
+
* `refresh_still_running`, or any other non-authoritative result remains fail-closed.
|
|
4127
|
+
*/
|
|
4128
|
+
const panelSchemaRefreshes = new Map();
|
|
4129
|
+
function sharedPanelSchemaRefresh(ctx) {
|
|
4130
|
+
const key = panelSchemaKey(ctx);
|
|
4131
|
+
const existing = panelSchemaRefreshes.get(key);
|
|
4132
|
+
if (existing)
|
|
4133
|
+
return existing.promise;
|
|
4134
|
+
const token = {};
|
|
4135
|
+
// Yield before dispatch so the map is installed before even a synchronously-throwing
|
|
4136
|
+
// bridge stub can settle this promise and run its cleanup.
|
|
4137
|
+
const promise = (async () => {
|
|
4138
|
+
await Promise.resolve();
|
|
4139
|
+
try {
|
|
4140
|
+
return await ctx.call({ cmd: "refresh_nodes" }, OBJECT_INFO_REFRESH_ACK_TIMEOUT_MS);
|
|
4141
|
+
}
|
|
4142
|
+
catch (err) {
|
|
4143
|
+
return fail(err);
|
|
4144
|
+
}
|
|
4145
|
+
finally {
|
|
4146
|
+
if (panelSchemaRefreshes.get(key)?.token === token)
|
|
4147
|
+
panelSchemaRefreshes.delete(key);
|
|
4148
|
+
}
|
|
4149
|
+
})();
|
|
4150
|
+
panelSchemaRefreshes.set(key, { token, promise });
|
|
4151
|
+
return promise;
|
|
4152
|
+
}
|
|
4153
|
+
function panelSchemaRefreshSucceeded(res) {
|
|
4154
|
+
if (res.isError)
|
|
4155
|
+
return false;
|
|
4156
|
+
return parseToolResultJson(res)?.refreshed === true;
|
|
4157
|
+
}
|
|
4158
|
+
function objectInfoWidgetRefreshNote(refresh, retryAfterRefresh = false) {
|
|
4159
|
+
const payload = parseToolResultJson(refresh);
|
|
4160
|
+
const stillRunning = payload?.reason === "refresh_still_running" ||
|
|
4161
|
+
/refresh_still_running|still running/i.test(textOfToolResult(refresh));
|
|
4162
|
+
const timedOut = isReplyTimeoutResult(refresh) || /did not reply .*refresh_nodes/i.test(textOfToolResult(refresh));
|
|
4163
|
+
if (retryAfterRefresh) {
|
|
4164
|
+
return (`\n\nThe authoritative panel_refresh_nodes completed, but the retry still could not ` +
|
|
4165
|
+
`obtain a usable /object_info schema. Nothing was written; the value and node ` +
|
|
4166
|
+
`validation refusal remain fail-closed. Retry after the panel settles, and do not ` +
|
|
4167
|
+
`repeat the write if the node definition is genuinely missing or invalid.`);
|
|
4168
|
+
}
|
|
4169
|
+
if (stillRunning || timedOut) {
|
|
4170
|
+
return (`\n\nThe widget write was refused before mutation because the authoritative node-definition ` +
|
|
4171
|
+
`refresh did not settle inside its bounded window (${stillRunning ? "it is still running" : "the refresh acknowledgement timed out"}). ` +
|
|
4172
|
+
`No usable schema was proven and nothing was written. Retry after that refresh settles.`);
|
|
4173
|
+
}
|
|
4174
|
+
return (`\n\nThe widget write was refused before mutation because panel_refresh_nodes did not ` +
|
|
4175
|
+
`return an authoritative refreshed:true result (${payload?.reason ?? "missing or malformed refresh result"}). ` +
|
|
4176
|
+
`No usable /object_info schema was proven and nothing was written; refusing to retry ` +
|
|
4177
|
+
`the value against an unknown or stale definition.`);
|
|
4178
|
+
}
|
|
4140
4179
|
/**
|
|
4141
4180
|
* #2202 — the panel's stale-combo recovery can time out after it has retired the
|
|
4142
4181
|
* usable whole-schema snapshot. That refusal is safe to repair here: the panel
|
|
@@ -13536,13 +13575,6 @@ export function buildPanelToolDefs() {
|
|
|
13536
13575
|
markPanelSchemaReady(panelSchemaKey(ctx));
|
|
13537
13576
|
return persistUnpromotedControlAfterGenerate(first, ctx, args.node_id);
|
|
13538
13577
|
}
|
|
13539
|
-
// #2004 — both whole-schema routes timed out. The panel refused BEFORE
|
|
13540
|
-
// mutating, so this is not outcome-unknown. Name the timeout rather than
|
|
13541
|
-
// leaving "cannot verify the node type" as "the type is missing".
|
|
13542
|
-
if (isObjectInfoStarvationRefusal(first)) {
|
|
13543
|
-
markPanelSchemaBlocked(panelSchemaKey(ctx));
|
|
13544
|
-
return appendToolResultText(first, objectInfoStarvationNote());
|
|
13545
|
-
}
|
|
13546
13578
|
// #2202 — stale-combo recovery may have retired the panel's last usable
|
|
13547
13579
|
// whole-schema snapshot before its bounded refresh wait abandoned. The
|
|
13548
13580
|
// panel refused before writing, so dispatching its idempotent,
|
|
@@ -13614,18 +13646,31 @@ export function buildPanelToolDefs() {
|
|
|
13614
13646
|
if (ambiguous && String(ambiguous.nodeId) === String(args.node_id)) {
|
|
13615
13647
|
return explainAmbiguousPromotedWidgetRefusal(first, args, ctx);
|
|
13616
13648
|
}
|
|
13649
|
+
// #2274 — a whole-schema budget refusal can be caused by a refresh already
|
|
13650
|
+
// registering definitions for this tab. Share one bounded refresh, and retry
|
|
13651
|
+
// the exact write only after the panel authoritatively reports refreshed:true.
|
|
13652
|
+
// This preserves the before-write refusal for a still-running/failed/malformed
|
|
13653
|
+
// refresh, and avoids the N independent refresh paths that made a burst of
|
|
13654
|
+
// otherwise-valid writes all refuse together.
|
|
13617
13655
|
if (isObjectInfoBudgetRefusal(first)) {
|
|
13618
|
-
|
|
13619
|
-
|
|
13620
|
-
|
|
13656
|
+
const schemaKey = panelSchemaKey(ctx);
|
|
13657
|
+
markPanelSchemaBlocked(schemaKey);
|
|
13658
|
+
const refreshed = await sharedPanelSchemaRefresh(ctx);
|
|
13659
|
+
if (!panelSchemaRefreshSucceeded(refreshed)) {
|
|
13660
|
+
return appendToolResultText(first, objectInfoWidgetRefreshNote(refreshed));
|
|
13661
|
+
}
|
|
13662
|
+
markPanelSchemaReady(schemaKey);
|
|
13621
13663
|
const retried = await write(args.node_id, args.widget);
|
|
13622
13664
|
if (!retried.isError) {
|
|
13623
|
-
markPanelSchemaReady(panelSchemaKey(ctx));
|
|
13624
13665
|
return persistUnpromotedControlAfterGenerate(retried, ctx, args.node_id);
|
|
13625
13666
|
}
|
|
13626
|
-
|
|
13627
|
-
|
|
13628
|
-
|
|
13667
|
+
if (isObjectInfoBudgetRefusal(retried)) {
|
|
13668
|
+
markPanelSchemaBlocked(schemaKey);
|
|
13669
|
+
first = appendToolResultText(retried, objectInfoWidgetRefreshNote(refreshed, true));
|
|
13670
|
+
}
|
|
13671
|
+
else {
|
|
13672
|
+
first = retried;
|
|
13673
|
+
}
|
|
13629
13674
|
}
|
|
13630
13675
|
// #1655 — the panel listed this widget as promoted while refusing it as
|
|
13631
13676
|
// not promoted. The listing is node.widgets; the write looks up host
|
|
@@ -14524,7 +14569,7 @@ export function buildPanelToolDefs() {
|
|
|
14524
14569
|
// Same bounded ack budget as the refresh-before-validate writes (#599): a
|
|
14525
14570
|
// fresh /object_info on a large install routinely exceeds the 6000 ms
|
|
14526
14571
|
// default, and this command's WHOLE purpose is to await that fetch.
|
|
14527
|
-
const res = await ctx
|
|
14572
|
+
const res = await sharedPanelSchemaRefresh(ctx);
|
|
14528
14573
|
// #2007 — a failed whole-schema refresh is not a down backend. It is also
|
|
14529
14574
|
// worse than a no-op: the panel CLEARS the last-known schema at the start
|
|
14530
14575
|
// of the run, so a timed-out refresh is why the next panel_set_widget
|