comfyui-mcp 0.52.74 → 0.52.75
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.
|
@@ -6554,6 +6554,103 @@ NOTE: an API-format load CAN re-mint the canvas workflow instance. If your next
|
|
|
6554
6554
|
`Clear it with panel_set_workflow_target({mode:"current"}), which re-derives the fence ` +
|
|
6555
6555
|
`from the live canvas, then retry. If the next command is not refused, nothing needs doing.`);
|
|
6556
6556
|
}
|
|
6557
|
+
/**
|
|
6558
|
+
* #2106 — a panel can apply a UI graph and then lose the fetch carrying the reply.
|
|
6559
|
+
*
|
|
6560
|
+
* Reconcile only the narrow, post-dispatch transport failure reported by the issue. A
|
|
6561
|
+
* dispatched request id is required: without it, "Failed to fetch" may be a pre-write
|
|
6562
|
+
* refusal and there is no mutation to reconcile. The live graph must also have the exact
|
|
6563
|
+
* requested content and still belong to the workflow instance that was active before the
|
|
6564
|
+
* load. Anything less remains outcome-unknown, so a real load failure is never turned into
|
|
6565
|
+
* a success by a coincidental canvas shape.
|
|
6566
|
+
*/
|
|
6567
|
+
function isBarePanelFetchFailure(res) {
|
|
6568
|
+
return res.isError === true && /^(?:Error:\s*)?Failed to fetch$/i.test(textOfToolResult(res));
|
|
6569
|
+
}
|
|
6570
|
+
function uiWorkflowNodeCount(value) {
|
|
6571
|
+
if (!value || typeof value !== "object" || !Array.isArray(value.nodes)) {
|
|
6572
|
+
return undefined;
|
|
6573
|
+
}
|
|
6574
|
+
return value.nodes.length;
|
|
6575
|
+
}
|
|
6576
|
+
function loadOutcomeUnknown(res, dispatchedRid) {
|
|
6577
|
+
if (!dispatchedRid)
|
|
6578
|
+
return res;
|
|
6579
|
+
return appendToolResultText(res, `\n\nOUTCOME UNKNOWN: panel_load_workflow was dispatched, but the panel reply failed with ` +
|
|
6580
|
+
`"Failed to fetch" and the live canvas did not prove the requested UI graph on the ` +
|
|
6581
|
+
`same workflow instance. Do not retry blindly. To retry this exact mutation, re-issue ` +
|
|
6582
|
+
`identical args plus retry_of:"${dispatchedRid}"; otherwise call normally.`);
|
|
6583
|
+
}
|
|
6584
|
+
async function reconcileFailedPanelLoad(res, ctx, data, graphBefore, tabAtDispatch, fenceBefore, dispatchedRid) {
|
|
6585
|
+
if (!isBarePanelFetchFailure(res) || !dispatchedRid)
|
|
6586
|
+
return res;
|
|
6587
|
+
const expectedNodeCount = uiWorkflowNodeCount(data);
|
|
6588
|
+
if (expectedNodeCount === undefined ||
|
|
6589
|
+
graphBefore == null ||
|
|
6590
|
+
fenceBefore.known !== true ||
|
|
6591
|
+
typeof fenceBefore.uuid !== "string" ||
|
|
6592
|
+
ctx.tabId !== tabAtDispatch) {
|
|
6593
|
+
return loadOutcomeUnknown(res, dispatchedRid);
|
|
6594
|
+
}
|
|
6595
|
+
// A matching post-failure graph is only a causal proof when the pre-dispatch
|
|
6596
|
+
// graph was different. If the requested graph was already present, the reads
|
|
6597
|
+
// cannot tell whether this RID changed anything, so keep the retry token.
|
|
6598
|
+
if (openLiveMatchesDestContent(graphBefore, data)) {
|
|
6599
|
+
return loadOutcomeUnknown(res, dispatchedRid);
|
|
6600
|
+
}
|
|
6601
|
+
try {
|
|
6602
|
+
const serialized = await ctx.call({ cmd: "graph_serialize" }, 8000);
|
|
6603
|
+
if (ctx.tabId !== tabAtDispatch || serialized.isError) {
|
|
6604
|
+
return loadOutcomeUnknown(res, dispatchedRid);
|
|
6605
|
+
}
|
|
6606
|
+
const serializedPayload = parseToolResultJson(serialized);
|
|
6607
|
+
const liveWorkflow = serializedPayload?.workflow;
|
|
6608
|
+
const liveNodeCount = typeof serializedPayload?.node_count === "number" &&
|
|
6609
|
+
Number.isInteger(serializedPayload.node_count) &&
|
|
6610
|
+
serializedPayload.node_count >= 0
|
|
6611
|
+
? serializedPayload.node_count
|
|
6612
|
+
: uiWorkflowNodeCount(liveWorkflow);
|
|
6613
|
+
if (liveNodeCount !== expectedNodeCount) {
|
|
6614
|
+
return loadOutcomeUnknown(res, dispatchedRid);
|
|
6615
|
+
}
|
|
6616
|
+
// Count alone is not proof: an old same-sized graph on the same workflow
|
|
6617
|
+
// instance would otherwise be reported as the requested load. Reuse the
|
|
6618
|
+
// existing fail-closed content matcher, which checks node identities/types,
|
|
6619
|
+
// link topology, non-empty widget values, and nested subgraph content while
|
|
6620
|
+
// tolerating frontend schema/presentation normalization.
|
|
6621
|
+
if (!openLiveMatchesDestContent(liveWorkflow, data)) {
|
|
6622
|
+
return loadOutcomeUnknown(res, dispatchedRid);
|
|
6623
|
+
}
|
|
6624
|
+
const listed = await ctx.call({ cmd: "workflow_list" }, 6000);
|
|
6625
|
+
if (ctx.tabId !== tabAtDispatch || listed.isError) {
|
|
6626
|
+
return loadOutcomeUnknown(res, dispatchedRid);
|
|
6627
|
+
}
|
|
6628
|
+
const listedPayload = parseToolResultJson(listed);
|
|
6629
|
+
if (!listedPayload)
|
|
6630
|
+
return loadOutcomeUnknown(res, dispatchedRid);
|
|
6631
|
+
const corroborated = corroborateActiveForFence(listedPayload);
|
|
6632
|
+
if (!corroborated.ok)
|
|
6633
|
+
return loadOutcomeUnknown(res, dispatchedRid);
|
|
6634
|
+
const liveUuid = responseWorkflowUuid(corroborated.active);
|
|
6635
|
+
if (!liveUuid || liveUuid !== fenceBefore.uuid) {
|
|
6636
|
+
return loadOutcomeUnknown(res, dispatchedRid);
|
|
6637
|
+
}
|
|
6638
|
+
return ok({
|
|
6639
|
+
loaded: true,
|
|
6640
|
+
reconciled: true,
|
|
6641
|
+
acknowledged_after_error: true,
|
|
6642
|
+
format: "ui",
|
|
6643
|
+
node_count: liveNodeCount,
|
|
6644
|
+
workflow_uuid: liveUuid,
|
|
6645
|
+
response_error: "Failed to fetch",
|
|
6646
|
+
note: "The panel applied the requested UI graph, but its reply failed after dispatch. " +
|
|
6647
|
+
"The same workflow instance now contains the expected graph; do not retry.",
|
|
6648
|
+
});
|
|
6649
|
+
}
|
|
6650
|
+
catch {
|
|
6651
|
+
return loadOutcomeUnknown(res, dispatchedRid);
|
|
6652
|
+
}
|
|
6653
|
+
}
|
|
6557
6654
|
async function rebindWorkflowFence(ctx, opts) {
|
|
6558
6655
|
const tabAtStart = ctx.tabId;
|
|
6559
6656
|
let before = currentWorkflowFence(ctx);
|
|
@@ -12195,9 +12292,27 @@ export function buildPanelToolDefs() {
|
|
|
12195
12292
|
throw new Error("Provide one of `pack` (a bundled pack name), `path` (a workflow .json on disk), or `graph` (a UI workflow).");
|
|
12196
12293
|
}
|
|
12197
12294
|
// Generous timeout — loading a large graph onto the live canvas can take a moment.
|
|
12198
|
-
const
|
|
12199
|
-
|
|
12200
|
-
|
|
12295
|
+
const tabAtDispatch = ctx.tabId;
|
|
12296
|
+
const fenceBefore = currentWorkflowFence(ctx);
|
|
12297
|
+
let graphBefore;
|
|
12298
|
+
if (uiWorkflowNodeCount(data) !== undefined) {
|
|
12299
|
+
try {
|
|
12300
|
+
const before = await ctx.call({ cmd: "graph_serialize" }, 8000);
|
|
12301
|
+
if (!before.isError)
|
|
12302
|
+
graphBefore = parseToolResultJson(before)?.workflow;
|
|
12303
|
+
}
|
|
12304
|
+
catch {
|
|
12305
|
+
// Without a pre-dispatch snapshot, a later matching graph cannot
|
|
12306
|
+
// prove that this request caused the transition.
|
|
12307
|
+
}
|
|
12308
|
+
}
|
|
12309
|
+
let dispatchedRid;
|
|
12310
|
+
const loaded = await ctx.call({ cmd: "graph_load", graph: data }, 30000, (rid) => {
|
|
12311
|
+
dispatchedRid = rid;
|
|
12312
|
+
});
|
|
12313
|
+
if (loaded.isError) {
|
|
12314
|
+
return reconcileFailedPanelLoad(loaded, ctx, data, graphBefore, tabAtDispatch, fenceBefore, dispatchedRid);
|
|
12315
|
+
}
|
|
12201
12316
|
// Keyed on the reply SAYING the graph was replaced, not merely on the call not
|
|
12202
12317
|
// erroring (codex r2). A non-error envelope that reports `loaded:false` did not
|
|
12203
12318
|
// replace anything, and telling that caller their fence is stale would be a
|