comfyui-mcp 0.50.100 → 0.50.102
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 +124 -7
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/services/workflow-converter.js +45 -3
- package/dist/services/workflow-converter.js.map +1 -1
- package/dist/tools/vocabulary.js +17 -0
- package/dist/tools/vocabulary.js.map +1 -1
- package/package.json +1 -1
|
@@ -1341,6 +1341,28 @@ function toolResultText(res) {
|
|
|
1341
1341
|
* else about it — including `isError` and any non-text content. Used where the
|
|
1342
1342
|
* original message must reach the caller VERBATIM and we are only adding what we
|
|
1343
1343
|
* additionally know, never restating (or re-classifying) what it already said. */
|
|
1344
|
+
/** All text in a tool result, joined — for quoting one result inside another. */
|
|
1345
|
+
function textOfToolResult(res) {
|
|
1346
|
+
return res.content.map((c) => c.text ?? "").join(" ").trim();
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* #1329 — the panel's STALE NODE SCHEMA refusal, which is worth acting on rather than
|
|
1350
|
+
* relaying.
|
|
1351
|
+
*
|
|
1352
|
+
* It is the one refusal here whose prescribed remedy has no decision in it: the class
|
|
1353
|
+
* this page registered has drifted from the server's, and `refresh_nodes` re-fetches
|
|
1354
|
+
* /object_info and re-registers it in place. The reporter ran that by hand twice and it
|
|
1355
|
+
* worked twice.
|
|
1356
|
+
*
|
|
1357
|
+
* Matched on the drift sentence rather than on "panel_refresh_nodes", which appears in
|
|
1358
|
+
* several unrelated remedies — keying on the recommendation would make any of them
|
|
1359
|
+
* trigger an add-retry.
|
|
1360
|
+
*/
|
|
1361
|
+
function isStaleNodeSchemaRefusal(res) {
|
|
1362
|
+
if (!res.isError)
|
|
1363
|
+
return false;
|
|
1364
|
+
return /added or retyped since this page loaded its node schema/i.test(textOfToolResult(res));
|
|
1365
|
+
}
|
|
1344
1366
|
function appendToolResultText(res, extra) {
|
|
1345
1367
|
const idx = res?.content?.findIndex((c) => c.type === "text") ?? -1;
|
|
1346
1368
|
const block = idx >= 0 ? res.content[idx] : undefined;
|
|
@@ -2487,6 +2509,67 @@ export function readOpenActiveAgainstTarget(active, path, activeConfirmed) {
|
|
|
2487
2509
|
* all. Requiring corroboration there is what kept the reporter's own case
|
|
2488
2510
|
* unexamined.
|
|
2489
2511
|
*/
|
|
2512
|
+
/**
|
|
2513
|
+
* #1337 — CLEAR THE FENCE ON THE VERDICT THAT PROVED IDENTITY.
|
|
2514
|
+
*
|
|
2515
|
+
* A reporter's session lost the canvas permanently: every graph call refused with
|
|
2516
|
+
* `workflow instance mismatch`, and the one fence-EXEMPT recovery could not clear it.
|
|
2517
|
+
*
|
|
2518
|
+
* `workflow_open` re-derives the fence only on its success branch. The verdict
|
|
2519
|
+
*
|
|
2520
|
+
* "workflow_open RAN and the canvas IS bound to X — that much was proven — but the
|
|
2521
|
+
* graph on it does not match the state that was loaded … You are NOT on the wrong
|
|
2522
|
+
* workflow: X IS the active one."
|
|
2523
|
+
*
|
|
2524
|
+
* is delivered as an ERROR, so control never reaches the re-derivation. That mismatches
|
|
2525
|
+
* two different levels: the verdict asserts IDENTITY was proven, and what it withholds
|
|
2526
|
+
* is CONTENT (frontend normalisation vs a partial load). Applying content-level
|
|
2527
|
+
* uncertainty to an identity-level fence is what leaves the session with no in-band
|
|
2528
|
+
* recovery — the only remaining move is a browser reload, which destroys unsaved work.
|
|
2529
|
+
*
|
|
2530
|
+
* MEASURED, because the comment that justified the old behaviour asserts otherwise:
|
|
2531
|
+
* • the panel really does publish NO workflow_uuid on this reply (its own
|
|
2532
|
+
* FENCE_NOT_REFRESHED text), so there is nothing to adopt FROM THE REPLY — true;
|
|
2533
|
+
* • but `workflow_list` IS fence-exempt (commandIsCanvasTargetless in the panel's
|
|
2534
|
+
* workflow-chat-identity.js), and the panel's own recovery text names
|
|
2535
|
+
* panel_list_workflows as "exempt from the fence" and says it "republishes the
|
|
2536
|
+
* active identity". So the fence CAN be re-derived here; nothing tried.
|
|
2537
|
+
*
|
|
2538
|
+
* This runs that exempt re-derivation and reports what it achieved. The open still
|
|
2539
|
+
* FAILS — the content warning is preserved verbatim, because "re-read before editing"
|
|
2540
|
+
* is still the right instruction — and nothing is adopted on the UNPROVEN verdict,
|
|
2541
|
+
* where identity itself is in doubt.
|
|
2542
|
+
*/
|
|
2543
|
+
async function clearFenceOnIdentityProvenOpen(ctx, res) {
|
|
2544
|
+
const text = toolResultText(res);
|
|
2545
|
+
// ONLY the class that states identity was proven. The UNPROVEN verdict ("could not
|
|
2546
|
+
// prove that the active canvas was rebound") must keep failing closed: re-deriving a
|
|
2547
|
+
// fence onto a canvas we cannot identify is how an edit lands on the wrong graph.
|
|
2548
|
+
if (!/the canvas IS bound to/i.test(text))
|
|
2549
|
+
return res;
|
|
2550
|
+
let note;
|
|
2551
|
+
try {
|
|
2552
|
+
const rebind = await rebindWorkflowFence(ctx);
|
|
2553
|
+
note =
|
|
2554
|
+
rebind.status === "refreshed" || rebind.status === "already_current"
|
|
2555
|
+
? `\n\nFENCE: CLEARED. This reply published no workflow_uuid, so the fence was re-derived ` +
|
|
2556
|
+
`from the panel's own fence-EXEMPT workflow_list read instead — the active identity is ` +
|
|
2557
|
+
`${rebind.uuid}, and graph commands from this session are stamped with it again. You do ` +
|
|
2558
|
+
`NOT need to reload the browser tab, and no unsaved canvas work is at risk. The content ` +
|
|
2559
|
+
`warning above still stands: re-read the graph before editing.`
|
|
2560
|
+
: `\n\nFENCE: NOT cleared (${rebind.status}). The re-derivation was attempted — the panel ` +
|
|
2561
|
+
`publishes no workflow_uuid on this verdict, so the fence-exempt workflow_list read is ` +
|
|
2562
|
+
`the only way to refresh it — and it did not establish an identity. Graph commands stay ` +
|
|
2563
|
+
`refused. Try panel_list_workflows directly; if that also fails, the panel tab is the ` +
|
|
2564
|
+
`thing to fix.`;
|
|
2565
|
+
}
|
|
2566
|
+
catch (err) {
|
|
2567
|
+
note =
|
|
2568
|
+
`\n\nFENCE: NOT cleared — the re-derivation threw, so the fence state is UNKNOWN and graph ` +
|
|
2569
|
+
`commands may still be refused. (${err instanceof Error ? err.message : String(err)})`;
|
|
2570
|
+
}
|
|
2571
|
+
return appendToolResultText(res, note);
|
|
2572
|
+
}
|
|
2490
2573
|
async function observeActiveAfterOpen(ctx, requestedPath) {
|
|
2491
2574
|
let list = null;
|
|
2492
2575
|
try {
|
|
@@ -3621,7 +3704,7 @@ async function openWorkflowWithVerify(path, ctx) {
|
|
|
3621
3704
|
return res;
|
|
3622
3705
|
const drift = await observeActiveAfterOpen(ctx, path);
|
|
3623
3706
|
if (!drift)
|
|
3624
|
-
return res;
|
|
3707
|
+
return await clearFenceOnIdentityProvenOpen(ctx, res);
|
|
3625
3708
|
return fail(`${toolResultText(res)}\n\nAND — checked after that failure — ${drift.activeLabel} is the ` +
|
|
3626
3709
|
`ACTIVE workflow now, not ${path}. Whatever that message says about ${path} being active, ` +
|
|
3627
3710
|
`it is not: the canvas you would read or write is ${drift.activeLabel}. Do NOT save, ` +
|
|
@@ -6675,12 +6758,46 @@ export function buildPanelToolDefs() {
|
|
|
6675
6758
|
.optional()
|
|
6676
6759
|
.describe("Canvas [x, y] (two numbers). Auto-placed beside existing nodes when omitted."),
|
|
6677
6760
|
title: z.string().optional().describe("Optional custom node title."),
|
|
6678
|
-
}, async (args, ctx) =>
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6761
|
+
}, async (args, ctx) => {
|
|
6762
|
+
// #599: the frontend gates the add on a FRESH /object_info (assertAddNode-
|
|
6763
|
+
// ResolvableRefreshing) so an uninstalled class can't be added as a
|
|
6764
|
+
// placeholder — that fetch can outlast the 6000 ms default on a large
|
|
6765
|
+
// install. Give it the bounded refresh ack budget.
|
|
6766
|
+
const add = () => ctx.call({ cmd: "graph_add_node", class_type: args.class_type, pos: args.pos, title: args.title }, OBJECT_INFO_REFRESH_ACK_TIMEOUT_MS);
|
|
6767
|
+
const first = await add();
|
|
6768
|
+
if (!isStaleNodeSchemaRefusal(first))
|
|
6769
|
+
return first;
|
|
6770
|
+
// #1329 — DO THE REFRESH THE REFUSAL ASKS FOR, instead of billing the caller.
|
|
6771
|
+
//
|
|
6772
|
+
// The reporter uploaded two images, and both LoadImage adds were refused with
|
|
6773
|
+
// "call panel_refresh_nodes and retry". Doing exactly that worked, both times.
|
|
6774
|
+
// That is a round trip and an agent-visible error for something with one
|
|
6775
|
+
// correct response and no decision in it.
|
|
6776
|
+
//
|
|
6777
|
+
// Safe to automate for a checkable reason, not a hopeful one: the panel's guard
|
|
6778
|
+
// throws BEFORE it creates anything ("Refuse before creating anything"), so the
|
|
6779
|
+
// retry cannot leave two nodes behind. Mutations are otherwise never re-issued
|
|
6780
|
+
// on our own initiative, and that rule is intact — this is not a transport
|
|
6781
|
+
// failure with an unknown outcome, it is a refusal that states its own.
|
|
6782
|
+
const refreshed = await ctx.call({ cmd: "refresh_nodes" }, OBJECT_INFO_REFRESH_ACK_TIMEOUT_MS);
|
|
6783
|
+
if (refreshed.isError) {
|
|
6784
|
+
// The refusal is the better error: it names what is wrong with the SCHEMA and
|
|
6785
|
+
// what clears it. Carry the refresh failure alongside so the caller knows the
|
|
6786
|
+
// automatic attempt happened and why it did not help.
|
|
6787
|
+
return appendToolResultText(first, `\n\n(Tried to clear this automatically: panel_refresh_nodes was dispatched and FAILED, ` +
|
|
6788
|
+
`so the schema is unchanged and retrying the add will refuse again. ` +
|
|
6789
|
+
`${textOfToolResult(refreshed)})`);
|
|
6790
|
+
}
|
|
6791
|
+
const second = await add();
|
|
6792
|
+
if (!isStaleNodeSchemaRefusal(second))
|
|
6793
|
+
return second;
|
|
6794
|
+
// Still stale after a successful refresh: report THAT, because it means the
|
|
6795
|
+
// remedy the refusal prescribes does not fix this instance and a caller
|
|
6796
|
+
// following it by hand would loop.
|
|
6797
|
+
return appendToolResultText(second, `\n\n(This was already retried ONCE automatically: panel_refresh_nodes reported success ` +
|
|
6798
|
+
`and the add still refuses, so repeating panel_refresh_nodes will not clear it. ` +
|
|
6799
|
+
`Reload the ComfyUI browser tab, which rebuilds the page's node registry from scratch.)`);
|
|
6800
|
+
}),
|
|
6684
6801
|
def("panel_remove_node", "Remove a node (and its connections) from the user's open graph by id. Undoable with Ctrl+Z.", { node_id: nodeId().describe("Node id from panel_graph_outline / panel_query_graph.") }, async (args, ctx) => ctx.call({ cmd: "graph_remove_node", node_id: args.node_id })),
|
|
6685
6802
|
def("panel_clear", "Remove EVERY node from the user's open graph — only for an explicit 'clear/reset the canvas'. Just CALL THIS DIRECTLY when they ask to clear: the tool itself pops a confirm card and only wipes on a yes (don't ask separately first). The wipe is a single Ctrl+Z undo. NEVER use this for a 'new workflow' — that's panel_new_workflow (a new tab, leaves this graph intact).", {}, async (_args, ctx) => {
|
|
6686
6803
|
const decision = await ctx.confirm("Clear the canvas? This removes every node from the open workflow. (One Ctrl+Z undoes it.)", "Clear canvas");
|