comfyui-mcp 0.52.37 → 0.52.39
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 +39 -147
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/services/node-management.js +75 -1
- package/dist/services/node-management.js.map +1 -1
- package/dist/services/panel-graph-cmd-tools.js +183 -0
- package/dist/services/panel-graph-cmd-tools.js.map +1 -0
- package/dist/services/ui-bridge.js +31 -6
- package/dist/services/ui-bridge.js.map +1 -1
- package/dist/services/workspace-env.js +144 -1
- package/dist/services/workspace-env.js.map +1 -1
- package/package.json +1 -1
|
@@ -148,6 +148,8 @@ function journalConversationFor(ctx) {
|
|
|
148
148
|
return conversationOfScopeAddress(ctx.tabId);
|
|
149
149
|
}
|
|
150
150
|
import { BRIDGE_DEFAULT_TIMEOUT_MS, BRIDGE_READ_DEFAULT_TIMEOUT_MS, dispatchOutcomeOf, GRAPH_CMD_EFFECT, isCapabilityRefusal, isPanelCmdUnsupportedError, isMidCommandDisconnectTagged, isReplyTimeoutTagged, isRoutingAmbiguity, requiresWorkflowStampEnforcement, } from "../services/ui-bridge.js";
|
|
151
|
+
import { PANEL_TOOL_BY_GRAPH_CMD, QUEUE_BUSY_READ_TOOLS, RETRY_TOKEN_CMD_BY_TOOL, panelToolForGraphCmd, } from "../services/panel-graph-cmd-tools.js";
|
|
152
|
+
export { PANEL_TOOL_BY_GRAPH_CMD, QUEUE_BUSY_READ_TOOLS, RETRY_TOKEN_CMD_BY_TOOL, panelToolForGraphCmd, };
|
|
151
153
|
import { withWorkflowTarget, } from "../services/workflow-target-store.js";
|
|
152
154
|
import { addUserMcpServer, readUserMcpServers, removeUserMcpServer, setUserMcpServerSecret, } from "../services/user-mcp-config.js";
|
|
153
155
|
import { setComfyuiSecret, setAgentSecret, isAllowedAgentSecretKey, receiptDisclosures, shadowedNote, storeDamageNote, } from "../services/panel-secrets.js";
|
|
@@ -812,33 +814,6 @@ function queueBusySnapshotNote() {
|
|
|
812
814
|
function graphCmdIsInertUnderQueueBusy(name) {
|
|
813
815
|
return GRAPH_CMD_EFFECT[name] === "inert";
|
|
814
816
|
}
|
|
815
|
-
/**
|
|
816
|
-
* #1933 — the READ TOOLS this refusal advertises, by their REGISTERED tool name.
|
|
817
|
-
*
|
|
818
|
-
* They must be tool names, not the `graph_outline` / `graph_query` /
|
|
819
|
-
* `graph_get_errors` bridge commands they dispatch: this string is the whole
|
|
820
|
-
* recovery contract for a fenced write, and an agent that follows it calls what
|
|
821
|
-
* it is told to call. A bridge command is not callable, so the advice returned
|
|
822
|
-
* "unknown tool" — a hard failure mid-degraded-state that the agent cannot
|
|
823
|
-
* diagnose, and from which it can only conclude that reads are unavailable
|
|
824
|
-
* during a render, which is precisely the state panel#1489 fixed and this
|
|
825
|
-
* sentence exists to advertise (reported from the field in
|
|
826
|
-
* artokun/comfyui-mcp-panel#1549).
|
|
827
|
-
*
|
|
828
|
-
* The mapping is NOT the `panel_` prefix mechanically applied — `graph_query`'s
|
|
829
|
-
* tool is `panel_query_graph`, not `panel_graph_query`. So these are written
|
|
830
|
-
* out, and `graph-read-during-render.test.ts` asserts every one of them is in
|
|
831
|
-
* `buildPanelToolDefs()` — the registry the caller actually dispatches against.
|
|
832
|
-
* That test is what stops this list from drifting again: the old literal was
|
|
833
|
-
* green for the whole of its life with all three names wrong, because the
|
|
834
|
-
* assertion matched `/graph_outline/`, which a wrong name and a right one both
|
|
835
|
-
* satisfy.
|
|
836
|
-
*/
|
|
837
|
-
export const QUEUE_BUSY_READ_TOOLS = [
|
|
838
|
-
"panel_graph_outline",
|
|
839
|
-
"panel_query_graph",
|
|
840
|
-
"panel_get_errors",
|
|
841
|
-
];
|
|
842
817
|
function graphCmdBlockedByRunningPrompt(cmd) {
|
|
843
818
|
const name = typeof cmd.cmd === "string" ? cmd.cmd : "";
|
|
844
819
|
if (!name.startsWith("graph_") || name === "graph_run")
|
|
@@ -3190,6 +3165,35 @@ function appendToolResultText(res, extra) {
|
|
|
3190
3165
|
content[idx] = { type: "text", text: `${block.text}${extra}` };
|
|
3191
3166
|
return { ...res, content };
|
|
3192
3167
|
}
|
|
3168
|
+
/**
|
|
3169
|
+
* #1873 — Save-As onto an EXISTING name 409s on purpose. ComfyUI's userdata POST
|
|
3170
|
+
* returns 409 when overwrite=false; the frontend's saveWorkflowAs then prompts
|
|
3171
|
+
* and, on confirm, DELETES the occupant. The panel refuses that delete path and
|
|
3172
|
+
* surfaces "choose a different name", which is what forced a third workflow file.
|
|
3173
|
+
*
|
|
3174
|
+
* Matched on the panel's relocating-save collision (`conflictError` in
|
|
3175
|
+
* workflow-save.js), not a generic "409 Conflict" — an in-place save can 409
|
|
3176
|
+
* over its OWN file (#442) and that remedy is not this one.
|
|
3177
|
+
*/
|
|
3178
|
+
function isSaveAsNameConflict(res) {
|
|
3179
|
+
if (!res.isError)
|
|
3180
|
+
return false;
|
|
3181
|
+
const text = textOfToolResult(res);
|
|
3182
|
+
return /already exists \(409 Conflict\)/i.test(text) && /choose a different name/i.test(text);
|
|
3183
|
+
}
|
|
3184
|
+
const SAVE_AS_NAME_CONFLICT_NOTE = `\n\nDo NOT pick a third name. Save-As never overwrites an existing file — that 409 is ` +
|
|
3185
|
+
`the copy contract, not a missing overwrite switch. To land the edited copy under the ` +
|
|
3186
|
+
`intended original name while keeping a backup of the pre-edit original:\n` +
|
|
3187
|
+
`1. panel_rename_workflow({path: "<original>", name: "<original>_pre_edit"}) — path can ` +
|
|
3188
|
+
`target a CLOSED listed workflow, so this does not disturb the active tab.\n` +
|
|
3189
|
+
`2. panel_rename_workflow({name: "<original>"}) — no path, so this renames the ACTIVE ` +
|
|
3190
|
+
`copy onto the freed name; the tab follows the file.\n` +
|
|
3191
|
+
`3. panel_save_workflow() — in-place save from then on.`;
|
|
3192
|
+
function withSaveAsNameConflictNote(res) {
|
|
3193
|
+
if (!isSaveAsNameConflict(res))
|
|
3194
|
+
return res;
|
|
3195
|
+
return appendToolResultText(res, SAVE_AS_NAME_CONFLICT_NOTE);
|
|
3196
|
+
}
|
|
3193
3197
|
// ---- #1695: bound the panel_set_widget previous/new echo --------------------
|
|
3194
3198
|
//
|
|
3195
3199
|
// Code-mode clients (Codex `functions.exec`) batch several panel_set_widget
|
|
@@ -9828,126 +9832,11 @@ const RETRY_OF_ARG = {
|
|
|
9828
9832
|
.optional()
|
|
9829
9833
|
.describe("Retry token (#694): pass the retry_of rid from a previous outcome-unknown failure of this identical call to retry that exact mutation; omit otherwise."),
|
|
9830
9834
|
};
|
|
9831
|
-
/**
|
|
9832
|
-
* #694 — the MUTATING panel tools that accept retry_of, keyed to the bridge
|
|
9833
|
-
* command each dispatches. Covers every command the bridge fences to a workflow
|
|
9834
|
-
* (GRAPH_CMD_EFFECT "targeted" — see requiresWorkflowStampEnforcement) plus the
|
|
9835
|
-
* four workflow mutators (workflow_save / workflow_save_as / workflow_rename /
|
|
9836
|
-
* workflow_close). Navigation/creation (workflow_open / workflow_new) and the
|
|
9837
|
-
* tools whose descriptions declare them view/read-only (panel_find_nodes,
|
|
9838
|
-
* panel_canvas, panel_screenshot, panel_list_subgraphs) are excluded: a read must
|
|
9839
|
-
* never mint or carry a retry token — its retry could be answered from the ledger
|
|
9840
|
-
* with a STALE outcome (codex gate).
|
|
9841
|
-
*
|
|
9842
|
-
* Four UI-STATE commands are admitted on purpose (graph_select_nodes,
|
|
9843
|
-
* graph_enter/exit_subgraph, graph_copy_nodes): they change selection, subgraph
|
|
9844
|
-
* scope or clipboard idempotently, so a deduped retry is a no-op. THIS MAP — not
|
|
9845
|
-
* the workflow fence — is what decides whether a token is minted and whether a
|
|
9846
|
-
* caller-supplied one reaches the wire, so those four keep behaving exactly as
|
|
9847
|
-
* they did before #778 reclassified them as `inert` for the fence. The two
|
|
9848
|
-
* questions are related but not the same, and answering one with the other is
|
|
9849
|
-
* the defect #778 is about.
|
|
9850
|
-
*
|
|
9851
|
-
* EXPLICIT MAP, mirroring the RETRY_SAFE_CMDS / MUTATING_GRAPH_EDIT_CMDS
|
|
9852
|
-
* maintenance model — keep in sync when mutating tools are added. Exported for
|
|
9853
|
-
* the #694 surface-integrity test.
|
|
9854
|
-
*/
|
|
9855
|
-
export const RETRY_TOKEN_CMD_BY_TOOL = {
|
|
9856
|
-
panel_add_node: "graph_add_node",
|
|
9857
|
-
panel_edit_node: "graph_edit_node",
|
|
9858
|
-
panel_remove_node: "graph_remove_node",
|
|
9859
|
-
panel_clear: "graph_clear",
|
|
9860
|
-
panel_flatten_workflow: "graph_load",
|
|
9861
|
-
panel_load_workflow: "graph_load",
|
|
9862
|
-
panel_connect: "graph_connect",
|
|
9863
|
-
panel_disconnect: "graph_disconnect",
|
|
9864
|
-
// Idempotent: inputs/outputs/default_mode are absolute values, so a deduped
|
|
9865
|
-
// retry writes the same metadata again rather than doubling anything.
|
|
9866
|
-
panel_configure_app_mode: "graph_configure_app_mode",
|
|
9867
|
-
panel_set_widget: "graph_set_widget",
|
|
9868
|
-
panel_remove_widget: "graph_remove_widget",
|
|
9869
|
-
panel_set_property: "graph_set_node_property",
|
|
9870
|
-
panel_move_node: "graph_move_node",
|
|
9871
|
-
panel_resize_node: "graph_resize_node",
|
|
9872
|
-
panel_auto_layout: "graph_auto_layout",
|
|
9873
|
-
panel_run: "graph_run",
|
|
9874
|
-
panel_save_workflow: "workflow_save", // workflow_save_as when `name` is given
|
|
9875
|
-
panel_rename_workflow: "workflow_rename",
|
|
9876
|
-
panel_close_workflow: "workflow_close",
|
|
9877
|
-
panel_select_nodes: "graph_select_nodes",
|
|
9878
|
-
panel_create_subgraph: "graph_create_subgraph",
|
|
9879
|
-
panel_subgraph_group: "graph_subgraph_group",
|
|
9880
|
-
panel_copy_nodes: "graph_copy_nodes",
|
|
9881
|
-
panel_paste_nodes: "graph_paste_nodes",
|
|
9882
|
-
panel_save_subgraph: "graph_save_subgraph",
|
|
9883
|
-
panel_add_subgraph: "graph_add_subgraph",
|
|
9884
|
-
panel_create_group: "graph_create_group",
|
|
9885
|
-
panel_move_group: "graph_move_group",
|
|
9886
|
-
panel_edit_group: "graph_edit_group",
|
|
9887
|
-
panel_remove_group: "graph_remove_group",
|
|
9888
|
-
panel_set_node_title: "graph_set_title",
|
|
9889
|
-
panel_set_node_collapsed: "graph_set_node_collapsed",
|
|
9890
|
-
panel_set_node_mode: "graph_set_node_mode",
|
|
9891
|
-
panel_set_node_color: "graph_set_node_color",
|
|
9892
|
-
panel_enter_subgraph: "graph_enter_subgraph",
|
|
9893
|
-
panel_exit_subgraph: "graph_exit_subgraph",
|
|
9894
|
-
panel_move_rail: "graph_move_rail",
|
|
9895
|
-
panel_promote_widget: "graph_promote_widget",
|
|
9896
|
-
panel_expose_subgraph_output: "graph_expose_subgraph_output",
|
|
9897
|
-
panel_expose_subgraph_input: "graph_expose_subgraph_input",
|
|
9898
|
-
panel_unexpose_subgraph_output: "graph_unexpose_subgraph_output",
|
|
9899
|
-
panel_unexpose_subgraph_input: "graph_unexpose_subgraph_input",
|
|
9900
|
-
panel_unpack_subgraph: "graph_unpack_subgraph",
|
|
9901
|
-
panel_update_node: "graph_update_node",
|
|
9902
|
-
};
|
|
9903
9835
|
/** #694 — the bridge commands the retry map admits, for the mint gate: a
|
|
9904
9836
|
* dispatched timeout/drop only mints a retry token when the command is in
|
|
9905
9837
|
* this set (bridge classification alone would include read/view-only
|
|
9906
9838
|
* commands that sit outside BRIDGE_READONLY_CMDS). */
|
|
9907
9839
|
export const RETRY_TOKEN_CMDS = new Set(Object.values(RETRY_TOKEN_CMD_BY_TOOL).concat(["workflow_save_as"]));
|
|
9908
|
-
/**
|
|
9909
|
-
* #1933 — the reverse of RETRY_TOKEN_CMD_BY_TOOL: which TOOL does a caller reach
|
|
9910
|
-
* this bridge command through?
|
|
9911
|
-
*
|
|
9912
|
-
* An agent-facing message written below the tool layer can only see `cmd.cmd`;
|
|
9913
|
-
* `PanelToolCtx.call` takes a command object and no tool identity, so by the
|
|
9914
|
-
* time a pre-dispatch refusal is worded the tool name is genuinely gone. This
|
|
9915
|
-
* recovers it from the map that already maintains the correspondence, rather
|
|
9916
|
-
* than adding a second hand-written list that can drift out of step with the
|
|
9917
|
-
* first.
|
|
9918
|
-
*
|
|
9919
|
-
* IT REFUSES TO GUESS. The forward map is not injective — `graph_load` is
|
|
9920
|
-
* dispatched by both `panel_load_workflow` and `panel_flatten_workflow` — and
|
|
9921
|
-
* naming one of two possible tools is a confidently wrong answer, which is worse
|
|
9922
|
-
* than declining to name one. Ambiguous commands are therefore OMITTED, and
|
|
9923
|
-
* callers must handle `undefined`.
|
|
9924
|
-
*
|
|
9925
|
-
* Coverage is not assumed either: every `targeted` GRAPH_CMD_EFFECT command
|
|
9926
|
-
* except `graph_run` is a value of the forward map, so the queue-busy fence
|
|
9927
|
-
* resolves a tool for everything it can refuse except `graph_load`.
|
|
9928
|
-
* `graph-read-during-render.test.ts` pins both halves — the resolution and the
|
|
9929
|
-
* refusal to guess.
|
|
9930
|
-
*/
|
|
9931
|
-
export const PANEL_TOOL_BY_GRAPH_CMD = (() => {
|
|
9932
|
-
const claims = new Map();
|
|
9933
|
-
for (const [tool, cmd] of Object.entries(RETRY_TOKEN_CMD_BY_TOOL)) {
|
|
9934
|
-
const prior = claims.get(cmd);
|
|
9935
|
-
if (prior)
|
|
9936
|
-
prior.push(tool);
|
|
9937
|
-
else
|
|
9938
|
-
claims.set(cmd, [tool]);
|
|
9939
|
-
}
|
|
9940
|
-
const unique = new Map();
|
|
9941
|
-
for (const [cmd, tools] of claims)
|
|
9942
|
-
if (tools.length === 1)
|
|
9943
|
-
unique.set(cmd, tools[0]);
|
|
9944
|
-
return unique;
|
|
9945
|
-
})();
|
|
9946
|
-
/** #1933 — the registered tool a bridge command is reached through, or
|
|
9947
|
-
* `undefined` when no tool, or more than one tool, claims it. */
|
|
9948
|
-
export function panelToolForGraphCmd(cmd) {
|
|
9949
|
-
return PANEL_TOOL_BY_GRAPH_CMD.get(cmd);
|
|
9950
|
-
}
|
|
9951
9840
|
/** #694 — augment one MUTATING tool def: accept retry_of and attach it, UNTOUCHED,
|
|
9952
9841
|
* to every mutating command the handler dispatches (per-command gated so a read
|
|
9953
9842
|
* probe inside the same handler — e.g. panel_flatten_workflow's live-canvas
|
|
@@ -12350,7 +12239,7 @@ export function buildPanelToolDefs() {
|
|
|
12350
12239
|
multi_select: args.multi_select,
|
|
12351
12240
|
});
|
|
12352
12241
|
}),
|
|
12353
|
-
def("panel_save_workflow", "Save the user's open workflow PROGRAMMATICALLY — no Save/Rename dialog ever pops. With no `name`: saves in place (or auto-names + persists a never-saved workflow). With `name`: if the workflow is ALREADY saved under a different name this is a SAVE-AS — it writes a NEW file and leaves the original untouched on disk (it NEVER renames/moves/destroys the original); for a never-saved workflow it is simply the first save. The result reports what happened: `saved_as`+`copied_from`+`original_on_disk` (a disk-verified check that the original file still exists) for a Save-As copy, or `first_save` for a brand-new workflow. Use this freely (e.g. after building a graph) — it won't interrupt the user.", { name: z.string().optional().describe("Name for the workflow (no .json needed). If the workflow is already saved under a different name, this writes a NEW file (Save-As COPY) and leaves the original in place — it never renames/moves/destroys it. Omit to save in place / auto-name an unsaved workflow.") }, async (args, ctx) => {
|
|
12242
|
+
def("panel_save_workflow", "Save the user's open workflow PROGRAMMATICALLY — no Save/Rename dialog ever pops. With no `name`: saves in place (or auto-names + persists a never-saved workflow). With `name`: if the workflow is ALREADY saved under a different name this is a SAVE-AS — it writes a NEW file and leaves the original untouched on disk (it NEVER renames/moves/destroys the original); for a never-saved workflow it is simply the first save. The result reports what happened: `saved_as`+`copied_from`+`original_on_disk` (a disk-verified check that the original file still exists) for a Save-As copy, or `first_save` for a brand-new workflow. A Save-As onto an EXISTING name is a 409 Conflict (the copy never overwrites) — do NOT pick a third name; panel_rename_workflow can target a closed listed original to free the name, then rename the active copy onto it, then panel_save_workflow() in place. Use this freely (e.g. after building a graph) — it won't interrupt the user.", { name: z.string().optional().describe("Name for the workflow (no .json needed). If the workflow is already saved under a different name, this writes a NEW file (Save-As COPY) and leaves the original in place — it never renames/moves/destroys it. An existing target name 409s; do not invent a third name — use panel_rename_workflow to free the original then rename the active copy onto it. Omit to save in place / auto-name an unsaved workflow.") }, async (args, ctx) => {
|
|
12354
12243
|
// #402: await a stable tab binding before dispatching the (mutating) save, so
|
|
12355
12244
|
// a save issued in the post-restart "Connected: none" window reaches a live
|
|
12356
12245
|
// tab instead of failing with a bare "Failed to fetch"/OUTCOME UNKNOWN. Pre-
|
|
@@ -12377,8 +12266,11 @@ export function buildPanelToolDefs() {
|
|
|
12377
12266
|
}
|
|
12378
12267
|
}
|
|
12379
12268
|
}
|
|
12380
|
-
if (res.isError)
|
|
12381
|
-
|
|
12269
|
+
if (res.isError) {
|
|
12270
|
+
// #1873 — named Save-As 409s by contract; the panel's "choose a different
|
|
12271
|
+
// name" is what forced a third file. Keep the 409 and name the rename path.
|
|
12272
|
+
return args.name ? withSaveAsNameConflictNote(res) : res;
|
|
12273
|
+
}
|
|
12382
12274
|
// #1045 — a SAVE-AS replaces the active workflow instance: the canvas is
|
|
12383
12275
|
// now the NEW file, with its own identity, while this session's command
|
|
12384
12276
|
// fence still names the pre-save one. Every graph call afterwards fails
|
|
@@ -13140,9 +13032,9 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
|
|
|
13140
13032
|
// ack-timeout, confirm via the authoritative workflow_list active identity
|
|
13141
13033
|
// before reporting failure — see openWorkflowWithVerify.
|
|
13142
13034
|
async (args, ctx) => openWorkflowWithVerify(args.path, ctx)),
|
|
13143
|
-
def("panel_rename_workflow", "Rename a workflow (the active one, or the one matching `path`).", {
|
|
13035
|
+
def("panel_rename_workflow", "Rename a workflow (the active one, or the one matching `path`). `path` can name a CLOSED listed workflow — renaming it does not disturb the active tab. After a Save-As copy, this is how to land the edited graph under the original name without a third file: rename the closed original aside (`path` + new name), then rename the active copy onto the freed name (no `path`), then panel_save_workflow().", {
|
|
13144
13036
|
name: z.string().describe("New name (no .json needed)."),
|
|
13145
|
-
path: z.string().optional().describe("Which workflow to rename; omit for the active one."),
|
|
13037
|
+
path: z.string().optional().describe("Which workflow to rename; omit for the active one. Can name a CLOSED listed workflow (does not disturb the active tab)."),
|
|
13146
13038
|
}, async (args, ctx) => ctx.call({ cmd: "workflow_rename", name: args.name, path: args.path }, 15000)),
|
|
13147
13039
|
def("panel_close_workflow", "Close a workflow tab (the active one, or the one matching `path`). Refuses if it has unsaved changes unless force:true — save first to avoid losing the user's work.", {
|
|
13148
13040
|
path: z.string().optional().describe("Which workflow to close; omit for the active one."),
|