comfyui-mcp 0.50.97 → 0.50.98

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.
@@ -697,6 +697,22 @@ function isWorkflowSwitchGuardRefusal(err) {
697
697
  const msg = err instanceof Error ? err.message : String(err ?? "");
698
698
  return /panel is switching\/refreshing|panel is switching or reloading/i.test(msg);
699
699
  }
700
+ /**
701
+ * #1330 — the panel's workflow-instance fence refused this command.
702
+ *
703
+ * Distinct from every other refusal here in one way that matters: the panel checks the
704
+ * fence BEFORE the handler runs, so "Nothing was applied" is structural, not a claim.
705
+ * That is what makes it safe to tell a caller to retry — and it is why this is worth
706
+ * separating from the transport failures around it, where the outcome is unknown.
707
+ *
708
+ * Matched on the leading token the panel preserves deliberately for exactly this
709
+ * purpose (see workflowInstanceMismatchMessage: "the leading `workflow instance
710
+ * mismatch:` token is preserved deliberately").
711
+ */
712
+ function isWorkflowInstanceMismatch(err) {
713
+ const msg = err instanceof Error ? err.message : String(err ?? "");
714
+ return /workflow instance mismatch/i.test(msg);
715
+ }
700
716
  let retrySettleMsOverride = null;
701
717
  /** Short pause before the single post-drop retry, letting the replacement tab
702
718
  * finish its reconnect hello so ensureReachable can resolve it. Test-overridable. */
@@ -4916,6 +4932,64 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets) {
4916
4932
  // sent agents into a futile retry/rebind loop that contradicted the embedded
4917
4933
  // guidance. Key on the TYPED marker and surface the cause verbatim instead; it
4918
4934
  // already names the real recovery (update + restart + browser hard-refresh).
4935
+ // #1330 — CORROBORATE A FENCE MISMATCH INSTEAD OF LETTING IT REPEAT.
4936
+ //
4937
+ // The reporter built 12 nodes successfully, then EVERY panel_connect was refused
4938
+ // on an instance mismatch. Calling panel_set_workflow_target({mode:"current"})
4939
+ // straight afterwards reported `already_current` naming the SAME uuid the command
4940
+ // had been issued for, and the retried connects all succeeded. So the mismatch was
4941
+ // transient and self-correcting, and the caller paid 14 refusals to find that out —
4942
+ // because nothing in the refusal distinguishes "the canvas really is a different
4943
+ // workflow" from "the identity flipped for a moment while you were building".
4944
+ //
4945
+ // The fence is NOT weakened and nothing is auto-applied. This performs the same
4946
+ // read-only re-derivation the documented recovery performs, then says which of the
4947
+ // two states was found. One informed retry replaces fourteen blind ones.
4948
+ //
4949
+ // Safe to recommend a retry because a fence refusal is checked BEFORE the handler
4950
+ // runs — "Nothing was applied" is structural here, not an echoed claim.
4951
+ if (isWorkflowInstanceMismatch(err) && isMutatingGraphCmd(cmd)) {
4952
+ const name = typeof cmd.cmd === "string" ? cmd.cmd : "panel command";
4953
+ const raw = err instanceof Error ? err.message : String(err);
4954
+ // The uuid the command CARRIED, read from the panel's own refusal rather than
4955
+ // from `cmd`: the stamp is applied downstream of here, so `cmd.workflow_uuid`
4956
+ // is undefined at this point and keying on it silently disabled the transient
4957
+ // branch — the one case this whole block exists for. The panel states both
4958
+ // sides of the comparison it performed, which is the authoritative record of
4959
+ // what was compared.
4960
+ const stamped = /issued for workflow instance ([0-9a-f-]{36})/i.exec(err instanceof Error ? err.message : String(err))?.[1] ?? null;
4961
+ let verdict;
4962
+ try {
4963
+ const rebind = await rebindWorkflowFence(ctx);
4964
+ verdict =
4965
+ rebind.status === "already_current" && stamped && rebind.uuid === stamped
4966
+ ? `\n\nAUTO-REBIND: ATTEMPTED, and the live canvas now reports the SAME workflow ` +
4967
+ `instance this command carried (${stamped}) — nothing needed repairing. The ` +
4968
+ `mismatch was TRANSIENT: the identity flipped and settled back, which happens ` +
4969
+ `while a new unsaved workflow is still materialising. RETRY THIS EXACT CALL ONCE. ` +
4970
+ `Nothing was applied, so a retry cannot double-apply, and re-issuing the whole ` +
4971
+ `build would duplicate the work that already succeeded.`
4972
+ : rebind.status === "already_current"
4973
+ ? `\n\nAUTO-REBIND: ATTEMPTED; the fence already named the live canvas ` +
4974
+ `(${rebind.uuid}), so it was not the stale side. Retry once — if it refuses ` +
4975
+ `again with the same pair, the two identities are genuinely disagreeing and ` +
4976
+ `panel_open_workflow is the way to settle which one you mean.`
4977
+ : rebind.status === "refreshed"
4978
+ ? `\n\nAUTO-REBIND: ATTEMPTED and the fence was RE-DERIVED onto the live canvas ` +
4979
+ `(now ${rebind.uuid}). Retry once. If you meant the EARLIER workflow, re-select ` +
4980
+ `it with panel_open_workflow first — this session now points at the live one.`
4981
+ : `\n\nAUTO-REBIND: ATTEMPTED and did NOT succeed (${rebind.status}), so the fence ` +
4982
+ `is unchanged and a bare retry will fail the same way. Re-select the workflow ` +
4983
+ `you mean with panel_open_workflow, then retry.`;
4984
+ }
4985
+ catch (rebindErr) {
4986
+ // Never let the diagnosis fail the call differently than it already failed.
4987
+ verdict =
4988
+ `\n\nAUTO-REBIND: ATTEMPTED and threw, so the fence state is UNKNOWN — this refusal ` +
4989
+ `stands on its own terms. (${rebindErr instanceof Error ? rebindErr.message : String(rebindErr)})`;
4990
+ }
4991
+ return fail(`${name} was NOT applied — nothing changed. ${raw}${verdict}`);
4992
+ }
4919
4993
  if (isCapabilityRefusal(err)) {
4920
4994
  return fail(err instanceof Error ? err.message : String(err));
4921
4995
  }