comfyui-mcp 0.52.160 → 0.52.162

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.
@@ -47,6 +47,7 @@ import { isPreExecutorRefusal } from "../services/panel-refusal.js";
47
47
  import { hostLinksAlreadyReindexed, isLandedUnexpose, laterSlotsAfterUnexpose, laterSlotsFromUnexposePayload, unexposeHostLinkShiftNote, } from "../services/unexpose-host-link-shift.js";
48
48
  import { retryConnectAgainstLiveGraph } from "../services/connect-live-graph.js";
49
49
  import { verifyPrimitiveForceInputAfterConnect } from "../services/primitive-force-input-connect.js";
50
+ import { retryWildcardSlotConnect } from "../services/wildcard-slot-connect.js";
50
51
  import { retryExposeSubgraphInput } from "../services/expose-ae-wildcard.js";
51
52
  import { applyLiveRootViewing, callAndRememberViewing, callWithRememberedSubgraph, clearStaleSubgraphIdentity, noteConfirmedViewingFromToolResult, parseViewingScope, } from "../services/subgraph-viewing-scope.js";
52
53
  import { assertScreenshotPersistAllowed, decodePngBase64, persistScreenshotPng, resolveScreenshotPersistPath, screenshotOverwriteFromArgs, screenshotPersistPathFromArgs, } from "./panel-screenshot-persist.js";
@@ -66,6 +67,7 @@ import { includeRequestedCreateGroupMembers } from "./create-group-membership.js
66
67
  import { sizeForUncollapse } from "./edit-node-uncollapse.js";
67
68
  import { fastGroupsFilterPropertyNote, isFastGroupsFilterProperty, } from "./rgthree-fast-groups-property.js";
68
69
  import { bindImportedTmpWorkflowUuid, bindLoadedWorkflowIdentity, isPlainObject, isStampMismatchSaveRefusal, openLiveMatchesDestAfterReconnect, openLiveMatchesDestContent, patchOpenIdentity, savedPathFromTabId, shouldRebindOpenIdentity, unsavedTmpWorkflowKey, workflowFromSerializeReply, } from "./open-identity-normalization.js";
70
+ import { contentOnlyRootShapeReadNote, isContentOnlyRootShapeMismatch, recoverContentOnlyGraphQuery, } from "./root-shape-mismatch.js";
69
71
  import { clearSwitchHold, describeSwitchHold, recordSwitchHold, successProvesSwitchCleared, } from "./switch-hold.js";
70
72
  import { NO_ORIGIN_REMEDY } from "./fence-refusal.js";
71
73
  import { completeGetErrorsAudit } from "./get-errors-audit.js";
@@ -5109,6 +5111,120 @@ function stripVerifiedLastObservedSchemaNote(res) {
5109
5111
  delete next.schema_source;
5110
5112
  return rewriteToolResultJson(res, next);
5111
5113
  }
5114
+ // ---- #2545: MiniMaxH3Director duration onValueChange write_warning ----------
5115
+ // The panel assigns duration, verifies the value, then invokes the widget
5116
+ // callback. ComfyUI's settingStore onValueChange throws
5117
+ // `Cannot read properties of undefined (reading 'options')` when editor/options
5118
+ // is missing. That throw is a post-write disclosure, not a failed assignment.
5119
+ // Relaying it as isError (or leaving write_warning as the salient failure)
5120
+ // makes a landed 5→6 write look like a retryable error.
5121
+ function isDirectorDurationCallbackWarning(widget, text) {
5122
+ if (widget.toLowerCase() !== "duration")
5123
+ return false;
5124
+ if (!/reading ['"]options['"]/.test(text))
5125
+ return false;
5126
+ return /onValueChange|settingStore|widget_callback/i.test(text);
5127
+ }
5128
+ function scalarWidgetValuesMatch(requested, observed) {
5129
+ if (Object.is(requested, observed))
5130
+ return true;
5131
+ if ((typeof requested === "number" || typeof requested === "string") &&
5132
+ (typeof observed === "number" || typeof observed === "string")) {
5133
+ return String(requested) === String(observed);
5134
+ }
5135
+ return false;
5136
+ }
5137
+ function recordFromUnknown(value) {
5138
+ if (!value || typeof value !== "object" || Array.isArray(value))
5139
+ return null;
5140
+ return value;
5141
+ }
5142
+ function parseJsonObjectFromToolText(res) {
5143
+ const parsed = parseToolResultJson(res);
5144
+ if (parsed)
5145
+ return parsed;
5146
+ const text = res?.content?.find((c) => c.type === "text")?.text;
5147
+ if (typeof text !== "string")
5148
+ return null;
5149
+ const trimmed = text.replace(/^Error:\s*/i, "").trim();
5150
+ const start = trimmed.indexOf("{");
5151
+ if (start < 0)
5152
+ return null;
5153
+ try {
5154
+ return recordFromUnknown(JSON.parse(trimmed.slice(start)));
5155
+ // unknown-ok: the reply is not JSON; keep the original widget-write result.
5156
+ }
5157
+ catch {
5158
+ return null;
5159
+ }
5160
+ }
5161
+ function stripWriteWarningFields(record) {
5162
+ const next = { ...record };
5163
+ delete next.write_warning;
5164
+ delete next.write_warning_frame;
5165
+ delete next.write_warning_source;
5166
+ return next;
5167
+ }
5168
+ function landedDurationWriteResult(payload, set) {
5169
+ const next = payload
5170
+ ? stripWriteWarningFields({ ...payload, set: stripWriteWarningFields(set) })
5171
+ : { set: stripWriteWarningFields(set) };
5172
+ return ok(next);
5173
+ }
5174
+ async function readNamedWidgetValue(ctx, nodeId, widget) {
5175
+ const probe = await ctx.call({
5176
+ cmd: "graph_query",
5177
+ ids: [nodeId],
5178
+ fields: "detail",
5179
+ limit: 1,
5180
+ max_chars: DYNAMIC_COMBO_PROBE_MAX_CHARS,
5181
+ }, 8000);
5182
+ if (probe.isError)
5183
+ return undefined;
5184
+ const detail = parseVerifiedQueriedNodeDetail(normalizeGraphQueryResult(probe));
5185
+ const requestedId = canonicalQueriedNodeId(nodeId);
5186
+ if (!detail || !requestedId || detail.id !== requestedId || !detail.widgets) {
5187
+ return undefined;
5188
+ }
5189
+ if (!Object.prototype.hasOwnProperty.call(detail.widgets, widget))
5190
+ return undefined;
5191
+ return detail.widgets[widget];
5192
+ }
5193
+ /**
5194
+ * #2545 — a verified Director duration write whose setting-store callback
5195
+ * threw is still a landed write. Swallow that write_warning when `set.value`
5196
+ * matches the request; if the panel reported it as an error without a set
5197
+ * envelope, one graph_query may prove the value. Anything else stays failed.
5198
+ */
5199
+ async function classifyLandedDirectorDurationWriteWarning(ctx, res, nodeId, widget, requested) {
5200
+ const text = textOfToolResult(res);
5201
+ const payload = parseJsonObjectFromToolText(res);
5202
+ const set = recordFromUnknown(payload?.set);
5203
+ const warningText = [
5204
+ typeof set?.write_warning === "string" ? set.write_warning : "",
5205
+ typeof set?.write_warning_frame === "string" ? set.write_warning_frame : "",
5206
+ typeof set?.write_warning_source === "string" ? set.write_warning_source : "",
5207
+ text,
5208
+ ].join("\n");
5209
+ if (!isDirectorDurationCallbackWarning(widget, warningText))
5210
+ return res;
5211
+ if (set && scalarWidgetValuesMatch(requested, set.value)) {
5212
+ return landedDurationWriteResult(payload, set);
5213
+ }
5214
+ if (!res.isError)
5215
+ return res;
5216
+ const observed = await readNamedWidgetValue(ctx, nodeId, widget);
5217
+ if (observed === undefined || !scalarWidgetValuesMatch(requested, observed)) {
5218
+ return res;
5219
+ }
5220
+ return ok({
5221
+ set: {
5222
+ node_id: nodeId,
5223
+ widget,
5224
+ value: observed,
5225
+ },
5226
+ });
5227
+ }
5112
5228
  async function exitSubgraphLevels(ctx, count) {
5113
5229
  const failures = [];
5114
5230
  for (let i = 0; i < count; i++) {
@@ -15872,6 +15988,30 @@ export function buildPanelToolDefs() {
15872
15988
  ? {}
15873
15989
  : { widget_max_chars: widgetMaxChars.value }),
15874
15990
  }, (cmd, timeoutMs) => ctx.call(cmd, timeoutMs));
15991
+ // #2544 — content-only [root-shape-mismatch] after custom-widget / builder
15992
+ // edits (same node count, unsaved live canvas). Inspect the live graph
15993
+ // read-only. Never open or rebind: that would discard the builder work.
15994
+ if (panelReply.isError && isContentOnlyRootShapeMismatch(toolResultText(panelReply))) {
15995
+ const recovered = await recoverContentOnlyGraphQuery((cmd, timeoutMs) => ctx.call(cmd, timeoutMs), {
15996
+ types: args.types,
15997
+ title: args.title,
15998
+ where: args.where,
15999
+ ids: args.ids,
16000
+ upstream_of: args.upstream_of,
16001
+ downstream_of: args.downstream_of,
16002
+ depth: args.depth,
16003
+ fields: args.fields,
16004
+ group_by: args.group_by,
16005
+ limit: args.limit,
16006
+ max_chars: args.max_chars,
16007
+ });
16008
+ if (recovered) {
16009
+ const recoveredReply = ok(recovered);
16010
+ rememberLiveRootViewing(ctx, recovered.viewing);
16011
+ return fitQueryGraphReply(recoveredReply, args.max_chars, widgetMaxChars.note ?? legacyWidgetMaxCharsNote(recoveredReply, widgetMaxChars.value));
16012
+ }
16013
+ return fail(contentOnlyRootShapeReadNote(toolResultText(panelReply)));
16014
+ }
15875
16015
  rememberLiveRootViewing(ctx, parseToolResultJson(panelReply)?.viewing);
15876
16016
  return fitQueryGraphReply(panelReply, args.max_chars, widgetMaxChars.note ?? legacyWidgetMaxCharsNote(panelReply, widgetMaxChars.value));
15877
16017
  }),
@@ -16618,7 +16758,7 @@ export function buildPanelToolDefs() {
16618
16758
  return fail(err);
16619
16759
  }
16620
16760
  }),
16621
- def("panel_connect", "Connect an output slot of one node to an input slot of another in the user's open graph. Slots accept a name ('MODEL', 'samples') or numeric index. If both slot args are omitted the panel picks the first type-compatible pairing. On failure the error lists every slot with its type and [connected] flag — re-check with panel_query_graph ({ids:[node_id], fields:'detail'}). A frontend PrimitiveNode only serializes through a target widget; connecting one to a forceInput-only / non-widget STRING is refused (panel_run would omit the required input) — use a backend STRING producer such as PrimitiveStringMultiline instead. Undoable.", {
16761
+ def("panel_connect", "Connect an output slot of one node to an input slot of another in the user's open graph. Slots accept a name ('MODEL', 'samples') or numeric index. If both slot args are omitted the panel picks the first type-compatible pairing. On failure the error lists every slot with its type and [connected] flag — re-check with panel_query_graph ({ids:[node_id], fields:'detail'}). LiteGraph wildcard-to-wildcard (`*` → `*`) pairings are compatible (a PrimitiveNode 'connect to widget input' output can land on LogicIF.when_true / when_false so the primitive becomes typed from the destination). A frontend PrimitiveNode only serializes through a target widget; connecting one to a forceInput-only / non-widget STRING is refused (panel_run would omit the required input) — use a backend STRING producer such as PrimitiveStringMultiline instead. Undoable.", {
16622
16762
  from_node_id: nodeId().describe("Source node id."),
16623
16763
  from_output: slotRef
16624
16764
  .optional()
@@ -16651,7 +16791,8 @@ export function buildPanelToolDefs() {
16651
16791
  };
16652
16792
  const call = (cmd, timeoutMs) => ctx.call(cmd, timeoutMs);
16653
16793
  const connected = await retryConnectAgainstLiveGraph(connectArgs, call);
16654
- return verifyPrimitiveForceInputAfterConnect(connectArgs, connected, call);
16794
+ const afterWildcard = await retryWildcardSlotConnect(connectArgs, connected, call);
16795
+ return verifyPrimitiveForceInputAfterConnect(connectArgs, afterWildcard, call);
16655
16796
  }),
16656
16797
  def("panel_disconnect", "Disconnect an input slot of a node in the user's open graph. Undoable with Ctrl+Z.", {
16657
16798
  node_id: nodeId().describe("Node id whose input to disconnect."),
@@ -16785,14 +16926,15 @@ export function buildPanelToolDefs() {
16785
16926
  mutationId = rid;
16786
16927
  }, beforeDispatch);
16787
16928
  const echoed = stripVerifiedLastObservedSchemaNote(summarizeSetWidgetEcho(written, echoFull));
16929
+ const classified = await classifyLandedDirectorDurationWriteWarning(ctx, echoed, nodeId, widget, value);
16788
16930
  // #2495 — ONLY a tagged no-reply on a named Power Lora row is settled
16789
16931
  // by a read. An acked executor error is a definite outcome.
16790
- if (isReplyTimeoutResult(echoed) &&
16932
+ if (isReplyTimeoutResult(classified) &&
16791
16933
  isPowerLoraDynamicRowWidget(widget) &&
16792
16934
  args.defer_until_idle !== true) {
16793
- return settlePowerLoraWidgetAfterAckTimeout(ctx, echoed, nodeId, widget, value, mutationId, dispatchTab);
16935
+ return settlePowerLoraWidgetAfterAckTimeout(ctx, classified, nodeId, widget, value, mutationId, dispatchTab);
16794
16936
  }
16795
- return echoed;
16937
+ return classified;
16796
16938
  };
16797
16939
  let writePromotedInner;
16798
16940
  const guardedWrite = async (nodeId, widget, targetExpectedNodeType = expectedNodeType) => {