comfyui-mcp 0.50.83 → 0.50.84
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.
|
@@ -2577,27 +2577,53 @@ function currentWorkflowFence(ctx) {
|
|
|
2577
2577
|
function corroborateActiveForFence(parsed) {
|
|
2578
2578
|
const active = parsed.active;
|
|
2579
2579
|
if (!active || typeof active !== "object") {
|
|
2580
|
-
|
|
2580
|
+
// SETTLES: mid-restore the frontend genuinely has no active workflow yet —
|
|
2581
|
+
// that is #1184's finding in this reply's terms, and it is the window here.
|
|
2582
|
+
return { ok: false, why: "the reply carried no active-workflow record", settles: true };
|
|
2581
2583
|
}
|
|
2582
2584
|
// #514: the panel says so itself when its active record is not confirmed. An
|
|
2583
2585
|
// explicit `false` is the panel telling us the value is untrustworthy — never
|
|
2584
2586
|
// adopt through that. (Absent = an older panel that cannot say; the list
|
|
2585
2587
|
// corroboration below then has to carry the whole weight.)
|
|
2586
2588
|
if (parsed.active_confirmed === false) {
|
|
2587
|
-
|
|
2589
|
+
// SETTLES: this is the reported window itself — the panel says "not yet",
|
|
2590
|
+
// and a moment later it says otherwise (#1292).
|
|
2591
|
+
return {
|
|
2592
|
+
ok: false,
|
|
2593
|
+
why: "the panel reported its active workflow as UNCONFIRMED",
|
|
2594
|
+
settles: true,
|
|
2595
|
+
};
|
|
2588
2596
|
}
|
|
2589
2597
|
const list = parsed.workflows;
|
|
2590
|
-
|
|
2598
|
+
// TWO DIFFERENT FACTS, and #1292 made the difference cost real time. An ABSENT
|
|
2599
|
+
// `workflows` field is a property of the installed build — it will not appear
|
|
2600
|
+
// by waiting — while an EMPTY list is a snapshot of what is open right now,
|
|
2601
|
+
// which a mid-restore panel reports before its tabs come back. Folding them
|
|
2602
|
+
// would make every recovery on an older panel 2.9s slower for nothing.
|
|
2603
|
+
if (!Array.isArray(list)) {
|
|
2591
2604
|
return {
|
|
2592
2605
|
ok: false,
|
|
2593
2606
|
why: "the reply carried no open-workflow list to corroborate the active record against",
|
|
2607
|
+
settles: false,
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
if (list.length === 0) {
|
|
2611
|
+
return {
|
|
2612
|
+
ok: false,
|
|
2613
|
+
why: "the reply's open-workflow list was empty, so nothing corroborates the active record",
|
|
2614
|
+
settles: true,
|
|
2594
2615
|
};
|
|
2595
2616
|
}
|
|
2596
2617
|
// Only an AFFIRMATIVE per-record flag can nominate the corroborating entry;
|
|
2597
2618
|
// inferring it from the active object would be circular.
|
|
2598
2619
|
const flaggedActive = list.filter((w) => !!w && typeof w === "object" && w.active === true);
|
|
2599
2620
|
if (flaggedActive.length === 0) {
|
|
2600
|
-
|
|
2621
|
+
// SETTLES: a snapshot taken before the panel re-flagged its active tab.
|
|
2622
|
+
return {
|
|
2623
|
+
ok: false,
|
|
2624
|
+
why: "no entry in the open-workflow list is marked active",
|
|
2625
|
+
settles: true,
|
|
2626
|
+
};
|
|
2601
2627
|
}
|
|
2602
2628
|
if (flaggedActive.length > 1) {
|
|
2603
2629
|
// A mixed/partial snapshot. Exactly the shape that would let another canvas's
|
|
@@ -2605,6 +2631,8 @@ function corroborateActiveForFence(parsed) {
|
|
|
2605
2631
|
return {
|
|
2606
2632
|
ok: false,
|
|
2607
2633
|
why: `${flaggedActive.length} entries in the open-workflow list are marked active (a mixed reply)`,
|
|
2634
|
+
// SETTLES: "mixed" IS the half-reconciled state, by definition.
|
|
2635
|
+
settles: true,
|
|
2608
2636
|
};
|
|
2609
2637
|
}
|
|
2610
2638
|
// Same tri-state primitive the pin path uses. `false` = they name DIFFERENT
|
|
@@ -2615,12 +2643,17 @@ function corroborateActiveForFence(parsed) {
|
|
|
2615
2643
|
return {
|
|
2616
2644
|
ok: false,
|
|
2617
2645
|
why: "the active record and the entry marked active in the open-workflow list name DIFFERENT workflows (a stale or mixed reply)",
|
|
2646
|
+
// SETTLES: a stale half of the snapshot catches up.
|
|
2647
|
+
settles: true,
|
|
2618
2648
|
};
|
|
2619
2649
|
}
|
|
2620
2650
|
if (verdict !== true) {
|
|
2621
2651
|
return {
|
|
2622
2652
|
ok: false,
|
|
2623
2653
|
why: "the active record and the open-workflow list share no comparable identity field, so nothing corroborates it",
|
|
2654
|
+
// DOES NOT SETTLE: a field the records do not carry will not appear by
|
|
2655
|
+
// waiting — that is the reply's SHAPE, not its timing.
|
|
2656
|
+
settles: false,
|
|
2624
2657
|
};
|
|
2625
2658
|
}
|
|
2626
2659
|
return { ok: true, active: active };
|
|
@@ -2687,6 +2720,36 @@ WHY THIS READ WAS NEEDED AT ALL: this session's panel is ${v.version}, and a ` +
|
|
|
2687
2720
|
return "";
|
|
2688
2721
|
}
|
|
2689
2722
|
}
|
|
2723
|
+
/**
|
|
2724
|
+
* #1292 — THE RECONCILIATION WINDOW IS OURS TO ABSORB, NOT THE CALLER'S TO WAIT OUT.
|
|
2725
|
+
*
|
|
2726
|
+
* Right after `panel_restart_comfyui` and the tab's reconnect, the panel is
|
|
2727
|
+
* briefly mid-reconciliation: it answers `workflow_list`, but its active record
|
|
2728
|
+
* is marked UNCONFIRMED (or the list is momentarily mixed). Corroboration
|
|
2729
|
+
* correctly refuses — and the caller got a hard failure for a state that fixes
|
|
2730
|
+
* itself, with the remedy "call this again in a moment". The reporter did, and
|
|
2731
|
+
* the identical call returned `already_current`.
|
|
2732
|
+
*
|
|
2733
|
+
* A recovery operation that tells you to retry it is one that has not finished
|
|
2734
|
+
* recovering. So this path rechecks with a FRESH read, three times over ~2.9s.
|
|
2735
|
+
*
|
|
2736
|
+
* WHY THESE AND NOT A LONGER BUDGET: this runs only when the rebind has already
|
|
2737
|
+
* failed, so the cost is paid on a path that was going to fail anyway — but it is
|
|
2738
|
+
* still latency added to a tool call, and the window this covers is a local
|
|
2739
|
+
* process reconnecting. The reporter's five seconds is an upper bound with a
|
|
2740
|
+
* human in it, not a measurement of the settle.
|
|
2741
|
+
*
|
|
2742
|
+
* WHY ONLY `uncorroborated`: the sibling failure `no_uuid` means the installed
|
|
2743
|
+
* panel exposes no workflow identity at all. Rechecking a build that will never
|
|
2744
|
+
* answer differently just makes the same error slower — the distinction the
|
|
2745
|
+
* status already draws, now with teeth.
|
|
2746
|
+
*
|
|
2747
|
+
* The corroboration rule itself does NOT move. It still fails closed on every
|
|
2748
|
+
* attempt; a recheck buys a fresh snapshot to judge, never a lower bar. What
|
|
2749
|
+
* would be unsafe is arbitrating a mixed reply, and that is exactly what is not
|
|
2750
|
+
* happening here.
|
|
2751
|
+
*/
|
|
2752
|
+
const FENCE_CORROBORATION_RECHECK_STEPS_MS = [400, 900, 1600];
|
|
2690
2753
|
async function rebindWorkflowFence(ctx) {
|
|
2691
2754
|
const tabAtStart = ctx.tabId;
|
|
2692
2755
|
let before = currentWorkflowFence(ctx);
|
|
@@ -2700,29 +2763,68 @@ async function rebindWorkflowFence(ctx) {
|
|
|
2700
2763
|
if (ctx.tabId !== tabAtStart)
|
|
2701
2764
|
before = currentWorkflowFence(ctx);
|
|
2702
2765
|
};
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2766
|
+
// One read + corroboration. Returns null when the READ failed in a way that is
|
|
2767
|
+
// its own status (`unreadable`/`healed_by_panel`) — those are not the transient
|
|
2768
|
+
// window this rechecks, and the settle inside unreadableOrHealed already covers
|
|
2769
|
+
// the case where the refusal itself repaired the fence.
|
|
2770
|
+
const readAndCorroborate = async () => {
|
|
2771
|
+
let parsed = null;
|
|
2772
|
+
try {
|
|
2773
|
+
const res = await ctx.call({ cmd: "workflow_list" }, 6000);
|
|
2774
|
+
syncFenceToCurrentTab();
|
|
2775
|
+
if (res?.isError) {
|
|
2776
|
+
return { done: await unreadableOrHealed(ctx, before, toolResultText(res)) };
|
|
2777
|
+
}
|
|
2778
|
+
parsed = parseToolResultJson(res);
|
|
2709
2779
|
}
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2780
|
+
catch (err) {
|
|
2781
|
+
syncFenceToCurrentTab();
|
|
2782
|
+
return {
|
|
2783
|
+
done: await unreadableOrHealed(ctx, before, err instanceof Error ? err.message : String(err ?? "unknown error")),
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
if (!parsed) {
|
|
2787
|
+
return {
|
|
2788
|
+
done: await unreadableOrHealed(ctx, before, "the panel's workflow_list reply was not readable as JSON"),
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
// CORROBORATE before adopting. The uuid must come from a record the panel's own
|
|
2792
|
+
// open-workflow list agrees is the live canvas — otherwise a stale or mixed
|
|
2793
|
+
// reply's uuid (valid in shape, belonging to ANOTHER canvas) would overwrite
|
|
2794
|
+
// this tab's stamp and be reported as a successful rebind.
|
|
2795
|
+
return { corroborated: corroborateActiveForFence(parsed) };
|
|
2796
|
+
};
|
|
2797
|
+
const first = await readAndCorroborate();
|
|
2798
|
+
if ("done" in first)
|
|
2799
|
+
return first.done;
|
|
2800
|
+
let corroborated = first.corroborated;
|
|
2801
|
+
let attempts = 1;
|
|
2802
|
+
let waitedMs = 0;
|
|
2803
|
+
// #1292 — recheck the RECONCILIATION WINDOW with a fresh read, never a lower bar.
|
|
2804
|
+
// Only a failure that CAN settle is worth re-reading (codex review, P2): a reply
|
|
2805
|
+
// whose SHAPE is wrong — an older build that sends no `workflows` array, records
|
|
2806
|
+
// sharing no comparable identity field — will say exactly the same thing 2.9s
|
|
2807
|
+
// later, so retrying it just makes the identical error slower. That is the same
|
|
2808
|
+
// reasoning that keeps `no_uuid` out of this loop, applied one level down.
|
|
2809
|
+
for (const waitMs of FENCE_CORROBORATION_RECHECK_STEPS_MS) {
|
|
2810
|
+
if (corroborated.ok || !corroborated.settles)
|
|
2811
|
+
break;
|
|
2812
|
+
await sleep(waitMs);
|
|
2813
|
+
waitedMs += waitMs;
|
|
2814
|
+
attempts++;
|
|
2815
|
+
const next = await readAndCorroborate();
|
|
2816
|
+
if ("done" in next)
|
|
2817
|
+
return next.done;
|
|
2818
|
+
corroborated = next.corroborated;
|
|
2718
2819
|
}
|
|
2719
|
-
// CORROBORATE before adopting. The uuid must come from a record the panel's own
|
|
2720
|
-
// open-workflow list agrees is the live canvas — otherwise a stale or mixed
|
|
2721
|
-
// reply's uuid (valid in shape, belonging to ANOTHER canvas) would overwrite
|
|
2722
|
-
// this tab's stamp and be reported as a successful rebind.
|
|
2723
|
-
const corroborated = corroborateActiveForFence(parsed);
|
|
2724
2820
|
if (!corroborated.ok) {
|
|
2725
|
-
return {
|
|
2821
|
+
return {
|
|
2822
|
+
status: "no_identity",
|
|
2823
|
+
before,
|
|
2824
|
+
kind: "uncorroborated",
|
|
2825
|
+
why: corroborated.why,
|
|
2826
|
+
rechecks: { attempts, waitedMs, settles: corroborated.settles },
|
|
2827
|
+
};
|
|
2726
2828
|
}
|
|
2727
2829
|
const active = corroborated.active;
|
|
2728
2830
|
const uuid = responseWorkflowUuid(active);
|
|
@@ -3062,9 +3164,28 @@ panelGapNote = "") {
|
|
|
3062
3164
|
`plain reload can serve the same cached bundle again; only a hard refresh replaces ` +
|
|
3063
3165
|
`it. If a hard refresh does not help, the installed pack itself predates the ` +
|
|
3064
3166
|
`per-workflow identity and must be UPDATED — no rebind can add it.`
|
|
3065
|
-
:
|
|
3066
|
-
|
|
3067
|
-
|
|
3167
|
+
: // #1292 — DO NOT PRESCRIBE WHAT WE JUST DID. This path now rechecks
|
|
3168
|
+
// with fresh reads before failing, so "call this again in a moment"
|
|
3169
|
+
// would be advice the tool already took on the caller's behalf — and
|
|
3170
|
+
// taking it a fourth time is the least likely thing left to work.
|
|
3171
|
+
// When the rechecks ran, say so and move on to the remedy that has
|
|
3172
|
+
// not been tried.
|
|
3173
|
+
(r.rechecks && !r.rechecks.settles
|
|
3174
|
+
? // Waiting cannot fix a reply whose SHAPE is wrong, so do not
|
|
3175
|
+
// prescribe waiting. This said "call this again in a moment" to
|
|
3176
|
+
// a caller whose panel will answer identically forever.
|
|
3177
|
+
`\n\nWHY RETRYING WILL NOT HELP: this is the shape of the panel's reply, not a ` +
|
|
3178
|
+
`moment in its reconciliation — a later read answers the same way. ` +
|
|
3179
|
+
`${RELOAD_TAB_REMEDY} If it survives a hard refresh, the installed pack is too ` +
|
|
3180
|
+
`old to corroborate its own active workflow and must be UPDATED.`
|
|
3181
|
+
: r.rechecks && r.rechecks.attempts > 1
|
|
3182
|
+
? `\n\nALREADY TRIED: the panel was re-read ${r.rechecks.attempts} times over ` +
|
|
3183
|
+
`~${(r.rechecks.waitedMs / 1000).toFixed(1)}s and never corroborated. That window ` +
|
|
3184
|
+
`covers the usual post-restart reconciliation, so this is probably NOT the ` +
|
|
3185
|
+
`transient case.\n\nWHAT TO DO: ${RELOAD_TAB_REMEDY}`
|
|
3186
|
+
: `\n\nWHAT TO DO: call this again in a moment. A stale or mixed workflow list is ` +
|
|
3187
|
+
`usually transient — it settles once the panel finishes reconciling its tabs. ` +
|
|
3188
|
+
`If it persists across a few attempts: ${RELOAD_TAB_REMEDY}`);
|
|
3068
3189
|
if (!r.before.known) {
|
|
3069
3190
|
return {
|
|
3070
3191
|
binding: "unverified",
|