comfyui-mcp 0.52.131 → 0.52.132

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.
@@ -42,7 +42,7 @@ import { readWorkflowListReadiness, workflowListReadinessOf, } from "../services
42
42
  import { isPreExecutorRefusal } from "../services/panel-refusal.js";
43
43
  import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
44
44
  import { parse as parseYaml } from "yaml";
45
- import { requiredPanelVersion, SEMVER_RE, LATE_ASK_TTL_MS, tabIncarnationSlot, SHOW_MEDIA_KIND_MIN_PANEL_VERSION, showMediaItemsPanelCannotPaint, BRIDGE_CAPABILITY_MIN_PANEL_VERSION, } from "../services/ui-bridge.js";
45
+ import { panelVersionForCapability, requiredPanelVersion, SEMVER_RE, LATE_ASK_TTL_MS, tabIncarnationSlot, SHOW_MEDIA_KIND_MIN_PANEL_VERSION, showMediaItemsPanelCannotPaint, } from "../services/ui-bridge.js";
46
46
  import { compareSemver } from "../services/self-update.js";
47
47
  import { describeInstallPanelAction } from "../services/panel-recovery.js";
48
48
  import { peekResolvedPanelBase, primePanelBase, verifiedPanelDiskVersion, } from "../services/panel-workspace.js";
@@ -5355,12 +5355,71 @@ async function authoritativePromotedScopeError(ctx, expected, innerNodeId) {
5355
5355
  const error = currentPromotedScopeError(ctx, expected, true, observed);
5356
5356
  return { error, observed };
5357
5357
  }
5358
- function promotedWriteRefusal(widget, reason, options) {
5359
- const remedy = options?.capabilityName === "enforces_expected_scope_graph_identity_at_write"
5360
- ? `This session requires panel >= ${BRIDGE_CAPABILITY_MIN_PANEL_VERSION.enforces_expected_scope_graph_identity_at_write} for promoted widget writes. Update your panel, then retry.`
5361
- : `Retry only after the panel binding and subgraph mapping are stable.`;
5358
+ /** A refusal a retry can legitimately clear. Anything caused by the panel BUILD
5359
+ * goes through {@link promotedPanelBuildRefusal} instead — #2365 carved the
5360
+ * first of those out with a `capabilityName` option on this function, and that
5361
+ * option is gone because every capability call site now uses the dedicated
5362
+ * path, which also names the advertised version and a route that works. */
5363
+ function promotedWriteRefusal(widget, reason) {
5362
5364
  return fail(`panel_set_widget refused the promoted "${widget}" write because ${reason}. ` +
5363
- `No graph_set_widget was dispatched; ${remedy}`);
5365
+ `No graph_set_widget was dispatched; retry only after the panel binding and ` +
5366
+ `subgraph mapping are stable.`);
5367
+ }
5368
+ /**
5369
+ * panel#1859 — the same refusal, for the one cause a retry can never reach.
5370
+ *
5371
+ * MCP 0.52.129 shipped the #2314 promoted-write fence; the panel build that
5372
+ * satisfies it shipped twenty minutes later in 0.15.101. Every user in between
5373
+ * got {@link promotedWriteRefusal}'s "retry only after the panel binding and
5374
+ * subgraph mapping are stable" for a condition that is a JS bundle on disk. The
5375
+ * reporter retried five times, re-issued panel_set_workflow_target (which
5376
+ * answered `already_current`), and hard-refreshed the tab; none of that
5377
+ * replaces the bundle, so the loop had no exit.
5378
+ *
5379
+ * So this says what is actually wrong, rules out the three things a caller
5380
+ * would otherwise try next, and names a route to the value that works today.
5381
+ * The refusal itself is unchanged — a pre-0.15.101 panel genuinely cannot
5382
+ * enforce the fence, and loosening it would reopen #2314.
5383
+ */
5384
+ function promotedPanelBuildRefusal(ctx, nodeId, widget, reason, capability) {
5385
+ // A fence capability reads `false` for TWO different facts: this hello did
5386
+ // not advertise it, or resolveTarget threw and there was no hello at all. Only
5387
+ // the first is about the bundle on disk. If the receiver cannot be resolved,
5388
+ // the honest answer is the transient one — telling someone whose tab just
5389
+ // dropped to update and hard-refresh is the same unreachable advice this
5390
+ // whole change exists to remove, and would be worse than what it replaced
5391
+ // because the wording below is emphatic that retrying cannot help.
5392
+ //
5393
+ // Read in the SAME synchronous turn as the capability that sent us here, with
5394
+ // no await between, so run-to-completion means both saw one connection.
5395
+ if (ctx.tabReceiverResolvable?.() !== true) {
5396
+ return promotedWriteRefusal(widget, "the receiving panel could not be resolved while its promoted-write fence " +
5397
+ "capabilities were read, so whether this is a stale binding or an " +
5398
+ "out-of-date panel build is not decidable from here");
5399
+ }
5400
+ const floor = panelVersionForCapability(capability);
5401
+ const advertised = tabAdvertisedPanelVersion(ctx);
5402
+ const update = describeInstallPanelAction("update", "update the ComfyUI-MCP panel via ComfyUI Manager");
5403
+ // Never interpolate the caller's raw node_id: it is untrusted argument shape.
5404
+ // A container id that will not canonicalize is dropped from the sentence
5405
+ // rather than echoed, which leaves the route stated but unparameterized.
5406
+ const canonicalId = canonicalQueriedNodeId(nodeId);
5407
+ const enterArg = canonicalId ? `node_id: ${canonicalId}` : "the container's node_id";
5408
+ return fail(`panel_set_widget refused the promoted "${widget}" write because the connected ` +
5409
+ `panel build ${reason}. No graph_set_widget was dispatched.\n` +
5410
+ `This is a panel BUILD skew, not a transient binding state. Retrying, ` +
5411
+ `panel_set_workflow_target, and reloading the ComfyUI tab all leave the same ` +
5412
+ `bundle running, so none of them can clear it.` +
5413
+ (advertised ? ` This tab announced panel ${advertised}.` : "") +
5414
+ ` A promoted-container write needs ${floor ? `panel ≥${floor}` : "a newer panel build"}. ` +
5415
+ `${update}, then HARD-REFRESH the browser tab (Ctrl+Shift+R, or Cmd+Shift+R on ` +
5416
+ `macOS); a restart alone leaves the tab running cached old JS.\n` +
5417
+ `Until then the value is still reachable by hand: ` +
5418
+ `panel_enter_subgraph(${enterArg}), then panel_set_widget on the ` +
5419
+ `inner node that owns "${widget}". If the write does not stick because the ` +
5420
+ `container's promoted rail holds the value, un-promote it first ` +
5421
+ `(panel_promote_widget with demote:true), write the inner node, and re-promote — ` +
5422
+ `that is the sequence panel#1859 reported as clearing it.`);
5364
5423
  }
5365
5424
  /**
5366
5425
  * #2314 — inspect a promoted target before the first write. A valid mapping is
@@ -5448,13 +5507,19 @@ async function preparePromotedWidgetWrite(ctx, nodeId, widget) {
5448
5507
  if (!envelope) {
5449
5508
  return promotedWriteRefusal(widget, "graph_get_subgraph returned a malformed, stale, or incomplete ownership envelope");
5450
5509
  }
5451
- // Check for required scope-identity fence capability early: if the panel doesn't
5452
- // support it, the scope witness cannot be extracted, and the real issue is the panel
5453
- // version, not a transient staleness. This guard comes before scope extraction so the
5454
- // error message correctly names the constraint (missing capability) rather than the
5455
- // symptom (missing viewing field).
5510
+ // panel#1859 — ask the HELLO before reading the witness out of the envelope.
5511
+ // A build that never advertised this fence also never writes
5512
+ // `subgraph_of.graph_identity` (both landed in panel 0.15.101; v0.15.85
5513
+ // replies with a bare `subgraph_of: { node_id, title }`), so diagnosing it as
5514
+ // a missing field describes the symptom while the cause is the bundle on
5515
+ // disk. This ordering is #2365's idea, kept.
5516
+ //
5517
+ // It also narrows the branch below to its true meaning: a receiver that DID
5518
+ // advertise the fence and still failed to publish the identity is an
5519
+ // inconsistent reply, which a retry can legitimately clear.
5456
5520
  if (ctx.tabExpectedScopeGraphIdentityFenceCapability?.() !== true) {
5457
- return promotedWriteRefusal(widget, "the receiving panel lacks the atomic promoted graph-identity write fence", { capabilityName: "enforces_expected_scope_graph_identity_at_write" });
5521
+ return promotedPanelBuildRefusal(ctx, nodeId, widget, "does not advertise the atomic promoted graph-identity write fence, so it " +
5522
+ "cannot publish the target graph identity a promoted write is fenced on", "enforces_expected_scope_graph_identity_at_write");
5458
5523
  }
5459
5524
  const scope = promotedScopeWitnessFromEnvelope(envelope);
5460
5525
  if (!scope) {
@@ -5512,16 +5577,20 @@ async function preparePromotedWidgetWrite(ctx, nodeId, widget) {
5512
5577
  if (terminalBlocked)
5513
5578
  return terminalBlocked;
5514
5579
  }
5580
+ // panel#1859 — these three are capability skew, exactly like the missing
5581
+ // graph identity above: the hello either advertises the fence or it does not,
5582
+ // and a caller cannot change that by trying again.
5515
5583
  if (ctx.tabExpectedNodeTypeFenceCapability?.() !== true) {
5516
- return promotedWriteRefusal(widget, "the receiving panel lacks the atomic inner-node write fence");
5584
+ return promotedPanelBuildRefusal(ctx, nodeId, widget, "does not advertise the atomic inner-node write fence", "enforces_expected_node_type_at_write");
5517
5585
  }
5518
- // Scope graph-identity fence capability was checked earlier (before scope extraction).
5519
- // If we reach this point, the capability was confirmed present. Legacy panels do not publish a parent-rail witness and retain the
5586
+ // The graph-identity fence is checked before the scope witness above (#2365),
5587
+ // so reaching here means the hello advertised it.
5588
+ // Legacy panels do not publish a parent-rail witness and retain the
5520
5589
  // established same-name recovery contract. A current witness-capable panel
5521
5590
  // must advertise the synchronous final rail re-resolution before MCP sends
5522
5591
  // the inner/shared-subgraph write.
5523
5592
  if (publishesCompleteTerminalWitness && ctx.tabPromotedParentRailFenceCapability?.() !== true) {
5524
- return promotedWriteRefusal(widget, "the receiving panel lacks the atomic promoted parent-rail write fence");
5593
+ return promotedPanelBuildRefusal(ctx, nodeId, widget, "does not advertise the atomic promoted parent-rail write fence", "enforces_promoted_parent_rail_at_write");
5525
5594
  }
5526
5595
  return {
5527
5596
  kind: "promoted-write",
@@ -11884,6 +11953,11 @@ export function makePanelToolCtx(bridge, tabId, workflowTargets, onRunTicketOpen
11884
11953
  bridge.tabPromotedTerminalWitnessCapability(ctx.tabId);
11885
11954
  ctx.tabPromotedParentRailFenceCapability = () => typeof bridge.tabPromotedParentRailFenceCapability === "function" &&
11886
11955
  bridge.tabPromotedParentRailFenceCapability(ctx.tabId);
11956
+ // Absent on a bridge that does not implement it, which reads as "cannot
11957
+ // prove the receiver is there" — the conservative direction, since it only
11958
+ // ever softens a refusal's wording away from blaming the panel build.
11959
+ ctx.tabReceiverResolvable = () => typeof bridge.tabReceiverResolvable === "function" &&
11960
+ bridge.tabReceiverResolvable(ctx.tabId);
11887
11961
  ctx.tabGraphMutationCapability = () => bridge.tabGraphMutationCapability(ctx.tabId);
11888
11962
  return ctx;
11889
11963
  }