comfyui-mcp 0.50.60 → 0.50.62
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.
|
@@ -1313,6 +1313,95 @@ function withTruncationHints(res, rules) {
|
|
|
1313
1313
|
* the bound this tool advertises did not apply. The flag is stripped again before the
|
|
1314
1314
|
* result is returned — it exists only to drive the rider.
|
|
1315
1315
|
*/
|
|
1316
|
+
/**
|
|
1317
|
+
* A 0-node outline is a CLAIM, and one made mid-restore is not established (#1184).
|
|
1318
|
+
*
|
|
1319
|
+
* Right after a ComfyUI restart and a tab switch, graph_outline returned
|
|
1320
|
+
* `node_count: 0` for a tab holding a 7-node starter graph — the frontend was
|
|
1321
|
+
* still restoring, and the canvas transiently had nothing on it. The agent
|
|
1322
|
+
* trusted the empty read, reported the canvas empty, and BUILT A NEW 9-NODE
|
|
1323
|
+
* PIPELINE alongside the invisible one. A later panel_query_graph showed 16
|
|
1324
|
+
* nodes: ids 1–7 had been there the whole time, which the frontend knew, because
|
|
1325
|
+
* the new adds started at id 8.
|
|
1326
|
+
*
|
|
1327
|
+
* That is the worst shape of this defect class, because the action taken on the
|
|
1328
|
+
* false read DESTROYS WORK: a duplicate pipeline cannot be un-built, and the user
|
|
1329
|
+
* is left with two overlapping graphs and no way to tell which nodes are theirs.
|
|
1330
|
+
*
|
|
1331
|
+
* It is also unusually cheap to get right. Unlike most "could not determine"
|
|
1332
|
+
* cases, an empty outline is trivially RE-VERIFIABLE: read it again, and the
|
|
1333
|
+
* restore has either finished or it has not.
|
|
1334
|
+
*
|
|
1335
|
+
* So an empty first read is re-read once after a short settle:
|
|
1336
|
+
* - second read non-empty → the first was a race; report the real graph.
|
|
1337
|
+
* - second read still empty → "empty" is now OBSERVED TWICE, not assumed, and
|
|
1338
|
+
* is reported plainly with no hedge (a blank canvas is a normal state and
|
|
1339
|
+
* must not be narrated as suspicious).
|
|
1340
|
+
*
|
|
1341
|
+
* The whole cost lands on the genuinely-empty case — one extra cheap read — which
|
|
1342
|
+
* is the right place for it: a blank canvas is common but harmless to re-check,
|
|
1343
|
+
* while a false empty is rare and expensive.
|
|
1344
|
+
*
|
|
1345
|
+
* Never throws: a failed re-read leaves the original reply exactly as it was.
|
|
1346
|
+
*/
|
|
1347
|
+
async function confirmEmptyOutline(ctx, res, reread) {
|
|
1348
|
+
try {
|
|
1349
|
+
if (res.isError)
|
|
1350
|
+
return res;
|
|
1351
|
+
const first = parseToolResultJson(res);
|
|
1352
|
+
if (!first || first.node_count !== 0)
|
|
1353
|
+
return res;
|
|
1354
|
+
// Which workflow this empty answer is ABOUT (codex review). The re-read is a
|
|
1355
|
+
// second round trip, and the user can switch tabs during it — so a non-empty
|
|
1356
|
+
// second outline might describe a DIFFERENT workflow entirely. Substituting
|
|
1357
|
+
// it would report workflow B's graph as though it confirmed a read of A,
|
|
1358
|
+
// which is a worse failure than the empty read this exists to fix.
|
|
1359
|
+
const identityBefore = currentWorkflowFence(ctx);
|
|
1360
|
+
// A BOUNDED POLL, not a single retry (codex review). A restore after a
|
|
1361
|
+
// ComfyUI restart has no guaranteed duration, and one 400ms attempt is an
|
|
1362
|
+
// arbitrary cliff: if the restore is still going at that instant, the fix
|
|
1363
|
+
// silently fails to cover the very report it is for. Poll briefly instead,
|
|
1364
|
+
// stopping the moment nodes appear.
|
|
1365
|
+
for (const waitMs of EMPTY_OUTLINE_RECHECK_STEPS_MS) {
|
|
1366
|
+
await sleep(waitMs);
|
|
1367
|
+
const second = await reread();
|
|
1368
|
+
// Redundant with the parsed-null guard below TODAY — an error result carries
|
|
1369
|
+
// no parseable JSON — and kept anyway: it states the intent directly, and
|
|
1370
|
+
// would still hold if an error result ever carried a structured body.
|
|
1371
|
+
if (second.isError)
|
|
1372
|
+
continue;
|
|
1373
|
+
const parsed = parseToolResultJson(second);
|
|
1374
|
+
if (!parsed || parsed.node_count === 0)
|
|
1375
|
+
continue;
|
|
1376
|
+
// Nodes appeared — but only adopt them if the canvas is still the SAME
|
|
1377
|
+
// workflow. A changed identity means the second read answers a different
|
|
1378
|
+
// question, so the original (honest, empty) answer stands.
|
|
1379
|
+
const identityAfter = currentWorkflowFence(ctx);
|
|
1380
|
+
if (identityBefore.known &&
|
|
1381
|
+
identityAfter.known &&
|
|
1382
|
+
identityBefore.uuid !== identityAfter.uuid) {
|
|
1383
|
+
return res;
|
|
1384
|
+
}
|
|
1385
|
+
return second;
|
|
1386
|
+
}
|
|
1387
|
+
return res;
|
|
1388
|
+
}
|
|
1389
|
+
catch {
|
|
1390
|
+
return res;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* The back-off schedule for re-reading an empty outline (#1184).
|
|
1395
|
+
*
|
|
1396
|
+
* A bounded poll rather than one attempt: a restore after a ComfyUI restart has
|
|
1397
|
+
* no guaranteed duration, so a single fixed wait is an arbitrary cliff that would
|
|
1398
|
+
* silently miss a slower restore (codex review). Stops the moment nodes appear.
|
|
1399
|
+
*
|
|
1400
|
+
* Bounded deliberately at ~1.2s total. A genuinely blank canvas is a COMMON
|
|
1401
|
+
* state and pays this whole cost, so it has to stay small enough not to be felt;
|
|
1402
|
+
* a longer poll would buy rarer restores at the expense of every empty read.
|
|
1403
|
+
*/
|
|
1404
|
+
const EMPTY_OUTLINE_RECHECK_STEPS_MS = [250, 400, 550];
|
|
1316
1405
|
function markBudgetIgnored(res, requested) {
|
|
1317
1406
|
if (typeof requested !== "number")
|
|
1318
1407
|
return res;
|
|
@@ -5647,7 +5736,9 @@ export function buildPanelToolDefs() {
|
|
|
5647
5736
|
}, async (args, ctx) => withTruncationHints(
|
|
5648
5737
|
// The synthetic `__budget_ignored` flag below is derived from the reply, not
|
|
5649
5738
|
// sent by the panel: a build that supports the budget echoes `max_chars` back.
|
|
5650
|
-
markBudgetIgnored(
|
|
5739
|
+
markBudgetIgnored(
|
|
5740
|
+
// #1184 — an empty outline is re-verified before it is believed.
|
|
5741
|
+
await confirmEmptyOutline(ctx, await ctx.call({ cmd: "graph_outline", max_chars: args.max_chars }), () => ctx.call({ cmd: "graph_outline", max_chars: args.max_chars })), args.max_chars), [
|
|
5651
5742
|
{
|
|
5652
5743
|
// #809 (codex gate): a panel older than this budget IGNORES `max_chars` and
|
|
5653
5744
|
// returns the full outline, so the bound this tool advertises silently did
|