comfyui-mcp 0.52.85 → 0.52.87

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/README.md CHANGED
@@ -610,6 +610,7 @@ npx -y comfyui-mcp@latest --comfyui-url http://localhost:8188 --force-remote
610
610
  | `COMFYUI_AUTH_SCHEME` | `Bearer` for `Authorization`, else none | Scheme prefix on the token value (e.g. `Bearer`, `Token`). |
611
611
  | `CIVITAI_API_TOKEN` | | CivitAI API token for model downloads |
612
612
  | `HUGGINGFACE_TOKEN` | | HuggingFace token for higher API rate limits |
613
+ | `COMFYUI_DOWNLOAD_PROXY` | | Optional HTTP/HTTPS proxy for model-download traffic only; standard proxy environment variables and static Windows WinINet settings are also honored |
613
614
  | `GITHUB_TOKEN` | | GitHub token for skill generation (avoids rate limits) |
614
615
  | `REGISTRY_ACCESS_TOKEN` | | Comfy Registry API key for `node_pack` (`action: "publish"`) (env-only, never logged) |
615
616
  | `COMFYUI_DOWNLOAD_CACHE_DIR` | `~/.comfyui-mcp/cache` | Content-addressed model-download cache (dedup + concurrent coalescing) |
@@ -5533,10 +5533,6 @@ function formatRunRejection(payload) {
5533
5533
  * overrides the flag-based rule and #213 does not regress: that reply had no
5534
5534
  * prompt id. The same asymmetry the retry guard already relies on.
5535
5535
  */
5536
- function acceptedPromptId(parsed) {
5537
- const pid = parsed?.prompt_id;
5538
- return typeof pid === "string" && pid.trim() !== "" ? pid.trim() : null;
5539
- }
5540
5536
  /**
5541
5537
  * EVERY prompt id a graph_run reply minted, first one first (#949).
5542
5538
  *
@@ -5548,6 +5544,8 @@ function acceptedPromptId(parsed) {
5548
5544
  *
5549
5545
  * Deduped and order-preserving: `prompt_id` normally repeats the first entry of
5550
5546
  * `prompt_ids`, and a duplicate would open two tickets for one render.
5547
+ * The Panel's scoped-run uncertainty receipt uses `queued_prompt_ids` for the
5548
+ * subset it did confirm (#2143); those are receipts too and must be ticketed.
5551
5549
  */
5552
5550
  export function acceptedPromptIds(parsed) {
5553
5551
  const out = [];
@@ -5563,8 +5561,24 @@ export function acceptedPromptIds(parsed) {
5563
5561
  if (Array.isArray(many))
5564
5562
  for (const v of many)
5565
5563
  add(v);
5564
+ const queued = parsed?.queued_prompt_ids;
5565
+ if (Array.isArray(queued))
5566
+ for (const v of queued)
5567
+ add(v);
5566
5568
  return out;
5567
5569
  }
5570
+ function acceptedPromptId(parsed) {
5571
+ return acceptedPromptIds(parsed)[0] ?? null;
5572
+ }
5573
+ /**
5574
+ * The Panel's explicit scoped-run uncertainty contract (#2143). This is a
5575
+ * normal, parseable receipt rather than a transport failure: the Panel knows
5576
+ * that one or more requests left the tab but cannot prove whether ComfyUI
5577
+ * accepted them. Do not reinterpret its `error` field as a refusal.
5578
+ */
5579
+ function isPanelQueueUncertainty(parsed) {
5580
+ return parsed?.queued_unknown === true;
5581
+ }
5568
5582
  function detectRunRejection(res) {
5569
5583
  // Bridge/transport/executor error: never a queue. Preserve it verbatim (#248),
5570
5584
  // no success note (#331). fail() already carries err.message (incl. any stack).
@@ -5573,6 +5587,12 @@ function detectRunRejection(res) {
5573
5587
  const parsed = parseToolResultJson(res);
5574
5588
  if (!parsed)
5575
5589
  return null; // unparseable non-error reply — don't regress a success
5590
+ // #2143 — `queued_unknown` is an explicit Panel receipt, not the ComfyUI
5591
+ // rejection channel. Its `error` field explains the timeout and its
5592
+ // `retry_guidance` tells the caller how to settle it; keep the receipt intact
5593
+ // and never redispatch from here.
5594
+ if (isPanelQueueUncertainty(parsed))
5595
+ return null;
5576
5596
  const topError = parsed.error;
5577
5597
  const nodeErrors = parsed.node_errors;
5578
5598
  const rejected = hasTopLevelError(topError) || hasNodeErrors(nodeErrors) || parsed.queued === false;
@@ -5687,8 +5707,8 @@ function isRetryableRunToNodeStampRace(res, rejection) {
5687
5707
  return false; // unreadable reply — an unknown, not a clean queue
5688
5708
  if (parsed.queued === true)
5689
5709
  return false; // observed queue evidence vetoes prose
5690
- if (typeof parsed.prompt_id === "string" && parsed.prompt_id.trim() !== "")
5691
- return false;
5710
+ if (acceptedPromptIds(parsed).length > 0)
5711
+ return false; // any receipt vetoes retry
5692
5712
  const text = toolResultText(rejection);
5693
5713
  return (/workflow graph CHANGED after the run was queued/i.test(text) &&
5694
5714
  /Nothing was queued/i.test(text));
@@ -11160,6 +11180,22 @@ function queueRunReceiptNote(promptId) {
11160
11180
  `Nothing was dispatched a second time. Do NOT re-run: a second panel_run would bill/queue ` +
11161
11181
  `another render behind the one already running.`);
11162
11182
  }
11183
+ /**
11184
+ * #2143 — a normal Panel timeout receipt is neither a confirmed queue nor a
11185
+ * refusal. Keep its retry guidance in the original JSON and add only the
11186
+ * local fact that this handler did not dispatch a second run.
11187
+ */
11188
+ function panelQueueUncertaintyNote(queuedPromptIds) {
11189
+ if (queuedPromptIds.length > 0) {
11190
+ return (`\n\n[UNCERTAIN] The Panel confirmed these prompt id(s) left its queue path: ` +
11191
+ `${queuedPromptIds.join(", ")}. Completion tickets were opened for them. ` +
11192
+ `Any remaining request(s) are still uncertain; follow the Panel's retry_guidance above ` +
11193
+ `before retrying. No second panel_run was dispatched.`);
11194
+ }
11195
+ return (`\n\n[UNCERTAIN] The Panel could not determine whether this run entered ComfyUI's queue. ` +
11196
+ `This is not a confirmed refusal, and no completion ticket can be opened without a prompt id. ` +
11197
+ `Follow the Panel's retry_guidance above before retrying. No second panel_run was dispatched.`);
11198
+ }
11163
11199
  async function pollLateAskReply(bridge, askId, timing, hardDeadline) {
11164
11200
  const take = bridge
11165
11201
  .takeLateAskReply;
@@ -13414,7 +13450,7 @@ export function buildPanelToolDefs() {
13414
13450
  // "does NOT match any run you queued … its origin is UNDETERMINED" — for
13415
13451
  // runs the agent had just queued itself. Ticket all of them.
13416
13452
  const queuedIds = acceptedPromptIds(runReply);
13417
- const queuedId = queuedIds[0];
13453
+ const panelQueueUncertain = isPanelQueueUncertainty(runReply);
13418
13454
  // #944: a run ComfyUI accepted while dropping some outputs reaches here
13419
13455
  // (a minted prompt id outranks the rejection signals). Say which outputs
13420
13456
  // were dropped, or the caller waits for files that will never be written.
@@ -13435,8 +13471,11 @@ export function buildPanelToolDefs() {
13435
13471
  `and this dispatch stacked a duplicate.`
13436
13472
  : "";
13437
13473
  // markSelfQueued with NO id still stamps the recent-self-queue timestamp,
13438
- // so the id-less case must still call it exactly once (#559).
13439
- if (queuedIds.length === 0)
13474
+ // so both the id-less genuine queue and the Panel's explicit uncertainty
13475
+ // receipt must call it exactly once (#559/#2143). The latter must not be
13476
+ // mistaken for a definite queue, but it is still unsafe to immediately
13477
+ // redispatch a request that the Panel says left its tab.
13478
+ if (queuedIds.length === 0 || panelQueueUncertain)
13440
13479
  QueueMonitor.markSelfQueued(null);
13441
13480
  for (const id of queuedIds)
13442
13481
  QueueMonitor.markSelfQueued(id);
@@ -13460,14 +13499,16 @@ export function buildPanelToolDefs() {
13460
13499
  // anti-poll note is allowed to make — true only if at least one run can
13461
13500
  // actually be correlated back.
13462
13501
  const ticketedPromptIds = [];
13463
- const correlatable = queuedIds.length === 0
13464
- ? RunCompletions.openRun(undefined, ticketOpts)
13465
- : queuedIds.map((id) => {
13466
- const opened = RunCompletions.openRun(id, ticketOpts);
13467
- if (opened)
13468
- ticketedPromptIds.push(id);
13469
- return opened;
13470
- }).some(Boolean);
13502
+ const correlatable = panelQueueUncertain && queuedIds.length === 0
13503
+ ? false
13504
+ : queuedIds.length === 0
13505
+ ? RunCompletions.openRun(undefined, ticketOpts)
13506
+ : queuedIds.map((id) => {
13507
+ const opened = RunCompletions.openRun(id, ticketOpts);
13508
+ if (opened)
13509
+ ticketedPromptIds.push(id);
13510
+ return opened;
13511
+ }).some(Boolean);
13471
13512
  // A fast QueueMonitor observation can be held before the panel reply
13472
13513
  // returns. Promote that in-memory arm immediately after its journal
13473
13514
  // ticket opens, so a completion burst cannot evict this owed run.
@@ -13521,10 +13562,12 @@ export function buildPanelToolDefs() {
13521
13562
  const fallbackTail = queuedIds.length === 1
13522
13563
  ? ""
13523
13564
  : ` (this run queued ${queuedIds.length} prompts, so the check is the queue as a whole; get_history settles the outcome of any that are no longer in flight)`;
13524
- const note = correlatable
13525
- ? "\n\n[IMPORTANT] You will be notified automatically with the output image(s)/video when the render finishes — do NOT poll queue (action:\"list\"), get_history, or get_image (action:\"list_outputs\"). Just end your turn now and wait for the result to be delivered to you." +
13526
- `\n[FALLBACK] That notification is journaled and replayed if an agent is not there to take it, and the orchestrator will fill one in from ComfyUI itself — its execution event or its history record — if the panel never reports the finish. But it cannot be GUARANTEED, so do not wait forever on it. If nothing has arrived LONG after this render should have finished (roughly twice the time you expect it to take), make ONE check with ${fallbackCheck} instead of idling indefinitely${fallbackTail}. One check, not a loop — and not before then.`
13527
- : "\n\n[IMPORTANT] The run was queued, but the panel reported NO prompt id for it, so a completion event CANNOT be correlated back to this run its outcome will be reported to you as UNDETERMINED. Do NOT simply idle and wait indefinitely: end your turn, and if nothing arrives, confirm the outcome with get_history (action:\"list\") before acting on it.";
13565
+ const note = panelQueueUncertain
13566
+ ? panelQueueUncertaintyNote(ticketedPromptIds)
13567
+ : correlatable
13568
+ ? "\n\n[IMPORTANT] You will be notified automatically with the output image(s)/video when the render finishesdo NOT poll queue (action:\"list\"), get_history, or get_image (action:\"list_outputs\"). Just end your turn now and wait for the result to be delivered to you." +
13569
+ `\n[FALLBACK] That notification is journaled and replayed if an agent is not there to take it, and the orchestrator will fill one in from ComfyUI itself — its execution event or its history record — if the panel never reports the finish. But it cannot be GUARANTEED, so do not wait forever on it. If nothing has arrived LONG after this render should have finished (roughly twice the time you expect it to take), make ONE check with ${fallbackCheck} instead of idling indefinitely${fallbackTail}. One check, not a loop — and not before then.`
13570
+ : "\n\n[IMPORTANT] The run was queued, but the panel reported NO prompt id for it, so a completion event CANNOT be correlated back to this run — its outcome will be reported to you as UNDETERMINED. Do NOT simply idle and wait indefinitely: end your turn, and if nothing arrives, confirm the outcome with get_history (action:\"list\") before acting on it.";
13528
13571
  // Backpressure note. A backlog is only alarming when it's a job we did NOT
13529
13572
  // queue (possibly foreign/stuck). Deliberately batching renders — a sweep,
13530
13573
  // a multi-variant comparison — is a NORMAL workflow, so a queue made of our