comfyui-mcp 0.50.63 → 0.50.65
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 +180 -5
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/services/self-update.js +66 -14
- package/dist/services/self-update.js.map +1 -1
- package/dist/services/workflow-deps.js +66 -2
- package/dist/services/workflow-deps.js.map +1 -1
- package/dist/services/workflow-url.js +9 -2
- package/dist/services/workflow-url.js.map +1 -1
- package/dist/tools/skills-access.js +10 -0
- package/dist/tools/skills-access.js.map +1 -1
- package/package.json +1 -1
|
@@ -985,6 +985,36 @@ async function probeDeclineRecovery(base, windowMs, intervalMs, probeTimeoutMs,
|
|
|
985
985
|
* Origin carries NO path, a boot target mounted under a basePath fails path-aware
|
|
986
986
|
* identity and is (soundly) fail-closed to dispatched-unconfirmed.
|
|
987
987
|
*/
|
|
988
|
+
/**
|
|
989
|
+
* #851 — what to tell the caller about `restart_comfyui` when the panel's confirmation
|
|
990
|
+
* card timed out.
|
|
991
|
+
*
|
|
992
|
+
* The old text recommended it unconditionally. It targets COMFYUI_URL, not the ComfyUI
|
|
993
|
+
* the panel runs inside, so a reporter who followed that advice aimed a restart at a
|
|
994
|
+
* different server than the one they had been working on — and got "No ComfyUI process
|
|
995
|
+
* found on port 8188" while the panel was working fine on the live canvas.
|
|
996
|
+
*
|
|
997
|
+
* `panelBase` comes from captureRebootHealthBase(), which is a PROOF rather than a
|
|
998
|
+
* comparison: null in remote/cloud mode, and whenever the tab's origin cannot be shown
|
|
999
|
+
* to front the local boot instance. "Unproven" is deliberately NOT folded into
|
|
1000
|
+
* "different" — that would fire the warning on ordinary remote installs where the two
|
|
1001
|
+
* ARE the same server, trading a wrong recommendation for a wrong alarm.
|
|
1002
|
+
*
|
|
1003
|
+
* Pure, so the three-way decision is testable without a live panel.
|
|
1004
|
+
*/
|
|
1005
|
+
export function restartTimeoutFallbackAdvice({ headlessBase, panelBase, }) {
|
|
1006
|
+
if (panelBase != null && sameHttpBase(headlessBase, panelBase)) {
|
|
1007
|
+
return "or use restart_comfyui to restart the server directly without a panel card.";
|
|
1008
|
+
}
|
|
1009
|
+
if (panelBase != null) {
|
|
1010
|
+
return (`Do NOT reach for restart_comfyui here without checking: it targets ${headlessBase}, ` +
|
|
1011
|
+
`while this panel is running inside ${panelBase}. Restarting the first would hit a ` +
|
|
1012
|
+
"different server than the one you have been working on — and may find nothing there at all.");
|
|
1013
|
+
}
|
|
1014
|
+
return (`or use restart_comfyui, which restarts ${headlessBase} directly without a panel ` +
|
|
1015
|
+
"card — check that this is the same ComfyUI you have been working on, since I " +
|
|
1016
|
+
"could not confirm which one this panel is running inside.");
|
|
1017
|
+
}
|
|
988
1018
|
function captureRebootHealthBase(ctx) {
|
|
989
1019
|
if (isCloudMode() || isRemoteMode())
|
|
990
1020
|
return null;
|
|
@@ -1402,6 +1432,102 @@ async function confirmEmptyOutline(ctx, res, reread) {
|
|
|
1402
1432
|
* a longer poll would buy rarer restores at the expense of every empty read.
|
|
1403
1433
|
*/
|
|
1404
1434
|
const EMPTY_OUTLINE_RECHECK_STEPS_MS = [250, 400, 550];
|
|
1435
|
+
/**
|
|
1436
|
+
* Enforce `max_chars` when the PANEL could not (#1203).
|
|
1437
|
+
*
|
|
1438
|
+
* A panel older than the budget protocol ignores `max_chars` and returns the
|
|
1439
|
+
* whole outline. The orchestrator already noticed — that is what
|
|
1440
|
+
* `markBudgetIgnored` is for — and then forwarded the unbounded reply anyway,
|
|
1441
|
+
* with a note explaining that the bound the caller had explicitly set did not
|
|
1442
|
+
* apply. A 137-node outline arrived that way, which is the exact context flood
|
|
1443
|
+
* `max_chars` exists to prevent, and the disclosure came AFTER the cost was
|
|
1444
|
+
* already paid. A warning is not a bound.
|
|
1445
|
+
*
|
|
1446
|
+
* WHY REFUSE RATHER THAN TRUNCATE. This tool's contract is that coverage is
|
|
1447
|
+
* never traded away: over budget the outline sheds RESOLUTION — widget values,
|
|
1448
|
+
* then titles, then a per-group summary — so whatever comes back still describes
|
|
1449
|
+
* the whole graph, and if even the floor will not fit it returns NO outline
|
|
1450
|
+
* rather than a partial one that reads as complete. Cutting the rendered text at
|
|
1451
|
+
* a character count would break exactly that guarantee: the result would look
|
|
1452
|
+
* like a finished outline that simply ends, and a caller acting on it would
|
|
1453
|
+
* believe the graph stops where the string does — the #1184 failure again, with
|
|
1454
|
+
* the truncation invisible instead of announced.
|
|
1455
|
+
*
|
|
1456
|
+
* Shedding resolution is the panel's job and needs the graph, not its rendering.
|
|
1457
|
+
* The orchestrator has only the rendered text, so the honest move is the one the
|
|
1458
|
+
* panel itself makes at its floor: return no outline, and say precisely what
|
|
1459
|
+
* happened, how big the outline actually was, and which of the three levers
|
|
1460
|
+
* (raise the bound, update the panel, scope the read) will work.
|
|
1461
|
+
*
|
|
1462
|
+
* The counts and `viewing` survive — they are the part of the reply that is
|
|
1463
|
+
* small, true, and independent of the budget.
|
|
1464
|
+
*/
|
|
1465
|
+
function enforceOutlineBudget(res, requested) {
|
|
1466
|
+
if (typeof requested !== "number")
|
|
1467
|
+
return res;
|
|
1468
|
+
const payload = parseToolResultJson(res);
|
|
1469
|
+
if (!payload)
|
|
1470
|
+
return res;
|
|
1471
|
+
// A panel that supports the budget echoes it back and has already applied its
|
|
1472
|
+
// own ladder. Nothing to enforce, and second-guessing it would be wrong.
|
|
1473
|
+
if (typeof payload.max_chars === "number")
|
|
1474
|
+
return res;
|
|
1475
|
+
const outline = payload.outline;
|
|
1476
|
+
if (typeof outline !== "string")
|
|
1477
|
+
return res;
|
|
1478
|
+
// Within the bound already. The panel ignored the parameter, but the reply
|
|
1479
|
+
// honours it, and refusing a reply that FITS would be a bound in name only.
|
|
1480
|
+
if (outline.length <= requested)
|
|
1481
|
+
return res;
|
|
1482
|
+
const idx = res.content.findIndex((c) => c.type === "text");
|
|
1483
|
+
if (idx < 0)
|
|
1484
|
+
return res;
|
|
1485
|
+
const nodes = typeof payload.node_count === "number" ? payload.node_count : null;
|
|
1486
|
+
const groups = typeof payload.group_count === "number" ? payload.group_count : null;
|
|
1487
|
+
const shape = nodes != null
|
|
1488
|
+
? `The graph has ${nodes} node(s)${groups != null ? ` and ${groups} group(s)` : ""}.`
|
|
1489
|
+
: "";
|
|
1490
|
+
// The refusal goes IN the outline field, not an empty string (codex review).
|
|
1491
|
+
// Two reasons. It is what the panel itself does at its own floor, so a caller
|
|
1492
|
+
// that handles one refusal handles both. And an empty outline next to
|
|
1493
|
+
// `node_count: 137` is exactly the contradiction #1184 was about — a consumer
|
|
1494
|
+
// that reads `outline` and finds nothing has been told the canvas is empty by
|
|
1495
|
+
// a reply whose own counts say otherwise. Bounded and fixed-length, so it
|
|
1496
|
+
// cannot itself overrun a budget whose floor is OUTLINE_MAX_CHARS_FLOOR.
|
|
1497
|
+
payload.outline =
|
|
1498
|
+
`NO OUTLINE — this panel build ignores \`max_chars\`, and its full reply (${outline.length} chars) exceeds the bound of ${requested} you set. ` +
|
|
1499
|
+
`It is withheld rather than cut, because a truncated outline would read as a complete one. ` +
|
|
1500
|
+
`${shape} See \`max_chars_hint\` for what to do.`.trim();
|
|
1501
|
+
payload.detail_level = "refused";
|
|
1502
|
+
payload.degraded = true;
|
|
1503
|
+
payload.budget_applied_by = "orchestrator";
|
|
1504
|
+
payload.unbounded_chars = outline.length;
|
|
1505
|
+
payload.degraded_reason =
|
|
1506
|
+
`This panel build ignores \`max_chars\`, so it returned the FULL outline (${outline.length} chars) against a bound of ${requested}. ` +
|
|
1507
|
+
`It is withheld rather than cut: a truncated outline would read as a complete one. ${shape}`.trim();
|
|
1508
|
+
return {
|
|
1509
|
+
...res,
|
|
1510
|
+
content: res.content.map((c, i) => i === idx && c.type === "text" ? { ...c, text: JSON.stringify(payload, null, 2) } : c),
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
/** The three levers when the orchestrator had to withhold an outline (#1203),
|
|
1514
|
+
* in the order worth trying. Shared so the two riders cannot drift. */
|
|
1515
|
+
function outlineBudgetRemedies(unbounded) {
|
|
1516
|
+
// Every "raise it" must name the ceiling, or a caller already at the maximum
|
|
1517
|
+
// is sent on a retry that cannot change anything (the repo-wide remedy check,
|
|
1518
|
+
// which caught this text before it shipped).
|
|
1519
|
+
// The ceiling has to sit next to the parameter name, not at the end of the
|
|
1520
|
+
// sentence: the remedy check reads "raise `X` … up to N" as one phrase, and it
|
|
1521
|
+
// is right to — a caller skimming for whether the retry is possible should not
|
|
1522
|
+
// have to finish the clause.
|
|
1523
|
+
const raise = unbounded == null
|
|
1524
|
+
? `Raise \`max_chars\` up to ${OUTLINE_MAX_CHARS_CEILING}`
|
|
1525
|
+
: unbounded <= OUTLINE_MAX_CHARS_CEILING
|
|
1526
|
+
? `Raise \`max_chars\` up to ${OUTLINE_MAX_CHARS_CEILING} — this outline needs at least ${unbounded} to be accepted whole`
|
|
1527
|
+
: `Raising \`max_chars\` will NOT help — the full outline is past its ceiling of ${OUTLINE_MAX_CHARS_CEILING}`;
|
|
1528
|
+
return (`${raise}; update the ComfyUI Agent Panel, which can shed per-node detail and still cover every node inside a smaller bound; ` +
|
|
1529
|
+
`or scope the read with panel_query_graph {ids:[…], fields:'detail'}.`);
|
|
1530
|
+
}
|
|
1405
1531
|
function markBudgetIgnored(res, requested) {
|
|
1406
1532
|
if (typeof requested !== "number")
|
|
1407
1533
|
return res;
|
|
@@ -5737,8 +5863,11 @@ export function buildPanelToolDefs() {
|
|
|
5737
5863
|
// The synthetic `__budget_ignored` flag below is derived from the reply, not
|
|
5738
5864
|
// sent by the panel: a build that supports the budget echoes `max_chars` back.
|
|
5739
5865
|
markBudgetIgnored(
|
|
5866
|
+
// #1203 — and ENFORCE it here when the panel could not, before the
|
|
5867
|
+
// unbounded outline reaches the caller who asked for a bound.
|
|
5868
|
+
enforceOutlineBudget(
|
|
5740
5869
|
// #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), [
|
|
5870
|
+
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), args.max_chars), [
|
|
5742
5871
|
{
|
|
5743
5872
|
// #809 (codex gate): a panel older than this budget IGNORES `max_chars` and
|
|
5744
5873
|
// returns the full outline, so the bound this tool advertises silently did
|
|
@@ -5747,8 +5876,24 @@ export function buildPanelToolDefs() {
|
|
|
5747
5876
|
// unbounded reply as "this fitted".
|
|
5748
5877
|
flag: "__budget_ignored",
|
|
5749
5878
|
key: "max_chars_hint",
|
|
5750
|
-
text: (p) =>
|
|
5751
|
-
|
|
5879
|
+
text: (p) => {
|
|
5880
|
+
const asked = typeof args.max_chars === "number" ? args.max_chars : OUTLINE_MAX_CHARS_DEFAULT;
|
|
5881
|
+
const nodes = typeof p.node_count === "number" ? p.node_count : "all";
|
|
5882
|
+
// #1203 — three different situations used to share one sentence
|
|
5883
|
+
// that described only the worst of them. Saying "the outline
|
|
5884
|
+
// below is the full, unbounded one" when it was withheld, or
|
|
5885
|
+
// when it fitted anyway, is its own false report.
|
|
5886
|
+
if (p.budget_applied_by === "orchestrator") {
|
|
5887
|
+
const unbounded = typeof p.unbounded_chars === "number" ? p.unbounded_chars : null;
|
|
5888
|
+
return (`This panel build does not support \`max_chars\`, so it returned the FULL outline for all ${nodes} node(s)` +
|
|
5889
|
+
`${unbounded != null ? ` (${unbounded} chars)` : ""} against your bound of ${asked}. ` +
|
|
5890
|
+
`NO outline is included: it is withheld rather than cut, because a truncated outline would read as a complete one. ` +
|
|
5891
|
+
outlineBudgetRemedies(unbounded));
|
|
5892
|
+
}
|
|
5893
|
+
return (`This panel build does not support \`max_chars\`, so the budget you set (${asked}) was not applied by the panel — ` +
|
|
5894
|
+
`the outline below is its full, unbounded reply for all ${nodes} node(s), which happens to fit within your bound. ` +
|
|
5895
|
+
`Update the ComfyUI Agent Panel to have the budget applied properly; on a larger graph an unbounded reply will not fit.`);
|
|
5896
|
+
},
|
|
5752
5897
|
},
|
|
5753
5898
|
{
|
|
5754
5899
|
// On an older panel this is never set either, so the rider is inert there.
|
|
@@ -5767,6 +5912,21 @@ export function buildPanelToolDefs() {
|
|
|
5767
5912
|
// it "still covers ALL nodes" would describe content the reader cannot
|
|
5768
5913
|
// see, which is the same lie as a silent cut.
|
|
5769
5914
|
if (p.detail_level === "refused") {
|
|
5915
|
+
// #1203 — an ORCHESTRATOR refusal is a different situation and
|
|
5916
|
+
// needs different advice. The panel refuses because even its
|
|
5917
|
+
// smallest whole-graph form did not fit, so the question is
|
|
5918
|
+
// whether raising the bound can ever help. Here the panel never
|
|
5919
|
+
// applied the bound at all: the full outline's size is known
|
|
5920
|
+
// exactly, updating the panel unlocks the shed ladder, and the
|
|
5921
|
+
// generic text's "this build does not report how large the
|
|
5922
|
+
// smallest form would be" would send the reader chasing a
|
|
5923
|
+
// capability the reply is already explaining is absent.
|
|
5924
|
+
if (p.budget_applied_by === "orchestrator") {
|
|
5925
|
+
const unbounded = typeof p.unbounded_chars === "number" ? p.unbounded_chars : null;
|
|
5926
|
+
return (`NO outline was returned: this panel build ignores \`max_chars\`, so its reply${unbounded != null ? ` (${unbounded} chars)` : ""} exceeded \`max_chars\`=${inForce} and a PARTIAL outline is deliberately withheld because it would read as complete. ` +
|
|
5927
|
+
`The graph has ${nodes} node(s) and ${groups} group(s). ` +
|
|
5928
|
+
outlineBudgetRemedies(unbounded));
|
|
5929
|
+
}
|
|
5770
5930
|
// And if the floor exceeds the CEILING, raising is a guaranteed second
|
|
5771
5931
|
// refusal — a dead retry inside the message explaining the first.
|
|
5772
5932
|
//
|
|
@@ -7925,11 +8085,26 @@ export function buildPanelToolDefs() {
|
|
|
7925
8085
|
const confirmBudget = Math.max(1, Math.min(RESTART_CONFIRM_TIMEOUT_MS, overallDeadline - Date.now()));
|
|
7926
8086
|
const decision = await ctx.confirm("Restart ComfyUI now? It (and this agent) will go down briefly, then reconnect and resume automatically.", "Restart ComfyUI", confirmBudget);
|
|
7927
8087
|
if (decision === "timeout") {
|
|
8088
|
+
// #851 — the fallback used to be recommended unconditionally, and
|
|
8089
|
+
// `restart_comfyui` targets COMFYUI_URL, NOT the ComfyUI the panel is
|
|
8090
|
+
// running inside. A reporter followed this advice against a panel driving a
|
|
8091
|
+
// different server and got `No ComfyUI process found on port 8188` while the
|
|
8092
|
+
// panel was working fine on the live canvas — a restart aimed at the wrong
|
|
8093
|
+
// machine, recommended by a call that never checked which machine that was.
|
|
8094
|
+
//
|
|
8095
|
+
// The information was already here and simply not consulted on this branch:
|
|
8096
|
+
// captureRebootHealthBase() is the origin the PANEL answers on, and the
|
|
8097
|
+
// adjacent decline branch already reads it. Only recommend the headless tool
|
|
8098
|
+
// when it provably points at the same server; otherwise name both and say
|
|
8099
|
+
// plainly that this call did not verify the other one.
|
|
8100
|
+
const fallback = restartTimeoutFallbackAdvice({
|
|
8101
|
+
headlessBase: getComfyUIBaseUrl(),
|
|
8102
|
+
panelBase: captureRebootHealthBase(ctx),
|
|
8103
|
+
});
|
|
7928
8104
|
return ok(`No confirmation received within ${Math.round(confirmBudget / 1000)}s, so I did NOT ` +
|
|
7929
8105
|
"restart ComfyUI. The panel tab may be backgrounded or still reconnecting after a " +
|
|
7930
8106
|
"previous restart, so the confirmation card wasn't answered. Tell me to restart it " +
|
|
7931
|
-
|
|
7932
|
-
"panel card.");
|
|
8107
|
+
`and I'll re-ask. ${fallback}`);
|
|
7933
8108
|
}
|
|
7934
8109
|
if (decision !== "yes") {
|
|
7935
8110
|
// #742: NEVER claim "not restarted" while the server is actually DOWN —
|