comfyui-mcp 0.52.61 → 0.52.63
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.
|
@@ -814,6 +814,10 @@ export const __panelToolsTestHooks = {
|
|
|
814
814
|
setRunLateAckGraceMs(ms) {
|
|
815
815
|
runLateAckGraceMsOverride = ms;
|
|
816
816
|
},
|
|
817
|
+
/** Shrink the #2078 save-timeout settle grace so a still-dirty follow-up does not wait 5s. */
|
|
818
|
+
setSaveTimeoutSettleGraceMs(ms) {
|
|
819
|
+
saveTimeoutSettleGraceMsOverride = ms;
|
|
820
|
+
},
|
|
817
821
|
isRetrySafeCmd,
|
|
818
822
|
isTransientReconnectError,
|
|
819
823
|
// CivitAI sample-image gating (#623): the predicate that decides which results'
|
|
@@ -3615,30 +3619,87 @@ function saveLandedAfterTimeout(list) {
|
|
|
3615
3619
|
const rec = active;
|
|
3616
3620
|
return rec.modified === false && rec.persisted === true;
|
|
3617
3621
|
}
|
|
3622
|
+
/**
|
|
3623
|
+
* A first save legitimately changes `tmp:<instance>` into `wf:<path>`. The
|
|
3624
|
+
* updated panel publishes the save command's eventual success under its exact
|
|
3625
|
+
* rid, so only that receipt can bridge the identity change. If the receipt field
|
|
3626
|
+
* is absent or does not match, the outcome remains unknown rather than crediting
|
|
3627
|
+
* a clean workflow that may belong to a tab the user switched to.
|
|
3628
|
+
*/
|
|
3629
|
+
function saveProbeMatchesLateReceipt(saveRid, list) {
|
|
3630
|
+
if (!saveRid || !Array.isArray(list?.late_save_receipts))
|
|
3631
|
+
return false;
|
|
3632
|
+
if (!list?.active || typeof list.active !== "object" || Array.isArray(list.active))
|
|
3633
|
+
return false;
|
|
3634
|
+
const receipt = list.late_save_receipts.find((candidate) => candidate &&
|
|
3635
|
+
typeof candidate === "object" &&
|
|
3636
|
+
!Array.isArray(candidate) &&
|
|
3637
|
+
candidate.rid === saveRid &&
|
|
3638
|
+
candidate.cmd === "workflow_save");
|
|
3639
|
+
if (!receipt || typeof receipt !== "object" || Array.isArray(receipt))
|
|
3640
|
+
return false;
|
|
3641
|
+
const result = receipt.result;
|
|
3642
|
+
if (!result || typeof result !== "object" || Array.isArray(result))
|
|
3643
|
+
return false;
|
|
3644
|
+
if (result.saved !== true)
|
|
3645
|
+
return false;
|
|
3646
|
+
// The panel's receipt is the save command's own eventual success. Still require
|
|
3647
|
+
// its produced workflow identity to match the currently observed active canvas;
|
|
3648
|
+
// a valid late save on A must not be credited after the user switched to B.
|
|
3649
|
+
return identityVerdict(result, list.active) === true;
|
|
3650
|
+
}
|
|
3618
3651
|
const SAVE_TIMEOUT_OUTCOME_UNKNOWN = `\n\nOUTCOME UNKNOWN: the save was already in flight when this budget fired. ` +
|
|
3619
3652
|
`Confirm by reading the saved file (or panel_list_workflows) before retrying — a re-issue may write twice.`;
|
|
3620
3653
|
/**
|
|
3621
|
-
* #
|
|
3622
|
-
*
|
|
3623
|
-
*
|
|
3624
|
-
*
|
|
3654
|
+
* #2078 — how long to keep reading tab state after the 13s save budget.
|
|
3655
|
+
*
|
|
3656
|
+
* #2004 already listed once. The reporter's PUT landed a moment later: that
|
|
3657
|
+
* snapshot was still dirty, the next panel_list_workflows was already clean.
|
|
3658
|
+
* Paid only on a path that has already spent its full bound. A canvas that
|
|
3659
|
+
* is clean on the first list returns immediately.
|
|
3660
|
+
*/
|
|
3661
|
+
const SAVE_TIMEOUT_SETTLE_GRACE_MS = 5_000;
|
|
3662
|
+
const SAVE_TIMEOUT_SETTLE_POLL_MS = 200;
|
|
3663
|
+
let saveTimeoutSettleGraceMsOverride = null;
|
|
3664
|
+
function saveTimeoutSettleGraceMs() {
|
|
3665
|
+
return saveTimeoutSettleGraceMsOverride ?? SAVE_TIMEOUT_SETTLE_GRACE_MS;
|
|
3666
|
+
}
|
|
3667
|
+
function saveAckAfterTimeout(list) {
|
|
3668
|
+
return ok({
|
|
3669
|
+
saved: true,
|
|
3670
|
+
late_ack: true,
|
|
3671
|
+
acknowledged_after_timeout: true,
|
|
3672
|
+
active: list?.active,
|
|
3673
|
+
});
|
|
3674
|
+
}
|
|
3675
|
+
/**
|
|
3676
|
+
* #2004 / #2078 — the panel's 13s budget fired while userdata PUT was still
|
|
3677
|
+
* running. A list that shows modified:false persisted:true is the save
|
|
3678
|
+
* completing. One snapshot is not enough: the timeout-time canvas is still
|
|
3679
|
+
* dirty, and the PUT can land between that read and the caller's next list.
|
|
3680
|
+
* Keep reading until the grace, then stay outcome-unknown. persisted:true
|
|
3681
|
+
* alone is the timeout-time snapshot of a previously-saved dirty tab, not proof.
|
|
3625
3682
|
*/
|
|
3626
|
-
async function settleWorkflowSaveTimeout(res, ctx) {
|
|
3683
|
+
async function settleWorkflowSaveTimeout(res, ctx, saveRid) {
|
|
3627
3684
|
if (!isWorkflowSaveBudgetTimeout(res))
|
|
3628
3685
|
return res;
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
}
|
|
3686
|
+
const deadline = Date.now() + saveTimeoutSettleGraceMs();
|
|
3687
|
+
for (;;) {
|
|
3688
|
+
try {
|
|
3689
|
+
const listRes = await ctx.call({ cmd: "workflow_list" }, 6000);
|
|
3690
|
+
const list = parseToolResultJson(listRes);
|
|
3691
|
+
if (saveLandedAfterTimeout(list) &&
|
|
3692
|
+
saveProbeMatchesLateReceipt(saveRid, list)) {
|
|
3693
|
+
return saveAckAfterTimeout(list);
|
|
3694
|
+
}
|
|
3638
3695
|
}
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3696
|
+
catch {
|
|
3697
|
+
/* keep polling until the grace; the note below is the honest remainder */
|
|
3698
|
+
}
|
|
3699
|
+
const left = deadline - Date.now();
|
|
3700
|
+
if (left <= 0)
|
|
3701
|
+
break;
|
|
3702
|
+
await sleep(Math.max(1, Math.min(SAVE_TIMEOUT_SETTLE_POLL_MS, left)));
|
|
3642
3703
|
}
|
|
3643
3704
|
return appendToolResultText(res, SAVE_TIMEOUT_OUTCOME_UNKNOWN);
|
|
3644
3705
|
}
|
|
@@ -8763,13 +8824,20 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets, onRunTicketOpen
|
|
|
8763
8824
|
// here is the same recovery, just delayed until a canvas tab exists; it is
|
|
8764
8825
|
// not a silent re-target.
|
|
8765
8826
|
if (isScopeAddress(ctx.tabId)) {
|
|
8766
|
-
|
|
8827
|
+
const deferredConsent = ctx.pendingScopeRecoveryConsent === true ||
|
|
8828
|
+
(typeof bridge.hasScopeRecoveryConsent === "function" &&
|
|
8829
|
+
bridge.hasScopeRecoveryConsent(ctx.tabId));
|
|
8830
|
+
if (deferredConsent) {
|
|
8767
8831
|
try {
|
|
8768
8832
|
const out = rebindToActiveTab({ scopeRecoveryConsent: true });
|
|
8769
|
-
if (out.rebound)
|
|
8833
|
+
if (out.rebound) {
|
|
8770
8834
|
ctx.pendingScopeRecoveryConsent = false;
|
|
8771
|
-
|
|
8835
|
+
bridge.clearScopeRecoveryConsent?.(ctx.tabId);
|
|
8836
|
+
}
|
|
8837
|
+
else if ((interactiveTabIds() ?? []).length > 0) {
|
|
8772
8838
|
ctx.pendingScopeRecoveryConsent = false;
|
|
8839
|
+
bridge.clearScopeRecoveryConsent?.(ctx.tabId);
|
|
8840
|
+
}
|
|
8773
8841
|
}
|
|
8774
8842
|
catch {
|
|
8775
8843
|
// still nothing bindable — keep the consent for the next attempt
|
|
@@ -13805,9 +13873,12 @@ export function buildPanelToolDefs() {
|
|
|
13805
13873
|
if (ctx.awaitReachable && !(await ctx.awaitReachable())) {
|
|
13806
13874
|
return noReachableTabFail(args.name ? "workflow_save_as" : "workflow_save", ctx);
|
|
13807
13875
|
}
|
|
13876
|
+
let saveRid;
|
|
13808
13877
|
const saveOnce = () => args.name
|
|
13809
13878
|
? ctx.call({ cmd: "workflow_save_as", name: args.name }, 15000)
|
|
13810
|
-
: ctx.call({ cmd: "workflow_save" }, 15000)
|
|
13879
|
+
: ctx.call({ cmd: "workflow_save" }, 15000, (rid) => {
|
|
13880
|
+
saveRid = rid;
|
|
13881
|
+
});
|
|
13811
13882
|
let res = await saveOnce();
|
|
13812
13883
|
// #1710 — dest tab + dest nodes, extra still stamped as the save-as SOURCE.
|
|
13813
13884
|
// Rebind extra to the confirmed dest, then retry the same save once.
|
|
@@ -13823,7 +13894,7 @@ export function buildPanelToolDefs() {
|
|
|
13823
13894
|
}
|
|
13824
13895
|
}
|
|
13825
13896
|
if (res.isError && !args.name)
|
|
13826
|
-
res = await settleWorkflowSaveTimeout(res, ctx);
|
|
13897
|
+
res = await settleWorkflowSaveTimeout(res, ctx, saveRid);
|
|
13827
13898
|
if (res.isError) {
|
|
13828
13899
|
// #1873 — named Save-As 409s by contract; the panel's "choose a different
|
|
13829
13900
|
// name" is what forced a third file. Keep the 409 and name the rename path.
|
|
@@ -14032,10 +14103,12 @@ export function buildPanelToolDefs() {
|
|
|
14032
14103
|
if (recoveringScope)
|
|
14033
14104
|
ctx.bridge.endScopeRecovery?.(before);
|
|
14034
14105
|
}
|
|
14035
|
-
// panel#1557 — the consent survives a zero-tab DEFER so a graph
|
|
14036
|
-
//
|
|
14037
|
-
if (deferredBind && recoveringScope)
|
|
14106
|
+
// panel#1557 — the consent survives a zero-tab DEFER so a later graph
|
|
14107
|
+
// call (possibly on a fresh HTTP MCP ctx) can finish the recovery.
|
|
14108
|
+
if (deferredBind && recoveringScope) {
|
|
14038
14109
|
ctx.pendingScopeRecoveryConsent = true;
|
|
14110
|
+
ctx.bridge.armScopeRecoveryConsent?.(before);
|
|
14111
|
+
}
|
|
14039
14112
|
// Detect the rebind regardless of whether awaitReachable or rebindToActiveTab
|
|
14040
14113
|
// performed it (either mutates ctx.tabId), so the note is never swallowed.
|
|
14041
14114
|
if (!deferredBind && ctx.tabId !== before) {
|