comfyui-mcp 0.52.52 → 0.52.53

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.
@@ -2250,16 +2250,17 @@ function clearSessionRestartDispatchIfSame(ctx, token) {
2250
2250
  * against the fixed `deadline`.
2251
2251
  */
2252
2252
  async function observeRecovery(timing, deadline, opts) {
2253
- const start = Date.now();
2253
+ const prior = opts.continueFrom;
2254
+ const start = prior ? Date.now() - prior.waited_ms : Date.now();
2254
2255
  const gate = opts.gate;
2255
2256
  const intervalMs = panelRebootTimingOverride
2256
2257
  ? Math.max(1, timing.intervalMs)
2257
2258
  : Math.max(50, timing.intervalMs);
2258
2259
  const probe = healthProbeOverride ?? probeComfyEndpoint;
2259
2260
  const currentDeadline = () => gate?.deadline ?? deadline;
2260
- let sawDown = false;
2261
- let lastStatus;
2262
- let attempts = 0;
2261
+ let sawDown = prior?.sawDown ?? false;
2262
+ let lastStatus = prior?.lastStatus;
2263
+ let attempts = prior?.attempts ?? 0;
2263
2264
  for (;;) {
2264
2265
  if (gate?.cancelled)
2265
2266
  break;
@@ -2319,6 +2320,63 @@ async function observeRecovery(timing, deadline, opts) {
2319
2320
  }
2320
2321
  return { ready: false, waited_ms: Date.now() - start, attempts, sawDown, lastStatus };
2321
2322
  }
2323
+ function startPostRestartTabWait(ctx, preRestartPanelIdentity, overallDeadline) {
2324
+ const budgetMs = Math.max(0, overallDeadline - Date.now());
2325
+ if (ctx.awaitPostRestartReachable) {
2326
+ return ctx.awaitPostRestartReachable(preRestartPanelIdentity, budgetMs);
2327
+ }
2328
+ if (ctx.awaitReachable)
2329
+ return ctx.awaitReachable(budgetMs);
2330
+ return Promise.resolve(true);
2331
+ }
2332
+ /**
2333
+ * #1996 r2 — keep watching /system_stats during the post-restart tab wait.
2334
+ *
2335
+ * The dedicated readiness budget still expires first (so a dead server is not
2336
+ * held until the whole-handler cap). The reconnect wait after it used to look
2337
+ * ONLY at the tab, so a server that started listening in that reserved window
2338
+ * was reported `server_ready:false` while the handler was still running — the
2339
+ * 0.52.51 recurrence (`recovered_ms:184461`, ComfyUI already listening, then
2340
+ * `panel_graph_outline` succeeded). Continue the SAME down→up observation in
2341
+ * parallel; abort it when the tab wait finishes so an instant wait cannot
2342
+ * stretch to `overallDeadline`.
2343
+ *
2344
+ * A later healthy sample still requires the earlier down (carried via
2345
+ * `continueFrom`). A tab coming back still does not certify the boot.
2346
+ */
2347
+ async function awaitTabWhileContinuingRecovery(args) {
2348
+ const tabWait = startPostRestartTabWait(args.ctx, args.preRestartPanelIdentity, args.overallDeadline);
2349
+ if (args.recovery.ready) {
2350
+ return { recovery: args.recovery, tabBack: await tabWait };
2351
+ }
2352
+ const gate = {
2353
+ dispatched: true,
2354
+ dispatchedAt: 0,
2355
+ cancelled: false,
2356
+ deadline: args.overallDeadline,
2357
+ waitDispatched: Promise.resolve(),
2358
+ };
2359
+ const extraPromise = observeRecovery(args.timing, args.overallDeadline, {
2360
+ healthBase: args.healthBase,
2361
+ gate,
2362
+ continueFrom: args.recovery,
2363
+ });
2364
+ const tabBack = await tabWait;
2365
+ gate.cancelled = true;
2366
+ const extra = await extraPromise;
2367
+ if (extra.ready)
2368
+ return { recovery: extra, tabBack };
2369
+ return {
2370
+ recovery: {
2371
+ ready: false,
2372
+ waited_ms: extra.waited_ms,
2373
+ attempts: extra.attempts,
2374
+ sawDown: args.recovery.sawDown || extra.sawDown,
2375
+ lastStatus: extra.lastStatus ?? args.recovery.lastStatus,
2376
+ },
2377
+ tabBack,
2378
+ };
2379
+ }
2322
2380
  // ---- workflow_open verify-after-timeout (#215/#319/#496/#661) --------------
2323
2381
  // `panel_open_workflow` forwards `workflow_open` over the UI bridge and waits for
2324
2382
  // the tab to ACK. When the target tab is BACKGROUNDED/FROZEN, or the workflow is
@@ -15046,16 +15104,7 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
15046
15104
  }
15047
15105
  // Otherwise (the process WAS stopped/started, or restartComfyUI's own readiness
15048
15106
  // poll merely expired — neither terminal) DEFER to OUR OWN observed DOWN→UP.
15049
- const recovery = await proofPromise;
15050
- // #742 r4/r5/r15: the managed restart was observed back — clear THIS
15051
- // session's record, CLEAR-IF-SAME: only when the session still holds
15052
- // the token THIS restart stamped (a concurrent dispatch's newer
15053
- // record survives). restartComfyUI also clears its own process-wide
15054
- // record on success; this covers only-observer-saw-it recoveries.
15055
- if (recovery.ready && headlessDispatchToken != null) {
15056
- clearSessionRestartDispatchIfSame(ctx, headlessDispatchToken);
15057
- }
15058
- const observed = recovery.via === "observed-cycle";
15107
+ let recovery = await proofPromise;
15059
15108
  // The headless path restarts ComfyUI out-of-band too. Server recovery alone
15060
15109
  // is not graph-tool readiness: wait for the browser tab to reconnect, then verify
15061
15110
  // the same workflow-stamp capability the bridge requires before it dispatches a
@@ -15066,11 +15115,27 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
15066
15115
  // bound one — `recovery.ready ? … : false` — so a managed kill+relaunch whose
15067
15116
  // cold start outran the observation window forfeited the reconnect wait too,
15068
15117
  // and reported a bare `false` for a tab nobody had looked at.
15069
- const tabBack = ctx.awaitPostRestartReachable
15070
- ? await ctx.awaitPostRestartReachable(preRestartPanelIdentity, Math.max(0, overallDeadline - Date.now()))
15071
- : ctx.awaitReachable
15072
- ? await ctx.awaitReachable(Math.max(0, overallDeadline - Date.now()))
15073
- : true;
15118
+ // #1996 r2: the tab wait also continues the health observation, so a boot
15119
+ // that starts listening in this reserved window is not reported unready
15120
+ // while the handler is still running.
15121
+ let tabBack;
15122
+ ({ recovery, tabBack } = await awaitTabWhileContinuingRecovery({
15123
+ recovery,
15124
+ timing: headlessTiming,
15125
+ healthBase,
15126
+ overallDeadline,
15127
+ ctx,
15128
+ preRestartPanelIdentity,
15129
+ }));
15130
+ // #742 r4/r5/r15: the managed restart was observed back — clear THIS
15131
+ // session's record, CLEAR-IF-SAME: only when the session still holds
15132
+ // the token THIS restart stamped (a concurrent dispatch's newer
15133
+ // record survives). restartComfyUI also clears its own process-wide
15134
+ // record on success; this covers only-observer-saw-it recoveries.
15135
+ if (recovery.ready && headlessDispatchToken != null) {
15136
+ clearSessionRestartDispatchIfSame(ctx, headlessDispatchToken);
15137
+ }
15138
+ const observed = recovery.via === "observed-cycle";
15074
15139
  // #1996: `recovery.ready &&` is LOAD-BEARING now that the wait above runs on
15075
15140
  // both outcomes. Without it a tab that re-registered while the server was still
15076
15141
  // unproven would report ready:true — turning the honest refusal this path
@@ -15857,13 +15922,7 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
15857
15922
  overallDeadline,
15858
15923
  reserveMs: reconnectWaitTiming().budgetMs,
15859
15924
  });
15860
- const recovery = await recoveryPromise;
15861
- // #742 r4/r5/r15: the dispatched restart was observed back — clear the
15862
- // record, CLEAR-IF-SAME: only when the session still holds the token
15863
- // THIS dispatch stamped (a concurrent dispatch's newer record survives).
15864
- if (recovery.ready && acceptedDispatchToken != null) {
15865
- clearSessionRestartDispatchIfSame(ctx, acceptedDispatchToken);
15866
- }
15925
+ let recovery = await recoveryPromise;
15867
15926
  // #400/#709: ComfyUI is healthy, but the panel's browser tab re-registers its own
15868
15927
  // socket a moment later. The old socket can still be reachable after dispatch, so
15869
15928
  // the generation waiter below requires a fresh hello before reporting readiness.
@@ -15882,11 +15941,24 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
15882
15941
  // it honestly could not give AND the reconnect wait it could have given, which is
15883
15942
  // the only thing on this path that calls `ensureReachable()` to heal the session
15884
15943
  // binding onto the returning tab. An unconfirmed boot now still gets watched.
15885
- const tabBack = ctx.awaitPostRestartReachable
15886
- ? await ctx.awaitPostRestartReachable(preRestartPanelIdentity, Math.max(0, overallDeadline - Date.now()))
15887
- : ctx.awaitReachable
15888
- ? await ctx.awaitReachable(Math.max(0, overallDeadline - Date.now()))
15889
- : true;
15944
+ // #1996 r2: the wait also continues /system_stats, so a boot that starts
15945
+ // listening in this reserved window is not reported unready while we are
15946
+ // still inside the handler.
15947
+ let tabBack;
15948
+ ({ recovery, tabBack } = await awaitTabWhileContinuingRecovery({
15949
+ recovery,
15950
+ timing,
15951
+ healthBase,
15952
+ overallDeadline,
15953
+ ctx,
15954
+ preRestartPanelIdentity,
15955
+ }));
15956
+ // #742 r4/r5/r15: the dispatched restart was observed back — clear the
15957
+ // record, CLEAR-IF-SAME: only when the session still holds the token
15958
+ // THIS dispatch stamped (a concurrent dispatch's newer record survives).
15959
+ if (recovery.ready && acceptedDispatchToken != null) {
15960
+ clearSessionRestartDispatchIfSame(ctx, acceptedDispatchToken);
15961
+ }
15890
15962
  if (!recovery.ready) {
15891
15963
  const waited = Math.round(recovery.waited_ms / 1000);
15892
15964
  // #1996: the refusal itself is NOT the defect and is left exactly as it was.