comfyui-mcp 0.51.20 → 0.51.21
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.
|
@@ -743,6 +743,67 @@ function isNoTrustedIdentityRefusal(err) {
|
|
|
743
743
|
const msg = err instanceof Error ? err.message : String(err ?? "");
|
|
744
744
|
return /no trusted identity/i.test(msg);
|
|
745
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* #1480 — the panel's GRAPH-BINDING guard refused: the live canvas carries a different
|
|
748
|
+
* workflow's identity tag than the active workflow (`graphBindingRefusalMessage`'s
|
|
749
|
+
* `root-workflow-uuid-mismatch` verdict).
|
|
750
|
+
*
|
|
751
|
+
* A THIRD distinct refusal, and the trio is easy to conflate. The two above are the
|
|
752
|
+
* bridge's per-command STAMP fence (`workflow instance mismatch`) and the bridge having
|
|
753
|
+
* NO identity to stamp with (`no trusted identity`); this one is the panel comparing the
|
|
754
|
+
* tag on the mounted LiteGraph root against the active workflow's identity and finding
|
|
755
|
+
* two tags that disagree. It is raised by the panel, before every executor, so its
|
|
756
|
+
* "was NOT applied" claim is structural — the same property that makes the two above
|
|
757
|
+
* safe to annotate.
|
|
758
|
+
*
|
|
759
|
+
* Matched on the bracketed token the panel emits for exactly this purpose
|
|
760
|
+
* (`[root-workflow-uuid-mismatch] …`), not on the prose after it, and ANCHORED to the
|
|
761
|
+
* start (codex): a bare `contains` also fires on any message that merely QUOTES the
|
|
762
|
+
* token — a wrapped or re-surfaced refusal, an error about the token as a value — and
|
|
763
|
+
* would then spend a round trip telling that caller to re-open a workflow. The anchor is
|
|
764
|
+
* the panel's real shape, not a hopeful one: a panel executor's error text becomes this
|
|
765
|
+
* Error's message verbatim, with no prefix (ui-bridge's reply-error reject).
|
|
766
|
+
*/
|
|
767
|
+
function isRootWorkflowUuidMismatch(err) {
|
|
768
|
+
const msg = err instanceof Error ? err.message : String(err ?? "");
|
|
769
|
+
return /^\s*\[root-workflow-uuid-mismatch\]/i.test(msg);
|
|
770
|
+
}
|
|
771
|
+
async function readActiveTabRebindSelector(ctx) {
|
|
772
|
+
const unknown = (why) => ({
|
|
773
|
+
status: "unknown",
|
|
774
|
+
selector: null,
|
|
775
|
+
title: null,
|
|
776
|
+
why,
|
|
777
|
+
});
|
|
778
|
+
let parsed = null;
|
|
779
|
+
try {
|
|
780
|
+
const res = await ctx.call({ cmd: "workflow_list" }, 6000);
|
|
781
|
+
if (res?.isError)
|
|
782
|
+
return unknown(`the workflow list read failed (${toolResultText(res)})`);
|
|
783
|
+
parsed = parseToolResultJson(res);
|
|
784
|
+
}
|
|
785
|
+
catch (err) {
|
|
786
|
+
return unknown(`the workflow list read threw (${err instanceof Error ? err.message : String(err)})`);
|
|
787
|
+
}
|
|
788
|
+
const active = parsed?.active;
|
|
789
|
+
if (!active || typeof active !== "object") {
|
|
790
|
+
return unknown("the workflow list reported no active workflow");
|
|
791
|
+
}
|
|
792
|
+
const rec = active;
|
|
793
|
+
const str = (v) => (typeof v === "string" && v ? v : null);
|
|
794
|
+
const title = str(rec.title);
|
|
795
|
+
const path = str(rec.path);
|
|
796
|
+
const routingKey = str(rec.routing_key) ?? str(rec.key);
|
|
797
|
+
// SAVED is decided by the presence of a real path, the same fact the panel keys
|
|
798
|
+
// `persisted` on — never by the routing key's spelling, which is panel-owned and
|
|
799
|
+
// would silently reclassify every tab if its prefix ever changed.
|
|
800
|
+
if (path)
|
|
801
|
+
return { status: "saved", selector: path, title, why: "" };
|
|
802
|
+
if (!routingKey) {
|
|
803
|
+
return unknown("the active tab reported neither a path nor a routing key");
|
|
804
|
+
}
|
|
805
|
+
return { status: "unsaved", selector: routingKey, title, why: "" };
|
|
806
|
+
}
|
|
746
807
|
let retrySettleMsOverride = null;
|
|
747
808
|
/** Short pause before the single post-drop retry, letting the replacement tab
|
|
748
809
|
* finish its reconnect hello so ensureReachable can resolve it. Test-overridable. */
|
|
@@ -5441,6 +5502,50 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets) {
|
|
|
5441
5502
|
}
|
|
5442
5503
|
return fail(`${name} was NOT applied — nothing changed. ${raw}${verdict}`);
|
|
5443
5504
|
}
|
|
5505
|
+
// #1480 — NAME A REMEDY THE TAB CAN ACTUALLY ACCEPT.
|
|
5506
|
+
//
|
|
5507
|
+
// The panel's own remedy for this verdict is `panel_open_workflow(<path>)`, which
|
|
5508
|
+
// is right for a saved tab and unfollowable for a never-saved one: that tab has no
|
|
5509
|
+
// path, and its title is not a selector. The reporter tried the title, was told
|
|
5510
|
+
// "no workflow matching", and found every other exit closed as well — reload
|
|
5511
|
+
// refuses while unsaved, save refuses under this same guard (#708). Canvas
|
|
5512
|
+
// readable, canvas unedittable, and the only way out was a human pressing Ctrl+S.
|
|
5513
|
+
//
|
|
5514
|
+
// Nothing here is auto-applied and the guard is NOT weakened. One read-only
|
|
5515
|
+
// `workflow_list` (exempt from this fence) establishes the single fact that decides
|
|
5516
|
+
// which remedy is reachable, and the refusal then names THAT one. Applied to reads
|
|
5517
|
+
// as well as mutations on purpose: `panel_graph_outline` refusing was half of the
|
|
5518
|
+
// reported dead end, and this verdict is raised before every executor, so the
|
|
5519
|
+
// "NOT applied" claim it rides on is equally true either way.
|
|
5520
|
+
if (isRootWorkflowUuidMismatch(err)) {
|
|
5521
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
5522
|
+
try {
|
|
5523
|
+
const tab = await readActiveTabRebindSelector(ctx);
|
|
5524
|
+
if (tab.status === "unsaved" && tab.selector) {
|
|
5525
|
+
return fail(`${raw}\n\nCHECKED, so this is not a guess: the active tab has NEVER BEEN SAVED ` +
|
|
5526
|
+
`(the workflow list reports path: null, filename: null${tab.title ? `, title: ${JSON.stringify(tab.title)}` : ""}). The remedy above names a <path> that DOES NOT EXIST for this tab, and its ` +
|
|
5527
|
+
`title is not a selector — passing either fails with "no workflow matching". ` +
|
|
5528
|
+
`USE ITS ROUTING KEY, which is the only selector an unsaved tab has and needs ` +
|
|
5529
|
+
`no file on disk: panel_open_workflow({path: ${JSON.stringify(tab.selector)}}). ` +
|
|
5530
|
+
`Do NOT reach for panel_save_workflow first — under this same guard it refuses ` +
|
|
5531
|
+
`too (#708) — and do NOT reload while the tab is unsaved. If re-opening by ` +
|
|
5532
|
+
`routing key still refuses, the two identities genuinely disagree and the user ` +
|
|
5533
|
+
`has to save or reload the tab by hand.`);
|
|
5534
|
+
}
|
|
5535
|
+
if (tab.status === "saved" && tab.selector) {
|
|
5536
|
+
return fail(`${raw}\n\nCHECKED: the active tab IS saved, so the remedy above is reachable as ` +
|
|
5537
|
+
`written — panel_open_workflow({path: ${JSON.stringify(tab.selector)}}).`);
|
|
5538
|
+
}
|
|
5539
|
+
return fail(`${raw}\n\nCHECKED, and the answer is UNKNOWN: which panel_open_workflow selector ` +
|
|
5540
|
+
`this tab accepts could not be established (${tab.why}). If it turns out to have ` +
|
|
5541
|
+
`never been saved, <path> will not name it — read panel_list_workflows and pass ` +
|
|
5542
|
+
`the active record's routing_key instead.`);
|
|
5543
|
+
}
|
|
5544
|
+
catch {
|
|
5545
|
+
// The diagnosis must never change how the call failed.
|
|
5546
|
+
return fail(raw);
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5444
5549
|
if (isCapabilityRefusal(err)) {
|
|
5445
5550
|
return fail(err instanceof Error ? err.message : String(err));
|
|
5446
5551
|
}
|