comfyui-mcp 0.50.76 → 0.50.78
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.
- package/dist/orchestrator/panel-tools.js +187 -6
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/services/download-jobs.js +15 -1
- package/dist/services/download-jobs.js.map +1 -1
- package/dist/utils/optional-dep.js +37 -3
- package/dist/utils/optional-dep.js.map +1 -1
- package/package.json +1 -1
|
@@ -2367,6 +2367,118 @@ function activeMatchesOpenRefreshTarget(active, path) {
|
|
|
2367
2367
|
const targetIdentity = canonicalRequestedSavedIdentity(path);
|
|
2368
2368
|
return !!targetIdentity && canonicalSavedRecordIdentity(active) === targetIdentity;
|
|
2369
2369
|
}
|
|
2370
|
+
/**
|
|
2371
|
+
* Would this active record plausibly BE the requested workflow? Used only to
|
|
2372
|
+
* SUPPRESS a drift warning, never to prove sameness, so it is deliberately
|
|
2373
|
+
* generous: every normalization that could make two spellings the same file is
|
|
2374
|
+
* applied, and ties break toward silence. A miss here costs a warning we could
|
|
2375
|
+
* have given; a false positive here FAILS a healthy open, which is far worse.
|
|
2376
|
+
*
|
|
2377
|
+
* `activeMatchesTarget` alone is not enough — it is raw string equality, so
|
|
2378
|
+
* `./workflows/a.json`, `workflows\a.json` and `Workflows/A.json` all read as
|
|
2379
|
+
* different files from `workflows/a.json`. This file already warns against
|
|
2380
|
+
* exactly that conflation for the recovery path. Case folding is safe HERE
|
|
2381
|
+
* (and only here) because the generous direction is the silent one.
|
|
2382
|
+
*/
|
|
2383
|
+
function plausiblySameSavedWorkflow(active, path) {
|
|
2384
|
+
if (activeMatchesTarget(active, path))
|
|
2385
|
+
return true;
|
|
2386
|
+
if (!active || typeof active !== "object")
|
|
2387
|
+
return false;
|
|
2388
|
+
const want = canonicalSavedWorkflowPath(path);
|
|
2389
|
+
if (!want)
|
|
2390
|
+
return false;
|
|
2391
|
+
const fold = (s) => s.toLowerCase();
|
|
2392
|
+
const wantFolded = fold(want);
|
|
2393
|
+
const wantNoExt = stripJsonExt(wantFolded);
|
|
2394
|
+
const a = active;
|
|
2395
|
+
for (const raw of [a.path, a.filename, a.key]) {
|
|
2396
|
+
const got = canonicalSavedWorkflowPath(raw);
|
|
2397
|
+
if (!got)
|
|
2398
|
+
continue;
|
|
2399
|
+
const gotFolded = fold(got);
|
|
2400
|
+
if (gotFolded === wantFolded)
|
|
2401
|
+
return true;
|
|
2402
|
+
if (wantNoExt !== null && stripJsonExt(gotFolded) === wantNoExt)
|
|
2403
|
+
return true;
|
|
2404
|
+
}
|
|
2405
|
+
return false;
|
|
2406
|
+
}
|
|
2407
|
+
export function readOpenActiveAgainstTarget(active, path, activeConfirmed) {
|
|
2408
|
+
// #433/#3014 — `active_confirmed:false` is the panel telling us this value is
|
|
2409
|
+
// untrustworthy. It is already refused as adoption evidence; it cannot be
|
|
2410
|
+
// better evidence for failing an open than it is for stamping one.
|
|
2411
|
+
if (activeConfirmed === false)
|
|
2412
|
+
return "indeterminate";
|
|
2413
|
+
if (activeMatchesOpenRefreshTarget(active, path))
|
|
2414
|
+
return "same";
|
|
2415
|
+
if (!canonicalRequestedSavedIdentity(path))
|
|
2416
|
+
return "indeterminate"; // our own side is unreadable
|
|
2417
|
+
if (!active || typeof active !== "object")
|
|
2418
|
+
return "indeterminate";
|
|
2419
|
+
// The STRICT gate above failing is not enough to warn. It requires a canonical
|
|
2420
|
+
// saved identity on both sides, and it refuses a record whose routing_key is
|
|
2421
|
+
// absent, replayed, or malformed (#716 P1) — even when that record's PATH is the
|
|
2422
|
+
// very workflow we opened. Declining to adopt a uuid on that evidence is right;
|
|
2423
|
+
// telling the caller a different canvas is mounted would be a false alarm on the
|
|
2424
|
+
// same workflow.
|
|
2425
|
+
if (plausiblySameSavedWorkflow(active, path))
|
|
2426
|
+
return "indeterminate";
|
|
2427
|
+
const a = active;
|
|
2428
|
+
const identified = (typeof a.path === "string" && a.path !== "") ||
|
|
2429
|
+
(typeof a.filename === "string" && a.filename !== "") ||
|
|
2430
|
+
(typeof a.key === "string" && a.key !== "");
|
|
2431
|
+
return identified ? "different" : "indeterminate";
|
|
2432
|
+
}
|
|
2433
|
+
/**
|
|
2434
|
+
* #887 — observe what is active after an open, WITHOUT adopting anything.
|
|
2435
|
+
*
|
|
2436
|
+
* Split out of `refreshOpenWorkflowUuid` because the two questions have different
|
|
2437
|
+
* preconditions. Adoption is gated on the open reply corroborating the requested
|
|
2438
|
+
* identity — a reply that cannot prove which workflow it opened must never
|
|
2439
|
+
* authorize a fence refresh. Pure observation needs none of that: "what does the
|
|
2440
|
+
* panel say is active right now" is answerable regardless of what the open replied,
|
|
2441
|
+
* and on the path that matters most the open reply is an ERROR carrying no JSON at
|
|
2442
|
+
* all. Requiring corroboration there is what kept the reporter's own case
|
|
2443
|
+
* unexamined.
|
|
2444
|
+
*/
|
|
2445
|
+
async function observeActiveAfterOpen(ctx, requestedPath) {
|
|
2446
|
+
let list = null;
|
|
2447
|
+
try {
|
|
2448
|
+
const res = await ctx.call({ cmd: "workflow_list" }, 6000);
|
|
2449
|
+
if (!res?.isError)
|
|
2450
|
+
list = parseToolResultJson(res);
|
|
2451
|
+
}
|
|
2452
|
+
catch {
|
|
2453
|
+
return null; // could not ask — nothing was observed, so nothing is claimed
|
|
2454
|
+
}
|
|
2455
|
+
if (!list)
|
|
2456
|
+
return null;
|
|
2457
|
+
if (readOpenActiveAgainstTarget(list.active, requestedPath, list.active_confirmed) !== "different") {
|
|
2458
|
+
return null;
|
|
2459
|
+
}
|
|
2460
|
+
return { drifted: true, activeLabel: describeActiveRecord(list.active) };
|
|
2461
|
+
}
|
|
2462
|
+
/**
|
|
2463
|
+
* Human-readable name for whatever the panel says is active, for the #887 warning.
|
|
2464
|
+
*
|
|
2465
|
+
* PATH FIRST, deliberately. The case this guard exists for is a same-basename
|
|
2466
|
+
* workflow in another directory, and naming it by its bare `filename` produces a
|
|
2467
|
+
* sentence that refutes itself — "workflows/a/foo.json was opened, but foo.json is
|
|
2468
|
+
* active now" reads as the same file. The directory IS the distinguishing
|
|
2469
|
+
* information. `title` precedes the bare filename for the unsaved case, where the
|
|
2470
|
+
* panel puts the human label there and leaves path/filename null.
|
|
2471
|
+
*/
|
|
2472
|
+
export function describeActiveRecord(active) {
|
|
2473
|
+
if (!active || typeof active !== "object")
|
|
2474
|
+
return "another workflow";
|
|
2475
|
+
const a = active;
|
|
2476
|
+
for (const v of [a.path, a.title, a.filename, a.key]) {
|
|
2477
|
+
if (typeof v === "string" && v !== "")
|
|
2478
|
+
return v;
|
|
2479
|
+
}
|
|
2480
|
+
return "another workflow";
|
|
2481
|
+
}
|
|
2370
2482
|
/** Normalizes only syntax the panel's saved-path/routing identity normalizes. */
|
|
2371
2483
|
function canonicalSavedWorkflowPath(value) {
|
|
2372
2484
|
if (typeof value !== "string" || !value)
|
|
@@ -3076,7 +3188,7 @@ async function refreshOpenWorkflowUuid(ctx, requestedPath, openResult) {
|
|
|
3076
3188
|
if (requestedUnsaved && requestedUnsaved === parsedOpen?.routing_key) {
|
|
3077
3189
|
refreshWorkflowUuid(ctx, parsedOpen);
|
|
3078
3190
|
}
|
|
3079
|
-
return;
|
|
3191
|
+
return null;
|
|
3080
3192
|
}
|
|
3081
3193
|
// THREE outcomes for this corroborating read, not two — and conflating the last
|
|
3082
3194
|
// two is #1071 (also #932/#1043).
|
|
@@ -3111,10 +3223,23 @@ async function refreshOpenWorkflowUuid(ctx, requestedPath, openResult) {
|
|
|
3111
3223
|
// value, which is fresher than the reply — or it names a DIFFERENT active
|
|
3112
3224
|
// workflow, in which case another tab won the slot and adopting our target's
|
|
3113
3225
|
// uuid would fence this session to a canvas that is not mounted. Adopt nothing.
|
|
3114
|
-
|
|
3115
|
-
|
|
3226
|
+
//
|
|
3227
|
+
// #887 — declining to adopt was right and was never enough. This is the ONLY
|
|
3228
|
+
// genuinely later observation in the whole open path: it sits behind a real
|
|
3229
|
+
// `await ctx.call(...)` round trip to the panel, so unlike anything inside the
|
|
3230
|
+
// panel's own synchronous post-load window it can actually see the active
|
|
3231
|
+
// pointer having settled somewhere else. It saw the contradiction, kept the
|
|
3232
|
+
// session safe, and told the CALLER nothing — the tool result still reported a
|
|
3233
|
+
// plain success naming the path the agent asked for. The agent then had every
|
|
3234
|
+
// reason to Save-As onto what it believed was that canvas.
|
|
3235
|
+
const reading = readOpenActiveAgainstTarget(list.active, requestedPath, list.active_confirmed);
|
|
3236
|
+
if (reading === "different") {
|
|
3237
|
+
return { drifted: true, activeLabel: describeActiveRecord(list.active) };
|
|
3238
|
+
}
|
|
3239
|
+
if (reading === "indeterminate")
|
|
3240
|
+
return null;
|
|
3116
3241
|
refreshWorkflowUuid(ctx, list.active) || refreshWorkflowUuid(ctx, parsedOpen);
|
|
3117
|
-
return;
|
|
3242
|
+
return null;
|
|
3118
3243
|
}
|
|
3119
3244
|
// COULD NOT ASK — the wedged case. Fall back to the reply's proven uuid rather
|
|
3120
3245
|
// than leaving the session fenced to a dead instance forever. This is strictly
|
|
@@ -3123,7 +3248,12 @@ async function refreshOpenWorkflowUuid(ctx, requestedPath, openResult) {
|
|
|
3123
3248
|
// actually mounted, whereas doing nothing here guarantees every subsequent
|
|
3124
3249
|
// command is refused. A reply that carries no uuid (the panel could not prove
|
|
3125
3250
|
// it) still refreshes nothing, so fail-closed is preserved.
|
|
3251
|
+
//
|
|
3252
|
+
// #887 — and NO drift notice from here, deliberately. This branch is reached
|
|
3253
|
+
// precisely because the corroborating read could not be made, so nothing was
|
|
3254
|
+
// observed about what is active. Warning here would be inventing the finding.
|
|
3126
3255
|
refreshWorkflowUuid(ctx, parsedOpen);
|
|
3256
|
+
return null;
|
|
3127
3257
|
}
|
|
3128
3258
|
/** Exact resolved-path check for an open receipt. A filename/basename is not a
|
|
3129
3259
|
* workflow identity: `other/foo.json` must never confirm `wanted/foo.json`. */
|
|
@@ -3250,8 +3380,59 @@ async function openWorkflowWithVerify(path, ctx) {
|
|
|
3250
3380
|
// #716 — re-read the active record after this exact successful open before
|
|
3251
3381
|
// refreshing the next command's stamp. This prevents a late reply from an
|
|
3252
3382
|
// earlier open from overwriting the fence after another tab became active.
|
|
3253
|
-
if (
|
|
3254
|
-
|
|
3383
|
+
if (res.isError) {
|
|
3384
|
+
// #887 — THE REPORTER'S OWN PATH. The panel throws for every open verdict
|
|
3385
|
+
// short of PROVEN, so the case in the ticket ("Tool errors saying the
|
|
3386
|
+
// requested workflow is active") arrives here, not below. Gating the
|
|
3387
|
+
// corroborating read on success left exactly the reported scenario
|
|
3388
|
+
// unexamined: the panel's message asserts the target IS active, and nothing
|
|
3389
|
+
// in this process ever checked whether it still was.
|
|
3390
|
+
//
|
|
3391
|
+
// OBSERVE ONLY — never adopt. The open did not prove itself, and the panel
|
|
3392
|
+
// deliberately withholds `workflow_uuid` in that case to keep the fence
|
|
3393
|
+
// fail-closed. Reading what is active cannot change that; it only adds a
|
|
3394
|
+
// fact to a message that is already an error.
|
|
3395
|
+
//
|
|
3396
|
+
// AND ONLY FOR THE CLASS WHERE THE LOAD ACTUALLY RAN. A genuine acked
|
|
3397
|
+
// failure — a missing file, a real executor error — means nothing was
|
|
3398
|
+
// opened, so another workflow being active is the expected state, not
|
|
3399
|
+
// drift; warning there would be noise, and this repo already pins that a
|
|
3400
|
+
// genuine error must not trigger a workflow_list round trip at all. Every
|
|
3401
|
+
// rebind-unproven message the panel emits opens with `workflow_open RAN`
|
|
3402
|
+
// (its own contract wording for "the load executed, the proof did not"),
|
|
3403
|
+
// which is exactly the class that can assert a false active workflow.
|
|
3404
|
+
if (!/workflow_open RAN/i.test(toolResultText(res)))
|
|
3405
|
+
return res;
|
|
3406
|
+
const drift = await observeActiveAfterOpen(ctx, path);
|
|
3407
|
+
if (!drift)
|
|
3408
|
+
return res;
|
|
3409
|
+
return fail(`${toolResultText(res)}\n\nAND — checked after that failure — ${drift.activeLabel} is the ` +
|
|
3410
|
+
`ACTIVE workflow now, not ${path}. Whatever that message says about ${path} being active, ` +
|
|
3411
|
+
`it is not: the canvas you would read or write is ${drift.activeLabel}. Do NOT save, ` +
|
|
3412
|
+
`save-as, or edit expecting ${path}. Call panel_list_workflows to see the current state.`);
|
|
3413
|
+
}
|
|
3414
|
+
{
|
|
3415
|
+
const drift = await refreshOpenWorkflowUuid(ctx, path, res);
|
|
3416
|
+
// #887 — the read above is the ONLY observation in this whole path taken
|
|
3417
|
+
// after a real round trip, so it is the only thing that can catch the active
|
|
3418
|
+
// pointer having settled elsewhere. It already declined to adopt the uuid;
|
|
3419
|
+
// reporting a plain success alongside that silence is what let an agent
|
|
3420
|
+
// Save-As onto the wrong canvas.
|
|
3421
|
+
//
|
|
3422
|
+
// This FAILS the open rather than annotating it. The caller asked for a
|
|
3423
|
+
// workflow to be made active and it is not active — a success naming the
|
|
3424
|
+
// requested path is false on the one point the caller acts on, and the next
|
|
3425
|
+
// act is typically a write. The session's fence is untouched (nothing was
|
|
3426
|
+
// adopted), so the tab that IS active keeps its own protection.
|
|
3427
|
+
if (drift) {
|
|
3428
|
+
return fail(`workflow_open: ${path} was opened, but ${drift.activeLabel} is the ACTIVE workflow now — ` +
|
|
3429
|
+
`the panel confirmed this on a re-read after the open completed. This session's ` +
|
|
3430
|
+
`workflow identity was NOT re-pointed at ${path}, so the canvas you would read or ` +
|
|
3431
|
+
`write is ${drift.activeLabel}, not ${path}. Do NOT save, save-as, or edit expecting ` +
|
|
3432
|
+
`${path}. Call panel_list_workflows to see the current state, then re-open ${path} ` +
|
|
3433
|
+
`if you still need it — another tab became active during or after the open.`);
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3255
3436
|
return res;
|
|
3256
3437
|
}
|
|
3257
3438
|
if (!dispatchedRid) {
|