comfyui-mcp 0.52.82 → 0.52.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.
|
@@ -190,7 +190,7 @@ import { RunCompletions } from "./run-completion-journal.js";
|
|
|
190
190
|
import { AskAnswers, askFingerprint, PANEL_ASK_ID_PREFIX, } from "./ask-answer-journal.js";
|
|
191
191
|
import { getQueueVerified, resetClient, resetObjectInfoCache, } from "../comfyui/client.js";
|
|
192
192
|
import { convertUiToApi, collectNodeTypes } from "../services/workflow-converter.js";
|
|
193
|
-
import { restartComfyUI, preflightLocalRestart, readServingArgv, describeArgvDrift, recordRestartDispatch, clearRestartDispatch, getRestartDispatchRecord, RESTART_DISPATCH_CAUSATION_WINDOW_MS, PROCESS_WIDE_RESTART_DISPATCH_TOKEN, __processControlTestHooks, } from "../services/process-control.js";
|
|
193
|
+
import { restartComfyUI, preflightLocalRestart, readServingArgv, resolveVerifiedProxyRestartTarget, describeArgvDrift, recordRestartDispatch, clearRestartDispatch, getRestartDispatchRecord, RESTART_DISPATCH_CAUSATION_WINDOW_MS, PROCESS_WIDE_RESTART_DISPATCH_TOKEN, __processControlTestHooks, } from "../services/process-control.js";
|
|
194
194
|
import { resetManagerApiCache } from "../services/manager-api-cache.js";
|
|
195
195
|
import { desktopSavedLaunchArgs, describeSavedLaunchArgDrift, } from "../services/desktop-launch-args.js";
|
|
196
196
|
import { config, isRemoteMode, isCloudMode, getBootLocalComfyUIBaseUrl, getComfyUIBaseUrl, getComfyuiTargetGeneration, } from "../config.js";
|
|
@@ -735,6 +735,10 @@ export const __panelToolsTestHooks = {
|
|
|
735
735
|
setLocalRestartPreflight(fn) {
|
|
736
736
|
localRestartPreflightOverride = fn;
|
|
737
737
|
},
|
|
738
|
+
/** Inject the narrow EZi/CEI proxy proof; null restores the live resolver. */
|
|
739
|
+
setVerifiedProxyRestartTarget(fn) {
|
|
740
|
+
verifiedProxyRestartTargetOverride = fn;
|
|
741
|
+
},
|
|
738
742
|
/** Inject a fast #742 decline-probe recheck window so decline-path tests
|
|
739
743
|
* don't wait the real ~6s. null restores the DECLINE_PROBE_* constants. */
|
|
740
744
|
setDeclineProbeTiming(timing) {
|
|
@@ -2221,6 +2225,16 @@ let healthProbeOverride = null;
|
|
|
2221
2225
|
/** Test injection for the #742 refuse-safe restart preflight (the real one is
|
|
2222
2226
|
* preflightLocalRestart in process-control). null → the live preflight. */
|
|
2223
2227
|
let localRestartPreflightOverride = null;
|
|
2228
|
+
let verifiedProxyRestartTargetOverride = null;
|
|
2229
|
+
function panelVerifiedProxyRestartTarget() {
|
|
2230
|
+
return (verifiedProxyRestartTargetOverride ?? resolveVerifiedProxyRestartTarget)();
|
|
2231
|
+
}
|
|
2232
|
+
function proxyTargetBindsPanel(ctx, target) {
|
|
2233
|
+
return (target != null &&
|
|
2234
|
+
sameHttpBase(getComfyUIBaseUrl(), target.proxyBase) &&
|
|
2235
|
+
ctx.bridge?.tabIsLocal?.(ctx.tabId) === true &&
|
|
2236
|
+
sameHttpBase(ctx.bridge?.tabServerOrigin?.(ctx.tabId), target.proxyBase));
|
|
2237
|
+
}
|
|
2224
2238
|
/** Test injection for the #742 decline-probe recheck window, so tests don't
|
|
2225
2239
|
* wait the real ~6s. null → the DECLINE_PROBE_* constants. */
|
|
2226
2240
|
let declineProbeTimingOverride = null;
|
|
@@ -5679,10 +5693,22 @@ function isRetryableRunToNodeStampRace(res, rejection) {
|
|
|
5679
5693
|
return (/workflow graph CHANGED after the run was queued/i.test(text) &&
|
|
5680
5694
|
/Nothing was queued/i.test(text));
|
|
5681
5695
|
}
|
|
5696
|
+
/**
|
|
5697
|
+
* #2120 — an older panel can decorate a fixed seed while it builds the deferred
|
|
5698
|
+
* run prompt. That can make the same scoped run hit the certified stamp race more
|
|
5699
|
+
* than once. The panel still has to identify the changed entry as a seed; this is
|
|
5700
|
+
* deliberately narrower than allowing an arbitrary third dispatch.
|
|
5701
|
+
*/
|
|
5702
|
+
const MAX_SCOPED_STAMP_RACE_RETRIES = 2;
|
|
5703
|
+
function isSeedRunToNodeStampRace(res, rejection) {
|
|
5704
|
+
return (isRetryableRunToNodeStampRace(res, rejection) &&
|
|
5705
|
+
/\bdiffering\b[\s\S]{0,160}\bseed\b/i.test(toolResultText(rejection)));
|
|
5706
|
+
}
|
|
5682
5707
|
export const __panelRunTestHooks = {
|
|
5683
5708
|
detectRunRejection,
|
|
5684
5709
|
formatRunRejection,
|
|
5685
5710
|
isRetryableRunToNodeStampRace,
|
|
5711
|
+
isSeedRunToNodeStampRace,
|
|
5686
5712
|
describeDroppedOutputs,
|
|
5687
5713
|
};
|
|
5688
5714
|
/** Drop a trailing .json (case-insensitive) so filename/path forms compare equal. */
|
|
@@ -13262,15 +13288,25 @@ export function buildPanelToolDefs() {
|
|
|
13262
13288
|
// "you'll be notified automatically" guidance. Only a genuine queue
|
|
13263
13289
|
// gets the anti-poll note below.
|
|
13264
13290
|
let rejection = detectRunRejection(res);
|
|
13265
|
-
// #772 —
|
|
13266
|
-
//
|
|
13267
|
-
//
|
|
13268
|
-
//
|
|
13291
|
+
// #772/#2120 — re-issue only the scoped-run stamp race the panel explicitly
|
|
13292
|
+
// certifies as not-queued. Gated on OUR OWN args (this really was a scoped run)
|
|
13293
|
+
// and on the panel having ANSWERED, so an unknown outcome is never retried.
|
|
13294
|
+
// The ordinary race gets one re-issue. A repeated race gets ONE additional
|
|
13295
|
+
// chance only when the panel names a differing seed entry — the fixed-seed
|
|
13296
|
+
// decoration race reported in #2120. Every attempt still passes the panel's
|
|
13297
|
+
// graph-stamp check independently; this is not a bypass or a blind mutation
|
|
13298
|
+
// retry.
|
|
13269
13299
|
let scopeRebuilt = false;
|
|
13270
|
-
|
|
13300
|
+
let scopeRetryCount = 0;
|
|
13301
|
+
while (rejection &&
|
|
13271
13302
|
typeof args.to_node_id === "number" &&
|
|
13272
|
-
isRetryableRunToNodeStampRace(res, rejection)
|
|
13303
|
+
isRetryableRunToNodeStampRace(res, rejection) &&
|
|
13304
|
+
(scopeRetryCount === 0 ||
|
|
13305
|
+
(scopeRetryCount < MAX_SCOPED_STAMP_RACE_RETRIES &&
|
|
13306
|
+
isSeedRunToNodeStampRace(res, rejection)))) {
|
|
13273
13307
|
scopeRebuilt = true;
|
|
13308
|
+
scopeRetryCount += 1;
|
|
13309
|
+
let retryResultReceived = false;
|
|
13274
13310
|
try {
|
|
13275
13311
|
// #1050 — SETTLE before re-issuing. The panel refuses this race when the
|
|
13276
13312
|
// graph changed between dispatch and apply, and re-dispatching in the
|
|
@@ -13292,6 +13328,7 @@ export function buildPanelToolDefs() {
|
|
|
13292
13328
|
// prompt id rather than allowing that stale arm to be synthesized (#2021).
|
|
13293
13329
|
runDispatchedAt = Date.now();
|
|
13294
13330
|
res = await ctx.call(runCmd, 20000, observeRunRid);
|
|
13331
|
+
retryResultReceived = true;
|
|
13295
13332
|
// #1175 — the re-issue can miss its window exactly as the first
|
|
13296
13333
|
// dispatch can, and this is the one whose outcome is genuinely open
|
|
13297
13334
|
// (the first was CERTIFIED to have queued nothing). Reconcile it on
|
|
@@ -13300,17 +13337,37 @@ export function buildPanelToolDefs() {
|
|
|
13300
13337
|
rejection = detectRunRejection(res);
|
|
13301
13338
|
}
|
|
13302
13339
|
catch (err) {
|
|
13340
|
+
// A bridge/late-receipt failure after ctx.call already returned must not
|
|
13341
|
+
// erase the retry's own result (notably its retry_of token). Preserve it
|
|
13342
|
+
// verbatim and stop: no further dispatch is justified by a failed local
|
|
13343
|
+
// reconciliation step.
|
|
13344
|
+
if (retryResultReceived) {
|
|
13345
|
+
return appendToolResultText(res, `\n\n(Dispatch history: the retry dispatch returned the result above, but local ` +
|
|
13346
|
+
`late-ack reconciliation failed before this handler could finish ` +
|
|
13347
|
+
`interpreting it. That result is preserved and no further dispatch was ` +
|
|
13348
|
+
`made. Do not infer an additional queue entry from the reconciliation ` +
|
|
13349
|
+
`failure alone. (${err instanceof Error ? err.message : String(err)})`);
|
|
13350
|
+
}
|
|
13303
13351
|
// ctx.call settles every panel/transport failure into a ToolResult and does
|
|
13304
13352
|
// not throw today — but this await is the one point where a throw would
|
|
13305
|
-
// DESTROY evidence we already hold: that
|
|
13306
|
-
// not-queued, and that
|
|
13307
|
-
// unknown. Propagating the raw throw would lose both and invite a
|
|
13308
|
-
// dispatch. Report what we observed, claim nothing we did not.
|
|
13309
|
-
|
|
13310
|
-
`
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13353
|
+
// DESTROY evidence we already hold: that every prior dispatch was
|
|
13354
|
+
// certified not-queued, and that this dispatch went out whose outcome is
|
|
13355
|
+
// now unknown. Propagating the raw throw would lose both and invite a
|
|
13356
|
+
// blind dispatch. Report what we observed, claim nothing we did not.
|
|
13357
|
+
if (scopeRetryCount === 1) {
|
|
13358
|
+
return fail(`panel_run re-issued the scoped run after the panel's certified run-to-node ` +
|
|
13359
|
+
`stamp race, and that SECOND dispatch failed before any reply could be read — ` +
|
|
13360
|
+
`its outcome is UNKNOWN and it may have queued a render. The FIRST dispatch was ` +
|
|
13361
|
+
`certified by the panel to have queued nothing. Do NOT re-run blindly: check the ` +
|
|
13362
|
+
`queue (action:"list") or get_history before deciding. (${err instanceof Error ? err.message : String(err)})`);
|
|
13363
|
+
}
|
|
13364
|
+
return fail(`panel_run re-issued the scoped run ${scopeRetryCount} times after the panel ` +
|
|
13365
|
+
`certified each prior run-to-node stamp race as not-queued, and the ` +
|
|
13366
|
+
`dispatch ${scopeRetryCount + 1} failed before any reply could be read — ` +
|
|
13367
|
+
`its outcome is UNKNOWN and it may have queued a render. The first ` +
|
|
13368
|
+
`${scopeRetryCount} dispatches were certified by the panel to have queued ` +
|
|
13369
|
+
`nothing. Do NOT re-run blindly: check the queue (action:"list") or ` +
|
|
13370
|
+
`get_history before deciding. (${err instanceof Error ? err.message : String(err)})`);
|
|
13314
13371
|
}
|
|
13315
13372
|
}
|
|
13316
13373
|
if (rejection) {
|
|
@@ -13327,14 +13384,24 @@ export function buildPanelToolDefs() {
|
|
|
13327
13384
|
// cannot tell whether these are one event or two.
|
|
13328
13385
|
if (!scopeRebuilt)
|
|
13329
13386
|
return lateAckNote ? appendToolResultText(rejection, lateAckNote) : rejection;
|
|
13330
|
-
return appendToolResultText(rejection,
|
|
13331
|
-
|
|
13332
|
-
|
|
13333
|
-
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
|
|
13387
|
+
return appendToolResultText(rejection, scopeRetryCount === 1
|
|
13388
|
+
? `\n\n(Dispatch history: the first graph_run was refused by the panel's run-to-node ` +
|
|
13389
|
+
`graph-stamp race, which CERTIFIED that nothing was queued, so the scoped run was ` +
|
|
13390
|
+
`re-issued exactly once — after a short pause to let pending graph edits land ` +
|
|
13391
|
+
`(#1050). The failure above is that SECOND dispatch; it was not retried again. ` +
|
|
13392
|
+
`Judge whether anything was queued from that message alone — the first dispatch ` +
|
|
13393
|
+
`definitely queued nothing. Racing AGAIN after the pause means the graph is still ` +
|
|
13394
|
+
`changing under the run: let the canvas settle, then re-run.)` +
|
|
13395
|
+
lateAckNote
|
|
13396
|
+
: `\n\n(Dispatch history: the first ${scopeRetryCount} graph_run dispatches were each ` +
|
|
13397
|
+
`refused by the panel's run-to-node graph-stamp race, and each CERTIFIED ` +
|
|
13398
|
+
`that nothing was queued. The scoped run was re-issued ${scopeRetryCount} ` +
|
|
13399
|
+
`times after short pauses to let pending graph edits land (#1050). The ` +
|
|
13400
|
+
`failure above is dispatch ${scopeRetryCount + 1}; it was not retried again. ` +
|
|
13401
|
+
`Judge whether anything was queued from that message alone — the first ` +
|
|
13402
|
+
`${scopeRetryCount} dispatches definitely queued nothing. Let the canvas ` +
|
|
13403
|
+
`settle, then re-run.)` +
|
|
13404
|
+
lateAckNote);
|
|
13338
13405
|
}
|
|
13339
13406
|
// Attribute this genuine queue to ourselves so a later panel_run in the
|
|
13340
13407
|
// same batch recognizes the in-flight job as our own and doesn't false-warn
|
|
@@ -13465,22 +13532,28 @@ export function buildPanelToolDefs() {
|
|
|
13465
13532
|
// clear_pending remedy that would wipe the user's whole batch (#559). A
|
|
13466
13533
|
// genuinely stalled render is caught by the turn-start stall notice, which
|
|
13467
13534
|
// does the staleness gating and keeps the interrupt guidance.
|
|
13468
|
-
// #772 — never report a dispatch history the caller did not see.
|
|
13469
|
-
//
|
|
13470
|
-
//
|
|
13535
|
+
// #772/#2120 — never report a dispatch history the caller did not see. Say
|
|
13536
|
+
// exactly which earlier dispatches were certified clean, so the agent does
|
|
13537
|
+
// not read a refusal (if it surfaces in a log) as a separate queued job.
|
|
13471
13538
|
//
|
|
13472
13539
|
// It says only what was OBSERVED (codex gate). An earlier draft said "exactly one
|
|
13473
13540
|
// render was queued" — but `batch_count` is "times to queue", so a successful
|
|
13474
13541
|
// dispatch can enqueue several, and the panel reports one prompt id, not a count.
|
|
13475
|
-
// The certified fact is about DISPATCHES, not renders: the
|
|
13476
|
-
// so everything in the queue came from the
|
|
13477
|
-
const retryNote =
|
|
13478
|
-
? "
|
|
13479
|
-
|
|
13480
|
-
"
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13542
|
+
// The certified fact is about DISPATCHES, not renders: the prior dispatches
|
|
13543
|
+
// queued nothing, so everything in the queue came from the final dispatch.
|
|
13544
|
+
const retryNote = scopeRetryCount === 0
|
|
13545
|
+
? ""
|
|
13546
|
+
: scopeRetryCount === 1
|
|
13547
|
+
? "\n\n[NOTE] The first dispatch of this scoped run was refused by the panel's run-to-node " +
|
|
13548
|
+
"graph-stamp race (the graph changed between dispatch and apply); the panel certified that " +
|
|
13549
|
+
"nothing was queued, so the scope was rebuilt and re-issued once, and THAT is the run above. " +
|
|
13550
|
+
"The first dispatch contributed NOTHING to the queue, so whatever this run queued was " +
|
|
13551
|
+
"queued once, not twice."
|
|
13552
|
+
: `\n\n[NOTE] The first ${scopeRetryCount} dispatches of this scoped run were refused by the ` +
|
|
13553
|
+
`panel's run-to-node graph-stamp race (the graph changed between dispatch and apply), ` +
|
|
13554
|
+
`and each was certified to have queued NOTHING. The scope was rebuilt and re-issued ` +
|
|
13555
|
+
`${scopeRetryCount} times, and THAT is the run above. Whatever this run queued came ` +
|
|
13556
|
+
`from the final dispatch; the earlier dispatches contributed NOTHING to the queue.`;
|
|
13484
13557
|
let warn = "";
|
|
13485
13558
|
if (pre.connected && pre.running) {
|
|
13486
13559
|
const pending = Math.max(0, pre.queueDepth - 1);
|
|
@@ -15561,6 +15634,10 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
15561
15634
|
return fail(surfaceErr +
|
|
15562
15635
|
" To restart the server without a panel confirmation card, use restart_comfyui.");
|
|
15563
15636
|
}
|
|
15637
|
+
// #2118: keep the proxy proof alive across the confirmation/preflight
|
|
15638
|
+
// awaits. The proxy is never treated as a ComfyUI process; it only lets
|
|
15639
|
+
// the tab-scoped Manager request be observed against the immutable backend.
|
|
15640
|
+
let proxyRestartTarget;
|
|
15564
15641
|
// #1819: resolve instance identity BEFORE the confirmation card. Asking the
|
|
15565
15642
|
// user to confirm a restart, then refusing because we cannot tell which
|
|
15566
15643
|
// ComfyUI this tab fronts, is two contradictory outcomes (timeout vs refuse)
|
|
@@ -15575,10 +15652,14 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
15575
15652
|
const identityBound = identityHealthBase != null &&
|
|
15576
15653
|
sameHttpBase(getComfyUIBaseUrl(), identityHealthBase);
|
|
15577
15654
|
if (!identityBound) {
|
|
15655
|
+
const candidate = await panelVerifiedProxyRestartTarget();
|
|
15656
|
+
if (proxyTargetBindsPanel(ctx, candidate)) {
|
|
15657
|
+
proxyRestartTarget = candidate;
|
|
15658
|
+
}
|
|
15578
15659
|
const tabStillHere = typeof ctx.bridge?.canReach === "function"
|
|
15579
15660
|
? ctx.bridge.canReach(ctx.tabId) === true
|
|
15580
15661
|
: true;
|
|
15581
|
-
if (tabStillHere) {
|
|
15662
|
+
if (tabStillHere && proxyRestartTarget == null) {
|
|
15582
15663
|
return restartRefusedPreservingBinding(ctx, unboundLocalRestartRefusalNote(ctx, identityHealthBase, identityBinding.blocker));
|
|
15583
15664
|
}
|
|
15584
15665
|
// Tab is gone: confirm will be unreachable and #1671 crash-recovery
|
|
@@ -16151,8 +16232,16 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
16151
16232
|
// "idle" and a restart aborts whatever is running.
|
|
16152
16233
|
const configuredRestartCommand = config.comfyuiRestartCommand;
|
|
16153
16234
|
if (configuredRestartCommand && !isRemoteMode() && !isCloudMode()) {
|
|
16154
|
-
|
|
16155
|
-
|
|
16235
|
+
if (proxyRestartTarget != null) {
|
|
16236
|
+
const refreshedProxy = await panelVerifiedProxyRestartTarget();
|
|
16237
|
+
proxyRestartTarget = proxyTargetBindsPanel(ctx, refreshedProxy)
|
|
16238
|
+
? refreshedProxy
|
|
16239
|
+
: undefined;
|
|
16240
|
+
}
|
|
16241
|
+
const commandHealthBase = proxyRestartTarget?.backendBase ?? captureRebootHealthBase(ctx);
|
|
16242
|
+
const commandBound = proxyRestartTarget != null ||
|
|
16243
|
+
(commandHealthBase != null && sameHttpBase(getComfyUIBaseUrl(), commandHealthBase));
|
|
16244
|
+
if (commandHealthBase != null && commandBound) {
|
|
16156
16245
|
if (force !== true) {
|
|
16157
16246
|
let busyCount = null;
|
|
16158
16247
|
try {
|
|
@@ -16191,7 +16280,13 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
16191
16280
|
// Re-heal and re-verify at the point of action, exactly as the
|
|
16192
16281
|
// dispatch path below does.
|
|
16193
16282
|
ctx.ensureReachable?.();
|
|
16194
|
-
|
|
16283
|
+
if (proxyRestartTarget != null) {
|
|
16284
|
+
const postProxy = await panelVerifiedProxyRestartTarget();
|
|
16285
|
+
proxyRestartTarget = proxyTargetBindsPanel(ctx, postProxy)
|
|
16286
|
+
? postProxy
|
|
16287
|
+
: undefined;
|
|
16288
|
+
}
|
|
16289
|
+
const postCheckHealthBase = proxyRestartTarget?.backendBase ?? captureRebootHealthBase(ctx);
|
|
16195
16290
|
if (postCheckHealthBase == null ||
|
|
16196
16291
|
!sameHttpBase(commandHealthBase, postCheckHealthBase)) {
|
|
16197
16292
|
return ok({
|
|
@@ -16271,10 +16366,20 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
16271
16366
|
// can be proven. They keep restart_comfyui, and the note says so. Weighed
|
|
16272
16367
|
// against #814 — where exactly such a user's server was stopped and never came
|
|
16273
16368
|
// back — declining is the recoverable side.
|
|
16369
|
+
// Re-resolve after the confirmation await. A proxy proof is an endpoint
|
|
16370
|
+
// identity claim, not a durable permission; a retarget or tab rebind makes
|
|
16371
|
+
// it unusable and returns to the existing refusal path.
|
|
16372
|
+
if (proxyRestartTarget != null) {
|
|
16373
|
+
const refreshedProxy = await panelVerifiedProxyRestartTarget();
|
|
16374
|
+
proxyRestartTarget = proxyTargetBindsPanel(ctx, refreshedProxy)
|
|
16375
|
+
? refreshedProxy
|
|
16376
|
+
: undefined;
|
|
16377
|
+
}
|
|
16274
16378
|
const preflightBinding = resolveRebootHealthBinding(ctx);
|
|
16275
|
-
const preflightHealthBase = preflightBinding.base;
|
|
16276
|
-
const preflightBound =
|
|
16277
|
-
|
|
16379
|
+
const preflightHealthBase = proxyRestartTarget?.backendBase ?? preflightBinding.base;
|
|
16380
|
+
const preflightBound = proxyRestartTarget != null ||
|
|
16381
|
+
(preflightHealthBase != null &&
|
|
16382
|
+
sameHttpBase(getComfyUIBaseUrl(), preflightHealthBase));
|
|
16278
16383
|
// #848: what the instance was OBSERVED running with, taken from the preflight
|
|
16279
16384
|
// that already had to resolve it. Nothing new is probed before the dispatch —
|
|
16280
16385
|
// the no-await invariant between the binding capture and the reboot stands.
|
|
@@ -16308,7 +16413,9 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
16308
16413
|
// EVERY retarget — any mutation, including a round trip back to the
|
|
16309
16414
|
// same base, is caught.
|
|
16310
16415
|
const preflightTargetGeneration = getComfyuiTargetGeneration();
|
|
16311
|
-
const preflight =
|
|
16416
|
+
const preflight = proxyRestartTarget
|
|
16417
|
+
? { ok: true, observedArgv: proxyRestartTarget.argv }
|
|
16418
|
+
: await (localRestartPreflightOverride ?? preflightLocalRestart)();
|
|
16312
16419
|
// #848: keep the observed launch arguments. Recorded here and spent ONLY on
|
|
16313
16420
|
// the success path far below, where the reboot has been proven to have
|
|
16314
16421
|
// cycled THIS instance — it never influences any decision above.
|
|
@@ -16327,11 +16434,19 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
16327
16434
|
// re-capture BEFORE trusting the result either way. The guarantee
|
|
16328
16435
|
// that must hold: a stop/reboot is only ever sent when the preflight
|
|
16329
16436
|
// validated THE instance the dispatch will cycle.
|
|
16437
|
+
if (proxyRestartTarget) {
|
|
16438
|
+
// The proxy branch performs no local stop/preflight. Its direct
|
|
16439
|
+
// backend proof is rechecked immediately before dispatch below.
|
|
16440
|
+
preflightArgvGeneration = proxyRestartTarget.generation;
|
|
16441
|
+
}
|
|
16330
16442
|
ctx.ensureReachable?.();
|
|
16331
16443
|
const postPreflightHealthBase = captureRebootHealthBase(ctx);
|
|
16332
|
-
const tabFrontsSameInstance =
|
|
16333
|
-
|
|
16334
|
-
|
|
16444
|
+
const tabFrontsSameInstance = proxyRestartTarget != null ||
|
|
16445
|
+
(postPreflightHealthBase != null &&
|
|
16446
|
+
sameHttpBase(preflightHealthBase, postPreflightHealthBase));
|
|
16447
|
+
const configStable = proxyRestartTarget != null
|
|
16448
|
+
? getComfyuiTargetGeneration() === proxyRestartTarget.generation
|
|
16449
|
+
: getComfyuiTargetGeneration() === preflightTargetGeneration;
|
|
16335
16450
|
if (!configStable && tabFrontsSameInstance) {
|
|
16336
16451
|
// r10: the target config moved MID-CHECK, so the preflight result
|
|
16337
16452
|
// — pass OR fail — cannot vouch for the tab-fronted instance (a
|
|
@@ -16409,7 +16524,17 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
16409
16524
|
// this SAME browser tab; a different tab can reuse the same saved-workflow
|
|
16410
16525
|
// routing id while the original is still reconnecting.
|
|
16411
16526
|
const preRestartPanelIdentity = ctx.panelConnectionIdentity?.();
|
|
16412
|
-
|
|
16527
|
+
if (proxyRestartTarget != null) {
|
|
16528
|
+
const dispatchProxy = await panelVerifiedProxyRestartTarget();
|
|
16529
|
+
proxyRestartTarget = proxyTargetBindsPanel(ctx, dispatchProxy)
|
|
16530
|
+
? dispatchProxy
|
|
16531
|
+
: undefined;
|
|
16532
|
+
if (proxyRestartTarget) {
|
|
16533
|
+
preflightArgv = [...proxyRestartTarget.argv];
|
|
16534
|
+
preflightArgvGeneration = proxyRestartTarget.generation;
|
|
16535
|
+
}
|
|
16536
|
+
}
|
|
16537
|
+
const healthBase = proxyRestartTarget?.backendBase ?? captureRebootHealthBase(ctx);
|
|
16413
16538
|
// THE BINDING RULE APPLIES AT THE DISPATCH POINT, NOT ONLY BEFORE THE AWAIT.
|
|
16414
16539
|
//
|
|
16415
16540
|
// The check above happens before the preflight; a tab or connection rebind
|
|
@@ -16422,7 +16547,8 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
16422
16547
|
//
|
|
16423
16548
|
// Same rule, same exclusions: only a LOCAL target we cannot tie to the
|
|
16424
16549
|
// instance this server accounts for is refused.
|
|
16425
|
-
const dispatchBound =
|
|
16550
|
+
const dispatchBound = proxyRestartTarget != null ||
|
|
16551
|
+
(healthBase != null && sameHttpBase(getComfyUIBaseUrl(), healthBase));
|
|
16426
16552
|
if (!dispatchBound && !isRemoteMode() && !isCloudMode()) {
|
|
16427
16553
|
return restartRefusedPreservingBinding(ctx, "Refusing to restart ComfyUI: the panel connection changed while the restart " +
|
|
16428
16554
|
"was being prepared, and I can no longer confirm which ComfyUI this tab " +
|
|
@@ -16614,6 +16740,7 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
16614
16740
|
// server replaced at the same URL between the reboot and here. That gap is real
|
|
16615
16741
|
// and tracked in #871; this gate narrows the window, it does not close it.
|
|
16616
16742
|
healthBase != null &&
|
|
16743
|
+
proxyRestartTarget == null &&
|
|
16617
16744
|
sameHttpBase(getComfyUIBaseUrl(), healthBase)) {
|
|
16618
16745
|
return runHeadlessManagedRestart({
|
|
16619
16746
|
healthBase,
|