comfyui-mcp 0.50.22 → 0.50.23

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.
@@ -2520,8 +2520,33 @@ refusalCause) {
2520
2520
  : ` It has no graph command fence; whether graph reads work is unknown, because the ` +
2521
2521
  `read that would have told us is the one that failed.`) +
2522
2522
  ` This is not a rebind — it is an unknown.` +
2523
- `\n\nWHAT TO DO: retry in a moment a busy or mid-reconnect panel often answers on ` +
2524
- `the next attempt. If it keeps failing: ${RELOAD_TAB_REMEDY}` +
2523
+ // #1071WHY the read failed decides the remedy, exactly as the
2524
+ // no_identity case below already does. Bucketing them would hand half the
2525
+ // callers an instruction they cannot act on.
2526
+ //
2527
+ // - REFUSED BY THE FENCE: retrying is provably useless. This rebind reads
2528
+ // workflow_list, which the panel fences, so the fence being wrong is
2529
+ // precisely what makes the read fail — every time, forever. A reporter
2530
+ // called this five times over twenty minutes on "retry in a moment" and
2531
+ // lost the canvas for the rest of the task. workflow_open is the one
2532
+ // command the fence EXEMPTS, so it still runs while everything else is
2533
+ // refused, and it now re-derives the fence from its own reply.
2534
+ // - ANY OTHER CAUSE (a dead tab, a transport drop, a busy panel): the
2535
+ // panel is not answering at all, so reopening would fail too and
2536
+ // retrying really is the reachable move.
2537
+ (/workflow instance mismatch/i.test(r.detail)
2538
+ ? `\n\nWHAT TO DO: reopen the workflow you want with panel_open_workflow(<path>). ` +
2539
+ `The panel refused the read because of the FENCE itself, so retrying this call ` +
2540
+ `cannot work — it needs the very read the fence blocks. The panel's fence EXEMPTS ` +
2541
+ `workflow_open, so it still runs while everything else is refused, and it ` +
2542
+ `re-derives this session's fence from its own reply. Note panel_list_workflows is ` +
2543
+ `fenced too, so if you do not already know the path, ask the user rather than ` +
2544
+ `guessing. If the canvas you want is an UNSAVED one (a blank workflow you just ` +
2545
+ `created), there is no path to reopen and opening anything else would abandon it — ` +
2546
+ `that case needs the tab refresh below, not a reopen. If reopening also fails: ` +
2547
+ `${RELOAD_TAB_REMEDY}`
2548
+ : `\n\nWHAT TO DO: retry in a moment — a busy or mid-reconnect panel often answers ` +
2549
+ `on the next attempt. If it keeps failing: ${RELOAD_TAB_REMEDY}`) +
2525
2550
  `\n\nUNDERLYING CAUSE (quoted verbatim — disregard any rebind advice inside it; ` +
2526
2551
  `rebinding is what just failed): ${r.detail}`,
2527
2552
  };
@@ -2621,18 +2646,52 @@ async function refreshOpenWorkflowUuid(ctx, requestedPath, openResult) {
2621
2646
  : null;
2622
2647
  if (!requestedIdentity || requestedIdentity !== openedIdentity)
2623
2648
  return;
2649
+ // THREE outcomes for this corroborating read, not two — and conflating the last
2650
+ // two is #1071 (also #932/#1043).
2651
+ //
2652
+ // `workflow_list` is NOT exempt from the panel's fence (activeWorkflowFenceApplies
2653
+ // exempts only canvas-independent ops, workflow_open/new, and non-active-targeted
2654
+ // rename/close). So in exactly the state this refresh exists to repair — a session
2655
+ // fenced to a workflow instance that is no longer active — the read is REFUSED by
2656
+ // the stale fence, the old code took the `return` / `catch`, and the fence was
2657
+ // never refreshed. The recovery was gated behind the one call the wedge blocks.
2658
+ //
2659
+ // The open reply already carries what is needed. The panel publishes
2660
+ // `workflow_uuid` there only after a FINAL SYNCHRONOUS check that `target` is
2661
+ // still the active workflow (#716, activeWorkflowUuidForOpenReply) — omitting it
2662
+ // otherwise, expressly so "the MCP keeps its existing fence fail-closed". It is a
2663
+ // fence-quality value the panel went out of its way to prove; using it when the
2664
+ // read cannot be made is what it is for.
2665
+ let list = null;
2666
+ let readable = false;
2624
2667
  try {
2625
- const list = parseToolResultJson(await ctx.call({ cmd: "workflow_list" }, 6000));
2626
- if (!list || !activeMatchesOpenRefreshTarget(list.active, requestedPath))
2627
- return;
2628
- // Prefer the just-read active UUID; use the command's correlated response as
2629
- // a compatibility fallback only after that same active-target confirmation.
2630
- refreshWorkflowUuid(ctx, list.active) || refreshWorkflowUuid(ctx, parseToolResultJson(openResult));
2668
+ const res = await ctx.call({ cmd: "workflow_list" }, 6000);
2669
+ if (!res?.isError) {
2670
+ list = parseToolResultJson(res);
2671
+ readable = list !== null;
2672
+ }
2631
2673
  }
2632
2674
  catch {
2633
- // A failed read must not convert a confirmed open into a failure, nor should
2634
- // it clear the old stamp. The next mutation remains safely fenced.
2675
+ readable = false;
2676
+ }
2677
+ if (readable) {
2678
+ // ANSWERED. It either confirms our target is active — prefer the just-read
2679
+ // value, which is fresher than the reply — or it names a DIFFERENT active
2680
+ // workflow, in which case another tab won the slot and adopting our target's
2681
+ // uuid would fence this session to a canvas that is not mounted. Adopt nothing.
2682
+ if (!activeMatchesOpenRefreshTarget(list.active, requestedPath))
2683
+ return;
2684
+ refreshWorkflowUuid(ctx, list.active) || refreshWorkflowUuid(ctx, parsedOpen);
2685
+ return;
2635
2686
  }
2687
+ // COULD NOT ASK — the wedged case. Fall back to the reply's proven uuid rather
2688
+ // than leaving the session fenced to a dead instance forever. This is strictly
2689
+ // safer than the alternative it replaces: a stamp the panel proved against its
2690
+ // own active workflow can only authorize commands naming the canvas that is
2691
+ // actually mounted, whereas doing nothing here guarantees every subsequent
2692
+ // command is refused. A reply that carries no uuid (the panel could not prove
2693
+ // it) still refreshes nothing, so fail-closed is preserved.
2694
+ refreshWorkflowUuid(ctx, parsedOpen);
2636
2695
  }
2637
2696
  /** Exact resolved-path check for an open receipt. A filename/basename is not a
2638
2697
  * workflow identity: `other/foo.json` must never confirm `wanted/foo.json`. */