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
  }